From 1f9263d69cb7ce2960c514c45ccca9ff85e67d0a Mon Sep 17 00:00:00 2001 From: hanlu Date: Mon, 8 Aug 2022 16:42:01 +0800 Subject: [PATCH 1/3] fix Signed-off-by: hanlu --- CMakeLists.txt | 1 + data_share/CMakeLists.txt | 1 + kv_store/CMakeLists.txt | 1 + mock/CMakeLists.txt | 1 + mock/innerkits/distributeddatamgr/dfx/reporter.h | 2 +- mock/src/mock_reporter.cpp | 2 +- preferences/CMakeLists.txt | 3 ++- relational_store/CMakeLists.txt | 1 + utils_native/CMakeLists.txt | 4 +++- 9 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22289d51..3619645d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ set(CMAKE_CXX_FLAGS "-std=c++1y -fno-rtti -fvisibility=default -D_GNU_SOURCE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -fPIC -fpic -ffunction-sections -D_GLIBC_MOCK") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-as-needed -ldl") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wimplicit-fallthrough") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=0") add_subdirectory(googletest) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/utils_native/base/include) diff --git a/data_share/CMakeLists.txt b/data_share/CMakeLists.txt index 39b62fe3..014e8074 100644 --- a/data_share/CMakeLists.txt +++ b/data_share/CMakeLists.txt @@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_FLAGS "-std=c++1y -fno-rtti -fvisibility=default -D_GNU_SOURCE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -fPIC -fpic -ffunction-sections -D_GLIBC_MOCK") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-as-needed -ldl") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=0") set(MOCK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../mock) add_definitions(-DNAPI_EXPERIMENTAL) diff --git a/kv_store/CMakeLists.txt b/kv_store/CMakeLists.txt index 6df8e63a..0350156c 100644 --- a/kv_store/CMakeLists.txt +++ b/kv_store/CMakeLists.txt @@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_FLAGS "-std=c++1y -fno-rtti -fvisibility=default -D_GNU_SOURCE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -fPIC -fpic -ffunction-sections -D_GLIBC_MOCK") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-as-needed -ldl") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=0") set(MOCK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../mock) #aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/frameworks/native/kv_store/src/kvstore_common kv_store_src) diff --git a/mock/CMakeLists.txt b/mock/CMakeLists.txt index afb87cf8..92bb7858 100644 --- a/mock/CMakeLists.txt +++ b/mock/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_CXX_FLAGS "-std=c++1y -fno-rtti -fvisibility=default -D_GNU_SOURCE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -fpic -fdata-sections -ffunction-sections -D_GLIBC_MOCK") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-Bsymbolic -Wl,--no-as-needed -ldl") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-macro-redefined -Wno-constant-conversion -Wno-sign-compare") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=0") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-incompatible-pointer-types") add_definitions(-DNDEBUG=1 -DHAVE_USLEEP=1 -DSQLITE_HAVE_ISNAN -DSQLITE_DEFAULT_JOURNAL_SIZE_LIMIT=1048576) add_definitions(-DSQLITE_THREADSAFE=2 -DSQLITE_TEMP_STORE=3 -DSQLITE_POWERSAFE_OVERWRITE=1) diff --git a/mock/innerkits/distributeddatamgr/dfx/reporter.h b/mock/innerkits/distributeddatamgr/dfx/reporter.h index 8adb5bd6..4c286e9d 100644 --- a/mock/innerkits/distributeddatamgr/dfx/reporter.h +++ b/mock/innerkits/distributeddatamgr/dfx/reporter.h @@ -38,7 +38,7 @@ public: KVSTORE_API StatisticReporter* TrafficStatistic(); KVSTORE_API StatisticReporter* ApiPerformanceStatistic(); - KVSTORE_API BehaviourReporter* BehaviourReporter(); + KVSTORE_API BehaviourReporter* GetBehaviourReporter(); }; } // namespace DistributedDataDfx } // namespace OHOS diff --git a/mock/src/mock_reporter.cpp b/mock/src/mock_reporter.cpp index e33ae53f..df4ccf03 100644 --- a/mock/src/mock_reporter.cpp +++ b/mock/src/mock_reporter.cpp @@ -110,7 +110,7 @@ StatisticReporter *Reporter::ApiPerformanceStatistic() return &reporter; } -BehaviourReporter *Reporter::BehaviourReporter() +BehaviourReporter *Reporter::GetBehaviourReporter() { class IBehaviourReporter : public BehaviourReporter { public: diff --git a/preferences/CMakeLists.txt b/preferences/CMakeLists.txt index 8d8d6a0c..262041bf 100644 --- a/preferences/CMakeLists.txt +++ b/preferences/CMakeLists.txt @@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_FLAGS "-std=c++1y -fno-rtti -fvisibility=default -D_GNU_SOURCE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -fPIC -fpic -ffunction-sections -D_GLIBC_MOCK") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-as-needed -ldl") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=0") set(MOCK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../mock) add_definitions(-DNAPI_EXPERIMENTAL) @@ -18,6 +19,6 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../utils_native/base/include) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../utils_native/safwk/native/include) include(${MOCK_DIR}/include/CMakeLists.txt OPTIONAL) -set(links secure mock relational_store) +set(links secure mock relational_store xml2) add_library(preferences SHARED ${preferences_src}) target_link_libraries(preferences ${links}) \ No newline at end of file diff --git a/relational_store/CMakeLists.txt b/relational_store/CMakeLists.txt index ed590cca..915232eb 100644 --- a/relational_store/CMakeLists.txt +++ b/relational_store/CMakeLists.txt @@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_FLAGS "-std=c++1y -fno-rtti -fvisibility=default -D_GNU_SOURCE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -fPIC -fpic -ffunction-sections -D_GLIBC_MOCK") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-as-needed -ldl") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=0") set(MOCK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../mock) add_definitions(-DNAPI_EXPERIMENTAL) diff --git a/utils_native/CMakeLists.txt b/utils_native/CMakeLists.txt index 7b9fce55..230865d8 100644 --- a/utils_native/CMakeLists.txt +++ b/utils_native/CMakeLists.txt @@ -9,4 +9,6 @@ aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/base/src/securec secureSrc) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/base/include) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/base/src) -add_library(secure SHARED ${secureSrc}) \ No newline at end of file +set(links rt) +add_library(secure SHARED ${secureSrc}) +target_link_libraries(secure ${links}) \ No newline at end of file -- Gitee From 7e3257ef5cbd90d92499a7a999068e04a6462125 Mon Sep 17 00:00:00 2001 From: hanlu Date: Mon, 8 Aug 2022 21:16:15 +0800 Subject: [PATCH 2/3] fix Signed-off-by: hanlu --- CMakeLists.txt | 4 +- mock/CMakeLists.txt | 1 - mock/distributeddb/BUILD.gn | 280 ++ mock/distributeddb/CMakeLists.txt | 51 + .../common/include/auto_launch.h | 185 ++ .../common/include/data_compression.h | 48 + .../distributeddb/common/include/data_value.h | 98 + mock/distributeddb/common/include/db_common.h | 71 + .../common/include/db_constant.h | 140 + .../common/include/db_dfx_adapter.h | 62 + .../common/include/db_dump_helper.h | 26 + mock/distributeddb/common/include/db_errno.h | 155 ++ mock/distributeddb/common/include/db_types.h | 140 + .../common/include/endian_convert.h | 55 + mock/distributeddb/common/include/hash.h | 31 + mock/distributeddb/common/include/ischema.h | 61 + .../common/include/json_object.h | 131 + mock/distributeddb/common/include/log_print.h | 57 + .../common/include/macro_utils.h | 45 + .../common/include/notification_chain.h | 143 + .../common/include/param_check_utils.h | 58 + mock/distributeddb/common/include/parcel.h | 233 ++ .../common/include/performance_analysis.h | 123 + .../common/include/platform_specific.h | 78 + .../distributeddb/common/include/ref_object.h | 80 + .../relational/relational_schema_object.h | 151 ++ .../common/include/res_finalizer.h | 41 + .../common/include/runtime_context.h | 137 + .../common/include/schema_constant.h | 68 + .../common/include/schema_negotiate.h | 64 + .../common/include/schema_object.h | 210 ++ .../common/include/schema_utils.h | 77 + .../common/include/semaphore_utils.h | 48 + mock/distributeddb/common/include/task_pool.h | 54 + .../common/include/user_change_monitor.h | 58 + .../common/include/value_hash_calc.h | 88 + .../common/include/value_object.h | 75 + mock/distributeddb/common/include/version.h | 72 + .../common/include/zlib_compression.h | 37 + mock/distributeddb/common/src/auto_launch.cpp | 1261 +++++++++ .../common/src/data_compression.cpp | 69 + mock/distributeddb/common/src/data_value.cpp | 368 +++ mock/distributeddb/common/src/db_common.cpp | 336 +++ mock/distributeddb/common/src/db_constant.cpp | 70 + .../common/src/db_dfx_adapter.cpp | 157 ++ .../common/src/db_dump_helper.cpp | 32 + .../common/src/evloop/include/event_fd.h | 74 + .../common/src/evloop/include/ievent.h | 58 + .../common/src/evloop/include/ievent_loop.h | 49 + .../common/src/evloop/src/event_impl.cpp | 380 +++ .../common/src/evloop/src/event_impl.h | 76 + .../src/evloop/src/event_loop_epoll.cpp | 287 ++ .../common/src/evloop/src/event_loop_epoll.h | 57 + .../common/src/evloop/src/event_loop_impl.cpp | 590 +++++ .../common/src/evloop/src/event_loop_impl.h | 85 + .../src/evloop/src/event_loop_select.cpp | 87 + .../common/src/evloop/src/event_loop_select.h | 49 + .../common/src/evloop/src/ievent.cpp | 59 + .../common/src/evloop/src/ievent_loop.cpp | 43 + .../common/src/flatbuffer_schema.cpp | 1005 +++++++ mock/distributeddb/common/src/hash.cpp | 46 + mock/distributeddb/common/src/json_object.cpp | 932 +++++++ .../common/src/lock_status_observer.cpp | 109 + .../common/src/lock_status_observer.h | 42 + mock/distributeddb/common/src/log_print.cpp | 132 + .../common/src/notification_chain.cpp | 337 +++ .../common/src/param_check_utils.cpp | 210 ++ mock/distributeddb/common/src/parcel.cpp | 541 ++++ .../common/src/performance_analysis.cpp | 228 ++ .../common/src/platform_specific.cpp | 328 +++ mock/distributeddb/common/src/query.cpp | 152 ++ .../common/src/query_expression.cpp | 278 ++ mock/distributeddb/common/src/ref_object.cpp | 198 ++ .../relational/relational_schema_object.cpp | 755 ++++++ .../common/src/runtime_context.cpp | 40 + .../common/src/runtime_context_impl.cpp | 673 +++++ .../common/src/runtime_context_impl.h | 179 ++ .../common/src/schema_constant.cpp | 61 + .../common/src/schema_negotiate.cpp | 247 ++ .../common/src/schema_object.cpp | 1152 ++++++++ .../distributeddb/common/src/schema_utils.cpp | 502 ++++ .../common/src/semaphore_utils.cpp | 62 + mock/distributeddb/common/src/task_pool.cpp | 41 + .../common/src/task_pool_impl.cpp | 297 +++ .../distributeddb/common/src/task_pool_impl.h | 85 + mock/distributeddb/common/src/task_queue.cpp | 75 + mock/distributeddb/common/src/task_queue.h | 40 + .../common/src/time_tick_monitor.cpp | 168 ++ .../common/src/time_tick_monitor.h | 72 + .../distributeddb/common/src/types_export.cpp | 81 + .../common/src/user_change_monitor.cpp | 123 + .../distributeddb/common/src/value_object.cpp | 172 ++ .../common/src/zlib_compression.cpp | 82 + .../communicator/include/combine_status.h | 55 + .../include/communicator_aggregator.h | 181 ++ .../include/communicator_type_define.h | 73 + .../communicator/include/frame_combiner.h | 79 + .../communicator/include/frame_retainer.h | 81 + .../communicator/include/iadapter.h | 79 + .../communicator/include/icommunicator.h | 90 + .../include/icommunicator_aggregator.h | 54 + .../communicator/include/message.h | 219 ++ .../communicator/include/message_transform.h | 45 + .../communicator/include/network_adapter.h | 95 + .../communicator/include/object_holder.h | 26 + .../include/object_holder_typed.h | 48 + .../communicator/include/parse_result.h | 175 ++ .../include/send_task_scheduler.h | 102 + .../communicator/src/combine_status.cpp | 81 + .../communicator/src/communicator.cpp | 272 ++ .../communicator/src/communicator.h | 93 + .../src/communicator_aggregator.cpp | 888 +++++++ .../communicator/src/communicator_linker.cpp | 457 ++++ .../communicator/src/communicator_linker.h | 122 + .../communicator/src/frame_combiner.cpp | 256 ++ .../communicator/src/frame_header.h | 73 + .../communicator/src/frame_retainer.cpp | 244 ++ .../communicator/src/header_converter.cpp | 82 + .../communicator/src/header_converter.h | 36 + .../communicator/src/message_transform.cpp | 24 + .../communicator/src/network_adapter.cpp | 406 +++ .../communicator/src/protocol_proto.cpp | 1083 ++++++++ .../communicator/src/protocol_proto.h | 137 + .../communicator/src/send_task_scheduler.cpp | 285 ++ .../communicator/src/serial_buffer.cpp | 262 ++ .../communicator/src/serial_buffer.h | 79 + .../include/auto_launch_export.h | 58 + mock/distributeddb/include/query.h | 166 ++ mock/distributeddb/include/query_expression.h | 187 ++ mock/distributeddb/include/types_export.h | 179 ++ .../interfaces/include/get_query_info.h | 30 + .../interfaces/include/intercepted_data.h | 48 + .../include/iprocess_communicator.h | 170 ++ .../include/iprocess_system_api_adapter.h | 56 + .../include/kv_store_changed_data.h | 39 + .../interfaces/include/kv_store_delegate.h | 108 + .../include/kv_store_delegate_manager.h | 132 + .../interfaces/include/kv_store_errno.h | 25 + .../include/kv_store_nb_conflict_data.h | 50 + .../interfaces/include/kv_store_nb_delegate.h | 221 ++ .../interfaces/include/kv_store_observer.h | 31 + .../interfaces/include/kv_store_result_set.h | 69 + .../include/kv_store_snapshot_delegate.h | 40 + .../relational/relational_store_delegate.h | 45 + .../relational/relational_store_manager.h | 54 + .../relational/relational_store_sqlite_ext.h | 51 + .../include/relational/runtime_config.h | 47 + .../include/relational/store_changed_data.h | 41 + .../include/relational/store_observer.h | 31 + .../interfaces/include/store_types.h | 124 + .../interfaces/src/intercepted_data_impl.cpp | 189 ++ .../interfaces/src/intercepted_data_impl.h | 56 + .../src/kv_store_changed_data_impl.cpp | 66 + .../src/kv_store_changed_data_impl.h | 50 + .../interfaces/src/kv_store_delegate_impl.cpp | 463 ++++ .../interfaces/src/kv_store_delegate_impl.h | 116 + .../src/kv_store_delegate_manager.cpp | 614 +++++ .../interfaces/src/kv_store_errno.cpp | 72 + .../src/kv_store_nb_conflict_data_impl.cpp | 70 + .../src/kv_store_nb_conflict_data_impl.h | 46 + .../src/kv_store_nb_delegate_impl.cpp | 917 +++++++ .../src/kv_store_nb_delegate_impl.h | 170 ++ .../src/kv_store_result_set_impl.cpp | 172 ++ .../interfaces/src/kv_store_result_set_impl.h | 82 + .../src/kv_store_snapshot_delegate_impl.cpp | 89 + .../src/kv_store_snapshot_delegate_impl.h | 54 + .../relational_store_changed_data_impl.cpp | 42 + .../relational_store_changed_data_impl.h | 44 + .../relational_store_delegate_impl.cpp | 154 ++ .../relational_store_delegate_impl.h | 56 + .../relational/relational_store_instance.h | 65 + .../relational/relational_store_manager.cpp | 152 ++ .../relational_store_sqlite_ext.cpp | 430 +++ .../relational/relational_sync_able_storage.h | 147 ++ .../src/relational/runtime_config.cpp | 93 + .../storage/include/db_properties.h | 65 + .../storage/include/iconnection.h | 40 + mock/distributeddb/storage/include/ikvdb.h | 67 + .../storage/include/ikvdb_connection.h | 133 + .../storage/include/ikvdb_factory.h | 64 + .../storage/include/ikvdb_result_set.h | 52 + .../storage/include/ikvdb_snapshot.h | 35 + .../storage/include/ikvdb_sync_interface.h | 35 + .../storage/include/isync_interface.h | 73 + .../storage/include/kvdb_commit_notify_data.h | 57 + .../storage/include/kvdb_conflict_entry.h | 36 + .../storage/include/kvdb_manager.h | 148 ++ .../storage/include/kvdb_pragma.h | 105 + .../storage/include/kvdb_properties.h | 84 + .../storage/include/multi_ver_def.h | 80 + .../include/multi_ver_kvdb_sync_interface.h | 73 + .../storage/include/multi_ver_vacuum.h | 152 ++ .../include/multi_ver_vacuum_executor.h | 82 + .../include/relational_db_sync_interface.h | 50 + .../include/relational_store_connection.h | 75 + .../storage/include/relationaldb_properties.h | 45 + .../storage/include/single_ver_kv_entry.h | 53 + .../include/single_ver_kvdb_sync_interface.h | 39 + .../storage/include/storage_engine_manager.h | 84 + .../storage/include/sync_generic_interface.h | 149 ++ .../storage/src/data_transformer.cpp | 338 +++ .../storage/src/data_transformer.h | 82 + .../storage/src/db_properties.cpp | 83 + .../storage/src/default_factory.cpp | 96 + .../storage/src/default_factory.h | 53 + .../storage/src/generic_kvdb.cpp | 415 +++ mock/distributeddb/storage/src/generic_kvdb.h | 198 ++ .../storage/src/generic_kvdb_connection.cpp | 349 +++ .../storage/src/generic_kvdb_connection.h | 114 + .../src/generic_single_ver_kv_entry.cpp | 462 ++++ .../storage/src/generic_single_ver_kv_entry.h | 111 + .../distributeddb/storage/src/iconnection.cpp | 37 + mock/distributeddb/storage/src/ikvdb_commit.h | 43 + .../storage/src/ikvdb_commit_storage.h | 65 + .../storage/src/ikvdb_factory.cpp | 35 + .../storage/src/ikvdb_raw_cursor.h | 47 + .../storage/src/irelational_store.h | 50 + .../kvdb_commit_notify_filterable_data.cpp | 104 + .../src/kvdb_commit_notify_filterable_data.h | 68 + .../storage/src/kvdb_manager.cpp | 972 +++++++ .../storage/src/kvdb_observer_handle.cpp | 45 + .../storage/src/kvdb_observer_handle.h | 38 + .../storage/src/kvdb_properties.cpp | 95 + mock/distributeddb/storage/src/kvdb_utils.cpp | 86 + mock/distributeddb/storage/src/kvdb_utils.h | 38 + .../storage/src/kvdb_windowed_result_set.cpp | 58 + .../storage/src/kvdb_windowed_result_set.h | 54 + mock/distributeddb/storage/src/local_kvdb.h | 31 + .../multiver/generic_multi_ver_kv_entry.cpp | 156 ++ .../src/multiver/generic_multi_ver_kv_entry.h | 67 + .../multiver/ikvdb_multi_ver_data_storage.h | 68 + .../multiver/ikvdb_multi_ver_transaction.h | 59 + .../storage/src/multiver/multi_ver_commit.cpp | 124 + .../storage/src/multiver/multi_ver_commit.h | 71 + .../storage/src/multiver/multi_ver_kv_entry.h | 40 + .../src/multiver/multi_ver_kvdata_storage.cpp | 559 ++++ .../src/multiver/multi_ver_kvdata_storage.h | 94 + .../src/multiver/multi_ver_natural_store.cpp | 1195 +++++++++ .../src/multiver/multi_ver_natural_store.h | 210 ++ ...i_ver_natural_store_commit_notify_data.cpp | 110 + ...lti_ver_natural_store_commit_notify_data.h | 59 + ...multi_ver_natural_store_commit_storage.cpp | 914 +++++++ .../multi_ver_natural_store_commit_storage.h | 127 + .../multi_ver_natural_store_connection.cpp | 524 ++++ .../multi_ver_natural_store_connection.h | 126 + .../multi_ver_natural_store_snapshot.cpp | 66 + .../multi_ver_natural_store_snapshot.h | 45 + .../multi_ver_natural_store_transfer_data.cpp | 53 + .../multi_ver_natural_store_transfer_data.h | 40 + .../src/multiver/multi_ver_storage_engine.cpp | 66 + .../src/multiver/multi_ver_storage_engine.h | 50 + .../multiver/multi_ver_storage_executor.cpp | 1503 +++++++++++ .../src/multiver/multi_ver_storage_executor.h | 189 ++ .../storage/src/multiver/multi_ver_vacuum.cpp | 674 +++++ .../multi_ver_vacuum_executor_impl.cpp | 328 +++ .../multiver/multi_ver_vacuum_executor_impl.h | 74 + .../src/multiver/multi_ver_value_object.cpp | 148 ++ .../src/multiver/multi_ver_value_object.h | 66 + .../storage/src/operation/database_oper.cpp | 580 ++++ .../storage/src/operation/database_oper.h | 106 + .../src/operation/local_database_oper.cpp | 214 ++ .../src/operation/local_database_oper.h | 57 + .../src/operation/multi_ver_database_oper.cpp | 289 ++ .../src/operation/multi_ver_database_oper.h | 64 + .../operation/single_ver_database_oper.cpp | 559 ++++ .../src/operation/single_ver_database_oper.h | 79 + .../storage/src/package_file.cpp | 571 ++++ mock/distributeddb/storage/src/package_file.h | 38 + .../src/relational_store_connection.cpp | 35 + .../storage/src/relational_store_instance.cpp | 227 ++ .../src/relational_sync_able_storage.cpp | 618 +++++ .../storage/src/relationaldb_properties.cpp | 40 + .../storage/src/result_entries_window.cpp | 186 ++ .../storage/src/result_entries_window.h | 50 + ...e_ver_natural_store_commit_notify_data.cpp | 280 ++ ...gle_ver_natural_store_commit_notify_data.h | 114 + .../storage/src/sqlite/query_object.cpp | 460 ++++ .../storage/src/sqlite/query_object.h | 112 + .../storage/src/sqlite/query_sync_object.cpp | 353 +++ .../storage/src/sqlite/query_sync_object.h | 60 + .../relational/sqlite_relational_store.cpp | 520 ++++ .../relational/sqlite_relational_store.h | 114 + .../sqlite_relational_store_connection.cpp | 232 ++ .../sqlite_relational_store_connection.h | 66 + ...qlite_single_relational_storage_engine.cpp | 271 ++ .../sqlite_single_relational_storage_engine.h | 64 + .../storage/src/sqlite/sqlite_import.h | 25 + .../storage/src/sqlite/sqlite_local_kvdb.cpp | 402 +++ .../storage/src/sqlite/sqlite_local_kvdb.h | 93 + .../sqlite/sqlite_local_kvdb_connection.cpp | 498 ++++ .../src/sqlite/sqlite_local_kvdb_connection.h | 111 + .../src/sqlite/sqlite_local_kvdb_snapshot.cpp | 54 + .../src/sqlite/sqlite_local_kvdb_snapshot.h | 49 + .../sqlite/sqlite_local_storage_engine.cpp | 30 + .../src/sqlite/sqlite_local_storage_engine.h | 36 + .../sqlite/sqlite_local_storage_executor.cpp | 252 ++ .../sqlite/sqlite_local_storage_executor.h | 81 + .../sqlite/sqlite_multi_ver_data_storage.cpp | 397 +++ .../sqlite/sqlite_multi_ver_data_storage.h | 89 + .../sqlite/sqlite_multi_ver_transaction.cpp | 1524 +++++++++++ .../src/sqlite/sqlite_multi_ver_transaction.h | 213 ++ .../src/sqlite/sqlite_query_helper.cpp | 1067 ++++++++ .../storage/src/sqlite/sqlite_query_helper.h | 151 ++ .../sqlite_single_ver_continue_token.cpp | 141 + .../sqlite/sqlite_single_ver_continue_token.h | 79 + .../sqlite_single_ver_database_upgrader.cpp | 350 +++ .../sqlite_single_ver_database_upgrader.h | 56 + .../sqlite_single_ver_forward_cursor.cpp | 145 + .../sqlite/sqlite_single_ver_forward_cursor.h | 63 + .../sqlite_single_ver_natural_store.cpp | 2345 +++++++++++++++++ .../sqlite/sqlite_single_ver_natural_store.h | 298 +++ ...te_single_ver_natural_store_connection.cpp | 1796 +++++++++++++ ...lite_single_ver_natural_store_connection.h | 230 ++ ...e_single_ver_relational_continue_token.cpp | 156 ++ ...ite_single_ver_relational_continue_token.h | 58 + ...single_ver_relational_storage_executor.cpp | 1316 +++++++++ ...e_single_ver_relational_storage_executor.h | 125 + .../sqlite/sqlite_single_ver_result_set.cpp | 306 +++ .../src/sqlite/sqlite_single_ver_result_set.h | 108 + ...te_single_ver_schema_database_upgrader.cpp | 237 ++ ...lite_single_ver_schema_database_upgrader.h | 41 + .../sqlite_single_ver_storage_engine.cpp | 1141 ++++++++ .../sqlite/sqlite_single_ver_storage_engine.h | 127 + .../sqlite_single_ver_storage_executor.cpp | 2168 +++++++++++++++ .../sqlite_single_ver_storage_executor.h | 421 +++ ...lite_single_ver_storage_executor_cache.cpp | 995 +++++++ .../sqlite_single_ver_storage_executor_sql.h | 271 ++ ..._single_ver_storage_executor_subscribe.cpp | 284 ++ .../src/sqlite/sqlite_storage_engine.cpp | 202 ++ .../src/sqlite/sqlite_storage_engine.h | 74 + .../src/sqlite/sqlite_storage_executor.cpp | 56 + .../src/sqlite/sqlite_storage_executor.h | 41 + .../storage/src/sqlite/sqlite_utils.cpp | 2154 +++++++++++++++ .../storage/src/sqlite/sqlite_utils.h | 231 ++ .../storage/src/storage_engine.cpp | 434 +++ .../storage/src/storage_engine.h | 132 + .../storage/src/storage_engine_manager.cpp | 315 +++ .../storage/src/storage_executor.cpp | 51 + .../storage/src/storage_executor.h | 55 + .../storage/src/sync_able_engine.cpp | 155 ++ .../storage/src/sync_able_engine.h | 66 + .../storage/src/sync_able_kvdb.cpp | 390 +++ .../storage/src/sync_able_kvdb.h | 135 + .../storage/src/sync_able_kvdb_connection.cpp | 326 +++ .../storage/src/sync_able_kvdb_connection.h | 76 + .../storage/src/upgrader/database_upgrader.h | 26 + .../upgrader/single_ver_database_upgrader.cpp | 71 + .../upgrader/single_ver_database_upgrader.h | 40 + .../single_ver_schema_database_upgrader.cpp | 103 + .../single_ver_schema_database_upgrader.h | 48 + mock/distributeddb/syncer/include/isyncer.h | 126 + .../syncer/include/syncer_proxy.h | 111 + .../distributeddb/syncer/src/ability_sync.cpp | 1208 +++++++++ mock/distributeddb/syncer/src/ability_sync.h | 248 ++ .../syncer/src/commit_history_sync.cpp | 717 +++++ .../syncer/src/commit_history_sync.h | 146 + .../syncer/src/communicator_proxy.cpp | 275 ++ .../syncer/src/communicator_proxy.h | 65 + mock/distributeddb/syncer/src/db_ability.cpp | 169 ++ mock/distributeddb/syncer/src/db_ability.h | 59 + .../syncer/src/device_manager.cpp | 172 ++ .../distributeddb/syncer/src/device_manager.h | 72 + .../syncer/src/generic_syncer.cpp | 818 ++++++ .../distributeddb/syncer/src/generic_syncer.h | 209 ++ mock/distributeddb/syncer/src/isync_engine.h | 90 + .../syncer/src/isync_state_machine.h | 62 + mock/distributeddb/syncer/src/isync_target.h | 59 + .../syncer/src/isync_task_context.h | 184 ++ mock/distributeddb/syncer/src/meta_data.cpp | 603 +++++ mock/distributeddb/syncer/src/meta_data.h | 177 ++ .../syncer/src/multi_ver_data_sync.cpp | 691 +++++ .../syncer/src/multi_ver_data_sync.h | 135 + .../syncer/src/multi_ver_sync_engine.cpp | 28 + .../syncer/src/multi_ver_sync_engine.h | 41 + .../src/multi_ver_sync_state_machine.cpp | 616 +++++ .../syncer/src/multi_ver_sync_state_machine.h | 140 + .../syncer/src/multi_ver_sync_target.h | 26 + .../src/multi_ver_sync_task_context.cpp | 240 ++ .../syncer/src/multi_ver_sync_task_context.h | 106 + .../syncer/src/multi_ver_syncer.cpp | 131 + .../syncer/src/multi_ver_syncer.h | 66 + .../src/query_sync_water_mark_helper.cpp | 595 +++++ .../syncer/src/query_sync_water_mark_helper.h | 176 ++ .../src/single_ver_data_message_schedule.cpp | 340 +++ .../src/single_ver_data_message_schedule.h | 75 + .../syncer/src/single_ver_data_packet.cpp | 473 ++++ .../syncer/src/single_ver_data_packet.h | 222 ++ .../syncer/src/single_ver_data_sync.cpp | 2031 ++++++++++++++ .../syncer/src/single_ver_data_sync.h | 275 ++ .../syncer/src/single_ver_data_sync_utils.cpp | 431 +++ .../syncer/src/single_ver_data_sync_utils.h | 90 + .../src/single_ver_kv_sync_task_context.cpp | 50 + .../src/single_ver_kv_sync_task_context.h | 40 + .../syncer/src/single_ver_kv_syncer.cpp | 295 +++ .../syncer/src/single_ver_kv_syncer.h | 55 + ...ingle_ver_relational_sync_task_context.cpp | 86 + .../single_ver_relational_sync_task_context.h | 53 + .../src/single_ver_relational_syncer.cpp | 182 ++ .../syncer/src/single_ver_relational_syncer.h | 60 + .../src/single_ver_serialize_manager.cpp | 713 +++++ .../syncer/src/single_ver_serialize_manager.h | 84 + .../syncer/src/single_ver_sync_engine.cpp | 125 + .../syncer/src/single_ver_sync_engine.h | 62 + .../src/single_ver_sync_state_machine.cpp | 1217 +++++++++ .../src/single_ver_sync_state_machine.h | 226 ++ .../syncer/src/single_ver_sync_target.cpp | 83 + .../syncer/src/single_ver_sync_target.h | 55 + .../src/single_ver_sync_task_context.cpp | 564 ++++ .../syncer/src/single_ver_sync_task_context.h | 181 ++ .../syncer/src/single_ver_syncer.cpp | 83 + .../syncer/src/single_ver_syncer.h | 42 + .../syncer/src/subscribe_manager.cpp | 386 +++ .../syncer/src/subscribe_manager.h | 130 + mock/distributeddb/syncer/src/sync_config.cpp | 31 + mock/distributeddb/syncer/src/sync_config.h | 46 + mock/distributeddb/syncer/src/sync_engine.cpp | 1067 ++++++++ mock/distributeddb/syncer/src/sync_engine.h | 227 ++ .../syncer/src/sync_operation.cpp | 316 +++ .../distributeddb/syncer/src/sync_operation.h | 185 ++ .../syncer/src/sync_state_machine.cpp | 397 +++ .../syncer/src/sync_state_machine.h | 160 ++ mock/distributeddb/syncer/src/sync_target.cpp | 87 + mock/distributeddb/syncer/src/sync_target.h | 60 + .../syncer/src/sync_task_context.cpp | 730 +++++ .../syncer/src/sync_task_context.h | 286 ++ mock/distributeddb/syncer/src/sync_types.h | 101 + .../syncer/src/syncer_factory.cpp | 45 + .../distributeddb/syncer/src/syncer_factory.h | 33 + .../distributeddb/syncer/src/syncer_proxy.cpp | 217 ++ mock/distributeddb/syncer/src/time_helper.cpp | 123 + mock/distributeddb/syncer/src/time_helper.h | 71 + mock/distributeddb/syncer/src/time_sync.cpp | 563 ++++ mock/distributeddb/syncer/src/time_sync.h | 131 + .../syncer/src/value_slice_sync.cpp | 631 +++++ .../syncer/src/value_slice_sync.h | 126 + mock/distributeddb/test/BUILD.gn | 772 ++++++ .../common/distributeddb_tools_test.cpp | 146 + .../common/distributeddb_tools_test.h | 65 + .../test/fuzztest/delegate_fuzzer/BUILD.gn | 103 + .../test/fuzztest/delegate_fuzzer/corpus/init | 14 + .../delegate_fuzzer/delegate_fuzzer.cpp | 103 + .../delegate_fuzzer/delegate_fuzzer.h | 21 + .../test/fuzztest/delegate_fuzzer/project.xml | 25 + .../test/fuzztest/fileoper_fuzzer/BUILD.gn | 104 + .../test/fuzztest/fileoper_fuzzer/corpus/init | 16 + .../fileoper_fuzzer/fileoper_fuzzer.cpp | 105 + .../fileoper_fuzzer/fileoper_fuzzer.h | 21 + .../test/fuzztest/fileoper_fuzzer/project.xml | 25 + .../test/fuzztest/importfile_fuzzer/BUILD.gn | 103 + .../fuzztest/importfile_fuzzer/corpus/init | 14 + .../importfile_fuzzer/importfile_fuzzer.cpp | 84 + .../importfile_fuzzer/importfile_fuzzer.h | 35 + .../fuzztest/importfile_fuzzer/project.xml | 25 + .../iprocesscommunicator_fuzzer/BUILD.gn | 102 + .../iprocesscommunicator_fuzzer/corpus/init | 14 + .../iprocesscommunicator_fuzzer.cpp | 138 + .../iprocesscommunicator_fuzzer.h | 21 + .../iprocesscommunicator_fuzzer/project.xml | 25 + .../fuzztest/kvstoreresultset_fuzzer/BUILD.gn | 103 + .../kvstoreresultset_fuzzer/corpus/init | 14 + .../kvstoreresultset_fuzzer.cpp | 95 + .../kvstoreresultset_fuzzer.h | 35 + .../kvstoreresultset_fuzzer/project.xml | 25 + .../test/fuzztest/nbdelegate_fuzzer/BUILD.gn | 103 + .../fuzztest/nbdelegate_fuzzer/corpus/init | 14 + .../nbdelegate_fuzzer/nbdelegate_fuzzer.cpp | 194 ++ .../nbdelegate_fuzzer/nbdelegate_fuzzer.h | 21 + .../fuzztest/nbdelegate_fuzzer/project.xml | 25 + .../test/fuzztest/parseckeck_fuzzer/BUILD.gn | 107 + .../fuzztest/parseckeck_fuzzer/corpus/init | 14 + .../parseckeck_fuzzer/parseckeck_fuzzer.cpp | 102 + .../parseckeck_fuzzer/parseckeck_fuzzer.h | 21 + .../fuzztest/parseckeck_fuzzer/project.xml | 25 + .../test/fuzztest/query_fuzzer/BUILD.gn | 104 + .../test/fuzztest/query_fuzzer/corpus/init | 16 + .../test/fuzztest/query_fuzzer/project.xml | 25 + .../fuzztest/query_fuzzer/query_fuzzer.cpp | 139 + .../test/fuzztest/query_fuzzer/query_fuzzer.h | 35 + .../test/fuzztest/rekey_fuzzer/BUILD.gn | 103 + .../test/fuzztest/rekey_fuzzer/corpus/init | 14 + .../test/fuzztest/rekey_fuzzer/project.xml | 25 + .../fuzztest/rekey_fuzzer/rekey_fuzzer.cpp | 100 + .../test/fuzztest/rekey_fuzzer/rekey_fuzzer.h | 21 + .../common/distributeddb_auto_launch_test.cpp | 1014 +++++++ .../common/distributeddb_common_test.cpp | 526 ++++ .../distributeddb_data_compression_test.cpp | 187 ++ .../distributeddb_data_generate_unit_test.cpp | 93 + .../distributeddb_data_generate_unit_test.h | 112 + .../distributeddb_json_precheck_unit_test.cpp | 207 ++ .../distributeddb_notification_chain_test.cpp | 186 ++ .../common/distributeddb_parcel_unit_test.cpp | 680 +++++ ...ibuteddb_relational_schema_object_test.cpp | 469 ++++ .../distributeddb_schema_object_test.cpp | 1090 ++++++++ .../common/distributeddb_schema_unit_test.cpp | 513 ++++ .../common/distributeddb_tools_unit_test.cpp | 964 +++++++ .../common/distributeddb_tools_unit_test.h | 310 +++ .../common/common/evloop_timer_unit_test.cpp | 418 +++ .../common/process_communicator_test_stub.h | 93 + .../common/communicator/adapter_stub.cpp | 413 +++ .../common/communicator/adapter_stub.h | 136 + .../distributeddb_communicator_common.cpp | 346 +++ .../distributeddb_communicator_common.h | 150 ++ .../distributeddb_communicator_deep_test.cpp | 562 ++++ ...buteddb_communicator_send_receive_test.cpp | 748 ++++++ .../distributeddb_communicator_test.cpp | 764 ++++++ ...tributeddb_interfaces_auto_launch_test.cpp | 727 +++++ ..._interfaces_data_operation_syncdb_test.cpp | 1381 ++++++++++ ...buteddb_interfaces_data_operation_test.cpp | 2006 ++++++++++++++ ...stributeddb_interfaces_data_value_test.cpp | 174 ++ ...teddb_interfaces_database_corrupt_test.cpp | 505 ++++ ...distributeddb_interfaces_database_test.cpp | 1435 ++++++++++ ...eddb_interfaces_device_identifier_test.cpp | 314 +++ ...teddb_interfaces_encrypt_database_test.cpp | 497 ++++ ...teddb_interfaces_encrypt_delegate_test.cpp | 1112 ++++++++ ...eddb_interfaces_import_and_export_test.cpp | 1132 ++++++++ ...stributeddb_interfaces_index_unit_test.cpp | 857 ++++++ ...nterfaces_nb_delegate_local_batch_test.cpp | 1138 ++++++++ ...interfaces_nb_delegate_schema_put_test.cpp | 360 +++ ...tributeddb_interfaces_nb_delegate_test.cpp | 2019 ++++++++++++++ ...stributeddb_interfaces_nb_publish_test.cpp | 792 ++++++ ...buteddb_interfaces_nb_transaction_test.cpp | 1104 ++++++++ ...ributeddb_interfaces_nb_unpublish_test.cpp | 481 ++++ .../distributeddb_interfaces_query_test.cpp | 228 ++ ...uteddb_interfaces_register_syncdb_test.cpp | 1871 +++++++++++++ ...uteddb_interfaces_relational_sync_test.cpp | 378 +++ ...stributeddb_interfaces_relational_test.cpp | 784 ++++++ ...teddb_interfaces_resultset_performance.cpp | 175 ++ ...nterfaces_schema_database_upgrade_test.cpp | 382 +++ ...erfaces_single_version_result_set_test.cpp | 534 ++++ ...teddb_interfaces_space_management_test.cpp | 530 ++++ ...terfaces_transaction_optimization_test.cpp | 811 ++++++ ...ddb_interfaces_transaction_syncdb_test.cpp | 643 +++++ ...tributeddb_interfaces_transaction_test.cpp | 675 +++++ ...uteddb_interfaces_transaction_testcase.cpp | 720 +++++ ...ibuteddb_interfaces_transaction_testcase.h | 85 + .../process_system_api_adapter_impl.cpp | 165 ++ .../process_system_api_adapter_impl.h | 50 + ...t_process_system_api_adapter_impl_test.cpp | 275 ++ .../distributeddb_data_transformer_test.cpp | 210 ++ .../distributeddb_file_package_test.cpp | 344 +++ .../distributeddb_multi_ver_vacuum_test.cpp | 753 ++++++ ...distributeddb_query_object_helper_test.cpp | 182 ++ ...distributeddb_relational_get_data_test.cpp | 1475 +++++++++++ .../distributeddb_sqlite_register_test.cpp | 207 ++ ...tributeddb_storage_commit_storage_test.cpp | 839 ++++++ ...tributeddb_storage_data_operation_test.cpp | 642 +++++ .../distributeddb_storage_encrypt_test.cpp | 1397 ++++++++++ ...tributeddb_storage_index_optimize_test.cpp | 376 +++ ..._memory_single_ver_naturall_store_test.cpp | 1040 ++++++++ .../distributeddb_storage_query_sync_test.cpp | 1233 +++++++++ ...buteddb_storage_register_conflict_test.cpp | 836 ++++++ ...buteddb_storage_register_observer_test.cpp | 828 ++++++ ...db_storage_resultset_and_json_optimize.cpp | 315 +++ ...rage_single_ver_natural_store_testcase.cpp | 1902 +++++++++++++ ...torage_single_ver_natural_store_testcase.h | 161 ++ ...uteddb_storage_single_ver_upgrade_test.cpp | 588 +++++ ...e_sqlite_single_ver_natural_store_test.cpp | 1081 ++++++++ ...ributeddb_storage_subscribe_query_test.cpp | 674 +++++ ...ibuteddb_storage_transaction_data_test.cpp | 1592 +++++++++++ ...uteddb_storage_transaction_record_test.cpp | 1103 ++++++++ .../multi_ver_vacuum_executor_stub.cpp | 127 + .../storage/multi_ver_vacuum_executor_stub.h | 60 + .../distributeddb_ability_sync_test.cpp | 556 ++++ .../distributeddb_anti_dos_sync_test.cpp | 316 +++ .../distributeddb_communicator_proxy_test.cpp | 347 +++ .../distributeddb_mock_sync_module_test.cpp | 601 +++++ .../distributeddb_multi_ver_p2p_sync_test.cpp | 1649 ++++++++++++ ...ributeddb_relational_ver_p2p_sync_test.cpp | 1241 +++++++++ ...ributeddb_single_ver_msg_schedule_test.cpp | 457 ++++ ...stributeddb_single_ver_multi_user_test.cpp | 775 ++++++ ...buteddb_single_ver_p2p_query_sync_test.cpp | 1641 ++++++++++++ ...eddb_single_ver_p2p_subsribe_sync_test.cpp | 881 +++++++ ...buteddb_single_ver_p2p_sync_check_test.cpp | 1273 +++++++++ ...distributeddb_single_ver_p2p_sync_test.cpp | 2290 ++++++++++++++++ ...stributeddb_syncer_device_manager_test.cpp | 242 ++ .../syncer/distributeddb_time_sync_test.cpp | 484 ++++ .../common/syncer/generic_virtual_device.cpp | 241 ++ .../common/syncer/generic_virtual_device.h | 59 + .../common/syncer/kv_virtual_device.cpp | 112 + .../common/syncer/kv_virtual_device.h | 42 + .../unittest/common/syncer/mock_auto_launch.h | 39 + .../common/syncer/mock_communicator.h | 41 + .../unittest/common/syncer/mock_meta_data.h | 29 + .../common/syncer/mock_single_ver_data_sync.h | 48 + .../syncer/mock_single_ver_state_machine.h | 62 + .../common/syncer/mock_sync_task_context.h | 54 + .../syncer/relational_virtual_device.cpp | 63 + .../common/syncer/relational_virtual_device.h | 40 + .../common/syncer/virtual_communicator.cpp | 181 ++ .../common/syncer/virtual_communicator.h | 102 + .../virtual_communicator_aggregator.cpp | 246 ++ .../syncer/virtual_communicator_aggregator.h | 93 + .../virtual_multi_ver_sync_db_interface.cpp | 238 ++ .../virtual_multi_ver_sync_db_interface.h | 105 + ...rtual_relational_ver_sync_db_interface.cpp | 357 +++ ...virtual_relational_ver_sync_db_interface.h | 126 + .../virtual_single_ver_sync_db_Interface.cpp | 435 +++ .../virtual_single_ver_sync_db_Interface.h | 151 ++ .../syncer/virtual_time_sync_communicator.cpp | 149 ++ .../syncer/virtual_time_sync_communicator.h | 83 + mock/include/CMakeLists.txt | 1 + .../objectstore/iobject_callback.h | 93 + .../objectstore/iobject_service.h | 36 + .../objectstore/object_service.h | 36 + .../objectstore/object_service_proxy.h | 41 + 605 files changed, 168382 insertions(+), 2 deletions(-) create mode 100644 mock/distributeddb/BUILD.gn create mode 100644 mock/distributeddb/CMakeLists.txt create mode 100644 mock/distributeddb/common/include/auto_launch.h create mode 100644 mock/distributeddb/common/include/data_compression.h create mode 100644 mock/distributeddb/common/include/data_value.h create mode 100644 mock/distributeddb/common/include/db_common.h create mode 100644 mock/distributeddb/common/include/db_constant.h create mode 100644 mock/distributeddb/common/include/db_dfx_adapter.h create mode 100644 mock/distributeddb/common/include/db_dump_helper.h create mode 100644 mock/distributeddb/common/include/db_errno.h create mode 100644 mock/distributeddb/common/include/db_types.h create mode 100644 mock/distributeddb/common/include/endian_convert.h create mode 100644 mock/distributeddb/common/include/hash.h create mode 100644 mock/distributeddb/common/include/ischema.h create mode 100644 mock/distributeddb/common/include/json_object.h create mode 100644 mock/distributeddb/common/include/log_print.h create mode 100644 mock/distributeddb/common/include/macro_utils.h create mode 100644 mock/distributeddb/common/include/notification_chain.h create mode 100644 mock/distributeddb/common/include/param_check_utils.h create mode 100644 mock/distributeddb/common/include/parcel.h create mode 100644 mock/distributeddb/common/include/performance_analysis.h create mode 100644 mock/distributeddb/common/include/platform_specific.h create mode 100644 mock/distributeddb/common/include/ref_object.h create mode 100644 mock/distributeddb/common/include/relational/relational_schema_object.h create mode 100644 mock/distributeddb/common/include/res_finalizer.h create mode 100644 mock/distributeddb/common/include/runtime_context.h create mode 100644 mock/distributeddb/common/include/schema_constant.h create mode 100644 mock/distributeddb/common/include/schema_negotiate.h create mode 100644 mock/distributeddb/common/include/schema_object.h create mode 100644 mock/distributeddb/common/include/schema_utils.h create mode 100644 mock/distributeddb/common/include/semaphore_utils.h create mode 100644 mock/distributeddb/common/include/task_pool.h create mode 100644 mock/distributeddb/common/include/user_change_monitor.h create mode 100644 mock/distributeddb/common/include/value_hash_calc.h create mode 100644 mock/distributeddb/common/include/value_object.h create mode 100644 mock/distributeddb/common/include/version.h create mode 100644 mock/distributeddb/common/include/zlib_compression.h create mode 100644 mock/distributeddb/common/src/auto_launch.cpp create mode 100644 mock/distributeddb/common/src/data_compression.cpp create mode 100644 mock/distributeddb/common/src/data_value.cpp create mode 100644 mock/distributeddb/common/src/db_common.cpp create mode 100644 mock/distributeddb/common/src/db_constant.cpp create mode 100644 mock/distributeddb/common/src/db_dfx_adapter.cpp create mode 100644 mock/distributeddb/common/src/db_dump_helper.cpp create mode 100644 mock/distributeddb/common/src/evloop/include/event_fd.h create mode 100644 mock/distributeddb/common/src/evloop/include/ievent.h create mode 100644 mock/distributeddb/common/src/evloop/include/ievent_loop.h create mode 100644 mock/distributeddb/common/src/evloop/src/event_impl.cpp create mode 100644 mock/distributeddb/common/src/evloop/src/event_impl.h create mode 100644 mock/distributeddb/common/src/evloop/src/event_loop_epoll.cpp create mode 100644 mock/distributeddb/common/src/evloop/src/event_loop_epoll.h create mode 100644 mock/distributeddb/common/src/evloop/src/event_loop_impl.cpp create mode 100644 mock/distributeddb/common/src/evloop/src/event_loop_impl.h create mode 100644 mock/distributeddb/common/src/evloop/src/event_loop_select.cpp create mode 100644 mock/distributeddb/common/src/evloop/src/event_loop_select.h create mode 100644 mock/distributeddb/common/src/evloop/src/ievent.cpp create mode 100644 mock/distributeddb/common/src/evloop/src/ievent_loop.cpp create mode 100644 mock/distributeddb/common/src/flatbuffer_schema.cpp create mode 100644 mock/distributeddb/common/src/hash.cpp create mode 100644 mock/distributeddb/common/src/json_object.cpp create mode 100644 mock/distributeddb/common/src/lock_status_observer.cpp create mode 100644 mock/distributeddb/common/src/lock_status_observer.h create mode 100644 mock/distributeddb/common/src/log_print.cpp create mode 100644 mock/distributeddb/common/src/notification_chain.cpp create mode 100644 mock/distributeddb/common/src/param_check_utils.cpp create mode 100644 mock/distributeddb/common/src/parcel.cpp create mode 100644 mock/distributeddb/common/src/performance_analysis.cpp create mode 100644 mock/distributeddb/common/src/platform_specific.cpp create mode 100644 mock/distributeddb/common/src/query.cpp create mode 100644 mock/distributeddb/common/src/query_expression.cpp create mode 100644 mock/distributeddb/common/src/ref_object.cpp create mode 100644 mock/distributeddb/common/src/relational/relational_schema_object.cpp create mode 100644 mock/distributeddb/common/src/runtime_context.cpp create mode 100644 mock/distributeddb/common/src/runtime_context_impl.cpp create mode 100644 mock/distributeddb/common/src/runtime_context_impl.h create mode 100644 mock/distributeddb/common/src/schema_constant.cpp create mode 100644 mock/distributeddb/common/src/schema_negotiate.cpp create mode 100644 mock/distributeddb/common/src/schema_object.cpp create mode 100644 mock/distributeddb/common/src/schema_utils.cpp create mode 100644 mock/distributeddb/common/src/semaphore_utils.cpp create mode 100644 mock/distributeddb/common/src/task_pool.cpp create mode 100644 mock/distributeddb/common/src/task_pool_impl.cpp create mode 100644 mock/distributeddb/common/src/task_pool_impl.h create mode 100644 mock/distributeddb/common/src/task_queue.cpp create mode 100644 mock/distributeddb/common/src/task_queue.h create mode 100644 mock/distributeddb/common/src/time_tick_monitor.cpp create mode 100644 mock/distributeddb/common/src/time_tick_monitor.h create mode 100644 mock/distributeddb/common/src/types_export.cpp create mode 100644 mock/distributeddb/common/src/user_change_monitor.cpp create mode 100644 mock/distributeddb/common/src/value_object.cpp create mode 100644 mock/distributeddb/common/src/zlib_compression.cpp create mode 100644 mock/distributeddb/communicator/include/combine_status.h create mode 100644 mock/distributeddb/communicator/include/communicator_aggregator.h create mode 100644 mock/distributeddb/communicator/include/communicator_type_define.h create mode 100644 mock/distributeddb/communicator/include/frame_combiner.h create mode 100644 mock/distributeddb/communicator/include/frame_retainer.h create mode 100644 mock/distributeddb/communicator/include/iadapter.h create mode 100644 mock/distributeddb/communicator/include/icommunicator.h create mode 100644 mock/distributeddb/communicator/include/icommunicator_aggregator.h create mode 100644 mock/distributeddb/communicator/include/message.h create mode 100644 mock/distributeddb/communicator/include/message_transform.h create mode 100644 mock/distributeddb/communicator/include/network_adapter.h create mode 100644 mock/distributeddb/communicator/include/object_holder.h create mode 100644 mock/distributeddb/communicator/include/object_holder_typed.h create mode 100644 mock/distributeddb/communicator/include/parse_result.h create mode 100644 mock/distributeddb/communicator/include/send_task_scheduler.h create mode 100644 mock/distributeddb/communicator/src/combine_status.cpp create mode 100644 mock/distributeddb/communicator/src/communicator.cpp create mode 100644 mock/distributeddb/communicator/src/communicator.h create mode 100644 mock/distributeddb/communicator/src/communicator_aggregator.cpp create mode 100644 mock/distributeddb/communicator/src/communicator_linker.cpp create mode 100644 mock/distributeddb/communicator/src/communicator_linker.h create mode 100644 mock/distributeddb/communicator/src/frame_combiner.cpp create mode 100644 mock/distributeddb/communicator/src/frame_header.h create mode 100644 mock/distributeddb/communicator/src/frame_retainer.cpp create mode 100644 mock/distributeddb/communicator/src/header_converter.cpp create mode 100644 mock/distributeddb/communicator/src/header_converter.h create mode 100644 mock/distributeddb/communicator/src/message_transform.cpp create mode 100644 mock/distributeddb/communicator/src/network_adapter.cpp create mode 100644 mock/distributeddb/communicator/src/protocol_proto.cpp create mode 100644 mock/distributeddb/communicator/src/protocol_proto.h create mode 100644 mock/distributeddb/communicator/src/send_task_scheduler.cpp create mode 100644 mock/distributeddb/communicator/src/serial_buffer.cpp create mode 100644 mock/distributeddb/communicator/src/serial_buffer.h create mode 100644 mock/distributeddb/include/auto_launch_export.h create mode 100644 mock/distributeddb/include/query.h create mode 100644 mock/distributeddb/include/query_expression.h create mode 100644 mock/distributeddb/include/types_export.h create mode 100644 mock/distributeddb/interfaces/include/get_query_info.h create mode 100644 mock/distributeddb/interfaces/include/intercepted_data.h create mode 100644 mock/distributeddb/interfaces/include/iprocess_communicator.h create mode 100644 mock/distributeddb/interfaces/include/iprocess_system_api_adapter.h create mode 100644 mock/distributeddb/interfaces/include/kv_store_changed_data.h create mode 100644 mock/distributeddb/interfaces/include/kv_store_delegate.h create mode 100644 mock/distributeddb/interfaces/include/kv_store_delegate_manager.h create mode 100644 mock/distributeddb/interfaces/include/kv_store_errno.h create mode 100644 mock/distributeddb/interfaces/include/kv_store_nb_conflict_data.h create mode 100644 mock/distributeddb/interfaces/include/kv_store_nb_delegate.h create mode 100644 mock/distributeddb/interfaces/include/kv_store_observer.h create mode 100644 mock/distributeddb/interfaces/include/kv_store_result_set.h create mode 100644 mock/distributeddb/interfaces/include/kv_store_snapshot_delegate.h create mode 100644 mock/distributeddb/interfaces/include/relational/relational_store_delegate.h create mode 100644 mock/distributeddb/interfaces/include/relational/relational_store_manager.h create mode 100644 mock/distributeddb/interfaces/include/relational/relational_store_sqlite_ext.h create mode 100644 mock/distributeddb/interfaces/include/relational/runtime_config.h create mode 100644 mock/distributeddb/interfaces/include/relational/store_changed_data.h create mode 100644 mock/distributeddb/interfaces/include/relational/store_observer.h create mode 100644 mock/distributeddb/interfaces/include/store_types.h create mode 100644 mock/distributeddb/interfaces/src/intercepted_data_impl.cpp create mode 100644 mock/distributeddb/interfaces/src/intercepted_data_impl.h create mode 100644 mock/distributeddb/interfaces/src/kv_store_changed_data_impl.cpp create mode 100644 mock/distributeddb/interfaces/src/kv_store_changed_data_impl.h create mode 100644 mock/distributeddb/interfaces/src/kv_store_delegate_impl.cpp create mode 100644 mock/distributeddb/interfaces/src/kv_store_delegate_impl.h create mode 100644 mock/distributeddb/interfaces/src/kv_store_delegate_manager.cpp create mode 100644 mock/distributeddb/interfaces/src/kv_store_errno.cpp create mode 100644 mock/distributeddb/interfaces/src/kv_store_nb_conflict_data_impl.cpp create mode 100644 mock/distributeddb/interfaces/src/kv_store_nb_conflict_data_impl.h create mode 100644 mock/distributeddb/interfaces/src/kv_store_nb_delegate_impl.cpp create mode 100644 mock/distributeddb/interfaces/src/kv_store_nb_delegate_impl.h create mode 100644 mock/distributeddb/interfaces/src/kv_store_result_set_impl.cpp create mode 100644 mock/distributeddb/interfaces/src/kv_store_result_set_impl.h create mode 100644 mock/distributeddb/interfaces/src/kv_store_snapshot_delegate_impl.cpp create mode 100644 mock/distributeddb/interfaces/src/kv_store_snapshot_delegate_impl.h create mode 100644 mock/distributeddb/interfaces/src/relational/relational_store_changed_data_impl.cpp create mode 100644 mock/distributeddb/interfaces/src/relational/relational_store_changed_data_impl.h create mode 100644 mock/distributeddb/interfaces/src/relational/relational_store_delegate_impl.cpp create mode 100644 mock/distributeddb/interfaces/src/relational/relational_store_delegate_impl.h create mode 100644 mock/distributeddb/interfaces/src/relational/relational_store_instance.h create mode 100644 mock/distributeddb/interfaces/src/relational/relational_store_manager.cpp create mode 100644 mock/distributeddb/interfaces/src/relational/relational_store_sqlite_ext.cpp create mode 100644 mock/distributeddb/interfaces/src/relational/relational_sync_able_storage.h create mode 100644 mock/distributeddb/interfaces/src/relational/runtime_config.cpp create mode 100644 mock/distributeddb/storage/include/db_properties.h create mode 100644 mock/distributeddb/storage/include/iconnection.h create mode 100644 mock/distributeddb/storage/include/ikvdb.h create mode 100644 mock/distributeddb/storage/include/ikvdb_connection.h create mode 100644 mock/distributeddb/storage/include/ikvdb_factory.h create mode 100644 mock/distributeddb/storage/include/ikvdb_result_set.h create mode 100644 mock/distributeddb/storage/include/ikvdb_snapshot.h create mode 100644 mock/distributeddb/storage/include/ikvdb_sync_interface.h create mode 100644 mock/distributeddb/storage/include/isync_interface.h create mode 100644 mock/distributeddb/storage/include/kvdb_commit_notify_data.h create mode 100644 mock/distributeddb/storage/include/kvdb_conflict_entry.h create mode 100644 mock/distributeddb/storage/include/kvdb_manager.h create mode 100644 mock/distributeddb/storage/include/kvdb_pragma.h create mode 100644 mock/distributeddb/storage/include/kvdb_properties.h create mode 100644 mock/distributeddb/storage/include/multi_ver_def.h create mode 100644 mock/distributeddb/storage/include/multi_ver_kvdb_sync_interface.h create mode 100644 mock/distributeddb/storage/include/multi_ver_vacuum.h create mode 100644 mock/distributeddb/storage/include/multi_ver_vacuum_executor.h create mode 100644 mock/distributeddb/storage/include/relational_db_sync_interface.h create mode 100644 mock/distributeddb/storage/include/relational_store_connection.h create mode 100644 mock/distributeddb/storage/include/relationaldb_properties.h create mode 100644 mock/distributeddb/storage/include/single_ver_kv_entry.h create mode 100644 mock/distributeddb/storage/include/single_ver_kvdb_sync_interface.h create mode 100644 mock/distributeddb/storage/include/storage_engine_manager.h create mode 100644 mock/distributeddb/storage/include/sync_generic_interface.h create mode 100644 mock/distributeddb/storage/src/data_transformer.cpp create mode 100644 mock/distributeddb/storage/src/data_transformer.h create mode 100644 mock/distributeddb/storage/src/db_properties.cpp create mode 100644 mock/distributeddb/storage/src/default_factory.cpp create mode 100644 mock/distributeddb/storage/src/default_factory.h create mode 100644 mock/distributeddb/storage/src/generic_kvdb.cpp create mode 100644 mock/distributeddb/storage/src/generic_kvdb.h create mode 100644 mock/distributeddb/storage/src/generic_kvdb_connection.cpp create mode 100644 mock/distributeddb/storage/src/generic_kvdb_connection.h create mode 100644 mock/distributeddb/storage/src/generic_single_ver_kv_entry.cpp create mode 100644 mock/distributeddb/storage/src/generic_single_ver_kv_entry.h create mode 100644 mock/distributeddb/storage/src/iconnection.cpp create mode 100644 mock/distributeddb/storage/src/ikvdb_commit.h create mode 100644 mock/distributeddb/storage/src/ikvdb_commit_storage.h create mode 100644 mock/distributeddb/storage/src/ikvdb_factory.cpp create mode 100644 mock/distributeddb/storage/src/ikvdb_raw_cursor.h create mode 100644 mock/distributeddb/storage/src/irelational_store.h create mode 100644 mock/distributeddb/storage/src/kvdb_commit_notify_filterable_data.cpp create mode 100644 mock/distributeddb/storage/src/kvdb_commit_notify_filterable_data.h create mode 100644 mock/distributeddb/storage/src/kvdb_manager.cpp create mode 100644 mock/distributeddb/storage/src/kvdb_observer_handle.cpp create mode 100644 mock/distributeddb/storage/src/kvdb_observer_handle.h create mode 100644 mock/distributeddb/storage/src/kvdb_properties.cpp create mode 100644 mock/distributeddb/storage/src/kvdb_utils.cpp create mode 100644 mock/distributeddb/storage/src/kvdb_utils.h create mode 100644 mock/distributeddb/storage/src/kvdb_windowed_result_set.cpp create mode 100644 mock/distributeddb/storage/src/kvdb_windowed_result_set.h create mode 100644 mock/distributeddb/storage/src/local_kvdb.h create mode 100644 mock/distributeddb/storage/src/multiver/generic_multi_ver_kv_entry.cpp create mode 100644 mock/distributeddb/storage/src/multiver/generic_multi_ver_kv_entry.h create mode 100644 mock/distributeddb/storage/src/multiver/ikvdb_multi_ver_data_storage.h create mode 100644 mock/distributeddb/storage/src/multiver/ikvdb_multi_ver_transaction.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_commit.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_commit.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_kv_entry.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_kvdata_storage.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_kvdata_storage.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_natural_store.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_natural_store.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_notify_data.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_notify_data.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_storage.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_storage.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_natural_store_connection.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_natural_store_connection.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_natural_store_snapshot.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_natural_store_snapshot.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_natural_store_transfer_data.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_natural_store_transfer_data.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_storage_engine.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_storage_engine.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_storage_executor.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_storage_executor.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_vacuum.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_vacuum_executor_impl.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_vacuum_executor_impl.h create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_value_object.cpp create mode 100644 mock/distributeddb/storage/src/multiver/multi_ver_value_object.h create mode 100644 mock/distributeddb/storage/src/operation/database_oper.cpp create mode 100644 mock/distributeddb/storage/src/operation/database_oper.h create mode 100644 mock/distributeddb/storage/src/operation/local_database_oper.cpp create mode 100644 mock/distributeddb/storage/src/operation/local_database_oper.h create mode 100644 mock/distributeddb/storage/src/operation/multi_ver_database_oper.cpp create mode 100644 mock/distributeddb/storage/src/operation/multi_ver_database_oper.h create mode 100644 mock/distributeddb/storage/src/operation/single_ver_database_oper.cpp create mode 100644 mock/distributeddb/storage/src/operation/single_ver_database_oper.h create mode 100644 mock/distributeddb/storage/src/package_file.cpp create mode 100644 mock/distributeddb/storage/src/package_file.h create mode 100644 mock/distributeddb/storage/src/relational_store_connection.cpp create mode 100644 mock/distributeddb/storage/src/relational_store_instance.cpp create mode 100644 mock/distributeddb/storage/src/relational_sync_able_storage.cpp create mode 100644 mock/distributeddb/storage/src/relationaldb_properties.cpp create mode 100644 mock/distributeddb/storage/src/result_entries_window.cpp create mode 100644 mock/distributeddb/storage/src/result_entries_window.h create mode 100644 mock/distributeddb/storage/src/single_ver_natural_store_commit_notify_data.cpp create mode 100644 mock/distributeddb/storage/src/single_ver_natural_store_commit_notify_data.h create mode 100644 mock/distributeddb/storage/src/sqlite/query_object.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/query_object.h create mode 100644 mock/distributeddb/storage/src/sqlite/query_sync_object.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/query_sync_object.h create mode 100644 mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store.h create mode 100644 mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store_connection.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store_connection.h create mode 100644 mock/distributeddb/storage/src/sqlite/relational/sqlite_single_relational_storage_engine.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/relational/sqlite_single_relational_storage_engine.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_import.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_connection.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_connection.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_snapshot.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_snapshot.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_local_storage_engine.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_local_storage_engine.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_local_storage_executor.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_local_storage_executor.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_data_storage.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_data_storage.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_transaction.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_transaction.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_query_helper.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_query_helper.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_continue_token.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_continue_token.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_database_upgrader.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_database_upgrader.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_forward_cursor.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_forward_cursor.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store_connection.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store_connection.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_continue_token.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_continue_token.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_storage_executor.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_storage_executor.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_result_set.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_result_set.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_schema_database_upgrader.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_schema_database_upgrader.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_engine.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_engine.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor_cache.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor_sql.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor_subscribe.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_storage_engine.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_storage_engine.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_storage_executor.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_storage_executor.h create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_utils.cpp create mode 100644 mock/distributeddb/storage/src/sqlite/sqlite_utils.h create mode 100644 mock/distributeddb/storage/src/storage_engine.cpp create mode 100644 mock/distributeddb/storage/src/storage_engine.h create mode 100644 mock/distributeddb/storage/src/storage_engine_manager.cpp create mode 100644 mock/distributeddb/storage/src/storage_executor.cpp create mode 100644 mock/distributeddb/storage/src/storage_executor.h create mode 100644 mock/distributeddb/storage/src/sync_able_engine.cpp create mode 100644 mock/distributeddb/storage/src/sync_able_engine.h create mode 100644 mock/distributeddb/storage/src/sync_able_kvdb.cpp create mode 100644 mock/distributeddb/storage/src/sync_able_kvdb.h create mode 100644 mock/distributeddb/storage/src/sync_able_kvdb_connection.cpp create mode 100644 mock/distributeddb/storage/src/sync_able_kvdb_connection.h create mode 100644 mock/distributeddb/storage/src/upgrader/database_upgrader.h create mode 100644 mock/distributeddb/storage/src/upgrader/single_ver_database_upgrader.cpp create mode 100644 mock/distributeddb/storage/src/upgrader/single_ver_database_upgrader.h create mode 100644 mock/distributeddb/storage/src/upgrader/single_ver_schema_database_upgrader.cpp create mode 100644 mock/distributeddb/storage/src/upgrader/single_ver_schema_database_upgrader.h create mode 100644 mock/distributeddb/syncer/include/isyncer.h create mode 100644 mock/distributeddb/syncer/include/syncer_proxy.h create mode 100644 mock/distributeddb/syncer/src/ability_sync.cpp create mode 100644 mock/distributeddb/syncer/src/ability_sync.h create mode 100644 mock/distributeddb/syncer/src/commit_history_sync.cpp create mode 100644 mock/distributeddb/syncer/src/commit_history_sync.h create mode 100644 mock/distributeddb/syncer/src/communicator_proxy.cpp create mode 100644 mock/distributeddb/syncer/src/communicator_proxy.h create mode 100644 mock/distributeddb/syncer/src/db_ability.cpp create mode 100644 mock/distributeddb/syncer/src/db_ability.h create mode 100644 mock/distributeddb/syncer/src/device_manager.cpp create mode 100644 mock/distributeddb/syncer/src/device_manager.h create mode 100644 mock/distributeddb/syncer/src/generic_syncer.cpp create mode 100644 mock/distributeddb/syncer/src/generic_syncer.h create mode 100644 mock/distributeddb/syncer/src/isync_engine.h create mode 100644 mock/distributeddb/syncer/src/isync_state_machine.h create mode 100644 mock/distributeddb/syncer/src/isync_target.h create mode 100644 mock/distributeddb/syncer/src/isync_task_context.h create mode 100644 mock/distributeddb/syncer/src/meta_data.cpp create mode 100644 mock/distributeddb/syncer/src/meta_data.h create mode 100644 mock/distributeddb/syncer/src/multi_ver_data_sync.cpp create mode 100644 mock/distributeddb/syncer/src/multi_ver_data_sync.h create mode 100644 mock/distributeddb/syncer/src/multi_ver_sync_engine.cpp create mode 100644 mock/distributeddb/syncer/src/multi_ver_sync_engine.h create mode 100644 mock/distributeddb/syncer/src/multi_ver_sync_state_machine.cpp create mode 100644 mock/distributeddb/syncer/src/multi_ver_sync_state_machine.h create mode 100644 mock/distributeddb/syncer/src/multi_ver_sync_target.h create mode 100644 mock/distributeddb/syncer/src/multi_ver_sync_task_context.cpp create mode 100644 mock/distributeddb/syncer/src/multi_ver_sync_task_context.h create mode 100644 mock/distributeddb/syncer/src/multi_ver_syncer.cpp create mode 100644 mock/distributeddb/syncer/src/multi_ver_syncer.h create mode 100644 mock/distributeddb/syncer/src/query_sync_water_mark_helper.cpp create mode 100644 mock/distributeddb/syncer/src/query_sync_water_mark_helper.h create mode 100644 mock/distributeddb/syncer/src/single_ver_data_message_schedule.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_data_message_schedule.h create mode 100644 mock/distributeddb/syncer/src/single_ver_data_packet.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_data_packet.h create mode 100644 mock/distributeddb/syncer/src/single_ver_data_sync.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_data_sync.h create mode 100644 mock/distributeddb/syncer/src/single_ver_data_sync_utils.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_data_sync_utils.h create mode 100644 mock/distributeddb/syncer/src/single_ver_kv_sync_task_context.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_kv_sync_task_context.h create mode 100644 mock/distributeddb/syncer/src/single_ver_kv_syncer.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_kv_syncer.h create mode 100644 mock/distributeddb/syncer/src/single_ver_relational_sync_task_context.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_relational_sync_task_context.h create mode 100644 mock/distributeddb/syncer/src/single_ver_relational_syncer.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_relational_syncer.h create mode 100644 mock/distributeddb/syncer/src/single_ver_serialize_manager.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_serialize_manager.h create mode 100644 mock/distributeddb/syncer/src/single_ver_sync_engine.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_sync_engine.h create mode 100644 mock/distributeddb/syncer/src/single_ver_sync_state_machine.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_sync_state_machine.h create mode 100644 mock/distributeddb/syncer/src/single_ver_sync_target.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_sync_target.h create mode 100644 mock/distributeddb/syncer/src/single_ver_sync_task_context.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_sync_task_context.h create mode 100644 mock/distributeddb/syncer/src/single_ver_syncer.cpp create mode 100644 mock/distributeddb/syncer/src/single_ver_syncer.h create mode 100644 mock/distributeddb/syncer/src/subscribe_manager.cpp create mode 100644 mock/distributeddb/syncer/src/subscribe_manager.h create mode 100644 mock/distributeddb/syncer/src/sync_config.cpp create mode 100644 mock/distributeddb/syncer/src/sync_config.h create mode 100644 mock/distributeddb/syncer/src/sync_engine.cpp create mode 100644 mock/distributeddb/syncer/src/sync_engine.h create mode 100644 mock/distributeddb/syncer/src/sync_operation.cpp create mode 100644 mock/distributeddb/syncer/src/sync_operation.h create mode 100644 mock/distributeddb/syncer/src/sync_state_machine.cpp create mode 100644 mock/distributeddb/syncer/src/sync_state_machine.h create mode 100644 mock/distributeddb/syncer/src/sync_target.cpp create mode 100644 mock/distributeddb/syncer/src/sync_target.h create mode 100644 mock/distributeddb/syncer/src/sync_task_context.cpp create mode 100644 mock/distributeddb/syncer/src/sync_task_context.h create mode 100644 mock/distributeddb/syncer/src/sync_types.h create mode 100644 mock/distributeddb/syncer/src/syncer_factory.cpp create mode 100644 mock/distributeddb/syncer/src/syncer_factory.h create mode 100644 mock/distributeddb/syncer/src/syncer_proxy.cpp create mode 100644 mock/distributeddb/syncer/src/time_helper.cpp create mode 100644 mock/distributeddb/syncer/src/time_helper.h create mode 100644 mock/distributeddb/syncer/src/time_sync.cpp create mode 100644 mock/distributeddb/syncer/src/time_sync.h create mode 100644 mock/distributeddb/syncer/src/value_slice_sync.cpp create mode 100644 mock/distributeddb/syncer/src/value_slice_sync.h create mode 100644 mock/distributeddb/test/BUILD.gn create mode 100644 mock/distributeddb/test/fuzztest/common/distributeddb_tools_test.cpp create mode 100644 mock/distributeddb/test/fuzztest/common/distributeddb_tools_test.h create mode 100644 mock/distributeddb/test/fuzztest/delegate_fuzzer/BUILD.gn create mode 100644 mock/distributeddb/test/fuzztest/delegate_fuzzer/corpus/init create mode 100644 mock/distributeddb/test/fuzztest/delegate_fuzzer/delegate_fuzzer.cpp create mode 100644 mock/distributeddb/test/fuzztest/delegate_fuzzer/delegate_fuzzer.h create mode 100644 mock/distributeddb/test/fuzztest/delegate_fuzzer/project.xml create mode 100644 mock/distributeddb/test/fuzztest/fileoper_fuzzer/BUILD.gn create mode 100644 mock/distributeddb/test/fuzztest/fileoper_fuzzer/corpus/init create mode 100644 mock/distributeddb/test/fuzztest/fileoper_fuzzer/fileoper_fuzzer.cpp create mode 100644 mock/distributeddb/test/fuzztest/fileoper_fuzzer/fileoper_fuzzer.h create mode 100644 mock/distributeddb/test/fuzztest/fileoper_fuzzer/project.xml create mode 100644 mock/distributeddb/test/fuzztest/importfile_fuzzer/BUILD.gn create mode 100644 mock/distributeddb/test/fuzztest/importfile_fuzzer/corpus/init create mode 100644 mock/distributeddb/test/fuzztest/importfile_fuzzer/importfile_fuzzer.cpp create mode 100644 mock/distributeddb/test/fuzztest/importfile_fuzzer/importfile_fuzzer.h create mode 100644 mock/distributeddb/test/fuzztest/importfile_fuzzer/project.xml create mode 100644 mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/BUILD.gn create mode 100644 mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/corpus/init create mode 100644 mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/iprocesscommunicator_fuzzer.cpp create mode 100644 mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/iprocesscommunicator_fuzzer.h create mode 100644 mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/project.xml create mode 100644 mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/BUILD.gn create mode 100644 mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/corpus/init create mode 100644 mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/kvstoreresultset_fuzzer.cpp create mode 100644 mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/kvstoreresultset_fuzzer.h create mode 100644 mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/project.xml create mode 100644 mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/BUILD.gn create mode 100644 mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/corpus/init create mode 100644 mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/nbdelegate_fuzzer.cpp create mode 100644 mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/nbdelegate_fuzzer.h create mode 100644 mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/project.xml create mode 100644 mock/distributeddb/test/fuzztest/parseckeck_fuzzer/BUILD.gn create mode 100644 mock/distributeddb/test/fuzztest/parseckeck_fuzzer/corpus/init create mode 100644 mock/distributeddb/test/fuzztest/parseckeck_fuzzer/parseckeck_fuzzer.cpp create mode 100644 mock/distributeddb/test/fuzztest/parseckeck_fuzzer/parseckeck_fuzzer.h create mode 100644 mock/distributeddb/test/fuzztest/parseckeck_fuzzer/project.xml create mode 100644 mock/distributeddb/test/fuzztest/query_fuzzer/BUILD.gn create mode 100644 mock/distributeddb/test/fuzztest/query_fuzzer/corpus/init create mode 100644 mock/distributeddb/test/fuzztest/query_fuzzer/project.xml create mode 100644 mock/distributeddb/test/fuzztest/query_fuzzer/query_fuzzer.cpp create mode 100644 mock/distributeddb/test/fuzztest/query_fuzzer/query_fuzzer.h create mode 100644 mock/distributeddb/test/fuzztest/rekey_fuzzer/BUILD.gn create mode 100644 mock/distributeddb/test/fuzztest/rekey_fuzzer/corpus/init create mode 100644 mock/distributeddb/test/fuzztest/rekey_fuzzer/project.xml create mode 100644 mock/distributeddb/test/fuzztest/rekey_fuzzer/rekey_fuzzer.cpp create mode 100644 mock/distributeddb/test/fuzztest/rekey_fuzzer/rekey_fuzzer.h create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_auto_launch_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_common_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_data_compression_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.h create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_json_precheck_unit_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_notification_chain_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_parcel_unit_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_relational_schema_object_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_schema_object_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_schema_unit_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_tools_unit_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/common/distributeddb_tools_unit_test.h create mode 100644 mock/distributeddb/test/unittest/common/common/evloop_timer_unit_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/common/process_communicator_test_stub.h create mode 100644 mock/distributeddb/test/unittest/common/communicator/adapter_stub.cpp create mode 100644 mock/distributeddb/test/unittest/common/communicator/adapter_stub.h create mode 100644 mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_common.cpp create mode 100644 mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_common.h create mode 100644 mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_deep_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_send_receive_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_auto_launch_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_data_operation_syncdb_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_data_operation_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_data_value_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_database_corrupt_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_database_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_device_identifier_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_encrypt_database_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_encrypt_delegate_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_import_and_export_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_index_unit_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_delegate_local_batch_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_delegate_schema_put_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_delegate_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_publish_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_transaction_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_unpublish_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_query_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_register_syncdb_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_relational_sync_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_relational_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_resultset_performance.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_schema_database_upgrade_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_single_version_result_set_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_space_management_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_optimization_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_syncdb_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_testcase.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_testcase.h create mode 100644 mock/distributeddb/test/unittest/common/interfaces/process_system_api_adapter_impl.cpp create mode 100644 mock/distributeddb/test/unittest/common/interfaces/process_system_api_adapter_impl.h create mode 100644 mock/distributeddb/test/unittest/common/interfaces/runtime_context_process_system_api_adapter_impl_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_data_transformer_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_file_package_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_multi_ver_vacuum_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_query_object_helper_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_relational_get_data_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_sqlite_register_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_commit_storage_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_data_operation_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_encrypt_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_index_optimize_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_memory_single_ver_naturall_store_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_query_sync_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_register_conflict_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_register_observer_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_resultset_and_json_optimize.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_single_ver_natural_store_testcase.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_single_ver_natural_store_testcase.h create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_single_ver_upgrade_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_sqlite_single_ver_natural_store_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_subscribe_query_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_transaction_data_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/distributeddb_storage_transaction_record_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/multi_ver_vacuum_executor_stub.cpp create mode 100644 mock/distributeddb/test/unittest/common/storage/multi_ver_vacuum_executor_stub.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_ability_sync_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_anti_dos_sync_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_communicator_proxy_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_mock_sync_module_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_multi_ver_p2p_sync_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_relational_ver_p2p_sync_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_msg_schedule_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_multi_user_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_query_sync_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_subsribe_sync_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_sync_check_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_sync_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_syncer_device_manager_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/distributeddb_time_sync_test.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/generic_virtual_device.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/generic_virtual_device.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/kv_virtual_device.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/kv_virtual_device.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/mock_auto_launch.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/mock_communicator.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/mock_meta_data.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/mock_single_ver_data_sync.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/mock_single_ver_state_machine.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/mock_sync_task_context.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/relational_virtual_device.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/relational_virtual_device.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/virtual_communicator.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/virtual_communicator.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/virtual_communicator_aggregator.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/virtual_communicator_aggregator.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/virtual_multi_ver_sync_db_interface.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/virtual_multi_ver_sync_db_interface.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/virtual_relational_ver_sync_db_interface.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/virtual_relational_ver_sync_db_interface.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/virtual_single_ver_sync_db_Interface.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/virtual_single_ver_sync_db_Interface.h create mode 100644 mock/distributeddb/test/unittest/common/syncer/virtual_time_sync_communicator.cpp create mode 100644 mock/distributeddb/test/unittest/common/syncer/virtual_time_sync_communicator.h create mode 100644 mock/innerkits/distributeddatamgr/objectstore/iobject_callback.h create mode 100644 mock/innerkits/distributeddatamgr/objectstore/iobject_service.h create mode 100644 mock/innerkits/distributeddatamgr/objectstore/object_service.h create mode 100644 mock/innerkits/distributeddatamgr/objectstore/object_service_proxy.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3619645d..4f706950 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,8 @@ add_subdirectory(relational_store) add_subdirectory(data_share) #add_subdirectory(kv_store) add_subdirectory(preferences) +add_subdirectory(data_object) +add_subdirectory(mock/distributeddb) set(CMAKE_CXX_FLAGS "-std=c++1y -fno-rtti -fvisibility=default -D_GNU_SOURCE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -fPIC -fpic -ffunction-sections -D_GLIBC_MOCK") @@ -30,7 +32,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/mock/innerkits/ipc/ipc_single/in include_directories(${CMAKE_CURRENT_SOURCE_DIR}/mock/innerkits/ipc/libdbinder/include) set(MAIN_BINARY ${PROJECT_NAME}) -set(links secure mock relational_store data_share preferences jsoncpp crypto) +set(links secure mock relational_store data_share preferences jsoncpp crypto libs data_object) set(links secure mock) add_executable(${MAIN_BINARY} dllmain.cpp) target_link_libraries(${MAIN_BINARY} ${links}) diff --git a/mock/CMakeLists.txt b/mock/CMakeLists.txt index 92bb7858..77639735 100644 --- a/mock/CMakeLists.txt +++ b/mock/CMakeLists.txt @@ -22,7 +22,6 @@ add_definitions(-DL_ENDIAN -DUNICODE -D_UNICODE -DWIN32_LEAN_AND_MEAN -D_MT -DWI add_definitions(-DNAPI_EXPERIMENTAL -DSQLITE_DISTRIBUTE_RELATIONAL) aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src mockSrc) aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/sqlite/src mockSrc) - include_directories(${CMAKE_CURRENT_SOURCE_DIR}/) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/napi) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/napi/src) diff --git a/mock/distributeddb/BUILD.gn b/mock/distributeddb/BUILD.gn new file mode 100644 index 00000000..deb0bbbe --- /dev/null +++ b/mock/distributeddb/BUILD.gn @@ -0,0 +1,280 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# 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. +import("//build/ohos.gni") + +config("distrdb_config") { + visibility = [ ":*" ] + include_dirs = [ + "include", + "interfaces/include", + "interfaces/src", + "interfaces/src/relational", + "common/include", + "common/include/relational", + "communicator/include", + "storage/include", + "storage/src", + "storage/src/multiver", + "storage/src/operation", + "storage/src/sqlite", + "storage/src/sqlite/relational", + "storage/src/upgrader", + "syncer/include", + "syncer/src", + "//third_party/openssl/include/", + ] + + defines = [ + "_LARGEFILE64_SOURCE", + "_FILE_OFFSET_BITS=64", + "SQLITE_HAS_CODEC", + "SQLITE_ENABLE_JSON1", + "USING_HILOG_LOGGER", + "USE_SQLITE_SYMBOLS", + "USING_DB_JSON_EXTRACT_AUTOMATICALLY", + "JSONCPP_USE_BUILDER", + "OMIT_FLATBUFFER", + "RELATIONAL_STORE", + "SQLITE_DISTRIBUTE_RELATIONAL", + "USE_DFX_ABILITY", + ] + if (is_debug) { + defines += [ "TRACE_SQLITE_EXECUTE" ] + } +} + +config("distrdb_public_config") { + visibility = [ "*:*" ] + include_dirs = [ + "interfaces/include", + "interfaces/include/relational", + "include", + ] +} + +group("build_module") { + deps = [ ":distributeddb" ] +} + +ohos_shared_library("distributeddb") { + sources = [ + "common/src/auto_launch.cpp", + "common/src/data_compression.cpp", + "common/src/data_value.cpp", + "common/src/db_common.cpp", + "common/src/db_constant.cpp", + "common/src/db_dfx_adapter.cpp", + "common/src/db_dump_helper.cpp", + "common/src/evloop/src/event_impl.cpp", + "common/src/evloop/src/event_loop_epoll.cpp", + "common/src/evloop/src/event_loop_impl.cpp", + "common/src/evloop/src/event_loop_select.cpp", + "common/src/evloop/src/ievent.cpp", + "common/src/evloop/src/ievent_loop.cpp", + "common/src/flatbuffer_schema.cpp", + "common/src/hash.cpp", + "common/src/json_object.cpp", + "common/src/lock_status_observer.cpp", + "common/src/log_print.cpp", + "common/src/notification_chain.cpp", + "common/src/param_check_utils.cpp", + "common/src/parcel.cpp", + "common/src/performance_analysis.cpp", + "common/src/platform_specific.cpp", + "common/src/query.cpp", + "common/src/query_expression.cpp", + "common/src/ref_object.cpp", + "common/src/relational/relational_schema_object.cpp", + "common/src/runtime_context.cpp", + "common/src/runtime_context_impl.cpp", + "common/src/schema_constant.cpp", + "common/src/schema_negotiate.cpp", + "common/src/schema_object.cpp", + "common/src/schema_utils.cpp", + "common/src/semaphore_utils.cpp", + "common/src/task_pool.cpp", + "common/src/task_pool_impl.cpp", + "common/src/task_queue.cpp", + "common/src/time_tick_monitor.cpp", + "common/src/types_export.cpp", + "common/src/user_change_monitor.cpp", + "common/src/value_object.cpp", + "common/src/zlib_compression.cpp", + "communicator/src/combine_status.cpp", + "communicator/src/communicator.cpp", + "communicator/src/communicator_aggregator.cpp", + "communicator/src/communicator_linker.cpp", + "communicator/src/frame_combiner.cpp", + "communicator/src/frame_retainer.cpp", + "communicator/src/header_converter.cpp", + "communicator/src/message_transform.cpp", + "communicator/src/network_adapter.cpp", + "communicator/src/protocol_proto.cpp", + "communicator/src/send_task_scheduler.cpp", + "communicator/src/serial_buffer.cpp", + "interfaces/src/intercepted_data_impl.cpp", + "interfaces/src/kv_store_changed_data_impl.cpp", + "interfaces/src/kv_store_delegate_impl.cpp", + "interfaces/src/kv_store_delegate_manager.cpp", + "interfaces/src/kv_store_errno.cpp", + "interfaces/src/kv_store_nb_conflict_data_impl.cpp", + "interfaces/src/kv_store_nb_delegate_impl.cpp", + "interfaces/src/kv_store_result_set_impl.cpp", + "interfaces/src/kv_store_snapshot_delegate_impl.cpp", + "interfaces/src/relational/relational_store_changed_data_impl.cpp", + "interfaces/src/relational/relational_store_delegate_impl.cpp", + "interfaces/src/relational/relational_store_manager.cpp", + "interfaces/src/relational/relational_store_sqlite_ext.cpp", + "interfaces/src/relational/runtime_config.cpp", + "storage/src/data_transformer.cpp", + "storage/src/db_properties.cpp", + "storage/src/default_factory.cpp", + "storage/src/generic_kvdb.cpp", + "storage/src/generic_kvdb_connection.cpp", + "storage/src/generic_single_ver_kv_entry.cpp", + "storage/src/iconnection.cpp", + "storage/src/ikvdb_factory.cpp", + "storage/src/kvdb_commit_notify_filterable_data.cpp", + "storage/src/kvdb_manager.cpp", + "storage/src/kvdb_observer_handle.cpp", + "storage/src/kvdb_properties.cpp", + "storage/src/kvdb_utils.cpp", + "storage/src/kvdb_windowed_result_set.cpp", + "storage/src/multiver/generic_multi_ver_kv_entry.cpp", + "storage/src/multiver/multi_ver_commit.cpp", + "storage/src/multiver/multi_ver_kvdata_storage.cpp", + "storage/src/multiver/multi_ver_natural_store.cpp", + "storage/src/multiver/multi_ver_natural_store_commit_notify_data.cpp", + "storage/src/multiver/multi_ver_natural_store_commit_storage.cpp", + "storage/src/multiver/multi_ver_natural_store_connection.cpp", + "storage/src/multiver/multi_ver_natural_store_snapshot.cpp", + "storage/src/multiver/multi_ver_natural_store_transfer_data.cpp", + "storage/src/multiver/multi_ver_storage_engine.cpp", + "storage/src/multiver/multi_ver_storage_executor.cpp", + "storage/src/multiver/multi_ver_vacuum.cpp", + "storage/src/multiver/multi_ver_vacuum_executor_impl.cpp", + "storage/src/multiver/multi_ver_value_object.cpp", + "storage/src/operation/database_oper.cpp", + "storage/src/operation/local_database_oper.cpp", + "storage/src/operation/multi_ver_database_oper.cpp", + "storage/src/operation/single_ver_database_oper.cpp", + "storage/src/package_file.cpp", + "storage/src/relational_store_connection.cpp", + "storage/src/relational_store_instance.cpp", + "storage/src/relational_sync_able_storage.cpp", + "storage/src/relationaldb_properties.cpp", + "storage/src/result_entries_window.cpp", + "storage/src/single_ver_natural_store_commit_notify_data.cpp", + "storage/src/sqlite/query_object.cpp", + "storage/src/sqlite/query_sync_object.cpp", + "storage/src/sqlite/relational/sqlite_relational_store.cpp", + "storage/src/sqlite/relational/sqlite_relational_store_connection.cpp", + "storage/src/sqlite/relational/sqlite_single_relational_storage_engine.cpp", + "storage/src/sqlite/sqlite_local_kvdb.cpp", + "storage/src/sqlite/sqlite_local_kvdb_connection.cpp", + "storage/src/sqlite/sqlite_local_kvdb_snapshot.cpp", + "storage/src/sqlite/sqlite_local_storage_engine.cpp", + "storage/src/sqlite/sqlite_local_storage_executor.cpp", + "storage/src/sqlite/sqlite_multi_ver_data_storage.cpp", + "storage/src/sqlite/sqlite_multi_ver_transaction.cpp", + "storage/src/sqlite/sqlite_query_helper.cpp", + "storage/src/sqlite/sqlite_single_ver_continue_token.cpp", + "storage/src/sqlite/sqlite_single_ver_database_upgrader.cpp", + "storage/src/sqlite/sqlite_single_ver_forward_cursor.cpp", + "storage/src/sqlite/sqlite_single_ver_natural_store.cpp", + "storage/src/sqlite/sqlite_single_ver_natural_store_connection.cpp", + "storage/src/sqlite/sqlite_single_ver_relational_continue_token.cpp", + "storage/src/sqlite/sqlite_single_ver_relational_storage_executor.cpp", + "storage/src/sqlite/sqlite_single_ver_result_set.cpp", + "storage/src/sqlite/sqlite_single_ver_schema_database_upgrader.cpp", + "storage/src/sqlite/sqlite_single_ver_storage_engine.cpp", + "storage/src/sqlite/sqlite_single_ver_storage_executor.cpp", + "storage/src/sqlite/sqlite_single_ver_storage_executor_cache.cpp", + "storage/src/sqlite/sqlite_single_ver_storage_executor_subscribe.cpp", + "storage/src/sqlite/sqlite_storage_engine.cpp", + "storage/src/sqlite/sqlite_storage_executor.cpp", + "storage/src/sqlite/sqlite_utils.cpp", + "storage/src/storage_engine.cpp", + "storage/src/storage_engine_manager.cpp", + "storage/src/storage_executor.cpp", + "storage/src/sync_able_engine.cpp", + "storage/src/sync_able_kvdb.cpp", + "storage/src/sync_able_kvdb_connection.cpp", + "storage/src/upgrader/single_ver_database_upgrader.cpp", + "storage/src/upgrader/single_ver_schema_database_upgrader.cpp", + "syncer/src/ability_sync.cpp", + "syncer/src/commit_history_sync.cpp", + "syncer/src/communicator_proxy.cpp", + "syncer/src/db_ability.cpp", + "syncer/src/device_manager.cpp", + "syncer/src/generic_syncer.cpp", + "syncer/src/meta_data.cpp", + "syncer/src/multi_ver_data_sync.cpp", + "syncer/src/multi_ver_sync_engine.cpp", + "syncer/src/multi_ver_sync_state_machine.cpp", + "syncer/src/multi_ver_sync_task_context.cpp", + "syncer/src/multi_ver_syncer.cpp", + "syncer/src/query_sync_water_mark_helper.cpp", + "syncer/src/single_ver_data_message_schedule.cpp", + "syncer/src/single_ver_data_packet.cpp", + "syncer/src/single_ver_data_sync.cpp", + "syncer/src/single_ver_data_sync_utils.cpp", + "syncer/src/single_ver_kv_sync_task_context.cpp", + "syncer/src/single_ver_kv_syncer.cpp", + "syncer/src/single_ver_relational_sync_task_context.cpp", + "syncer/src/single_ver_relational_syncer.cpp", + "syncer/src/single_ver_serialize_manager.cpp", + "syncer/src/single_ver_sync_engine.cpp", + "syncer/src/single_ver_sync_state_machine.cpp", + "syncer/src/single_ver_sync_target.cpp", + "syncer/src/single_ver_sync_task_context.cpp", + "syncer/src/single_ver_syncer.cpp", + "syncer/src/subscribe_manager.cpp", + "syncer/src/sync_config.cpp", + "syncer/src/sync_engine.cpp", + "syncer/src/sync_operation.cpp", + "syncer/src/sync_state_machine.cpp", + "syncer/src/sync_target.cpp", + "syncer/src/sync_task_context.cpp", + "syncer/src/syncer_factory.cpp", + "syncer/src/syncer_proxy.cpp", + "syncer/src/time_helper.cpp", + "syncer/src/time_sync.cpp", + "syncer/src/value_slice_sync.cpp", + ] + + configs = [ ":distrdb_config" ] + public_configs = [ ":distrdb_public_config" ] + + deps = [ + "//third_party/sqlite:sqlite", + "//third_party/zlib:libz", + "//utils/native/base:utils", + ] + + configs += [ "//third_party/jsoncpp:jsoncpp_config" ] + ldflags = [ "-Wl,--exclude-libs,ALL" ] + deps += [ + "//third_party/jsoncpp:jsoncpp", + "//third_party/openssl:libcrypto_shared", + ] + + external_deps = [ + "hisysevent_native:libhisysevent", + "hitrace_native:hitrace_meter", + "hiviewdfx_hilog_native:libhilog", + ] + + subsystem_name = "distributeddatamgr" + part_name = "distributeddatamgr" +} diff --git a/mock/distributeddb/CMakeLists.txt b/mock/distributeddb/CMakeLists.txt new file mode 100644 index 00000000..9a3901b1 --- /dev/null +++ b/mock/distributeddb/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.10.2) +project(libs) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -fno-rtti -fvisibility=default -D_GNU_SOURCE") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -fPIC -fpic -ffunction-sections -D_GLIBC_MOCK") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-as-needed -ldl") +set(MOCK_DIR ../../mock) +add_definitions(-DUSE_HARMONY_SQLITE -DUSING_HILOG_LOGGER -DOMIT_FLATBUFFER -DEVLOOP_TIMER_ONLY) #-DOMIT_JSON +add_definitions(-DUSING_DB_JSON_EXTRACT_AUTOMATICALLY -DSQLITE_ENABLE_JSON1 -DRELATIONAL_STORE -DOMIT_ZLIB) +add_definitions(-DWINDOWS_PLATFORM -DSQLITE_HAS_CODEC -DJSONCPP_USE_BUILDER -DEKEYREVOKED=128) +add_definitions(-Dfseeko64=fseeko) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/common/src libsSrc) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/common/src/evloop/src libsSrc) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/common/src/relational libsSrc) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/communicator/src libsSrc) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/interfaces/src libsSrc) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/interfaces/src/relational libsSrc) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/storage/src libsSrc) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/storage/src/multiver libsSrc) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/storage/src/operation libsSrc) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/storage/src/sqlite libsSrc) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/storage/src/sqlite/relational libsSrc) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/storage/src/upgrader libsSrc) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/syncer/src libsSrc) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/interfaces/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/interfaces/include/relational) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/interfaces/src) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/interfaces/src/relational) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/common/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/common/include/relational) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/storage/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/storage/src) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/storage/src/multiver) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/storage/src/sqlite) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/storage/src/sqlite/relational) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/storage/src/operation) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/storage/src/upgrader) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/syncer/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/syncer/src) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/communicator/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../utils_native/base/include) +include_directories(/usr/include/jsoncpp) +include_directories(${MOCK_DIR}) +include_directories(${MOCK_DIR}/sqlite/include) +include_directories(${MOCK_DIR}/innerkits/hilog_native/libhilog/include) + +set(links secure mock jsoncpp crypto) +add_library(libs SHARED ${libsSrc}) +target_link_libraries(libs ${links}) diff --git a/mock/distributeddb/common/include/auto_launch.h b/mock/distributeddb/common/include/auto_launch.h new file mode 100644 index 00000000..0aa1bcb8 --- /dev/null +++ b/mock/distributeddb/common/include/auto_launch.h @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 AUTO_LAUNCH_H +#define AUTO_LAUNCH_H + +#include +#include +#include +#include "auto_launch_export.h" +#include "db_properties.h" +#include "ikvdb_connection.h" +#include "icommunicator_aggregator.h" +#include "kv_store_observer.h" +#include "kvdb_properties.h" +#include "types_export.h" +#include "relational_store_connection.h" +#include "relationaldb_properties.h" +#include "store_observer.h" + +namespace DistributedDB { +enum class AutoLaunchItemState { + UN_INITIAL = 0, + IN_ENABLE, + IN_LIFE_CYCLE_CALL_BACK, // in LifeCycleCallback + IN_COMMUNICATOR_CALL_BACK, // in OnConnectCallback or CommunicatorLackCallback + IDLE, +}; + +enum class DBType { + DB_KV = 0, + DB_RELATION, + DB_INVALID, +}; + +struct AutoLaunchItem { + std::shared_ptr propertiesPtr; + AutoLaunchNotifier notifier; + KvStoreObserver *observer = nullptr; + int conflictType = 0; + KvStoreNbConflictNotifier conflictNotifier; + void *conn = nullptr; + KvDBObserverHandle *observerHandle = nullptr; + bool isWriteOpenNotified = false; + AutoLaunchItemState state = AutoLaunchItemState::UN_INITIAL; + bool isDisable = false; + bool inObserver = false; + bool isAutoSync = true; + DBType type = DBType::DB_INVALID; + StoreObserver *storeObserver = nullptr; +}; + +class AutoLaunch { +public: + static int GetAutoLaunchProperties(const AutoLaunchParam ¶m, const DBType &openType, bool checkDir, + std::shared_ptr &propertiesPtr); + + AutoLaunch() = default; + + virtual ~AutoLaunch(); + + DISABLE_COPY_ASSIGN_MOVE(AutoLaunch); + + void SetCommunicatorAggregator(ICommunicatorAggregator *aggregator); + + int EnableKvStoreAutoLaunch(const KvDBProperties &properties, AutoLaunchNotifier notifier, + const AutoLaunchOption &option); + + int DisableKvStoreAutoLaunch(const std::string &normalIdentifier, const std::string &dualTupleIdentifier, + const std::string &userId); + + void GetAutoLaunchSyncDevices(const std::string &identifier, std::vector &devices) const; + + void SetAutoLaunchRequestCallback(const AutoLaunchRequestCallback &callback, DBType type); + + void Dump(int fd); + +protected: + static int OpenOneConnection(AutoLaunchItem &autoLaunchItem); + + // we will return errCode, if errCode != E_OK + static int CloseConnectionStrict(AutoLaunchItem &autoLaunchItem); + + static void CloseNotifier(const AutoLaunchItem &autoLaunchItem); + + static int SetConflictNotifier(AutoLaunchItem &autoLaunchItem); + + static int GetAutoLaunchKVProperties(const AutoLaunchParam ¶m, + const std::shared_ptr &propertiesPtr, bool checkDir); + + static int GetAutoLaunchRelationProperties(const AutoLaunchParam ¶m, + const std::shared_ptr &propertiesPtr); + + static int OpenKvConnection(AutoLaunchItem &autoLaunchItem); + + static int OpenRelationalConnection(AutoLaunchItem &autoLaunchItem); + + static int PragmaAutoSync(AutoLaunchItem &autoLaunchItem); + + int EnableKvStoreAutoLaunchParmCheck(AutoLaunchItem &autoLaunchItem, const std::string &normalIdentifier, + const std::string &dualTupleIdentifier, bool isDualTupleMode); + + int GetKVConnectionInEnable(AutoLaunchItem &autoLaunchItem, const std::string &identifier); + + // before ReleaseDatabaseConnection, if errCode != E_OK, we not return, we try close more + virtual void TryCloseConnection(AutoLaunchItem &autoLaunchItem); + + int RegisterObserverAndLifeCycleCallback(AutoLaunchItem &autoLaunchItem, const std::string &identifier, + bool isExt); + + int RegisterObserver(AutoLaunchItem &autoLaunchItem, const std::string &identifier, bool isExt); + + void ObserverFunc(const KvDBCommitNotifyData ¬ifyData, const std::string &identifier, + const std::string &userId); + + void ConnectionLifeCycleCallbackTask(const std::string &identifier, const std::string &userId); + + void OnlineCallBackTask(); + + void GetDoOpenMap(std::map> &doOpenMap); + + void GetConnInDoOpenMap(std::map> &doOpenMap); + + void UpdateGlobalMap(std::map> &doOpenMap); + + void ReceiveUnknownIdentifierCallBackTask(const std::string &identifier, const std::string &userId); + + void ConnectionLifeCycleCallback(const std::string &identifier, const std::string &userId); + + void OnlineCallBack(const std::string &device, bool isConnect); + + int ReceiveUnknownIdentifierCallBack(const LabelType &label, const std::string &originalUserId); + + int AutoLaunchExt(const std::string &identifier, const std::string &userId); + + void AutoLaunchExtTask(const std::string &identifier, const std::string &userId, AutoLaunchItem &autoLaunchItem); + + void ExtObserverFunc(const KvDBCommitNotifyData ¬ifyData, const std::string &identifier, + const std::string &userId); + + void ExtConnectionLifeCycleCallback(const std::string &identifier, const std::string &userId); + + void ExtConnectionLifeCycleCallbackTask(const std::string &identifier, const std::string &userId); + + int ExtAutoLaunchRequestCallBack(const std::string &identifier, AutoLaunchParam ¶m, DBType &openType); + + int RegisterLifeCycleCallback(AutoLaunchItem &autoLaunchItem, const std::string &identifier, bool isExt); + + void TryCloseKvConnection(AutoLaunchItem &autoLaunchItem); + + void TryCloseRelationConnection(AutoLaunchItem &autoLaunchItem); + + void EraseAutoLauchItem(const std::string &identifier, const std::string &userId); + + void NotifyInvalidParam(const AutoLaunchItem &autoLaunchItem); + + int CheckAutoLaunchRealPath(const AutoLaunchItem &autoLaunchItem); + + mutable std::mutex dataLock_; + mutable std::mutex communicatorLock_; + std::set onlineDevices_; + // key: label, value: + std::map> autoLaunchItemMap_; + ICommunicatorAggregator *communicatorAggregator_ = nullptr; + std::condition_variable cv_; + + std::mutex extLock_; + std::map autoLaunchRequestCallbackMap_; + // key: label, value: + std::map> extItemMap_; +}; +} // namespace DistributedDB +#endif // AUTO_LAUNCH_H diff --git a/mock/distributeddb/common/include/data_compression.h b/mock/distributeddb/common/include/data_compression.h new file mode 100644 index 00000000..449fe7a3 --- /dev/null +++ b/mock/distributeddb/common/include/data_compression.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DATA_COMPRESSION_H +#define DATA_COMPRESSION_H + +#include +#include +#include + +#include "types_export.h" + +namespace DistributedDB { +class DataCompression { +public: + static DataCompression *GetInstance(CompressAlgorithm algo); + static void GetCompressionAlgo(std::set &algorithmSet); + static int TransferCompressionAlgo(uint32_t compressAlgoType, CompressAlgorithm &algoType); + + virtual int Compress(const std::vector &srcData, std::vector &destData) const = 0; + virtual int Uncompress(const std::vector &srcData, std::vector &destData, uint32_t destLen) + const = 0; + +protected: + DataCompression() = default; + virtual ~DataCompression() = default; + DataCompression(const DataCompression& compression) = delete; + DataCompression& operator= (const DataCompression& compression) = delete; + + static void Register(CompressAlgorithm algo, DataCompression *compression); + +private: + static std::map &GetCompressionAlgos(); + static std::map &GetTransMap(); +}; +} // namespace DistributedDB +#endif // DATA_COMPRESSION_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/data_value.h b/mock/distributeddb/common/include/data_value.h new file mode 100644 index 00000000..f355f7f5 --- /dev/null +++ b/mock/distributeddb/common/include/data_value.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTED_DB_DATA_VALUE_H +#define DISTRIBUTED_DB_DATA_VALUE_H + +#include +#include +#include +#include +namespace DistributedDB { +// field types stored in sqlite +enum class StorageType { + STORAGE_TYPE_NONE = 0, + STORAGE_TYPE_NULL, + STORAGE_TYPE_INTEGER, + STORAGE_TYPE_REAL, + STORAGE_TYPE_TEXT, + STORAGE_TYPE_BLOB +}; + +class Blob { +public: + Blob(); + ~Blob(); + + Blob(Blob &&); + Blob(const Blob &) = delete; + Blob &operator=(Blob &&) noexcept; + Blob &operator=(const Blob &) = delete; + + const uint8_t* GetData() const; + uint32_t GetSize() const; + + int WriteBlob(const uint8_t *ptrArray, const uint32_t &size); + +private: + uint8_t* ptr_; + uint32_t size_; +}; + +class DataValue { +public: + DataValue(); + ~DataValue(); + + // copy constructor + DataValue(const DataValue &dataValue); + DataValue &operator=(const DataValue &dataValue); + // move constructor + DataValue(DataValue &&dataValue) noexcept; + DataValue &operator=(DataValue &&dataValue) noexcept; + DataValue &operator=(int64_t intVal); + DataValue &operator=(double doubleVal); + DataValue &operator=(const Blob &blob); + DataValue &operator=(const std::string &string); + int Set(Blob *&blob); + + // equals + bool operator==(const DataValue &dataValue) const; + bool operator!=(const DataValue &dataValue) const; + + StorageType GetType() const; + int GetInt64(int64_t &outVal) const; + int GetDouble(double &outVal) const; + int GetBlob(Blob *&outVal) const; + int SetBlob(const Blob &val); + int GetBlob(Blob &outVal) const; + int SetText(const std::string &val); + int SetText(const uint8_t *val, uint32_t length); + int GetText(std::string &outVal) const; + void ResetValue(); + int GetBlobLength(uint32_t &length) const; + std::string ToString() const; + +private: + StorageType type_ = StorageType::STORAGE_TYPE_NULL; + union { + void* zeroMem; + Blob* blobPtr; + double dValue; + int64_t iValue; + } value_{}; +}; +} +#endif // DISTRIBUTED_DB_DATA_VALUE_H diff --git a/mock/distributeddb/common/include/db_common.h b/mock/distributeddb/common/include/db_common.h new file mode 100644 index 00000000..338475c6 --- /dev/null +++ b/mock/distributeddb/common/include/db_common.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_COMMON_H +#define DISTRIBUTEDDB_COMMON_H + +#include +#include +#include "db_types.h" +#include "store_types.h" +#include "kvdb_properties.h" + +namespace DistributedDB { +class DBCommon final { +public: + static int CreateDirectory(const std::string &directory); + + static void StringToVector(const std::string &src, std::vector &dst); + static void VectorToString(const std::vector &src, std::string &dst); + + static std::string VectorToHexString(const std::vector &inVec, const std::string &separator = ""); + + static void PrintHexVector(const std::vector &data, int line = 0, const std::string &tag = ""); + + static std::string TransferStringToHex(const std::string &origStr); + + static std::string TransferHashString(const std::string &devName); + + static int CalcValueHash(const std::vector &Value, std::vector &hashValue); + + static int CreateStoreDirectory(const std::string &directory, const std::string &identifierName, + const std::string &subDir, bool isCreate); + + static int CopyFile(const std::string &srcFile, const std::string &dstFile); + + static int RemoveAllFilesOfDirectory(const std::string &dir, bool isNeedRemoveDir = true); + + static std::string GenerateIdentifierId(const std::string &storeId, + const std::string &appId, const std::string &userId); + + static std::string GenerateDualTupleIdentifierId(const std::string &storeId, const std::string &appId); + + static void SetDatabaseIds(KvDBProperties &properties, const std::string &appId, const std::string &userId, + const std::string &storeId); + + static std::string StringMasking(const std::string &oriStr, size_t remain = 3); // remain 3 unmask + + static std::string GetDistributedTableName(const std::string &device, const std::string &tableName); + + static void GetDeviceFromName(const std::string &deviceTableName, std::string &deviceHash, std::string &tableName); +}; + +// Define short macro substitute for original long expression for convenience of using +#define VEC_TO_STR(x) DBCommon::VectorToHexString(x).c_str() +#define STR_MASK(x) DBCommon::StringMasking(x).c_str() +#define STR_TO_HEX(x) DBCommon::TransferStringToHex(x).c_str() +} // namespace DistributedDB + +#endif // DISTRIBUTEDDB_COMMON_H diff --git a/mock/distributeddb/common/include/db_constant.h b/mock/distributeddb/common/include/db_constant.h new file mode 100644 index 00000000..6ecd3149 --- /dev/null +++ b/mock/distributeddb/common/include/db_constant.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_CONSTANT_H +#define DISTRIBUTEDDB_CONSTANT_H + +#include + +namespace DistributedDB { +class DBConstant { +public: + static constexpr size_t MAX_KEY_SIZE = 1024; + static constexpr size_t MAX_VALUE_SIZE = 4194304; + static constexpr size_t MAX_BATCH_SIZE = 128; + static constexpr size_t MAX_DEV_LENGTH = 128; + static constexpr size_t MAX_TRANSACTION_ENTRY_SIZE = 128; + + static constexpr size_t MAX_DATA_DIR_LENGTH = 512; + + static constexpr size_t MAX_INKEYS_SIZE = 128; + + static constexpr int DB_TYPE_LOCAL = 1; + static constexpr int DB_TYPE_MULTI_VER = 2; + static constexpr int DB_TYPE_SINGLE_VER = 3; + + static constexpr int QUEUED_SYNC_LIMIT_DEFAULT = 32; + static constexpr int QUEUED_SYNC_LIMIT_MIN = 1; + static constexpr int QUEUED_SYNC_LIMIT_MAX = 4096; + + static constexpr int MAX_DEVICES_SIZE = 100; + static constexpr int MAX_COMMIT_SIZE = 1000000; + static constexpr int MAX_ENTRIES_SIZE = 1000000; + + static constexpr uint32_t MAX_COLUMN = 32767; + + // In querySync, when getting query data finished, + // if the block size reach the half of max block size, will get deleted data next; + // if the block size not reach the half of max block size, will not get deleted data. + static constexpr float QUERY_SYNC_THRESHOLD = 0.50; + + static constexpr uint64_t MAX_USER_ID_LENGTH = 128; + static constexpr uint64_t MAX_APP_ID_LENGTH = 128; + static constexpr uint64_t MAX_STORE_ID_LENGTH = 128; + + static const std::string MULTI_SUB_DIR; + static const std::string SINGLE_SUB_DIR; + static const std::string LOCAL_SUB_DIR; + + static const std::string MAINDB_DIR; + static const std::string METADB_DIR; + static const std::string CACHEDB_DIR; + + static const std::string LOCAL_DATABASE_NAME; + static const std::string MULTI_VER_DATA_STORE; + static const std::string MULTI_VER_COMMIT_STORE; + static const std::string MULTI_VER_VALUE_STORE; + static const std::string MULTI_VER_META_STORE; + static const std::string SINGLE_VER_DATA_STORE; + static const std::string SINGLE_VER_META_STORE; + static const std::string SINGLE_VER_CACHE_STORE; + + static const std::string SQLITE_URL_PRE; + static const std::string SQLITE_DB_EXTENSION; + static const std::string SQLITE_MEMDB_IDENTIFY; + static const std::string SCHEMA_KEY; + + static const std::string PATH_POSTFIX_UNPACKED; + static const std::string PATH_POSTFIX_IMPORT_BACKUP; + static const std::string PATH_POSTFIX_IMPORT_ORIGIN; + static const std::string PATH_POSTFIX_IMPORT_DUP; + static const std::string PATH_POSTFIX_EXPORT_BACKUP; + static const std::string PATH_POSTFIX_DB_INCOMPLETE; // use for make sure create datebase and set label complete + + static const std::string REKEY_FILENAME_POSTFIX_PRE; + static const std::string REKEY_FILENAME_POSTFIX_OK; + static const std::string UPGRADE_POSTFIX; + static const std::string SET_SECOPT_POSTFIX; // used for make sure meta split upgrade atomically + + static const std::string PATH_BACKUP_POSTFIX; + + static const std::string ID_CONNECTOR; + + static const std::string DELETE_KVSTORE_REMOVING; + static const std::string DB_LOCK_POSTFIX; + + static const std::string SUBSCRIBE_QUERY_PREFIX; + static const std::string TRIGGER_REFERENCES_NEW; + static const std::string TRIGGER_REFERENCES_OLD; + + static const std::string UPDATE_META_FUNC; + + static const std::string SYSTEM_TABLE_PREFIX; + + static constexpr uint32_t AUTO_SYNC_TIMEOUT = 5000; // 5s + static constexpr uint32_t MANUAL_SYNC_TIMEOUT = 5000; // 5s + + static const size_t MAX_NORMAL_PACK_ITEM_SIZE = 4000; + static const size_t MAX_HPMODE_PACK_ITEM_SIZE = 2000; // slide window mode to reduce last ack transfer time + + static constexpr uint32_t MIN_MTU_SIZE = 1024; // 1KB + static constexpr uint32_t MAX_MTU_SIZE = 5242880; // 5MB + + static constexpr uint32_t MIN_TIMEOUT = 5000; // 5s + static constexpr uint32_t MAX_TIMEOUT = 60000; // 60s + + static constexpr uint8_t DEFAULT_COMPTRESS_RATE = 100; + + static constexpr size_t MAX_SYNC_BLOCK_SIZE = 31457280; // 30MB + + static constexpr int DOUBLE_PRECISION = 15; + static constexpr int MAX_DISTRIBUTED_TABLE_COUNT = 32; + + static constexpr uint64_t MAX_LOG_SIZE_HIGH = 0x400000000ULL; // 16GB + static constexpr uint64_t MAX_LOG_SIZE_LOW = 0x400000ULL; // 4MB + static constexpr uint64_t MAX_LOG_SIZE_DEFAULT = 0x40000000ULL; // 1GB + + static constexpr int DEF_LIFE_CYCLE_TIME = 60000; // 60S + + static constexpr int RELATIONAL_LOG_TABLE_FIELD_NUM = 7; // field num is relational distributed log table + + static constexpr uint64_t IGNORE_CONNECTION_ID = 0; + // For relational + static const std::string RELATIONAL_PREFIX; + static const std::string TIMESTAMP_ALIAS; +}; +} // namespace DistributedDB + +#endif // DISTRIBUTEDDB_CONSTANT_H diff --git a/mock/distributeddb/common/include/db_dfx_adapter.h b/mock/distributeddb/common/include/db_dfx_adapter.h new file mode 100644 index 00000000..cc4f40cd --- /dev/null +++ b/mock/distributeddb/common/include/db_dfx_adapter.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DB_DFX_ADAPTER_H +#define DB_DFX_ADAPTER_H + +#include +#include + +namespace DistributedDB { +enum DBEventType { + FAULT = 1, + STATISTIC = 2, + SECURITY = 3, + BEHAVIOR = 4 +}; +struct ReportTask { + std::string eventName; + std::string appId; + std::string userId; + std::string storeId; + int errCode = 0; +}; +class DBDfxAdapter { +public: + static void Dump(int fd, const std::vector &args); + + static void ReportFault(const ReportTask &reportTask); + + static void StartTrace(const std::string &action); + static void FinishTrace(); + + static void StartTraceSQL(); + static void FinishTraceSQL(); + + static void StartAsyncTrace(const std::string &action, int32_t taskId); + static void FinishAsyncTrace(const std::string &action, int32_t taskId); + + static const std::string SYNC_ACTION; + static const std::string EVENT_OPEN_DATABASE_FAILED; +private: + static const std::string EVENT_CODE; + static const std::string APP_ID; + static const std::string USER_ID; + static const std::string STORE_ID; + static const std::string SQLITE_EXECUTE; +}; +} // namespace DistributedDB + +#endif // DB_DFX_ADAPTER_H diff --git a/mock/distributeddb/common/include/db_dump_helper.h b/mock/distributeddb/common/include/db_dump_helper.h new file mode 100644 index 00000000..9dcf8dcf --- /dev/null +++ b/mock/distributeddb/common/include/db_dump_helper.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DB_DUMP_HELPER_H +#define DB_DUMP_HELPER_H + +namespace DistributedDB { +class DBDumpHelper { +public: + static void Dump(int fd, const char *format, ...); +}; +} // namespace DistributedDB + +#endif // DB_DFX_ADAPTER_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/db_errno.h b/mock/distributeddb/common/include/db_errno.h new file mode 100644 index 00000000..63a19748 --- /dev/null +++ b/mock/distributeddb/common/include/db_errno.h @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_ERRNO_H +#define DISTRIBUTEDDB_ERRNO_H + +#include + +namespace DistributedDB { +constexpr int E_OK = 0; +constexpr int E_BASE = 1000; // different from the other errno. +constexpr int E_NOT_SUPPORT = (E_BASE + 1); // not support currently. +constexpr int E_INVALID_DB = (E_BASE + 2); // invalid db or connection. +constexpr int E_NOT_FOUND = (E_BASE + 3); // not found the resource. +constexpr int E_BUSY = (E_BASE + 4); // the db is busy +constexpr int E_UNEXPECTED_DATA = (E_BASE + 5); // Data does not match expectation. +constexpr int E_STALE = (E_BASE + 6); // Resource has been stopped, killed or destroyed. +constexpr int E_INVALID_ARGS = (E_BASE + 7); // the input args is invalid. +constexpr int E_REGISTER_OBSERVER = (E_BASE + 8); // error in register observer related function. +constexpr int E_TRANSACT_STATE = (E_BASE + 9); // transaction state error. +constexpr int E_SECUREC_ERROR = (E_BASE + 10); // security interface returns error +constexpr int E_OUT_OF_MEMORY = (E_BASE + 11); // out of memory +constexpr int E_NOT_PERMIT = (E_BASE + 12); // operation is not permitted +constexpr int E_ALREADY_REGISTER = (E_BASE + 13); // function or handle already registered and not allowed replace +constexpr int E_ALREADY_ALLOC = (E_BASE + 14); // Object had already been allocated +constexpr int E_ALREADY_RELEASE = (E_BASE + 15); // Object had already been released +constexpr int E_CONTAINER_FULL = (E_BASE + 16); // container full +constexpr int E_CONTAINER_EMPTY = (E_BASE + 17); // container empty +constexpr int E_CONTAINER_FULL_TO_NOTFULL = (E_BASE + 18); // container status changed from full to not full +constexpr int E_CONTAINER_NOTEMPTY_TO_EMPTY = (E_BASE + 19); // container status changed from full to not full +constexpr int E_WAIT_RETRY = (E_BASE + 20); // wait and retry later +constexpr int E_PARSE_FAIL = (E_BASE + 21); // parse packet or frame fail +constexpr int E_TIMEOUT = (E_BASE + 22); // time out +constexpr int E_SERIALIZE_ERROR = (E_BASE + 23); // serialize error +constexpr int E_DESERIALIZE_ERROR = (E_BASE + 24); // deserialize error +constexpr int E_NOT_REGISTER = (E_BASE + 25); // handler or function not registered +constexpr int E_LENGTH_ERROR = (E_BASE + 26); // error relative to length +constexpr int E_UNFINISHED = (E_BASE + 27); // get sync data unfinished. +constexpr int E_FINISHED = (E_BASE + 28); // get sync data finished. +constexpr int E_INVALID_MESSAGE_ID = (E_BASE + 29); // invalid messageId error +constexpr int E_MESSAGE_ID_ERROR = (E_BASE + 30); // messageId is not expected +constexpr int E_MESSAGE_TYPE_ERROR = (E_BASE + 31); // messageType is not expected +constexpr int E_PERIPHERAL_INTERFACE_FAIL = (E_BASE + 32); // peripheral interface fail +constexpr int E_NOT_INIT = (E_BASE + 33); // module may not init +constexpr int E_MAX_LIMITS = (E_BASE + 34); // over max limits. +constexpr int E_INVALID_CONNECTION = (E_BASE + 35); // invalid db connection. +constexpr int E_NO_SUCH_ENTRY = (E_BASE + 36); // invalid db connection. +constexpr int E_INTERNAL_ERROR = (E_BASE + 37); // an error due to code logic that is a bug +constexpr int E_CONTAINER_ONLY_DELAY_TASK = (E_BASE + 38); // only delay task left in the container +constexpr int E_SUM_CALCULATE_FAIL = (E_BASE + 39); // only delay task left in the container +constexpr int E_SUM_MISMATCH = (E_BASE + 40); // check sum mismatch +constexpr int E_OUT_OF_DATE = (E_BASE + 41); // things is out of date +constexpr int E_OBJ_IS_KILLED = (E_BASE + 42); // the refObject has been killed. +constexpr int E_SYSTEM_API_FAIL = (E_BASE + 43); // call the system api failed +constexpr int E_INVALID_DATA = (E_BASE + 44); // invalid data +constexpr int E_OUT_OF_IDS = (E_BASE + 45); // out of ids. +constexpr int E_SEND_DATA = (E_BASE + 46); // need send data +constexpr int E_NEED_TIMER = (E_BASE + 47); // timer is still need +constexpr int E_NO_NEED_TIMER = (E_BASE + 48); // timer no longer need +constexpr int E_COMBINE_FAIL = (E_BASE + 49); // fail in combining a frame +constexpr int E_END_TIMER = (E_BASE + 50); // timer no longer needed +constexpr int E_CALC_HASH = (E_BASE + 51); // calc hash error +constexpr int E_REMOVE_FILE = (E_BASE + 52); // remove file failed +constexpr int E_STATE_MACHINE_ERROR = (E_BASE + 53); // sync state machine error +constexpr int E_NO_DATA_SEND = (E_BASE + 54); // no data to send +constexpr int E_RECV_FINISHED = (E_BASE + 55); // recv finished +constexpr int E_NEED_PULL_REPONSE = (E_BASE + 56); // need to response pull request +constexpr int E_NO_SYNC_TASK = (E_BASE + 57); // no sync task to do +constexpr int E_INVALID_PASSWD_OR_CORRUPTED_DB = (E_BASE + 58); // invalid password or corrupted database. +constexpr int E_RESULT_SET_STATUS_INVALID = (E_BASE + 59); // status of result set is invalid. +constexpr int E_RESULT_SET_EMPTY = (E_BASE + 60); // the result set is empty. +constexpr int E_UPGRADE_FAILED = (E_BASE + 61); // the upgrade failed. +constexpr int E_INVALID_FILE = (E_BASE + 62); // import invalid file. +constexpr int E_INVALID_PATH = (E_BASE + 63); // the path is invalid. +constexpr int E_EMPTY_PATH = (E_BASE + 64); // the path is empty. +constexpr int E_TASK_BREAK_OFF = (E_BASE + 65); // task quit due to normal break off or error happen +constexpr int E_INCORRECT_DATA = (E_BASE + 66); // data in the database is incorrect +constexpr int E_NO_RESOURCE_FOR_USE = (E_BASE + 67); // no resource such as dbhandle for use +constexpr int E_LAST_SYNC_FRAME = (E_BASE + 68); // this frame is the last frame for this sync +constexpr int E_VERSION_NOT_SUPPORT = (E_BASE + 69); // version not support in any layer +constexpr int E_FRAME_TYPE_NOT_SUPPORT = (E_BASE + 70); // frame type not support +constexpr int E_INVALID_TIME = (E_BASE + 71); // the time is invalid +constexpr int E_INVALID_VERSION = (E_BASE + 72); // sqlite storage version is invalid +constexpr int E_SCHEMA_NOTEXIST = (E_BASE + 73); // schema does not exist +constexpr int E_INVALID_SCHEMA = (E_BASE + 74); // the schema is invalid +constexpr int E_SCHEMA_MISMATCH = (E_BASE + 75); // the schema is mismatch +constexpr int E_INVALID_FORMAT = (E_BASE + 76); // the value is invalid json or mismatch with the schema. +constexpr int E_READ_ONLY = (E_BASE + 77); // only have the read permission. +constexpr int E_NEED_ABILITY_SYNC = (E_BASE + 78); // ability sync has not done +constexpr int E_WAIT_NEXT_MESSAGE = (E_BASE + 79); // need remote device send a next message. +constexpr int E_LOCAL_DELETED = (E_BASE + 80); // local data is deleted by the unpublish. +constexpr int E_LOCAL_DEFEAT = (E_BASE + 81); // local data defeat the sync data while unpublish. +constexpr int E_LOCAL_COVERED = (E_BASE + 82); // local data is covered by the sync data while unpublish. +constexpr int E_INVALID_QUERY_FORMAT = (E_BASE + 83); // query format is not valid. +constexpr int E_INVALID_QUERY_FIELD = (E_BASE + 84); // query field is not valid. +constexpr int E_ALREADY_OPENED = (E_BASE + 85); // the database is already opened. +constexpr int E_ALREADY_SET = (E_BASE + 86); // already set. +constexpr int E_SAVE_DATA_NOTIFY = (E_BASE + 87); // notify remote device to keep alive, don't timeout +constexpr int E_RE_SEND_DATA = (E_BASE + 88); // need re send data +constexpr int E_EKEYREVOKED = (E_BASE + 89); // the EKEYREVOKED error +constexpr int E_SECURITY_OPTION_CHECK_ERROR = (E_BASE + 90); // remote device's SecurityOption not equal to local +constexpr int E_SYSTEM_API_ADAPTER_CALL_FAILED = (E_BASE + 91); // Adapter call failed +constexpr int E_NOT_NEED_DELETE_MSG = (E_BASE + 92); // not need delete msg, will be delete by sliding window receiver +constexpr int E_SLIDING_WINDOW_SENDER_ERR = (E_BASE + 93); // sliding window sender err +constexpr int E_SLIDING_WINDOW_RECEIVER_INVALID_MSG = (E_BASE + 94); // sliding window receiver invalid msg +constexpr int E_IGNORE_DATA = (E_BASE + 95); // ignore the data changed by other devices and ignore the same data. +constexpr int E_FORBID_CACHEDB = (E_BASE + 96); // such after rekey can not check passwd due to file control. +constexpr int E_INTERCEPT_DATA_FAIL = (E_BASE + 97); // Intercept push data failed. +constexpr int E_INVALID_COMPRESS_ALGO = (E_BASE + 98); // The algo is defined, but there's no implement for the algo. +constexpr int E_LOG_OVER_LIMITS = (E_BASE + 99); // The log file size is over the limits. +constexpr int E_MODE_MISMATCH = (E_BASE + 100); // dual sync mode mismatch +constexpr int E_NO_NEED_ACTIVE = (E_BASE + 101); // no need to active sync mode +// Num 150+ is reserved for schema related errno, since it may be added regularly +constexpr int E_JSON_PARSE_FAIL = (E_BASE + 150); // Parse json fail in grammatical level +constexpr int E_JSON_INSERT_PATH_EXIST = (E_BASE + 151); // Path already exist before insert +constexpr int E_JSON_INSERT_PATH_CONFLICT = (E_BASE + 152); // Nearest path ends with type not object +constexpr int E_JSON_DELETE_PATH_NOT_FOUND = (E_BASE + 153); // Path to delete not found +constexpr int E_SCHEMA_PARSE_FAIL = (E_BASE + 160); // Parse schema fail in content level +constexpr int E_SCHEMA_EQUAL_EXACTLY = (E_BASE + 161); // Two schemas are exactly the same +constexpr int E_SCHEMA_UNEQUAL_COMPATIBLE = (E_BASE + 162); // New schema contain different index +constexpr int E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE = (E_BASE + 163); // New schema contain more field(index may differ) +constexpr int E_SCHEMA_UNEQUAL_INCOMPATIBLE = (E_BASE + 164); // New schema contain more field or index +constexpr int E_SCHEMA_VIOLATE_VALUE = (E_BASE + 165); // New schema violate values already exist in dbFile +constexpr int E_FLATBUFFER_VERIFY_FAIL = (E_BASE + 170); // Verify flatbuffer content(schema or value) fail. +constexpr int E_VALUE_MATCH = (E_BASE + 180); // Value match schema(strict or compatible) without amend +constexpr int E_VALUE_MATCH_AMENDED = (E_BASE + 181); // Value match schema(strict or compatible) with amend +constexpr int E_VALUE_MISMATCH_FEILD_COUNT = (E_BASE + 182); // Value mismatch schema in field count +constexpr int E_VALUE_MISMATCH_FEILD_TYPE = (E_BASE + 183); // Value mismatch schema in field type +constexpr int E_VALUE_MISMATCH_CONSTRAINT = (E_BASE + 184); // Value mismatch schema in constraint +constexpr int E_VALUE_MISMATCH_OTHER_REASON = (E_BASE + 185); // Value mismatch schema in other reason +constexpr int E_RELATIONAL_TABLE_EQUAL = (E_BASE + 186); // In table is same +constexpr int E_RELATIONAL_TABLE_COMPATIBLE = (E_BASE + 187); // In table is compatible +constexpr int E_RELATIONAL_TABLE_COMPATIBLE_UPGRADE = (E_BASE + 188); // In table has more fields with default value +constexpr int E_RELATIONAL_TABLE_INCOMPATIBLE = (E_BASE + 189); // In table is incompatible +// Num 200+ is reserved for fixed value errno, which should not be changed between time +// Message with errorNo of Feedback-type is generated by CommunicatorAggregator without data part(No deserial if exist) +constexpr int E_FEEDBACK_UNKNOWN_MESSAGE = (E_BASE + 200); // Unknown message feedback from remote device +constexpr int E_FEEDBACK_COMMUNICATOR_NOT_FOUND = (E_BASE + 201); // Communicator not found feedback from remote device +constexpr int E_DISTRIBUTED_SCHEMA_NOT_FOUND = (E_BASE + 202); // Schema was not found in relational distributed tables +constexpr int E_DISTRIBUTED_SCHEMA_CHANGED = (E_BASE + 203); // Schema has change when do sync +} // namespace DistributedDB + +#endif // DISTRIBUTEDDB_ERRNO_H diff --git a/mock/distributeddb/common/include/db_types.h b/mock/distributeddb/common/include/db_types.h new file mode 100644 index 00000000..b8dabd59 --- /dev/null +++ b/mock/distributeddb/common/include/db_types.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_TYPES_H +#define DISTRIBUTEDDB_TYPES_H + +#include +#include +#include +#include + +#include "types_export.h" +#include "db_constant.h" + +namespace DistributedDB { +using Timestamp = uint64_t; +using ContinueToken = void *; +using DeviceID = std::string; +using TimeOffset = int64_t; +using ErrorCode = int; +using SyncId = uint64_t; +using WaterMark = uint64_t; +using DatabaseCorruptHandler = std::function; +using DatabaseLifeCycleNotifier = std::function; +const uint32_t MTU_SIZE = 5 * 1024 * 1024; // 5 M, 1024 is scale + +struct DataItem { + Key key; + Value value; + Timestamp timestamp = 0; + uint64_t flag = 0; + std::string origDev; + Timestamp writeTimestamp = 0; + std::string dev; + bool neglect = false; + Key hashKey{}; + static constexpr uint64_t DELETE_FLAG = 0x01; + static constexpr uint64_t LOCAL_FLAG = 0x02; + static constexpr uint64_t REMOVE_DEVICE_DATA_FLAG = 0x04; // only use for cachedb + static constexpr uint64_t REMOVE_DEVICE_DATA_NOTIFY_FLAG = 0x08; // only use for cachedb + // Only use for query sync and subscribe. ATTENTION!!! this flag should not write into mainDB. + // Mark the changed row data does not match with query sync(or subscribe) condition. + static constexpr uint64_t REMOTE_DEVICE_DATA_MISS_QUERY = 0x10; + static constexpr uint64_t UPDATE_FLAG = 0X20; +}; + +struct PragmaPublishInfo { + Key key; + bool deleteLocal = false; + bool updateTimestamp = false; + KvStoreNbPublishAction action; +}; + +struct PragmaUnpublishInfo { + Key key; + bool isDeleteSync = false; + bool isUpdateTime = false; +}; + +struct IOption { + static constexpr int LOCAL_DATA = 1; + static constexpr int SYNC_DATA = 2; + int dataType = LOCAL_DATA; +}; + +struct DataSizeSpecInfo { + uint32_t blockSize = MTU_SIZE; + size_t packetSize = DBConstant::MAX_HPMODE_PACK_ITEM_SIZE; +}; + +enum NotificationEventType { + DATABASE_COMMIT_EVENT = 0 +}; + +// Following are schema related common definition +using FieldName = std::string; +using FieldPath = std::vector; +// Normally, LEAF_FIELD_NULL will not appear in valid schema. LEAF_FIELD_LONG contain LEAF_FIELD_INTEGER, both are +// signed type and LEAF_FIELD_DOUBLE contain LEAF_FIELD_LONG. We don't parse into an array, so array are always leaf +// type. We parse into an object, LEAF_FIELD_OBJECT means an empty object, INTERNAL_FIELD_OBJECT however not empty. +enum class FieldType { + LEAF_FIELD_NULL, + LEAF_FIELD_BOOL, + LEAF_FIELD_INTEGER, + LEAF_FIELD_LONG, + LEAF_FIELD_DOUBLE, + LEAF_FIELD_STRING, + LEAF_FIELD_ARRAY, + LEAF_FIELD_OBJECT, + INTERNAL_FIELD_OBJECT, +}; +using TypeValue = std::pair; // Define for parameter convenience + +// Schema compatibility check behave differently for different value source +enum class ValueSource { + FROM_LOCAL, + FROM_SYNC, + FROM_DBFILE, +}; +// Represent raw-value from database to avoid copy. the first is the value start pointer, the second is the length. +using RawValue = std::pair; +using RawString = const std::string::value_type *; + +enum class OperatePerm { + NORMAL_PERM, + REKEY_MONOPOLIZE_PERM, + IMPORT_MONOPOLIZE_PERM, + DISABLE_PERM, +}; + +enum SingleVerConflictResolvePolicy { + DEFAULT_LAST_WIN = 0, + DENY_OTHER_DEV_AMEND_CUR_DEV_DATA = 1, +}; + +struct SyncTimeRange { + Timestamp beginTime = 0; + Timestamp deleteBeginTime = 0; + Timestamp endTime = static_cast(INT64_MAX); + Timestamp deleteEndTime = static_cast(INT64_MAX); + Timestamp lastQueryTime = 0; + bool IsValid() const + { + return (beginTime <= endTime && deleteBeginTime <= deleteEndTime); + } +}; +} // namespace DistributedDB +#endif // DISTRIBUTEDDB_TYPES_H diff --git a/mock/distributeddb/common/include/endian_convert.h b/mock/distributeddb/common/include/endian_convert.h new file mode 100644 index 00000000..97060c20 --- /dev/null +++ b/mock/distributeddb/common/include/endian_convert.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 ENDIAN_CONVERT_H +#define ENDIAN_CONVERT_H + +#include +#include + +namespace DistributedDB { +inline bool IsBigEndian() +{ + uint32_t data = 0x12345678; // 0x12345678 only used here, for endian test + uint8_t *firstByte = reinterpret_cast(&data); + if (*firstByte == 0x12) { // 0x12 only used here, for endian test + return true; + } + return false; +} + +template T HostToNet(const T &from) +{ + if (IsBigEndian()) { + return from; + } else { + T to; + size_t typeLen = sizeof(T); + const uint8_t *fromByte = reinterpret_cast(&from); + uint8_t *toByte = reinterpret_cast(&to); + for (size_t i = 0; i < typeLen; i++) { + toByte[i] = fromByte[typeLen - i - 1]; // 1 is for index boundary + } + return to; + } +} + +template T NetToHost(const T &from) +{ + return HostToNet(from); +} +} + +#endif \ No newline at end of file diff --git a/mock/distributeddb/common/include/hash.h b/mock/distributeddb/common/include/hash.h new file mode 100644 index 00000000..d2ee5332 --- /dev/null +++ b/mock/distributeddb/common/include/hash.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 HASH_H +#define HASH_H + +#include +#include + +namespace DistributedDB { +class Hash { + const static uint64_t PRIME_SEED = 33; // 33 is a prime seed +public: + static uint64_t HashFunc(const std::string &input); + static uint32_t Hash32Func(const std::string &input); +}; +} + +#endif diff --git a/mock/distributeddb/common/include/ischema.h b/mock/distributeddb/common/include/ischema.h new file mode 100644 index 00000000..f4413b25 --- /dev/null +++ b/mock/distributeddb/common/include/ischema.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_SCHEMA_H +#define I_SCHEMA_H + +#include + +#include "db_types.h" + +namespace DistributedDB { +// SchemaType::NONE represent for KV database which do not have schema. Only invalid SchemaObject is NONE type. +// Enum value must not be changed except SchemaType::UNRECOGNIZED. +enum class SchemaType : uint8_t { + NONE = 0, + JSON = 1, + FLATBUFFER = 2, + RELATIVE = 3, + UNRECOGNIZED = 4 +}; + +inline SchemaType ReadSchemaType(uint8_t inType) +{ + if (inType >= static_cast(SchemaType::UNRECOGNIZED)) { + return SchemaType::UNRECOGNIZED; + } + return static_cast(inType); +} + +struct SchemaAttribute { + FieldType type = FieldType::LEAF_FIELD_NULL; + bool isIndexable = false; + bool hasNotNullConstraint = false; + bool hasDefaultValue = false; + FieldValue defaultValue; // Has default value in union part and default construction in string part + std::string customFieldType {}; // Custom field type like BIGINT, DECIMAL, CHARACTER ... +}; + +class ISchema { +public: + ISchema() = default; + virtual ~ISchema() = default; + virtual int ParseFromSchemaString(const std::string &inSchemaString) = 0; + virtual bool IsSchemaValid() const = 0; + virtual SchemaType GetSchemaType() const = 0; + virtual std::string ToSchemaString() const = 0; +}; +} +#endif // I_SCHEMA_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/json_object.h b/mock/distributeddb/common/include/json_object.h new file mode 100644 index 00000000..3d79c31a --- /dev/null +++ b/mock/distributeddb/common/include/json_object.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 JSON_OBJECT_H +#define JSON_OBJECT_H + +#include +#include +#include +#include +#ifndef OMIT_JSON +#include +#endif +#include "db_types.h" + +namespace DistributedDB { +// JsonObject is the abstraction of JsonString, it hides the JsonLib that we use and other messy details. +// JsonObject do not support concurrence inherently, use it locally or under mutex protection. +class JsonObject { +public: + // Set max allowed nest depth and return the value before set. + static uint32_t SetMaxNestDepth(uint32_t nestDepth); + + // Calculate nest depth when json string is legal or estimate depth by legal part from illegal json. + static uint32_t CalculateNestDepth(const std::string &inString, int &errCode); + static uint32_t CalculateNestDepth(const uint8_t *dataBegin, const uint8_t *dataEnd, int &errCode); + + // Support default constructor, copy constructor and copy assignment + JsonObject() = default; + ~JsonObject() = default; + JsonObject(const JsonObject &); + JsonObject& operator=(const JsonObject &); + + explicit JsonObject(const Json::Value &value); + + // Should be called on an invalid JsonObject, create new JsonObject if need to reparse + // Require the type of the root to be JsonObject, otherwise parse fail + int Parse(const std::string &inString); + int Parse(const std::vector &inData); // Whether ends with '\0' in vector is OK + + // The end refer to the byte after the last valid byte + int Parse(const uint8_t *dataBegin, const uint8_t *dataEnd); + + bool IsValid() const; + + // Unnecessary spacing will be removed and fieldName resorted by lexicographical order + std::string ToString() const; + + bool IsFieldPathExist(const FieldPath &inPath) const; + int GetFieldTypeByFieldPath(const FieldPath &inPath, FieldType &outType) const; + int GetFieldValueByFieldPath(const FieldPath &inPath, FieldValue &outValue) const; + + int GetObjectArrayByFieldPath(const FieldPath &inPath, std::vector &outArray) const; + int GetStringArrayByFieldPath(const FieldPath &inPath, std::vector &outArray) const; + + int GetObjectByFieldPath(const FieldPath &inPath, JsonObject &outObj) const; + + // An empty fieldPath indicate the root, the outSubPath should be empty before call, we will not empty it at first. + // If inPath is of multiple path, then outSubPath is combination of result of each inPath. + int GetSubFieldPath(const FieldPath &inPath, std::set &outSubPath) const; + int GetSubFieldPath(const std::set &inPath, std::set &outSubPath) const; + int GetSubFieldPathAndType(const FieldPath &inPath, std::map &outSubPathType) const; + int GetSubFieldPathAndType(const std::set &inPath, std::map &outSubPathType) const; + + // If inPath not refer to an array, return error. + int GetArraySize(const FieldPath &inPath, uint32_t &outSize) const; + + // If inPath not refer to an array, return error. If not all members are string or array type, return error. + // If array-type member is empty, ignore. If not all members of the array-type member are string, return error. + int GetArrayContentOfStringOrStringArray(const FieldPath &inPath, + std::vector> &outContent) const; + + // Can be called no matter JsonObject valid or not. Invalid turn into valid after call(insert on invalid never fail + // if parameter is valid). An empty inPath is not allowed. LEAF_FIELD_ARRAY and INTERNAL_FIELD_OBJECT is not + // supported. infinite double is not support. inValue is ignored for LEAF_FIELD_NULL. + // When inPath already exist, append value if it's an arrayObject, otherwise returns -E_JSON_INSERT_PATH_EXIST + // if nearest path ends with type not object, rets -E_JSON_INSERT_PATH_CONFLICT. + // Otherwise, insert field as well as filling up intermediate field, then returns E_OK; + // isAppend: when it's true, append inValue as path is an arrayObject if path not exist. + int InsertField(const FieldPath &inPath, FieldType inType, const FieldValue &inValue, bool isAppend = false); + + // Add json object to an array field. should be called on an valid JsonObject. Never turn into invalid after call. + // If inPath not refer to an array, return error. + int InsertField(const FieldPath &inPath, const JsonObject &inValue, bool isAppend = false); + + // Should be called on an valid JsonObject. Never turn into invalid after call. An empty inPath is not allowed. + // If inPath not exist, returns -E_JSON_DELETE_PATH_NOT_FOUND. Otherwise, delete field from its parent returns E_OK; + int DeleteField(const FieldPath &inPath); +private: +#ifndef OMIT_JSON + // Auxiliary Method: If inPath not refer to an array, return error. If not all members are string, return error. + int GetStringArrayContentByJsonValue(const Json::Value &value, std::vector &outStringArray) const; + + // Common Type Judgement Logic + int GetFieldTypeByJsonValue(const Json::Value &value, FieldType &outType) const; + + // Return E_OK if JsonValueNode found at exact the path, otherwise not E_OK + const Json::Value &GetJsonValueByFieldPath(const FieldPath &inPath, int &errCode) const; + + // REQUIRE: JsonObject is valid(Root value is object type). + // If inPath empty(means root), set exact and nearest to root value and nearDepth to 0, then ret E_OK; + // If JsonValue exist at exact path, set exact to this JsonValue, set nearest to its parent JsonValue, set nearDepth + // to the depth of this parent JsonValue, then ret E_OK; + // If exact path no exist, set exact to nullptr, set nearest to nearest JsonValue that can be found, set nearDepth + // to the depth of this nearest JsonValue, then ret -E_NOT_FOUND; + int LocateJsonValueByFieldPath(const FieldPath &inPath, Json::Value *&exact, + Json::Value *&nearest, uint32_t &nearDepth); + + // create if path not exist + int MoveToPath(const FieldPath &inPath, Json::Value *&exact, Json::Value *&nearest); + + static uint32_t maxNestDepth_; + + bool isValid_ = false; + Json::Value value_; +#endif +}; +} // namespace DistributedDB +#endif // JSON_OBJECT_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/log_print.h b/mock/distributeddb/common/include/log_print.h new file mode 100644 index 00000000..0c164d3e --- /dev/null +++ b/mock/distributeddb/common/include/log_print.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_LOG_PRINT_H +#define DISTRIBUTEDDB_LOG_PRINT_H + +#include +#include +#include +#include + +namespace DistributedDB { +const std::string LOG_TAG_KV = "DistributedDB"; + +class Logger { +public: + enum class Level { + LEVEL_DEBUG, + LEVEL_INFO, + LEVEL_WARN, + LEVEL_ERROR, + LEVEL_FATAL + }; + + virtual ~Logger() {}; + static Logger *GetInstance(); + static void RegisterLogger(Logger *logger); + static void Log(Level level, const std::string &tag, const char *func, int line, const char *format, ...); + +private: + virtual void Print(Level level, const std::string &tag, const std::string &msg) = 0; + static void PreparePrivateLog(const char *format, std::string &outStrFormat); + static Logger *logHandler; + static const std::string PRIVATE_TAG; +}; + +#define NO_LOG(...) // No log in normal and release. Used for convenience when deep debugging +#define LOGD(...) Logger::Log(Logger::Level::LEVEL_DEBUG, LOG_TAG_KV, __FUNCTION__, __LINE__, __VA_ARGS__) +#define LOGI(...) Logger::Log(Logger::Level::LEVEL_INFO, LOG_TAG_KV, __FUNCTION__, __LINE__, __VA_ARGS__) +#define LOGW(...) Logger::Log(Logger::Level::LEVEL_WARN, LOG_TAG_KV, __FUNCTION__, __LINE__, __VA_ARGS__) +#define LOGE(...) Logger::Log(Logger::Level::LEVEL_ERROR, LOG_TAG_KV, __FUNCTION__, __LINE__, __VA_ARGS__) +#define LOGF(...) Logger::Log(Logger::Level::LEVEL_FATAL, LOG_TAG_KV, __FUNCTION__, __LINE__, __VA_ARGS__) +} // namespace DistributedDB + +#endif // DISTRIBUTEDDB_LOG_PRINT_H diff --git a/mock/distributeddb/common/include/macro_utils.h b/mock/distributeddb/common/include/macro_utils.h new file mode 100644 index 00000000..ec2ccd61 --- /dev/null +++ b/mock/distributeddb/common/include/macro_utils.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MACRO_UTILS_H +#define MACRO_UTILS_H + +namespace DistributedDB { +#define DISABLE_COPY_ASSIGN_MOVE(ClassName) \ + ClassName(const ClassName &) = delete; \ + ClassName(ClassName &&) = delete; \ + ClassName& operator=(const ClassName &) = delete; \ + ClassName& operator=(ClassName &&) = delete + +#define DECLARE_OBJECT_TAG(ClassName) \ + std::string GetObjectTag() const override; \ + constexpr static const char * const classTag = "Class-"#ClassName + +#define DEFINE_OBJECT_TAG_FACILITIES(ClassName) \ + std::string ClassName::GetObjectTag() const \ + { \ + return ClassName::classTag; \ + } + +#define BYTE_8_ALIGN(x) (((x) + (8 - 1)) & ~(8 - 1)) + +#define BITX(x) (1 << (x)) + +#define ULL(x) (static_cast(x)) + +// Convert var or enum to variable name for printf +#define VNAME(name) (#name) +} +#endif // MACRO_UTILS_H diff --git a/mock/distributeddb/common/include/notification_chain.h b/mock/distributeddb/common/include/notification_chain.h new file mode 100644 index 00000000..8c0d50b5 --- /dev/null +++ b/mock/distributeddb/common/include/notification_chain.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 NOTIFICATION_CHAIN_H +#define NOTIFICATION_CHAIN_H + +#include +#include +#include +#include + +#include "ref_object.h" + +namespace DistributedDB { +using EventType = unsigned int; + +class NotificationChain final : public RefObject { +private: + class ListenerChain; + +public: + class Listener final : public RefObject { + public: + using OnEvent = std::function; + using OnFinalize = std::function; + + // Called by ListenerChain.callbackListeners, it will call the OnEvent + void NotifyListener(void *arg); + + // Drop this listener. after call this function, the listener will be destroy + int Drop(bool wait = false); + + // Enter kill-waiting state if 'onEvent()' is invoking when 'KillObj()'. + void KillWait(); + + // Set the listener chain we belong to. + void SetOwner(ListenerChain *listenerChain); + + Listener(const OnEvent &onEvent, const OnFinalize &onFinalize); + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(Listener); + + protected: + ~Listener() override; + + private: + // will be call when this listener destroy + void Finalize() const; + bool EnterEventAction(); + void LeaveEventAction(); + + DECLARE_OBJECT_TAG(Listener); + + constexpr static int KILL_WAIT_SECONDS = 5; // wait only 5 seconds when killing to avoid dead-lock. + OnEvent onEvent_; + OnFinalize onFinalize_; + ListenerChain *listenerChain_; + std::thread::id eventRunningThread_; + std::condition_variable safeKill_; + }; + + // Add a listener from the NotificationChain. it will return a Listener handle + // The param type should match the RegisterEventsType + // The param onEvent will be call when events happened. + // The param onFinalize will be call when this listener destroy + Listener *RegisterListener(EventType type, const Listener::OnEvent &onEvent, + const Listener::OnFinalize &onFinalize, int &errCode); + + // User to register an events type to the NotificationChain, needs to call at init + int RegisterEventType(EventType type); + + // User to unregister an events type. + int UnRegisterEventType(EventType type); + + // Should be call when events happened. + void NotifyEvent(EventType type, void *arg); + + NotificationChain() = default; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(NotificationChain); + +protected: + ~NotificationChain() override; + +private: + class ListenerChain final : public RefObject { + public: + // Add a listener to the ListenerChain + int RegisterListener(Listener *listener); + + // Remove a listener to the ListenerChain + int UnRegisterListener(Listener *listener, bool wait = false); + + // Callback all the listeners + void NotifyListeners(void *arg); + + // Clear all listeners + void ClearListeners(); + + ListenerChain(); + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(ListenerChain); + protected: + ~ListenerChain() override; + + private: + // Used to back up listenerSet_, need to lock + void BackupListenerSet(std::set &backupSet) const; + + DECLARE_OBJECT_TAG(ListenerChain); + + std::set listenerSet_; + }; + + // Find a ListenerChain from the eventChains_ with given type, + // this function needs to lock. + ListenerChain *FindAndGetListenerChainLocked(EventType type); + + // Find a ListenerChain from the eventChains_ with given type, + ListenerChain *FindListenerChain(EventType type) const; + + DECLARE_OBJECT_TAG(NotificationChain); + + std::map eventChains_; +}; +} // namespace DistributedDB + +#endif // NOTIFICATION_CHAIN_H diff --git a/mock/distributeddb/common/include/param_check_utils.h b/mock/distributeddb/common/include/param_check_utils.h new file mode 100644 index 00000000..f55e66a4 --- /dev/null +++ b/mock/distributeddb/common/include/param_check_utils.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 PARAM_CHECK_UTILS_H +#define PARAM_CHECK_UTILS_H + +#include + +#include "db_types.h" +#include "auto_launch_export.h" +#include "schema_object.h" + +namespace DistributedDB { +class ParamCheckUtils final { +public: + + static bool CheckDataDir(const std::string &dir, std::string &canonicalDir); + + // Check if the storeID is a safe arg. + static bool IsStoreIdSafe(const std::string &storeId); + + // check appId, userId, storeId. + static bool CheckStoreParameter(const std::string &storeId, const std::string &appId, const std::string &userId, + bool isIgnoreUserIdCheck = false); + + // check encrypted args for KvStore. + static bool CheckEncryptedParameter(CipherType cipher, const CipherPassword &passwd); + + static bool CheckConflictNotifierType(int conflictType); + + static bool CheckSecOption(const SecurityOption &secOption); + + static bool CheckObserver(const Key &key, unsigned int mode); + + static bool IsS3SECEOpt(const SecurityOption &secOpt); + + static int CheckAndTransferAutoLaunchParam(const AutoLaunchParam ¶m, bool checkDir, + SchemaObject &schemaObject, std::string &canonicalDir); + + static uint8_t GetValidCompressionRate(uint8_t compressionRate); + + static bool CheckRelationalTableName(const std::string &tableName); +}; +} // namespace DistributedDB + +#endif // DISTRIBUTEDDB_PARAM_CHECK_UTILS_H diff --git a/mock/distributeddb/common/include/parcel.h b/mock/distributeddb/common/include/parcel.h new file mode 100644 index 00000000..fb42ce19 --- /dev/null +++ b/mock/distributeddb/common/include/parcel.h @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 PARCEL_H +#define PARCEL_H + +#include +#include +#include +#include + +#include "endian_convert.h" +#include "securec.h" +#include "macro_utils.h" +#include "db_errno.h" +#include "log_print.h" +#ifndef OMIT_MULTI_VER +#include "multi_ver_def.h" +#endif + +namespace DistributedDB { +class Parcel { +public: + Parcel(uint8_t *inBuf, uint32_t length); + ~Parcel(); + bool IsError() const; + int WriteBool(bool data); + uint32_t ReadBool(bool &data); + int WriteInt(int data); + uint32_t ReadInt(int &val); + int WriteUInt8(uint8_t data); + uint32_t ReadUInt8(uint8_t &val); + int WriteDouble(double data); + uint32_t ReadDouble(double &val); + int WriteInt64(int64_t data); + uint32_t ReadInt64(int64_t &val); + int WriteUInt32(uint32_t data); + uint32_t ReadUInt32(uint32_t &val); + int WriteUInt64(uint64_t data); + uint32_t ReadUInt64(uint64_t &val); + int WriteVectorChar(const std::vector &data); + uint32_t ReadVectorChar(std::vector &val); + int WriteString(const std::string &inVal); + uint32_t ReadString(std::string &outVal); + bool IsContinueRead(); +#ifndef OMIT_MULTI_VER + int WriteMultiVerCommit(const MultiVerCommitNode &commit); + uint32_t ReadMultiVerCommit(MultiVerCommitNode &commit); + int WriteMultiVerCommits(const std::vector &commits); + uint32_t ReadMultiVerCommits(std::vector &commits); +#endif + + template + int WriteVector(const std::vector &data) + { + static_assert(std::is_pod::value, "type T is not pod"); + if (data.size() > INT32_MAX || sizeof(T) > INT32_MAX) { + LOGE("[WriteVector] invalid vector. vec.size:%zu, sizeof(T):%zu", data.size(), sizeof(T)); + isError_ = true; + return -E_PARSE_FAIL; + } + if (IsError()) { + return -E_PARSE_FAIL; + } + uint32_t len = data.size(); + uint64_t stepLen = static_cast(data.size()) * sizeof(T) + sizeof(uint32_t); + len = HostToNet(len); + if (bufPtr_ == nullptr || stepLen > INT32_MAX || parcelLen_ + BYTE_8_ALIGN(stepLen) > totalLen_) { + LOGE("[WriteVector] bufPtr:%d, stepLen:%llu, totalLen:%llu, parcelLen:%llu", + bufPtr_ != nullptr, ULL(stepLen), ULL(totalLen_), ULL(parcelLen_)); + isError_ = true; + return -E_PARSE_FAIL; + } + errno_t errCode = memcpy_s(bufPtr_, totalLen_ - parcelLen_, &len, sizeof(uint32_t)); + if (errCode != EOK) { + LOGE("[ReadVector] totalLen:%llu, parcelLen:%llu", ULL(totalLen_), ULL(parcelLen_)); + isError_ = true; + return -E_SECUREC_ERROR; + } + bufPtr_ += sizeof(uint32_t); + for (auto iter : data) { + *(reinterpret_cast(bufPtr_)) = HostToNet(iter); + bufPtr_ += sizeof(T); + } + bufPtr_ += BYTE_8_ALIGN(stepLen) - stepLen; + parcelLen_ += BYTE_8_ALIGN(stepLen); + return errCode; + } + + template + uint32_t ReadVector(std::vector &val) + { + static_assert(std::is_pod::value, "type T is not pod"); + if (IsError()) { + return 0; + } + if (bufPtr_ == nullptr || parcelLen_ + sizeof(uint32_t) > totalLen_ || sizeof(T) > INT32_MAX) { + LOGE("[ReadVector] bufPtr:%d, totalLen:%llu, parcelLen:%llu, sizeof(T):%zu", + bufPtr_ != nullptr, ULL(totalLen_), ULL(parcelLen_), sizeof(T)); + isError_ = true; + return 0; + } + uint32_t len = *(reinterpret_cast(bufPtr_)); + len = NetToHost(len); + if (len > INT32_MAX) { + LOGE("[ReadVector] invalid length:%u", len); + isError_ = true; + return 0; + } + uint64_t stepLen = static_cast(len) * sizeof(T) + sizeof(uint32_t); + if (stepLen > INT32_MAX || parcelLen_ + BYTE_8_ALIGN(stepLen) > totalLen_) { + LOGE("[ReadVector] stepLen:%llu, totalLen:%llu, parcelLen:%llu", ULL(stepLen), ULL(totalLen_), + ULL(parcelLen_)); + isError_ = true; + return 0; + } + bufPtr_ += sizeof(uint32_t); + val.resize(len); + for (uint32_t i = 0; i < len; i++) { + val[i] = NetToHost(*(reinterpret_cast(bufPtr_))); + bufPtr_ += sizeof(T); + } + bufPtr_ += BYTE_8_ALIGN(stepLen) - stepLen; + parcelLen_ += BYTE_8_ALIGN(stepLen); + stepLen = BYTE_8_ALIGN(stepLen); + return static_cast(stepLen); + } + + int WriteBlob(const char *buffer, uint32_t bufLen); + uint32_t ReadBlob(char *buffer, uint32_t bufLen); + void EightByteAlign(); // Avoid reading a single data type across 8 bytes + static uint32_t GetBoolLen(); + static uint32_t GetIntLen(); + static uint32_t GetUInt8Len(); + static uint32_t GetUInt32Len(); + static uint32_t GetUInt64Len(); + static uint32_t GetInt64Len(); + static uint32_t GetDoubleLen(); + static uint32_t GetVectorCharLen(const std::vector &data); + + template + static uint32_t GetVectorLen(const std::vector &data) + { + if (data.size() > INT32_MAX || sizeof(T) > INT32_MAX) { + return 0; + } + uint64_t len = sizeof(uint32_t) + static_cast(data.size()) * sizeof(T); + len = BYTE_8_ALIGN(len); + if (len > INT32_MAX) { + return 0; + } + return static_cast(len); + } + + static uint32_t GetEightByteAlign(uint32_t len); + static uint32_t GetStringLen(const std::string &data); +#ifndef OMIT_MULTI_VER + static uint32_t GetMultiVerCommitLen(const MultiVerCommitNode &commit); + static uint32_t GetMultiVerCommitsLen(const std::vector &commits); +#endif + static uint32_t GetAppendedLen(); + +private: + template + int WriteInteger(T integer); + template + uint32_t ReadInteger(T &integer); + + bool isError_ = false; + uint8_t *buf_ = nullptr; + uint8_t *bufPtr_ = nullptr; + uint64_t parcelLen_ = 0; + uint64_t totalLen_ = 0; +}; + +template +uint32_t Parcel::ReadInteger(T &integer) +{ + if (IsError()) { + return 0; + } + if (bufPtr_ == nullptr || parcelLen_ + sizeof(T) > totalLen_) { + LOGE("[ReadInteger] bufPtr:%d, totalLen:%llu, parcelLen:%llu, sizeof(T):%zu", + bufPtr_ != nullptr, ULL(totalLen_), ULL(parcelLen_), sizeof(T)); + isError_ = true; + return 0; + } + integer = *(reinterpret_cast(bufPtr_)); + bufPtr_ += sizeof(T); + parcelLen_ += sizeof(T); + integer = NetToHost(integer); + return sizeof(T); +} + +template +int Parcel::WriteInteger(T integer) +{ + if (IsError()) { + return -E_PARSE_FAIL; + } + T inData = HostToNet(integer); + if (parcelLen_ + sizeof(T) > totalLen_) { + LOGE("[WriteInteger] totalLen:%llu, parcelLen:%llu, sizeof(T):%zu", ULL(totalLen_), ULL(parcelLen_), sizeof(T)); + isError_ = true; + return -E_PARSE_FAIL; + } + errno_t errCode = memcpy_s(bufPtr_, totalLen_ - parcelLen_, &inData, sizeof(T)); + if (errCode != EOK) { + LOGE("[WriteInteger] bufPtr:%d, totalLen:%llu, parcelLen:%llu, sizeof(T):%zu", + bufPtr_ != nullptr, ULL(totalLen_), ULL(parcelLen_), sizeof(T)); + isError_ = true; + return -E_SECUREC_ERROR; + } + bufPtr_ += sizeof(T); + parcelLen_ += sizeof(T); + return errCode; +} +} // namespace DistributedDB + +#endif // PARCEL_H + diff --git a/mock/distributeddb/common/include/performance_analysis.h b/mock/distributeddb/common/include/performance_analysis.h new file mode 100644 index 00000000..4a89f22f --- /dev/null +++ b/mock/distributeddb/common/include/performance_analysis.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 PERFORMANCE_ANALYSIS_H +#define PERFORMANCE_ANALYSIS_H + +#include +#include + +#include "db_types.h" + +namespace DistributedDB { +enum PT_TEST_RECORDS : uint32_t { + RECORD_PUT_DATA = 1, + RECORD_SYNC_TOTAL, + RECORD_WATERMARK_SYNC, + RECORD_READ_DATA, + RECORD_SAVE_DATA, + RECORD_SAVE_LOCAL_WATERMARK, + RECORD_SAVE_PEER_WATERMARK, + RECORD_DATA_SEND_REQUEST_TO_ACK_RECV, + RECORD_DATA_REQUEST_RECV_TO_SEND_ACK, + RECORD_MACHINE_START_TO_PUSH_SEND, + RECORD_ACK_RECV_TO_USER_CALL_BACK, +}; + +enum MV_TEST_RECORDS : uint32_t { + RECORD_SEND_LOCAL_DATA_CHANGED_TO_COMMIT_REQUEST_RECV = 3, + RECORD_GET_DEVICE_LATEST_COMMIT, + RECORD_COMMIT_SEND_REQUEST_TO_ACK_RECV, + RECORD_GET_COMMIT_TREE, + RECORD_DATA_GET_VALID_COMMIT, + RECORD_DATA_ENTRY_SEND_REQUEST_TO_ACK_RECV, + RECORD_GET_COMMIT_DATA, + RECORD_GET_VALUE_SLICE_NODE, + RECORD_VALUE_SLICE_SEND_REQUEST_TO_ACK_RECV, + RECORD_READ_VALUE_SLICE, + RECORD_SAVE_VALUE_SLICE, + RECORD_PUT_COMMIT_DATA, + RECORD_MERGE, +}; + +struct TimePair { + Timestamp startTime = 0; + Timestamp endTime = 0; +}; + +struct StatisticsInfo { + Timestamp max = 0; + Timestamp min = 0; + float average = 0.0; +}; + +struct SingleStatistics { + std::vector timeInfo; +}; + +class PerformanceAnalysis { +public: + explicit PerformanceAnalysis(uint32_t step); + ~PerformanceAnalysis(); + + static PerformanceAnalysis *GetInstance(int stepNum = 20); + + void TimeRecordStart(); + + void TimeRecordEnd(); + + void StepTimeRecordStart(uint32_t step); + + void StepTimeRecordEnd(uint32_t step); + + std::string GetStatistics(); + + void OpenPerformanceAnalysis(); + + void ClosePerformanceAnalysis(); + + void SetFileNumber(const std::string &FileID); + +private: + + bool IsStepValid(uint32_t step) const; + + bool IsOpen() const; + + bool InsertTimeRecord(const TimePair &timePair, uint32_t step); + + bool GetTimeRecord(uint32_t step, TimePair &timePair) const; + + void OutStatistics(); + + void Clear(); + + void Close(); + + const static int MAX_TIMERECORD_STEP_NUM = 200; + const static std::string STATISTICAL_DATA_FILE_NAME_HEADER; + const static std::string CSV_FILE_EXTENSION; + const static std::string DEFAULT_FILE_NAME; + SingleStatistics timeRecordData_; + std::vector stepTimeRecordInfo_; + std::vector counts_; + uint32_t stepNum_; + bool isOpen_; + std::ofstream outFile; + int fileNumber_; + std::string fileID_; +}; +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/common/include/platform_specific.h b/mock/distributeddb/common/include/platform_specific.h new file mode 100644 index 00000000..0c2773f0 --- /dev/null +++ b/mock/distributeddb/common/include/platform_specific.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 PLATFORM_SPECIFIC_H +#define PLATFORM_SPECIFIC_H + +#include +#include +#include + +namespace DistributedDB { +namespace OS { +enum FileType { + FILE = 0, + PATH = 1, + OTHER = 2, +}; + +struct FileAttr { + std::string fileName; + FileType fileType; + uint64_t fileLen; +}; + +// Shield the representation method of file handles on different platforms +struct FileHandle { + int handle = -1; +}; + +int CalFileSize(const std::string &fileUrl, uint64_t &size); + +bool CheckPathExistence(const std::string &filePath); + +int MakeDBDirectory(const std::string &directory); + +int RemoveFile(const std::string &filePath); +// Can only remove empty directory +int RemoveDBDirectory(const std::string &directory); + +int GetRealPath(const std::string &inOriPath, std::string &outRealPath); + +int GetCurrentSysTimeInMicrosecond(uint64_t &outTime); + +int GetMonotonicRelativeTimeInMicrosecond(uint64_t &outTime); + +int CreateFileByFileName(const std::string &fileName); + +void SplitFilePath(const std::string &filePath, std::string &fileDir, std::string &fileName); + +int GetFileAttrFromPath(const std::string &filePath, std::list &files, bool isNeedAllPath = false); + +int GetFilePermissions(const std::string &fileName, uint32_t &permissions); + +int SetFilePermissions(const std::string &fileName, uint32_t permissions); + +int RenameFilePath(const std::string &oldFilePath, const std::string &newFilePath); + +int OpenFile(const std::string &fileName, FileHandle &handle); +int CloseFile(FileHandle &handle); + +int FileLock(const FileHandle &handle, bool isBlock); // be careful use block=true, may block process +int FileUnlock(FileHandle &handle); +} // namespace OS +} // namespace DistributedDB + +#endif // PLATFORM_SPECIFIC_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/ref_object.h b/mock/distributeddb/common/include/ref_object.h new file mode 100644 index 00000000..7fce0ea1 --- /dev/null +++ b/mock/distributeddb/common/include/ref_object.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_DB_REF_OBJECT_H +#define KV_DB_REF_OBJECT_H + +#include +#include +#include +#include +#include +#include "macro_utils.h" + +namespace DistributedDB { +class RefObject { +public: + class AutoLock final { + public: + AutoLock(const RefObject *obj, bool unlocked = true); + ~AutoLock(); + void Lock(); + void Unlock(); + + private: + DISABLE_COPY_ASSIGN_MOVE(AutoLock); + const RefObject *refObj_; + bool IsLocked_; + }; + + RefObject(); + + /* Invoked before this object deleted. */ + void OnLastRef(const std::function &callback) const; + + /* Invoked when kill object, with lock held. */ + void OnKill(const std::function &callback); + + bool IsKilled() const; + void KillObj(); + void LockObj() const; + void UnlockObj() const; + bool WaitLockedUntil(std::condition_variable &cv, + const std::function &condition, int seconds = 0); + + /* Work as static members, avoid to 'delete this' */ + static void IncObjRef(const RefObject *obj); + static void DecObjRef(const RefObject *obj); + static void KillAndDecObjRef(RefObject *obj); + +protected: + virtual ~RefObject(); + virtual std::string GetObjectTag() const; + +private: + constexpr static const char * const classTag = "Class-RefObject"; + + DISABLE_COPY_ASSIGN_MOVE(RefObject); + + /* A const object can also be locked/unlocked/ref()/unref() */ + mutable std::atomic refCount_; + mutable std::mutex objLock_; + std::atomic isKilled_; + mutable std::function onLast_; + std::function onKill_; +}; +} // namespace DistributedDB + +#endif // KV_DB_REF_OBJECT_H diff --git a/mock/distributeddb/common/include/relational/relational_schema_object.h b/mock/distributeddb/common/include/relational/relational_schema_object.h new file mode 100644 index 00000000..884dcd4c --- /dev/null +++ b/mock/distributeddb/common/include/relational/relational_schema_object.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RELATIONAL_SCHEMA_OBJECT_H +#define RELATIONAL_SCHEMA_OBJECT_H +#ifdef RELATIONAL_STORE +#include +#include "data_value.h" +#include "json_object.h" +#include "parcel.h" +#include "ischema.h" + +namespace DistributedDB { +using CompositeFields = std::vector; +class FieldInfo { +public: + const std::string &GetFieldName() const; + void SetFieldName(const std::string &fileName); + const std::string &GetDataType() const; + void SetDataType(const std::string &dataType); + bool IsNotNull() const; + void SetNotNull(bool isNotNull); + // Use string type to save the default value define in the create table sql. + // No need to use the real value because sqlite will complete them. + bool HasDefaultValue() const; + const std::string &GetDefaultValue() const; + void SetDefaultValue(const std::string &value); + // convert to StorageType according "Determination Of Column Affinity" + StorageType GetStorageType() const; + void SetStorageType(StorageType storageType); + + int GetColumnId() const; + void SetColumnId(int cid); + + // return field define string like ("fieldName": "MY INT(21), NOT NULL, DEFAULT 123") + std::string ToAttributeString() const; + + int CompareWithField(const FieldInfo &inField) const; +private: + std::string fieldName_; + std::string dataType_; // Type may be null + StorageType storageType_ = StorageType::STORAGE_TYPE_NONE; + bool isNotNull_ = false; + bool hasDefaultValue_ = false; + std::string defaultValue_; + int64_t cid_ = -1; +}; + +class TableInfo { +public: + const std::string &GetTableName() const; + bool GetAutoIncrement() const; + const std::string &GetCreateTableSql() const; + const std::map &GetFields() const; // + const std::map &GetIndexDefine() const; + const FieldName &GetPrimaryKey() const; + + void SetTableName(const std::string &tableName); + void SetAutoIncrement(bool autoInc); + void SetCreateTableSql(std::string sql); // set 'autoInc_' flag when set sql + void AddField(const FieldInfo &field); + void AddIndexDefine(const std::string &indexName, const CompositeFields &indexDefine); + void SetPrimaryKey(const FieldName &fieldName); // not support composite index now + std::string ToTableInfoString() const; + + int CompareWithTable(const TableInfo &inTableInfo) const; + std::map GetSchemaDefine() const; + std::string GetFieldName(uint32_t cid) const; // cid begin with 0 + const std::vector &GetFieldInfos() const; // Sort by cid + bool IsValid() const; + +private: + void AddFieldDefineString(std::string &attrStr) const; + void AddIndexDefineString(std::string &attrStr) const; + + int CompareWithTableFields(const std::map &inTableFields) const; + int CompareWithTableIndex(const std::map &inTableIndex) const; + + std::string tableName_; + bool autoInc_ = false; // only 'INTEGER PRIMARY KEY' could be defined as 'AUTOINCREMENT' + std::string sql_; + std::map fields_; + FieldName primaryKey_; + std::map indexDefines_; + mutable std::vector fieldInfos_; +}; + +class RelationalSchemaObject : public ISchema { +public: + RelationalSchemaObject() = default; + ~RelationalSchemaObject() override = default; + + bool IsSchemaValid() const override; + + SchemaType GetSchemaType() const override; + + std::string ToSchemaString() const override; + + // Should be called on an invalid SchemaObject, create new SchemaObject if need to reparse + int ParseFromSchemaString(const std::string &inSchemaString) override; + + void AddRelationalTable(const TableInfo& tb); + + void RemoveRelationalTable(const std::string &tableName); + + const std::map &GetTables() const; + + std::vector GetTableNames() const; + + TableInfo GetTable(const std::string& tableName) const; + +private: + int CompareAgainstSchemaObject(const std::string &inSchemaString, std::map &cmpRst) const; + + int CompareAgainstSchemaObject(const RelationalSchemaObject &inSchemaObject, + std::map &cmpRst) const; + + int ParseRelationalSchema(const JsonObject &inJsonObject); + int ParseCheckSchemaType(const JsonObject &inJsonObject); + int ParseCheckSchemaVersion(const JsonObject &inJsonObject); + int ParseCheckSchemaTableDefine(const JsonObject &inJsonObject); + int ParseCheckTableInfo(const JsonObject &inJsonObject); + int ParseCheckTableName(const JsonObject &inJsonObject, TableInfo &resultTable); + int ParseCheckTableDefine(const JsonObject &inJsonObject, TableInfo &resultTable); + int ParseCheckTableFieldInfo(const JsonObject &inJsonObject, const FieldPath &path, FieldInfo &table); + int ParseCheckTableAutoInc(const JsonObject &inJsonObject, TableInfo &resultTable); + int ParseCheckTableIndex(const JsonObject &inJsonObject, TableInfo &resultTable); + int ParseCheckTablePrimaryKey(const JsonObject &inJsonObject, TableInfo &resultTable); + + void GenerateSchemaString(); + + bool isValid_ = false; // set to true after parse success from string or add at least one relational table + SchemaType schemaType_ = SchemaType::RELATIVE; // Default RELATIVE + std::string schemaString_; // The minified and valid schemaString + std::string schemaVersion_; + std::map tables_; +}; +} // namespace DistributedDB +#endif // RELATIONAL_STORE +#endif // RELATIONAL_SCHEMA_OBJECT_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/res_finalizer.h b/mock/distributeddb/common/include/res_finalizer.h new file mode 100644 index 00000000..ba9764de --- /dev/null +++ b/mock/distributeddb/common/include/res_finalizer.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RES_FINALIZER_H +#define RES_FINALIZER_H + +#include +#include "macro_utils.h" + +namespace DistributedDB { +// RAII style resource finalizer for using in functions where the resource should be finalized before each return after +// the resource had been allocated. Just create an instance as function local stack variable and provide finalizer +// function after where the resource allocated. Suggest using this RAII style instead of using goto statement. +class ResFinalizer { +public: + explicit ResFinalizer(const std::function &inFinalizer) : finalizer_(inFinalizer) {} + ~ResFinalizer() + { + if (finalizer_) { + finalizer_(); + } + } + + DISABLE_COPY_ASSIGN_MOVE(ResFinalizer); +private: + std::function finalizer_; +}; +} // namespace DistributedDB +#endif // RES_FINALIZER_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/runtime_context.h b/mock/distributeddb/common/include/runtime_context.h new file mode 100644 index 00000000..a03926e0 --- /dev/null +++ b/mock/distributeddb/common/include/runtime_context.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RUNTIME_CONTEXT_H +#define RUNTIME_CONTEXT_H + +#include +#include +#include + +#include "auto_launch.h" +#include "auto_launch_export.h" +#include "icommunicator_aggregator.h" +#include "iprocess_system_api_adapter.h" +#include "kv_store_observer.h" +#include "kvdb_properties.h" +#include "macro_utils.h" +#include "notification_chain.h" +#include "types_export.h" + +namespace DistributedDB { +using TimerId = uint64_t; +using TimerAction = std::function; +using TimerFinalizer = std::function; +using TaskAction = std::function; +using TimeChangedAction = std::function; +using LockStatusNotifier = std::function; +using UserChangedAction = std::function; + +class RuntimeContext { +public: + DISABLE_COPY_ASSIGN_MOVE(RuntimeContext); + + // Global setting interfaces. + virtual void SetProcessLabel(const std::string &label) = 0; + virtual std::string GetProcessLabel() const = 0; + + // If the pre adapter is not nullptr, set new adapter will release the pre adapter, + // must be called after SetCommunicatorAggregator + virtual int SetCommunicatorAdapter(IAdapter *adapter) = 0; + virtual int GetCommunicatorAggregator(ICommunicatorAggregator *&outAggregator) = 0; + virtual void SetCommunicatorAggregator(ICommunicatorAggregator *inAggregator) = 0; + virtual int GetLocalIdentity(std::string &outTarget) = 0; + + // Timer interfaces. + virtual int SetTimer(int milliSeconds, const TimerAction &action, + const TimerFinalizer &finalizer, TimerId &timerId) = 0; + virtual int ModifyTimer(TimerId timerId, int milliSeconds) = 0; + virtual void RemoveTimer(TimerId timerId, bool wait = false) = 0; + + // Task interfaces. + virtual int ScheduleTask(const TaskAction &task) = 0; + virtual int ScheduleQueuedTask(const std::string &queueTag, + const TaskAction &task) = 0; + + // Shrink as much memory as possible. + virtual void ShrinkMemory(const std::string &description) = 0; + + // Register a time changed lister, it will be callback when local time changed. + virtual NotificationChain::Listener *RegisterTimeChangedLister(const TimeChangedAction &action, int &errCode) = 0; + + // Get the global context object(singleton), never return nullptr. + static RuntimeContext *GetInstance(); + + virtual int SetPermissionCheckCallback(const PermissionCheckCallback &callback) = 0; + + virtual int SetPermissionCheckCallback(const PermissionCheckCallbackV2 &callback) = 0; + + virtual int RunPermissionCheck(const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) const = 0; + + virtual int EnableKvStoreAutoLaunch(const KvDBProperties &properties, AutoLaunchNotifier notifier, + const AutoLaunchOption &option) = 0; + + virtual int DisableKvStoreAutoLaunch(const std::string &normalIdentifier, const std::string &dualTupleIdentifier, + const std::string &userId) = 0; + + virtual void GetAutoLaunchSyncDevices(const std::string &identifier, std::vector &devices) const = 0; + + virtual void SetAutoLaunchRequestCallback(const AutoLaunchRequestCallback &callback, DBType type) = 0; + + virtual NotificationChain::Listener *RegisterLockStatusLister(const LockStatusNotifier &action, int &errCode) = 0; + + virtual bool IsAccessControlled() const = 0; + + virtual int SetSecurityOption(const std::string &filePath, const SecurityOption &option) const = 0; + + virtual int GetSecurityOption(const std::string &filePath, SecurityOption &option) const = 0; + + virtual bool CheckDeviceSecurityAbility(const std::string &devId, const SecurityOption &option) const = 0; + + virtual int SetProcessSystemApiAdapter(const std::shared_ptr &adapter) = 0; + + virtual bool IsProcessSystemApiAdapterValid() const = 0; + + virtual bool IsCommunicatorAggregatorValid() const = 0; + + // Notify TIME_CHANGE_EVENT. + virtual void NotifyTimestampChanged(TimeOffset offset) const = 0; + + virtual void SetStoreStatusNotifier(const StoreStatusNotifier ¬ifier) = 0; + + virtual void NotifyDatabaseStatusChange(const std::string &userId, const std::string &appId, + const std::string &storeId, const std::string &deviceId, bool onlineStatus) = 0; + + virtual int SetSyncActivationCheckCallback(const SyncActivationCheckCallback &callback) = 0; + + virtual bool IsSyncerNeedActive(std::string &userId, std::string &appId, std::string &storeId) const = 0; + + virtual NotificationChain::Listener *RegisterUserChangedListerner(const UserChangedAction &action, + EventType event) = 0; + + virtual int NotifyUserChanged() const = 0; + + // Generate global sessionId in current process + virtual uint32_t GenerateSessionId() = 0; + + virtual void DumpCommonInfo(int fd) = 0; +protected: + RuntimeContext() = default; + virtual ~RuntimeContext() {} +}; +} // namespace DistributedDB + +#endif // RUNTIME_CONTEXT_H diff --git a/mock/distributeddb/common/include/schema_constant.h b/mock/distributeddb/common/include/schema_constant.h new file mode 100644 index 00000000..81077bc8 --- /dev/null +++ b/mock/distributeddb/common/include/schema_constant.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 SCHEMA_CONSTANT_H +#define SCHEMA_CONSTANT_H + +#include + +// This header is supposed to be included only in source files. Do not include it in any header files. +namespace DistributedDB { +class SchemaConstant final { +public: + static const std::string KEYWORD_SCHEMA_VERSION; + static const std::string KEYWORD_SCHEMA_MODE; + static const std::string KEYWORD_SCHEMA_DEFINE; + static const std::string KEYWORD_SCHEMA_INDEXES; + static const std::string KEYWORD_SCHEMA_SKIPSIZE; + static const std::string KEYWORD_SCHEMA_TYPE; + static const std::string KEYWORD_SCHEMA_TABLE; + static const std::string KEYWORD_INDEX; // For FlatBuffer-Schema + + static const std::string KEYWORD_MODE_STRICT; + static const std::string KEYWORD_MODE_COMPATIBLE; + + static const std::string KEYWORD_TYPE_BOOL; + static const std::string KEYWORD_TYPE_INTEGER; + static const std::string KEYWORD_TYPE_LONG; + static const std::string KEYWORD_TYPE_DOUBLE; + static const std::string KEYWORD_TYPE_STRING; + + static const std::string KEYWORD_ATTR_NOT_NULL; + static const std::string KEYWORD_ATTR_DEFAULT; + static const std::string KEYWORD_ATTR_VALUE_NULL; + static const std::string KEYWORD_ATTR_VALUE_TRUE; + static const std::string KEYWORD_ATTR_VALUE_FALSE; + + static const std::string KEYWORD_TYPE_RELATIVE; + static const std::string SCHEMA_SUPPORT_VERSION; + static const std::string SCHEMA_SUPPORT_VERSION_V2; + + static const uint32_t SCHEMA_META_FEILD_COUNT_MAX; + static const uint32_t SCHEMA_META_FEILD_COUNT_MIN; + static const uint32_t SCHEMA_FEILD_NAME_LENGTH_MAX; + static const uint32_t SCHEMA_FEILD_NAME_LENGTH_MIN; + static const uint32_t SCHEMA_FEILD_NAME_COUNT_MAX; + static const uint32_t SCHEMA_FEILD_NAME_COUNT_MIN; + static const uint32_t SCHEMA_FEILD_PATH_DEPTH_MAX; + static const uint32_t SCHEMA_INDEX_COUNT_MAX; + static const uint32_t SCHEMA_STRING_SIZE_LIMIT; + static const uint32_t SCHEMA_DEFAULT_STRING_SIZE_LIMIT; + static const uint32_t SCHEMA_SKIPSIZE_MAX; + + static const uint32_t SECURE_BYTE_ALIGN; +}; +} // namespace DistributedDB +#endif // SCHEMA_CONSTANT_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/schema_negotiate.h b/mock/distributeddb/common/include/schema_negotiate.h new file mode 100644 index 00000000..cddb6d6c --- /dev/null +++ b/mock/distributeddb/common/include/schema_negotiate.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 SCHEMA_NEGOTIATE_H +#define SCHEMA_NEGOTIATE_H + +#include "schema_object.h" +#include "relational_schema_object.h" + +namespace DistributedDB { +struct SyncOpinion { + bool permitSync = false; + bool requirePeerConvert = false; + bool checkOnReceive = false; +}; + +struct SyncStrategy { + bool permitSync = false; + bool convertOnSend = false; + bool convertOnReceive = false; + bool checkOnReceive = false; +}; + +using RelationalSyncOpinion = std::map; +using RelationalSyncStrategy = std::map; + +class SchemaNegotiate { +public: + // The remoteSchemaType may beyond local SchemaType definition + static SyncOpinion MakeLocalSyncOpinion(const SchemaObject &localSchema, const std::string &remoteSchema, + uint8_t remoteSchemaType); + + // The remoteOpinion.checkOnReceive is ignored + static SyncStrategy ConcludeSyncStrategy(const SyncOpinion &localOpinion, const SyncOpinion &remoteOpinion); + + static RelationalSyncOpinion MakeLocalSyncOpinion(const RelationalSchemaObject &localSchema, + const std::string &remoteSchema, uint8_t remoteSchemaType); + + // The remoteOpinion.checkOnReceive is ignored + static RelationalSyncStrategy ConcludeSyncStrategy(const RelationalSyncOpinion &localOpinion, + const RelationalSyncOpinion &remoteOpinion); + + static uint32_t CalculateParcelLen(const RelationalSyncOpinion &opinions); + static int SerializeData(const RelationalSyncOpinion &opinions, Parcel &parcel); + static int DeserializeData(Parcel &parcel, RelationalSyncOpinion &opinion); + +private: + SchemaNegotiate() = default; + ~SchemaNegotiate() = default; +}; +} + +#endif // SCHEMA_NEGOTIATE_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/schema_object.h b/mock/distributeddb/common/include/schema_object.h new file mode 100644 index 00000000..2cf9091a --- /dev/null +++ b/mock/distributeddb/common/include/schema_object.h @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SCHEMA_OBJECT_H +#define SCHEMA_OBJECT_H + +#include +#include +#ifndef OMIT_FLATBUFFER +#include +#endif // OMIT_FLATBUFFER +#include "db_types.h" +#include "macro_utils.h" +#include "relational_schema_object.h" +#include "ischema.h" +#include "value_object.h" + +namespace DistributedDB { +using IndexName = FieldPath; +using IndexFieldInfo = std::pair; +using IndexInfo = std::vector; +template using PairConstPointer = std::pair; + +struct IndexDifference { + std::map change; + std::map increase; + std::set decrease; +}; + +class SchemaObject : public ISchema { +public: + static std::string GetExtractFuncName(SchemaType inSchemaType); + static std::string GenerateExtractSQL(SchemaType inSchemaType, const FieldPath &inFieldpath, FieldType inFieldType, + uint32_t skipSize, const std::string &accessStr = ""); + + // Support default constructor, copy constructor and copy assignment + SchemaObject(); + ~SchemaObject() = default; + SchemaObject(const SchemaObject &); + SchemaObject& operator=(const SchemaObject &); +#ifdef RELATIONAL_STORE + explicit SchemaObject(const TableInfo &tableInfo); // The construct func can only be used for query. +#endif // RELATIONAL_STORE + + // Move constructor and move assignment is not need currently + SchemaObject(SchemaObject &&) = delete; + SchemaObject& operator=(SchemaObject &&) = delete; + + // Should be called on an invalid SchemaObject, create new SchemaObject if need to reparse + int ParseFromSchemaString(const std::string &inSchemaString) override; + + bool IsSchemaValid() const override; + SchemaType GetSchemaType() const override; + + // For Json-Schema : Unnecessary spacing will be removed and fieldname resorted by lexicographical order + // For FlatBuffer-Schema : Original binary schema(Base64 decoded if need) + std::string ToSchemaString() const override; + + uint32_t GetSkipSize() const; + std::map GetIndexInfo() const; + bool IsIndexExist(const IndexName &indexName) const; + + // Return E_OK if queryale. outType will be set if path exist no matter binary or not + int CheckQueryableAndGetFieldType(const FieldPath &inPath, FieldType &outType) const; + + // Attention: it doesn't return E_OK. instead: + // E_JSON_PARSE_FAIL : the inSchemaString is not an valid json + // E_SCHEMA_PARSE_FAIL : the inSchemaString is not an valid schema + // E_SCHEMA_EQUAL_EXACTLY : the inSchema is exactly equal to this SchemaObject + // E_SCHEMA_UNEQUAL_COMPATIBLE : the inSchema is not equal to but only index differ with this SchemaObject + // E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE : the inSchema is not equal to but can upgrade from this SchemaObject + // E_SCHEMA_UNEQUAL_INCOMPATIBLE : the inSchema is not equal to and can not upgrade from this SchemaObject + int CompareAgainstSchemaString(const std::string &inSchemaString) const; + int CompareAgainstSchemaString(const std::string &inSchemaString, IndexDifference &indexDiffer) const; + int CompareAgainstSchemaObject(const SchemaObject &inSchemaObject) const; + int CompareAgainstSchemaObject(const SchemaObject &inSchemaObject, IndexDifference &indexDiffer) const; + + // Attention: it doesn't return E_OK. instead: + // E_VALUE_MATCH : Value match schema(no matter strict or compatible mode) without any change + // E_VALUE_MATCH_AMENDED : Value match schema(no matter strict or compatible mode) with some amendment + // E_VALUE_MISMATCH_FEILD_COUNT : Value contain more field then schema when in strict mode + // E_VALUE_MISMATCH_FEILD_TYPE : Type of some fields of value mismatch schema + // E_VALUE_MISMATCH_CONSTRAINT : Some fields of value violate the NotNull constraint against schema + // E_VALUE_MISMATCH_OTHER_REASON : Value mismatch schema because of other reason unmentioned + int CheckValueAndAmendIfNeed(ValueSource sourceType, ValueObject &inValue) const; + + // Currently only for flatBuffer-type schema and value. + // Accept the original entry-value, return E_OK or E_FLATBUFFER_VERIFY_FAIL. + int VerifyValue(ValueSource sourceType, const Value &inValue) const; + int VerifyValue(ValueSource sourceType, const RawValue &inValue) const; + + // Accept the original value from database. The cache will not be expanded. Return E_OK if nothing error. + // The ExtractValue is with nice performance by carefully not use std-class to avoid memory allocation. + // But currently it can only deal with path with $. prefix and only one depth. However, meet current demand. + int ExtractValue(ValueSource sourceType, RawString inPath, const RawValue &inValue, TypeValue &outExtract, + std::vector *cache) const; +private: + enum class SchemaMode { + STRICT, + COMPATIBLE, + }; + using SchemaDefine = std::map; + + // For Json-Schema : Parsing related methods. + int ParseJsonSchema(const JsonObject &inJsonObject); + int CheckMetaFieldCountAndType(const JsonObject &inJsonObject) const; + int ParseCheckSchemaVersionMode(const JsonObject &inJsonObject); + int ParseCheckSchemaDefine(const JsonObject &inJsonObject); + int CheckSchemaDefineItemDecideAttribute(const JsonObject &inJsonObject, const FieldPath &inPath, FieldType inType, + SchemaAttribute &outAttr) const; + int ParseCheckSchemaIndexes(const JsonObject &inJsonObject); + int ParseCheckSchemaSkipSize(const JsonObject &inJsonObject); + + // For both Json-Schema and FlatBuffer-Schema. + int ParseCheckEachIndexFromStringArray(const std::vector &inStrArray); + int CheckFieldPathIndexableThenSave(const std::vector &inPathVec, IndexInfo &infoToSave); + + // CompareAgainstSchemaObject related sub methods + int CompareSchemaVersionMode(const SchemaObject &newSchema) const; + int CompareSchemaSkipSize(const SchemaObject &newSchema) const; + int CompareSchemaDefine(const SchemaObject &newSchema) const; + int CompareSchemaDefineByDepth(const SchemaDefine &oldDefine, const SchemaDefine &newDefine) const; + int CompareSchemaAttribute(const SchemaAttribute &oldAttr, const SchemaAttribute &newAttr) const; + int CompareSchemaDefaultValue(const SchemaAttribute &oldAttr, const SchemaAttribute &newAttr) const; + int CompareSchemaIndexes(const SchemaObject &newSchema, IndexDifference &indexDiffer) const; + + // CheckValueAndAmendIfNeed related sub methods + int CheckValue(const ValueObject &inValue, std::set &lackingPaths) const; + int AmendValueIfNeed(ValueObject &inValue, const std::set &lackingPaths, bool &amended) const; + + // It is better using a class to represent flatBuffer-Schema related other than more private method(As well as for + // Json-Schema in the future refactor). Delegation is chosen other than inheritance for accessing SchemaObject. + // Choose inner-class other than friend-class to avoid forward declaration and using pointer. + class FlatBufferSchema { + public: + explicit FlatBufferSchema(SchemaObject &owner) : owner_(owner) {}; + ~FlatBufferSchema() = default; + DISABLE_COPY_ASSIGN_MOVE(FlatBufferSchema); + // Copy-Constructor can not define due to Const-Ref member. Code standard require copy assignment be deleted. + void CopyFrom(const FlatBufferSchema &other); + + std::string GetDescription() const; + + // Judge whether it's flatbuffer type schema, no matter whether it is Base64 encoded, provide a decoded one. + static bool IsFlatBufferSchema(const std::string &inOriginal, std::string &outDecoded); + + // Accept a decoded and verified flatbuffer-schema, then parse its content + int ParseFlatBufferSchema(const std::string &inDecoded); + + // Compare based on self. + // return E_SCHEMA_EQUAL_EXACTLY or E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE or E_SCHEMA_UNEQUAL_INCOMPATIBLE + int CompareFlatBufferDefine(const FlatBufferSchema &other) const; + + // Accept a no-skipsize(so byte-aligned) value, return E_OK or E_FLATBUFFER_VERIFY_FAIL. + int VerifyFlatBufferValue(const RawValue &inValue, bool tryNoSizePrefix) const; + + // Accept a no-skipsize(so byte-aligned) value. + int ExtractFlatBufferValue(RawString inPath, const RawValue &inValue, TypeValue &outExtract, + bool tryNoSizePrefix) const; + private: +#ifndef OMIT_FLATBUFFER + using RawIndexInfos = std::map; // First the fieldName, second the index-attr value. + + const reflection::Schema *GetSchema() const; + + int ParseCheckRootTableAttribute(const reflection::Object &rootTable); + int ParseCheckRootTableDefine(const reflection::Schema &schema, const reflection::Object &rootTable, + RawIndexInfos &indexCollect); + int ParseCheckFieldInfo(const reflection::Schema &schema, const reflection::Field &field, + const FieldPath &path, RawIndexInfos &indexCollect); + void CollectRawIndexInfos(const reflection::Field &field, RawIndexInfos &indexCollect) const; + int ParseCheckStructDefine(const reflection::Schema &schema, const reflection::Field &field, + const FieldPath &path); + int ParseCheckIndexes(const RawIndexInfos &indexCollect); + + int CompareTableOrStructDefine(const PairConstPointer &bothSchema, + const PairConstPointer &bothObject, bool isRoot, std::set &compared) const; + int CompareStruct(const PairConstPointer &bothSchema, + const PairConstPointer &bothField, std::set &compared) const; +#endif + SchemaObject &owner_; + std::string description_; + }; + + bool isValid_ = false; + SchemaType schemaType_ = SchemaType::NONE; // Default NONE + FlatBufferSchema flatbufferSchema_; + std::string schemaString_; // The minified and valid schemaString + + std::string schemaVersion_; + SchemaMode schemaMode_ = SchemaMode::STRICT; // Only for Json-Schema, Consider refactor into JsonSchema class + uint32_t schemaSkipSize_ = 0; + std::map schemaIndexes_; + std::map schemaDefine_; // SchemaDefine classified by the depth of fieldpath +}; +} // namespace DistributedDB + +#endif // SCHEMA_OBJECT_H diff --git a/mock/distributeddb/common/include/schema_utils.h b/mock/distributeddb/common/include/schema_utils.h new file mode 100644 index 00000000..0f353c28 --- /dev/null +++ b/mock/distributeddb/common/include/schema_utils.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SCHEMA_UTILS_H +#define SCHEMA_UTILS_H + +#include "db_types.h" +#include "schema_object.h" + +// This header is supposed to be included only in source files. Do not include it in any header files. +namespace DistributedDB { +class SchemaUtils { +public: + // Check if any invalid exist, parse it into SchemaAttribute if totally valid and return E_OK + // Number don't support format of scientific notation. SchemaAttribute.isIndexable always set true. + // Prefix and postfix spaces or tabs is allowed. + // Customer FieldType will be accepted if useAffinity is true. + static int ParseAndCheckSchemaAttribute(const std::string &inAttrString, SchemaAttribute &outAttr, + bool useAffinity = false); + + // Check if any invalid exist, parse it into FieldPath if totally valid and return E_OK + // Each fieldName of the fieldPath will be check valid as well. Path depth will be check. + // Prefix and postfix spaces or tabs is allowed. Prefix $. can be not exist. + // Parameter permitPrefix means whether $. prefix is permited. If not, return E_SCHEMA_PARSE_FAIL. + static int ParseAndCheckFieldPath(const std::string &inPathString, FieldPath &outPath, bool permitPrefix = true); + + // Return E_OK if it is totally valid. Prefix and postfix spaces or tabs is not allowed. + static int CheckFieldName(const FieldName &inName); + + // Remove prefix and postfix spaces or tabs + static std::string Strip(const std::string &inString); + + // Strip the namespace from the full-name, this method mainly for flatbuffer-type schema + static std::string StripNameSpace(const std::string &inFullName); + + static std::string FieldTypeString(FieldType inType); + static std::string SchemaTypeString(SchemaType inType); + + // Restore to string representation of fieldPath with $. prefix + static std::string FieldPathString(const FieldPath &inPath); + + SchemaUtils() = delete; + ~SchemaUtils() = delete; + +private: + + static int SplitSchemaAttribute(const std::string &inAttrString, std::vector &outAttrString); + static int MakeTrans(const std::string &oriContent, size_t &pos); + + static int ParseSchemaAttribute(std::vector &attrContext, SchemaAttribute &outAttr, bool useAffinity); + + static int TransformDefaultValue(std::string &defaultContent, SchemaAttribute &outAttr); + + static int TransToDouble(const std::string &defaultContent, SchemaAttribute &outAttr); + + static int TransToInteger(const std::string &defaultContent, SchemaAttribute &outAttr); + + static int TransToLong(const std::string &defaultContent, SchemaAttribute &outAttr); + + static int TransToString(const std::string &defaultContent, SchemaAttribute &outAttr); + + static int TransToBool(const std::string &defaultContent, SchemaAttribute &outAttr); +}; +} // namespace DistributedDB +#endif // SCHEMA_UTILS_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/semaphore_utils.h b/mock/distributeddb/common/include/semaphore_utils.h new file mode 100644 index 00000000..9cdb3f56 --- /dev/null +++ b/mock/distributeddb/common/include/semaphore_utils.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SEMAPHORE_UTILS_H +#define SEMAPHORE_UTILS_H + +#include +#include +#include +#include +#include "macro_utils.h" + +namespace DistributedDB { +class SemaphoreUtils { +public: + explicit SemaphoreUtils(int count); + ~SemaphoreUtils(); + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SemaphoreUtils); + + bool WaitSemaphore(int waitSecond); + + void WaitSemaphore(); + + void SendSemaphore(); + +private: + bool CompareCount() const; + std::mutex lockMutex_; + std::condition_variable cv_; + int count_; +}; +} + +#endif diff --git a/mock/distributeddb/common/include/task_pool.h b/mock/distributeddb/common/include/task_pool.h new file mode 100644 index 00000000..fe5d8c8b --- /dev/null +++ b/mock/distributeddb/common/include/task_pool.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 TASK_POOL_H +#define TASK_POOL_H + +#include +#include +#include "macro_utils.h" + +namespace DistributedDB { +using Task = std::function; + +class TaskPool { +public: + // Start the task pool. + virtual int Start() = 0; + + // Stop the task pool. + virtual void Stop() = 0; + + // Schedule a task, the task can be ran in any thread. + virtual int Schedule(const Task &task) = 0; + + // Schedule tasks using FIFO policy(tasks with the same 'tag'). + virtual int Schedule(const std::string &tag, const Task &task) = 0; + + // Shrink memory associated with the given tag if possible. + virtual void ShrinkMemory(const std::string &tag) = 0; + + // Create/Destroy a task pool. + static TaskPool *Create(int maxThreads, int minThreads, int &errCode); + static void Release(TaskPool *&taskPool); + +protected: + TaskPool() = default; + virtual ~TaskPool() {} + DISABLE_COPY_ASSIGN_MOVE(TaskPool); +}; +} // namespace DistributedDB + +#endif // TASK_POOL_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/user_change_monitor.h b/mock/distributeddb/common/include/user_change_monitor.h new file mode 100644 index 00000000..6591a40c --- /dev/null +++ b/mock/distributeddb/common/include/user_change_monitor.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 USER_CHANGE_MONITOR_H +#define USER_CHANGE_MONITOR_H + +#include +#include "platform_specific.h" +#include "macro_utils.h" +#include "notification_chain.h" +#include "runtime_context.h" + +namespace DistributedDB { +class UserChangeMonitor final { +public: + UserChangeMonitor(); + ~UserChangeMonitor(); + + DISABLE_COPY_ASSIGN_MOVE(UserChangeMonitor); + + // Start the UserChangeMonitor + int Start(); + + // Stop the UserChangeMonitor + void Stop(); + + // Register a user changed lister, it will be callback when user changed. + NotificationChain::Listener *RegisterUserChangedListerner(const UserChangedAction &action, EventType event, + int &errCode); + + // Notify USER_CHANGE_EVENT. + void NotifyUserChanged() const; + static constexpr EventType USER_ACTIVE_EVENT = 3; + static constexpr EventType USER_NON_ACTIVE_EVENT = 4; + static constexpr EventType USER_ACTIVE_TO_NON_ACTIVE_EVENT = 5; +private: + // prepare notifier chain + int PrepareNotifierChain(); + + mutable std::shared_mutex userChangeMonitorLock_; + NotificationChain *userNotifier_; + bool isStarted_ = false; +}; +} // namespace DistributedDB + +#endif // USER_CHANGE_MONITOR_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/value_hash_calc.h b/mock/distributeddb/common/include/value_hash_calc.h new file mode 100644 index 00000000..2a02ccc5 --- /dev/null +++ b/mock/distributeddb/common/include/value_hash_calc.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 VALUE_HASH_CALC_H +#define VALUE_HASH_CALC_H + +#include + +#include + +#include "db_types.h" +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +class ValueHashCalc { +public: + ValueHashCalc() {}; + ~ValueHashCalc() + { + if (context_ != nullptr) { + delete context_; + context_ = nullptr; + } + } + + int Initialize() + { + context_ = new (std::nothrow) SHA256_CTX; + if (context_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + + int errCode = SHA256_Init(context_); + if (errCode == 0) { + LOGE("sha init failed:%d", errCode); + return -E_CALC_HASH; + } + return E_OK; + } + + int Update(const std::vector &value) + { + if (context_ == nullptr) { + return -E_CALC_HASH; + } + int errCode = SHA256_Update(context_, value.data(), value.size()); + if (errCode == 0) { + LOGE("sha update failed:%d", errCode); + return -E_CALC_HASH; + } + return E_OK; + } + + int GetResult(std::vector &value) + { + if (context_ == nullptr) { + return -E_CALC_HASH; + } + + value.resize(SHA256_DIGEST_LENGTH); + int errCode = SHA256_Final(value.data(), context_); + if (errCode == 0) { + LOGE("sha get result failed:%d", errCode); + return -E_CALC_HASH; + } + + return E_OK; + } + +private: + SHA256_CTX *context_ = nullptr; +}; +} + +#endif // VALUE_HASH_CALC_H diff --git a/mock/distributeddb/common/include/value_object.h b/mock/distributeddb/common/include/value_object.h new file mode 100644 index 00000000..78ef9155 --- /dev/null +++ b/mock/distributeddb/common/include/value_object.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 VALUE_OBJECT_H +#define VALUE_OBJECT_H + +#include "json_object.h" + +namespace DistributedDB { +// ValueObject is the abstraction of value of KvEntry, a value is not always an json in different solutions. +// Thus, ValueObject can't just inherit JsonObject, although their methods are nearly the same. +class ValueObject { +public: + // Support default constructor, copy constructor and copy assignment + ValueObject() = default; + ~ValueObject() = default; + ValueObject(const ValueObject &); + ValueObject& operator=(const ValueObject &); + + // Move constructor and move assignment is not need currently + ValueObject(ValueObject &&) = delete; + ValueObject& operator=(ValueObject &&) = delete; + + // Should be called on an invalid ValueObject, create new ValueObject if need to reparse + int Parse(const std::string &inString); + int Parse(const std::vector &inData); // Whether ends with '\0' in vector is OK + + // The end refer to the byte after the last valid byte + int Parse(const uint8_t *dataBegin, const uint8_t *dataEnd, uint32_t offset = 0); + + bool IsValid() const; + + // Unnecessary spacing will be removed and fieldname resorted by lexicographical order + std::string ToString() const; + void WriteIntoVector(std::vector &outData) const; // An vector version ToString + + bool IsFieldPathExist(const FieldPath &inPath) const; + int GetFieldTypeByFieldPath(const FieldPath &inPath, FieldType &outType) const; + int GetFieldValueByFieldPath(const FieldPath &inPath, FieldValue &outValue) const; + + // An empty fieldpath indicate the root, the outSubPath should be empty before call, we will not empty it at first. + // If inPath is of multiple path, then outSubPath is combination of result of each inPath. + int GetSubFieldPath(const FieldPath &inPath, std::set &outSubPath) const; + int GetSubFieldPath(const std::set &inPath, std::set &outSubPath) const; + int GetSubFieldPathAndType(const FieldPath &inPath, std::map &outSubPathType) const; + int GetSubFieldPathAndType(const std::set &inPath, std::map &outSubPathType) const; + + // Can be called no matter ValueObject valid or not. Invalid turn into valid after successful call. An empty inPath + // is not allowed. LEAF_FIELD_ARRAY and INTERNAL_FIELD_OBJECT is not supported. infinite double is not support. + // inValue is ignored for LEAF_FIELD_NULL. If inPath already exist or nearest path ends with type not object, + // returns not E_OK. Otherwise insert field as well as filling up intermediate field, then returns E_OK; + int InsertField(const FieldPath &inPath, FieldType inType, const FieldValue &inValue); + + // Should be called on an valid ValueObject. Never turn into invalid after call. An empty inPath is not allowed. + // If inPath not exist, returns not E_OK. Otherwise delete field from its parent returns E_OK; + int DeleteField(const FieldPath &inPath); +private: + bool isValid_ = false; + JsonObject value_; + std::vector dataBeforeOffset_; +}; +} // namespace DistributedDB +#endif // VALUE_OBJECT_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/version.h b/mock/distributeddb/common/include/version.h new file mode 100644 index 00000000..857d186f --- /dev/null +++ b/mock/distributeddb/common/include/version.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 VERSION_H +#define VERSION_H + +#include +#include + +namespace DistributedDB { +// Version Regulation: +// Module version is always equal to the max version of its submodule. +// If a module or submodule upgrade to higher version, DO NOT simply increase current version by 1. +// First: you have to preserve current version by renaming it as a historical version. +// Second: Update the current version to the version to be release. +// Finally: Update its parent module's version if exist. +// Why we update the current version to the version to be release? For example, if module A has submodule B and C, +// if now version of B is 105, and C is 101, thus version of A is 105; if now release version is 106 and we upgrade +// submodule C, if we simply change version of C to 102 then version of A is still 105, but if we change version of C +// to 106 then version of A is now 106, so we can know that something had changed for module A. +const std::string SOFTWARE_VERSION_STRING = "1.1.5"; // DistributedDB current version string. +constexpr uint32_t SOFTWARE_VERSION_BASE = 100; // Software version base value, do not change it +constexpr uint32_t SOFTWARE_VERSION_RELEASE_1_0 = SOFTWARE_VERSION_BASE + 1; // 1 for first released version +constexpr uint32_t SOFTWARE_VERSION_RELEASE_2_0 = SOFTWARE_VERSION_BASE + 2; // 2 for second released version +constexpr uint32_t SOFTWARE_VERSION_RELEASE_3_0 = SOFTWARE_VERSION_BASE + 3; // 3 for third released version +constexpr uint32_t SOFTWARE_VERSION_RELEASE_4_0 = SOFTWARE_VERSION_BASE + 4; // 4 for fourth released version +constexpr uint32_t SOFTWARE_VERSION_RELEASE_5_0 = SOFTWARE_VERSION_BASE + 5; // 5 for fifth released version +constexpr uint32_t SOFTWARE_VERSION_RELEASE_6_0 = SOFTWARE_VERSION_BASE + 6; // 6 for sixth released version +constexpr uint32_t SOFTWARE_VERSION_EARLIEST = SOFTWARE_VERSION_RELEASE_1_0; +#ifdef RELATIONAL_STORE +constexpr uint32_t SOFTWARE_VERSION_CURRENT = SOFTWARE_VERSION_RELEASE_6_0; +#else +constexpr uint32_t SOFTWARE_VERSION_CURRENT = SOFTWARE_VERSION_RELEASE_5_0; +#endif +constexpr int VERSION_INVALID = INT32_MAX; + +// Storage Related Version +// LocalNaturalStore Related Version +constexpr int LOCAL_STORE_VERSION_CURRENT = SOFTWARE_VERSION_RELEASE_1_0; +// SingleVerNaturalStore Related Version +constexpr int SINGLE_VER_STORE_VERSION_V1 = SOFTWARE_VERSION_RELEASE_1_0; +constexpr int SINGLE_VER_STORE_VERSION_V2 = SOFTWARE_VERSION_RELEASE_2_0; +constexpr int SINGLE_VER_STORE_VERSION_CURRENT = SOFTWARE_VERSION_RELEASE_3_0; +// MultiVerNaturalStore Related Version +constexpr uint32_t VERSION_FILE_VERSION_CURRENT = 1; +constexpr uint32_t MULTI_VER_STORE_VERSION_CURRENT = SOFTWARE_VERSION_RELEASE_1_0; +constexpr int MULTI_VER_COMMIT_STORAGE_VERSION_CURRENT = SOFTWARE_VERSION_RELEASE_1_0; +constexpr int MULTI_VER_DATA_STORAGE_VERSION_CURRENT = SOFTWARE_VERSION_RELEASE_1_0; +constexpr int MULTI_VER_METADATA_STORAGE_VERSION_CURRENT = SOFTWARE_VERSION_RELEASE_1_0; +constexpr int MULTI_VER_VALUESLICE_STORAGE_VERSION_CURRENT = SOFTWARE_VERSION_RELEASE_1_0; + +// Syncer Related Version +constexpr int TIME_SYNC_VERSION_V1 = SOFTWARE_VERSION_RELEASE_1_0; // time sync proctol added in version 101. +constexpr int ABILITY_SYNC_VERSION_V1 = SOFTWARE_VERSION_RELEASE_2_0; // Ability sync proctol added in version 102. +constexpr uint32_t SINGLE_VER_SYNC_PROCTOL_V1 = SOFTWARE_VERSION_RELEASE_1_0; // The 1st version num +constexpr uint32_t SINGLE_VER_SYNC_PROCTOL_V2 = SOFTWARE_VERSION_RELEASE_2_0; // The 2nd version num +constexpr uint32_t SINGLE_VER_SYNC_PROCTOL_V3 = SOFTWARE_VERSION_RELEASE_3_0; // The third version num +} // namespace DistributedDB + +#endif // VERSION_H \ No newline at end of file diff --git a/mock/distributeddb/common/include/zlib_compression.h b/mock/distributeddb/common/include/zlib_compression.h new file mode 100644 index 00000000..4f14b2c7 --- /dev/null +++ b/mock/distributeddb/common/include/zlib_compression.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 ZLIB_COMPRESSION_H +#define ZLIB_COMPRESSION_H +#ifndef OMIT_ZLIB +#include +#include "data_compression.h" + +namespace DistributedDB { +class ZlibCompression final : public DataCompression { +public: + ZlibCompression(); + ~ZlibCompression() = default; + + int Compress(const std::vector &srcData, std::vector &destData) const override; + int Uncompress(const std::vector &srcData, std::vector &destData, uint32_t destLen) const + override; + +protected: + ZlibCompression(const ZlibCompression& compression) = delete; + ZlibCompression& operator= (const ZlibCompression& compression) = delete; +}; +} // namespace DistributedDB +#endif // OMIT_ZLIB +#endif // ZLIB_COMPRESSION_H \ No newline at end of file diff --git a/mock/distributeddb/common/src/auto_launch.cpp b/mock/distributeddb/common/src/auto_launch.cpp new file mode 100644 index 00000000..c2337dd4 --- /dev/null +++ b/mock/distributeddb/common/src/auto_launch.cpp @@ -0,0 +1,1261 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "auto_launch.h" + +#include + +#include "db_common.h" +#include "db_dump_helper.h" +#include "db_dfx_adapter.h" +#include "db_errno.h" +#include "kv_store_changed_data_impl.h" +#include "kv_store_nb_conflict_data_impl.h" +#include "kvdb_manager.h" +#include "kvdb_pragma.h" +#include "log_print.h" +#include "param_check_utils.h" +#include "relational_store_instance.h" +#include "relational_store_changed_data_impl.h" +#include "runtime_context.h" +#include "semaphore_utils.h" +#include "sync_able_kvdb_connection.h" + +namespace DistributedDB { +namespace { + constexpr int MAX_AUTO_LAUNCH_ITEM_NUM = 8; +} + +void AutoLaunch::SetCommunicatorAggregator(ICommunicatorAggregator *aggregator) +{ + LOGI("[AutoLaunch] SetCommunicatorAggregator"); + std::lock_guard autoLock(communicatorLock_); + int errCode; + if (communicatorAggregator_ != nullptr) { + LOGI("[AutoLaunch] SetCommunicatorAggregator communicatorAggregator_ is not nullptr"); + errCode = communicatorAggregator_->RegOnConnectCallback(nullptr, nullptr); + if (errCode != E_OK) { + LOGW("[AutoLaunch] communicatorAggregator_->RegOnConnectCallback(nullptr, nullptr), errCode:%d", errCode); + } + errCode = communicatorAggregator_->RegCommunicatorLackCallback(nullptr, nullptr); + if (errCode != E_OK) { + LOGW("[AutoLaunch] communicatorAggregator_->RegCommunicatorLackCallback(nullptr, nullptr), errCode:%d", + errCode); + } + } + communicatorAggregator_ = aggregator; + if (aggregator == nullptr) { + LOGI("[AutoLaunch] SetCommunicatorAggregator aggregator is nullptr"); + return; + } + errCode = aggregator->RegOnConnectCallback(std::bind(&AutoLaunch::OnlineCallBack, this, + std::placeholders::_1, std::placeholders::_2), nullptr); + if (errCode != E_OK) { + LOGW("[AutoLaunch] aggregator->RegOnConnectCallback errCode:%d", errCode); + } + errCode = aggregator->RegCommunicatorLackCallback( + std::bind(&AutoLaunch::ReceiveUnknownIdentifierCallBack, this, std::placeholders::_1, std::placeholders::_2), + nullptr); + if (errCode != E_OK) { + LOGW("[AutoLaunch] aggregator->RegCommunicatorLackCallback errCode:%d", errCode); + } +} + +AutoLaunch::~AutoLaunch() +{ + { + std::lock_guard autoLock(communicatorLock_); + LOGI("[AutoLaunch] ~AutoLaunch()"); + if (communicatorAggregator_ != nullptr) { + communicatorAggregator_->RegOnConnectCallback(nullptr, nullptr); + communicatorAggregator_->RegCommunicatorLackCallback(nullptr, nullptr); + communicatorAggregator_ = nullptr; + } + } + // {identifier, userId} + std::set> inDisableSet; + std::set> inWaitIdleSet; + std::unique_lock autoLock(dataLock_); + for (auto &items : autoLaunchItemMap_) { + for (auto &iter : items.second) { + if (iter.second.isDisable) { + inDisableSet.insert({ items.first, iter.first }); + } else if (iter.second.state == AutoLaunchItemState::IDLE && (!iter.second.inObserver)) { + TryCloseConnection(iter.second); + } else { + inWaitIdleSet.insert({ items.first, iter.first }); + iter.second.isDisable = true; + } + } + } + for (const auto &identifierInfo : inDisableSet) { + cv_.wait(autoLock, [identifierInfo, this] { + return autoLaunchItemMap_.count(identifierInfo.first) == 0 || + autoLaunchItemMap_[identifierInfo.first].count(identifierInfo.second) == 0 || + (!autoLaunchItemMap_[identifierInfo.first][identifierInfo.second].isDisable); + }); + if (autoLaunchItemMap_.count(identifierInfo.first) != 0 && + autoLaunchItemMap_[identifierInfo.first].count(identifierInfo.second)) { + TryCloseConnection(autoLaunchItemMap_[identifierInfo.first][identifierInfo.second]); + } + } + for (const auto &info : inWaitIdleSet) { + cv_.wait(autoLock, [info, this] { + return (autoLaunchItemMap_[info.first][info.second].state == AutoLaunchItemState::IDLE) && + (!autoLaunchItemMap_[info.first][info.second].inObserver); + }); + TryCloseConnection(autoLaunchItemMap_[info.first][info.second]); + } +} + +int AutoLaunch::EnableKvStoreAutoLaunchParmCheck(AutoLaunchItem &autoLaunchItem, const std::string &normalIdentifier, + const std::string &dualTupleIdentifier, bool isDualTupleMode) +{ + std::lock_guard autoLock(dataLock_); + std::string userId = autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::USER_ID, ""); + if (isDualTupleMode && autoLaunchItemMap_.count(normalIdentifier) != 0 && + autoLaunchItemMap_[normalIdentifier].count(userId) != 0) { + LOGE("[AutoLaunch] EnableKvStoreAutoLaunchParmCheck identifier is already enabled in normal tuple mode"); + return -E_ALREADY_SET; + } + if (!isDualTupleMode && autoLaunchItemMap_.count(dualTupleIdentifier) != 0 && + autoLaunchItemMap_[dualTupleIdentifier].count(userId) != 0) { + LOGE("[AutoLaunch] EnableKvStoreAutoLaunchParmCheck identifier is already enabled in dual tuple mode"); + return -E_ALREADY_SET; + } + std::string identifier = isDualTupleMode ? dualTupleIdentifier : normalIdentifier; + if (identifier.empty()) { + LOGE("[AutoLaunch] EnableKvStoreAutoLaunchParmCheck identifier is invalid"); + return -E_INVALID_ARGS; + } + if (autoLaunchItemMap_.count(identifier) != 0 && autoLaunchItemMap_[identifier].count(userId) != 0) { + LOGE("[AutoLaunch] EnableKvStoreAutoLaunchParmCheck identifier is already enabled!"); + return -E_ALREADY_SET; + } + uint32_t autoLaunchItemSize = 0; + for (const auto &item : autoLaunchItemMap_) { + autoLaunchItemSize += item.second.size(); + } + if (autoLaunchItemSize == MAX_AUTO_LAUNCH_ITEM_NUM) { + LOGE("[AutoLaunch] EnableKvStoreAutoLaunchParmCheck size is max(8) now"); + return -E_MAX_LIMITS; + } + autoLaunchItem.state = AutoLaunchItemState::IN_ENABLE; + autoLaunchItemMap_[identifier][userId] = autoLaunchItem; + LOGI("[AutoLaunch] EnableKvStoreAutoLaunchParmCheck ok identifier=%.6s, isDual=%d", + STR_TO_HEX(identifier), isDualTupleMode); + return E_OK; +} + +int AutoLaunch::EnableKvStoreAutoLaunch(const KvDBProperties &properties, AutoLaunchNotifier notifier, + const AutoLaunchOption &option) +{ + LOGI("[AutoLaunch] EnableKvStoreAutoLaunch"); + bool isDualTupleMode = properties.GetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, false); + std::string dualTupleIdentifier = properties.GetStringProp(KvDBProperties::DUAL_TUPLE_IDENTIFIER_DATA, ""); + std::string identifier = properties.GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + std::string userId = properties.GetStringProp(KvDBProperties::USER_ID, ""); + std::string appId = properties.GetStringProp(DBProperties::APP_ID, ""); + std::string storeId = properties.GetStringProp(DBProperties::STORE_ID, ""); + std::shared_ptr ptr = std::make_shared(properties); + AutoLaunchItem autoLaunchItem { ptr, notifier, option.observer, option.conflictType, option.notifier }; + autoLaunchItem.isAutoSync = option.isAutoSync; + autoLaunchItem.type = DBType::DB_KV; + int errCode = EnableKvStoreAutoLaunchParmCheck(autoLaunchItem, identifier, dualTupleIdentifier, isDualTupleMode); + if (errCode != E_OK) { + LOGE("[AutoLaunch] EnableKvStoreAutoLaunch failed errCode:%d", errCode); + return errCode; + } + if (isDualTupleMode && !RuntimeContext::GetInstance()->IsSyncerNeedActive(userId, appId, storeId)) { + std::lock_guard autoLock(dataLock_); + std::string tmpIdentifier = isDualTupleMode ? dualTupleIdentifier : identifier; + LOGI("[AutoLaunch] GetDoOpenMap identifier=%.6s no need to open", STR_TO_HEX(tmpIdentifier)); + autoLaunchItemMap_[tmpIdentifier][userId].state = AutoLaunchItemState::IDLE; + return errCode; + } + errCode = GetKVConnectionInEnable(autoLaunchItem, isDualTupleMode ? dualTupleIdentifier : identifier); + if (errCode == E_OK) { + LOGI("[AutoLaunch] EnableKvStoreAutoLaunch ok"); + } else { + LOGE("[AutoLaunch] EnableKvStoreAutoLaunch failed errCode:%d", errCode); + } + return errCode; +} + +int AutoLaunch::GetKVConnectionInEnable(AutoLaunchItem &autoLaunchItem, const std::string &identifier) +{ + LOGI("[AutoLaunch] GetKVConnectionInEnable"); + int errCode; + std::shared_ptr properties = std::static_pointer_cast(autoLaunchItem.propertiesPtr); + std::string userId = properties->GetStringProp(KvDBProperties::USER_ID, ""); + autoLaunchItem.conn = KvDBManager::GetDatabaseConnection(*properties, errCode, false); + if (errCode == -E_ALREADY_OPENED) { + LOGI("[AutoLaunch] GetKVConnectionInEnable user already getkvstore by self"); + std::lock_guard autoLock(dataLock_); + autoLaunchItemMap_[identifier][userId].state = AutoLaunchItemState::IDLE; + return E_OK; + } + if (autoLaunchItem.conn == nullptr) { + EraseAutoLauchItem(identifier, userId); + return errCode; + } + bool isEmpty = false; + { + std::lock_guard onlineDevicesLock(dataLock_); + isEmpty = onlineDevices_.empty(); + } + if (isEmpty) { + LOGI("[AutoLaunch] GetKVConnectionInEnable no online device, ReleaseDatabaseConnection"); + IKvDBConnection *kvConn = static_cast(autoLaunchItem.conn); + errCode = KvDBManager::ReleaseDatabaseConnection(kvConn); + if (errCode != E_OK) { + LOGE("[AutoLaunch] GetKVConnectionInEnable ReleaseDatabaseConnection failed errCode:%d", errCode); + EraseAutoLauchItem(identifier, userId); + return errCode; + } + std::lock_guard autoLock(dataLock_); + autoLaunchItemMap_[identifier][userId].state = AutoLaunchItemState::IDLE; + return E_OK; + } + errCode = RegisterObserverAndLifeCycleCallback(autoLaunchItem, identifier, false); + if (errCode == E_OK) { + std::lock_guard autoLock(dataLock_); + autoLaunchItemMap_[identifier][userId].state = AutoLaunchItemState::IDLE; + autoLaunchItemMap_[identifier][userId].conn = autoLaunchItem.conn; + autoLaunchItemMap_[identifier][userId].observerHandle = autoLaunchItem.observerHandle; + } else { + LOGE("[AutoLaunch] GetKVConnectionInEnable RegisterObserverAndLifeCycleCallback err, do CloseConnection"); + TryCloseConnection(autoLaunchItem); // do nothing if failed + EraseAutoLauchItem(identifier, userId); + } + return errCode; +} + +// we will return errCode, if errCode != E_OK +int AutoLaunch::CloseConnectionStrict(AutoLaunchItem &autoLaunchItem) +{ + LOGI("[AutoLaunch] CloseConnectionStrict"); + if (autoLaunchItem.conn == nullptr) { + LOGI("[AutoLaunch] CloseConnectionStrict conn is nullptr, do nothing"); + return E_OK; + } + IKvDBConnection *kvConn = static_cast(autoLaunchItem.conn); + int errCode = kvConn->RegisterLifeCycleCallback(nullptr); + if (errCode != E_OK) { + LOGE("[AutoLaunch] CloseConnectionStrict RegisterLifeCycleCallback failed errCode:%d", errCode); + return errCode; + } + if (autoLaunchItem.observerHandle != nullptr) { + errCode = kvConn->UnRegisterObserver(autoLaunchItem.observerHandle); + if (errCode != E_OK) { + LOGE("[AutoLaunch] CloseConnectionStrict UnRegisterObserver failed errCode:%d", errCode); + return errCode; + } + autoLaunchItem.observerHandle = nullptr; + } + errCode = KvDBManager::ReleaseDatabaseConnection(kvConn); + if (errCode != E_OK) { + LOGE("[AutoLaunch] CloseConnectionStrict ReleaseDatabaseConnection failed errCode:%d", errCode); + } + return errCode; +} + +// before ReleaseDatabaseConnection, if errCode != E_OK, we not return, we try close more +void AutoLaunch::TryCloseConnection(AutoLaunchItem &autoLaunchItem) +{ + LOGI("[AutoLaunch] TryCloseConnection"); + switch (autoLaunchItem.type) { + case DBType::DB_KV: + TryCloseKvConnection(autoLaunchItem); + break; + case DBType::DB_RELATION: + TryCloseRelationConnection(autoLaunchItem); + break; + default: + LOGD("[AutoLaunch] Unknown type[%d] when try to close connection", static_cast(autoLaunchItem.type)); + break; + } +} + +int AutoLaunch::RegisterObserverAndLifeCycleCallback(AutoLaunchItem &autoLaunchItem, const std::string &identifier, + bool isExt) +{ + int errCode = RegisterObserver(autoLaunchItem, identifier, isExt); + if (errCode != E_OK) { + return errCode; + } + LOGI("[AutoLaunch] RegisterObserver ok"); + + errCode = RegisterLifeCycleCallback(autoLaunchItem, identifier, isExt); + if (errCode != E_OK) { + LOGE("[AutoLaunch] RegisterLifeCycleCallback failed, errCode:%d", errCode); + return errCode; + } + LOGI("[AutoLaunch] RegisterLifeCycleCallback ok"); + + errCode = SetConflictNotifier(autoLaunchItem); + if (errCode != E_OK) { + LOGE("[AutoLaunch] SetConflictNotifier failed, errCode:%d", errCode); + return errCode; + } + + return PragmaAutoSync(autoLaunchItem); +} + +int AutoLaunch::RegisterObserver(AutoLaunchItem &autoLaunchItem, const std::string &identifier, bool isExt) +{ + if (autoLaunchItem.conn == nullptr) { + LOGE("[AutoLaunch] autoLaunchItem.conn is nullptr"); + return -E_INTERNAL_ERROR; + } + LOGI("[AutoLaunch] RegisterObserver type=%d", static_cast(autoLaunchItem.type)); + if (autoLaunchItem.type == DBType::DB_RELATION) { + RelationalStoreConnection *conn = static_cast(autoLaunchItem.conn); + conn->RegisterObserverAction([autoLaunchItem](const std::string &changedDevice) { + RelationalStoreChangedDataImpl data(changedDevice); + if (autoLaunchItem.propertiesPtr != nullptr) { + data.SetStoreProperty({ + autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::USER_ID, ""), + autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::APP_ID, ""), + autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::STORE_ID, "") + }); + } + if (autoLaunchItem.storeObserver) { + LOGD("begin to observer onchange, changedDevice=%s", STR_MASK(changedDevice)); + autoLaunchItem.storeObserver->OnChange(data); + } + }); + return E_OK; + } + std::shared_ptr properties = + std::static_pointer_cast(autoLaunchItem.propertiesPtr); + std::string userId = properties->GetStringProp(KvDBProperties::USER_ID, ""); + int errCode; + Key key; + KvDBObserverHandle *observerHandle = nullptr; + IKvDBConnection *kvConn = static_cast(autoLaunchItem.conn); + if (isExt) { + observerHandle = kvConn->RegisterObserver(OBSERVER_CHANGES_FOREIGN, key, + std::bind(&AutoLaunch::ExtObserverFunc, this, std::placeholders::_1, identifier, userId), errCode); + } else { + observerHandle = kvConn->RegisterObserver(OBSERVER_CHANGES_FOREIGN, key, + std::bind(&AutoLaunch::ObserverFunc, this, std::placeholders::_1, identifier, userId), errCode); + } + + if (errCode != E_OK) { + LOGE("[AutoLaunch] RegisterObserver failed:%d!", errCode); + return errCode; + } + autoLaunchItem.observerHandle = observerHandle; + return E_OK; +} + +void AutoLaunch::ObserverFunc(const KvDBCommitNotifyData ¬ifyData, const std::string &identifier, + const std::string &userId) +{ + LOGD("[AutoLaunch] ObserverFunc identifier=%.6s", STR_TO_HEX(identifier)); + AutoLaunchItem autoLaunchItem; + std::string appId; + std::string storeId; + { + std::lock_guard autoLock(dataLock_); + if (autoLaunchItemMap_.count(identifier) == 0 || autoLaunchItemMap_[identifier].count(userId) == 0) { + LOGE("[AutoLaunch] ObserverFunc err no this identifier in map"); + return; + } + if (autoLaunchItemMap_[identifier][userId].isDisable) { + LOGI("[AutoLaunch] ObserverFunc isDisable, do nothing"); + return; + } + autoLaunchItemMap_[identifier][userId].inObserver = true; + autoLaunchItem.observer = autoLaunchItemMap_[identifier][userId].observer; + autoLaunchItem.isWriteOpenNotified = autoLaunchItemMap_[identifier][userId].isWriteOpenNotified; + autoLaunchItem.notifier = autoLaunchItemMap_[identifier][userId].notifier; + + std::shared_ptr properties = + std::static_pointer_cast(autoLaunchItemMap_[identifier][userId].propertiesPtr); + appId = properties->GetStringProp(KvDBProperties::APP_ID, ""); + storeId = properties->GetStringProp(KvDBProperties::STORE_ID, ""); + } + if (autoLaunchItem.observer != nullptr) { + LOGI("[AutoLaunch] do user observer"); + KvStoreChangedDataImpl data(¬ifyData); + (autoLaunchItem.observer)->OnChange(data); + } + LOGI("[AutoLaunch] in observer autoLaunchItem.isWriteOpenNotified:%d", autoLaunchItem.isWriteOpenNotified); + + if (!autoLaunchItem.isWriteOpenNotified && autoLaunchItem.notifier != nullptr) { + { + std::lock_guard autoLock(dataLock_); + autoLaunchItemMap_[identifier][userId].isWriteOpenNotified = true; + } + AutoLaunchNotifier notifier = autoLaunchItem.notifier; + int retCode = RuntimeContext::GetInstance()->ScheduleTask([notifier, userId, appId, storeId] { + LOGI("[AutoLaunch] notify the user auto opened event"); + notifier(userId, appId, storeId, AutoLaunchStatus::WRITE_OPENED); + }); + if (retCode != E_OK) { + LOGE("[AutoLaunch] ObserverFunc notifier ScheduleTask retCode:%d", retCode); + } + } + std::lock_guard autoLock(dataLock_); + autoLaunchItemMap_[identifier][userId].inObserver = false; + cv_.notify_all(); +} + +int AutoLaunch::DisableKvStoreAutoLaunch(const std::string &normalIdentifier, const std::string &dualTupleIdentifier, + const std::string &userId) +{ + std::string identifier = (autoLaunchItemMap_.count(normalIdentifier) == 0) ? dualTupleIdentifier : normalIdentifier; + LOGI("[AutoLaunch] DisableKvStoreAutoLaunch identifier=%.6s", STR_TO_HEX(identifier)); + AutoLaunchItem autoLaunchItem; + { + std::unique_lock autoLock(dataLock_); + if (autoLaunchItemMap_.count(identifier) == 0 || autoLaunchItemMap_[identifier].count(userId) == 0) { + LOGE("[AutoLaunch] DisableKvStoreAutoLaunch identifier is not exist!"); + return -E_NOT_FOUND; + } + if (autoLaunchItemMap_[identifier][userId].isDisable) { + LOGI("[AutoLaunch] DisableKvStoreAutoLaunch already disabling in another thread, do nothing here"); + return -E_BUSY; + } + if (autoLaunchItemMap_[identifier][userId].state == AutoLaunchItemState::IN_ENABLE) { + LOGE("[AutoLaunch] DisableKvStoreAutoLaunch enable not return, do not disable!"); + return -E_BUSY; + } + autoLaunchItemMap_[identifier][userId].isDisable = true; + if (autoLaunchItemMap_[identifier][userId].state != AutoLaunchItemState::IDLE) { + LOGI("[AutoLaunch] DisableKvStoreAutoLaunch wait idle"); + cv_.wait(autoLock, [identifier, userId, this] { + return (autoLaunchItemMap_[identifier][userId].state == AutoLaunchItemState::IDLE) && + (!autoLaunchItemMap_[identifier][userId].inObserver); + }); + LOGI("[AutoLaunch] DisableKvStoreAutoLaunch wait idle ok"); + } + autoLaunchItem = autoLaunchItemMap_[identifier][userId]; + } + + int errCode = CloseConnectionStrict(autoLaunchItem); + if (errCode != E_OK) { + LOGE("[AutoLaunch] DisableKvStoreAutoLaunch CloseConnection failed errCode:%d", errCode); + std::lock_guard autoLock(dataLock_); + autoLaunchItemMap_[identifier][userId].isDisable = false; + autoLaunchItemMap_[identifier][userId].observerHandle = autoLaunchItem.observerHandle; + cv_.notify_all(); + return errCode; + } + + EraseAutoLauchItem(identifier, userId); + cv_.notify_all(); + if (autoLaunchItem.isWriteOpenNotified && autoLaunchItem.notifier) { + RuntimeContext::GetInstance()->ScheduleTask([autoLaunchItem] { CloseNotifier(autoLaunchItem); }); + } + LOGI("[AutoLaunch] DisableKvStoreAutoLaunch ok"); + return E_OK; +} + +void AutoLaunch::GetAutoLaunchSyncDevices(const std::string &identifier, std::vector &devices) const +{ + devices.clear(); + devices.shrink_to_fit(); + std::lock_guard autoLock(dataLock_); + if (autoLaunchItemMap_.count(identifier) == 0) { + LOGD("[AutoLaunch] GetSyncDevices identifier is not exist!"); + return; + } + for (const auto &device : onlineDevices_) { + devices.push_back(device); + } +} + +void AutoLaunch::CloseNotifier(const AutoLaunchItem &autoLaunchItem) +{ + if (autoLaunchItem.notifier) { + std::string userId = autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::USER_ID, ""); + std::string appId = autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::APP_ID, ""); + std::string storeId = autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::STORE_ID, ""); + LOGI("[AutoLaunch] CloseNotifier do autoLaunchItem.notifier"); + autoLaunchItem.notifier(userId, appId, storeId, AutoLaunchStatus::WRITE_CLOSED); + LOGI("[AutoLaunch] CloseNotifier do autoLaunchItem.notifier finished"); + } else { + LOGI("[AutoLaunch] CloseNotifier autoLaunchItem.notifier is nullptr"); + } +} + +void AutoLaunch::ConnectionLifeCycleCallbackTask(const std::string &identifier, const std::string &userId) +{ + LOGI("[AutoLaunch] ConnectionLifeCycleCallbackTask identifier=%.6s", STR_TO_HEX(identifier)); + AutoLaunchItem autoLaunchItem; + { + std::lock_guard autoLock(dataLock_); + if (autoLaunchItemMap_.count(identifier) == 0 || autoLaunchItemMap_[identifier].count(userId) == 0) { + LOGE("[AutoLaunch] ConnectionLifeCycleCallback identifier is not exist!"); + return; + } + if (autoLaunchItemMap_[identifier][userId].isDisable) { + LOGI("[AutoLaunch] ConnectionLifeCycleCallback isDisable, do nothing"); + return; + } + if (autoLaunchItemMap_[identifier][userId].state != AutoLaunchItemState::IDLE) { + LOGI("[AutoLaunch] ConnectionLifeCycleCallback state:%d is not idle, do nothing", + static_cast(autoLaunchItemMap_[identifier][userId].state)); + return; + } + autoLaunchItemMap_[identifier][userId].state = AutoLaunchItemState::IN_LIFE_CYCLE_CALL_BACK; + autoLaunchItem = autoLaunchItemMap_[identifier][userId]; + } + LOGI("[AutoLaunch] ConnectionLifeCycleCallbackTask do CloseConnection"); + TryCloseConnection(autoLaunchItem); // do nothing if failed + LOGI("[AutoLaunch] ConnectionLifeCycleCallback do CloseConnection finished"); + { + std::lock_guard lock(dataLock_); + autoLaunchItemMap_[identifier][userId].state = AutoLaunchItemState::IDLE; + autoLaunchItemMap_[identifier][userId].conn = nullptr; + autoLaunchItemMap_[identifier][userId].isWriteOpenNotified = false; + cv_.notify_all(); + LOGI("[AutoLaunch] ConnectionLifeCycleCallback notify_all"); + } + if (autoLaunchItem.isWriteOpenNotified) { + CloseNotifier(autoLaunchItem); + } +} + +void AutoLaunch::ConnectionLifeCycleCallback(const std::string &identifier, const std::string &userId) +{ + LOGI("[AutoLaunch] ConnectionLifeCycleCallback identifier=%.6s", STR_TO_HEX(identifier)); + int errCode = RuntimeContext::GetInstance()->ScheduleTask(std::bind(&AutoLaunch::ConnectionLifeCycleCallbackTask, + this, identifier, userId)); + if (errCode != E_OK) { + LOGE("[AutoLaunch] ConnectionLifeCycleCallback ScheduleTask failed"); + } +} + +int AutoLaunch::OpenOneConnection(AutoLaunchItem &autoLaunchItem) +{ + LOGI("[AutoLaunch] GetOneConnection"); + int errCode; + switch (autoLaunchItem.type) { + case DBType::DB_KV: + errCode = OpenKvConnection(autoLaunchItem); + break; + case DBType::DB_RELATION: + errCode = OpenRelationalConnection(autoLaunchItem); + break; + default: + errCode = -E_INVALID_ARGS; + } + if (errCode == -E_INVALID_PASSWD_OR_CORRUPTED_DB) { + std::string userId = autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::USER_ID, ""); + std::string appId = autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::APP_ID, ""); + std::string storeId = autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::STORE_ID, ""); + DBDfxAdapter::ReportFault( { DBDfxAdapter::EVENT_OPEN_DATABASE_FAILED, userId, appId, storeId, errCode } ); + } + return errCode; +} + +void AutoLaunch::OnlineCallBack(const std::string &device, bool isConnect) +{ + LOGI("[AutoLaunch] OnlineCallBack device:%s{private}, isConnect:%d", device.c_str(), isConnect); + if (!isConnect) { + std::lock_guard autoLock(dataLock_); + onlineDevices_.erase(device); + return; + } + { + std::lock_guard autoLock(dataLock_); + onlineDevices_.insert(device); + } + + int errCode = RuntimeContext::GetInstance()->ScheduleTask(std::bind(&AutoLaunch::OnlineCallBackTask, this)); + if (errCode != E_OK) { + LOGE("[AutoLaunch] OnlineCallBack ScheduleTask failed"); + } +} + +void AutoLaunch::OnlineCallBackTask() +{ + LOGI("[AutoLaunch] OnlineCallBackTask"); + // > + std::map> doOpenMap; + GetDoOpenMap(doOpenMap); + GetConnInDoOpenMap(doOpenMap); + UpdateGlobalMap(doOpenMap); +} + +void AutoLaunch::GetDoOpenMap(std::map> &doOpenMap) +{ + std::lock_guard autoLock(dataLock_); + LOGI("[AutoLaunch] GetDoOpenMap"); + for (auto &items : autoLaunchItemMap_) { + for (auto &iter : items.second) { + std::string userId = iter.second.propertiesPtr->GetStringProp(DBProperties::USER_ID, ""); + std::string appId = iter.second.propertiesPtr->GetStringProp(DBProperties::APP_ID, ""); + std::string storeId = iter.second.propertiesPtr->GetStringProp(DBProperties::STORE_ID, ""); + bool isDualTupleMode = iter.second.propertiesPtr->GetBoolProp(DBProperties::SYNC_DUAL_TUPLE_MODE, false); + if (iter.second.isDisable) { + LOGI("[AutoLaunch] GetDoOpenMap this item isDisable do nothing"); + continue; + } else if (iter.second.state != AutoLaunchItemState::IDLE) { + LOGI("[AutoLaunch] GetDoOpenMap this item state:%d is not idle do nothing", + static_cast(iter.second.state)); + continue; + } else if (iter.second.conn != nullptr) { + LOGI("[AutoLaunch] GetDoOpenMap this item is opened"); + continue; + } else if (isDualTupleMode && !RuntimeContext::GetInstance()->IsSyncerNeedActive(userId, appId, storeId)) { + LOGI("[AutoLaunch] GetDoOpenMap this item no need to open"); + continue; + } else { + doOpenMap[items.first][iter.first] = iter.second; + iter.second.state = AutoLaunchItemState::IN_COMMUNICATOR_CALL_BACK; + LOGI("[AutoLaunch] GetDoOpenMap this item in IN_COMMUNICATOR_CALL_BACK"); + } + } + } +} + +void AutoLaunch::GetConnInDoOpenMap(std::map> &doOpenMap) +{ + LOGI("[AutoLaunch] GetConnInDoOpenMap doOpenMap.size():%zu", doOpenMap.size()); + if (doOpenMap.empty()) { + return; + } + uint32_t totalSize = 0; + for (auto &items : doOpenMap) { + totalSize += items.second.size(); + } + SemaphoreUtils sema(1 - totalSize); + for (auto &items : doOpenMap) { + for (auto &iter : items.second) { + int errCode = RuntimeContext::GetInstance()->ScheduleTask([&sema, &iter, &items, this] { + int ret = OpenOneConnection(iter.second); + LOGI("[AutoLaunch] GetConnInDoOpenMap GetOneConnection errCode:%d\n", ret); + if (iter.second.conn == nullptr) { + sema.SendSemaphore(); + LOGI("[AutoLaunch] GetConnInDoOpenMap in open thread finish SendSemaphore"); + return; + } + ret = RegisterObserverAndLifeCycleCallback(iter.second, items.first, false); + if (ret != E_OK) { + LOGE("[AutoLaunch] GetConnInDoOpenMap failed, we do CloseConnection"); + TryCloseConnection(iter.second); // if here failed, do nothing + iter.second.conn = nullptr; + } + sema.SendSemaphore(); + LOGI("[AutoLaunch] GetConnInDoOpenMap in open thread finish SendSemaphore"); + }); + if (errCode != E_OK) { + LOGE("[AutoLaunch] GetConnInDoOpenMap ScheduleTask failed, SendSemaphore"); + sema.SendSemaphore(); + } + } + } + LOGI("[AutoLaunch] GetConnInDoOpenMap WaitSemaphore"); + sema.WaitSemaphore(); + LOGI("[AutoLaunch] GetConnInDoOpenMap WaitSemaphore ok"); +} + +void AutoLaunch::UpdateGlobalMap(std::map> &doOpenMap) +{ + std::lock_guard autoLock(dataLock_); + LOGI("[AutoLaunch] UpdateGlobalMap"); + for (auto &items : doOpenMap) { + for (auto &iter : items.second) { + if (iter.second.conn != nullptr) { + autoLaunchItemMap_[items.first][iter.first].conn = iter.second.conn; + autoLaunchItemMap_[items.first][iter.first].observerHandle = iter.second.observerHandle; + autoLaunchItemMap_[items.first][iter.first].isWriteOpenNotified = false; + LOGI("[AutoLaunch] UpdateGlobalMap opened conn update map"); + } + autoLaunchItemMap_[items.first][iter.first].state = AutoLaunchItemState::IDLE; + LOGI("[AutoLaunch] UpdateGlobalMap opened conn set state IDLE"); + } + } + cv_.notify_all(); + LOGI("[AutoLaunch] UpdateGlobalMap finish notify_all"); +} + +void AutoLaunch::ReceiveUnknownIdentifierCallBackTask(const std::string &identifier, const std::string &userId) +{ + LOGI("[AutoLaunch] ReceiveUnknownIdentifierCallBackTask identifier=%.6s", STR_TO_HEX(identifier)); + AutoLaunchItem autoLaunchItem; + { + std::lock_guard autoLock(dataLock_); + autoLaunchItem = autoLaunchItemMap_[identifier][userId]; + } + int errCode = OpenOneConnection(autoLaunchItem); + LOGI("[AutoLaunch] ReceiveUnknownIdentifierCallBack GetOneConnection errCode:%d\n", errCode); + if (autoLaunchItem.conn == nullptr) { + std::lock_guard autoLock(dataLock_); + autoLaunchItemMap_[identifier][userId].state = AutoLaunchItemState::IDLE; + cv_.notify_all(); + LOGI("[AutoLaunch] ReceiveUnknownIdentifierCallBackTask set state IDLE"); + return; + } + errCode = RegisterObserverAndLifeCycleCallback(autoLaunchItem, identifier, false); + if (errCode != E_OK) { + LOGE("[AutoLaunch] ReceiveUnknownIdentifierCallBackTask RegisterObserverAndLifeCycleCallback failed"); + LOGI("[AutoLaunch] ReceiveUnknownIdentifierCallBackTask do CloseConnection"); + TryCloseConnection(autoLaunchItem); // if here failed, do nothing + std::lock_guard autoLock(dataLock_); + autoLaunchItemMap_[identifier][userId].state = AutoLaunchItemState::IDLE; + cv_.notify_all(); + LOGI("[AutoLaunch] ReceiveUnknownIdentifierCallBackTask set state IDLE"); + return; + } + std::lock_guard autoLock(dataLock_); + autoLaunchItemMap_[identifier][userId].conn = autoLaunchItem.conn; + autoLaunchItemMap_[identifier][userId].observerHandle = autoLaunchItem.observerHandle; + autoLaunchItemMap_[identifier][userId].isWriteOpenNotified = false; + autoLaunchItemMap_[identifier][userId].state = AutoLaunchItemState::IDLE; + cv_.notify_all(); + LOGI("[AutoLaunch] ReceiveUnknownIdentifierCallBackTask conn opened set state IDLE"); +} + +int AutoLaunch::ReceiveUnknownIdentifierCallBack(const LabelType &label, const std::string &originalUserId) +{ + const std::string identifier(label.begin(), label.end()); + // originalUserId size maybe 0 + std::string userId = originalUserId; + if (originalUserId.size() == 0 && autoLaunchItemMap_.count(identifier) != 0 && + autoLaunchItemMap_[identifier].size() > 1) { + LOGE("[AutoLaunch] normal tuple mode userId larger than one userId"); + goto EXT; + } + if (originalUserId.size() == 0 && autoLaunchItemMap_.count(identifier) != 0 && + autoLaunchItemMap_[identifier].size() == 1) { + // normal tuple mode + userId = autoLaunchItemMap_[identifier].begin()->first; + } + LOGI("[AutoLaunch] ReceiveUnknownIdentifierCallBack identifier=%.6s", STR_TO_HEX(identifier)); + int errCode; + { + std::lock_guard autoLock(dataLock_); + if (autoLaunchItemMap_.count(identifier) == 0 || autoLaunchItemMap_[identifier].count(userId) == 0) { + LOGI("[AutoLaunch] ReceiveUnknownIdentifierCallBack not find identifier"); + goto EXT; + } else if (autoLaunchItemMap_[identifier][userId].isDisable) { + LOGI("[AutoLaunch] ReceiveUnknownIdentifierCallBack isDisable ,do nothing"); + return -E_NOT_FOUND; // not E_OK is ok for communicator + } else if (autoLaunchItemMap_[identifier][userId].conn != nullptr) { + LOGI("[AutoLaunch] ReceiveUnknownIdentifierCallBack conn is not nullptr"); + return E_OK; + } else if (autoLaunchItemMap_[identifier][userId].state != AutoLaunchItemState::IDLE) { + LOGI("[AutoLaunch] ReceiveUnknownIdentifierCallBack state:%d is not idle, do nothing", + static_cast(autoLaunchItemMap_[identifier][userId].state)); + return E_OK; + } + autoLaunchItemMap_[identifier][userId].state = AutoLaunchItemState::IN_COMMUNICATOR_CALL_BACK; + LOGI("[AutoLaunch] ReceiveUnknownIdentifierCallBack set state IN_COMMUNICATOR_CALL_BACK"); + } + + errCode = RuntimeContext::GetInstance()->ScheduleTask(std::bind( + &AutoLaunch::ReceiveUnknownIdentifierCallBackTask, this, identifier, userId)); + if (errCode != E_OK) { + LOGE("[AutoLaunch] ReceiveUnknownIdentifierCallBack ScheduleTask failed"); + std::lock_guard autoLock(dataLock_); + autoLaunchItemMap_[identifier][userId].state = AutoLaunchItemState::IDLE; + } + return errCode; + +EXT: + return AutoLaunchExt(identifier, userId); +} + +void AutoLaunch::SetAutoLaunchRequestCallback(const AutoLaunchRequestCallback &callback, DBType type) +{ + LOGI("[AutoLaunch] SetAutoLaunchRequestCallback type[%d]", static_cast(type)); + std::lock_guard lock(extLock_); + if (callback) { + autoLaunchRequestCallbackMap_[type] = callback; + } else if (autoLaunchRequestCallbackMap_.find(type) != autoLaunchRequestCallbackMap_.end()) { + autoLaunchRequestCallbackMap_.erase(type); + } +} + +int AutoLaunch::AutoLaunchExt(const std::string &identifier, const std::string &userId) +{ + AutoLaunchParam param; + // for non dual tuple mode, userId is "" + param.userId = userId; + DBType openType = DBType::DB_INVALID; + int errCode = ExtAutoLaunchRequestCallBack(identifier, param, openType); + if (errCode != E_OK) { + return errCode; // not E_OK is ok for communicator + } + + std::shared_ptr ptr; + errCode = AutoLaunch::GetAutoLaunchProperties(param, openType, false, ptr); + if (errCode != E_OK) { + LOGE("[AutoLaunch] AutoLaunchExt param check fail errCode:%d", errCode); + if (!param.notifier) { + return errCode; + } + int retCode = RuntimeContext::GetInstance()->ScheduleTask([param] { + param.notifier(param.userId, param.appId, param.storeId, INVALID_PARAM); + }); + if (retCode != E_OK) { + LOGE("[AutoLaunch] AutoLaunchExt notifier ScheduleTask retCode:%d", retCode); + } + return errCode; + } + AutoLaunchItem autoLaunchItem{ptr, param.notifier, param.option.observer, param.option.conflictType, + param.option.notifier}; + autoLaunchItem.isAutoSync = param.option.isAutoSync; + autoLaunchItem.type = openType; + autoLaunchItem.storeObserver = param.option.storeObserver; + errCode = RuntimeContext::GetInstance()->ScheduleTask(std::bind(&AutoLaunch::AutoLaunchExtTask, this, + identifier, param.userId, autoLaunchItem)); + if (errCode != E_OK) { + LOGE("[AutoLaunch] AutoLaunchExt ScheduleTask errCode:%d", errCode); + } + return errCode; +} + +void AutoLaunch::AutoLaunchExtTask(const std::string &identifier, const std::string &userId, + AutoLaunchItem &autoLaunchItem) +{ + { + std::lock_guard autoLock(extLock_); + if (extItemMap_.count(identifier) != 0 && extItemMap_[identifier].count(userId) != 0) { + LOGE("[AutoLaunch] extItemMap has this identifier"); + return; + } + extItemMap_[identifier][userId] = autoLaunchItem; + } + bool abort = false; + do { + int errCode = CheckAutoLaunchRealPath(autoLaunchItem); + if (errCode != E_OK) { + abort = true; + break; + } + errCode = OpenOneConnection(autoLaunchItem); + LOGI("[AutoLaunch] AutoLaunchExtTask GetOneConnection errCode:%d", errCode); + if (autoLaunchItem.conn == nullptr) { + abort = true; + break; + } + errCode = RegisterObserverAndLifeCycleCallback(autoLaunchItem, identifier, true); + if (errCode != E_OK) { + LOGE("[AutoLaunch] AutoLaunchExtTask RegisterObserverAndLifeCycleCallback failed"); + TryCloseConnection(autoLaunchItem); // if here failed, do nothing + abort = true; + } + } while (false); + if (abort) { + std::lock_guard autoLock(extLock_); + extItemMap_[identifier].erase(userId); + if (extItemMap_[identifier].size() == 0) { + extItemMap_.erase(identifier); + } + return; + } + std::lock_guard autoLock(extLock_); + extItemMap_[identifier][userId].conn = autoLaunchItem.conn; + extItemMap_[identifier][userId].observerHandle = autoLaunchItem.observerHandle; + extItemMap_[identifier][userId].isWriteOpenNotified = false; + LOGI("[AutoLaunch] AutoLaunchExtTask ok"); +} + +void AutoLaunch::ExtObserverFunc(const KvDBCommitNotifyData ¬ifyData, const std::string &identifier, + const std::string &userId) +{ + LOGD("[AutoLaunch] ExtObserverFunc identifier=%.6s", STR_TO_HEX(identifier)); + AutoLaunchItem autoLaunchItem; + AutoLaunchNotifier notifier; + { + std::lock_guard autoLock(extLock_); + if (extItemMap_.count(identifier) == 0 || extItemMap_[identifier].count(userId) == 0) { + LOGE("[AutoLaunch] ExtObserverFunc this identifier not in map"); + return; + } + autoLaunchItem = extItemMap_[identifier][userId]; + } + if (autoLaunchItem.observer != nullptr) { + LOGD("[AutoLaunch] do user observer"); + KvStoreChangedDataImpl data(¬ifyData); + autoLaunchItem.observer->OnChange(data); + } + + { + std::lock_guard autoLock(extLock_); + if (extItemMap_.count(identifier) != 0 && extItemMap_[identifier].count(userId) != 0 && + !extItemMap_[identifier][userId].isWriteOpenNotified && + autoLaunchItem.notifier != nullptr) { + extItemMap_[identifier][userId].isWriteOpenNotified = true; + notifier = autoLaunchItem.notifier; + } else { + return; + } + } + + std::string appId = autoLaunchItem.propertiesPtr->GetStringProp(KvDBProperties::APP_ID, ""); + std::string storeId = autoLaunchItem.propertiesPtr->GetStringProp(KvDBProperties::STORE_ID, ""); + int retCode = RuntimeContext::GetInstance()->ScheduleTask([notifier, userId, appId, storeId] { + LOGI("[AutoLaunch] ExtObserverFunc do user notifier WRITE_OPENED"); + notifier(userId, appId, storeId, AutoLaunchStatus::WRITE_OPENED); + }); + if (retCode != E_OK) { + LOGE("[AutoLaunch] ExtObserverFunc notifier ScheduleTask retCode:%d", retCode); + } +} + +void AutoLaunch::ExtConnectionLifeCycleCallback(const std::string &identifier, const std::string &userId) +{ + LOGI("[AutoLaunch] ExtConnectionLifeCycleCallback identifier=%.6s", STR_TO_HEX(identifier)); + int errCode = RuntimeContext::GetInstance()->ScheduleTask(std::bind( + &AutoLaunch::ExtConnectionLifeCycleCallbackTask, this, identifier, userId)); + if (errCode != E_OK) { + LOGE("[AutoLaunch] ExtConnectionLifeCycleCallback ScheduleTask failed"); + } +} + +void AutoLaunch::ExtConnectionLifeCycleCallbackTask(const std::string &identifier, const std::string &userId) +{ + LOGI("[AutoLaunch] ExtConnectionLifeCycleCallbackTask identifier=%.6s", STR_TO_HEX(identifier)); + AutoLaunchItem autoLaunchItem; + { + std::lock_guard autoLock(extLock_); + if (extItemMap_.count(identifier) == 0 || extItemMap_[identifier].count(userId) == 0) { + LOGE("[AutoLaunch] ExtConnectionLifeCycleCallbackTask identifier is not exist!"); + return; + } + autoLaunchItem = extItemMap_[identifier][userId]; + extItemMap_[identifier].erase(userId); + if (extItemMap_[identifier].size() == 0) { + extItemMap_.erase(identifier); + } + } + LOGI("[AutoLaunch] ExtConnectionLifeCycleCallbackTask do CloseConnection"); + TryCloseConnection(autoLaunchItem); // do nothing if failed + if (autoLaunchItem.isWriteOpenNotified) { + CloseNotifier(autoLaunchItem); + } +} + +int AutoLaunch::SetConflictNotifier(AutoLaunchItem &autoLaunchItem) +{ + if (autoLaunchItem.type != DBType::DB_KV) { + LOGD("[AutoLaunch] Current Type[%d] Not Support ConflictNotifier Now", static_cast(autoLaunchItem.type)); + return E_OK; + } + + IKvDBConnection *kvConn = static_cast(autoLaunchItem.conn); + int conflictType = autoLaunchItem.conflictType; + const KvStoreNbConflictNotifier ¬ifier = autoLaunchItem.conflictNotifier; + if (conflictType == 0) { + return E_OK; + } + int errCode; + if (!notifier) { + errCode = kvConn->SetConflictNotifier(conflictType, nullptr); + goto END; + } + + errCode = kvConn->SetConflictNotifier(conflictType, + [conflictType, notifier](const KvDBCommitNotifyData &data) { + int resultCode; + const std::list entries = data.GetCommitConflicts(resultCode); + if (resultCode != E_OK) { + LOGE("Get commit conflicted entries failed:%d!", resultCode); + return; + } + + for (const auto &entry : entries) { + // Prohibit signed numbers to perform bit operations + uint32_t entryType = static_cast(entry.type); + uint32_t type = static_cast(conflictType); + if ((entryType & type) != 0) { + KvStoreNbConflictDataImpl dataImpl; + dataImpl.SetConflictData(entry); + notifier(dataImpl); + } + } + }); + +END: + if (errCode != E_OK) { + LOGE("[KvStoreNbDelegate] Register conflict failed:%d!", errCode); + } + return errCode; +} + +int AutoLaunch::GetAutoLaunchProperties(const AutoLaunchParam ¶m, const DBType &openType, bool checkDir, + std::shared_ptr &propertiesPtr) +{ + switch (openType) { + case DBType::DB_KV: { + propertiesPtr = std::make_shared(); + std::shared_ptr kvPtr = std::static_pointer_cast(propertiesPtr); + return GetAutoLaunchKVProperties(param, kvPtr, checkDir); + } + case DBType::DB_RELATION: { + propertiesPtr = std::make_shared(); + std::shared_ptr rdbPtr = + std::static_pointer_cast(propertiesPtr); + return GetAutoLaunchRelationProperties(param, rdbPtr); + } + default: + return -E_INVALID_ARGS; + } +} + +int AutoLaunch::GetAutoLaunchKVProperties(const AutoLaunchParam ¶m, + const std::shared_ptr &propertiesPtr, bool checkDir) +{ + SchemaObject schemaObject; + std::string canonicalDir; + int errCode = ParamCheckUtils::CheckAndTransferAutoLaunchParam(param, checkDir, schemaObject, canonicalDir); + if (errCode != E_OK) { + return errCode; + } + + if (param.option.isEncryptedDb) { + propertiesPtr->SetPassword(param.option.cipher, param.option.passwd); + } + propertiesPtr->SetStringProp(KvDBProperties::DATA_DIR, canonicalDir); + propertiesPtr->SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, param.option.createIfNecessary); + propertiesPtr->SetBoolProp(KvDBProperties::CREATE_DIR_BY_STORE_ID_ONLY, param.option.createDirByStoreIdOnly); + propertiesPtr->SetBoolProp(KvDBProperties::MEMORY_MODE, false); + propertiesPtr->SetBoolProp(KvDBProperties::ENCRYPTED_MODE, param.option.isEncryptedDb); + propertiesPtr->SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + propertiesPtr->SetSchema(schemaObject); + if (RuntimeContext::GetInstance()->IsProcessSystemApiAdapterValid()) { + propertiesPtr->SetIntProp(KvDBProperties::SECURITY_LABEL, param.option.secOption.securityLabel); + propertiesPtr->SetIntProp(KvDBProperties::SECURITY_FLAG, param.option.secOption.securityFlag); + } + propertiesPtr->SetBoolProp(KvDBProperties::COMPRESS_ON_SYNC, param.option.isNeedCompressOnSync); + if (param.option.isNeedCompressOnSync) { + propertiesPtr->SetIntProp(KvDBProperties::COMPRESSION_RATE, + ParamCheckUtils::GetValidCompressionRate(param.option.compressionRate)); + } + propertiesPtr->SetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, param.option.syncDualTupleMode); + DBCommon::SetDatabaseIds(*propertiesPtr, param.appId, param.userId, param.storeId); + return E_OK; +} + +int AutoLaunch::GetAutoLaunchRelationProperties(const AutoLaunchParam ¶m, + const std::shared_ptr &propertiesPtr) +{ + if (!ParamCheckUtils::CheckStoreParameter(param.storeId, param.appId, param.userId)) { + LOGE("[AutoLaunch] CheckStoreParameter is invalid."); + return -E_INVALID_ARGS; + } + propertiesPtr->SetStringProp(RelationalDBProperties::DATA_DIR, param.path); + propertiesPtr->SetIdentifier(param.userId, param.appId, param.storeId); + return E_OK; +} + +int AutoLaunch::ExtAutoLaunchRequestCallBack(const std::string &identifier, AutoLaunchParam ¶m, DBType &openType) +{ + std::lock_guard lock(extLock_); + if (autoLaunchRequestCallbackMap_.empty()) { + LOGI("[AutoLaunch] autoLaunchRequestCallbackMap_ is empty"); + return -E_NOT_FOUND; // not E_OK is ok for communicator + } + + bool needOpen = false; + for (const auto &[type, callBack] : autoLaunchRequestCallbackMap_) { + needOpen = callBack(identifier, param); + if (needOpen) { + openType = type; + break; + } + } + + if (!needOpen) { + LOGI("[AutoLaunch] autoLaunchRequestCallback is not need open"); + return -E_NOT_FOUND; // not E_OK is ok for communicator + } + // inner error happened + if (openType >= DBType::DB_INVALID) { + LOGW("[AutoLaunch] Unknown DB Type, Ignore the open request"); + return -E_NOT_FOUND; // not E_OK is ok for communicator + } + return E_OK; +} + +int AutoLaunch::OpenKvConnection(AutoLaunchItem &autoLaunchItem) +{ + std::shared_ptr properties = + std::static_pointer_cast(autoLaunchItem.propertiesPtr); + int errCode = E_OK; + IKvDBConnection *conn = KvDBManager::GetDatabaseConnection(*properties, errCode, false); + if (errCode == -E_ALREADY_OPENED) { + LOGI("[AutoLaunch] GetOneConnection user already getkvstore by self"); + } else if (conn == nullptr) { + LOGE("[AutoLaunch] GetOneConnection GetDatabaseConnection failed errCode:%d", errCode); + } + autoLaunchItem.conn = conn; + return errCode; +} + +int AutoLaunch::OpenRelationalConnection(AutoLaunchItem &autoLaunchItem) +{ + std::shared_ptr properties = + std::static_pointer_cast(autoLaunchItem.propertiesPtr); + int errCode = E_OK; + auto conn = RelationalStoreInstance::GetDatabaseConnection(*properties, errCode); + if (errCode == -E_ALREADY_OPENED) { + LOGI("[AutoLaunch] GetOneConnection user already openstore by self"); + } else if (conn == nullptr) { + LOGE("[AutoLaunch] GetOneConnection GetDatabaseConnection failed errCode:%d", errCode); + } + autoLaunchItem.conn = conn; + return errCode; +} + +int AutoLaunch::RegisterLifeCycleCallback(AutoLaunchItem &autoLaunchItem, const std::string &identifier, + bool isExt) +{ + int errCode = E_OK; + DatabaseLifeCycleNotifier notifier; + if (isExt) { + notifier = std::bind( + &AutoLaunch::ExtConnectionLifeCycleCallback, this, std::placeholders::_1, std::placeholders::_2); + } else { + notifier = std::bind(&AutoLaunch::ConnectionLifeCycleCallback, + this, std::placeholders::_1, std::placeholders::_2); + } + switch (autoLaunchItem.type) { + case DBType::DB_KV: + errCode = static_cast(autoLaunchItem.conn)->RegisterLifeCycleCallback(notifier); + break; + case DBType::DB_RELATION: + errCode = + static_cast(autoLaunchItem.conn)->RegisterLifeCycleCallback(notifier); + break; + default: + LOGD("[AutoLaunch] Unknown Type[%d]", static_cast(autoLaunchItem.type)); + break; + } + return errCode; +} + +int AutoLaunch::PragmaAutoSync(AutoLaunchItem &autoLaunchItem) +{ + int errCode = E_OK; + if (autoLaunchItem.type != DBType::DB_KV) { + LOGD("[AutoLaunch] Current Type[%d] Not Support AutoSync Now", static_cast(autoLaunchItem.type)); + return errCode; + } + + bool enAutoSync = autoLaunchItem.isAutoSync; + errCode = static_cast(autoLaunchItem.conn)->Pragma(PRAGMA_AUTO_SYNC, + static_cast(&enAutoSync)); + if (errCode != E_OK) { + LOGE("[AutoLaunch] PRAGMA_AUTO_SYNC failed, errCode:%d", errCode); + return errCode; + } + LOGI("[AutoLaunch] set PRAGMA_AUTO_SYNC ok, enAutoSync=%d", enAutoSync); + return errCode; +} + +void AutoLaunch::TryCloseKvConnection(AutoLaunchItem &autoLaunchItem) +{ + LOGI("[AutoLaunch] TryCloseKvConnection"); + if (autoLaunchItem.conn == nullptr) { + LOGI("[AutoLaunch] TryCloseKvConnection conn is nullptr, do nothing"); + return; + } + IKvDBConnection *kvConn = static_cast(autoLaunchItem.conn); + int errCode = kvConn->RegisterLifeCycleCallback(nullptr); + if (errCode != E_OK) { + LOGE("[AutoLaunch] TryCloseKvConnection RegisterLifeCycleCallback failed errCode:%d", errCode); + } + if (autoLaunchItem.observerHandle != nullptr) { + errCode = kvConn->UnRegisterObserver(autoLaunchItem.observerHandle); + if (errCode != E_OK) { + LOGE("[AutoLaunch] TryCloseKvConnection UnRegisterObserver failed errCode:%d", errCode); + } + autoLaunchItem.observerHandle = nullptr; + } + errCode = KvDBManager::ReleaseDatabaseConnection(kvConn); + if (errCode != E_OK) { + LOGE("[AutoLaunch] TryCloseKvConnection ReleaseDatabaseConnection failed errCode:%d", errCode); + } +} + +void AutoLaunch::TryCloseRelationConnection(AutoLaunchItem &autoLaunchItem) +{ + LOGI("[AutoLaunch] TryCloseRelationConnection"); + if (autoLaunchItem.conn == nullptr) { + LOGI("[AutoLaunch] TryCloseRelationConnection conn is nullptr, do nothing"); + return; + } + RelationalStoreConnection *rdbConn = static_cast(autoLaunchItem.conn); + int errCode = rdbConn->RegisterLifeCycleCallback(nullptr); + if (errCode != E_OK) { + LOGE("[AutoLaunch] TryCloseRelationConnection RegisterLifeCycleCallback failed errCode:%d", errCode); + } + errCode = rdbConn->Close(); + if (errCode != E_OK) { + LOGE("[AutoLaunch] TryCloseRelationConnection close connection failed errCode:%d", errCode); + } +} + +void AutoLaunch::EraseAutoLauchItem(const std::string &identifier, const std::string &userId) +{ + std::lock_guard autoLock(dataLock_); + autoLaunchItemMap_[identifier].erase(userId); + if (autoLaunchItemMap_[identifier].empty()) { + autoLaunchItemMap_.erase(identifier); + } +} + +void AutoLaunch::NotifyInvalidParam(const AutoLaunchItem &autoLaunchItem) +{ + if (!autoLaunchItem.notifier) { + return; + } + int retCode = RuntimeContext::GetInstance()->ScheduleTask([autoLaunchItem] { + std::string userId = autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::USER_ID, ""); + std::string appId = autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::APP_ID, ""); + std::string storeId = autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::STORE_ID, ""); + autoLaunchItem.notifier(userId, appId, storeId, INVALID_PARAM); + }); + if (retCode != E_OK) { + LOGE("[AutoLaunch] AutoLaunchExt notifier ScheduleTask retCode:%d", retCode); + } +} + +int AutoLaunch::CheckAutoLaunchRealPath(const AutoLaunchItem &autoLaunchItem) +{ + std::string canonicalDir; + std::string dataDir = autoLaunchItem.propertiesPtr->GetStringProp(DBProperties::DATA_DIR, ""); + if (!ParamCheckUtils::CheckDataDir(dataDir, canonicalDir)) { + LOGE("[AutoLaunch] CheckDataDir is invalid Auto Launch failed."); + NotifyInvalidParam(autoLaunchItem); + return -E_INVALID_ARGS; + } + autoLaunchItem.propertiesPtr->SetStringProp(DBProperties::DATA_DIR, canonicalDir); + return E_OK; +} + +void AutoLaunch::Dump(int fd) +{ + std::lock_guard lock(dataLock_); + DBDumpHelper::Dump(fd, "\tenableAutoLaunch info [\n"); + for (const auto &[label, userItem] : autoLaunchItemMap_) { + DBDumpHelper::Dump(fd, "\t\tlabel = %s, userId = [\n", DBCommon::TransferStringToHex(label).c_str()); + for (const auto &entry : userItem) { + DBDumpHelper::Dump(fd, "\t\t\t%s\n", entry.first.c_str()); + } + DBDumpHelper::Dump(fd, "\t\t]\n"); + } + DBDumpHelper::Dump(fd, "\t]\n"); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/data_compression.cpp b/mock/distributeddb/common/src/data_compression.cpp new file mode 100644 index 00000000..c0b221f0 --- /dev/null +++ b/mock/distributeddb/common/src/data_compression.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "data_compression.h" +#include +#include "db_errno.h" + +namespace DistributedDB { +void DataCompression::GetCompressionAlgo(std::set &algorithmSet) +{ + algorithmSet.clear(); + for (const auto &item : GetCompressionAlgos()) { + algorithmSet.insert(item.first); + } +} + +int DataCompression::TransferCompressionAlgo(uint32_t compressAlgoType, CompressAlgorithm &algoType) +{ + auto iter = GetTransMap().find(compressAlgoType); + if (iter == GetTransMap().end()) { + return -E_INVALID_ARGS; + } + algoType = iter->second; + return E_OK; +} + +DataCompression *DataCompression::GetInstance(CompressAlgorithm algo) +{ + auto iter = GetCompressionAlgos().find(algo); + if (iter == GetCompressionAlgos().end()) { + return nullptr; + } + return iter->second; +} + +// All supported compression algorithm should call this function to register their instance. +void DataCompression::Register(CompressAlgorithm algo, DataCompression *compressionPtr) +{ + if (GetInstance(algo) != nullptr) { + return; + } + GetCompressionAlgos().insert({algo, compressionPtr}); + GetTransMap().insert({static_cast(algo), algo}); +} + +std::map &DataCompression::GetCompressionAlgos() +{ + static std::map compressionAlgos; + return compressionAlgos; +} + +std::map &DataCompression::GetTransMap() +{ + static std::map transferMap; + return transferMap; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/data_value.cpp b/mock/distributeddb/common/src/data_value.cpp new file mode 100644 index 00000000..81929ae3 --- /dev/null +++ b/mock/distributeddb/common/src/data_value.cpp @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "data_value.h" + +#include "db_errno.h" +#include "relational_schema_object.h" +#include "securec.h" + +namespace DistributedDB { +Blob::Blob() : ptr_(nullptr), size_(0) +{ +} + +Blob::~Blob() +{ + if (ptr_ != nullptr) { + delete[] ptr_; + ptr_ = nullptr; + } + size_ = 0; +} + +Blob::Blob(Blob &&blob) : ptr_(blob.ptr_), size_(blob.size_) +{ + blob.ptr_ = nullptr; + blob.size_ = 0; +} + +Blob &Blob::operator=(Blob &&blob) noexcept +{ + if (&blob != this) { + delete[] ptr_; + ptr_ = blob.ptr_; + size_ = blob.size_; + blob.ptr_ = nullptr; + blob.size_ = 0; + } + return *this; +} + +const uint8_t *Blob::GetData() const +{ + return ptr_; +} + +uint32_t Blob::GetSize() const +{ + return size_; +} + +int Blob::WriteBlob(const uint8_t *ptrArray, const uint32_t &size) +{ + if (ptrArray == nullptr || size == 0) { + return E_OK; + } + + delete[] ptr_; + ptr_ = nullptr; + + ptr_ = new (std::nothrow) uint8_t[size]; + if (ptr_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + errno_t errCode = memcpy_s(ptr_, size, ptrArray, size); + if (errCode != EOK) { + return -E_SECUREC_ERROR; + } + size_ = size; + return E_OK; +} + +DataValue::DataValue() : type_(StorageType::STORAGE_TYPE_NULL) +{ + value_.zeroMem = nullptr; +} + +DataValue::~DataValue() +{ + ResetValue(); +} + +DataValue::DataValue(const DataValue &dataValue) +{ + *this = dataValue; +} + +DataValue::DataValue(DataValue &&dataValue) noexcept +{ + *this = std::move(dataValue); +} + +DataValue &DataValue::operator=(const DataValue &dataValue) +{ + if (&dataValue == this) { + return *this; + } + ResetValue(); + switch (dataValue.type_) { + case StorageType::STORAGE_TYPE_INTEGER: + (void)dataValue.GetInt64(this->value_.iValue); + break; + case StorageType::STORAGE_TYPE_REAL: + (void)dataValue.GetDouble(this->value_.dValue); + break; + case StorageType::STORAGE_TYPE_BLOB: + case StorageType::STORAGE_TYPE_TEXT: + (void)dataValue.GetBlob(this->value_.blobPtr); + break; + default: + break; + } + type_ = dataValue.type_; + return *this; +} + +DataValue &DataValue::operator=(DataValue &&dataValue) noexcept +{ + if (&dataValue == this) { + return *this; + } + ResetValue(); + this->type_ = dataValue.type_; + this->value_ = dataValue.value_; + switch (type_) { + case StorageType::STORAGE_TYPE_BLOB: + case StorageType::STORAGE_TYPE_TEXT: + dataValue.value_.blobPtr = nullptr; + break; + default: + break; + } + return *this; +} + +DataValue &DataValue::operator=(int64_t intVal) +{ + ResetValue(); + type_ = StorageType::STORAGE_TYPE_INTEGER; + value_.iValue = intVal; + return *this; +} + +DataValue &DataValue::operator=(double doubleVal) +{ + ResetValue(); + type_ = StorageType::STORAGE_TYPE_REAL; + value_.dValue = doubleVal; + return *this; +} + +DataValue &DataValue::operator=(const Blob &blob) +{ + (void)SetBlob(blob); + return *this; +} + +int DataValue::Set(Blob *&blob) +{ + ResetValue(); + if (blob == nullptr || blob->GetSize() <= 0) { + LOGE("Transfer Blob to DataValue failed."); + return -E_INVALID_ARGS; + } + type_ = StorageType::STORAGE_TYPE_BLOB; + value_.blobPtr = blob; + blob = nullptr; + return E_OK; +} + +DataValue &DataValue::operator=(const std::string &string) +{ + (void)SetText(string); + return *this; +} + +bool DataValue::operator==(const DataValue &dataValue) const +{ + if (dataValue.type_ != type_) { + return false; + } + switch (type_) { + case StorageType::STORAGE_TYPE_INTEGER: + return dataValue.value_.iValue == value_.iValue; + case StorageType::STORAGE_TYPE_REAL: + return dataValue.value_.dValue == value_.dValue; + case StorageType::STORAGE_TYPE_BLOB: + case StorageType::STORAGE_TYPE_TEXT: + if (dataValue.value_.blobPtr->GetSize() != value_.blobPtr->GetSize()) { + return false; + } + for (uint32_t i = 0; i < dataValue.value_.blobPtr->GetSize(); ++i) { + if (dataValue.value_.blobPtr->GetData()[i] != value_.blobPtr->GetData()[i]) { + return false; + } + } + return true; + default: + return true; + } +} + +bool DataValue::operator!=(const DataValue &dataValue) const +{ + return !(*this == dataValue); +} + +int DataValue::GetDouble(double &outVal) const +{ + if (type_ != StorageType::STORAGE_TYPE_REAL) { + return -E_NOT_SUPPORT; + } + outVal = value_.dValue; + return E_OK; +} + +int DataValue::GetInt64(int64_t &outVal) const +{ + if (type_ != StorageType::STORAGE_TYPE_INTEGER) { + return -E_NOT_SUPPORT; + } + outVal = value_.iValue; + return E_OK; +} + +int DataValue::GetBlob(Blob *&outVal) const +{ + if (type_ != StorageType::STORAGE_TYPE_BLOB && type_ != StorageType::STORAGE_TYPE_TEXT) { + return -E_NOT_SUPPORT; + } + delete outVal; + outVal = nullptr; + outVal = new (std::nothrow) Blob(); + if (outVal == nullptr) { + return -E_OUT_OF_MEMORY; + } + return outVal->WriteBlob(value_.blobPtr->GetData(), value_.blobPtr->GetSize()); +} + +int DataValue::SetBlob(const Blob &val) +{ + ResetValue(); + if (val.GetSize() <= 0) { + return E_OK; + } + value_.blobPtr = new(std::nothrow) Blob(); + if (value_.blobPtr == nullptr) { + return -E_OUT_OF_MEMORY; + } + type_ = StorageType::STORAGE_TYPE_BLOB; + int errCode = E_OK; + if (val.GetSize() > 0) { + errCode = value_.blobPtr->WriteBlob(val.GetData(), val.GetSize()); + } + return errCode; +} + +int DataValue::GetBlob(Blob &outVal) const +{ + if (type_ != StorageType::STORAGE_TYPE_BLOB && type_ != StorageType::STORAGE_TYPE_TEXT) { + return -E_NOT_SUPPORT; + } + return outVal.WriteBlob(value_.blobPtr->GetData(), value_.blobPtr->GetSize()); +} + +int DataValue::SetText(const std::string &val) +{ + return SetText(reinterpret_cast(val.c_str()), val.length()); +} + +int DataValue::SetText(const uint8_t *val, uint32_t length) +{ + ResetValue(); + value_.blobPtr = new(std::nothrow) Blob(); + if (value_.blobPtr == nullptr) { + return -E_OUT_OF_MEMORY; + } + type_ = StorageType::STORAGE_TYPE_TEXT; + return value_.blobPtr->WriteBlob(val, length); +} + +int DataValue::GetText(std::string &outValue) const +{ + if (type_ != StorageType::STORAGE_TYPE_TEXT) { + return -E_NOT_SUPPORT; + } + const uint8_t *data = value_.blobPtr->GetData(); + uint32_t len = value_.blobPtr->GetSize(); + if (len == 0) { + outValue = ""; + return E_OK; + } + outValue.resize(len); + outValue.assign(data, data + len); + return E_OK; +} + +StorageType DataValue::GetType() const +{ + return type_; +} + +int DataValue::GetBlobLength(uint32_t &length) const +{ + if (type_ != StorageType::STORAGE_TYPE_BLOB && type_ != StorageType::STORAGE_TYPE_TEXT) { + return -E_NOT_SUPPORT; + } + length = value_.blobPtr->GetSize(); + return E_OK; +} + +void DataValue::ResetValue() +{ + switch (type_) { + case StorageType::STORAGE_TYPE_TEXT: + case StorageType::STORAGE_TYPE_BLOB: + delete value_.blobPtr; + value_.blobPtr = nullptr; + break; + case StorageType::STORAGE_TYPE_NULL: + case StorageType::STORAGE_TYPE_INTEGER: + case StorageType::STORAGE_TYPE_REAL: + default: + break; + } + type_ = StorageType::STORAGE_TYPE_NULL; + value_.zeroMem = nullptr; +} + +std::string DataValue::ToString() const +{ + std::string res; + switch (type_) { + case StorageType::STORAGE_TYPE_TEXT: + (void)GetText(res); + break; + case StorageType::STORAGE_TYPE_BLOB: + res = "NOT SUPPORT"; + break; + case StorageType::STORAGE_TYPE_NULL: + res = "null"; + break; + case StorageType::STORAGE_TYPE_INTEGER: + res = std::to_string(value_.iValue); + break; + case StorageType::STORAGE_TYPE_REAL: + res = std::to_string(value_.dValue); + break; + default: + res = "default"; + break; + } + return "[" + res + "]"; +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/common/src/db_common.cpp b/mock/distributeddb/common/src/db_common.cpp new file mode 100644 index 00000000..96914ab2 --- /dev/null +++ b/mock/distributeddb/common/src/db_common.cpp @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" + +#include +#include + +#include "db_errno.h" +#include "platform_specific.h" +#include "hash.h" +#include "value_hash_calc.h" + +namespace DistributedDB { +namespace { + void RemoveFiles(const std::list &fileList, OS::FileType type) + { + for (const auto &item : fileList) { + if (item.fileType != type) { + continue; + } + int errCode = OS::RemoveFile(item.fileName.c_str()); + if (errCode != E_OK) { + LOGE("Remove file failed:%d", errno); + } + } + } + + void RemoveDirectories(const std::list &fileList, OS::FileType type) + { + for (auto item = fileList.rbegin(); item != fileList.rend(); ++item) { + if (item->fileType != type) { + continue; + } + int errCode = OS::RemoveDBDirectory(item->fileName); + if (errCode != 0) { + LOGE("Remove directory failed:%d", errno); + } + } + } + const std::string HEX_CHAR_MAP = "0123456789abcdef"; + const std::string CAP_HEX_CHAR_MAP = "0123456789ABCDEF"; +} + +int DBCommon::CreateDirectory(const std::string &directory) +{ + bool isExisted = OS::CheckPathExistence(directory); + if (!isExisted) { + int errCode = OS::MakeDBDirectory(directory); + if (errCode != E_OK) { + return errCode; + } + } + return E_OK; +} + +void DBCommon::StringToVector(const std::string &src, std::vector &dst) +{ + dst.resize(src.size()); + dst.assign(src.begin(), src.end()); +} + +void DBCommon::VectorToString(const std::vector &src, std::string &dst) +{ + dst.clear(); + dst.assign(src.begin(), src.end()); +} + +std::string DBCommon::VectorToHexString(const std::vector &inVec, const std::string &separator) +{ + std::string outString; + for (auto &entry : inVec) { + outString.push_back(CAP_HEX_CHAR_MAP[entry >> 4]); // high 4 bits to one hex. + outString.push_back(CAP_HEX_CHAR_MAP[entry & 0x0F]); // low 4 bits to one hex. + outString += separator; + } + outString.erase(outString.size() - separator.size(), separator.size()); // remove needless separator at last + return outString; +} + +void DBCommon::PrintHexVector(const std::vector &data, int line, const std::string &tag) +{ + const size_t maxDataLength = 1024; + const int byteHexNum = 2; + size_t dataLength = data.size(); + + if (data.size() > maxDataLength) { + dataLength = maxDataLength; + } + + char *buff = new (std::nothrow) char[dataLength * byteHexNum + 1]; // dual and add one for the end; + if (buff == nullptr) { + return; + } + + for (std::vector::size_type i = 0; i < dataLength; ++i) { + buff[byteHexNum * i] = CAP_HEX_CHAR_MAP[data[i] >> 4]; // high 4 bits to one hex. + buff[byteHexNum * i + 1] = CAP_HEX_CHAR_MAP[data[i] & 0x0F]; // low 4 bits to one hex. + } + buff[dataLength * byteHexNum] = '\0'; + + if (line == 0) { + LOGD("[%s] size:%zu -- %s", tag.c_str(), data.size(), buff); + } else { + LOGD("[%s][%d] size:%zu -- %s", tag.c_str(), line, data.size(), buff); + } + + delete []buff; + return; +} + +std::string DBCommon::TransferHashString(const std::string &devName) +{ + if (devName.empty()) { + return ""; + } + std::vector devVect(devName.begin(), devName.end()); + std::vector hashVect; + int errCode = CalcValueHash(devVect, hashVect); + if (errCode != E_OK) { + return ""; + } + + return std::string(hashVect.begin(), hashVect.end()); +} + +std::string DBCommon::TransferStringToHex(const std::string &origStr) +{ + if (origStr.empty()) { + return ""; + } + + std::string tmp; + for (auto item : origStr) { + unsigned char currentByte = static_cast(item); + tmp.push_back(HEX_CHAR_MAP[currentByte >> 4]); // high 4 bits to one hex. + tmp.push_back(HEX_CHAR_MAP[currentByte & 0x0F]); // low 4 bits to one hex. + } + return tmp; +} + +int DBCommon::CalcValueHash(const std::vector &value, std::vector &hashValue) +{ + ValueHashCalc hashCalc; + int errCode = hashCalc.Initialize(); + if (errCode != E_OK) { + return -E_INTERNAL_ERROR; + } + + errCode = hashCalc.Update(value); + if (errCode != E_OK) { + return -E_INTERNAL_ERROR; + } + + errCode = hashCalc.GetResult(hashValue); + if (errCode != E_OK) { + return -E_INTERNAL_ERROR; + } + + return E_OK; +} + +int DBCommon::CreateStoreDirectory(const std::string &directory, const std::string &identifierName, + const std::string &subDir, bool isCreate) +{ + std::string newDir = directory; + if (newDir.back() != '/') { + newDir += "/"; + } + + newDir += identifierName; + if (!isCreate) { + if (!OS::CheckPathExistence(newDir)) { + LOGE("Required path does not exist and won't create."); + return -E_INVALID_ARGS; + } + return E_OK; + } + + if (directory.empty()) { + return -E_INVALID_ARGS; + } + + int errCode = DBCommon::CreateDirectory(newDir); + if (errCode != E_OK) { + return errCode; + } + + newDir += ("/" + subDir); + return DBCommon::CreateDirectory(newDir); +} + +int DBCommon::CopyFile(const std::string &srcFile, const std::string &dstFile) +{ + const int copyBlockSize = 4096; + std::vector tmpBlock(copyBlockSize, 0); + int errCode; + FILE *fileIn = fopen(srcFile.c_str(), "rb"); + if (fileIn == nullptr) { + LOGE("[Common:CpFile] open the source file error:%d", errno); + return -E_INVALID_FILE; + } + FILE *fileOut = fopen(dstFile.c_str(), "wb"); + if (fileOut == nullptr) { + LOGE("[Common:CpFile] open the target file error:%d", errno); + errCode = -E_INVALID_FILE; + goto END; + } + for (;;) { + size_t readSize = fread(static_cast(tmpBlock.data()), 1, copyBlockSize, fileIn); + if (readSize < copyBlockSize) { + // not end and have error. + if (feof(fileIn) != 0 && ferror(fileIn) != 0) { + LOGE("Copy the file error:%d", errno); + errCode = -E_SYSTEM_API_FAIL; + break; + } + } + + if (readSize != 0) { + size_t writeSize = fwrite(static_cast(tmpBlock.data()), 1, readSize, fileOut); + if (ferror(fileOut) != 0 || writeSize != readSize) { + LOGE("Write the data while copy:%d", errno); + errCode = -E_SYSTEM_API_FAIL; + break; + } + } + + if (feof(fileIn) != 0) { + errCode = E_OK; + break; + } + } + +END: + if (fileIn != nullptr) { + (void)fclose(fileIn); + fileIn = nullptr; + } + if (fileOut != nullptr) { + (void)fclose(fileOut); + fileOut = nullptr; + } + return errCode; +} + +int DBCommon::RemoveAllFilesOfDirectory(const std::string &dir, bool isNeedRemoveDir) +{ + std::list fileList; + bool isExisted = OS::CheckPathExistence(dir); + if (!isExisted) { + return E_OK; + } + int errCode = OS::GetFileAttrFromPath(dir, fileList, true); + if (errCode != E_OK) { + return errCode; + } + + RemoveFiles(fileList, OS::FileType::FILE); + RemoveDirectories(fileList, OS::FileType::PATH); + if (isNeedRemoveDir) { + // Pay attention to the order of deleting the directory + if (OS::CheckPathExistence(dir) && OS::RemoveDBDirectory(dir.c_str()) != 0) { + LOGI("Remove the directory error:%d", errno); + errCode = -E_SYSTEM_API_FAIL; + } + } + + return errCode; +} + +std::string DBCommon::GenerateIdentifierId(const std::string &storeId, + const std::string &appId, const std::string &userId) +{ + return userId + "-" + appId + "-" + storeId; +} + +std::string DBCommon::GenerateDualTupleIdentifierId(const std::string &storeId, const std::string &appId) +{ + return appId + "-" + storeId; +} + +void DBCommon::SetDatabaseIds(KvDBProperties &properties, const std::string &appId, const std::string &userId, + const std::string &storeId) +{ + properties.SetIdentifier(userId, appId, storeId); + std::string oriStoreDir; + std::string identifier = GenerateIdentifierId(storeId, appId, userId); + if (properties.GetBoolProp(KvDBProperties::CREATE_DIR_BY_STORE_ID_ONLY, false)) { + oriStoreDir = storeId; + } else { + oriStoreDir = identifier; + } + std::string hashIdentifier = TransferHashString(identifier); + std::string hashDir = TransferHashString(oriStoreDir); + std::string hexHashDir = TransferStringToHex(hashDir); + properties.SetStringProp(KvDBProperties::IDENTIFIER_DIR, hexHashDir); +} + +std::string DBCommon::StringMasking(const std::string &oriStr, size_t remain) +{ + if (oriStr.size() > remain) { + return oriStr.substr(0, remain); + } + return oriStr; +} + +std::string DBCommon::GetDistributedTableName(const std::string &device, const std::string &tableName) +{ + std::string deviceHashHex = DBCommon::TransferStringToHex(DBCommon::TransferHashString(device)); + return DBConstant::RELATIONAL_PREFIX + tableName + "_" + deviceHashHex; +} + +void DBCommon::GetDeviceFromName(const std::string &deviceTableName, std::string &deviceHash, std::string &tableName) +{ + std::size_t found = deviceTableName.rfind('_'); + if (found != std::string::npos && found + 1 < deviceTableName.length() && + found > DBConstant::RELATIONAL_PREFIX.length()) { + deviceHash = deviceTableName.substr(found + 1); + tableName = deviceTableName.substr(DBConstant::RELATIONAL_PREFIX.length(), + found - DBConstant::RELATIONAL_PREFIX.length()); + } +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/db_constant.cpp b/mock/distributeddb/common/src/db_constant.cpp new file mode 100644 index 00000000..35f3cf65 --- /dev/null +++ b/mock/distributeddb/common/src/db_constant.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_constant.h" + +namespace DistributedDB { +const std::string DBConstant::MULTI_SUB_DIR = "multi_ver"; +const std::string DBConstant::SINGLE_SUB_DIR = "single_ver"; +const std::string DBConstant::LOCAL_SUB_DIR = "local"; + +const std::string DBConstant::MAINDB_DIR = "main"; +const std::string DBConstant::METADB_DIR = "meta"; +const std::string DBConstant::CACHEDB_DIR = "cache"; + +const std::string DBConstant::LOCAL_DATABASE_NAME = "local"; +const std::string DBConstant::MULTI_VER_DATA_STORE = "multi_ver_data"; +const std::string DBConstant::MULTI_VER_COMMIT_STORE = "commit_logs"; +const std::string DBConstant::MULTI_VER_VALUE_STORE = "value_storage"; +const std::string DBConstant::MULTI_VER_META_STORE = "meta_storage"; +const std::string DBConstant::SINGLE_VER_DATA_STORE = "gen_natural_store"; +const std::string DBConstant::SINGLE_VER_META_STORE = "meta"; +const std::string DBConstant::SINGLE_VER_CACHE_STORE = "cache"; + +const std::string DBConstant::SQLITE_URL_PRE = "file:"; +const std::string DBConstant::SQLITE_DB_EXTENSION = ".db"; +const std::string DBConstant::SQLITE_MEMDB_IDENTIFY = "?mode=memory&cache=shared"; + +const std::string DBConstant::SCHEMA_KEY = "schemaKey"; + +const std::string DBConstant::PATH_POSTFIX_UNPACKED = "_unpacked"; +const std::string DBConstant::PATH_POSTFIX_IMPORT_BACKUP = "_import_bak"; +const std::string DBConstant::PATH_POSTFIX_IMPORT_ORIGIN = "_import_ori"; +const std::string DBConstant::PATH_POSTFIX_IMPORT_DUP = "_import_dup"; +const std::string DBConstant::PATH_POSTFIX_EXPORT_BACKUP = "_export_bak"; +const std::string DBConstant::PATH_POSTFIX_DB_INCOMPLETE = "_db_incomplete.lock"; + +const std::string DBConstant::REKEY_FILENAME_POSTFIX_PRE = "_ctrl_pre"; +const std::string DBConstant::REKEY_FILENAME_POSTFIX_OK = "_ctrl_ok"; +const std::string DBConstant::UPGRADE_POSTFIX = "_upgrade.lock"; +const std::string DBConstant::SET_SECOPT_POSTFIX = "_secopt.lock"; +const std::string DBConstant::PATH_BACKUP_POSTFIX = "_bak"; + +const std::string DBConstant::ID_CONNECTOR = "-"; + +const std::string DBConstant::DELETE_KVSTORE_REMOVING = "_removing"; +const std::string DBConstant::DB_LOCK_POSTFIX = ".lock"; + +const std::string DBConstant::SUBSCRIBE_QUERY_PREFIX = "subscribe_query_"; + +const std::string DBConstant::TRIGGER_REFERENCES_NEW = "NEW."; +const std::string DBConstant::TRIGGER_REFERENCES_OLD = "OLD."; + +const std::string DBConstant::UPDATE_META_FUNC = "update_meta_within_trigger"; + +const std::string DBConstant::SYSTEM_TABLE_PREFIX = "naturalbase_rdb_"; +const std::string DBConstant::RELATIONAL_PREFIX = "naturalbase_rdb_aux_"; +const std::string DBConstant::TIMESTAMP_ALIAS = "naturalbase_rdb_aux_timestamp"; +} \ No newline at end of file diff --git a/mock/distributeddb/common/src/db_dfx_adapter.cpp b/mock/distributeddb/common/src/db_dfx_adapter.cpp new file mode 100644 index 00000000..6ffcc0dd --- /dev/null +++ b/mock/distributeddb/common/src/db_dfx_adapter.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "db_dfx_adapter.h" + +#include +#include +#include +#include + +#include "log_print.h" +#include "db_dump_helper.h" +#include "db_errno.h" +#include "kvdb_manager.h" +#include "relational_store_instance.h" +#include "runtime_context.h" +#include "sqlite_utils.h" +#ifdef USE_DFX_ABILITY +#include "hitrace_meter.h" +#include "hisysevent.h" +#endif + +namespace DistributedDB { +namespace { +#ifdef USE_DFX_ABILITY +constexpr uint64_t HITRACE_LABEL = HITRACE_TAG_DISTRIBUTEDDATA; +#endif +constexpr const char *DUMP_PARAM = "dump-distributeddb"; +} + +const std::string DBDfxAdapter::EVENT_CODE = "ERROR_CODE"; +const std::string DBDfxAdapter::APP_ID = "APP_ID"; +const std::string DBDfxAdapter::USER_ID = "USER_ID"; +const std::string DBDfxAdapter::STORE_ID = "STORE_ID"; +const std::string DBDfxAdapter::SQLITE_EXECUTE = "SQLITE_EXECUTE"; +const std::string DBDfxAdapter::SYNC_ACTION = "SYNC_ACTION"; +const std::string DBDfxAdapter::EVENT_OPEN_DATABASE_FAILED = "OPEN_DATABASE_FAILED"; + +void DBDfxAdapter::Dump(int fd, const std::vector &args) +{ + if (!args.empty()) { + const std::u16string u16DumpParam = + std::wstring_convert, char16_t> {}.from_bytes(DUMP_PARAM); + auto find = std::any_of(args.begin(), args.end(), [&u16DumpParam](const std::u16string &arg) { + return arg == u16DumpParam; + }); + if (!find) { + return; + } + } + DBDumpHelper::Dump(fd, "DistributedDB Dump Message Info:\n\n"); + DBDumpHelper::Dump(fd, "DistributedDB Database Basic Message Info:\n"); + KvDBManager::GetInstance()->Dump(fd); + RelationalStoreInstance::GetInstance()->Dump(fd); + DBDumpHelper::Dump(fd, "DistributedDB Common Message Info:\n"); + RuntimeContext::GetInstance()->DumpCommonInfo(fd); + DBDumpHelper::Dump(fd, "\tlast error msg = %s\n", SQLiteUtils::GetLastErrorMsg().c_str()); +} + +#ifdef USE_DFX_ABILITY +void DBDfxAdapter::ReportFault(const ReportTask &reportTask) +{ + RuntimeContext::GetInstance()->ScheduleTask([=]() { + // call hievent here + OHOS::HiviewDFX::HiSysEvent::Write(OHOS::HiviewDFX::HiSysEvent::Domain::DISTRIBUTED_DATAMGR, + reportTask.eventName, + OHOS::HiviewDFX::HiSysEvent::EventType::FAULT, + APP_ID, reportTask.appId, + STORE_ID, reportTask.storeId, + EVENT_CODE, std::to_string(reportTask.errCode)); + }); +} + +void DBDfxAdapter::StartTrace(const std::string &action) +{ + ::StartTrace(HITRACE_LABEL, action); +} + +void DBDfxAdapter::FinishTrace() +{ + ::FinishTrace(HITRACE_LABEL); +} + +void DBDfxAdapter::StartTraceSQL() +{ +#ifdef TRACE_SQLITE_EXECUTE + ::StartTrace(HITRACE_LABEL, SQLITE_EXECUTE); +#endif +} + +void DBDfxAdapter::FinishTraceSQL() +{ +#ifdef TRACE_SQLITE_EXECUTE + ::FinishTrace(HITRACE_LABEL); +#endif +} + +void DBDfxAdapter::StartAsyncTrace(const std::string &action, int32_t taskId) +{ + // call hitrace here + // need include bytrace.h + ::StartAsyncTrace(HITRACE_LABEL, action, taskId); +} + +void DBDfxAdapter::FinishAsyncTrace(const std::string &action, int32_t taskId) +{ + // call hitrace here + ::FinishAsyncTrace(HITRACE_LABEL, action, taskId); +} + +#else +void DBDfxAdapter::ReportFault(const ReportTask &reportTask) +{ + (void) reportTask; +} + +void DBDfxAdapter::StartTrace(const std::string &action) +{ + (void) action; +} + +void DBDfxAdapter::FinishTrace() +{ +} + +void DBDfxAdapter::StartAsyncTrace(const std::string &action, int32_t taskId) +{ + (void) action; + (void) taskId; +} + +void DBDfxAdapter::FinishAsyncTrace(const std::string &action, int32_t taskId) +{ + (void) action; + (void) taskId; +} + +void DBDfxAdapter::StartTraceSQL() +{ +} + +void DBDfxAdapter::FinishTraceSQL() +{ +} +#endif +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/common/src/db_dump_helper.cpp b/mock/distributeddb/common/src/db_dump_helper.cpp new file mode 100644 index 00000000..26217542 --- /dev/null +++ b/mock/distributeddb/common/src/db_dump_helper.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "db_dump_helper.h" + +#include +#include + +namespace DistributedDB { +void DBDumpHelper::Dump(int fd, const char *format, ...) +{ + va_list argList; + va_start(argList, format); +#if defined _WIN32 + (void) fd; +#else + vdprintf(fd, format, argList); +#endif + va_end(argList); +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/common/src/evloop/include/event_fd.h b/mock/distributeddb/common/src/evloop/include/event_fd.h new file mode 100644 index 00000000..7700f867 --- /dev/null +++ b/mock/distributeddb/common/src/evloop/include/event_fd.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 EVENT_FD_H +#define EVENT_FD_H + +#include "platform_specific.h" + +#if defined EVLOOP_TIMER_ONLY +using Handle = int; +static const int INVALID_HANDLE = -1; +#define IS_VALID_HANDLE(h) false +#define CLOSE_HANDLE(h) +#else +#include +using Handle = int; +static const int INVALID_HANDLE = -1; +#define IS_VALID_HANDLE(h) ((h) > 0) +#define CLOSE_HANDLE(h) do { close(h); } while (0) +#endif + +namespace DistributedDB { +class EventFd final { +public: + EventFd() : fd_(INVALID_HANDLE) {} + explicit EventFd(Handle handle) : fd_(handle) {} + + ~EventFd() + { + // we can't close it. + fd_ = INVALID_HANDLE; + } + + bool IsValid() const + { + return IS_VALID_HANDLE(fd_); + } + + operator Handle() const + { + return fd_; + } + + bool operator==(const EventFd &other) const + { + return other.fd_ == fd_; + } + + void Close() + { + if (IsValid()) { + CLOSE_HANDLE(fd_); + fd_ = INVALID_HANDLE; + } + } + +private: + Handle fd_; +}; +} + +#endif // EVENT_FD_H diff --git a/mock/distributeddb/common/src/evloop/include/ievent.h b/mock/distributeddb/common/src/evloop/include/ievent.h new file mode 100644 index 00000000..ce66e008 --- /dev/null +++ b/mock/distributeddb/common/src/evloop/include/ievent.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 IEVENT_H +#define IEVENT_H + +#include "ref_object.h" +#include "macro_utils.h" +#include "event_fd.h" + +namespace DistributedDB { +using EventTime = int64_t; +using EventsMask = unsigned int; +using EventAction = std::function; +using EventFinalizer = std::function; + +class IEvent : public virtual RefObject { +public: + enum EventType { + ET_READ = 0x01, + ET_WRITE = 0x02, + ET_ERROR = 0x04, + ET_TIMEOUT = 0x08, + }; + + IEvent() = default; + DISABLE_COPY_ASSIGN_MOVE(IEvent); + + virtual int SetAction(const EventAction &action, const EventFinalizer &finalizer = nullptr) = 0; + virtual int AddEvents(EventsMask events) = 0; + virtual int RemoveEvents(EventsMask events) = 0; + virtual int SetTimeout(EventTime timeout) = 0; + virtual int Detach(bool wait) = 0; + virtual void IgnoreFinalizer() = 0; + + // The following 2 static methods is used to create real event objects, + // instead of an event object factory. + static IEvent *CreateEvent(EventTime timeout, int &errCode); + static IEvent *CreateEvent(EventFd fd, EventsMask events, EventTime timeout, int &errCode); + +protected: + virtual ~IEvent() {}; +}; +} + +#endif // IEVENT_H diff --git a/mock/distributeddb/common/src/evloop/include/ievent_loop.h b/mock/distributeddb/common/src/evloop/include/ievent_loop.h new file mode 100644 index 00000000..b7451b8c --- /dev/null +++ b/mock/distributeddb/common/src/evloop/include/ievent_loop.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 IEVENT_LOOP_H +#define IEVENT_LOOP_H + +#include "ref_object.h" +#include "macro_utils.h" + +namespace DistributedDB { +class IEvent; + +// Abstract of event loop. +class IEventLoop : public virtual RefObject { +public: + IEventLoop() = default; + + DISABLE_COPY_ASSIGN_MOVE(IEventLoop); + + // Add an event object to the loop. + virtual int Add(IEvent *event) = 0; + + // Remove an event object from the loop. + virtual int Remove(IEvent *event) = 0; + + // Run the loop. + virtual int Run() = 0; + + // Create a loop object. + static IEventLoop *CreateEventLoop(int &errCode); + +protected: + virtual ~IEventLoop() {}; +}; +} + +#endif // IEVENT_LOOP_H diff --git a/mock/distributeddb/common/src/evloop/src/event_impl.cpp b/mock/distributeddb/common/src/evloop/src/event_impl.cpp new file mode 100644 index 00000000..4a70a3ae --- /dev/null +++ b/mock/distributeddb/common/src/evloop/src/event_impl.cpp @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "event_impl.h" +#include "db_errno.h" +#include "log_print.h" +#include "event_loop_impl.h" + +namespace DistributedDB { +EventImpl::EventImpl(EventTime timeout) + : events_(ET_TIMEOUT), + revents_(0), + timeout_(timeout), + start_(0), + loop_(nullptr), + ignoreFinalizer_(false) +{ + if (timeout_ < 0) { + timeout_ = MAX_TIME_VALUE; + } + + OnKill([this]() { + UnlockObj(); + (void)Detach(false); + LockObj(); + }); + + OnLastRef([this]() { + if (finalizer_ && !ignoreFinalizer_) { + finalizer_(); + } + }); +} + +EventImpl::EventImpl(EventFd fd, EventsMask events, EventTime timeout) + : fd_(fd), + events_(events), + revents_(0), + timeout_(timeout), + start_(0), + loop_(nullptr), + ignoreFinalizer_(false) +{ + if (!(events & ET_TIMEOUT) || (timeout_ < 0)) { + timeout_ = MAX_TIME_VALUE; + } + if (!fd_.IsValid()) { + events_ &= ~(ET_READ | ET_WRITE | ET_ERROR); + } + + OnKill([this]() { + UnlockObj(); + (void)Detach(false); + LockObj(); + }); + + OnLastRef([this]() { + if (finalizer_ && !ignoreFinalizer_) { + finalizer_(); + } + }); +} + +EventImpl::~EventImpl() +{ + if (loop_ != nullptr) { + loop_->DecObjRef(loop_); + loop_ = nullptr; + } + if (fd_.IsValid()) { + fd_.Close(); + } +} + +int EventImpl::SetAction(const EventAction &action, const EventFinalizer &finalizer) +{ + if (!action || action_) { + return -E_INVALID_ARGS; + } + if (IsKilled()) { + return -E_OBJ_IS_KILLED; + } + + action_ = action; + finalizer_ = finalizer; + return E_OK; +} + +int EventImpl::AddEvents(EventsMask events) +{ + if (!IsValidArg(events)) { + return -E_INVALID_ARGS; + } + + EventsMask genericEvents = ET_READ | ET_WRITE | ET_ERROR; + if ((genericEvents & events) && !IsValidFd()) { + LOGE("ev add events failed, fd is invalid."); + return -E_INVALID_ARGS; + } + + EventLoopImpl *loop = nullptr; + { + RefObject::AutoLock lockGuard(this); + if (loop_ == nullptr) { + events_ |= events; + return E_OK; + } + loop = loop_; + loop->IncObjRef(loop); + } + + int errCode = loop->Modify(this, true, events); + loop->DecObjRef(loop); + if (errCode != E_OK) { + LOGE("ev add events failed, err: '%d'.", errCode); + } + return errCode; +} + +int EventImpl::RemoveEvents(EventsMask events) +{ + if (!IsValidArg(events)) { + return -E_INVALID_ARGS; + } + + EventsMask genericEvents = ET_READ | ET_WRITE | ET_ERROR; + if ((genericEvents & events) && !IsValidFd()) { + LOGE("ev remove events failed, fd is invalid."); + return -E_INVALID_ARGS; + } + + EventLoopImpl *loop = nullptr; + { + RefObject::AutoLock lockGuard(this); + if (loop_ == nullptr) { + events_ &= ~events; + return E_OK; + } + loop = loop_; + loop->IncObjRef(loop); + } + + int errCode = loop->Modify(this, false, events); + loop->DecObjRef(loop); + if (errCode != E_OK) { + LOGE("ev remove events failed, err: '%d'.", errCode); + } + return errCode; +} + +int EventImpl::SetTimeout(EventTime timeout) +{ + if (!IsValidArg(timeout)) { + return -E_INVALID_ARGS; + } + + EventLoopImpl *loop = nullptr; + { + RefObject::AutoLock lockGuard(this); + if (loop_ == nullptr) { + timeout_ = timeout; + return E_OK; + } + loop = loop_; + loop->IncObjRef(loop); + } + + int errCode = loop->Modify(this, timeout); + loop->DecObjRef(loop); + if (errCode != E_OK) { + LOGE("ev set timeout failed, err: '%d'.", errCode); + } + return errCode; +} + +int EventImpl::Detach(bool wait) +{ + EventLoopImpl *loop = nullptr; + { + RefObject::AutoLock lockGuard(this); + if (loop_ == nullptr) { + return E_OK; + } + loop = loop_; + loop->IncObjRef(loop); + } + + int errCode = loop->Remove(this); + if (errCode == -E_OBJ_IS_KILLED) { + errCode = E_OK; + } + + if ((errCode == E_OK) && wait) { + bool started = true; + if (!loop->IsInLoopThread(started)) { + Wait(); + } + loop->DecObjRef(loop); + return E_OK; + } + + loop->DecObjRef(loop); + return errCode; +} + +void EventImpl::IgnoreFinalizer() +{ + ignoreFinalizer_ = true; +} + +int EventImpl::CheckStatus() const +{ + RefObject::AutoLock lockGuard(this); + if (IsKilled()) { + return -E_OBJ_IS_KILLED; + } + if (!action_) { + return -E_INVALID_ARGS; + } + return E_OK; +} + +bool EventImpl::IsTimer() const +{ + return !IsValidFd(); +} + +bool EventImpl::IsValidFd() const +{ + return fd_.IsValid(); +} + +EventFd EventImpl::GetEventFd() const +{ + return fd_; +} + +EventsMask EventImpl::GetEvents() const +{ + return events_; +} + +bool EventImpl::SetLoop(EventLoopImpl *loop) +{ + RefObject::AutoLock lockGuard(this); + if (loop == nullptr) { + if (loop_ != nullptr) { + loop_->DecObjRef(loop_); + loop_ = nullptr; + } + detached_.notify_one(); + return true; + } + if (loop_ == nullptr) { + loop->IncObjRef(loop); + loop_ = loop; + return true; + } + return false; +} + +void EventImpl::Wait() +{ + RefObject::AutoLock lockGuard(this); + WaitLockedUntil(detached_, [this]()->bool { return loop_ == nullptr; }); +} + +bool EventImpl::Attached(const EventLoopImpl *loop, bool &isLoopConfused) const +{ + RefObject::AutoLock lockGuard(this); + if (loop_ != nullptr && loop != nullptr && loop_ != loop) { + // the event object is attached to another loop. + isLoopConfused = true; + } else { + isLoopConfused = false; + } + // returns true when both are nullptr. + return loop_ == loop; +} + +void EventImpl::SetEvents(bool isAdd, EventsMask events) +{ + if (isAdd) { + events_ |= events; + } else { + events_ &= ~events; + } +} + +void EventImpl::SetRevents(EventsMask events) +{ + EventsMask genericEvents = ET_READ | ET_WRITE | ET_ERROR; + EventsMask revents = events & genericEvents; + if (revents) { + revents_ = revents; + } else { + revents_ = events & ET_TIMEOUT; + } +} + +void EventImpl::SetTimeoutPeriod(EventTime timeout) +{ + if (timeout < 0) { + timeout_ = MAX_TIME_VALUE; + } else { + timeout_ = timeout; + } +} + +void EventImpl::SetStartTime(EventTime startTime) +{ + start_ = startTime; +} + +bool EventImpl::GetTimeoutPoint(EventTime &timePoint) const +{ + if (events_ & ET_TIMEOUT) { + timePoint = start_ + timeout_; + return true; + } + timePoint = MAX_TIME_VALUE; + return false; +} + +void EventImpl::UpdateElapsedTime(EventTime now) +{ + if (events_ & ET_TIMEOUT) { + EventTime timePoint = start_ + timeout_; + if ((now >= timePoint) || (now < start_)) { + start_ = now; + if (!revents_) { + revents_ = ET_TIMEOUT; + } + } + } +} + +int EventImpl::Dispatch() +{ + if (!action_) { + return -E_INVALID_ARGS; + } + + int errCode = E_OK; + if (!IsKilled()) { + if (revents_) { + errCode = action_(revents_); + if (errCode != E_OK) { + LOGI("ev action() returns '%d'.", errCode); + } + } + } + return errCode; +} + +bool EventImpl::IsValidArg(EventsMask events) const +{ + EventsMask allEvents = ET_READ | ET_WRITE | ET_ERROR | ET_TIMEOUT; + return ((events != 0) && ((events & (~allEvents)) == 0)); +} + +bool EventImpl::IsValidArg(EventTime timeout) const +{ + return (timeout >= 0) && (timeout <= MAX_TIME_VALUE); +} + +DEFINE_OBJECT_TAG_FACILITIES(EventImpl) +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/evloop/src/event_impl.h b/mock/distributeddb/common/src/evloop/src/event_impl.h new file mode 100644 index 00000000..48964b55 --- /dev/null +++ b/mock/distributeddb/common/src/evloop/src/event_impl.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 EVENT_IMPL_H +#define EVENT_IMPL_H + +#include +#include +#include "../include/ievent.h" + +namespace DistributedDB { +class EventLoopImpl; + +class EventImpl : public IEvent { +public: + explicit EventImpl(EventTime timeout); + EventImpl(EventFd fd, EventsMask events, EventTime timeout); + ~EventImpl() override; + + int SetAction(const EventAction &action, const EventFinalizer &finalizer) override; + int AddEvents(EventsMask events) override; + int RemoveEvents(EventsMask events) override; + int SetTimeout(EventTime timeout) override; + int Detach(bool wait) override; + void IgnoreFinalizer() override; + + int CheckStatus() const; + bool IsTimer() const; + bool IsValidFd() const; + EventFd GetEventFd() const; + EventsMask GetEvents() const; + bool SetLoop(EventLoopImpl *loop); + void Wait(); + bool Attached(const EventLoopImpl *loop, bool &isLoopConfused) const; + void SetEvents(bool isAdd, EventsMask events); + void SetRevents(EventsMask events); + void SetTimeoutPeriod(EventTime timeout); + void SetStartTime(EventTime startTime); + bool GetTimeoutPoint(EventTime &timePoint) const; + void UpdateElapsedTime(EventTime now); + int Dispatch(); + bool IsValidArg(EventsMask events) const; + bool IsValidArg(EventTime timeout) const; + DISABLE_COPY_ASSIGN_MOVE(EventImpl); + + static constexpr int MAX_TIME_VALUE = INT_MAX / 2; // half of the max + +private: + DECLARE_OBJECT_TAG(EventImpl); + + EventFd fd_; + EventsMask events_; + EventsMask revents_; + EventTime timeout_; // should not < 0 + EventTime start_; + EventLoopImpl *loop_; + EventAction action_; + EventFinalizer finalizer_; + bool ignoreFinalizer_; + std::condition_variable detached_; +}; +} + +#endif // EVENT_IMPL_H diff --git a/mock/distributeddb/common/src/evloop/src/event_loop_epoll.cpp b/mock/distributeddb/common/src/evloop/src/event_loop_epoll.cpp new file mode 100644 index 00000000..6519f3d5 --- /dev/null +++ b/mock/distributeddb/common/src/evloop/src/event_loop_epoll.cpp @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "event_loop_epoll.h" + +#ifdef EVENT_LOOP_USE_EPOLL +#include +#include "event_impl.h" +#include "log_print.h" +#include "db_errno.h" + +namespace DistributedDB { +EventLoopEpoll::EventLoopEpoll() + : pollFdCount_(0) +{ +} + +EventLoopEpoll::~EventLoopEpoll() +{ + if (wakeUpFd_.IsValid()) { + wakeUpFd_.Close(); + } + if (epollFd_.IsValid()) { + epollFd_.Close(); + } +} + +int EventLoopEpoll::Initialize() +{ + if (epollFd_.IsValid()) { + return -E_INVALID_ARGS; + } + + int errCode; + wakeUpFd_ = EventFd(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); + if (!wakeUpFd_.IsValid()) { + errCode = -errno; + LOGE("Create event fd failed, err:'%d'", errCode); + return errCode; + } + + epollFd_ = EventFd(epoll_create(EPOLL_INIT_REVENTS)); + if (!epollFd_.IsValid()) { + errCode = -errno; + wakeUpFd_.Close(); + LOGE("Create epoll fd failed, err:'%d'", errCode); + return errCode; + } + + struct epoll_event event; + event.events = EPOLLIN; + event.data.ptr = this; + errCode = epoll_ctl(epollFd_, EPOLL_CTL_ADD, wakeUpFd_, &event); + if (errCode < 0) { + errCode = -errno; + epollFd_.Close(); + wakeUpFd_.Close(); + LOGE("Add wake up fd to epoll failed, err:'%d'", errCode); + return errCode; + } + + ++pollFdCount_; + return E_OK; +} + +int EventLoopEpoll::Prepare(const std::set &polling) +{ + if (pollFdCount_ > 0) { + revents_.resize(pollFdCount_); + return E_OK; + } + LOGE("Prepared epoll loop failed, fd count:'%d'", pollFdCount_); + return -E_INTERNAL_ERROR; +} + +int EventLoopEpoll::Poll(EventTime sleepTime) +{ + if (sleepTime > INT_MAX) { + LOGE("[EventLoopEpoll][Poll] sleepTime is too large!"); + return -E_INVALID_ARGS; + } + int nReady = epoll_wait(epollFd_, &revents_[0], revents_.size(), sleepTime); + if (nReady < 0) { + int errCode = -errno; + if (errCode != -EINTR) { + LOGE("Call epoll wait failed, err:'%d'", errCode); + return errCode; + } + nReady = 0; + } + + for (int index = 0; index < nReady; ++index) { + struct epoll_event *revent = &revents_[index]; + if (revent->data.ptr == this) { + EpollWokenUp(); + continue; + } + auto event = static_cast(revent->data.ptr); + EventsMask revents = CalEventsMask(revent->events); + event->SetRevents(revents); + } + return E_OK; +} + +int EventLoopEpoll::WakeUp() +{ + int64_t incValue = 1; + + while (true) { + int nWrite = write(wakeUpFd_, &incValue, sizeof(incValue)); + if (nWrite == sizeof(incValue)) { + break; + } + + int errCode = -errno; + if (errCode == -EINTR) { + continue; + } + if (errCode == -EAGAIN) { + // We have already signalled the loop. + break; + } + LOGE("Write loop wake up data failed, err:'%d'", errCode); + return errCode; + } + return E_OK; +} + +int EventLoopEpoll::Exit(const std::set &polling) +{ + if (revents_.capacity() > 0) { + std::vector revents; + revents.swap(revents_); + } + wakeUpFd_.Close(); + epollFd_.Close(); + pollFdCount_ = 0; + return E_OK; +} + +void EventLoopEpoll::EpollWokenUp() +{ + while (true) { + int64_t intValue; + int nRead = read(wakeUpFd_, &intValue, sizeof(intValue)); + if (nRead < 0) { + int errCode = -errno; + if (errCode == -EINTR) { + continue; + } + if (errCode != -EAGAIN) { + LOGE("Clear loop wake up data failed, err:'%d'", errCode); + } + } + break; + } +} + +uint32_t EventLoopEpoll::CalEpollEvents(EventsMask events) const +{ + uint32_t epollEvents = 0; + if (events & IEvent::ET_READ) { + epollEvents |= EPOLLIN; + } + if (events & IEvent::ET_WRITE) { + epollEvents |= EPOLLOUT; + } + if (events & IEvent::ET_ERROR) { + epollEvents |= EPOLLERR; + } + return epollEvents; +} + +EventsMask EventLoopEpoll::CalEventsMask(uint32_t epollEvents) +{ + EventsMask events = 0; + if (epollEvents & EPOLLIN) { + events |= IEvent::ET_READ; + } + if (epollEvents & EPOLLOUT) { + events |= IEvent::ET_WRITE; + } + if (epollEvents & EPOLLERR) { + events |= IEvent::ET_ERROR; + } + return events; +} + +int EventLoopEpoll::EpollCtl(int operation, EventImpl *event, EventsMask events) +{ + if (operation != EPOLL_CTL_ADD && + operation != EPOLL_CTL_MOD && + operation != EPOLL_CTL_DEL) { + return -E_INVALID_ARGS; + } + if (event == nullptr) { + return -E_INVALID_ARGS; + } + + EventFd fd = event->GetEventFd(); + if (fd.IsValid()) { + return -E_INVALID_ARGS; + } + + uint32_t epollEvents = CalEpollEvents(events); + struct epoll_event epollEvent; + epollEvent.events = epollEvents; + epollEvent.data.ptr = event; + + int errCode = epoll_ctl(epollFd_, operation, fd, &epollEvent); + if (errCode < 0) { + errCode = -errno; + return errCode; + } + return E_OK; +} + +int EventLoopEpoll::AddEvent(EventImpl *event) +{ + if (event == nullptr) { + return -E_INVALID_ARGS; + } + + EventsMask events = event->GetEvents(); + int errCode = EpollCtl(EPOLL_CTL_ADD, event, events); + if (errCode != E_OK) { + LOGE("Add fd to epoll set failed, err:'%d'", errCode); + return errCode; + } + + ++pollFdCount_; + return E_OK; +} + +int EventLoopEpoll::RemoveEvent(EventImpl *event) +{ + if (event == nullptr) { + return -E_INVALID_ARGS; + } + + EventsMask events = event->GetEvents(); + int errCode = EpollCtl(EPOLL_CTL_DEL, event, events); + if (errCode != E_OK) { + LOGE("Remove fd from epoll set failed, err:'%d'", errCode); + return errCode; + } + + --pollFdCount_; + return E_OK; +} + +int EventLoopEpoll::ModifyEvent(EventImpl *event, bool isAdd, EventsMask events) +{ + if (event == nullptr) { + return -E_INVALID_ARGS; + } + + EventsMask newEvents = event->GetEvents(); + if (isAdd) { + newEvents |= events; + } else { + newEvents &= ~events; + } + + int errCode = EpollCtl(EPOLL_CTL_MOD, event, newEvents); + if (errCode != E_OK) { + LOGE("Modify fd in epoll set failed, err:'%d'", errCode); + return errCode; + } + return E_OK; +} + +DEFINE_OBJECT_TAG_FACILITIES(EventLoopEpoll) +} +#endif // EVENT_LOOP_USE_EPOLL diff --git a/mock/distributeddb/common/src/evloop/src/event_loop_epoll.h b/mock/distributeddb/common/src/evloop/src/event_loop_epoll.h new file mode 100644 index 00000000..d802afad --- /dev/null +++ b/mock/distributeddb/common/src/evloop/src/event_loop_epoll.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 EVENT_LOOP_EPOLL_H +#define EVENT_LOOP_EPOLL_H + +#include "event_loop_impl.h" + +#ifdef EVENT_LOOP_USE_EPOLL +#include +#include + +namespace DistributedDB { +class EventLoopEpoll : public EventLoopImpl { +public: + EventLoopEpoll(); + ~EventLoopEpoll() override; + + int Initialize() override; + +private: + int Prepare(const std::set &polling) override; + int Poll(EventTime sleepTime) override; + int WakeUp() override; + int Exit(const std::set &polling) override; + int AddEvent(EventImpl *event) override; + int RemoveEvent(EventImpl *event) override; + int ModifyEvent(EventImpl *event, bool isAdd, EventsMask events) override; + + void EpollWokenUp(); + uint32_t CalEpollEvents(EventsMask events) const; + EventsMask CalEventsMask(uint32_t epollEvents); + int EpollCtl(int operation, EventImpl *event, EventsMask events); + + DECLARE_OBJECT_TAG(EventLoopEpoll); + + static constexpr int EPOLL_INIT_REVENTS = 32; + EventFd wakeUpFd_; + EventFd epollFd_; + int pollFdCount_; + std::vector revents_; +}; +} +#endif // EVENT_LOOP_USE_EPOLL +#endif // EVENT_LOOP_EPOLL_H diff --git a/mock/distributeddb/common/src/evloop/src/event_loop_impl.cpp b/mock/distributeddb/common/src/evloop/src/event_loop_impl.cpp new file mode 100644 index 00000000..bc920a86 --- /dev/null +++ b/mock/distributeddb/common/src/evloop/src/event_loop_impl.cpp @@ -0,0 +1,590 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "event_loop_impl.h" + +#include + +#include "db_errno.h" +#include "log_print.h" +#include "event_impl.h" + +namespace DistributedDB { +class EventRequest { +public: + enum { + ADD_EVENT = 1, + REMOVE_EVENT, + SET_TIMEOUT, + MOD_EVENTS_ADD, + MOD_EVENTS_REMOVE, + }; + + EventRequest(int type, EventImpl *event, EventsMask events) + : type_(type), + event_(event), + events_(events), + timeout_(0) + { + if (event != nullptr) { + event->IncObjRef(event); + } + } + + EventRequest(int type, EventImpl *event, EventTime timeout) + : type_(type), + event_(event), + events_(0), + timeout_(timeout) + { + if (event != nullptr) { + event->IncObjRef(event); + } + } + + ~EventRequest() + { + if (event_ != nullptr) { + event_->DecObjRef(event_); + event_ = nullptr; + } + } + + static bool IsValidType(int type) + { + if (type < ADD_EVENT || type > MOD_EVENTS_REMOVE) { + return false; + } + return true; + } + + int GetType() const + { + return type_; + } + + void GetEvent(EventImpl *&event) const + { + event = event_; + } + + EventsMask GetEvents() const + { + return events_; + } + + EventTime GetTimeout() const + { + return timeout_; + } + +private: + int type_; + EventImpl *event_; + EventsMask events_; + EventTime timeout_; +}; + +EventLoopImpl::EventLoopImpl() + : pollingSetChanged_(false) +{ + OnKill([this](){ OnKillLoop(); }); +} + +EventLoopImpl::~EventLoopImpl() +{} + +int EventLoopImpl::Add(IEvent *event) +{ + if (event == nullptr) { + return -E_INVALID_ARGS; + } + + auto eventImpl = static_cast(event); + if (!eventImpl->SetLoop(this)) { + LOGE("Add ev to loop failed, already attached."); + return -E_INVALID_ARGS; + } + + EventTime timeout = 0; + int errCode = QueueRequest(EventRequest::ADD_EVENT, eventImpl, timeout); + if (errCode != E_OK) { + eventImpl->SetLoop(nullptr); + LOGE("Add ev to loop failed. err: '%d'.", errCode); + } + return errCode; +} + +int EventLoopImpl::Remove(IEvent *event) +{ + if (event == nullptr) { + return -E_INVALID_ARGS; + } + + auto eventImpl = static_cast(event); + bool isLoopConfused = false; + if (!eventImpl->Attached(this, isLoopConfused)) { + if (isLoopConfused) { + LOGE("Remove ev' from loop failed, loop confused."); + return -E_UNEXPECTED_DATA; + } + return E_OK; + } + + EventTime timeout = 0; + int errCode = QueueRequest(EventRequest::REMOVE_EVENT, eventImpl, timeout); + if (errCode != E_OK) { + LOGE("Remove ev from loop failed. err: '%d'.", errCode); + } + return errCode; +} + +int EventLoopImpl::Run() +{ + { + RefObject::AutoLock lockGuard(this); + if (IsKilled()) { + LOGE("Try to run a killed loop."); + return -E_OBJ_IS_KILLED; + } + if (loopThread_ != std::thread::id()) { + LOGE("Try to run a threaded loop."); + return -E_BUSY; + } + loopThread_ = std::this_thread::get_id(); + } + + int errCode; + IncObjRef(this); + + while (true) { + errCode = ProcessRequest(); + if (errCode != E_OK) { + break; + } + + errCode = Prepare(polling_); + if (errCode != E_OK) { + break; + } + + EventTime sleepTime = CalSleepTime(); + errCode = Poll(sleepTime); + if (errCode != E_OK) { + break; + } + + errCode = ProcessRequest(); + if (errCode != E_OK) { + break; + } + + errCode = DispatchAll(); + if (errCode != E_OK) { + break; + } + } + + CleanLoop(); + DecObjRef(this); + if (errCode == -E_OBJ_IS_KILLED) { + LOGD("Loop exited."); + } else { + LOGE("Loop exited, err:'%d'.", errCode); + } + return errCode; +} + +int EventLoopImpl::Modify(EventImpl *event, bool isAdd, EventsMask events) +{ + if (event == nullptr) { + return -E_INVALID_ARGS; + } + + int type = isAdd ? EventRequest::MOD_EVENTS_ADD : + EventRequest::MOD_EVENTS_REMOVE; + int errCode = QueueRequest(type, event, events); + if (errCode != E_OK) { + LOGE("Modify loop ev events failed. err: '%d'.", errCode); + } + return errCode; +} + +int EventLoopImpl::Modify(EventImpl *event, EventTime time) +{ + if (event == nullptr) { + return -E_INVALID_ARGS; + } + + int errCode = QueueRequest(EventRequest::SET_TIMEOUT, event, time); + if (errCode != E_OK) { + LOGE("Mod loop ev time failed. err: '%d'.", errCode); + } + return errCode; +} + +EventTime EventLoopImpl::GetTime() const +{ + uint64_t microsecond = 0; + OS::GetMonotonicRelativeTimeInMicrosecond(microsecond); // It is not very possible to fail, if so use 0 as default + return static_cast(microsecond / 1000); // 1000 is the multiple between microsecond and millisecond +} + +int EventLoopImpl::SendRequestToLoop(EventRequest *eventRequest) +{ + if (eventRequest == nullptr) { + return -E_INVALID_ARGS; + } + + RefObject::AutoLock lockGuard(this); + if (IsKilled()) { + return -E_OBJ_IS_KILLED; + } + requests_.push_back(eventRequest); + WakeUp(); + return E_OK; +} + +template +int EventLoopImpl::QueueRequest(int type, EventImpl *event, T argument) +{ + if (!EventRequest::IsValidType(type)) { + return -E_INVALID_ARGS; + } + if (event == nullptr || + !event->IsValidArg(argument)) { + return -E_INVALID_ARGS; + } + + if (IsKilled()) { // pre-check + return -E_OBJ_IS_KILLED; + } + + int errCode; + if (event != nullptr) { + errCode = event->CheckStatus(); + if (errCode != E_OK) { + if (errCode != -E_OBJ_IS_KILLED || + type != EventRequest::REMOVE_EVENT) { + return errCode; + } + } + } + + auto eventRequest = new (std::nothrow) EventRequest(type, event, argument); + if (eventRequest == nullptr) { + return -E_OUT_OF_MEMORY; + } + + errCode = SendRequestToLoop(eventRequest); + if (errCode != E_OK) { + delete eventRequest; + eventRequest = nullptr; + } + return errCode; +} + +bool EventLoopImpl::IsInLoopThread(bool &started) const +{ + if (loopThread_ == std::thread::id()) { + started = false; + } else { + started = true; + } + return std::this_thread::get_id() == loopThread_; +} + +bool EventLoopImpl::EventObjectExists(EventImpl *event) const +{ + return polling_.find(event) != polling_.end(); +} + +bool EventLoopImpl::EventFdExists(const EventImpl *event) const +{ + if (!event->IsValidFd()) { + return false; + } + for (auto ev : polling_) { + if (ev->GetEventFd() == event->GetEventFd()) { + return true; + } + } + return false; +} + +int EventLoopImpl::AddEventObject(EventImpl *event, EventTime now) +{ + if (event == nullptr) { + return -E_INVALID_ARGS; + } + if (EventObjectExists(event)) { + LOGE("Add event object failed. ev already exists."); + return -EEXIST; + } + if (EventFdExists(event)) { + LOGE("Add event object failed. ev fd already exists."); + return -EEXIST; + } + + int errCode = E_OK; + if (!event->IsTimer()) { + errCode = AddEvent(event); + } + + if (errCode == E_OK) { + polling_.insert(event); + event->SetStartTime(now); + event->SetRevents(0); + event->IncObjRef(event); + pollingSetChanged_ = true; + } else { + LOGE("Add event failed. err: '%d'.", errCode); + } + return errCode; +} + +int EventLoopImpl::RemoveEventObject(EventImpl *event) +{ + if (event == nullptr) { + return -E_INVALID_ARGS; + } + if (!EventObjectExists(event)) { + return -E_NO_SUCH_ENTRY; + } + + int errCode = E_OK; + if (!event->IsTimer()) { + errCode = RemoveEvent(event); + } + + if (errCode == E_OK) { + polling_.erase(event); + event->SetLoop(nullptr); + event->DecObjRef(event); + pollingSetChanged_ = true; + } else { + LOGE("Remove event failed. err: '%d'.", errCode); + } + return errCode; +} + +int EventLoopImpl::ModifyEventObject(EventImpl *event, bool isAdd, EventsMask events) +{ + if (event == nullptr) { + return -E_INVALID_ARGS; + } + if (!EventObjectExists(event)) { + return -EEXIST; + } + + int errCode = E_OK; + if (!event->IsTimer()) { + EventsMask genericEvents = events & (~IEvent::ET_TIMEOUT); + if (genericEvents) { + errCode = ModifyEvent(event, isAdd, genericEvents); + } + } + + if (errCode == E_OK) { + event->SetEvents(isAdd, events); + } else { + LOGE("Modify event' failed. err: '%d'.", errCode); + } + return errCode; +} + +int EventLoopImpl::ModifyEventObject(EventImpl *event, EventTime timeout) +{ + if (event == nullptr) { + return -E_INVALID_ARGS; + } + if (!EventObjectExists(event)) { + return -E_NO_SUCH_ENTRY; + } + event->SetTimeoutPeriod(timeout); + return E_OK; +} + +void EventLoopImpl::ProcessRequest(std::list &requests) +{ + EventTime now = GetTime(); + while (true) { + if (requests.empty()) { + break; + } + + EventRequest *request = requests.front(); + requests.pop_front(); + if (request == nullptr) { + continue; + } + + if (!IsKilled()) { + EventImpl *event = nullptr; + request->GetEvent(event); + EventsMask events = request->GetEvents(); + EventTime timeout = request->GetTimeout(); + + switch (request->GetType()) { + case EventRequest::ADD_EVENT: + (void)(AddEventObject(event, now)); + break; + + case EventRequest::REMOVE_EVENT: + (void)(RemoveEventObject(event)); + break; + + case EventRequest::MOD_EVENTS_ADD: + (void)(ModifyEventObject(event, true, events)); + break; + + case EventRequest::MOD_EVENTS_REMOVE: + (void)(ModifyEventObject(event, false, events)); + break; + + case EventRequest::SET_TIMEOUT: + (void)(ModifyEventObject(event, timeout)); + break; + + default: + break; + } + } + + delete request; + request = nullptr; + } +} + +int EventLoopImpl::ProcessRequest() +{ + int errCode = E_OK; + std::list requests; + { + RefObject::AutoLock lockGuard(this); + if (IsKilled()) { + errCode = -E_OBJ_IS_KILLED; + } + if (requests_.empty()) { + return errCode; + } + std::swap(requests, requests_); + } + + ProcessRequest(requests); + return errCode; +} + +EventTime EventLoopImpl::CalSleepTime() const +{ + EventTime now = GetTime(); + EventTime minInterval = EventImpl::MAX_TIME_VALUE; + + for (auto event : polling_) { + if (event == nullptr) { + continue; + } + + EventTime t; + bool valid = event->GetTimeoutPoint(t); + if (!valid) { + continue; + } + + if (t <= now) { + return 0; + } + + EventTime interval = t - now; + if (interval < minInterval) { + minInterval = interval; + } + } + + return minInterval; +} + +int EventLoopImpl::DispatchAll() +{ + do { + EventTime now = GetTime(); + pollingSetChanged_ = false; + + for (auto event : polling_) { + if (IsKilled()) { + return -E_OBJ_IS_KILLED; + } + if (event == nullptr) { + continue; + } + + event->IncObjRef(event); + event->UpdateElapsedTime(now); + int errCode = event->Dispatch(); + if (errCode != E_OK) { + RemoveEventObject(event); + } else { + event->SetRevents(0); + } + event->DecObjRef(event); + + if (pollingSetChanged_) { + break; + } + } + } while (pollingSetChanged_); + return E_OK; +} + +void EventLoopImpl::CleanLoop() +{ + if (!IsKilled()) { + return; + } + + ProcessRequest(); + std::set polling = std::move(polling_); + int errCode = Exit(polling); + if (errCode != E_OK) { + LOGE("Exit loop failed when cleanup, err:'%d'.", errCode); + } + + for (auto event : polling) { + if (event != nullptr) { + event->KillAndDecObjRef(event); + } + } +} + +void EventLoopImpl::OnKillLoop() +{ + bool started = true; + if (IsInLoopThread(started)) { + // Loop object is set to state: killed, + // everything will be done in loop.Run() + return; + } + + if (started) { + // Ditto + WakeUp(); + } else { + // Drop the lock. + UnlockObj(); + CleanLoop(); + LockObj(); + } +} +} diff --git a/mock/distributeddb/common/src/evloop/src/event_loop_impl.h b/mock/distributeddb/common/src/evloop/src/event_loop_impl.h new file mode 100644 index 00000000..91c270ba --- /dev/null +++ b/mock/distributeddb/common/src/evloop/src/event_loop_impl.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 EVENT_LOOP_IMPL_H +#define EVENT_LOOP_IMPL_H + +#include +#include +#include +#include "platform_specific.h" +#include "../include/ievent_loop.h" +#include "../include/ievent.h" + +#if defined EVLOOP_TIMER_ONLY +#define EVENT_LOOP_USE_SELECT +#else +#define EVENT_LOOP_USE_EPOLL +#endif + +namespace DistributedDB { +class EventImpl; +class EventRequest; + +class EventLoopImpl : public IEventLoop { +public: + EventLoopImpl(); + ~EventLoopImpl() override; + DISABLE_COPY_ASSIGN_MOVE(EventLoopImpl); + + int Add(IEvent *event) override; + int Remove(IEvent *event) override; + int Run() override; + int Modify(EventImpl *event, bool isAdd, EventsMask events); + int Modify(EventImpl *event, EventTime time); + + // Initialize the loop, code removed from the constructor. + virtual int Initialize() = 0; + bool IsInLoopThread(bool &started) const; + +private: + virtual int Prepare(const std::set &polling) = 0; + virtual int Poll(EventTime sleepTime) = 0; + virtual int WakeUp() = 0; + virtual int Exit(const std::set &polling) = 0; + virtual int AddEvent(EventImpl *event) = 0; + virtual int RemoveEvent(EventImpl *event) = 0; + virtual int ModifyEvent(EventImpl *event, bool isAdd, EventsMask events) = 0; + virtual EventTime GetTime() const; + + template + int QueueRequest(int type, EventImpl *event, T argument); + int SendRequestToLoop(EventRequest *eventRequest); + bool EventObjectExists(EventImpl *event) const; + bool EventFdExists(const EventImpl *event) const; + int AddEventObject(EventImpl *event, EventTime now); + int RemoveEventObject(EventImpl *event); + int ModifyEventObject(EventImpl *event, bool isAdd, EventsMask events); + int ModifyEventObject(EventImpl *event, EventTime timeout); + void ProcessRequest(std::list &requests); + int ProcessRequest(); + EventTime CalSleepTime() const; + int DispatchAll(); + void CleanLoop(); + void OnKillLoop(); + + std::list requests_; + std::set polling_; + bool pollingSetChanged_; + std::thread::id loopThread_; +}; +} + +#endif // EVENT_LOOP_IMPL_H diff --git a/mock/distributeddb/common/src/evloop/src/event_loop_select.cpp b/mock/distributeddb/common/src/evloop/src/event_loop_select.cpp new file mode 100644 index 00000000..b26b382f --- /dev/null +++ b/mock/distributeddb/common/src/evloop/src/event_loop_select.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "event_loop_select.h" + +#ifdef EVENT_LOOP_USE_SELECT +#include +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +EventLoopSelect::EventLoopSelect() +{ +} + +EventLoopSelect::~EventLoopSelect() +{ +} + +int EventLoopSelect::Initialize() +{ + return E_OK; +} + +int EventLoopSelect::Prepare(const std::set &polling) +{ + (void)polling; + return E_OK; +} + +int EventLoopSelect::Poll(EventTime sleepTime) +{ + std::unique_lock lockGuard(wakeUpMutex_); + auto now = std::chrono::system_clock::now(); + auto to = now + std::chrono::milliseconds(sleepTime); + wakeUpCondition_.wait_until(lockGuard, to); + return E_OK; +} + +int EventLoopSelect::WakeUp() +{ + wakeUpCondition_.notify_one(); + return E_OK; +} + +int EventLoopSelect::Exit(const std::set &polling) +{ + (void)polling; + return E_OK; +} + +int EventLoopSelect::AddEvent(EventImpl *event) +{ + (void)event; + return E_OK; +} + +int EventLoopSelect::RemoveEvent(EventImpl *event) +{ + (void)event; + return E_OK; +} + +int EventLoopSelect::ModifyEvent(EventImpl *event, bool isAdd, EventsMask events) +{ + (void)event; + (void)isAdd; + (void)events; + return E_OK; +} + +DEFINE_OBJECT_TAG_FACILITIES(EventLoopSelect) +} // namespace DistributedDB + +#endif // EVENT_LOOP_USE_SELECT diff --git a/mock/distributeddb/common/src/evloop/src/event_loop_select.h b/mock/distributeddb/common/src/evloop/src/event_loop_select.h new file mode 100644 index 00000000..a4d77d15 --- /dev/null +++ b/mock/distributeddb/common/src/evloop/src/event_loop_select.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 EVENT_LOOP_SELECT_H +#define EVENT_LOOP_SELECT_H + +#include "event_loop_impl.h" + +#ifdef EVENT_LOOP_USE_SELECT +#include +#include + +namespace DistributedDB { +class EventLoopSelect : public EventLoopImpl { +public: + EventLoopSelect(); + ~EventLoopSelect(); + + int Initialize() override; + +private: + int Prepare(const std::set &polling) override; + int Poll(EventTime sleepTime) override; + int WakeUp() override; + int Exit(const std::set &polling) override; + int AddEvent(EventImpl *event) override; + int RemoveEvent(EventImpl *event) override; + int ModifyEvent(EventImpl *event, bool isAdd, EventsMask events) override; + + DECLARE_OBJECT_TAG(EventLoopSelect); + + std::mutex wakeUpMutex_; + std::condition_variable wakeUpCondition_; +}; +} // namespace DistributedDB +#endif // EVENT_LOOP_USE_SELECT +#endif // EVENT_LOOP_SELECT_H diff --git a/mock/distributeddb/common/src/evloop/src/ievent.cpp b/mock/distributeddb/common/src/evloop/src/ievent.cpp new file mode 100644 index 00000000..361edfe0 --- /dev/null +++ b/mock/distributeddb/common/src/evloop/src/ievent.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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/ievent.h" +#include "db_errno.h" +#include "event_impl.h" + +namespace DistributedDB { +IEvent *IEvent::CreateEvent(EventTime timeout, int &errCode) +{ + if (timeout < 0) { + errCode = -E_INVALID_ARGS; + return nullptr; + } + + IEvent *event = new (std::nothrow) EventImpl(timeout); + if (event == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + errCode = E_OK; + return event; +} + +IEvent *IEvent::CreateEvent(EventFd fd, EventsMask events, + EventTime timeout, int &errCode) +{ + errCode = -E_INVALID_ARGS; + if (!events) { + return nullptr; + } + if ((events & ET_TIMEOUT) && (timeout < 0)) { + return nullptr; + } + if (!(events & ET_TIMEOUT) && !fd.IsValid()) { + return nullptr; + } + + IEvent *event = new (std::nothrow) EventImpl(fd, events, timeout); + if (event == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + errCode = E_OK; + return event; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/evloop/src/ievent_loop.cpp b/mock/distributeddb/common/src/evloop/src/ievent_loop.cpp new file mode 100644 index 00000000..ce30ab36 --- /dev/null +++ b/mock/distributeddb/common/src/evloop/src/ievent_loop.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "event_loop_impl.h" +#include "db_errno.h" + +#ifdef EVENT_LOOP_USE_EPOLL +#include "event_loop_epoll.h" +using EventLoop = DistributedDB::EventLoopEpoll; +#else +#include "event_loop_select.h" +using EventLoop = DistributedDB::EventLoopSelect; +#endif + +namespace DistributedDB { +IEventLoop *IEventLoop::CreateEventLoop(int &errCode) +{ + EventLoopImpl *loop = new (std::nothrow) EventLoop; + if (loop == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + + errCode = loop->Initialize(); + if (errCode != E_OK) { + delete loop; + loop = nullptr; + } + return loop; +} +} diff --git a/mock/distributeddb/common/src/flatbuffer_schema.cpp b/mock/distributeddb/common/src/flatbuffer_schema.cpp new file mode 100644 index 00000000..021b5ec7 --- /dev/null +++ b/mock/distributeddb/common/src/flatbuffer_schema.cpp @@ -0,0 +1,1005 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "schema_object.h" +#include +#include +#include "schema_constant.h" +#include "schema_utils.h" +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +namespace { +#ifndef OMIT_FLATBUFFER +constexpr double EPSILON = 0.000001; // 0.000001 for tolerance +inline bool IsDoubleNearlyEqual(double left, double right) +{ + if (std::fabs(left - right) < EPSILON) { + return true; + } + double absBigger = std::max(std::fabs(left), std::fabs(right)); + double relativeDiff = ((absBigger == 0.0) ? 0.0 : (std::fabs(left - right) / absBigger)); // 0.0 for double 0 + return relativeDiff < EPSILON; +} +#endif // OMIT_FLATBUFFER +} + +void SchemaObject::FlatBufferSchema::CopyFrom(const FlatBufferSchema &other) +{ + // The SchemaObject guarantee not CopyFrom "Self"; owner_ can only be set at construction. + description_ = other.description_; +} + +std::string SchemaObject::FlatBufferSchema::GetDescription() const +{ + return description_; +} + +#ifndef OMIT_FLATBUFFER +bool SchemaObject::FlatBufferSchema::IsFlatBufferSchema(const std::string &inOriginal, std::string &outDecoded) +{ + if (inOriginal.empty()) { + LOGE("[FBSchema][Is] OriSchema empty."); + return false; + } + if (inOriginal.size() >= SchemaConstant::SCHEMA_STRING_SIZE_LIMIT * 2) { // 2 :Maximum base64 encode size multiple + // Base64 encode will not exceed 2 times original binary + LOGE("[FBSchema][Is] OriSchemaSize=%zu too large even after base64 encode.", inOriginal.size()); + return false; + } + auto oriSchemaBuf = reinterpret_cast(inOriginal.c_str()); + flatbuffers::Verifier oriVerifier(oriSchemaBuf, inOriginal.size()); + if (reflection::VerifySizePrefixedSchemaBuffer(oriVerifier)) { + outDecoded = inOriginal; // The original one is the decoded one + return true; + } + outDecoded.clear(); + return false; +} + +// A macro check pointer get from flatbuffer that won't be nullptr(required field) in fact after verified by flatbuffer +#define CHECK_NULL_UNLIKELY_RETURN_ERROR(pointer) \ + if ((pointer) == nullptr) { \ + return -E_INTERNAL_ERROR; \ + } + +namespace { +constexpr uint32_t ROOT_DEFINE_DEPTH = 0; +constexpr uint32_t SIZE_PREFIX_SIZE = sizeof(flatbuffers::uoffset_t); +constexpr int32_t INDEX_OF_NOT_ENUM = -1; + +inline bool AttributeExistAndHasValue(const reflection::KeyValue *inAttr) +{ + return (inAttr != nullptr) && (inAttr->value() != nullptr) && (inAttr->value()->size() > 0); +} + +inline bool IsIntegerType(reflection::BaseType inType) +{ + return (inType >= reflection::BaseType::Bool) && (inType <= reflection::BaseType::ULong); +} + +inline bool IsRealType(reflection::BaseType inType) +{ + return (inType >= reflection::BaseType::Float) && (inType <= reflection::BaseType::Double); +} + +inline bool IsScalarType(reflection::BaseType inType) +{ + return IsIntegerType(inType) || IsRealType(inType); +} + +inline bool IsStringType(reflection::BaseType inType) +{ + return inType == reflection::BaseType::String; +} + +inline bool IsIndexableType(reflection::BaseType inType) +{ + return IsScalarType(inType) || IsStringType(inType); +} + +inline bool IsVectorType(reflection::BaseType inType) +{ + return inType == reflection::BaseType::Vector; +} + +inline bool IsStringOrVectorType(reflection::BaseType inType) +{ + return IsStringType(inType) || IsVectorType(inType); +} + +inline bool IsObjectType(reflection::BaseType inType) +{ + return inType == reflection::BaseType::Obj; +} + +inline bool IsSupportTypeAtRoot(reflection::BaseType inType) +{ + return IsIndexableType(inType) || IsVectorType(inType) || IsObjectType(inType); +} + +inline bool IsRequiredSupportType(reflection::BaseType inType) +{ + return IsStringOrVectorType(inType) || IsObjectType(inType); +} + +inline bool IsConflict(bool deprecated, bool required) +{ + return deprecated && required; +} +} + +int SchemaObject::FlatBufferSchema::ParseFlatBufferSchema(const std::string &inDecoded) +{ + description_.clear(); // For recovering from a fail parse + // The upper logic had guaranteed that the inDecoded be verified OK + auto schema = reflection::GetSizePrefixedSchema(inDecoded.c_str()); + CHECK_NULL_UNLIKELY_RETURN_ERROR(schema); + auto rootTable = schema->root_table(); + if (rootTable == nullptr || rootTable->is_struct()) { + LOGE("[FBSchema][Parse] Root table nullptr or is struct."); + return -E_SCHEMA_PARSE_FAIL; + } + + auto rootTableName = rootTable->name(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(rootTableName); + description_ += ("RootTableName=" + SchemaUtils::StripNameSpace(rootTableName->str()) + ";"); + + int errCode = ParseCheckRootTableAttribute(*rootTable); + if (errCode != E_OK) { + return errCode; + } + + RawIndexInfos indexCollect; + errCode = ParseCheckRootTableDefine(*schema, *rootTable, indexCollect); + if (errCode != E_OK) { + return errCode; + } + + errCode = ParseCheckIndexes(indexCollect); + if (errCode != E_OK) { + return errCode; + } + return E_OK; +} + +int SchemaObject::FlatBufferSchema::CompareFlatBufferDefine(const FlatBufferSchema &other) const +{ + // Schema had been parsed and constraint checked before this function is called, so as we assumed. + // Here in the compare procedure, we only check null-point, do not check or suspect of constraint any more. + auto selfSchema = GetSchema(); + auto otherSchema = other.GetSchema(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(selfSchema); + CHECK_NULL_UNLIKELY_RETURN_ERROR(otherSchema); + auto selfRootTable = selfSchema->root_table(); + auto otherRootTable = otherSchema->root_table(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(selfRootTable); + CHECK_NULL_UNLIKELY_RETURN_ERROR(otherRootTable); + auto selfRootTableName = selfRootTable->name(); + auto otherRootTableName = otherRootTable->name(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(selfRootTableName); + CHECK_NULL_UNLIKELY_RETURN_ERROR(otherRootTableName); + + std::string selfRootName = SchemaUtils::StripNameSpace(selfRootTableName->str()); + std::string otherRootName = SchemaUtils::StripNameSpace(otherRootTableName->str()); + if (selfRootName != otherRootName) { + LOGE("[FBSchema][Compare] RootName differ, self=%s, other=%s.", selfRootName.c_str(), otherRootName.c_str()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + // We don't have to compare rootTableAttribute or index here, they are done by SchemaObject + std::set comparedTypeNameSet; + return CompareTableOrStructDefine({selfSchema, otherSchema}, {selfRootTable, otherRootTable}, true, + comparedTypeNameSet); +} + +namespace { +int CheckSizePrefixRawValue(const RawValue &inValue) +{ + if (inValue.first == nullptr) { // Unlikely + return -E_INVALID_ARGS; + } + if (inValue.second <= SIZE_PREFIX_SIZE) { + LOGE("[FBSchema][CheckSizePreValue] ValueSize=%u too short.", inValue.second); + return -E_INVALID_ARGS; + } + auto realSize = flatbuffers::ReadScalar(inValue.first); + if (realSize != inValue.second - SIZE_PREFIX_SIZE) { + LOGE("[FBSchema][CheckSizePreValue] RealSize=%u mismatch valueSize=(%u-4).", realSize, inValue.second); + return -E_INVALID_ARGS; + } + return E_OK; +} +} + +int SchemaObject::FlatBufferSchema::VerifyFlatBufferValue(const RawValue &inValue, bool tryNoSizePrefix) const +{ + (void)tryNoSizePrefix; // Use it in the future, currently we demand value is sizePrefixed + int errCode = CheckSizePrefixRawValue(inValue); + if (errCode != E_OK) { + return errCode; + } + auto schema = GetSchema(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(schema); + auto rootTable = schema->root_table(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(rootTable); + if (!flatbuffers::Verify(*schema, *rootTable, inValue.first + SIZE_PREFIX_SIZE, + inValue.second - SIZE_PREFIX_SIZE)) { + return -E_FLATBUFFER_VERIFY_FAIL; + } + return E_OK; +} + +namespace { +FieldType MapFieldType(reflection::BaseType inType) +{ + static std::map fieldTypeMap{ + {reflection::BaseType::Bool, FieldType::LEAF_FIELD_BOOL}, + {reflection::BaseType::Byte, FieldType::LEAF_FIELD_INTEGER}, + {reflection::BaseType::UByte, FieldType::LEAF_FIELD_INTEGER}, + {reflection::BaseType::Short, FieldType::LEAF_FIELD_INTEGER}, + {reflection::BaseType::UShort, FieldType::LEAF_FIELD_INTEGER}, + {reflection::BaseType::Int, FieldType::LEAF_FIELD_INTEGER}, + {reflection::BaseType::UInt, FieldType::LEAF_FIELD_LONG}, + {reflection::BaseType::Long, FieldType::LEAF_FIELD_LONG}, + {reflection::BaseType::ULong, FieldType::LEAF_FIELD_DOUBLE}, + {reflection::BaseType::Float, FieldType::LEAF_FIELD_DOUBLE}, + {reflection::BaseType::Double, FieldType::LEAF_FIELD_DOUBLE}, + {reflection::BaseType::String, FieldType::LEAF_FIELD_STRING}, + {reflection::BaseType::Vector, FieldType::LEAF_FIELD_ARRAY}, + {reflection::BaseType::Obj, FieldType::INTERNAL_FIELD_OBJECT}, + }; + if (fieldTypeMap.count(inType) == 0) { + return FieldType::LEAF_FIELD_NULL; + } + return fieldTypeMap[inType]; +} + +RawString CheckDollarDotAndSkipIt(RawString inPath) +{ + if (inPath == nullptr) { + return nullptr; + } + auto pathStr = inPath; + if (*pathStr++ != '$') { + return nullptr; + } + if (*pathStr++ != '.') { + return nullptr; + } + if (*pathStr == 0) { + return nullptr; + } + return pathStr; +} + +const reflection::Field *GetFieldInfoFromSchemaByPath(const reflection::Schema &schema, RawString pathStr) +{ + auto rootTable = schema.root_table(); + if (rootTable == nullptr) { // Unlikely + return nullptr; + } + auto rootFields = rootTable->fields(); + if (rootFields == nullptr) { // Unlikely + return nullptr; + } + // Unlikely to return nullptr, except internal-error happened + return rootFields->LookupByKey(pathStr); +} + +int DoVerifyBeforeExtract(const flatbuffers::Table &rootValue, const reflection::Field &fieldInfo, + const flatbuffers::Verifier &verifier) +{ + auto type = fieldInfo.type(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(type); + bool verifyResult = true; + switch (type->base_type()) { + case reflection::Bool: + case reflection::Byte: + case reflection::UByte: + verifyResult = rootValue.VerifyField(verifier, fieldInfo.offset()); + break; + case reflection::Short: + case reflection::UShort: + verifyResult = rootValue.VerifyField(verifier, fieldInfo.offset()); + break; + case reflection::Int: + case reflection::UInt: + verifyResult = rootValue.VerifyField(verifier, fieldInfo.offset()); + break; + case reflection::Long: + case reflection::ULong: + verifyResult = rootValue.VerifyField(verifier, fieldInfo.offset()); + break; + case reflection::Float: + verifyResult = rootValue.VerifyField(verifier, fieldInfo.offset()); + break; + case reflection::Double: + verifyResult = rootValue.VerifyField(verifier, fieldInfo.offset()); + break; + case reflection::String: + verifyResult = rootValue.VerifyField(verifier, fieldInfo.offset()) && + verifier.VerifyString(flatbuffers::GetFieldS(rootValue, fieldInfo)); // VerifyString can accept null + break; + default: + return -E_NOT_SUPPORT; + } + return (verifyResult ? E_OK : -E_FLATBUFFER_VERIFY_FAIL); +} + +inline std::string DoExtractString(const flatbuffers::Table &rootValue, const reflection::Field &fieldInfo) +{ + auto strVal = flatbuffers::GetFieldS(rootValue, fieldInfo); + if (strVal == nullptr) { + return ""; + } + return strVal->str(); +} + +int DoExtractValue(const flatbuffers::Table &rootValue, const reflection::Field &fieldInfo, TypeValue &outExtract) +{ + auto type = fieldInfo.type(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(type); + switch (type->base_type()) { + case reflection::Bool: + outExtract.second.boolValue = (flatbuffers::GetFieldI(rootValue, fieldInfo) != 0); + break; + case reflection::Byte: + outExtract.second.integerValue = flatbuffers::GetFieldI(rootValue, fieldInfo); + break; + case reflection::UByte: + outExtract.second.integerValue = flatbuffers::GetFieldI(rootValue, fieldInfo); + break; + case reflection::Short: + outExtract.second.integerValue = flatbuffers::GetFieldI(rootValue, fieldInfo); + break; + case reflection::UShort: + outExtract.second.integerValue = flatbuffers::GetFieldI(rootValue, fieldInfo); + break; + case reflection::Int: + outExtract.second.integerValue = flatbuffers::GetFieldI(rootValue, fieldInfo); + break; + case reflection::UInt: + outExtract.second.longValue = flatbuffers::GetFieldI(rootValue, fieldInfo); + break; + case reflection::Long: + outExtract.second.longValue = flatbuffers::GetFieldI(rootValue, fieldInfo); + break; + case reflection::ULong: + outExtract.second.doubleValue = flatbuffers::GetFieldI(rootValue, fieldInfo); + break; + case reflection::Float: + outExtract.second.doubleValue = flatbuffers::GetFieldF(rootValue, fieldInfo); + break; + case reflection::Double: + outExtract.second.doubleValue = flatbuffers::GetFieldF(rootValue, fieldInfo); + break; + case reflection::String: + outExtract.second.stringValue = DoExtractString(rootValue, fieldInfo); + break; + default: + return -E_NOT_SUPPORT; + } + return E_OK; +} + +int ExtractFlatBufferValueFinal(const flatbuffers::Table &rootValue, const reflection::Field &fieldInfo, + const flatbuffers::Verifier &verifier, TypeValue &outExtract) +{ + auto type = fieldInfo.type(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(type); + auto baseType = type->base_type(); + if (!IsIndexableType(baseType)) { + LOGE("[ExtractFinal] BaseType=%s not indexable.", reflection::EnumNameBaseType(baseType)); + return -E_NOT_SUPPORT; + } + outExtract.first = MapFieldType(type->base_type()); + int errCode = DoVerifyBeforeExtract(rootValue, fieldInfo, verifier); + if (errCode != E_OK) { + LOGE("[ExtractFinal] DoVerify fail, errCode=%d.", errCode); + return errCode; + } + errCode = DoExtractValue(rootValue, fieldInfo, outExtract); + if (errCode != E_OK) { + LOGE("[ExtractFinal] DoExtract fail, errCode=%d.", errCode); + return errCode; + } + return E_OK; +} +} + +int SchemaObject::FlatBufferSchema::ExtractFlatBufferValue(RawString inPath, const RawValue &inValue, + TypeValue &outExtract, bool tryNoSizePrefix) const +{ + // NOTE!!! This function is performance sensitive !!! Carefully not to allocate memory often!!! + (void)tryNoSizePrefix; // Use it in the future, currently we demand value is sizePrefixed + int errCode = CheckSizePrefixRawValue(inValue); + if (errCode != E_OK) { + return errCode; + } + auto pathStr = CheckDollarDotAndSkipIt(inPath); + if (pathStr == nullptr) { // Unlikely + LOGE("[FBSchema][Extract] inPath not begin with $. or nothing after it."); + return -E_INVALID_ARGS; + } + auto schema = GetSchema(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(schema); + // Currently we don't support nest-path + auto fieldInfo = GetFieldInfoFromSchemaByPath(*schema, pathStr); + if (fieldInfo == nullptr) { + LOGE("[FBSchema][Extract] FieldInfo of path=%s not found.", pathStr); + return -E_INTERNAL_ERROR; + } + // Begin extract, we have to minimal verify if we don't trust value from database + auto valueRealBegin = inValue.first + SIZE_PREFIX_SIZE; + auto valueRealSize = inValue.second - SIZE_PREFIX_SIZE; + flatbuffers::Verifier verifier(valueRealBegin, valueRealSize); + auto offset = verifier.VerifyOffset(0); // Attention: Verify root offset before we call GetAnyRoot + if (offset == 0) { + LOGE("[FBSchema][Extract] Verity root offset failed."); + return -E_FLATBUFFER_VERIFY_FAIL; + } + auto rootValue = flatbuffers::GetAnyRoot(valueRealBegin); + if (rootValue == nullptr) { + LOGE("[FBSchema][Extract] Get rootTable from value fail."); + return -E_INVALID_DATA; + } + // Verity vTable of rootTable before we extract anything from rootValue by reflection + bool vTableOk = rootValue->VerifyTableStart(verifier); + if (!vTableOk) { + LOGE("[FBSchema][Extract] Verify vTable of rootTable of value fail."); + return -E_FLATBUFFER_VERIFY_FAIL; + } + errCode = ExtractFlatBufferValueFinal(*rootValue, *fieldInfo, verifier, outExtract); + if (errCode != E_OK) { + return errCode; + } + verifier.EndTable(); + return E_OK; +} + +const reflection::Schema *SchemaObject::FlatBufferSchema::GetSchema() const +{ + // This function is called after schemaString_ had been verified by flatbuffer + return reflection::GetSizePrefixedSchema(owner_.schemaString_.c_str()); +} + +int SchemaObject::FlatBufferSchema::ParseCheckRootTableAttribute(const reflection::Object &rootTable) +{ + auto rootTableAttr = rootTable.attributes(); + if (rootTableAttr == nullptr) { + LOGE("[FBSchema][ParseRootAttr] Root table no attribute."); + return -E_SCHEMA_PARSE_FAIL; + } + + auto versionAttr = rootTableAttr->LookupByKey(SchemaConstant::KEYWORD_SCHEMA_VERSION.c_str()); + if (!AttributeExistAndHasValue(versionAttr)) { + LOGE("[FBSchema][ParseRootAttr] No SCHEMA_VERSION attribute or no value."); + return -E_SCHEMA_PARSE_FAIL; + } + if (SchemaUtils::Strip(versionAttr->value()->str()) != SchemaConstant::SCHEMA_SUPPORT_VERSION) { + LOGE("[FBSchema][ParseRootAttr] Unexpect SCHEMA_VERSION=%s.", versionAttr->value()->c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + owner_.schemaVersion_ = SchemaConstant::SCHEMA_SUPPORT_VERSION; + description_ += (SchemaConstant::KEYWORD_SCHEMA_VERSION + "=" + SchemaConstant::SCHEMA_SUPPORT_VERSION + ";"); + + auto skipsizeAttr = rootTableAttr->LookupByKey(SchemaConstant::KEYWORD_SCHEMA_SKIPSIZE.c_str()); + if (!AttributeExistAndHasValue(skipsizeAttr)) { + LOGI("[FBSchema][ParseRootAttr] No SCHEMA_SKIPSIZE attribute or no value."); + owner_.schemaSkipSize_ = 0; // Default skipsize value + return E_OK; + } + std::string skipsizeStr = SchemaUtils::Strip(skipsizeAttr->value()->str()); + int skipsizeInt = strtol(skipsizeStr.c_str(), nullptr, 10); // 10: decimal + if (std::to_string(skipsizeInt) != skipsizeStr || skipsizeInt < 0 || + static_cast(skipsizeInt) > SchemaConstant::SCHEMA_SKIPSIZE_MAX) { + LOGE("[FBSchema][ParseRootAttr] Unexpect SCHEMA_SKIPSIZE value=%s.", skipsizeAttr->value()->c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + owner_.schemaSkipSize_ = static_cast(skipsizeInt); + description_ += (SchemaConstant::KEYWORD_SCHEMA_SKIPSIZE + "=" + skipsizeStr + ";"); + return E_OK; +} + +int SchemaObject::FlatBufferSchema::ParseCheckRootTableDefine(const reflection::Schema &schema, + const reflection::Object &rootTable, RawIndexInfos &indexCollect) +{ + // Clear schemaDefine_ to recover from a fail parse + owner_.schemaDefine_.clear(); + auto fields = rootTable.fields(); + if (fields == nullptr || fields->size() == 0) { + LOGE("[FBSchema][ParseRootDefine] Empty define."); + return -E_SCHEMA_PARSE_FAIL; + } + for (uint32_t i = 0; i < fields->size(); i++) { + auto eachField = (*fields)[i]; + CHECK_NULL_UNLIKELY_RETURN_ERROR(eachField); + + auto name = eachField->name(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(name); + int errCode = SchemaUtils::CheckFieldName(name->str()); + if (errCode != E_OK) { + LOGE("[FBSchema][ParseRootDefine] Invalid fieldName=%s, errCode=%d.", name->c_str(), errCode); + return -E_SCHEMA_PARSE_FAIL; + } + FieldPath path{name->str()}; + if (owner_.schemaDefine_[ROOT_DEFINE_DEPTH].count(path) != 0) { // Unlikely + LOGE("[FBSchema][ParseRootDefine] FieldPath=%s already exist at root.", name->c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + + errCode = ParseCheckFieldInfo(schema, *eachField, path, indexCollect); + if (errCode != E_OK) { + LOGE("[FBSchema][ParseRootDefine] ParseFieldInfo errCode=%d, FieldPath=%s.", errCode, + SchemaUtils::FieldPathString(path).c_str()); + return errCode; + } + } + uint32_t fieldPathCount = 0; + for (uint32_t depth = ROOT_DEFINE_DEPTH; depth < SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX; depth++) { + if (owner_.schemaDefine_.count(depth) != 0) { + fieldPathCount += owner_.schemaDefine_[depth].size(); + } + } + if (fieldPathCount > SchemaConstant::SCHEMA_FEILD_NAME_COUNT_MAX) { + LOGE("[FBSchema][ParseRootDefine] FieldPath count=%u exceed the limitation.", fieldPathCount); + return -E_SCHEMA_PARSE_FAIL; + } + return E_OK; +} + +namespace { +bool CheckFieldTypeSupport(const reflection::Type &inType, bool isRootField) +{ + auto baseType = inType.base_type(); + if (isRootField) { + if (!IsSupportTypeAtRoot(baseType)) { + LOGE("[FBSchema][DecideType] BaseType=%s not support at root.", reflection::EnumNameBaseType(baseType)); + return false; + } + if (IsIntegerType(baseType) && (inType.index() != INDEX_OF_NOT_ENUM)) { + LOGE("[FBSchema][DecideType] BaseType=%s is enum, not support.", reflection::EnumNameBaseType(baseType)); + return false; + } + if (IsVectorType(baseType)) { + auto elementType = inType.element(); + if (!IsIndexableType(elementType)) { + LOGE("[FBSchema][DecideType] ElementType=%s not support for vector.", + reflection::EnumNameBaseType(elementType)); + return false; + } + } + } else { + // Currently only support nest in Struct, support only scalar and nest-struct + if (!IsScalarType(baseType) && !IsObjectType(baseType)) { + LOGE("[FBSchema][DecideType] BaseType=%s not support for struct.", reflection::EnumNameBaseType(baseType)); + return false; + } + } + return true; +} +} + +int SchemaObject::FlatBufferSchema::ParseCheckFieldInfo(const reflection::Schema &schema, + const reflection::Field &field, const FieldPath &path, RawIndexInfos &indexCollect) +{ + if (path.empty() || path.size() > SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX) { + LOGE("[FBSchema][ParseField] FieldPath size=%zu invalid.", path.size()); + return -E_SCHEMA_PARSE_FAIL; + } + uint32_t depth = path.size() - 1; // Depth count from zero + bool isRootField = (depth == ROOT_DEFINE_DEPTH); + SchemaAttribute &fieldInfo = owner_.schemaDefine_[depth][path]; // Create new entry in schemaDefine_ + + auto type = field.type(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(type); + if (!CheckFieldTypeSupport(*type, isRootField)) { + return -E_SCHEMA_PARSE_FAIL; + } + auto baseType = type->base_type(); + // Only type and isIndexable of SchemaAttribute is necessary + fieldInfo.type = MapFieldType(baseType); + fieldInfo.isIndexable = (IsIndexableType(baseType) && isRootField); + description_ += (SchemaUtils::FieldPathString(path) + "=" + reflection::EnumNameBaseType(baseType) + ";"); + + if (IsRequiredSupportType(baseType)) { + if (IsConflict(field.deprecated(), field.required())) { + LOGE("[FBSchema][ParseField] Deprecated conflict with required."); + return -E_SCHEMA_PARSE_FAIL; + } + } + if (fieldInfo.isIndexable) { + CollectRawIndexInfos(field, indexCollect); + } + if (IsObjectType(baseType)) { + int errCode = ParseCheckStructDefine(schema, field, path); + if (errCode != E_OK) { + return errCode; + } + } + return E_OK; +} + +void SchemaObject::FlatBufferSchema::CollectRawIndexInfos(const reflection::Field &field, + RawIndexInfos &indexCollect) const +{ + auto name = field.name(); + if (name == nullptr) { // Not possible + return; + } + auto fieldAttr = field.attributes(); + if (fieldAttr == nullptr) { + return; + } + auto indexAttr = fieldAttr->LookupByKey(SchemaConstant::KEYWORD_INDEX.c_str()); + if (indexAttr == nullptr) { + return; + } + if (indexAttr->value() == nullptr) { + indexCollect[name->str()] = ""; // Must be SingleField-Index + return; + } + indexCollect[name->str()] = indexAttr->value()->str(); // May still be empty string +} + +int SchemaObject::FlatBufferSchema::ParseCheckStructDefine(const reflection::Schema &schema, + const reflection::Field &field, const FieldPath &path) +{ + if (path.size() >= SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX) { + LOGE("[FBSchema][ParseStruct] Struct define at depth limitation."); + return -E_SCHEMA_PARSE_FAIL; + } + auto objects = schema.objects(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(objects); + auto type = field.type(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(type); + auto objIndex = type->index(); + if (objIndex < 0 || static_cast(objIndex) >= objects->size()) { // Unlikely + return -E_INTERNAL_ERROR; + } + auto structObj = (*objects)[objIndex]; + CHECK_NULL_UNLIKELY_RETURN_ERROR(structObj); + auto structName = structObj->name(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(structName); + if (!structObj->is_struct()) { + LOGE("[FBSchema][ParseStruct] Nest table=%s not support.", structName->c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + description_ += ("StructName=" + SchemaUtils::StripNameSpace(structName->str()) + ";"); + + // Parse fields + auto structFields = structObj->fields(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(structFields); + // Flatbuffer guarantee that struct will not be empty size, even if it is empty, we just ignore it + for (uint32_t i = 0; i < structFields->size(); i++) { + auto eachField = (*structFields)[i]; + CHECK_NULL_UNLIKELY_RETURN_ERROR(eachField); + auto eachName = eachField->name(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(eachName); + int errCode = SchemaUtils::CheckFieldName(eachName->str()); + if (errCode != E_OK) { + LOGE("[FBSchema][ParseStruct] Invalid fieldName=%s, errCode=%d.", eachName->c_str(), errCode); + return -E_SCHEMA_PARSE_FAIL; + } + FieldPath eachPath = path; + eachPath.push_back(eachName->str()); + RawIndexInfos notUsed; + errCode = ParseCheckFieldInfo(schema, *eachField, eachPath, notUsed); + if (errCode != E_OK) { + LOGE("[FBSchema][ParseStruct] ParseFieldInfo errCode=%d, FieldPath=%s.", errCode, + SchemaUtils::FieldPathString(eachPath).c_str()); + return errCode; + } + } + return E_OK; +} + +namespace { +inline bool IsNotCompositeIndex(const std::string &indexStr) +{ + // In fact, test found that attrValue will be "0" if not exist + return indexStr.empty() || indexStr == std::string("0"); +} +} + +int SchemaObject::FlatBufferSchema::ParseCheckIndexes(const RawIndexInfos &indexCollect) +{ + for (const auto &entry : indexCollect) { + std::vector indexStrArray{entry.first}; // Entry.first is fieldName at root that was checked valid + const std::string &rawIndexStr = entry.second; + if (IsNotCompositeIndex(rawIndexStr)) { + int errCode = owner_.ParseCheckEachIndexFromStringArray(indexStrArray); + if (errCode != E_OK) { + LOGE("[FBSchema][ParseIndex] Create single-index=%s fail, errCode=%d.", entry.first.c_str(), errCode); + return errCode; + } + description_ += ("INDEX=" + entry.first + ";"); + continue; + } + // Parse other indexField + for (uint32_t curPos = 0; curPos < rawIndexStr.size();) { + uint32_t nextCommaPos = rawIndexStr.find_first_of(',', curPos); + std::string eachIndexField = rawIndexStr.substr(curPos, nextCommaPos - curPos); + eachIndexField = SchemaUtils::Strip(eachIndexField); + if (!eachIndexField.empty()) { // Continuous ',' just ignore + indexStrArray.push_back(eachIndexField); + } + if (nextCommaPos >= rawIndexStr.size()) { // No ',' anymore + break; + } + curPos = nextCommaPos + 1; + } + int errCode = owner_.ParseCheckEachIndexFromStringArray(indexStrArray); + if (errCode != E_OK) { + LOGE("[FBSchema][ParseIndex] Create composite-index=%s, rawStr=%s fail, errCode=%d.", entry.first.c_str(), + rawIndexStr.c_str(), errCode); + return errCode; + } + description_ += ("INDEX=" + entry.first + ";"); + } + if (owner_.schemaIndexes_.size() > SchemaConstant::SCHEMA_INDEX_COUNT_MAX) { + LOGE("[FBSchema][ParseIndex] Index count=%zu exceed limitation.", owner_.schemaIndexes_.size()); + return -E_SCHEMA_PARSE_FAIL; + } + return E_OK; +} + +namespace { +inline bool IsNotEqualNotCompatible(int errCode) +{ + return (errCode != -E_SCHEMA_EQUAL_EXACTLY) && (errCode != -E_SCHEMA_UNEQUAL_COMPATIBLE) && + (errCode != -E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE); +} + +int CompareFieldCount(bool isRoot, uint32_t selfCount, uint32_t otherCount) +{ + if (isRoot) { + if (otherCount < selfCount) { + LOGE("[FBSchema][CompareRoot] RootFieldSize: other=%u less than self=%u.", otherCount, selfCount); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } else { + if (selfCount != otherCount) { + LOGE("[FBSchema][CompareRoot] StructFieldSize: self=%u differ with other=%u.", selfCount, otherCount); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } + return (selfCount == otherCount) ? -E_SCHEMA_EQUAL_EXACTLY : -E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE; +} + +int CompareFieldInfoBesideType(const reflection::Field &selfField, const reflection::Field &otherField, + reflection::BaseType theType) +{ + // Compare offset + if (selfField.offset() != otherField.offset()) { + LOGE("[FBSchema][CompareField] Offset differ: self=%u, other=%u.", selfField.offset(), otherField.offset()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + // Compare default value + if (selfField.default_integer() != otherField.default_integer()) { + LOGE("[FBSchema][CompareField] DefaultInteger differ: self=%lld, other=%lld.", + static_cast(selfField.default_integer()), static_cast(otherField.default_integer())); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + // QUEER: for the same default_real value in fbs, flatbuffer will generate different value in binary ??? + if (!IsDoubleNearlyEqual(selfField.default_real(), otherField.default_real())) { + LOGE("[FBSchema][CompareField] DefaultReal differ: self=%f, other=%f.", selfField.default_real(), + otherField.default_real()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + // Ignore deprecated, Compare required + if (IsRequiredSupportType(theType)) { + if (selfField.required() != otherField.required()) { + LOGE("[FBSchema][CompareField] Require differ: self=%d, other=%d.", selfField.required(), + otherField.required()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } + return -E_SCHEMA_EQUAL_EXACTLY; +} + +int CompareFieldInfo(const reflection::Field &selfField, const reflection::Field &otherField, bool &isStruct) +{ + auto selfType = selfField.type(); + auto otherType = otherField.type(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(selfType); + CHECK_NULL_UNLIKELY_RETURN_ERROR(otherType); + // Compare type + auto selfBaseType = selfType->base_type(); + auto otherBaseType = otherType->base_type(); + if (selfBaseType != otherBaseType) { + LOGE("[FBSchema][CompareField] BaseType differ: self=%s, other=%s.", reflection::EnumNameBaseType(selfBaseType), + reflection::EnumNameBaseType(otherBaseType)); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + if (IsVectorType(selfBaseType)) { + auto selfElementType = selfType->element(); + auto otherElementType = otherType->element(); + if (selfElementType != otherElementType) { + LOGE("[FBSchema][CompareField] ElementType differ: self=%u, other=%u.", selfElementType, otherElementType); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } + if (IsObjectType(selfBaseType)) { + isStruct = true; + } + return CompareFieldInfoBesideType(selfField, otherField, selfBaseType); +} + +// Split from original functions which would be longer than 50 line +int CompareExtraField(const PairConstPointer &bothObject) +{ + // This is private function, the caller guarantee that inputParameter not nullptr + auto selfFields = bothObject.first->fields(); + auto otherFields = bothObject.second->fields(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(selfFields); + CHECK_NULL_UNLIKELY_RETURN_ERROR(otherFields); + // Each field in other not in self, should not be required + for (uint32_t i = 0; i < otherFields->size(); i++) { + auto eachOtherField = (*otherFields)[i]; + CHECK_NULL_UNLIKELY_RETURN_ERROR(eachOtherField); + auto otherName = eachOtherField->name(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(otherName); + auto correspondSelfField = selfFields->LookupByKey(otherName->c_str()); + if (correspondSelfField != nullptr) { + continue; + } + if (eachOtherField->required()) { + LOGE("[FBSchema][CompareDefine] Extra field=%s should not be required.", otherName->c_str()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } + return -E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE; +} +} + +int SchemaObject::FlatBufferSchema::CompareTableOrStructDefine(const PairConstPointer &bothSchema, + const PairConstPointer &bothObject, bool isRoot, std::set &compared) const +{ + // This is private function, the caller guarantee that inputParameter not nullptr + auto selfFields = bothObject.first->fields(); + auto otherFields = bothObject.second->fields(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(selfFields); + CHECK_NULL_UNLIKELY_RETURN_ERROR(otherFields); + int errCode = CompareFieldCount(isRoot, selfFields->size(), otherFields->size()); + if (errCode == -E_SCHEMA_UNEQUAL_INCOMPATIBLE) { + return errCode; + } + // Each field in self should be in other, and they should be same + for (uint32_t i = 0; i < selfFields->size(); i++) { + auto eachSelfField = (*selfFields)[i]; + CHECK_NULL_UNLIKELY_RETURN_ERROR(eachSelfField); + auto selfName = eachSelfField->name(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(selfName); + auto correspondOtherField = otherFields->LookupByKey(selfName->c_str()); + if (correspondOtherField == nullptr) { + LOGE("[FBSchema][CompareDefine] SelfField=%s not found in other.", selfName->c_str()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + bool isStruct = false; + errCode = CompareFieldInfo(*eachSelfField, *correspondOtherField, isStruct); + if (IsNotEqualNotCompatible(errCode)) { + LOGE("[FBSchema][CompareDefine] Compare info of field=%s fail, errCode=%d.", selfName->c_str(), errCode); + return errCode; + } + if (isStruct) { + // Previous parse guarantee that recursion will not be unlimited, don't be afraid. + errCode = CompareStruct(bothSchema, {eachSelfField, correspondOtherField}, compared); + if (IsNotEqualNotCompatible(errCode)) { + return errCode; + } + } + } + if (selfFields->size() == otherFields->size()) { + return -E_SCHEMA_EQUAL_EXACTLY; + } + return CompareExtraField(bothObject); +} + +int SchemaObject::FlatBufferSchema::CompareStruct(const PairConstPointer &bothSchema, + const PairConstPointer &bothField, std::set &compared) const +{ + // This is private function, the caller guarantee that inputParameter not nullptr + auto selfObjects = bothSchema.first->objects(); + auto otherObjects = bothSchema.second->objects(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(selfObjects); + CHECK_NULL_UNLIKELY_RETURN_ERROR(otherObjects); + auto selfType = bothField.first->type(); + auto otherType = bothField.second->type(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(selfType); + CHECK_NULL_UNLIKELY_RETURN_ERROR(otherType); + auto selfObjIndex = selfType->index(); + auto otherObjIndex = otherType->index(); + if (selfObjIndex < 0 || static_cast(selfObjIndex) >= selfObjects->size()) { // Unlikely + return -E_INTERNAL_ERROR; + } + if (otherObjIndex < 0 || static_cast(otherObjIndex) >= otherObjects->size()) { // Unlikely + return -E_INTERNAL_ERROR; + } + auto selfStructObj = (*selfObjects)[selfObjIndex]; + auto otherStructObj = (*otherObjects)[otherObjIndex]; + CHECK_NULL_UNLIKELY_RETURN_ERROR(selfStructObj); + CHECK_NULL_UNLIKELY_RETURN_ERROR(otherStructObj); + // Previous parse can guarantee that they are both struct, no need to check again + auto selfStructName = selfStructObj->name(); + auto otherStructName = otherStructObj->name(); + CHECK_NULL_UNLIKELY_RETURN_ERROR(selfStructName); + CHECK_NULL_UNLIKELY_RETURN_ERROR(otherStructName); + std::string selfName = SchemaUtils::StripNameSpace(selfStructName->str()); + std::string otherName = SchemaUtils::StripNameSpace(otherStructName->str()); + if (selfName != otherName) { + LOGE("[FBSchema][CompareStruct] The field is not of same struct type, self=%s, other=%s.", + selfName.c_str(), otherName.c_str()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + if (compared.count(selfName) != 0) { // This struct-type had already been compared, no need to do recurse again + return -E_SCHEMA_EQUAL_EXACTLY; + } + compared.insert(selfName); + // Compare struct detail + if (selfStructObj->minalign() != otherStructObj->minalign()) { + LOGE("[FBSchema][CompareStruct] The struct minalign differ, self=%d, other=%d.", + selfStructObj->minalign(), otherStructObj->minalign()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + if (selfStructObj->bytesize() != otherStructObj->bytesize()) { + LOGE("[FBSchema][CompareStruct] The struct bytesize differ, self=%d, other=%d.", + selfStructObj->bytesize(), otherStructObj->bytesize()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + // Previous parse guarantee that recursion will not be unlimited, don't be afraid. + return CompareTableOrStructDefine(bothSchema, {selfStructObj, otherStructObj}, false, compared); +} +#else // OMIT_FLATBUFFER +bool SchemaObject::FlatBufferSchema::IsFlatBufferSchema(const std::string &inOriginal, std::string &outDecoded) +{ + (void)inOriginal; + (void)outDecoded; + LOGW("FlatBuffer Omit From Compile."); + return false; +} + +int SchemaObject::FlatBufferSchema::ParseFlatBufferSchema(const std::string &inDecoded) +{ + (void)inDecoded; + owner_.schemaType_ = SchemaType::FLATBUFFER; // For fix compile warning + return -E_NOT_PERMIT; +} + +int SchemaObject::FlatBufferSchema::CompareFlatBufferDefine(const FlatBufferSchema &other) const +{ + (void)other; + return -E_NOT_PERMIT; +} + +int SchemaObject::FlatBufferSchema::VerifyFlatBufferValue(const RawValue &inValue, bool tryNoSizePrefix) const +{ + (void)inValue; + (void)tryNoSizePrefix; + return -E_NOT_PERMIT; +} + +int SchemaObject::FlatBufferSchema::ExtractFlatBufferValue(RawString inPath, const RawValue &inValue, + TypeValue &outExtract, bool tryNoSizePrefix) const +{ + (void)inPath; + (void)inValue; + (void)outExtract; + (void)tryNoSizePrefix; + return -E_NOT_PERMIT; +} +#endif // OMIT_FLATBUFFER +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/hash.cpp b/mock/distributeddb/common/src/hash.cpp new file mode 100644 index 00000000..94c9f1b2 --- /dev/null +++ b/mock/distributeddb/common/src/hash.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "hash.h" + +namespace DistributedDB { +uint64_t Hash::HashFunc(const std::string &input) +{ + uint64_t hash = 0; + size_t idx = 0; + + for (idx = 0; idx < input.size(); idx++) { + hash = (hash * PRIME_SEED) + input.at(idx); + } + + return hash; +} + +uint32_t Hash::Hash32Func(const std::string &input) +{ + uint32_t hash = 0; + size_t idx = 0; + + for (idx = 0; idx < input.size(); idx++) { + hash = (hash << 4) + input.at(idx); // left shift the lowest 4 bits for 4 bits. + uint32_t x = (hash & 0xf0000000); + if (x != 0) { + hash ^= (x >> 24); // right shift the high byte for 24 bits. + } + hash &= ~x; + } + return (hash & 0x7fffffff); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/json_object.cpp b/mock/distributeddb/common/src/json_object.cpp new file mode 100644 index 00000000..eeaf9965 --- /dev/null +++ b/mock/distributeddb/common/src/json_object.cpp @@ -0,0 +1,932 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "json_object.h" +#include +#include +#include +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +#ifndef OMIT_JSON +namespace { + const uint32_t MAX_NEST_DEPTH = 100; +#ifdef JSONCPP_USE_BUILDER + const int JSON_VALUE_PRECISION = 16; + const std::string JSON_CONFIG_INDENTATION = "indentation"; + const std::string JSON_CONFIG_COLLECT_COMMENTS = "collectComments"; + const std::string JSON_CONFIG_PRECISION = "precision"; +#endif +} +uint32_t JsonObject::maxNestDepth_ = MAX_NEST_DEPTH; + +uint32_t JsonObject::SetMaxNestDepth(uint32_t nestDepth) +{ + uint32_t preValue = maxNestDepth_; + // No need to check the reasonability, only test code will use this method + maxNestDepth_ = nestDepth; + return preValue; +} + +uint32_t JsonObject::CalculateNestDepth(const std::string &inString, int &errCode) +{ + auto begin = reinterpret_cast(inString.c_str()); + auto end = begin + inString.size(); + return CalculateNestDepth(begin, end, errCode); +} + +uint32_t JsonObject::CalculateNestDepth(const uint8_t *dataBegin, const uint8_t *dataEnd, int &errCode) +{ + if (dataBegin == nullptr || dataEnd == nullptr || dataBegin >= dataEnd) { + errCode = -E_INVALID_ARGS; + return maxNestDepth_ + 1; // return a invalid depth + } + bool isInString = false; + uint32_t maxDepth = 0; + uint32_t objectDepth = 0; + uint32_t arrayDepth = 0; + uint32_t numOfEscape = 0; + + for (auto ptr = dataBegin; ptr < dataEnd; ptr++) { + if (*ptr == '"' && numOfEscape % 2 == 0) { // 2 used to detect parity + isInString = !isInString; + continue; + } + if (!isInString) { + if (*ptr == '{') { + objectDepth++; + maxDepth = std::max(maxDepth, objectDepth + arrayDepth); + } + if (*ptr == '}') { + objectDepth = ((objectDepth > 0) ? (objectDepth - 1) : 0); + } + if (*ptr == '[') { + arrayDepth++; + maxDepth = std::max(maxDepth, objectDepth + arrayDepth); + } + if (*ptr == ']') { + arrayDepth = ((arrayDepth > 0) ? (arrayDepth - 1) : 0); + } + } + numOfEscape = ((*ptr == '\\') ? (numOfEscape + 1) : 0); + } + return maxDepth; +} + +JsonObject::JsonObject(const JsonObject &other) +{ + isValid_ = other.isValid_; + value_ = other.value_; +} + +JsonObject& JsonObject::operator=(const JsonObject &other) +{ + if (&other != this) { + isValid_ = other.isValid_; + value_ = other.value_; + } + return *this; +} + +JsonObject::JsonObject(const Json::Value &value) : isValid_(true), value_(value) +{ +} + +int JsonObject::Parse(const std::string &inString) +{ + // The jsoncpp lib parser in strict mode will still regard root type jsonarray as valid, but we require jsonobject + if (isValid_) { + LOGE("[Json][Parse] Already Valid."); + return -E_NOT_PERMIT; + } + int errCode = E_OK; + uint32_t nestDepth = CalculateNestDepth(inString, errCode); + if (errCode != E_OK || nestDepth > maxNestDepth_) { + LOGE("[Json][Parse] Json calculate nest depth failed %d, depth=%u exceed max allowed=%u.", errCode, nestDepth, + maxNestDepth_); + return -E_JSON_PARSE_FAIL; + } +#ifdef JSONCPP_USE_BUILDER + JSONCPP_STRING errs; + Json::CharReaderBuilder builder; + Json::CharReaderBuilder::strictMode(&builder.settings_); + builder[JSON_CONFIG_COLLECT_COMMENTS] = false; + std::unique_ptr const jsonReader(builder.newCharReader()); + + auto begin = reinterpret_cast(inString.c_str()); + auto end = reinterpret_cast(inString.c_str() + inString.length()); + if (!jsonReader->parse(begin, end, &value_, &errs)) { + value_ = Json::Value(); + LOGE("[Json][Parse] Parse string to JsonValue fail, reason=%s.", errs.c_str()); + return -E_JSON_PARSE_FAIL; + } +#else + Json::Reader reader(Json::Features::strictMode()); + if (!reader.parse(inString, value_, false)) { + value_ = Json::Value(); + LOGE("[Json][Parse] Parse string to JsonValue fail, reason=%s.", reader.getFormattedErrorMessages().c_str()); + return -E_JSON_PARSE_FAIL; + } +#endif + // The jsoncpp lib parser in strict mode will still regard root type jsonarray as valid, but we require jsonobject + if (value_.type() != Json::ValueType::objectValue) { + value_ = Json::Value(); + LOGE("[Json][Parse] Not an object at root."); + return -E_JSON_PARSE_FAIL; + } + isValid_ = true; + return E_OK; +} + +int JsonObject::Parse(const std::vector &inData) +{ + if (inData.empty()) { + return -E_INVALID_ARGS; + } + return Parse(inData.data(), inData.data() + inData.size()); +} + +int JsonObject::Parse(const uint8_t *dataBegin, const uint8_t *dataEnd) +{ + if (isValid_) { + LOGE("[Json][Parse] Already Valid."); + return -E_NOT_PERMIT; + } + if (dataBegin == nullptr || dataEnd == nullptr || dataBegin >= dataEnd) { + return -E_INVALID_ARGS; + } + int errCode = E_OK; + uint32_t nestDepth = CalculateNestDepth(dataBegin, dataEnd, errCode); + if (errCode != E_OK || nestDepth > maxNestDepth_) { + LOGE("[Json][Parse] Json calculate nest depth failed %d, depth=%u exceed max allowed=%u.", errCode, nestDepth, + maxNestDepth_); + return -E_JSON_PARSE_FAIL; + } +#ifdef JSONCPP_USE_BUILDER + auto begin = reinterpret_cast(dataBegin); + auto end = reinterpret_cast(dataEnd); + + JSONCPP_STRING errs; + Json::CharReaderBuilder builder; + Json::CharReaderBuilder::strictMode(&builder.settings_); + builder[JSON_CONFIG_COLLECT_COMMENTS] = false; + std::unique_ptr const jsonReader(builder.newCharReader()); + // The endDoc parameter of reader::parse refer to the byte after the string itself + if (!jsonReader->parse(begin, end, &value_, &errs)) { + value_ = Json::Value(); + LOGE("[Json][Parse] Parse dataRange to JsonValue fail, reason=%s.", errs.c_str()); + return -E_JSON_PARSE_FAIL; + } +#else + Json::Reader reader(Json::Features::strictMode()); + auto begin = reinterpret_cast(dataBegin); + auto end = reinterpret_cast(dataEnd); + // The endDoc parameter of reader::parse refer to the byte after the string itself + if (!reader.parse(begin, end, value_, false)) { + value_ = Json::Value(); + LOGE("[Json][Parse] Parse dataRange to JsonValue fail, reason=%s.", reader.getFormattedErrorMessages().c_str()); + return -E_JSON_PARSE_FAIL; + } +#endif + // The jsoncpp lib parser in strict mode will still regard root type jsonarray as valid, but we require jsonobject + if (value_.type() != Json::ValueType::objectValue) { + value_ = Json::Value(); + LOGE("[Json][Parse] Not an object at root."); + return -E_JSON_PARSE_FAIL; + } + isValid_ = true; + return E_OK; +} + +bool JsonObject::IsValid() const +{ + return isValid_; +} + +std::string JsonObject::ToString() const +{ + if (!isValid_) { + LOGE("[Json][ToString] Not Valid Yet."); + return std::string(); + } +#ifdef JSONCPP_USE_BUILDER + Json::StreamWriterBuilder writerBuilder; + writerBuilder[JSON_CONFIG_INDENTATION] = ""; + writerBuilder[JSON_CONFIG_PRECISION] = JSON_VALUE_PRECISION; + std::unique_ptr const jsonWriter(writerBuilder.newStreamWriter()); + std::stringstream ss; + jsonWriter->write(value_, &ss); + // The endingLineFeedSymbol is left empty by default. + return ss.str(); +#else + Json::FastWriter fastWriter; + // Call omitEndingLineFeed to let JsonCpp not append an \n at the end of string. If not doing so, when passing a + // minified jsonString, the result of this function will be one byte longer then the original, which may cause the + // result checked as length invalid by upper logic when the original length is just at the limitation boundary. + fastWriter.omitEndingLineFeed(); + return fastWriter.write(value_); +#endif +} + +bool JsonObject::IsFieldPathExist(const FieldPath &inPath) const +{ + if (!isValid_) { + LOGE("[Json][isExisted] Not Valid Yet."); + return false; + } + int errCode = E_OK; + (void)GetJsonValueByFieldPath(inPath, errCode); // Ignore return const reference + return (errCode == E_OK); +} + +int JsonObject::GetFieldTypeByFieldPath(const FieldPath &inPath, FieldType &outType) const +{ + if (!isValid_) { + LOGE("[Json][GetType] Not Valid Yet."); + return -E_NOT_PERMIT; + } + int errCode = E_OK; + const Json::Value &valueNode = GetJsonValueByFieldPath(inPath, errCode); + if (errCode != E_OK) { + return errCode; + } + return GetFieldTypeByJsonValue(valueNode, outType); +} + +int JsonObject::GetFieldValueByFieldPath(const FieldPath &inPath, FieldValue &outValue) const +{ + if (!isValid_) { + LOGE("[Json][GetValue] Not Valid Yet."); + return -E_NOT_PERMIT; + } + int errCode = E_OK; + const Json::Value &valueNode = GetJsonValueByFieldPath(inPath, errCode); + if (errCode != E_OK) { + return errCode; + } + FieldType valueType; + errCode = GetFieldTypeByJsonValue(valueNode, valueType); + if (errCode != E_OK) { + return errCode; + } + switch (valueType) { + case FieldType::LEAF_FIELD_BOOL: + outValue.boolValue = valueNode.asBool(); + break; + case FieldType::LEAF_FIELD_INTEGER: + outValue.integerValue = valueNode.asInt(); + break; + case FieldType::LEAF_FIELD_LONG: + outValue.longValue = valueNode.asInt64(); + break; + case FieldType::LEAF_FIELD_DOUBLE: + outValue.doubleValue = valueNode.asDouble(); + break; + case FieldType::LEAF_FIELD_STRING: + outValue.stringValue = valueNode.asString(); + break; + default: + return -E_NOT_SUPPORT; + } + return E_OK; +} + +int JsonObject::GetSubFieldPath(const FieldPath &inPath, std::set &outSubPath) const +{ + if (!isValid_) { + LOGE("[Json][GetSubPath] Not Valid Yet."); + return -E_NOT_PERMIT; + } + int errCode = E_OK; + const Json::Value &valueNode = GetJsonValueByFieldPath(inPath, errCode); + if (errCode != E_OK) { + return errCode; + } + if (valueNode.type() != Json::ValueType::objectValue) { + return -E_NOT_SUPPORT; + } + // Note: the subFields JsonCpp returnout will be different from each other + std::vector subFields = valueNode.getMemberNames(); + for (const auto &eachSubField : subFields) { + FieldPath eachSubPath = inPath; + eachSubPath.push_back(eachSubField); + outSubPath.insert(eachSubPath); + } + return E_OK; +} + +int JsonObject::GetSubFieldPath(const std::set &inPath, std::set &outSubPath) const +{ + for (const auto &eachPath : inPath) { + int errCode = GetSubFieldPath(eachPath, outSubPath); + if (errCode != E_OK) { + return errCode; + } + } + return E_OK; +} + +int JsonObject::GetSubFieldPathAndType(const FieldPath &inPath, std::map &outSubPathType) const +{ + if (!isValid_) { + LOGE("[Json][GetSubPathType] Not Valid Yet."); + return -E_NOT_PERMIT; + } + int errCode = E_OK; + const Json::Value &valueNode = GetJsonValueByFieldPath(inPath, errCode); + if (errCode != E_OK) { + return errCode; + } + if (valueNode.type() != Json::ValueType::objectValue) { + return -E_NOT_SUPPORT; + } + // Note: the subFields JsonCpp returnout will be different from each other + std::vector subFields = valueNode.getMemberNames(); + for (const auto &eachSubField : subFields) { + FieldPath eachSubPath = inPath; + eachSubPath.push_back(eachSubField); + FieldType eachSubType; + errCode = GetFieldTypeByJsonValue(valueNode[eachSubField], eachSubType); + if (errCode != E_OK) { + return errCode; + } + outSubPathType[eachSubPath] = eachSubType; + } + return E_OK; +} + +int JsonObject::GetSubFieldPathAndType(const std::set &inPath, + std::map &outSubPathType) const +{ + for (const auto &eachPath : inPath) { + int errCode = GetSubFieldPathAndType(eachPath, outSubPathType); + if (errCode != E_OK) { + return errCode; + } + } + return E_OK; +} + +int JsonObject::GetArraySize(const FieldPath &inPath, uint32_t &outSize) const +{ + if (!isValid_) { + LOGE("[Json][GetArraySize] Not Valid Yet."); + return -E_NOT_PERMIT; + } + int errCode = E_OK; + const Json::Value &valueNode = GetJsonValueByFieldPath(inPath, errCode); + if (errCode != E_OK) { + return errCode; + } + if (valueNode.type() != Json::ValueType::arrayValue) { + return -E_NOT_SUPPORT; + } + outSize = valueNode.size(); + return E_OK; +} + +int JsonObject::GetArrayContentOfStringOrStringArray(const FieldPath &inPath, + std::vector> &outContent) const +{ + if (!isValid_) { + LOGE("[Json][GetArrayContent] Not Valid Yet."); + return -E_NOT_PERMIT; + } + int errCode = E_OK; + const Json::Value &valueNode = GetJsonValueByFieldPath(inPath, errCode); + if (errCode != E_OK) { + LOGW("[Json][GetArrayContent] Get JsonValue Fail=%d.", errCode); + return errCode; + } + if (valueNode.type() != Json::ValueType::arrayValue) { + LOGE("[Json][GetArrayContent] Not an array."); + return -E_NOT_SUPPORT; + } + for (uint32_t index = 0; index < valueNode.size(); index++) { + const Json::Value &eachArrayItem = valueNode[index]; + if (eachArrayItem.isString()) { + outContent.emplace_back(std::vector({eachArrayItem.asString()})); + continue; + } + if (eachArrayItem.isArray()) { + if (eachArrayItem.empty()) { + continue; // Ignore empty array-type member + } + outContent.emplace_back(std::vector()); + errCode = GetStringArrayContentByJsonValue(eachArrayItem, outContent.back()); + if (errCode == E_OK) { + continue; // Everything ok + } + } + // If reach here, then something is not ok + outContent.clear(); + LOGE("[Json][GetArrayContent] Not string or array or GetStringArray fail=%d at index=%u.", errCode, index); + return -E_NOT_SUPPORT; + } + return E_OK; +} + +namespace { +bool InsertFieldCheckParameter(const FieldPath &inPath, FieldType inType, const FieldValue &inValue, + uint32_t maxNestDepth) +{ + if (inPath.empty() || inPath.size() > maxNestDepth || inType == FieldType::LEAF_FIELD_ARRAY || + inType == FieldType::INTERNAL_FIELD_OBJECT) { + return false; + } + // Infinite double not support + if (inType == FieldType::LEAF_FIELD_DOUBLE && !std::isfinite(inValue.doubleValue)) { + return false; + } + return true; +} + +void LeafJsonNodeAppendValue(Json::Value &leafNode, FieldType inType, const FieldValue &inValue) +{ + if (inType == FieldType::LEAF_FIELD_STRING) { + leafNode.append(Json::Value(inValue.stringValue)); + } +} + +// Function design for InsertField call on an null-type Json::Value +void LeafJsonNodeAssignValue(Json::Value &leafNode, FieldType inType, const FieldValue &inValue) +{ + switch (inType) { + case FieldType::LEAF_FIELD_BOOL: + leafNode = Json::Value(inValue.boolValue); + break; + case FieldType::LEAF_FIELD_INTEGER: + // Cast to Json::Int to avoid "ambiguous call of overloaded function" + leafNode = Json::Value(static_cast(inValue.integerValue)); + break; + case FieldType::LEAF_FIELD_LONG: + // Cast to Json::Int64 to avoid "ambiguous call of overloaded function" + leafNode = Json::Value(static_cast(inValue.longValue)); + break; + case FieldType::LEAF_FIELD_DOUBLE: + leafNode = Json::Value(inValue.doubleValue); + break; + case FieldType::LEAF_FIELD_STRING: + leafNode = Json::Value(inValue.stringValue); + break; + case FieldType::LEAF_FIELD_OBJECT: + leafNode = Json::Value(Json::ValueType::objectValue); + break; + default: + // For LEAF_FIELD_NULL, Do nothing. + // For LEAF_FIELD_ARRAY and INTERNAL_FIELD_OBJECT, Not Support, had been excluded by InsertField + return; + } +} +} + +// move the nearest to the leaf of inPath, if not exist, will create it, else it should be an array object. +int JsonObject::MoveToPath(const FieldPath &inPath, Json::Value *&exact, Json::Value *&nearest) +{ + uint32_t nearDepth = 0; + int errCode = LocateJsonValueByFieldPath(inPath, exact, nearest, nearDepth); + if (errCode != -E_NOT_FOUND) { // Path already exist and it's not an array object + return -E_JSON_INSERT_PATH_EXIST; + } + // nearDepth 0 represent for root value. nearDepth equal to inPath.size indicate an exact path match + if (nearest == nullptr || nearDepth >= inPath.size()) { // Impossible + return -E_INTERNAL_ERROR; + } + if (nearest->type() != Json::ValueType::objectValue) { // path ends with type not object + return -E_JSON_INSERT_PATH_CONFLICT; + } + // Use nearDepth as startIndex pointing to the first field that lacked + for (uint32_t lackFieldIndex = nearDepth; lackFieldIndex < inPath.size(); lackFieldIndex++) { + // The new JsonValue is null-type, we can safely add members to an null-type JsonValue which will turn into + // object-type after member adding. Then move "nearest" to point to the new JsonValue. + nearest = &((*nearest)[inPath[lackFieldIndex]]); + } + return E_OK; +} + +int JsonObject::InsertField(const FieldPath &inPath, const JsonObject &inValue, bool isAppend) +{ + if (inPath.empty() || inPath.size() > maxNestDepth_ || !inValue.IsValid()) { + return -E_INVALID_ARGS; + } + if (!isValid_) { + value_ = Json::Value(Json::ValueType::objectValue); + isValid_ = true; + } + Json::Value *exact = nullptr; + Json::Value *nearest = nullptr; + int errCode = MoveToPath(inPath, exact, nearest); + if (errCode != E_OK) { + return errCode; + } + LOGD("nearest type is %d", nearest->type()); + if (isAppend || nearest->type() == Json::ValueType::arrayValue) { + nearest->append(inValue.value_); + } else { + *nearest = inValue.value_; + } + return E_OK; +} + +int JsonObject::InsertField(const FieldPath &inPath, FieldType inType, const FieldValue &inValue, bool isAppend) +{ + if (!InsertFieldCheckParameter(inPath, inType, inValue, maxNestDepth_)) { + return -E_INVALID_ARGS; + } + if (!isValid_) { + // Insert on invalid object never fail after parameter check ok, so here no need concern rollback. + value_ = Json::Value(Json::ValueType::objectValue); + isValid_ = true; + } + Json::Value *exact = nullptr; + Json::Value *nearest = nullptr; + int errCode = MoveToPath(inPath, exact, nearest); + if (errCode != E_OK) { + return errCode; + } + // Here "nearest" points to the JsonValue(null-type now) corresponding to the last field + if (isAppend || nearest->type() == Json::ValueType::arrayValue) { + LeafJsonNodeAppendValue(*nearest, inType, inValue); + } else { + LeafJsonNodeAssignValue(*nearest, inType, inValue); + } + return E_OK; +} + +int JsonObject::DeleteField(const FieldPath &inPath) +{ + if (!isValid_) { + LOGE("[Json][DeleteField] Not Valid Yet."); + return -E_NOT_PERMIT; + } + if (inPath.empty()) { + return -E_INVALID_ARGS; + } + Json::Value *exact = nullptr; + Json::Value *nearest = nullptr; + uint32_t nearDepth = 0; + int errCode = LocateJsonValueByFieldPath(inPath, exact, nearest, nearDepth); + if (errCode != E_OK) { // Path not exist + return -E_JSON_DELETE_PATH_NOT_FOUND; + } + // nearDepth should be equal to inPath.size() - 1, because nearest is at the parent path of inPath + if (nearest == nullptr || nearest->type() != Json::ValueType::objectValue || nearDepth != inPath.size() - 1) { + return -E_INTERNAL_ERROR; // Impossible + } + // Remove member from nearest, ignore returned removed Value, use nearDepth as index pointing to last field of path. + (void)nearest->removeMember(inPath[nearDepth]); + return E_OK; +} + +int JsonObject::GetStringArrayContentByJsonValue(const Json::Value &value, + std::vector &outStringArray) const +{ + if (value.type() != Json::ValueType::arrayValue) { + LOGE("[Json][GetStringArrayByValue] Not an array."); + return -E_NOT_SUPPORT; + } + for (uint32_t index = 0; index < value.size(); index++) { + const Json::Value &eachArrayItem = value[index]; + if (!eachArrayItem.isString()) { + LOGE("[Json][GetStringArrayByValue] Index=%u in Array is not string.", index); + outStringArray.clear(); + return -E_NOT_SUPPORT; + } + outStringArray.push_back(eachArrayItem.asString()); + } + return E_OK; +} + +int JsonObject::GetFieldTypeByJsonValue(const Json::Value &value, FieldType &outType) const +{ + Json::ValueType valueType = value.type(); + switch (valueType) { + case Json::ValueType::nullValue: + outType = FieldType::LEAF_FIELD_NULL; + break; + case Json::ValueType::booleanValue: + outType = FieldType::LEAF_FIELD_BOOL; + break; + // The case intValue and uintValue cover from INT64_MIN to UINT64_MAX. Inside this range, isInt() take range + // from INT32_MIN to INT32_MAX, which should be regard as LEAF_FIELD_INTEGER; isInt64() take range from + // INT64_MIN to INT64_MAX, which should be regard as LEAF_FIELD_LONG if it is not LEAF_FIELD_INTEGER; + // INT64_MAX + 1 to UINT64_MAX will be regard as LEAF_FIELD_DOUBLE, therefore lose its precision when read out + // as double value. + case Json::ValueType::intValue: + case Json::ValueType::uintValue: + if (value.isInt()) { + outType = FieldType::LEAF_FIELD_INTEGER; + } else if (value.isInt64()) { + outType = FieldType::LEAF_FIELD_LONG; + } else { + outType = FieldType::LEAF_FIELD_DOUBLE; // The isDouble() judge is always true in this case. + } + break; + // Integral value beyond range INT64_MIN to UINT64_MAX will be recognized as realValue and lose its precision. + // Value in scientific notation or has decimal point will be recognized as realValue without exception, + // no matter whether the value is large or small, no matter with or without non-zero decimal part. + // In a word, when regard as DOUBLE type, a value can not guarantee its presision + case Json::ValueType::realValue: + // The isDouble() judge is always true in this case. A value exceed double range is not support. + outType = FieldType::LEAF_FIELD_DOUBLE; + if (!std::isfinite(value.asDouble())) { + LOGE("[Json][GetTypeByJson] Infinite double not support."); + return -E_NOT_SUPPORT; + } + break; + case Json::ValueType::stringValue: + outType = FieldType::LEAF_FIELD_STRING; + break; + case Json::ValueType::arrayValue: + outType = FieldType::LEAF_FIELD_ARRAY; + break; + case Json::ValueType::objectValue: + if (value.getMemberNames().empty()) { + outType = FieldType::LEAF_FIELD_OBJECT; + break; + } + outType = FieldType::INTERNAL_FIELD_OBJECT; + break; + default: + LOGE("[Json][GetTypeByJson] no such type."); + return -E_NOT_SUPPORT; + } + return E_OK; +} + +const Json::Value &JsonObject::GetJsonValueByFieldPath(const FieldPath &inPath, int &errCode) const +{ + // Root path always exist + if (inPath.empty()) { + errCode = E_OK; + return value_; + } + const Json::Value *valueNode = &value_; + for (const auto &eachPathSegment : inPath) { + if ((valueNode->type() != Json::ValueType::objectValue) || (!valueNode->isMember(eachPathSegment))) { + // Current JsonValue is not an object, or no such member field + errCode = -E_INVALID_PATH; + return value_; + } + valueNode = &((*valueNode)[eachPathSegment]); + } + errCode = E_OK; + return *valueNode; +} + +int JsonObject::LocateJsonValueByFieldPath(const FieldPath &inPath, Json::Value *&exact, + Json::Value *&nearest, uint32_t &nearDepth) +{ + if (!isValid_) { + return -E_NOT_PERMIT; + } + exact = &value_; + nearest = &value_; + nearDepth = 0; + if (inPath.empty()) { + return E_OK; + } + for (const auto &eachPathSegment : inPath) { + nearest = exact; // Let "nearest" trace "exact" before "exact" go deeper + if (nearest != &value_) { + nearDepth++; // For each "nearest" trace up "exact", increase nearDepth to indicate where it is. + } + if ((exact->type() != Json::ValueType::objectValue) || (!exact->isMember(eachPathSegment))) { + // "exact" is not an object, or no such member field + exact = nullptr; // Set "exact" to nullptr indicate exact path not exist + return -E_NOT_FOUND; + } + exact = &((*exact)[eachPathSegment]); // "exact" go deeper + } + if (exact->type() == Json::ValueType::arrayValue) { + return -E_NOT_FOUND; // could append value if path is an array field. + } + // Here, JsonValue exist at exact path, "nearest" is "exact" parent. + return E_OK; +} + +int JsonObject::GetObjectArrayByFieldPath(const FieldPath &inPath, std::vector &outArray) const +{ + if (!isValid_) { + LOGE("[Json][GetValue] Not Valid Yet."); + return -E_NOT_PERMIT; + } + int errCode = E_OK; + const Json::Value &valueNode = GetJsonValueByFieldPath(inPath, errCode); + if (errCode != E_OK) { + LOGE("[Json][GetValue] Get json value failed. %d", errCode); + return errCode; + } + + if (!valueNode.isArray()) { + LOGE("[Json][GetValue] Not Array type."); + return -E_NOT_PERMIT; + } + for (Json::ArrayIndex i = 0; i < valueNode.size(); ++i) { + outArray.emplace_back(JsonObject(valueNode[i])); + } + return E_OK; +} + +int JsonObject::GetObjectByFieldPath(const FieldPath &inPath, JsonObject &outObj) const +{ + if (!isValid_) { + LOGE("[Json][GetValue] Not Valid Yet."); + return -E_NOT_PERMIT; + } + int errCode = E_OK; + const Json::Value &valueNode = GetJsonValueByFieldPath(inPath, errCode); + if (errCode != E_OK) { + LOGE("[Json][GetValue] Get json value failed. %d", errCode); + return errCode; + } + + if (!valueNode.isObject()) { + LOGE("[Json][GetValue] Not Object type."); + return -E_NOT_PERMIT; + } + outObj = JsonObject(valueNode); + return E_OK; +} + +int JsonObject::GetStringArrayByFieldPath(const FieldPath &inPath, std::vector &outArray) const +{ + if (!isValid_) { + LOGE("[Json][GetValue] Not Valid Yet."); + return -E_NOT_PERMIT; + } + int errCode = E_OK; + const Json::Value &valueNode = GetJsonValueByFieldPath(inPath, errCode); + if (errCode != E_OK) { + LOGE("[Json][GetValue] Get json value failed. %d", errCode); + return errCode; + } + + return GetStringArrayContentByJsonValue(valueNode, outArray); +} + +#else // OMIT_JSON +uint32_t JsonObject::SetMaxNestDepth(uint32_t nestDepth) +{ + (void)nestDepth; + return 0; +} + +uint32_t JsonObject::CalculateNestDepth(const std::string &inString, int &errCode) +{ + (void)inString; + (void)errCode; + return 0; +} + +uint32_t JsonObject::CalculateNestDepth(const uint8_t *dataBegin, const uint8_t *dataEnd, int &errCode) +{ + (void)dataBegin; + (void)dataEnd; + (void)errCode; + return 0; +} + +JsonObject::JsonObject(const JsonObject &other) = default; + +JsonObject& JsonObject::operator=(const JsonObject &other) = default; + +int JsonObject::Parse(const std::string &inString) +{ + (void)inString; + LOGW("[Json][Parse] Json Omit From Compile."); + return -E_NOT_PERMIT; +} + +int JsonObject::Parse(const std::vector &inData) +{ + (void)inData; + LOGW("[Json][Parse] Json Omit From Compile."); + return -E_NOT_PERMIT; +} + +int JsonObject::Parse(const uint8_t *dataBegin, const uint8_t *dataEnd) +{ + (void)dataBegin; + (void)dataEnd; + LOGW("[Json][Parse] Json Omit From Compile."); + return -E_NOT_PERMIT; +} + +bool JsonObject::IsValid() const +{ + return false; +} + +std::string JsonObject::ToString() const +{ + return std::string(); +} + +bool JsonObject::IsFieldPathExist(const FieldPath &inPath) const +{ + (void)inPath; + return false; +} + +int JsonObject::GetFieldTypeByFieldPath(const FieldPath &inPath, FieldType &outType) const +{ + (void)inPath; + (void)outType; + return -E_NOT_PERMIT; +} + +int JsonObject::GetFieldValueByFieldPath(const FieldPath &inPath, FieldValue &outValue) const +{ + (void)inPath; + (void)outValue; + return -E_NOT_PERMIT; +} + +int JsonObject::GetSubFieldPath(const FieldPath &inPath, std::set &outSubPath) const +{ + (void)inPath; + (void)outSubPath; + return -E_NOT_PERMIT; +} + +int JsonObject::GetSubFieldPath(const std::set &inPath, std::set &outSubPath) const +{ + (void)inPath; + (void)outSubPath; + return -E_NOT_PERMIT; +} + +int JsonObject::GetSubFieldPathAndType(const FieldPath &inPath, std::map &outSubPathType) const +{ + (void)inPath; + (void)outSubPathType; + return -E_NOT_PERMIT; +} + +int JsonObject::GetSubFieldPathAndType(const std::set &inPath, + std::map &outSubPathType) const +{ + (void)inPath; + (void)outSubPathType; + return -E_NOT_PERMIT; +} + +int JsonObject::GetArraySize(const FieldPath &inPath, uint32_t &outSize) const +{ + (void)inPath; + (void)outSize; + return -E_NOT_PERMIT; +} + +int JsonObject::GetArrayContentOfStringOrStringArray(const FieldPath &inPath, + std::vector> &outContent) const +{ + (void)inPath; + (void)outContent; + return -E_NOT_PERMIT; +} + +int JsonObject::InsertField(const FieldPath &inPath, FieldType inType, const FieldValue &inValue) +{ + (void)inPath; + (void)inType; + (void)inValue; + return -E_NOT_PERMIT; +} + +int JsonObject::InsertField(const FieldPath &inPath, const JsonObject &inValue, bool isAppend = false) +{ + (void)inPath; + (void)inValue; + (void)isAppend; + return -E_NOT_PERMIT; +} + +int JsonObject::DeleteField(const FieldPath &inPath) +{ + (void)inPath; + return -E_NOT_PERMIT; +} + +int JsonObject::GetArrayValueByFieldPath(const FieldPath &inPath, JsonObject &outArray) const +{ + (void)inPath; + (void)outArray; + return -E_NOT_PERMIT; +} +#endif // OMIT_JSON +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/lock_status_observer.cpp b/mock/distributeddb/common/src/lock_status_observer.cpp new file mode 100644 index 00000000..c26da197 --- /dev/null +++ b/mock/distributeddb/common/src/lock_status_observer.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "lock_status_observer.h" + +#include "log_print.h" + +namespace DistributedDB { +LockStatusObserver::LockStatusObserver() + : lockStatusChangedNotifier_(nullptr), + isStarted_(false) +{} + +LockStatusObserver::~LockStatusObserver() +{ + Stop(); +} + +int LockStatusObserver::Start() +{ + if (isStarted_) { + return E_OK; + } + + int errCode = PrepareNotifierChain(); + if (errCode != E_OK) { + LOGE("PrepareNotifierChain failed, errorCode = %d", errCode); + return errCode; + } + isStarted_ = true; + return E_OK; +} + +bool LockStatusObserver::IsStarted() const +{ + return isStarted_; +} + +void LockStatusObserver::Stop() +{ + if (!isStarted_) { + return; + } + + lockStatusChangedNotifier_->UnRegisterEventType(LOCK_STATUS_CHANGE_EVENT); + RefObject::KillAndDecObjRef(lockStatusChangedNotifier_); + lockStatusChangedNotifier_ = nullptr; + isStarted_ = false; +} + +int LockStatusObserver::PrepareNotifierChain() +{ + if (lockStatusChangedNotifier_ != nullptr) { + return E_OK; + } + + lockStatusChangedNotifier_ = new (std::nothrow) NotificationChain(); + if (lockStatusChangedNotifier_ == nullptr) { + LOGE("lockStatusChangedNotifier_ is nullptr"); + return -E_OUT_OF_MEMORY; + } + + int errCode = lockStatusChangedNotifier_->RegisterEventType(LOCK_STATUS_CHANGE_EVENT); + if (errCode != E_OK) { + LOGE("RegisterEventType failed, errCode = %d", errCode); + RefObject::KillAndDecObjRef(lockStatusChangedNotifier_); + lockStatusChangedNotifier_ = nullptr; + } + return errCode; +} + +NotificationChain::Listener *LockStatusObserver::RegisterLockStatusChangedLister(const LockStatusNotifier &action, + int &errCode) const +{ + if (lockStatusChangedNotifier_ == nullptr) { + LOGE("lockStatusChangedNotifier_ is nullptr"); + errCode = -E_NOT_INIT; + return nullptr; + } + + if (!action) { + LOGE("action is nullptr"); + errCode = -E_INVALID_ARGS; + return nullptr; + } + return lockStatusChangedNotifier_->RegisterListener(LOCK_STATUS_CHANGE_EVENT, action, nullptr, errCode); +} + +void LockStatusObserver::OnStatusChange(bool isLocked) const +{ + if (lockStatusChangedNotifier_ == nullptr) { + LOGE("lockStatusChangedNotifier_ is nullptr"); + return; + } + lockStatusChangedNotifier_->NotifyEvent(LOCK_STATUS_CHANGE_EVENT, &isLocked); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/lock_status_observer.h b/mock/distributeddb/common/src/lock_status_observer.h new file mode 100644 index 00000000..ae221b5f --- /dev/null +++ b/mock/distributeddb/common/src/lock_status_observer.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 LOCK_STATUS_OBSERVER_H +#define LOCK_STATUS_OBSERVER_H + +#include "notification_chain.h" +#include "runtime_context.h" + +namespace DistributedDB { +class LockStatusObserver final { +public: + LockStatusObserver(); + ~LockStatusObserver(); + DISABLE_COPY_ASSIGN_MOVE(LockStatusObserver); + int Start(); + void Stop(); + void OnStatusChange(bool isLocked) const; + bool IsStarted() const; + NotificationChain::Listener *RegisterLockStatusChangedLister(const LockStatusNotifier &action, int &errCode) const; + +private: + static const EventType LOCK_STATUS_CHANGE_EVENT = 2; + int PrepareNotifierChain(); + NotificationChain *lockStatusChangedNotifier_; + bool isStarted_; +}; +} + +#endif diff --git a/mock/distributeddb/common/src/log_print.cpp b/mock/distributeddb/common/src/log_print.cpp new file mode 100644 index 00000000..43132b5d --- /dev/null +++ b/mock/distributeddb/common/src/log_print.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "log_print.h" + +#include +#include +#include +#include + +#include "securec.h" +#include "platform_specific.h" +#include "hilog/log.h" + +namespace DistributedDB { +Logger *Logger::logHandler = nullptr; +const std::string Logger::PRIVATE_TAG = "s{private}"; + +class HiLogger : public Logger { +public: + void Print(Level level, const std::string &tag, const std::string &msg) override + { + if (msg.empty()) { + return; + } + const std::string format = "%{public}s"; + OHOS::HiviewDFX::HiLogLabel label = { LOG_CORE, 0xD001630, tag.c_str() }; // log module id. + switch (level) { + case Level::LEVEL_DEBUG: + (void)OHOS::HiviewDFX::HiLog::Debug(label, format.c_str(), msg.c_str()); + break; + case Level::LEVEL_INFO: + (void)OHOS::HiviewDFX::HiLog::Info(label, format.c_str(), msg.c_str()); + break; + case Level::LEVEL_WARN: + (void)OHOS::HiviewDFX::HiLog::Warn(label, format.c_str(), msg.c_str()); + break; + case Level::LEVEL_ERROR: + (void)OHOS::HiviewDFX::HiLog::Error(label, format.c_str(), msg.c_str()); + break; + case Level::LEVEL_FATAL: + (void)OHOS::HiviewDFX::HiLog::Fatal(label, format.c_str(), msg.c_str()); + break; + default: + break; + } + } +}; + +Logger *Logger::GetInstance() +{ + static std::mutex logInstanceLock; + static std::atomic logInstance = nullptr; + // For Double-Checked Locking, we need check logInstance twice + if (logInstance == nullptr) { + std::lock_guard lock(logInstanceLock); + if (logInstance == nullptr) { + // Here, we new logInstance to print log, if new failed, we can do nothing. + logInstance = new (std::nothrow) HiLogger; + } + } + return logInstance; +} + +void Logger::RegisterLogger(Logger *logger) +{ + static std::mutex logHandlerLock; + if (logger == nullptr) { + return; + } + if (logHandler == nullptr) { + std::lock_guard lock(logHandlerLock); + if (logHandler == nullptr) { + logHandler = logger; + } + } +} + +void Logger::Log(Level level, const std::string &tag, const char *func, int line, const char *format, ...) +{ + (void)func; + (void)line; + if (format == nullptr) { + return; + } + + static const int maxLogLength = 1024; + va_list argList; + va_start(argList, format); + char logBuff[maxLogLength]; + std::string msg; + std::string formatTemp; + PreparePrivateLog(format, formatTemp); + int bytes = vsnprintf_s(logBuff, maxLogLength, maxLogLength - 1, formatTemp.c_str(), argList); + if (bytes < 0) { + msg = "log buffer overflow!"; + } else { + msg = logBuff; + } + va_end(argList); + if (logHandler != nullptr) { + logHandler->Print(level, tag, msg); + return; + } + + Logger::RegisterLogger(Logger::GetInstance()); + if (logHandler != nullptr) { + logHandler->Print(level, tag, msg); + } +} + +void Logger::PreparePrivateLog(const char *format, std::string &outStrFormat) +{ + outStrFormat = format; + std::string::size_type pos = outStrFormat.find(PRIVATE_TAG); + if (pos != std::string::npos) { + outStrFormat.replace(pos, PRIVATE_TAG.size(), ".3s"); + } +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/notification_chain.cpp b/mock/distributeddb/common/src/notification_chain.cpp new file mode 100644 index 00000000..4b7daea5 --- /dev/null +++ b/mock/distributeddb/common/src/notification_chain.cpp @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "notification_chain.h" + +#include +#include + +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +NotificationChain::Listener *NotificationChain::RegisterListener( + EventType type, const Listener::OnEvent &onEvent, const Listener::OnFinalize &onFinalize, int &errCode) +{ + errCode = E_OK; + if (!onEvent) { + LOGE("[NotificationChain] Register listener failed, 'onEvent()' is null!"); + errCode = -E_INVALID_ARGS; + return nullptr; + } + + NotificationChain::ListenerChain *listenerChain = FindAndGetListenerChainLocked(type); + if (listenerChain == nullptr) { + LOGE("[NotificationChain] Register listener failed, no event type %u found!", type); + errCode = -E_NOT_REGISTER; + return nullptr; + } + + NotificationChain::Listener *listener = new (std::nothrow) + NotificationChain::Listener(onEvent, onFinalize); + if (listener == nullptr) { + listenerChain->DecObjRef(listenerChain); + listenerChain = nullptr; + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + + errCode = listenerChain->RegisterListener(listener); + if (errCode != E_OK) { + LOGE("[NotificationChain] Register listener failed, event type %u has been unregistered!", type); + listener->DecObjRef(listener); + listener = nullptr; + listenerChain->DecObjRef(listenerChain); + listenerChain = nullptr; + return nullptr; + } + + listenerChain->DecObjRef(listenerChain); + listenerChain = nullptr; + return listener; +} + +int NotificationChain::RegisterEventType(EventType type) +{ + AutoLock lockGuard(this); + if (IsKilled()) { + LOGI("Register event failed, the notification chain has been killed!"); + return -E_STALE; + } + + ListenerChain *listenerChain = FindListenerChain(type); + if (listenerChain != nullptr) { + LOGE("[NotificationChain] Register event failed, event type %u has been registered!", type); + return -E_ALREADY_REGISTER; + } + + listenerChain = new (std::nothrow) ListenerChain(); + if (listenerChain == nullptr) { + LOGE("[NotificationChain] Register event failed, OOM!"); + return -E_OUT_OF_MEMORY; + } + + listenerChain->OnKill([listenerChain] { + listenerChain->ClearListeners(); + }); + eventChains_.insert(std::pair(type, listenerChain)); + IncObjRef(this); + return E_OK; +} + +int NotificationChain::UnRegisterEventType(EventType type) +{ + NotificationChain::ListenerChain *listenerChain = nullptr; + { + AutoLock lockGuard(this); + listenerChain = FindListenerChain(type); + if (listenerChain == nullptr) { + LOGE("[NotificationChain] UnRegister event failed, event %u is not registered!", type); + return -E_NOT_FOUND; + } + eventChains_.erase(type); + } + + listenerChain->KillAndDecObjRef(listenerChain); + listenerChain = nullptr; + DecObjRef(this); + return E_OK; +} + +void NotificationChain::NotifyEvent(EventType type, void *arg) +{ + NotificationChain::ListenerChain *listenerChain = FindAndGetListenerChainLocked(type); + if (listenerChain == nullptr) { + return; + } + listenerChain->NotifyListeners(arg); + listenerChain->DecObjRef(listenerChain); + listenerChain = nullptr; +} + +NotificationChain::ListenerChain::ListenerChain() {} + +NotificationChain::ListenerChain::~ListenerChain() {} + +NotificationChain::ListenerChain *NotificationChain::FindAndGetListenerChainLocked(EventType type) +{ + AutoLock lockGuard(this); + ListenerChain *listenerChain = FindListenerChain(type); + if (listenerChain == nullptr) { + return nullptr; + } + listenerChain->IncObjRef(listenerChain); + return listenerChain; +} + +NotificationChain::ListenerChain *NotificationChain::FindListenerChain(EventType type) const +{ + auto iter = eventChains_.find(type); + if (iter != eventChains_.end()) { + return iter->second; + } + return nullptr; +} + +int NotificationChain::ListenerChain::RegisterListener(Listener *listener) +{ + AutoLock lockGuard(this); + if (IsKilled()) { + return -E_STALE; + } + if (listenerSet_.find(listener) != listenerSet_.end()) { + return -E_ALREADY_REGISTER; + } + listenerSet_.insert(listener); + listener->SetOwner(this); + return E_OK; +} + +int NotificationChain::ListenerChain::UnRegisterListener(Listener *listener, bool wait) +{ + if (listener == nullptr) { + return -E_INVALID_ARGS; + } + + { + AutoLock lockGuard(this); + auto result = listenerSet_.find(listener); + if (result != listenerSet_.end()) { + if (wait) { + listener->OnKill([listener]() { + listener->KillWait(); + }); + } + listenerSet_.erase(result); + } + } + + listener->KillAndDecObjRef(listener); + listener = nullptr; + return E_OK; +} + +void NotificationChain::ListenerChain::BackupListenerSet(std::set &backupSet) const +{ + for (auto listener : listenerSet_) { + listener->IncObjRef(listener); + backupSet.insert(listener); + } +} + +void NotificationChain::ListenerChain::NotifyListeners(void *arg) +{ + std::set tmpSet; + { + AutoLock lockGuard(this); + if (IsKilled()) { + return; + } + BackupListenerSet(tmpSet); + } + + for (auto listener : tmpSet) { + if (listener != nullptr) { + listener->NotifyListener(arg); + listener->DecObjRef(listener); + listener = nullptr; + } + } +} + +void NotificationChain::ListenerChain::ClearListeners() +{ + std::set tmpSet; + BackupListenerSet(tmpSet); + listenerSet_.clear(); + // Enter this function with lock held(OnKill() is invoked with object lock held), so drop it. + UnlockObj(); + + for (auto listener : tmpSet) { + // Drop the ref 1 which increased in 'BackupListenerSet()', + // the origal 1 will be dropped when user call listener->Drop(); + listener->KillAndDecObjRef(listener); + listener = nullptr; + } + + // Lock it again before leaving. + LockObj(); +} + +void NotificationChain::Listener::NotifyListener(void *arg) +{ + if (onEvent_ && !IsKilled()) { + if (EnterEventAction()) { + onEvent_(arg); + LeaveEventAction(); + } + } +} + +void NotificationChain::Listener::Finalize() const +{ + if (onFinalize_) { + onFinalize_(); + } +} + +bool NotificationChain::Listener::EnterEventAction() +{ + AutoLock lockGuard(this); + if (IsKilled()) { + return false; + } + // We never call onEvent() of the same listener in parallel with 2 or more threads. + eventRunningThread_ = std::this_thread::get_id(); + return true; +} + +void NotificationChain::Listener::LeaveEventAction() +{ + AutoLock lockGuard(this); + eventRunningThread_ = std::thread::id(); + safeKill_.notify_one(); +} + +void NotificationChain::Listener::KillWait() +{ + // We entered with object lock held. + if ((eventRunningThread_ == std::thread::id()) || + (eventRunningThread_ == std::this_thread::get_id())) { + return; + } + + LOGW("[NotificationChain] Try to kill an active event listener, now wait."); + bool noDeadLock = WaitLockedUntil(safeKill_, [this]() { + if (eventRunningThread_ == std::thread::id()) { + return true; + } + return false; + }, KILL_WAIT_SECONDS); + if (!noDeadLock) { + LOGE("[NotificationChain] Dead lock maybe happen, we stop waiting the listener."); + } else { + LOGW("[NotificationChain] Wait the active event listener ok."); + } +} + +void NotificationChain::Listener::SetOwner(ListenerChain *listenerChain) +{ + if (listenerChain_ != nullptr) { + listenerChain_->DecObjRef(listenerChain_); + } + listenerChain_ = listenerChain; + if (listenerChain_ != nullptr) { + listenerChain_->IncObjRef(listenerChain_); + } +} + +int NotificationChain::Listener::Drop(bool wait) +{ + if (listenerChain_ == nullptr) { + LOGE("[NotificationChain] Drop listener failed, lost the chain!"); + return -E_INTERNAL_ERROR; + } + return listenerChain_->UnRegisterListener(this, wait); +} + +NotificationChain::Listener::Listener(const OnEvent &onEvent, const OnFinalize &onFinalize) + : onEvent_(onEvent), + onFinalize_(onFinalize), + listenerChain_(nullptr) +{ + OnLastRef([this]() { + this->Finalize(); + }); +} + +NotificationChain::Listener::~Listener() +{ + SetOwner(nullptr); +} + +NotificationChain::~NotificationChain() +{ + for (auto &iter : eventChains_) { + iter.second->KillAndDecObjRef(iter.second); + iter.second = nullptr; + } + eventChains_.clear(); +} + +DEFINE_OBJECT_TAG_FACILITIES(NotificationChain) +DEFINE_OBJECT_TAG_FACILITIES(NotificationChain::Listener) +DEFINE_OBJECT_TAG_FACILITIES(NotificationChain::ListenerChain) +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/common/src/param_check_utils.cpp b/mock/distributeddb/common/src/param_check_utils.cpp new file mode 100644 index 00000000..e7a94aac --- /dev/null +++ b/mock/distributeddb/common/src/param_check_utils.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "param_check_utils.h" + +#include "db_errno.h" +#include "platform_specific.h" +#include "log_print.h" + +namespace DistributedDB { +bool ParamCheckUtils::CheckDataDir(const std::string &dataDir, std::string &canonicalDir) +{ + if (dataDir.empty() || (dataDir.length() > DBConstant::MAX_DATA_DIR_LENGTH)) { + LOGE("Invalid data directory[%zu]", dataDir.length()); + return false; + } + + if (OS::GetRealPath(dataDir, canonicalDir) != E_OK) { + return false; + } + // After normalizing the path, determine whether the path is a legal path considered by the program. + // There has been guaranteed by the upper layer, So there is no need trustlist set here. + return true; +} + +bool ParamCheckUtils::IsStoreIdSafe(const std::string &storeId) +{ + if (storeId.empty() || (storeId.length() > DBConstant::MAX_STORE_ID_LENGTH)) { + LOGE("Invalid store id[%zu]", storeId.length()); + return false; + } + + auto iter = std::find_if_not(storeId.begin(), storeId.end(), + [](char value) { return (std::isalnum(value) || value == '_'); }); + if (iter != storeId.end()) { + LOGE("Invalid store id format"); + return false; + } + return true; +} + +bool ParamCheckUtils::CheckStoreParameter(const std::string &storeId, const std::string &appId, + const std::string &userId, bool isIgnoreUserIdCheck) +{ + if (!IsStoreIdSafe(storeId)) { + return false; + } + if (!isIgnoreUserIdCheck) { + if (userId.empty() || userId.length() > DBConstant::MAX_USER_ID_LENGTH) { + LOGE("Invalid user info[%zu][%zu]", userId.length(), appId.length()); + return false; + } + if (userId.find(DBConstant::ID_CONNECTOR) != std::string::npos) { + LOGE("Invalid userId character in the store para info."); + return false; + } + } + if (appId.empty() || appId.length() > DBConstant::MAX_APP_ID_LENGTH) { + LOGE("Invalid app info[%zu][%zu]", userId.length(), appId.length()); + return false; + } + + if ((appId.find(DBConstant::ID_CONNECTOR) != std::string::npos) || + (storeId.find(DBConstant::ID_CONNECTOR) != std::string::npos)) { + LOGE("Invalid character in the store para info."); + return false; + } + return true; +} + +bool ParamCheckUtils::CheckEncryptedParameter(CipherType cipher, const CipherPassword &passwd) +{ + if (cipher != CipherType::DEFAULT && cipher != CipherType::AES_256_GCM) { + LOGE("Invalid cipher type!"); + return false; + } + + return (passwd.GetSize() != 0); +} + +bool ParamCheckUtils::CheckConflictNotifierType(int conflictType) +{ + if (conflictType <= 0) { + return false; + } + // Divide the type into different types. + if (conflictType >= CONFLICT_NATIVE_ALL) { + conflictType -= CONFLICT_NATIVE_ALL; + } + if (conflictType >= CONFLICT_FOREIGN_KEY_ORIG) { + conflictType -= CONFLICT_FOREIGN_KEY_ORIG; + } + if (conflictType >= CONFLICT_FOREIGN_KEY_ONLY) { + conflictType -= CONFLICT_FOREIGN_KEY_ONLY; + } + return (conflictType == 0); +} + +bool ParamCheckUtils::CheckSecOption(const SecurityOption &secOption) +{ + if (secOption.securityLabel > S4 || secOption.securityLabel < NOT_SET) { + LOGE("[DBCommon] SecurityLabel is invalid, label is [%d].", secOption.securityLabel); + return false; + } + if (secOption.securityFlag != 0) { + if ((secOption.securityLabel != S3 && secOption.securityLabel != S4) || secOption.securityFlag != SECE) { + LOGE("[DBCommon] SecurityFlag is invalid."); + return false; + } + } + return true; +} + +bool ParamCheckUtils::CheckObserver(const Key &key, unsigned int mode) +{ + if (key.size() > DBConstant::MAX_KEY_SIZE) { + return false; + } + + if (mode > OBSERVER_CHANGES_LOCAL_ONLY || mode < OBSERVER_CHANGES_NATIVE) { + return false; + } + return true; +} + +bool ParamCheckUtils::IsS3SECEOpt(const SecurityOption &secOpt) +{ + SecurityOption S3SeceOpt = {SecurityLabel::S3, SecurityFlag::SECE}; + return (secOpt == S3SeceOpt); +} + +int ParamCheckUtils::CheckAndTransferAutoLaunchParam(const AutoLaunchParam ¶m, bool checkDir, + SchemaObject &schemaObject, std::string &canonicalDir) +{ + if ((param.option.notifier && !ParamCheckUtils::CheckConflictNotifierType(param.option.conflictType)) || + (!param.option.notifier && param.option.conflictType != 0)) { + LOGE("[AutoLaunch] CheckConflictNotifierType is invalid."); + return -E_INVALID_ARGS; + } + if (!ParamCheckUtils::CheckStoreParameter(param.storeId, param.appId, param.userId)) { + LOGE("[AutoLaunch] CheckStoreParameter is invalid."); + return -E_INVALID_ARGS; + } + + const AutoLaunchOption &option = param.option; + if (!ParamCheckUtils::CheckSecOption(option.secOption)) { + LOGE("[AutoLaunch] CheckSecOption is invalid."); + return -E_INVALID_ARGS; + } + + if (option.isEncryptedDb) { + if (!ParamCheckUtils::CheckEncryptedParameter(option.cipher, option.passwd)) { + LOGE("[AutoLaunch] CheckEncryptedParameter is invalid."); + return -E_INVALID_ARGS; + } + } + + if (!param.option.schema.empty()) { + schemaObject.ParseFromSchemaString(param.option.schema); + if (!schemaObject.IsSchemaValid()) { + LOGE("[AutoLaunch] ParseFromSchemaString is invalid."); + return -E_INVALID_SCHEMA; + } + } + + if (!checkDir) { + canonicalDir = param.option.dataDir; + return E_OK; + } + + if (!ParamCheckUtils::CheckDataDir(param.option.dataDir, canonicalDir)) { + LOGE("[AutoLaunch] CheckDataDir is invalid."); + return -E_INVALID_ARGS; + } + return E_OK; +} + +uint8_t ParamCheckUtils::GetValidCompressionRate(uint8_t compressionRate) +{ + // Valid when between 1 and 100. When compressionRate is invalid, change it to default rate. + if (compressionRate < 1 || compressionRate > DBConstant::DEFAULT_COMPTRESS_RATE) { + LOGD("Invalid compression rate:%u.", compressionRate); + compressionRate = DBConstant::DEFAULT_COMPTRESS_RATE; + } + return compressionRate; +} + +bool ParamCheckUtils::CheckRelationalTableName(const std::string &tableName) +{ + auto iter = std::find_if_not(tableName.begin(), tableName.end(), [](char c) { + return (std::isalnum(c) || c == '_'); + }); + if (iter != tableName.end()) { + return false; + } + return tableName.compare(0, DBConstant::SYSTEM_TABLE_PREFIX.size(), DBConstant::SYSTEM_TABLE_PREFIX) != 0; +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/common/src/parcel.cpp b/mock/distributeddb/common/src/parcel.cpp new file mode 100644 index 00000000..372a9bfc --- /dev/null +++ b/mock/distributeddb/common/src/parcel.cpp @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "parcel.h" + +#include + +#include "endian_convert.h" +#include "securec.h" +#include "macro_utils.h" +#include "log_print.h" +#include "db_errno.h" +#include "db_constant.h" + +namespace DistributedDB { +Parcel::Parcel(uint8_t *inBuf, uint32_t len) + : buf_(inBuf), + bufPtr_(inBuf), + totalLen_(len) +{ + if (inBuf == nullptr || len == 0) { + isError_ = true; + } +} + +Parcel::~Parcel() +{ + buf_ = nullptr; + bufPtr_ = nullptr; +} + +bool Parcel::IsError() const +{ + return isError_; +} + +int Parcel::WriteBool(bool data) +{ + uint8_t value = data ? 1 : 0; + return WriteUInt8(value); +} + +uint32_t Parcel::ReadBool(bool &val) +{ + uint8_t intVal = 0; + uint32_t len = ReadUInt8(intVal); + val = intVal == 1 ? true : false; + return len; +} + +int Parcel::WriteInt(int32_t data) +{ + return WriteInteger(data); +} + +uint32_t Parcel::ReadInt(int32_t &val) +{ + return ReadInteger(val); +} + +int Parcel::WriteUInt8(uint8_t data) +{ + return WriteInteger(data); +} + +uint32_t Parcel::ReadUInt8(uint8_t &val) +{ + return ReadInteger(val); +} + +int Parcel::WriteDouble(double data) +{ + double inData = HostToNet(data); + if (isError_ || parcelLen_ + sizeof(double) > totalLen_) { + isError_ = true; + return -E_PARSE_FAIL; + } + errno_t errCode = memcpy_s(bufPtr_, totalLen_ - parcelLen_, &inData, sizeof(double)); + if (errCode != EOK) { + isError_ = true; + return -E_SECUREC_ERROR; + } + bufPtr_ += sizeof(double); + parcelLen_ += sizeof(double); + return E_OK; +} + +uint32_t Parcel::ReadDouble(double &val) +{ + if (isError_ || bufPtr_ == nullptr || parcelLen_ + sizeof(double) > totalLen_) { + isError_ = true; + return 0; + } + val = *(reinterpret_cast(bufPtr_)); + bufPtr_ += sizeof(double); + parcelLen_ += sizeof(double); + val = NetToHost(val); + return sizeof(double); +} + +int Parcel::WriteInt64(int64_t data) +{ + return WriteInteger(data); +} + +uint32_t Parcel::ReadInt64(int64_t &val) +{ + return ReadInteger(val); +} + +int Parcel::WriteUInt32(uint32_t data) +{ + return WriteInteger(data); +} + +uint32_t Parcel::ReadUInt32(uint32_t &val) +{ + return ReadInteger(val); +} + +int Parcel::WriteUInt64(uint64_t data) +{ + return WriteInteger(data); +} + +uint32_t Parcel::ReadUInt64(uint64_t &val) +{ + return ReadInteger(val); +} + +int Parcel::WriteVectorChar(const std::vector& data) +{ + return WriteVector(data); +} + +uint32_t Parcel::ReadVectorChar(std::vector& val) +{ + return ReadVector(val); +} + +int Parcel::WriteString(const std::string &inVal) +{ + if (inVal.size() > INT32_MAX) { + LOGE("[WriteString] Invalid string, size:%zu.", inVal.size()); + isError_ = true; + return -E_PARSE_FAIL; + } + if (IsError()) { + return -E_PARSE_FAIL; + } + uint32_t len = inVal.size(); + uint64_t stepLen = sizeof(uint32_t) + static_cast(inVal.size()); + len = HostToNet(len); + if (stepLen > INT32_MAX || parcelLen_ + BYTE_8_ALIGN(stepLen) > totalLen_) { + LOGE("[WriteString] stepLen:%" PRIu64 ", totalLen:%" PRIu64 ", parcelLen:%" PRIu64, stepLen, totalLen_, + parcelLen_); + isError_ = true; + return -E_PARSE_FAIL; + } + errno_t errCode = memcpy_s(bufPtr_, totalLen_ - parcelLen_, &len, sizeof(uint32_t)); + if (errCode != EOK) { + LOGE("[WriteString] bufPtr:%d, totalLen:%" PRIu64 ", parcelLen:%" PRIu64, bufPtr_ != nullptr, totalLen_, + parcelLen_); + isError_ = true; + return -E_SECUREC_ERROR; + } + bufPtr_ += sizeof(uint32_t); + if (inVal.size() == 0) { + bufPtr_ += BYTE_8_ALIGN(stepLen) - stepLen; + parcelLen_ += BYTE_8_ALIGN(stepLen); + return errCode; + } + errCode = memcpy_s(bufPtr_, totalLen_ - parcelLen_ - sizeof(uint32_t), inVal.c_str(), inVal.size()); + if (errCode != EOK) { + LOGE("[WriteString] totalLen:%" PRIu64 ", parcelLen:%" PRIu64 ", inVal.size:%zu.", + totalLen_, parcelLen_, inVal.size()); + isError_ = true; + return -E_SECUREC_ERROR; + } + bufPtr_ += inVal.size(); + bufPtr_ += BYTE_8_ALIGN(stepLen) - stepLen; + parcelLen_ += BYTE_8_ALIGN(stepLen); + return E_OK; +} + +uint32_t Parcel::ReadString(std::string &outVal) +{ + if (IsError()) { + return 0; + } + if (bufPtr_ == nullptr || parcelLen_ + sizeof(uint32_t) > totalLen_) { + LOGE("[ReadString] bufPtr:%d, totalLen:%" PRIu64 ", parcelLen:%" PRIu64, bufPtr_ != nullptr, totalLen_, + parcelLen_); + isError_ = true; + return 0; + } + uint32_t len = *(reinterpret_cast(bufPtr_)); + len = NetToHost(len); + uint64_t stepLen = static_cast(len) + sizeof(uint32_t); + if (stepLen > INT32_MAX || parcelLen_ + BYTE_8_ALIGN(stepLen) > totalLen_) { + LOGE("[ReadString] stepLen:%" PRIu64 ", totalLen:%" PRIu64 ", parcelLen:%" PRIu64, stepLen, totalLen_, + parcelLen_); + isError_ = true; + return 0; + } + outVal.resize(len); + outVal.assign(bufPtr_ + sizeof(uint32_t), bufPtr_ + stepLen); + bufPtr_ += BYTE_8_ALIGN(stepLen); + parcelLen_ += BYTE_8_ALIGN(stepLen); + stepLen = BYTE_8_ALIGN(stepLen); + return static_cast(stepLen); +} + +bool Parcel::IsContinueRead() +{ + return (parcelLen_ < totalLen_); +} + +#ifndef OMIT_MULTI_VER +int Parcel::WriteMultiVerCommit(const MultiVerCommitNode &commit) +{ + int errCode = WriteVectorChar(commit.commitId); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write commitId err!"); + isError_ = true; + return errCode; + } + errCode = WriteVectorChar(commit.leftParent); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write leftParent err!"); + return errCode; + } + errCode = WriteVectorChar(commit.rightParent); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write rightParent err!"); + return errCode; + } + errCode = WriteUInt64(commit.timestamp); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write timestamp err!"); + return errCode; + } + errCode = WriteUInt64(commit.version); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write version err!"); + return errCode; + } + errCode = WriteUInt64(commit.isLocal); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write isLocal err!"); + return errCode; + } + errCode = WriteString(commit.deviceInfo); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write deviceInfo err!"); + } + return errCode; +} + +uint32_t Parcel::ReadMultiVerCommit(MultiVerCommitNode &commit) +{ + if (isError_) { + return 0; + } + uint64_t len = ReadVectorChar(commit.commitId); + len += ReadVectorChar(commit.leftParent); + len += ReadVectorChar(commit.rightParent); + len += ReadUInt64(commit.timestamp); + len += ReadUInt64(commit.version); + len += ReadUInt64(commit.isLocal); + len += ReadString(commit.deviceInfo); + if (isError_ || len > INT32_MAX) { + isError_ = true; + return 0; + } + return static_cast(len); +} +int Parcel::WriteMultiVerCommits(const std::vector &commits) +{ + uint64_t len = commits.size(); + int errCode = WriteUInt64(len); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write len err!"); + isError_ = true; + return errCode; + } + for (auto &iter : commits) { + errCode = WriteVectorChar(iter.commitId); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write commitId err!"); + return errCode; + } + errCode = WriteVectorChar(iter.leftParent); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write leftParent err!"); + return errCode; + } + errCode = WriteVectorChar(iter.rightParent); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write rightParent err!"); + return errCode; + } + errCode = WriteUInt64(iter.timestamp); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write timestamp err!"); + return errCode; + } + errCode = WriteUInt64(iter.version); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write version err!"); + return errCode; + } + errCode = WriteUInt64(iter.isLocal); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write isLocal err!"); + return errCode; + } + errCode = WriteString(iter.deviceInfo); + if (errCode != E_OK) { + LOGE("Parcel::WriteMultiVerCommit write deviceInfo err!"); + return errCode; + } + EightByteAlign(); + } + EightByteAlign(); + return errCode; +} + +uint32_t Parcel::ReadMultiVerCommits(std::vector &commits) +{ + uint64_t len = 0; + uint64_t size = 0; + len += ReadUInt64(size); + if (isError_) { + return 0; + } + if (size > DBConstant::MAX_COMMIT_SIZE) { + isError_ = true; + LOGE("Parcel::ReadMultiVerCommits commits size too large: %" PRIu64, size); + return 0; + } + for (uint64_t i = 0; i < size; i++) { + MultiVerCommitNode commit; + len += ReadVectorChar(commit.commitId); + len += ReadVectorChar(commit.leftParent); + len += ReadVectorChar(commit.rightParent); + len += ReadUInt64(commit.timestamp); + len += ReadUInt64(commit.version); + len += ReadUInt64(commit.isLocal); + len += ReadString(commit.deviceInfo); + commits.push_back(commit); + EightByteAlign(); + len = BYTE_8_ALIGN(len); + if (isError_ || len > INT32_MAX) { + isError_ = true; + return 0; + } + } + len = BYTE_8_ALIGN(len); + + return static_cast(len); +} +#endif + +int Parcel::WriteBlob(const char *buffer, uint32_t bufLen) +{ + if (buffer == nullptr) { + LOGE("[WriteBlob] Invalid buffer."); + isError_ = true; + return -E_INVALID_ARGS; + } + if (IsError()) { + return -E_PARSE_FAIL; + } + if (parcelLen_ + bufLen > totalLen_) { + LOGE("[WriteBlob] bufLen:%" PRIu32 ", totalLen:%" PRIu64 ", parcelLen:%" PRIu64, bufLen, totalLen_, parcelLen_); + isError_ = true; + return -E_PARSE_FAIL; + } + uint32_t leftLen = static_cast(totalLen_ - parcelLen_); + int errCode = memcpy_s(bufPtr_, leftLen, buffer, bufLen); + if (errCode != EOK) { + LOGE("[WriteBlob] leftLen:%u, bufLen:%u", leftLen, bufLen); + isError_ = true; + return -E_SECUREC_ERROR; + } + uint32_t length = (BYTE_8_ALIGN(bufLen) < leftLen) ? BYTE_8_ALIGN(bufLen) : leftLen; + bufPtr_ += length; + parcelLen_ += length; + return E_OK; +} +uint32_t Parcel::ReadBlob(char *buffer, uint32_t bufLen) +{ + if (buffer == nullptr) { + LOGE("[ReadBlob] Invalid buffer."); + isError_ = true; + return 0; + } + if (IsError()) { + return 0; + } + uint32_t leftLen = static_cast(totalLen_ - parcelLen_); + if (parcelLen_ + bufLen > totalLen_) { + LOGE("[ReadBlob] bufLen:%" PRIu32 ", totalLen:%" PRIu64 ", parcelLen:%" PRIu64, bufLen, totalLen_, parcelLen_); + isError_ = true; + return 0; + } + int errCode = memcpy_s(buffer, bufLen, bufPtr_, bufLen); + if (errCode != EOK) { + LOGE("[ReadBlob] bufLen:%u", bufLen); + isError_ = true; + return 0; + } + uint32_t length = (BYTE_8_ALIGN(bufLen) < leftLen) ? BYTE_8_ALIGN(bufLen) : leftLen; + bufPtr_ += length; + parcelLen_ += length; + return length; +} + +uint32_t Parcel::GetBoolLen() +{ + return GetUInt8Len(); +} + +uint32_t Parcel::GetUInt8Len() +{ + return sizeof(uint8_t); +} + +uint32_t Parcel::GetIntLen() +{ + return sizeof(int32_t); +} + +uint32_t Parcel::GetUInt32Len() +{ + return sizeof(uint32_t); +} + +uint32_t Parcel::GetUInt64Len() +{ + return sizeof(uint64_t); +} + +uint32_t Parcel::GetInt64Len() +{ + return sizeof(int64_t); +} + +uint32_t Parcel::GetDoubleLen() +{ + return sizeof(double); +} + +uint32_t Parcel::GetVectorCharLen(const std::vector &data) +{ + return GetVectorLen(data); +} + +uint32_t Parcel::GetStringLen(const std::string &data) +{ + if (data.size() > INT32_MAX) { + return 0; + } + uint64_t len = sizeof(uint32_t) + static_cast(data.size()); + len = BYTE_8_ALIGN(len); + if (len > INT32_MAX) { + return 0; + } + return static_cast(len); +} + +#ifndef OMIT_MULTI_VER +uint32_t Parcel::GetMultiVerCommitLen(const MultiVerCommitNode &commit) +{ + uint64_t len = GetVectorCharLen(commit.commitId); + len += GetVectorCharLen(commit.leftParent); + len += GetVectorCharLen(commit.rightParent); + len += GetUInt64Len(); + len += GetUInt64Len(); + len += GetUInt64Len(); + len += GetStringLen(commit.deviceInfo); + if (len > INT32_MAX) { + return 0; + } + return static_cast(len); +} + +uint32_t Parcel::GetMultiVerCommitsLen(const std::vector &commits) +{ + uint64_t len = GetUInt64Len(); + for (auto &iter : commits) { + len += GetVectorCharLen(iter.commitId); + len += GetVectorCharLen(iter.leftParent); + len += GetVectorCharLen(iter.rightParent); + len += GetUInt64Len(); + len += GetUInt64Len(); + len += GetUInt64Len(); + len += GetStringLen(iter.deviceInfo); + len = BYTE_8_ALIGN(len); + if (len > INT32_MAX) { + return 0; + } + } + len = BYTE_8_ALIGN(len); + if (len > INT32_MAX) { + return 0; + } + return static_cast(len); +} +#endif + +void Parcel::EightByteAlign() +{ + bufPtr_ += BYTE_8_ALIGN(parcelLen_) - parcelLen_; + parcelLen_ = BYTE_8_ALIGN(parcelLen_); +} + +uint32_t Parcel::GetEightByteAlign(uint32_t len) +{ + return BYTE_8_ALIGN(len); +} + +uint32_t Parcel::GetAppendedLen() +{ + // 8 is 8-byte-align max append len, there are 2 8-byte-align totally + return sizeof(uint32_t) + sizeof(uint32_t) + 8 * 2; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/performance_analysis.cpp b/mock/distributeddb/common/src/performance_analysis.cpp new file mode 100644 index 00000000..f0aa482b --- /dev/null +++ b/mock/distributeddb/common/src/performance_analysis.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "performance_analysis.h" + +#include + +#include "db_errno.h" +#include "macro_utils.h" +#include "time_helper.h" +#include "log_print.h" +#include "platform_specific.h" + +namespace DistributedDB { +const std::string PerformanceAnalysis::STATISTICAL_DATA_FILE_NAME_HEADER = "/data/log/statistic"; +const std::string PerformanceAnalysis::CSV_FILE_EXTENSION = ".csv"; +const std::string PerformanceAnalysis::DEFAULT_FILE_NAME = "default00"; + +PerformanceAnalysis *PerformanceAnalysis::GetInstance(int stepNum) +{ + static PerformanceAnalysis inst(stepNum); + return &inst; +} + +PerformanceAnalysis::PerformanceAnalysis(uint32_t inStepNum) + : isOpen_(false) +{ + if (inStepNum == 0) { + stepNum_ = 0; + } + stepNum_ = inStepNum; + counts_.resize(stepNum_); + timeRecordData_.timeInfo.resize(stepNum_); + stepTimeRecordInfo_.resize(stepNum_); + for (auto &stepIter : stepTimeRecordInfo_) { + stepIter.max = 0; + stepIter.min = ULLONG_MAX; + stepIter.average = 0; + } + for (auto iter = counts_.begin(); iter != counts_.end(); ++iter) { + *iter = 0; + } + fileNumber_ = 0; + fileID_ = std::string(DEFAULT_FILE_NAME) + std::to_string(fileNumber_); +} + +PerformanceAnalysis::~PerformanceAnalysis() {}; + +bool PerformanceAnalysis::IsStepValid(uint32_t step) const +{ + return (stepNum_ < MAX_TIMERECORD_STEP_NUM && step < stepNum_); +} + +bool PerformanceAnalysis::IsOpen() const +{ + return isOpen_; +} + +void PerformanceAnalysis::OpenPerformanceAnalysis() +{ + isOpen_ = true; +} + +void PerformanceAnalysis::ClosePerformanceAnalysis() +{ + isOpen_ = false; +} + +bool PerformanceAnalysis::InsertTimeRecord(const TimePair &timePair, uint32_t step) +{ + if (!IsStepValid(step)) { + return false; + } + timeRecordData_.timeInfo[step] = timePair; + return true; +} + +bool PerformanceAnalysis::GetTimeRecord(uint32_t step, TimePair &timePair) const +{ + if (!IsStepValid(step)) { + return false; + } + timePair = timeRecordData_.timeInfo[step]; + return true; +} + +void PerformanceAnalysis::TimeRecordStart() +{ + if (!IsOpen()) { + return; + } + StepTimeRecordStart(0); +} + +void PerformanceAnalysis::TimeRecordEnd() +{ + if (!IsOpen()) { + return; + } + StepTimeRecordEnd(0); +} + +void PerformanceAnalysis::StepTimeRecordStart(uint32_t step) +{ + if (!IsOpen()) { + return; + } + if (!IsStepValid(step)) { + return; + } + TimePair timePair = {0, 0}; + uint64_t curTime = 0; + int errCode = OS::GetCurrentSysTimeInMicrosecond(curTime); + if (errCode != E_OK) { + LOGE("[performance_analysis] GetCurrentSysTimeInMicrosecond fail"); + } else { + timePair.startTime = curTime; + LOGD("[performance_analysis] StepTimeRecordStart step:%" PRIu32 ", curTime:%" PRIu64, step, curTime); + (void)InsertTimeRecord(timePair, step); + } +} + +void PerformanceAnalysis::StepTimeRecordEnd(uint32_t step) +{ + if (!IsOpen()) { + return; + } + if (!IsStepValid(step)) { + return; + } + TimePair timePair = {0, 0}; + bool errCode = GetTimeRecord(step, timePair); + if (!errCode) { + return; + } + (void)InsertTimeRecord({0, 0}, step); + + uint64_t curTime = 0; + (void)OS::GetCurrentSysTimeInMicrosecond(curTime); + timePair.endTime = curTime; + LOGD("[performance_analysis] StepTimeRecordEnd step:%" PRIu32 ", curTime:%" PRIu64, step, curTime); + + if ((timePair.endTime < timePair.startTime) || (timePair.startTime == 0) || (timePair.endTime == 0)) { + return; + } + Timestamp offset = timePair.endTime - timePair.startTime; + if (stepTimeRecordInfo_[step].max < offset) { + stepTimeRecordInfo_[step].max = offset; + } + if (offset < stepTimeRecordInfo_[step].min) { + stepTimeRecordInfo_[step].min = offset; + } + counts_[step]++; + if (counts_[step] == 0) { + stepTimeRecordInfo_[step].average = 0; + return; + } + stepTimeRecordInfo_[step].average += (static_cast(offset) - + stepTimeRecordInfo_[step].average) / counts_[step]; +} + +std::string PerformanceAnalysis::GetStatistics() +{ + std::string result; + for (size_t i = 0; i < stepTimeRecordInfo_.size(); i++) { + if (stepTimeRecordInfo_[i].max != 0) { + result += "\nstep : " + std::to_string(i) + "\n"; + result += "max: " + std::to_string(stepTimeRecordInfo_[i].max) + "\n"; + result += "min: " + std::to_string(stepTimeRecordInfo_[i].min) + "\n"; + result += "average: " + + std::to_string(static_cast(stepTimeRecordInfo_[i].average)) + "\n"; + result += "count: " + std::to_string(counts_[i]) + "\n"; + } + } + OutStatistics(); + Clear(); + return result; +} + +void PerformanceAnalysis::OutStatistics() +{ + std::string addrStatistics = STATISTICAL_DATA_FILE_NAME_HEADER + fileID_ + CSV_FILE_EXTENSION; + outFile.open(addrStatistics, std::ios_base::app); + // This part filters the zeros data + outFile << "stepNum" << "," << "maxTime(us)" << "," << "minTime(us)" << "," << "averageTime(us)" + << "," << "count" << "," << "\n"; + for (size_t i = 0; i < stepTimeRecordInfo_.size(); i++) { // output to performance file + if (stepTimeRecordInfo_[i].max != 0) { + outFile << i << "," << stepTimeRecordInfo_[i].max<< "," << stepTimeRecordInfo_[i].min + << "," << stepTimeRecordInfo_[i].average << "," << counts_[i] << "," << "\n"; + } + } + LOGD("outFile success and exit!"); + outFile.close(); +} + +void PerformanceAnalysis::Clear() +{ + counts_.clear(); + timeRecordData_.timeInfo.clear(); + stepTimeRecordInfo_.clear(); + counts_.resize(stepNum_); + timeRecordData_.timeInfo.resize(stepNum_); + stepTimeRecordInfo_.resize(stepNum_); + for (auto &iter : stepTimeRecordInfo_) { + iter.max = 0; + iter.min = ULLONG_MAX; + iter.average = 0; + } + fileID_ = std::string(DEFAULT_FILE_NAME) + std::to_string(fileNumber_); +} + +void PerformanceAnalysis::SetFileNumber(const std::string &FileID) +{ + fileID_ = FileID; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/platform_specific.cpp b/mock/distributeddb/common/src/platform_specific.cpp new file mode 100644 index 00000000..f83f4d03 --- /dev/null +++ b/mock/distributeddb/common/src/platform_specific.cpp @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "platform_specific.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "securec.h" +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +namespace OS { +/* + * Common part that is the same between each os + */ +namespace { + const int ACCESS_MODE_EXISTENCE = 0; + const uint64_t MULTIPLES_BETWEEN_SECONDS_AND_MICROSECONDS = 1000000; +} +bool CheckPathExistence(const std::string &filePath) +{ + return (access(filePath.c_str(), ACCESS_MODE_EXISTENCE) == 0); +} + +int RenameFilePath(const std::string &oldFilePath, const std::string &newFilePath) +{ + int errCode = rename(oldFilePath.c_str(), newFilePath.c_str()); + if (errCode < 0) { + LOGE("[Rename] Rename file fail. err = %d", errno); + return -E_SYSTEM_API_FAIL; + } + LOGI("Rename file path successfully!"); + return E_OK; +} + +int RemoveFile(const std::string &filePath) +{ + int errCode = remove(filePath.c_str()); + if (errCode < 0) { + LOGE("[RemoveFile] Remove file fail. err = %d", errno); + return -E_SYSTEM_API_FAIL; + } + LOGI("Remove file successfully!"); + return E_OK; +} + +int CalFileSize(const std::string &fileUrl, uint64_t &size) +{ + struct stat fileStat; + if (fileUrl.empty() || stat(fileUrl.c_str(), &fileStat) < 0 || fileStat.st_size < 0) { + int errCode = (errno == ENOENT) ? -E_NOT_FOUND : -E_INVALID_DB; + LOGD("Get file[%zu] size failed, errno [%d].", fileUrl.size(), errno); + return errCode; + } + + size = static_cast(fileStat.st_size); + return E_OK; +} + +void SplitFilePath(const std::string &filePath, std::string &fileDir, std::string &fileName) +{ + if (filePath.empty()) { + return; + } + + auto slashPos = filePath.find_last_of('/'); + if (slashPos == std::string::npos) { + fileName = filePath; + fileDir = ""; + return; + } + + fileDir = filePath.substr(0, slashPos); + fileName = filePath.substr(slashPos + 1); + return; +} + +int MakeDBDirectory(const std::string &directory) +{ + int errCode = mkdir(directory.c_str(), (S_IRWXU | S_IRGRP | S_IXGRP)); // The permission is 750 for linux based os + if (errCode < 0) { + LOGE("[MakeDir] Make directory fail:%d.", errno); + return -E_SYSTEM_API_FAIL; + } + return E_OK; +} + +int RemoveDBDirectory(const std::string &directory) +{ + return remove(directory.c_str()); +} + +int CreateFileByFileName(const std::string &fileName) +{ + int fp = open(fileName.c_str(), (O_WRONLY | O_CREAT), (S_IRUSR | S_IWUSR | S_IRGRP)); + if (fp < 0) { + LOGE("[CreateFile] Create file fail:%d.", errno); + return -E_SYSTEM_API_FAIL; + } + close(fp); + return E_OK; +} + +int GetRealPath(const std::string &inOriPath, std::string &outRealPath) +{ + const unsigned int MAX_PATH_LENGTH = PATH_MAX; + if (inOriPath.length() > MAX_PATH_LENGTH || MAX_PATH_LENGTH > 0x10000) { // max limit is 64K(0x10000). + LOGE("[RealPath] OriPath too long."); + return -E_INVALID_ARGS; + } + + char *realPath = new (std::nothrow) char[MAX_PATH_LENGTH + 1]; + if (realPath == nullptr) { + return -E_OUT_OF_MEMORY; + } + if (memset_s(realPath, MAX_PATH_LENGTH + 1, 0, MAX_PATH_LENGTH + 1) != EOK) { + delete []realPath; + return -E_SECUREC_ERROR; + } + + if (realpath(inOriPath.c_str(), realPath) == nullptr) { + LOGE("[OS] Realpath error:%d.", errno); + delete []realPath; + return -E_SYSTEM_API_FAIL; + } + outRealPath = std::string(realPath); + delete []realPath; + return E_OK; +} + +int GetCurrentSysTimeInMicrosecond(uint64_t &outTime) +{ + struct timeval rawTime; + int errCode = gettimeofday(&rawTime, nullptr); + if (errCode < 0) { + LOGE("[GetSysTime] Fail:%d.", errCode); + return -E_SYSTEM_API_FAIL; + } + outTime = static_cast(rawTime.tv_sec) * MULTIPLES_BETWEEN_SECONDS_AND_MICROSECONDS + + static_cast(rawTime.tv_usec); + return E_OK; +} + +namespace { + const uint64_t MULTIPLES_BETWEEN_MICROSECONDS_AND_NANOSECONDS = 1000; +} + +int GetMonotonicRelativeTimeInMicrosecond(uint64_t &outTime) +{ + struct timespec rawTime; + int errCode = clock_gettime(CLOCK_BOOTTIME, &rawTime); + if (errCode < 0) { + LOGE("[GetMonoTime] Fail."); + return -E_SYSTEM_API_FAIL; + } + outTime = static_cast(rawTime.tv_sec) * MULTIPLES_BETWEEN_SECONDS_AND_MICROSECONDS + + static_cast(rawTime.tv_nsec) / MULTIPLES_BETWEEN_MICROSECONDS_AND_NANOSECONDS; + return E_OK; +} + +static int GetFilePathAttr(const std::string &topPath, const std::string &relativePath, + std::list &files, bool isNeedAllPath) +{ + DIR *dir = opendir(topPath.c_str()); + if (dir == nullptr) { + LOGE("Open dir error:%d.", errno); + return -E_INVALID_PATH; + } + struct stat fileStat; + std::string fileAbsName; + int errCode = E_OK; + FileAttr file; + for (struct dirent *fileDirInfo = readdir(dir); fileDirInfo != nullptr; fileDirInfo = readdir(dir)) { + switch (fileDirInfo->d_type) { + case DT_REG: + file.fileType = FILE; + break; + case DT_DIR: + file.fileType = PATH; + break; + default: + file.fileType = OTHER; + } + if (strlen(fileDirInfo->d_name) == 0 || strcmp(fileDirInfo->d_name, ".") == 0 || + strcmp(fileDirInfo->d_name, "..") == 0) { + continue; + } + file.fileName = relativePath + fileDirInfo->d_name; + fileAbsName = topPath + "/" + fileDirInfo->d_name; + errCode = stat(fileAbsName.c_str(), &fileStat); + if (errCode != 0) { + LOGE("[GetFileAttr]Get file stat failed, error = %d.", errno); + errCode = -E_INVALID_PATH; + break; + } + if (isNeedAllPath) { + file.fileName = fileAbsName; + } + file.fileLen = static_cast(fileStat.st_size); + files.push_back(file); + if (file.fileType == PATH) { + errCode = GetFilePathAttr(fileAbsName, relativePath + fileDirInfo->d_name + "/", files, isNeedAllPath); + if (errCode != E_OK) { + break; + } + } + } + + closedir(dir); + return errCode; +} + +int GetFileAttrFromPath(const std::string &filePath, std::list &files, bool isNeedAllPath) +{ + return GetFilePathAttr(filePath, std::string(), files, isNeedAllPath); +} + +int GetFilePermissions(const std::string &fileName, uint32_t &permissions) +{ + struct stat fileStat; + int errCode = stat(fileName.c_str(), &fileStat); + if (errCode != E_OK) { + permissions = S_IRUSR | S_IWUSR; + LOGE("Get file stat failed, error = %d.", errno); + return -E_SYSTEM_API_FAIL; + } + permissions = fileStat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); + return E_OK; +} + +int SetFilePermissions(const std::string &fileName, uint32_t permissions) +{ + if (permissions > (S_IRWXU | S_IRWXG | S_IRWXO)) { + return -E_INVALID_ARGS; + } + int errCode = chmod(fileName.c_str(), permissions); + if (errCode != E_OK) { + LOGE("Set file permissions failed, error = %d.", errno); + return -E_SYSTEM_API_FAIL; + } + return E_OK; +} + +int OpenFile(const std::string &fileName, FileHandle &handle) +{ + handle.handle = open(fileName.c_str(), (O_WRONLY | O_CREAT), (S_IRUSR | S_IWUSR | S_IRGRP)); + if (handle.handle < 0) { + LOGE("[FileLock] can not open file when lock it:[%d]", errno); + return -E_SYSTEM_API_FAIL; + } + return E_OK; +} + +int CloseFile(FileHandle &handle) +{ + if (close(handle.handle) != 0) { + LOGE("close file failed, errno:%d", errno); + return -E_SYSTEM_API_FAIL; + } + handle.handle = -1; + return E_OK; +} + +int FileLock(const FileHandle &handle, bool isBlock) +{ + if (handle.handle < 0) { + LOGE("[FileLock] can not open file when lock it:[%d]", errno); + return -E_SYSTEM_API_FAIL; + } + + struct flock fileLockInfo; + (void)memset_s(&fileLockInfo, sizeof(fileLockInfo), 0, sizeof(fileLockInfo)); + fileLockInfo.l_type = F_WRLCK; + fileLockInfo.l_whence = SEEK_SET; + fileLockInfo.l_start = 0; + fileLockInfo.l_len = 0; + LOGD("Lock file isBlock[%d]", isBlock); + if (fcntl(handle.handle, isBlock ? F_SETLKW : F_SETLK, &fileLockInfo) == -1 && !isBlock) { + LOGD("Lock file is Blocked, please retry!"); + return -E_BUSY; + } + LOGI("file locked! errno:%d", errno); + return E_OK; +} + +int FileUnlock(FileHandle &handle) +{ + if (handle.handle == -1) { + LOGI("[FileUnlock] file handle is invalid!"); + return E_OK; + } + + struct flock fileLockInfo; + (void)memset_s(&fileLockInfo, sizeof(fileLockInfo), 0, sizeof(fileLockInfo)); + fileLockInfo.l_type = F_UNLCK; + fileLockInfo.l_whence = SEEK_SET; + fileLockInfo.l_start = 0; + fileLockInfo.l_len = 0; + if (fcntl(handle.handle, F_SETLK, &fileLockInfo) == -1) { + LOGE("Unlock file failed. errno:%d", errno); + return -E_SYSTEM_API_FAIL; + } + return CloseFile(handle); +} +} // namespace OS +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/query.cpp b/mock/distributeddb/common/src/query.cpp new file mode 100644 index 00000000..6d2e2fb2 --- /dev/null +++ b/mock/distributeddb/common/src/query.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "query.h" +namespace DistributedDB { +Query::Query(const std::string &tableName) +{ + queryExpression_.SetTableName(tableName); +} +Query Query::Select() +{ + Query query; + return query; +} + +Query Query::Select(const std::string &tableName) +{ + Query query(tableName); + return query; +} + +Query &Query::BeginGroup() +{ + queryExpression_.BeginGroup(); + return *this; +} + +Query &Query::EndGroup() +{ + queryExpression_.EndGroup(); + return *this; +} + +Query &Query::IsNotNull(const std::string &field) +{ + queryExpression_.IsNotNull(field); + return *this; +} + +Query &Query::PrefixKey(const std::vector &key) +{ + queryExpression_.QueryByPrefixKey(key); + return *this; +} + +Query &Query::SuggestIndex(const std::string &indexName) +{ + queryExpression_.QueryBySuggestIndex(indexName); + return *this; +} + +Query &Query::InKeys(const std::set &keys) +{ + queryExpression_.InKeys(keys); + return *this; +} + +Query &Query::OrderBy(const std::string &field, bool isAsc) +{ + queryExpression_.OrderBy(field, isAsc); + return *this; +} + +Query &Query::Limit(int number, int offset) +{ + queryExpression_.Limit(number, offset); + return *this; +} + +Query &Query::Like(const std::string &field, const std::string &value) +{ + queryExpression_.Like(field, value); + return *this; +} + +Query &Query::NotLike(const std::string &field, const std::string &value) +{ + queryExpression_.NotLike(field, value); + return *this; +} + +Query &Query::IsNull(const std::string &field) +{ + queryExpression_.IsNull(field); + return *this; +} + +Query &Query::And() +{ + queryExpression_.And(); + return *this; +} + +Query &Query::Or() +{ + queryExpression_.Or(); + return *this; +} + +void Query::ExecuteCompareOperation(QueryObjType operType, const std::string &field, const QueryValueType type, + const FieldValue &fieldValue) +{ + switch (operType) { + case QueryObjType::EQUALTO: + queryExpression_.EqualTo(field, type, fieldValue); + break; + case QueryObjType::NOT_EQUALTO: + queryExpression_.NotEqualTo(field, type, fieldValue); + break; + case QueryObjType::GREATER_THAN: + queryExpression_.GreaterThan(field, type, fieldValue); + break; + case QueryObjType::LESS_THAN: + queryExpression_.LessThan(field, type, fieldValue); + break; + case QueryObjType::GREATER_THAN_OR_EQUALTO: + queryExpression_.GreaterThanOrEqualTo(field, type, fieldValue); + break; + case QueryObjType::LESS_THAN_OR_EQUALTO: + queryExpression_.LessThanOrEqualTo(field, type, fieldValue); + break; + default: + return; + } +} + +void Query::ExecuteCompareOperation(QueryObjType operType, const std::string &field, const QueryValueType type, + const std::vector &fieldValues) +{ + switch (operType) { + case QueryObjType::IN: + queryExpression_.In(field, type, fieldValues); + break; + case QueryObjType::NOT_IN: + queryExpression_.NotIn(field, type, fieldValues); + break; + default: + return; + } +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/common/src/query_expression.cpp b/mock/distributeddb/common/src/query_expression.cpp new file mode 100644 index 00000000..6de2914e --- /dev/null +++ b/mock/distributeddb/common/src/query_expression.cpp @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "query_expression.h" +#include "log_print.h" +#include "schema_utils.h" +#include "db_errno.h" + +namespace DistributedDB { +namespace { + const int MAX_OPR_TIMES = 256; +} // namespace + +void QueryExpression::AssemblyQueryInfo(const QueryObjType queryOperType, const std::string& field, + const QueryValueType type, const std::vector &values, bool isNeedFieldPath = true) +{ + if (queryInfo_.size() > MAX_OPR_TIMES) { + SetErrFlag(false); + LOGE("Operate too much times!"); + return; + } + + if (!GetErrFlag()) { + LOGE("Illegal data node!"); + return; + } + + FieldPath outPath; + if (isNeedFieldPath) { + if (SchemaUtils::ParseAndCheckFieldPath(field, outPath) != E_OK) { + SetErrFlag(false); + LOGE("Field path illegal!"); + return; + } + } + std::string formatedField; + if (isTableNameSpecified_) { // remove '$.' prefix in relational query + for (auto it = outPath.begin(); it < outPath.end(); ++it) { + if (it != outPath.begin()) { + formatedField += "."; + } + formatedField += *it; + } + } else { + formatedField = field; + } + queryInfo_.emplace_back(QueryObjNode{queryOperType, formatedField, type, values}); +} + +QueryExpression::QueryExpression() + : errFlag_(true), + tableName_("sync_data"), // default kv type store table name + isTableNameSpecified_(false) // default no specify for kv type store table name +{} + +void QueryExpression::EqualTo(const std::string& field, const QueryValueType type, const FieldValue &value) +{ + std::vector fieldValues{value}; + AssemblyQueryInfo(QueryObjType::EQUALTO, field, type, fieldValues); +} + +void QueryExpression::NotEqualTo(const std::string& field, const QueryValueType type, const FieldValue &value) +{ + std::vector fieldValues{value}; + AssemblyQueryInfo(QueryObjType::NOT_EQUALTO, field, type, fieldValues); +} + +void QueryExpression::GreaterThan(const std::string& field, const QueryValueType type, const FieldValue &value) +{ + if (type == QueryValueType::VALUE_TYPE_BOOL) { + LOGD("Prohibit the use of bool for comparison!"); + SetErrFlag(false); + } + std::vector fieldValues{value}; + AssemblyQueryInfo(QueryObjType::GREATER_THAN, field, type, fieldValues); +} + +void QueryExpression::LessThan(const std::string& field, const QueryValueType type, const FieldValue &value) +{ + if (type == QueryValueType::VALUE_TYPE_BOOL) { + LOGD("Prohibit the use of bool for comparison!"); + SetErrFlag(false); + } + std::vector fieldValues{value}; + AssemblyQueryInfo(QueryObjType::LESS_THAN, field, type, fieldValues); +} + +void QueryExpression::GreaterThanOrEqualTo(const std::string& field, const QueryValueType type, const FieldValue &value) +{ + if (type == QueryValueType::VALUE_TYPE_BOOL) { + LOGD("Prohibit the use of bool for comparison!"); + SetErrFlag(false); + } + std::vector fieldValues{value}; + AssemblyQueryInfo(QueryObjType::GREATER_THAN_OR_EQUALTO, field, type, fieldValues); +} + +void QueryExpression::LessThanOrEqualTo(const std::string& field, const QueryValueType type, const FieldValue &value) +{ + if (type == QueryValueType::VALUE_TYPE_BOOL) { + LOGD("Prohibit the use of bool for comparison!"); + SetErrFlag(false); + } + std::vector fieldValues{value}; + AssemblyQueryInfo(QueryObjType::LESS_THAN_OR_EQUALTO, field, type, fieldValues); +} + +void QueryExpression::OrderBy(const std::string& field, bool isAsc) +{ + FieldValue fieldValue; + fieldValue.boolValue = isAsc; + std::vector fieldValues{fieldValue}; + AssemblyQueryInfo(QueryObjType::ORDERBY, field, QueryValueType::VALUE_TYPE_BOOL, fieldValues); +} + +void QueryExpression::Like(const std::string& field, const std::string &value) +{ + FieldValue fieldValue; + fieldValue.stringValue = value; + std::vector fieldValues{fieldValue}; + AssemblyQueryInfo(QueryObjType::LIKE, field, QueryValueType::VALUE_TYPE_STRING, fieldValues); +} + +void QueryExpression::NotLike(const std::string& field, const std::string &value) +{ + FieldValue fieldValue; + fieldValue.stringValue = value; + std::vector fieldValues{fieldValue}; + AssemblyQueryInfo(QueryObjType::NOT_LIKE, field, QueryValueType::VALUE_TYPE_STRING, fieldValues); +} + +void QueryExpression::Limit(int number, int offset) +{ + FieldValue fieldNumber; + fieldNumber.integerValue = number; + FieldValue fieldOffset; + fieldOffset.integerValue = offset; + std::vector fieldValues{fieldNumber, fieldOffset}; + AssemblyQueryInfo(QueryObjType::LIMIT, std::string(), QueryValueType::VALUE_TYPE_INTEGER, fieldValues, false); +} + +void QueryExpression::IsNull(const std::string& field) +{ + AssemblyQueryInfo(QueryObjType::IS_NULL, field, QueryValueType::VALUE_TYPE_NULL, std::vector()); +} + +void QueryExpression::IsNotNull(const std::string& field) +{ + AssemblyQueryInfo(QueryObjType::IS_NOT_NULL, field, QueryValueType::VALUE_TYPE_NULL, std::vector()); +} + +void QueryExpression::In(const std::string& field, const QueryValueType type, const std::vector &values) +{ + AssemblyQueryInfo(QueryObjType::IN, field, type, values); +} + +void QueryExpression::NotIn(const std::string& field, const QueryValueType type, const std::vector &values) +{ + AssemblyQueryInfo(QueryObjType::NOT_IN, field, type, values); +} + +void QueryExpression::And() +{ + AssemblyQueryInfo(QueryObjType::AND, std::string(), QueryValueType::VALUE_TYPE_NULL, + std::vector(), false); +} + +void QueryExpression::Or() +{ + AssemblyQueryInfo(QueryObjType::OR, std::string(), QueryValueType::VALUE_TYPE_NULL, + std::vector(), false); +} + +void QueryExpression::QueryByPrefixKey(const std::vector &key) +{ + queryInfo_.emplace_back(QueryObjNode{QueryObjType::QUERY_BY_KEY_PREFIX, std::string(), + QueryValueType::VALUE_TYPE_NULL, std::vector()}); + prefixKey_ = key; +} + +void QueryExpression::QueryBySuggestIndex(const std::string &indexName) +{ + queryInfo_.emplace_back(QueryObjNode{QueryObjType::SUGGEST_INDEX, indexName, + QueryValueType::VALUE_TYPE_STRING, std::vector()}); + suggestIndex_ = indexName; +} + +void QueryExpression::InKeys(const std::set &keys) +{ + queryInfo_.emplace_back(QueryObjNode{QueryObjType::IN_KEYS, std::string(), QueryValueType::VALUE_TYPE_NULL, + std::vector()}); + keys_ = keys; +} + +const std::list &QueryExpression::GetQueryExpression() +{ + if (!GetErrFlag()) { + queryInfo_.clear(); + queryInfo_.emplace_back(QueryObjNode{QueryObjType::OPER_ILLEGAL}); + LOGE("Query operate illegal!"); + } + return queryInfo_; +} + +std::vector QueryExpression::GetPreFixKey() const +{ + return prefixKey_; +} + +void QueryExpression::SetTableName(const std::string &tableName) +{ + tableName_ = tableName; + isTableNameSpecified_ = true; +} + +const std::string &QueryExpression::GetTableName() +{ + return tableName_; +} + +bool QueryExpression::IsTableNameSpecified() const +{ + return isTableNameSpecified_; +} + +std::string QueryExpression::GetSuggestIndex() const +{ + return suggestIndex_; +} + +const std::set &QueryExpression::GetKeys() const +{ + return keys_; +} + +void QueryExpression::BeginGroup() +{ + queryInfo_.emplace_back(QueryObjNode{QueryObjType::BEGIN_GROUP, std::string(), + QueryValueType::VALUE_TYPE_NULL, std::vector()}); +} + +void QueryExpression::EndGroup() +{ + queryInfo_.emplace_back(QueryObjNode{QueryObjType::END_GROUP, std::string(), + QueryValueType::VALUE_TYPE_NULL, std::vector()}); +} + +void QueryExpression::Reset() +{ + errFlag_ = true; + queryInfo_.clear(); + prefixKey_.clear(); + prefixKey_.shrink_to_fit(); + suggestIndex_.clear(); + keys_.clear(); +} + +void QueryExpression::SetErrFlag(bool flag) +{ + errFlag_ = flag; +} + +bool QueryExpression::GetErrFlag() +{ + return errFlag_; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/ref_object.cpp b/mock/distributeddb/common/src/ref_object.cpp new file mode 100644 index 00000000..a3cd1076 --- /dev/null +++ b/mock/distributeddb/common/src/ref_object.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "ref_object.h" +#include "log_print.h" + +namespace DistributedDB { +constexpr static int MAX_REF_COUNT = 1024; + +RefObject::AutoLock::AutoLock(const RefObject *obj, bool unlocked) + : refObj_(obj), + IsLocked_(false) +{ + if (refObj_ != nullptr) { + if (unlocked) { + refObj_->LockObj(); + } + IsLocked_ = true; + } +} + +void RefObject::AutoLock::Lock() +{ + if (refObj_ != nullptr) { + if (!IsLocked_) { + refObj_->LockObj(); + IsLocked_ = true; + } else { + LOGE("RefObject-AutoLock: obj' acquires lock more than once."); + } + } +} + +void RefObject::AutoLock::Unlock() +{ + if (refObj_ != nullptr) { + if (IsLocked_) { + refObj_->UnlockObj(); + IsLocked_ = false; + } else { + LOGE("RefObject-AutoLock: obj releases lock more than once."); + } + } +} + +RefObject::AutoLock::~AutoLock() +{ + if (refObj_ != nullptr) { + if (IsLocked_) { + refObj_->UnlockObj(); + IsLocked_ = false; + } + refObj_ = nullptr; + } +} + +RefObject::RefObject() + : refCount_(1), + isKilled_(false) +{} + +RefObject::~RefObject() +{ + int refCount = refCount_.load(std::memory_order_seq_cst); + if (refCount > 0) { + LOGF("object is destructed with ref-count > 0., refCount = %d", refCount); + } +} + +void RefObject::OnLastRef(const std::function &callback) const +{ + if (onLast_) { + std::string tag = GetObjectTag(); + LOGW("%s object set 'OnLastRef()' callback twice.", tag.c_str()); + return; + } + onLast_ = callback; +} + +void RefObject::OnKill(const std::function &callback) +{ + if (onKill_) { + std::string tag = GetObjectTag(); + LOGW("%s object set 'OnKill()' callback twice.", tag.c_str()); + return; + } + onKill_ = callback; +} + +bool RefObject::IsKilled() const +{ + return isKilled_; +} + +void RefObject::KillObj() +{ + std::lock_guard lockGuard(objLock_); + if (!IsKilled()) { + isKilled_ = true; + if (onKill_) { + onKill_(); + } + } +} + +void RefObject::LockObj() const +{ + objLock_.lock(); +} + +void RefObject::UnlockObj() const +{ + objLock_.unlock(); +} + +bool RefObject::WaitLockedUntil(std::condition_variable &cv, + const std::function &condition, int seconds) +{ + // Enter with lock held. + if (!condition) { + return false; + } + + bool waitOk = true; + { + std::unique_lock lock(objLock_, std::adopt_lock_t()); + while (!condition()) { + if (seconds > 0) { + cv.wait_for(lock, std::chrono::seconds(seconds)); + waitOk = condition(); + break; + } else { + cv.wait(lock); + } + } + } + + // Lock has just been dropped in unique_lock::~unique_lock(), + // so we lock it again. + LockObj(); + return waitOk; +} + +void RefObject::IncObjRef(const RefObject *obj) +{ + if (obj == nullptr) { + return; + } + int refCount = obj->refCount_.fetch_add(1, std::memory_order_seq_cst); + if ((refCount <= 0) || (refCount >= MAX_REF_COUNT)) { + std::string tag = obj->GetObjectTag(); + LOGF("%s object is refed with ref-count=%d.", tag.c_str(), refCount); + } +} + +void RefObject::DecObjRef(const RefObject *obj) +{ + if (obj == nullptr) { + return; + } + int refCount = obj->refCount_.fetch_sub(1, std::memory_order_seq_cst); + if (refCount <= 0) { + std::string tag = obj->GetObjectTag(); + LOGF("%s object is unrefed with ref-count(%d) <= 0.", tag.c_str(), refCount); + } else { + if (refCount == 1) { + if (obj->onLast_) { + obj->onLast_(); + } + delete obj; + } + } +} + +void RefObject::KillAndDecObjRef(RefObject *obj) +{ + if (obj == nullptr) { + return; + } + obj->KillObj(); + obj->DecObjRef(obj); + obj = nullptr; +} + +DEFINE_OBJECT_TAG_FACILITIES(RefObject) +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/relational/relational_schema_object.cpp b/mock/distributeddb/common/src/relational/relational_schema_object.cpp new file mode 100644 index 00000000..75e565da --- /dev/null +++ b/mock/distributeddb/common/src/relational/relational_schema_object.cpp @@ -0,0 +1,755 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "relational_schema_object.h" + +#include + +#include "json_object.h" +#include "schema_constant.h" +#include "schema_utils.h" + +namespace DistributedDB { +const std::string &FieldInfo::GetFieldName() const +{ + return fieldName_; +} + +void FieldInfo::SetFieldName(const std::string &fileName) +{ + fieldName_ = fileName; +} + +const std::string &FieldInfo::GetDataType() const +{ + return dataType_; +} + +static StorageType AffinityType(const std::string &dataType) +{ + return StorageType::STORAGE_TYPE_NULL; +} + +void FieldInfo::SetDataType(const std::string &dataType) +{ + dataType_ = dataType; + transform(dataType_.begin(), dataType_.end(), dataType_.begin(), ::tolower); + storageType_ = AffinityType(dataType_); +} + +bool FieldInfo::IsNotNull() const +{ + return isNotNull_; +} + +void FieldInfo::SetNotNull(bool isNotNull) +{ + isNotNull_ = isNotNull; +} + +bool FieldInfo::HasDefaultValue() const +{ + return hasDefaultValue_; +} + +const std::string &FieldInfo::GetDefaultValue() const +{ + return defaultValue_; +} + +void FieldInfo::SetDefaultValue(const std::string &value) +{ + hasDefaultValue_ = true; + defaultValue_ = value; +} + +// convert to StorageType according "Determination Of Column Affinity" +StorageType FieldInfo::GetStorageType() const +{ + return storageType_; +} + +void FieldInfo::SetStorageType(StorageType storageType) +{ + storageType_ = storageType; +} + +int FieldInfo::GetColumnId() const +{ + return cid_; +} + +void FieldInfo::SetColumnId(int cid) +{ + cid_ = cid; +} + +std::string FieldInfo::ToAttributeString() const +{ + std::string attrStr = "\"" + fieldName_ + "\": {"; + attrStr += "\"COLUMN_ID\":" + std::to_string(cid_) + ","; + attrStr += "\"TYPE\":\"" + dataType_ + "\","; + attrStr += "\"NOT_NULL\":" + std::string(isNotNull_ ? "true" : "false"); + if (hasDefaultValue_) { + attrStr += ","; + attrStr += "\"DEFAULT\":\"" + defaultValue_ + "\""; + } + attrStr += "}"; + return attrStr; +} + +int FieldInfo::CompareWithField(const FieldInfo &inField) const +{ + if (fieldName_ != inField.GetFieldName() || dataType_ != inField.GetDataType() || + isNotNull_ != inField.IsNotNull()) { + return false; + } + if (hasDefaultValue_ && inField.HasDefaultValue()) { + return defaultValue_ == inField.GetDefaultValue(); + } + return hasDefaultValue_ == inField.HasDefaultValue(); +} + +const std::string &TableInfo::GetTableName() const +{ + return tableName_; +} + +void TableInfo::SetTableName(const std::string &tableName) +{ + tableName_ = tableName; +} + +void TableInfo::SetAutoIncrement(bool autoInc) +{ + autoInc_ = autoInc; +} + +bool TableInfo::GetAutoIncrement() const +{ + return autoInc_; +} + +const std::string &TableInfo::GetCreateTableSql() const +{ + return sql_; +} + +void TableInfo::SetCreateTableSql(std::string sql) +{ + sql_ = sql; + for (auto &c : sql) { + c = static_cast(std::toupper(c)); + } + if (sql.find("AUTOINCREMENT") != std::string::npos) { + autoInc_ = true; + } +} + +const std::map &TableInfo::GetFields() const +{ + return fields_; +} + +const std::vector &TableInfo::GetFieldInfos() const +{ + if (!fieldInfos_.empty() && fieldInfos_.size() == fields_.size()) { + return fieldInfos_; + } + fieldInfos_.resize(fields_.size()); + if (fieldInfos_.size() != fields_.size()) { + LOGE("GetField error, alloc memory failed."); + return fieldInfos_; + } + for (const auto &entry : fields_) { + if (static_cast(entry.second.GetColumnId()) >= fieldInfos_.size()) { + LOGE("Cid is over field size."); + fieldInfos_.clear(); + return fieldInfos_; + } + fieldInfos_.at(entry.second.GetColumnId()) = entry.second; + } + return fieldInfos_; +} + +std::string TableInfo::GetFieldName(uint32_t cid) const +{ + if (cid >= fields_.size() || GetFieldInfos().empty()) { + return {}; + } + return GetFieldInfos().at(cid).GetFieldName(); +} + +bool TableInfo::IsValid() const +{ + return !tableName_.empty(); +} + +void TableInfo::AddField(const FieldInfo &field) +{ + fields_[field.GetFieldName()] = field; +} + +const std::map &TableInfo::GetIndexDefine() const +{ + return indexDefines_; +} + +void TableInfo::AddIndexDefine(const std::string &indexName, const CompositeFields &indexDefine) +{ + indexDefines_[indexName] = indexDefine; +} + +const FieldName &TableInfo::GetPrimaryKey() const +{ + return primaryKey_; +} + +void TableInfo::SetPrimaryKey(const FieldName &fieldName) +{ + primaryKey_ = fieldName; +} + +void TableInfo::AddFieldDefineString(std::string &attrStr) const +{ + if (fields_.empty()) { + return; + } + attrStr += R"("DEFINE": {)"; + for (auto itField = fields_.begin(); itField != fields_.end(); ++itField) { + attrStr += itField->second.ToAttributeString(); + if (itField != std::prev(fields_.end(), 1)) { + attrStr += ","; + } + } + attrStr += "},"; +} + +void TableInfo::AddIndexDefineString(std::string &attrStr) const +{ + if (indexDefines_.empty()) { + return; + } + attrStr += R"(,"INDEX": {)"; + for (auto itIndexDefine = indexDefines_.begin(); itIndexDefine != indexDefines_.end(); ++itIndexDefine) { + attrStr += "\"" + (*itIndexDefine).first + "\": [\""; + for (auto itField = itIndexDefine->second.begin(); itField != itIndexDefine->second.end(); ++itField) { + attrStr += *itField; + if (itField != itIndexDefine->second.end() - 1) { + attrStr += "\",\""; + } + } + attrStr += "\"]"; + if (itIndexDefine != std::prev(indexDefines_.end(), 1)) { + attrStr += ","; + } + } + attrStr += "}"; +} + +int TableInfo::CompareWithTable(const TableInfo &inTableInfo) const +{ + if (tableName_ != inTableInfo.GetTableName()) { + LOGW("[Relational][Compare] Table name is not same"); + return -E_RELATIONAL_TABLE_INCOMPATIBLE; + } + + if (primaryKey_ != inTableInfo.GetPrimaryKey()) { + LOGW("[Relational][Compare] Table primary key is not same"); + return -E_RELATIONAL_TABLE_INCOMPATIBLE; + } + + int fieldCompareResult = CompareWithTableFields(inTableInfo.GetFields()); + if (fieldCompareResult == -E_RELATIONAL_TABLE_INCOMPATIBLE) { + LOGW("[Relational][Compare] Compare table fields with in table, %d", fieldCompareResult); + return -E_RELATIONAL_TABLE_INCOMPATIBLE; + } + + int indexCompareResult = CompareWithTableIndex(inTableInfo.GetIndexDefine()); + return (fieldCompareResult == -E_RELATIONAL_TABLE_EQUAL) ? indexCompareResult : fieldCompareResult; +} + +int TableInfo::CompareWithTableFields(const std::map &inTableFields) const +{ + auto itLocal = fields_.begin(); + auto itInTable = inTableFields.begin(); + int errCode = -E_RELATIONAL_TABLE_EQUAL; + while (itLocal != fields_.end() && itInTable != inTableFields.end()) { + if (itLocal->first == itInTable->first) { // Same field + if (!itLocal->second.CompareWithField(itInTable->second)) { // Compare field + LOGW("[Relational][Compare] Table field is incompatible"); // not compatible + return -E_RELATIONAL_TABLE_INCOMPATIBLE; + } + itLocal++; // Compare next field + } else { // Assume local table fields is a subset of in table + if (itInTable->second.IsNotNull() && !itInTable->second.HasDefaultValue()) { // Upgrade field not compatible + LOGW("[Relational][Compare] Table upgrade field should allowed to be empty or have default value."); + return -E_RELATIONAL_TABLE_INCOMPATIBLE; + } + errCode = -E_RELATIONAL_TABLE_COMPATIBLE_UPGRADE; + } + itInTable++; // Next in table field + } + + if (itLocal != fields_.end()) { + LOGW("[Relational][Compare] Table field is missing"); + return -E_RELATIONAL_TABLE_INCOMPATIBLE; + } + + if (itInTable == inTableFields.end()) { + return errCode; + } + + while (itInTable != inTableFields.end()) { + if (itInTable->second.IsNotNull() && !itInTable->second.HasDefaultValue()) { + LOGW("[Relational][Compare] Table upgrade field should allowed to be empty or have default value."); + return -E_RELATIONAL_TABLE_INCOMPATIBLE; + } + itInTable++; + } + return -E_RELATIONAL_TABLE_COMPATIBLE_UPGRADE; +} + +int TableInfo::CompareWithTableIndex(const std::map &inTableIndex) const +{ + // Index comparison results do not affect synchronization decisions + auto itLocal = indexDefines_.begin(); + auto itInTable = inTableIndex.begin(); + while (itLocal != indexDefines_.end() && itInTable != inTableIndex.end()) { + if (itLocal->first != itInTable->first || itLocal->second != itInTable->second) { + return -E_RELATIONAL_TABLE_COMPATIBLE; + } + itLocal++; + itInTable++; + } + return (itLocal == indexDefines_.end() && itInTable == inTableIndex.end()) ? -E_RELATIONAL_TABLE_EQUAL : + -E_RELATIONAL_TABLE_COMPATIBLE; +} + +std::string TableInfo::ToTableInfoString() const +{ + std::string attrStr; + attrStr += "{"; + attrStr += R"("NAME": ")" + tableName_ + "\","; + AddFieldDefineString(attrStr); + attrStr += R"("AUTOINCREMENT": )"; + if (autoInc_) { + attrStr += "true,"; + } else { + attrStr += "false,"; + } + if (!primaryKey_.empty()) { + attrStr += R"("PRIMARY_KEY": ")" + primaryKey_ + "\""; + } + AddIndexDefineString(attrStr); + attrStr += "}"; + return attrStr; +} + +std::map TableInfo::GetSchemaDefine() const +{ + std::map schemaDefine; + for (const auto &[fieldName, fieldInfo] : GetFields()) { + FieldValue defaultValue; + defaultValue.stringValue = fieldInfo.GetDefaultValue(); + schemaDefine[std::vector { fieldName }] = SchemaAttribute { + .type = FieldType::LEAF_FIELD_NULL, // For relational schema, the json field type is unimportant. + .isIndexable = true, // For relational schema, all field is indexable. + .hasNotNullConstraint = fieldInfo.IsNotNull(), + .hasDefaultValue = fieldInfo.HasDefaultValue(), + .defaultValue = defaultValue, + .customFieldType = {} + }; + } + return schemaDefine; +} + +bool RelationalSchemaObject::IsSchemaValid() const +{ + return isValid_; +} + +SchemaType RelationalSchemaObject::GetSchemaType() const +{ + return schemaType_; +} + +std::string RelationalSchemaObject::ToSchemaString() const +{ + return schemaString_; +} + +int RelationalSchemaObject::ParseFromSchemaString(const std::string &inSchemaString) +{ + if (isValid_) { + return -E_NOT_PERMIT; + } + + if (inSchemaString.empty() || inSchemaString.size() > SchemaConstant::SCHEMA_STRING_SIZE_LIMIT) { + LOGE("[RelationalSchema][Parse] SchemaSize=%zu is invalid.", inSchemaString.size()); + return -E_INVALID_ARGS; + } + JsonObject schemaObj; + int errCode = schemaObj.Parse(inSchemaString); + if (errCode != E_OK) { + LOGE("[RelationalSchema][Parse] Schema json string parse failed: %d.", errCode); + return errCode; + } + + errCode = ParseRelationalSchema(schemaObj); + if (errCode != E_OK) { + LOGE("[RelationalSchema][Parse] Parse to relational schema failed: %d.", errCode); + return errCode; + } + + schemaType_ = SchemaType::RELATIVE; + schemaString_ = schemaObj.ToString(); + isValid_ = true; + return E_OK; +} + +void RelationalSchemaObject::GenerateSchemaString() +{ + schemaString_ = {}; + schemaString_ += "{"; + schemaString_ += R"("SCHEMA_VERSION":"2.0",)"; + schemaString_ += R"("SCHEMA_TYPE":"RELATIVE",)"; + schemaString_ += R"("TABLES":[)"; + for (auto it = tables_.begin(); it != tables_.end(); it++) { + if (it != tables_.begin()) { + schemaString_ += ","; + } + schemaString_ += it->second.ToTableInfoString(); + } + schemaString_ += R"(])"; + schemaString_ += "}"; +} + +void RelationalSchemaObject::AddRelationalTable(const TableInfo &tb) +{ + tables_[tb.GetTableName()] = tb; + isValid_ = true; + GenerateSchemaString(); +} + + +void RelationalSchemaObject::RemoveRelationalTable(const std::string &tableName) +{ + tables_.erase(tableName); + GenerateSchemaString(); +} + +const std::map &RelationalSchemaObject::GetTables() const +{ + return tables_; +} + +std::vector RelationalSchemaObject::GetTableNames() const +{ + std::vector tableNames; + for (const auto &it : tables_) { + tableNames.emplace_back(it.first); + } + return tableNames; +} + +TableInfo RelationalSchemaObject::GetTable(const std::string &tableName) const +{ + auto it = tables_.find(tableName); + if (it != tables_.end()) { + return it->second; + } + return {}; +} + +int RelationalSchemaObject::CompareAgainstSchemaObject(const std::string &inSchemaString, + std::map &cmpRst) const +{ + return E_OK; +} + +int RelationalSchemaObject::CompareAgainstSchemaObject(const RelationalSchemaObject &inSchemaObject, + std::map &cmpRst) const +{ + return E_OK; +} + +namespace { +int GetMemberFromJsonObject(const JsonObject &inJsonObject, const std::string &fieldName, FieldType expectType, + bool isNecessary, FieldValue &fieldValue) +{ + if (!inJsonObject.IsFieldPathExist(FieldPath {fieldName})) { + if (isNecessary) { + LOGE("[RelationalSchema][Parse] Get schema %s not exist. isNecessary: %d", fieldName.c_str(), isNecessary); + return -E_SCHEMA_PARSE_FAIL; + } + return -E_NOT_FOUND; + } + + FieldType fieldType; + int errCode = inJsonObject.GetFieldTypeByFieldPath(FieldPath {fieldName}, fieldType); + if (errCode != E_OK) { + LOGE("[RelationalSchema][Parse] Get schema %s fieldType failed: %d.", fieldName.c_str(), errCode); + return -E_SCHEMA_PARSE_FAIL; + } + + if (fieldType != expectType) { + LOGE("[RelationalSchema][Parse] Expect %s fieldType %d but: %d.", fieldName.c_str(), + static_cast(expectType), static_cast(fieldType)); + return -E_SCHEMA_PARSE_FAIL; + } + + errCode = inJsonObject.GetFieldValueByFieldPath(FieldPath {fieldName}, fieldValue); + if (errCode != E_OK) { + LOGE("[RelationalSchema][Parse] Get schema %s value failed: %d.", fieldName.c_str(), errCode); + return -E_SCHEMA_PARSE_FAIL; + } + return E_OK; +} +} + +int RelationalSchemaObject::ParseRelationalSchema(const JsonObject &inJsonObject) +{ + int errCode = ParseCheckSchemaVersion(inJsonObject); + if (errCode != E_OK) { + return errCode; + } + errCode = ParseCheckSchemaType(inJsonObject); + if (errCode != E_OK) { + return errCode; + } + return ParseCheckSchemaTableDefine(inJsonObject); +} + +int RelationalSchemaObject::ParseCheckSchemaVersion(const JsonObject &inJsonObject) +{ + FieldValue fieldValue; + int errCode = GetMemberFromJsonObject(inJsonObject, SchemaConstant::KEYWORD_SCHEMA_VERSION, + FieldType::LEAF_FIELD_STRING, true, fieldValue); + if (errCode != E_OK) { + return errCode; + } + + if (SchemaUtils::Strip(fieldValue.stringValue) != SchemaConstant::SCHEMA_SUPPORT_VERSION_V2) { + LOGE("[RelationalSchema][Parse] Unexpected SCHEMA_VERSION=%s.", fieldValue.stringValue.c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + schemaVersion_ = SchemaConstant::SCHEMA_SUPPORT_VERSION_V2; + return E_OK; +} + +int RelationalSchemaObject::ParseCheckSchemaType(const JsonObject &inJsonObject) +{ + FieldValue fieldValue; + int errCode = GetMemberFromJsonObject(inJsonObject, SchemaConstant::KEYWORD_SCHEMA_TYPE, + FieldType::LEAF_FIELD_STRING, true, fieldValue); + if (errCode != E_OK) { + return errCode; + } + + if (SchemaUtils::Strip(fieldValue.stringValue) != SchemaConstant::KEYWORD_TYPE_RELATIVE) { + LOGE("[RelationalSchema][Parse] Unexpected SCHEMA_TYPE=%s.", fieldValue.stringValue.c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + schemaType_ = SchemaType::RELATIVE; + return E_OK; +} + +int RelationalSchemaObject::ParseCheckSchemaTableDefine(const JsonObject &inJsonObject) +{ + FieldType fieldType; + int errCode = inJsonObject.GetFieldTypeByFieldPath(FieldPath {SchemaConstant::KEYWORD_SCHEMA_TABLE}, fieldType); + if (errCode != E_OK) { + LOGE("[RelationalSchema][Parse] Get schema TABLES fieldType failed: %d.", errCode); + return -E_SCHEMA_PARSE_FAIL; + } + if (FieldType::LEAF_FIELD_ARRAY != fieldType) { + LOGE("[RelationalSchema][Parse] Expect TABLES fieldType ARRAY but %s.", + SchemaUtils::FieldTypeString(fieldType).c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + std::vector tables; + errCode = inJsonObject.GetObjectArrayByFieldPath(FieldPath{SchemaConstant::KEYWORD_SCHEMA_TABLE}, tables); + if (errCode != E_OK) { + LOGE("[RelationalSchema][Parse] Get schema TABLES value failed: %d.", errCode); + return -E_SCHEMA_PARSE_FAIL; + } + for (const JsonObject &table : tables) { + errCode = ParseCheckTableInfo(table); + if (errCode != E_OK) { + LOGE("[RelationalSchema][Parse] Parse schema TABLES failed: %d.", errCode); + return errCode; + } + } + return E_OK; +} + +int RelationalSchemaObject::ParseCheckTableInfo(const JsonObject &inJsonObject) +{ + TableInfo resultTable; + int errCode = ParseCheckTableName(inJsonObject, resultTable); + if (errCode != E_OK) { + return errCode; + } + errCode = ParseCheckTableDefine(inJsonObject, resultTable); + if (errCode != E_OK) { + return errCode; + } + errCode = ParseCheckTableAutoInc(inJsonObject, resultTable); + if (errCode != E_OK) { + return errCode; + } + errCode = ParseCheckTablePrimaryKey(inJsonObject, resultTable); + if (errCode != E_OK) { + return errCode; + } + errCode = ParseCheckTableIndex(inJsonObject, resultTable); + if (errCode != E_OK) { + return errCode; + } + tables_[resultTable.GetTableName()] = resultTable; + return E_OK; +} + +int RelationalSchemaObject::ParseCheckTableName(const JsonObject &inJsonObject, TableInfo &resultTable) +{ + FieldValue fieldValue; + int errCode = GetMemberFromJsonObject(inJsonObject, "NAME", FieldType::LEAF_FIELD_STRING, + true, fieldValue); + if (errCode == E_OK) { + resultTable.SetTableName(fieldValue.stringValue); + } + return errCode; +} + +int RelationalSchemaObject::ParseCheckTableDefine(const JsonObject &inJsonObject, TableInfo &resultTable) +{ + std::map tableFields; + int errCode = inJsonObject.GetSubFieldPathAndType(FieldPath {"DEFINE"}, tableFields); + if (errCode != E_OK) { + LOGE("[RelationalSchema][Parse] Get schema TABLES DEFINE failed: %d.", errCode); + return -E_SCHEMA_PARSE_FAIL; + } + + for (const auto &field : tableFields) { + if (field.second != FieldType::INTERNAL_FIELD_OBJECT) { + LOGE("[RelationalSchema][Parse] Expect schema TABLES DEFINE fieldType INTERNAL OBJECT but : %s.", + SchemaUtils::FieldTypeString(field.second).c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + + JsonObject fieldObj; + errCode = inJsonObject.GetObjectByFieldPath(field.first, fieldObj); + if (errCode != E_OK) { + LOGE("[RelationalSchema][Parse] Get table field object failed. %d", errCode); + return errCode; + } + + FieldInfo fieldInfo; + fieldInfo.SetFieldName(field.first[1]); // 1 : table name element in path + errCode = ParseCheckTableFieldInfo(fieldObj, field.first, fieldInfo); + if (errCode != E_OK) { + LOGE("[RelationalSchema][Parse] Parse table field info failed. %d", errCode); + return -E_SCHEMA_PARSE_FAIL; + } + resultTable.AddField(fieldInfo); + } + return E_OK; +} + +int RelationalSchemaObject::ParseCheckTableFieldInfo(const JsonObject &inJsonObject, const FieldPath &path, + FieldInfo &field) +{ + FieldValue fieldValue; + int errCode = GetMemberFromJsonObject(inJsonObject, "COLUMN_ID", FieldType::LEAF_FIELD_INTEGER, true, fieldValue); + if (errCode != E_OK) { + return errCode; + } + field.SetColumnId(fieldValue.integerValue); + + errCode = GetMemberFromJsonObject(inJsonObject, "TYPE", FieldType::LEAF_FIELD_STRING, true, fieldValue); + if (errCode != E_OK) { + return errCode; + } + field.SetDataType(fieldValue.stringValue); + + errCode = GetMemberFromJsonObject(inJsonObject, "NOT_NULL", FieldType::LEAF_FIELD_BOOL, true, fieldValue); + if (errCode != E_OK) { + return errCode; + } + field.SetNotNull(fieldValue.boolValue); + + errCode = GetMemberFromJsonObject(inJsonObject, "DEFAULT", FieldType::LEAF_FIELD_STRING, false, fieldValue); + if (errCode == E_OK) { + field.SetDefaultValue(fieldValue.stringValue); + } else if (errCode != -E_NOT_FOUND) { + return errCode; + } + + return E_OK; +} + +int RelationalSchemaObject::ParseCheckTableAutoInc(const JsonObject &inJsonObject, TableInfo &resultTable) +{ + FieldValue fieldValue; + int errCode = GetMemberFromJsonObject(inJsonObject, "AUTOINCREMENT", FieldType::LEAF_FIELD_BOOL, false, fieldValue); + if (errCode == E_OK) { + resultTable.SetAutoIncrement(fieldValue.boolValue); + } else if (errCode != -E_NOT_FOUND) { + return errCode; + } + return E_OK; +} + +int RelationalSchemaObject::ParseCheckTablePrimaryKey(const JsonObject &inJsonObject, TableInfo &resultTable) +{ + FieldValue fieldValue; + int errCode = GetMemberFromJsonObject(inJsonObject, "PRIMARY_KEY", FieldType::LEAF_FIELD_STRING, false, fieldValue); + if (errCode == E_OK) { + resultTable.SetPrimaryKey(fieldValue.stringValue); + } + return errCode; +} + +int RelationalSchemaObject::ParseCheckTableIndex(const JsonObject &inJsonObject, TableInfo &resultTable) +{ + if (!inJsonObject.IsFieldPathExist(FieldPath {"INDEX"})) { // INDEX is not necessary + return E_OK; + } + std::map tableFields; + int errCode = inJsonObject.GetSubFieldPathAndType(FieldPath {"INDEX"}, tableFields); + if (errCode != E_OK) { + LOGE("[RelationalSchema][Parse] Get schema TABLES INDEX failed: %d.", errCode); + return -E_SCHEMA_PARSE_FAIL; + } + + for (const auto &field : tableFields) { + if (field.second != FieldType::LEAF_FIELD_ARRAY) { + LOGE("[RelationalSchema][Parse] Expect schema TABLES INDEX fieldType ARRAY but : %s.", + SchemaUtils::FieldTypeString(field.second).c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + CompositeFields indexDefine; + errCode = inJsonObject.GetStringArrayByFieldPath(field.first, indexDefine); + if (errCode != E_OK) { + LOGE("[RelationalSchema][Parse] Get schema TABLES INDEX field value failed: %d.", errCode); + return -E_SCHEMA_PARSE_FAIL; + } + resultTable.AddIndexDefine(field.first[1], indexDefine); // 1 : second element in path + } + return E_OK; +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/common/src/runtime_context.cpp b/mock/distributeddb/common/src/runtime_context.cpp new file mode 100644 index 00000000..9acf1e19 --- /dev/null +++ b/mock/distributeddb/common/src/runtime_context.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "runtime_context_impl.h" +#include "version.h" +#include "log_print.h" + +namespace DistributedDB { +RuntimeContext *RuntimeContext::GetInstance() +{ + static char instMemory[sizeof(RuntimeContextImpl)]; + static std::mutex instLock_; + static std::atomic instPtr = nullptr; + // For Double-Checked Locking, we need check insPtr twice + if (instPtr == nullptr) { + std::lock_guard lock(instLock_); + if (instPtr == nullptr) { + // Use instMemory to make sure this singleton not free before other object. + // This operation needn't to malloc memory, we needn't to check nullptr. + instPtr = new (instMemory) RuntimeContextImpl; + LOGI("DistributedDB Version : %s", SOFTWARE_VERSION_STRING.c_str()); + } + } + return instPtr; +} +} // namespace DistributedDB + diff --git a/mock/distributeddb/common/src/runtime_context_impl.cpp b/mock/distributeddb/common/src/runtime_context_impl.cpp new file mode 100644 index 00000000..91a4b45d --- /dev/null +++ b/mock/distributeddb/common/src/runtime_context_impl.cpp @@ -0,0 +1,673 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "runtime_context_impl.h" +#include "db_errno.h" +#include "db_dfx_adapter.h" +#include "log_print.h" +#include "communicator_aggregator.h" +#include "network_adapter.h" + +namespace DistributedDB { +RuntimeContextImpl::RuntimeContextImpl() + : adapter_(nullptr), + communicatorAggregator_(nullptr), + mainLoop_(nullptr), + currentTimerId_(0), + taskPool_(nullptr), + taskPoolReportsTimerId_(0), + timeTickMonitor_(nullptr), + systemApiAdapter_(nullptr), + lockStatusObserver_(nullptr), + currentSessionId_(1) +{ +} + +// Destruct the object. +RuntimeContextImpl::~RuntimeContextImpl() +{ + if (taskPoolReportsTimerId_ > 0) { + RemoveTimer(taskPoolReportsTimerId_, true); + taskPoolReportsTimerId_ = 0; + } + if (taskPool_ != nullptr) { + taskPool_->Stop(); + taskPool_->Release(taskPool_); + taskPool_ = nullptr; + } + if (mainLoop_ != nullptr) { + mainLoop_->KillAndDecObjRef(mainLoop_); + mainLoop_ = nullptr; + } + SetCommunicatorAggregator(nullptr); + (void)SetCommunicatorAdapter(nullptr); + systemApiAdapter_ = nullptr; + delete lockStatusObserver_; + lockStatusObserver_ = nullptr; + userChangeMonitor_ = nullptr; +} + +// Set the label of this process. +void RuntimeContextImpl::SetProcessLabel(const std::string &label) +{ + std::lock_guard labelLock(labelMutex_); + processLabel_ = label; +} + +std::string RuntimeContextImpl::GetProcessLabel() const +{ + std::lock_guard labelLock(labelMutex_); + return processLabel_; +} + +int RuntimeContextImpl::SetCommunicatorAdapter(IAdapter *adapter) +{ + { + std::lock_guard autoLock(communicatorLock_); + if (adapter_ != nullptr) { + if (communicatorAggregator_ != nullptr) { + return -E_NOT_SUPPORT; + } + delete adapter_; + } + adapter_ = adapter; + } + ICommunicatorAggregator *communicatorAggregator = nullptr; + GetCommunicatorAggregator(communicatorAggregator); + autoLaunch_.SetCommunicatorAggregator(communicatorAggregator); + return E_OK; +} + +int RuntimeContextImpl::GetCommunicatorAggregator(ICommunicatorAggregator *&outAggregator) +{ + outAggregator = nullptr; + std::lock_guard lock(communicatorLock_); + if (communicatorAggregator_ != nullptr) { + outAggregator = communicatorAggregator_; + return E_OK; + } + + if (adapter_ == nullptr) { + LOGE("Adapter has not set!"); + return -E_NOT_INIT; + } + + communicatorAggregator_ = new (std::nothrow) CommunicatorAggregator; + if (communicatorAggregator_ == nullptr) { + LOGE("CommunicatorAggregator create failed, may be no available memory!"); + return -E_OUT_OF_MEMORY; + } + + int errCode = communicatorAggregator_->Initialize(adapter_); + if (errCode != E_OK) { + LOGE("CommunicatorAggregator init failed, err = %d!", errCode); + RefObject::KillAndDecObjRef(communicatorAggregator_); + communicatorAggregator_ = nullptr; + } + outAggregator = communicatorAggregator_; + return errCode; +} + +void RuntimeContextImpl::SetCommunicatorAggregator(ICommunicatorAggregator *inAggregator) +{ + std::lock_guard autoLock(communicatorLock_); + if (communicatorAggregator_ != nullptr) { + autoLaunch_.SetCommunicatorAggregator(nullptr); + communicatorAggregator_->Finalize(); + RefObject::KillAndDecObjRef(communicatorAggregator_); + } + communicatorAggregator_ = inAggregator; + autoLaunch_.SetCommunicatorAggregator(communicatorAggregator_); +} + +int RuntimeContextImpl::GetLocalIdentity(std::string &outTarget) +{ + std::lock_guard autoLock(communicatorLock_); + if (communicatorAggregator_ != nullptr) { + return communicatorAggregator_->GetLocalIdentity(outTarget); + } + return -E_NOT_INIT; +} + +// Add and start a timer. +int RuntimeContextImpl::SetTimer(int milliSeconds, const TimerAction &action, + const TimerFinalizer &finalizer, TimerId &timerId) +{ + timerId = 0; + if ((milliSeconds < 0) || !action) { + return -E_INVALID_ARGS; + } + + IEventLoop *loop = nullptr; + int errCode = PrepareLoop(loop); + if (errCode != E_OK) { + LOGE("SetTimer(), prepare loop failed."); + return errCode; + } + + IEvent *evTimer = IEvent::CreateEvent(milliSeconds, errCode); + if (evTimer == nullptr) { + loop->DecObjRef(loop); + loop = nullptr; + return errCode; + } + + errCode = AllocTimerId(evTimer, timerId); + if (errCode != E_OK) { + evTimer->DecObjRef(evTimer); + evTimer = nullptr; + loop->DecObjRef(loop); + loop = nullptr; + return errCode; + } + + evTimer->SetAction([this, timerId, action](EventsMask revents) -> int { + int errCodeInner = action(timerId); + if (errCodeInner != E_OK) { + RemoveTimer(timerId, false); + } + return errCodeInner; + }, + finalizer); + + errCode = loop->Add(evTimer); + if (errCode != E_OK) { + evTimer->IgnoreFinalizer(); + RemoveTimer(timerId, false); + timerId = 0; + } + + loop->DecObjRef(loop); + loop = nullptr; + return errCode; +} + +// Modify the interval of the timer. +int RuntimeContextImpl::ModifyTimer(TimerId timerId, int milliSeconds) +{ + if (milliSeconds < 0) { + return -E_INVALID_ARGS; + } + + std::lock_guard autoLock(timersLock_); + auto iter = timers_.find(timerId); + if (iter == timers_.end()) { + return -E_NO_SUCH_ENTRY; + } + + IEvent *evTimer = iter->second; + if (evTimer == nullptr) { + return -E_INTERNAL_ERROR; + } + return evTimer->SetTimeout(milliSeconds); +} + +// Remove the timer. +void RuntimeContextImpl::RemoveTimer(TimerId timerId, bool wait) +{ + IEvent *evTimer = nullptr; + { + std::lock_guard autoLock(timersLock_); + auto iter = timers_.find(timerId); + if (iter == timers_.end()) { + return; + } + evTimer = iter->second; + timers_.erase(iter); + } + + if (evTimer != nullptr) { + evTimer->Detach(wait); + evTimer->DecObjRef(evTimer); + evTimer = nullptr; + } +} + +// Task interfaces. +int RuntimeContextImpl::ScheduleTask(const TaskAction &task) +{ + std::lock_guard autoLock(taskLock_); + int errCode = PrepareTaskPool(); + if (errCode != E_OK) { + LOGE("Schedule task failed, fail to prepare task pool."); + return errCode; + } + return taskPool_->Schedule(task); +} + +int RuntimeContextImpl::ScheduleQueuedTask(const std::string &queueTag, + const TaskAction &task) +{ + std::lock_guard autoLock(taskLock_); + int errCode = PrepareTaskPool(); + if (errCode != E_OK) { + LOGE("Schedule queued task failed, fail to prepare task pool."); + return errCode; + } + return taskPool_->Schedule(queueTag, task); +} + +void RuntimeContextImpl::ShrinkMemory(const std::string &description) +{ + std::lock_guard autoLock(taskLock_); + if (taskPool_ != nullptr) { + taskPool_->ShrinkMemory(description); + } +} + +NotificationChain::Listener *RuntimeContextImpl::RegisterTimeChangedLister(const TimeChangedAction &action, + int &errCode) +{ + std::lock_guard autoLock(timeTickMonitorLock_); + if (timeTickMonitor_ == nullptr) { + timeTickMonitor_ = std::make_unique(); + errCode = timeTickMonitor_->Start(); + if (errCode != E_OK) { + LOGE("TimeTickMonitor start failed!"); + timeTickMonitor_ = nullptr; + return nullptr; + } + } + return timeTickMonitor_->RegisterTimeChangedLister(action, errCode); +} + +int RuntimeContextImpl::PrepareLoop(IEventLoop *&loop) +{ + std::lock_guard autoLock(loopLock_); + if (mainLoop_ != nullptr) { + loop = mainLoop_; + loop->IncObjRef(loop); // ref 1 returned to caller. + return E_OK; + } + + int errCode = E_OK; + loop = IEventLoop::CreateEventLoop(errCode); + if (loop == nullptr) { + return errCode; + } + + loop->IncObjRef(loop); // ref 1 owned by thread. + std::thread loopThread([loop]() { + loop->Run(); + loop->DecObjRef(loop); // ref 1 dropped by thread. + }); + loopThread.detach(); + + mainLoop_ = loop; + loop->IncObjRef(loop); // ref 1 returned to caller. + return E_OK; +} + +int RuntimeContextImpl::PrepareTaskPool() +{ + if (taskPool_ != nullptr) { + return E_OK; + } + + int errCode = E_OK; + TaskPool *taskPool = TaskPool::Create(MAX_TP_THREADS, MIN_TP_THREADS, errCode); + if (taskPool == nullptr) { + return errCode; + } + + errCode = taskPool->Start(); + if (errCode != E_OK) { + taskPool->Release(taskPool); + return errCode; + } + + taskPool_ = taskPool; + return E_OK; +} + +int RuntimeContextImpl::AllocTimerId(IEvent *evTimer, TimerId &timerId) +{ + if (evTimer == nullptr) { + return -E_INVALID_ARGS; + } + + std::lock_guard autoLock(timersLock_); + TimerId startId = currentTimerId_; + while (++currentTimerId_ != startId) { + if (currentTimerId_ == 0) { + continue; + } + if (timers_.find(currentTimerId_) == timers_.end()) { + timerId = currentTimerId_; + timers_[timerId] = evTimer; + return E_OK; + } + } + return -E_OUT_OF_IDS; +} + +int RuntimeContextImpl::SetPermissionCheckCallback(const PermissionCheckCallback &callback) +{ + std::unique_lock writeLock(permissionCheckCallbackMutex_); + permissionCheckCallback_ = callback; + LOGI("SetPermissionCheckCallback ok"); + return E_OK; +} + +int RuntimeContextImpl::SetPermissionCheckCallback(const PermissionCheckCallbackV2 &callback) +{ + std::unique_lock writeLock(permissionCheckCallbackMutex_); + permissionCheckCallbackV2_ = callback; + LOGI("SetPermissionCheckCallback V2 ok"); + return E_OK; +} + +int RuntimeContextImpl::RunPermissionCheck(const std::string &userId, const std::string &appId, + const std::string &storeId, const std::string &deviceId, uint8_t flag) const +{ + bool checkResult = false; + std::shared_lock autoLock(permissionCheckCallbackMutex_); + if (permissionCheckCallbackV2_) { + checkResult = permissionCheckCallbackV2_(userId, appId, storeId, deviceId, flag); + if (checkResult) { + return E_OK; + } else { + return -E_NOT_PERMIT; + } + } else if (permissionCheckCallback_) { + checkResult = permissionCheckCallback_(userId, appId, storeId, flag); + if (checkResult) { + return E_OK; + } else { + return -E_NOT_PERMIT; + } + } else { + return E_OK; + } +} + +int RuntimeContextImpl::EnableKvStoreAutoLaunch(const KvDBProperties &properties, AutoLaunchNotifier notifier, + const AutoLaunchOption &option) +{ + return autoLaunch_.EnableKvStoreAutoLaunch(properties, notifier, option); +} + +int RuntimeContextImpl::DisableKvStoreAutoLaunch(const std::string &normalIdentifier, + const std::string &dualTupleIdentifier, const std::string &userId) +{ + return autoLaunch_.DisableKvStoreAutoLaunch(normalIdentifier, dualTupleIdentifier, userId); +} + +void RuntimeContextImpl::GetAutoLaunchSyncDevices(const std::string &identifier, + std::vector &devices) const +{ + return autoLaunch_.GetAutoLaunchSyncDevices(identifier, devices); +} + +void RuntimeContextImpl::SetAutoLaunchRequestCallback(const AutoLaunchRequestCallback &callback, DBType type) +{ + autoLaunch_.SetAutoLaunchRequestCallback(callback, type); +} + +NotificationChain::Listener *RuntimeContextImpl::RegisterLockStatusLister(const LockStatusNotifier &action, + int &errCode) +{ + std::lock(lockStatusLock_, systemApiAdapterLock_); + std::lock_guard lockStatusLock(lockStatusLock_, std::adopt_lock); + std::lock_guard systemApiAdapterLock(systemApiAdapterLock_, std::adopt_lock); + if (lockStatusObserver_ == nullptr) { + lockStatusObserver_ = new (std::nothrow) LockStatusObserver(); + if (lockStatusObserver_ == nullptr) { + LOGE("lockStatusObserver_ is nullptr"); + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + } + + if (!lockStatusObserver_->IsStarted()) { + errCode = lockStatusObserver_->Start(); + if (errCode != E_OK) { + LOGE("lockStatusObserver start failed, err = %d", errCode); + delete lockStatusObserver_; + lockStatusObserver_ = nullptr; + return nullptr; + } + + if (systemApiAdapter_ != nullptr) { + auto callback = std::bind(&LockStatusObserver::OnStatusChange, + lockStatusObserver_, std::placeholders::_1); + errCode = systemApiAdapter_->RegOnAccessControlledEvent(callback); + if (errCode != OK) { + LOGE("Register access control event change failed, err = %d", errCode); + delete lockStatusObserver_; + lockStatusObserver_ = nullptr; + return nullptr; + } + } + } + + NotificationChain::Listener *listener = lockStatusObserver_->RegisterLockStatusChangedLister(action, errCode); + if ((listener == nullptr) || (errCode != E_OK)) { + LOGE("Register lock status changed listener failed, err = %d", errCode); + delete lockStatusObserver_; + lockStatusObserver_ = nullptr; + return nullptr; + } + return listener; +} + +bool RuntimeContextImpl::IsAccessControlled() const +{ + std::lock_guard autoLock(systemApiAdapterLock_); + if (systemApiAdapter_ == nullptr) { + return false; + } + return systemApiAdapter_->IsAccessControlled(); +} + +int RuntimeContextImpl::SetSecurityOption(const std::string &filePath, const SecurityOption &option) const +{ + std::lock_guard autoLock(systemApiAdapterLock_); + if (systemApiAdapter_ == nullptr || !OS::CheckPathExistence(filePath)) { + LOGI("Adapter is not set, or path not existed, not support set security option!"); + return -E_NOT_SUPPORT; + } + + if (option == SecurityOption()) { + LOGD("SecurityOption is NOT_SET,Not need to set security option!"); + return E_OK; + } + + std::string fileRealPath; + int errCode = OS::GetRealPath(filePath, fileRealPath); + if (errCode != E_OK) { + LOGE("Get real path failed when set security option!"); + return errCode; + } + + errCode = systemApiAdapter_->SetSecurityOption(fileRealPath, option); + if (errCode != OK) { + if (errCode == NOT_SUPPORT) { + return -E_NOT_SUPPORT; + } + LOGE("SetSecurityOption failed, errCode = %d", errCode); + return -E_SYSTEM_API_ADAPTER_CALL_FAILED; + } + return E_OK; +} + +int RuntimeContextImpl::GetSecurityOption(const std::string &filePath, SecurityOption &option) const +{ + std::lock_guard autoLock(systemApiAdapterLock_); + if (systemApiAdapter_ == nullptr) { + LOGI("Get Security option, but not set system api adapter!"); + return -E_NOT_SUPPORT; + } + int errCode = systemApiAdapter_->GetSecurityOption(filePath, option); + if (errCode != OK) { + if (errCode == NOT_SUPPORT) { + return -E_NOT_SUPPORT; + } + LOGE("GetSecurityOption failed, errCode = %d", errCode); + return -E_SYSTEM_API_ADAPTER_CALL_FAILED; + } + + LOGD("Get security option from system adapter [%d, %d]", option.securityLabel, option.securityFlag); + // This interface may return success but failed to obtain the flag and modified it to -1 + if (option.securityFlag == INVALID_SEC_FLAG) { + // Currently ignoring the failure to obtain flags -1 other than S3, modify the flag to the default value + if (option.securityLabel == S3) { + LOGE("GetSecurityOption failed, SecurityOption is invalid [3, -1]!"); + return -E_SYSTEM_API_ADAPTER_CALL_FAILED; + } + option.securityFlag = 0; // 0 is default value + } + return E_OK; +} + +bool RuntimeContextImpl::CheckDeviceSecurityAbility(const std::string &devId, const SecurityOption &option) const +{ + std::lock_guard autoLock(systemApiAdapterLock_); + if (systemApiAdapter_ == nullptr) { + return true; + } + return systemApiAdapter_->CheckDeviceSecurityAbility(devId, option); +} + +int RuntimeContextImpl::SetProcessSystemApiAdapter(const std::shared_ptr &adapter) +{ + std::lock(lockStatusLock_, systemApiAdapterLock_); + std::lock_guard lockStatusLock(lockStatusLock_, std::adopt_lock); + std::lock_guard systemApiAdapterLock(systemApiAdapterLock_, std::adopt_lock); + systemApiAdapter_ = adapter; + if (systemApiAdapter_ != nullptr && lockStatusObserver_ != nullptr && lockStatusObserver_->IsStarted()) { + auto callback = std::bind(&LockStatusObserver::OnStatusChange, + lockStatusObserver_, std::placeholders::_1); + int errCode = systemApiAdapter_->RegOnAccessControlledEvent(callback); + if (errCode != OK) { + LOGE("Register access controlled event failed while setting adapter, err = %d", errCode); + delete lockStatusObserver_; + lockStatusObserver_ = nullptr; + return -E_SYSTEM_API_ADAPTER_CALL_FAILED; + } + } + return E_OK; +} + +bool RuntimeContextImpl::IsProcessSystemApiAdapterValid() const +{ + std::lock_guard autoLock(systemApiAdapterLock_); + return (systemApiAdapter_ != nullptr); +} + +void RuntimeContextImpl::NotifyTimestampChanged(TimeOffset offset) const +{ + std::lock_guard autoLock(timeTickMonitorLock_); + if (timeTickMonitor_ == nullptr) { + LOGD("NotifyTimestampChanged fail, timeTickMonitor_ is null."); + return; + } + timeTickMonitor_->NotifyTimeChange(offset); +} + +bool RuntimeContextImpl::IsCommunicatorAggregatorValid() const +{ + std::lock_guard autoLock(communicatorLock_); + if (communicatorAggregator_ == nullptr && adapter_ == nullptr) { + return false; + } + return true; +} + +void RuntimeContextImpl::SetStoreStatusNotifier(const StoreStatusNotifier ¬ifier) +{ + std::unique_lock writeLock(databaseStatusCallbackMutex_); + databaseStatusNotifyCallback_ = notifier; + LOGI("SetStoreStatusNotifier ok"); +} + +void RuntimeContextImpl::NotifyDatabaseStatusChange(const std::string &userId, const std::string &appId, + const std::string &storeId, const std::string &deviceId, bool onlineStatus) +{ + ScheduleTask([this, userId, appId, storeId, deviceId, onlineStatus] { + std::shared_lock autoLock(databaseStatusCallbackMutex_); + if (databaseStatusNotifyCallback_) { + LOGI("start notify database status:%d", onlineStatus); + databaseStatusNotifyCallback_(userId, appId, storeId, deviceId, onlineStatus); + } + }); +} + +int RuntimeContextImpl::SetSyncActivationCheckCallback(const SyncActivationCheckCallback &callback) +{ + std::unique_lock writeLock(syncActivationCheckCallbackMutex_); + syncActivationCheckCallback_ = callback; + LOGI("SetSyncActivationCheckCallback ok"); + return E_OK; +} + +bool RuntimeContextImpl::IsSyncerNeedActive(std::string &userId, std::string &appId, std::string &storeId) const +{ + std::shared_lock autoLock(syncActivationCheckCallbackMutex_); + if (syncActivationCheckCallback_) { + return syncActivationCheckCallback_(userId, appId, storeId); + } + return true; +} + +NotificationChain::Listener *RuntimeContextImpl::RegisterUserChangedListerner(const UserChangedAction &action, + EventType event) +{ + int errCode; + std::lock_guard autoLock(userChangeMonitorLock_); + if (userChangeMonitor_ == nullptr) { + userChangeMonitor_ = std::make_unique(); + errCode = userChangeMonitor_->Start(); + if (errCode != E_OK) { + LOGE("UserChangeMonitor start failed!"); + userChangeMonitor_ = nullptr; + return nullptr; + } + } + NotificationChain::Listener *listener = userChangeMonitor_->RegisterUserChangedListerner(action, event, errCode); + if ((listener == nullptr) || (errCode != E_OK)) { + LOGE("Register user status changed listener failed, err = %d", errCode); + return nullptr; + } + return listener; +} + +int RuntimeContextImpl::NotifyUserChanged() const +{ + { + std::lock_guard autoLock(userChangeMonitorLock_); + if (userChangeMonitor_ == nullptr) { + LOGD("userChangeMonitor is null, all db is in normal sync mode"); + return E_OK; + } + } + userChangeMonitor_->NotifyUserChanged(); + return E_OK; +} + +uint32_t RuntimeContextImpl::GenerateSessionId() +{ + uint32_t sessionId = currentSessionId_++; + if (sessionId == 0) { + sessionId = currentSessionId_++; + } + return sessionId; +} + +void RuntimeContextImpl::DumpCommonInfo(int fd) +{ + autoLaunch_.Dump(fd); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/runtime_context_impl.h b/mock/distributeddb/common/src/runtime_context_impl.h new file mode 100644 index 00000000..c36fe9a1 --- /dev/null +++ b/mock/distributeddb/common/src/runtime_context_impl.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RUNTIME_CONTEXT_IMPL_H +#define RUNTIME_CONTEXT_IMPL_H + +#include +#include +#include + +#include "runtime_context.h" +#include "task_pool.h" +#include "evloop/include/ievent.h" +#include "evloop/include/ievent_loop.h" +#include "lock_status_observer.h" +#include "time_tick_monitor.h" +#include "icommunicator_aggregator.h" +#include "auto_launch.h" +#include "user_change_monitor.h" + +namespace DistributedDB { +class RuntimeContextImpl final : public RuntimeContext { +public: + RuntimeContextImpl(); + ~RuntimeContextImpl() override; + + // Get/Set the label of this process. + void SetProcessLabel(const std::string &label) override; + std::string GetProcessLabel() const override; + int SetCommunicatorAdapter(IAdapter *adapter) override; + int GetCommunicatorAggregator(ICommunicatorAggregator *&outAggregator) override; + void SetCommunicatorAggregator(ICommunicatorAggregator *inAggregator) override; + int GetLocalIdentity(std::string &outTarget) override; + // Add and start a timer. + int SetTimer(int milliSeconds, const TimerAction &action, + const TimerFinalizer &finalizer, TimerId &timerId) override; + + // Modify the interval of the timer. + int ModifyTimer(TimerId timerId, int milliSeconds) override; + + // Remove the timer. + void RemoveTimer(TimerId timerId, bool wait) override; + + // Task interfaces. + int ScheduleTask(const TaskAction &task) override; + int ScheduleQueuedTask(const std::string &queueTag, const TaskAction &task) override; + + // Shrink as much memory as possible. + void ShrinkMemory(const std::string &description) override; + + // Register a time changed lister, it will be callback when local time changed. + NotificationChain::Listener *RegisterTimeChangedLister(const TimeChangedAction &action, int &errCode) override; + + int SetPermissionCheckCallback(const PermissionCheckCallback &callback) override; + + int SetPermissionCheckCallback(const PermissionCheckCallbackV2 &callback) override; + + int RunPermissionCheck(const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) const override; + + int EnableKvStoreAutoLaunch(const KvDBProperties &properties, AutoLaunchNotifier notifier, + const AutoLaunchOption &option) override; + + int DisableKvStoreAutoLaunch(const std::string &normalIdentifier, const std::string &dualTupleIdentifier, + const std::string &userId) override; + + void GetAutoLaunchSyncDevices(const std::string &identifier, std::vector &devices) const override; + + void SetAutoLaunchRequestCallback(const AutoLaunchRequestCallback &callback, DBType type) override; + + NotificationChain::Listener *RegisterLockStatusLister(const LockStatusNotifier &action, int &errCode) override; + + bool IsAccessControlled() const override; + + int SetSecurityOption(const std::string &filePath, const SecurityOption &option) const override; + + int GetSecurityOption(const std::string &filePath, SecurityOption &option) const override; + + bool CheckDeviceSecurityAbility(const std::string &devId, const SecurityOption &option) const override; + + int SetProcessSystemApiAdapter(const std::shared_ptr &adapter) override; + + bool IsProcessSystemApiAdapterValid() const override; + + bool IsCommunicatorAggregatorValid() const override; + + // Notify TIME_CHANGE_EVENT. + void NotifyTimestampChanged(TimeOffset offset) const override; + + void SetStoreStatusNotifier(const StoreStatusNotifier ¬ifier) override; + + void NotifyDatabaseStatusChange(const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, bool onlineStatus) override; + + int SetSyncActivationCheckCallback(const SyncActivationCheckCallback &callback) override; + + bool IsSyncerNeedActive(std::string &userId, std::string &appId, std::string &storeId) const override; + + // Register a user changed lister, it will be callback when user change. + NotificationChain::Listener *RegisterUserChangedListerner(const UserChangedAction &action, + EventType event) override; + // Notify TIME_CHANGE_EVENT. + int NotifyUserChanged() const override; + + uint32_t GenerateSessionId() override; + + void DumpCommonInfo(int fd) override; +private: + static constexpr int MAX_TP_THREADS = 10; // max threads of the task pool. + static constexpr int MIN_TP_THREADS = 1; // min threads of the task pool. + static constexpr int TASK_POOL_REPORTS_INTERVAL = 10000; // task pool reports its state every 10 seconds. + + int PrepareLoop(IEventLoop *&loop); + int PrepareTaskPool(); + int AllocTimerId(IEvent *evTimer, TimerId &timerId); + + // Context fields + mutable std::mutex labelMutex_; + std::string processLabel_; + + // Communicator + mutable std::mutex communicatorLock_; + IAdapter *adapter_; + ICommunicatorAggregator *communicatorAggregator_; + + // Loop and timer + mutable std::mutex loopLock_; + IEventLoop *mainLoop_; + std::mutex timersLock_; + TimerId currentTimerId_; + std::map timers_; + + // Task pool + std::mutex taskLock_; + TaskPool *taskPool_; + TimerId taskPoolReportsTimerId_; + + // TimeTick + mutable std::mutex timeTickMonitorLock_; + std::unique_ptr timeTickMonitor_; + + mutable std::shared_mutex permissionCheckCallbackMutex_{}; + PermissionCheckCallback permissionCheckCallback_; + PermissionCheckCallbackV2 permissionCheckCallbackV2_; + + AutoLaunch autoLaunch_; + + // System api + mutable std::recursive_mutex systemApiAdapterLock_; + std::shared_ptr systemApiAdapter_; + mutable std::mutex lockStatusLock_; // Mutex for lockStatusObserver_. + LockStatusObserver *lockStatusObserver_; + + mutable std::shared_mutex databaseStatusCallbackMutex_{}; + StoreStatusNotifier databaseStatusNotifyCallback_; + + mutable std::shared_mutex syncActivationCheckCallbackMutex_{}; + SyncActivationCheckCallback syncActivationCheckCallback_; + + mutable std::mutex userChangeMonitorLock_; + std::unique_ptr userChangeMonitor_; + + std::atomic currentSessionId_; +}; +} // namespace DistributedDB + +#endif // RUNTIME_CONTEXT_IMPL_H diff --git a/mock/distributeddb/common/src/schema_constant.cpp b/mock/distributeddb/common/src/schema_constant.cpp new file mode 100644 index 00000000..d833a7ee --- /dev/null +++ b/mock/distributeddb/common/src/schema_constant.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "schema_constant.h" + +namespace DistributedDB { +const std::string SchemaConstant::KEYWORD_SCHEMA_VERSION = "SCHEMA_VERSION"; +const std::string SchemaConstant::KEYWORD_SCHEMA_MODE = "SCHEMA_MODE"; +const std::string SchemaConstant::KEYWORD_SCHEMA_DEFINE = "SCHEMA_DEFINE"; +const std::string SchemaConstant::KEYWORD_SCHEMA_INDEXES = "SCHEMA_INDEXES"; +const std::string SchemaConstant::KEYWORD_SCHEMA_SKIPSIZE = "SCHEMA_SKIPSIZE"; +const std::string SchemaConstant::KEYWORD_SCHEMA_TYPE = "SCHEMA_TYPE"; +const std::string SchemaConstant::KEYWORD_SCHEMA_TABLE = "TABLES"; +const std::string SchemaConstant::KEYWORD_INDEX = "INDEX"; // For FlatBuffer-Schema + +const std::string SchemaConstant::KEYWORD_MODE_STRICT = "STRICT"; +const std::string SchemaConstant::KEYWORD_MODE_COMPATIBLE = "COMPATIBLE"; + +const std::string SchemaConstant::KEYWORD_TYPE_BOOL = "BOOL"; +const std::string SchemaConstant::KEYWORD_TYPE_INTEGER = "INTEGER"; +const std::string SchemaConstant::KEYWORD_TYPE_LONG = "LONG"; +const std::string SchemaConstant::KEYWORD_TYPE_DOUBLE = "DOUBLE"; +const std::string SchemaConstant::KEYWORD_TYPE_STRING = "STRING"; + +const std::string SchemaConstant::KEYWORD_ATTR_NOT_NULL = "NOT NULL"; +const std::string SchemaConstant::KEYWORD_ATTR_DEFAULT = "DEFAULT"; +const std::string SchemaConstant::KEYWORD_ATTR_VALUE_NULL = "null"; +const std::string SchemaConstant::KEYWORD_ATTR_VALUE_TRUE = "true"; +const std::string SchemaConstant::KEYWORD_ATTR_VALUE_FALSE = "false"; + +const std::string SchemaConstant::KEYWORD_TYPE_RELATIVE = "RELATIVE"; +const std::string SchemaConstant::SCHEMA_SUPPORT_VERSION = "1.0"; +const std::string SchemaConstant::SCHEMA_SUPPORT_VERSION_V2 = "2.0"; + +const uint32_t SchemaConstant::SCHEMA_META_FEILD_COUNT_MAX = 5; +const uint32_t SchemaConstant::SCHEMA_META_FEILD_COUNT_MIN = 3; +const uint32_t SchemaConstant::SCHEMA_FEILD_NAME_LENGTH_MAX = 64; +const uint32_t SchemaConstant::SCHEMA_FEILD_NAME_LENGTH_MIN = 1; +const uint32_t SchemaConstant::SCHEMA_FEILD_NAME_COUNT_MAX = 256; +const uint32_t SchemaConstant::SCHEMA_FEILD_NAME_COUNT_MIN = 1; +const uint32_t SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX = 4; +const uint32_t SchemaConstant::SCHEMA_INDEX_COUNT_MAX = 32; +const uint32_t SchemaConstant::SCHEMA_STRING_SIZE_LIMIT = 524288; // 512K +const uint32_t SchemaConstant::SCHEMA_DEFAULT_STRING_SIZE_LIMIT = 4096; // 4K +const uint32_t SchemaConstant::SCHEMA_SKIPSIZE_MAX = 4194302; // 4M - 2 Bytes + +const uint32_t SchemaConstant::SECURE_BYTE_ALIGN = 8; // 8 bytes align +} // namespace DistributedDB + diff --git a/mock/distributeddb/common/src/schema_negotiate.cpp b/mock/distributeddb/common/src/schema_negotiate.cpp new file mode 100644 index 00000000..ad031644 --- /dev/null +++ b/mock/distributeddb/common/src/schema_negotiate.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "schema_negotiate.h" + +#include "log_print.h" +#include "schema_utils.h" + +namespace DistributedDB { +// Some principle in current version describe below. (Relative-type will be introduced in future but not involved now) +// 1. PermitSync: Be false may because schemaType-unrecognized, schemaType-different, schema-unparsable, +// schemaVersion-unrecognized, schema-incompatible, and so on. +// 2. RequirePeerConvert: Be true normally when permitSync false, for future possible sync and convert(by remote). +// 3. checkOnReceive: Be false when local is KV-DB, or when local is not KV-DB only if schema type equal as well as +// define equal or remote is the upgradation of local. +SyncOpinion SchemaNegotiate::MakeLocalSyncOpinion(const SchemaObject &localSchema, const std::string &remoteSchema, + uint8_t remoteSchemaType) +{ + SchemaType localType = localSchema.GetSchemaType(); // An invalid schemaObject will return SchemaType::NONE + SchemaType remoteType = ReadSchemaType(remoteSchemaType); + // Logic below only be correct in current version, should be redesigned if new type added in the future + // 1. If remote-type unrecognized(Include Relative-type), Do not permit sync. + if (remoteType == SchemaType::UNRECOGNIZED) { + LOGE("[Schema][Opinion] Remote-type=%" PRIu8 " unrecognized.", remoteSchemaType); + return SyncOpinion{false, true, true}; + } + // 2. If local-type is KV(Here remote-type is within recognized), Always permit sync. + if (localType == SchemaType::NONE) { + LOGI("[Schema][Opinion] Local-type KV."); + return SyncOpinion{true, false, false}; + } + // 3. If remote-type is KV(Here local-type can only be JSON or FLATBUFFER), Always permit sync but need check. + if (remoteType == SchemaType::NONE) { + LOGI("[Schema][Opinion] Remote-type KV."); + return SyncOpinion{true, false, true}; + } + // 4. If local-type differ with remote-type(Here both type can only be JSON or FLATBUFFER), Do not permit sync. + if (localType != remoteType) { + LOGE("[Schema][Opinion] Local-type=%s differ remote-type=%s.", SchemaUtils::SchemaTypeString(localType).c_str(), + SchemaUtils::SchemaTypeString(remoteType).c_str()); + return SyncOpinion{false, true, true}; + } + // 5. If schema parse fail, Do not permit sync. + SchemaObject remoteSchemaObj; + int errCode = remoteSchemaObj.ParseFromSchemaString(remoteSchema); + if (errCode != E_OK) { + LOGE("[Schema][Opinion] Parse remote-schema fail, errCode=%d, remote-type=%s.", errCode, + SchemaUtils::SchemaTypeString(remoteType).c_str()); + return SyncOpinion{false, true, true}; + } + // 6. If remote-schema is not incompatible based on local-schema(SchemaDefine Equal), Permit sync and don't check. + errCode = localSchema.CompareAgainstSchemaObject(remoteSchemaObj); + if (errCode != -E_SCHEMA_UNEQUAL_INCOMPATIBLE) { + return SyncOpinion{true, false, false}; + } + // 7. If local-schema is not incompatible based on remote-schema(Can only be COMPATIBLE_UPGRADE), Sync and check. + errCode = remoteSchemaObj.CompareAgainstSchemaObject(localSchema); + if (errCode != -E_SCHEMA_UNEQUAL_INCOMPATIBLE) { + return SyncOpinion{true, false, true}; + } + // 8. Local-schema incompatible with remote-schema mutually. + LOGE("[Schema][Opinion] Local-schema incompatible with remote-schema mutually."); + return SyncOpinion{false, true, true}; +} + +SyncStrategy SchemaNegotiate::ConcludeSyncStrategy(const SyncOpinion &localOpinion, const SyncOpinion &remoteOpinion) +{ + SyncStrategy outStrategy; + // Any side permit sync, the final conclusion is permit sync. + outStrategy.permitSync = (localOpinion.permitSync || remoteOpinion.permitSync); + bool convertConflict = (localOpinion.requirePeerConvert && remoteOpinion.requirePeerConvert); + if (convertConflict) { + outStrategy.permitSync = false; + } + // Responsible for conversion on send now that local do not require remote to do conversion + outStrategy.convertOnSend = (!localOpinion.requirePeerConvert); + // Responsible for conversion on receive since remote will not do conversion on send and require local to convert + outStrategy.convertOnReceive = remoteOpinion.requirePeerConvert; + // Only depend on local opinion + outStrategy.checkOnReceive = localOpinion.checkOnReceive; + LOGI("[Schema][Strategy] PermitSync=%d, SendConvert=%d, ReceiveConvert=%d, ReceiveCheck=%d.", + outStrategy.permitSync, outStrategy.convertOnSend, outStrategy.convertOnReceive, outStrategy.checkOnReceive); + return outStrategy; +} + +RelationalSyncOpinion SchemaNegotiate::MakeLocalSyncOpinion(const RelationalSchemaObject &localSchema, + const std::string &remoteSchema, uint8_t remoteSchemaType) +{ + SchemaType localType = localSchema.GetSchemaType(); + SchemaType remoteType = ReadSchemaType(remoteSchemaType); + if (remoteType == SchemaType::UNRECOGNIZED) { + LOGW("[RelationalSchema][opinion] Remote schema type %d is unrecognized.", remoteSchemaType); + return {}; + } + + if (remoteType != SchemaType::RELATIVE) { + LOGW("[RelationalSchema][opinion] Not support sync with schema type: local-type=[%s] remote-type=[%s]", + SchemaUtils::SchemaTypeString(localType).c_str(), SchemaUtils::SchemaTypeString(remoteType).c_str()); + return {}; + } + + if (!localSchema.IsSchemaValid()) { + LOGW("[RelationalSchema][opinion] Local schema is not valid"); + return {}; + } + + RelationalSchemaObject remoteSchemaObj; + int errCode = remoteSchemaObj.ParseFromSchemaString(remoteSchema); + if (errCode != E_OK) { + LOGW("[RelationalSchema][opinion] Parse remote schema failed %d, remote schema type %s", errCode, + SchemaUtils::SchemaTypeString(remoteType).c_str()); + return {}; + } + + RelationalSyncOpinion opinion; + for (const auto &it : localSchema.GetTables()) { + if (remoteSchemaObj.GetTable(it.first).GetTableName() != it.first) { + LOGW("[RelationalSchema][opinion] Table was missing in remote schema"); + continue; + } + // remote table is compatible(equal or upgrade) based on local table, permit sync and don't need check + errCode = it.second.CompareWithTable(remoteSchemaObj.GetTable(it.first)); + if (errCode != -E_RELATIONAL_TABLE_INCOMPATIBLE) { + opinion[it.first] = {true, false, false}; + continue; + } + // local table is compatible upgrade based on remote table, permit sync and need check + errCode = remoteSchemaObj.GetTable(it.first).CompareWithTable(it.second); + if (errCode != -E_RELATIONAL_TABLE_INCOMPATIBLE) { + opinion[it.first] = {true, false, true}; + continue; + } + // local table is incompatible with remote table mutually, don't permit sync and need check + LOGW("[RelationalSchema][opinion] Local table is incompatible with remote table mutually."); + opinion[it.first] = {false, true, true}; + } + + return opinion; +} + +RelationalSyncStrategy SchemaNegotiate::ConcludeSyncStrategy(const RelationalSyncOpinion &localOpinion, + const RelationalSyncOpinion &remoteOpinion) +{ + RelationalSyncStrategy syncStrategy; + for (const auto &itLocal : localOpinion) { + if (remoteOpinion.find(itLocal.first) == remoteOpinion.end()) { + LOGW("[RelationalSchema][Strategy] Table opinion is not found from remote."); + continue; + } + SyncOpinion localTableOpinion = itLocal.second; + SyncOpinion remoteTableOpinion = remoteOpinion.at(itLocal.first); + syncStrategy[itLocal.first] = ConcludeSyncStrategy(localTableOpinion, remoteTableOpinion); + } + + return syncStrategy; +} + +namespace { + const std::string MAGIC = "relational_opinion"; + const uint32_t SYNC_OPINION_VERSION = 1; +} // namespace + + +uint32_t SchemaNegotiate::CalculateParcelLen(const RelationalSyncOpinion &opinions) +{ + uint64_t len = Parcel::GetStringLen(MAGIC); + len += Parcel::GetUInt32Len(); + len += Parcel::GetUInt32Len(); + len = Parcel::GetEightByteAlign(len); + for (const auto &it : opinions) { + len += Parcel::GetStringLen(it.first); + len += Parcel::GetUInt32Len(); + len += Parcel::GetUInt32Len(); + len = Parcel::GetEightByteAlign(len); + } + if (len > UINT32_MAX) { + return 0; + } + return static_cast(len); +} + +int SchemaNegotiate::SerializeData(const RelationalSyncOpinion &opinions, Parcel &parcel) +{ + (void)parcel.WriteString(MAGIC); + (void)parcel.WriteUInt32(SYNC_OPINION_VERSION); + (void)parcel.WriteUInt32(static_cast(opinions.size())); + (void)parcel.EightByteAlign(); + for (const auto &it : opinions) { + (void)parcel.WriteString(it.first); + (void)parcel.WriteUInt32(it.second.permitSync); + (void)parcel.WriteUInt32(it.second.requirePeerConvert); + (void)parcel.EightByteAlign(); + } + return parcel.IsError() ? -E_INVALID_ARGS : E_OK; +} + +int SchemaNegotiate::DeserializeData(Parcel &parcel, RelationalSyncOpinion &opinion) +{ + if (!parcel.IsContinueRead()) { + return E_OK; + } + std::string magicStr; + (void)parcel.ReadString(magicStr); + if (magicStr != MAGIC) { + LOGE("Deserialize sync opinion failed while read MAGIC string [%s]", magicStr.c_str()); + return -E_INVALID_ARGS; + } + uint32_t version; + (void)parcel.ReadUInt32(version); + if (version != SYNC_OPINION_VERSION) { + LOGE("Not support sync opinion version: %u", version); + return -E_NOT_SUPPORT; + } + uint32_t opinionSize; + (void)parcel.ReadUInt32(opinionSize); + (void)parcel.EightByteAlign(); + static const uint32_t MAX_OPINION_SIZE = 1024; // max 1024 opinions + if (parcel.IsError() || opinionSize > MAX_OPINION_SIZE) { + return -E_INVALID_ARGS; + } + for (uint32_t i = 0; i < opinionSize; i++) { + std::string tableName; + SyncOpinion tableOpinion; + (void)parcel.ReadString(tableName); + uint32_t permitSync; + (void)parcel.ReadUInt32(permitSync); + tableOpinion.permitSync = static_cast(permitSync); + uint32_t requirePeerConvert; + (void)parcel.ReadUInt32(requirePeerConvert); + tableOpinion.requirePeerConvert = static_cast(requirePeerConvert); + (void)parcel.EightByteAlign(); + opinion[tableName] = tableOpinion; + } + return parcel.IsError() ? -E_INVALID_ARGS : E_OK; +} +} \ No newline at end of file diff --git a/mock/distributeddb/common/src/schema_object.cpp b/mock/distributeddb/common/src/schema_object.cpp new file mode 100644 index 00000000..e8bb982d --- /dev/null +++ b/mock/distributeddb/common/src/schema_object.cpp @@ -0,0 +1,1152 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "schema_object.h" +#include "schema_utils.h" +#include "db_errno.h" +#include "log_print.h" +#include "schema_constant.h" + +namespace DistributedDB { +namespace { +const std::string JSON_EXTRACT_FUNC_NAME = "json_extract_by_path"; +const std::string FLATBUFFER_EXTRACT_FUNC_NAME = "flatbuffer_extract_by_path"; + +// For Json-Schema, display its original content before parse. For FlatBuffer-Schema, only display its parsed content. +void DisplaySchemaLineByLine(SchemaType inType, const std::string &inSchema) +{ + constexpr uint32_t lengthPerLine = 400; // 400 char per line + constexpr uint32_t usualMaxLine = 25; // For normal schema, 25 line for 10k length is quite enough + LOGD("[Schema][Display] IS %s, LENGTH=%zu.", SchemaUtils::SchemaTypeString(inType).c_str(), inSchema.size()); + uint32_t totalLine = (inSchema.size() + lengthPerLine - 1) / lengthPerLine; + for (uint32_t line = 0; line < totalLine; line++) { + if (line >= usualMaxLine) { + LOGD("......(UNCOMPLETED SCHEMA)"); + break; + } + std::string lineStr = inSchema.substr(line * lengthPerLine, lengthPerLine); + LOGD("%s", lineStr.c_str()); + } +} +} + +std::string SchemaObject::GetExtractFuncName(SchemaType inSchemaType) +{ + if (inSchemaType == SchemaType::JSON) { + return JSON_EXTRACT_FUNC_NAME; + } else { + return FLATBUFFER_EXTRACT_FUNC_NAME; + } +} + +std::string SchemaObject::GenerateExtractSQL(SchemaType inSchemaType, const FieldPath &inFieldpath, + FieldType inFieldType, uint32_t skipSize, const std::string &accessStr) +{ + static std::map fieldTypeMapSQLiteType { + {FieldType::LEAF_FIELD_BOOL, "INT"}, + {FieldType::LEAF_FIELD_INTEGER, "INT"}, + {FieldType::LEAF_FIELD_LONG, "INT"}, + {FieldType::LEAF_FIELD_DOUBLE, "REAL"}, + {FieldType::LEAF_FIELD_STRING, "TEXT"}, + }; + if (inFieldpath.empty()) { + LOGE("[Schema][GenExtract] Path empty."); + return ""; + } + if (fieldTypeMapSQLiteType.count(inFieldType) == 0) { + LOGE("[Schema][GenExtract] FieldType not support."); + return ""; + } + std::string resultSql = " CAST("; // Reserve blank at begin for convenience. + resultSql += GetExtractFuncName(inSchemaType); + resultSql += "(" + accessStr + "value, '"; + resultSql += SchemaUtils::FieldPathString(inFieldpath); + resultSql += "', "; + resultSql += std::to_string(skipSize); + resultSql += ") AS "; + resultSql += fieldTypeMapSQLiteType[inFieldType]; + resultSql += ") "; // Reserve blank at end for convenience. + return resultSql; +} + +SchemaObject::SchemaObject() : flatbufferSchema_(*this) {}; + +SchemaObject::SchemaObject(const SchemaObject &other) + : flatbufferSchema_(*this) +{ + isValid_ = other.isValid_; + schemaType_ = other.schemaType_; + schemaString_ = other.schemaString_; + schemaVersion_ = other.schemaVersion_; + schemaMode_ = other.schemaMode_; + schemaSkipSize_ = other.schemaSkipSize_; + schemaIndexes_ = other.schemaIndexes_; + schemaDefine_ = other.schemaDefine_; +} + +SchemaObject& SchemaObject::operator=(const SchemaObject &other) +{ + if (&other != this) { + isValid_ = other.isValid_; + schemaType_ = other.schemaType_; + flatbufferSchema_.CopyFrom(other.flatbufferSchema_); + schemaString_ = other.schemaString_; + schemaVersion_ = other.schemaVersion_; + schemaMode_ = other.schemaMode_; + schemaSkipSize_ = other.schemaSkipSize_; + schemaIndexes_ = other.schemaIndexes_; + schemaDefine_ = other.schemaDefine_; + } + return *this; +} + +#ifdef RELATIONAL_STORE +SchemaObject::SchemaObject(const TableInfo &tableInfo) : flatbufferSchema_(*this) +{ + isValid_ = true; + schemaType_ = SchemaType::NONE; // Default NONE + schemaVersion_ = "1.0"; + SchemaDefine schemaDefine = tableInfo.GetSchemaDefine(); + schemaDefine_.insert({ 0, schemaDefine }); +} +#endif // RELATIONAL_STORE + +int SchemaObject::ParseFromSchemaString(const std::string &inSchemaString) +{ + if (isValid_) { + return -E_NOT_PERMIT; + } + + // Judge whether it is FlatBuffer-Schema then check the schema-size first + SchemaType estimateType = SchemaType::JSON; // Estimate as JSON type firstly + std::string decoded; + if (FlatBufferSchema::IsFlatBufferSchema(inSchemaString, decoded)) { + estimateType = SchemaType::FLATBUFFER; + LOGD("[Schema][Parse] FlatBuffer-Type, Decode before=%zu, after=%zu.", inSchemaString.size(), decoded.size()); + } + const std::string &oriSchema = ((estimateType == SchemaType::FLATBUFFER) ? decoded : inSchemaString); + if (oriSchema.size() > SchemaConstant::SCHEMA_STRING_SIZE_LIMIT) { + LOGE("[Schema][Parse] SchemaSize=%zu Too Large.", oriSchema.size()); + return -E_INVALID_ARGS; + } + + // Parse the corresponding type schema + if (estimateType == SchemaType::FLATBUFFER) { + int errCode = flatbufferSchema_.ParseFlatBufferSchema(oriSchema); + if (errCode != E_OK) { + return errCode; + } + DisplaySchemaLineByLine(SchemaType::FLATBUFFER, flatbufferSchema_.GetDescription()); + schemaType_ = SchemaType::FLATBUFFER; + schemaString_ = oriSchema; + } else { + DisplaySchemaLineByLine(SchemaType::JSON, oriSchema); + JsonObject schemaJson; + int errCode = schemaJson.Parse(oriSchema); + if (errCode != E_OK) { + LOGE("[Schema][Parse] Json parse schema fail, errCode=%d, Not FlatBuffer Not Json.", errCode); + return errCode; + } + errCode = ParseJsonSchema(schemaJson); + if (errCode != E_OK) { + return errCode; + } + schemaType_ = SchemaType::JSON; + schemaString_ = schemaJson.ToString(); // Save the minify type of version string + } + + isValid_ = true; + return E_OK; +} + +bool SchemaObject::IsSchemaValid() const +{ + return isValid_; +} + +SchemaType SchemaObject::GetSchemaType() const +{ + return schemaType_; +} + +std::string SchemaObject::ToSchemaString() const +{ + return schemaString_; +} + +uint32_t SchemaObject::GetSkipSize() const +{ + return schemaSkipSize_; +} + +std::map SchemaObject::GetIndexInfo() const +{ + if (!isValid_) { + // An invalid SchemaObject may contain some dirty info produced by failed parse. + return std::map(); + } + return schemaIndexes_; +} + +bool SchemaObject::IsIndexExist(const IndexName &indexName) const +{ + if (!isValid_) { + return false; + } + return (schemaIndexes_.count(indexName) != 0); +} + +int SchemaObject::CheckQueryableAndGetFieldType(const FieldPath &inPath, FieldType &outType) const +{ + if (inPath.empty()) { + return -E_INVALID_ARGS; + } + if (schemaDefine_.count(inPath.size() - 1) == 0) { + return -E_NOT_FOUND; + } + if (schemaDefine_.at(inPath.size() - 1).count(inPath) == 0) { + return -E_NOT_FOUND; + } + const SchemaAttribute &targetAttr = schemaDefine_.at(inPath.size() - 1).at(inPath); + outType = targetAttr.type; + return (targetAttr.isIndexable ? E_OK : -E_NOT_SUPPORT); +} + +int SchemaObject::CompareAgainstSchemaString(const std::string &inSchemaString) const +{ + IndexDifference indexDiffer; + return CompareAgainstSchemaString(inSchemaString, indexDiffer); +} + +int SchemaObject::CompareAgainstSchemaString(const std::string &inSchemaString, IndexDifference &indexDiffer) const +{ + if (!isValid_) { + return -E_NOT_PERMIT; + } + SchemaObject newSchema; + int errCode = newSchema.ParseFromSchemaString(inSchemaString); + if (errCode != E_OK) { + return errCode; + } + return CompareAgainstSchemaObject(newSchema, indexDiffer); +} + +int SchemaObject::CompareAgainstSchemaObject(const SchemaObject &inSchemaObject) const +{ + IndexDifference indexDiffer; + return CompareAgainstSchemaObject(inSchemaObject, indexDiffer); +} + +int SchemaObject::CompareAgainstSchemaObject(const SchemaObject &inSchemaObject, IndexDifference &indexDiffer) const +{ + if (!isValid_ || !inSchemaObject.isValid_) { + return -E_NOT_PERMIT; + } + if (schemaType_ != inSchemaObject.schemaType_) { + LOGE("[Schema][Compare] Self is %s, other is %s.", SchemaUtils::SchemaTypeString(schemaType_).c_str(), + SchemaUtils::SchemaTypeString(inSchemaObject.schemaType_).c_str()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + + // Return E_SCHEMA_EQUAL_EXACTLY or E_SCHEMA_UNEQUAL_INCOMPATIBLE + int verModeResult = CompareSchemaVersionMode(inSchemaObject); + if (verModeResult == -E_SCHEMA_UNEQUAL_INCOMPATIBLE) { + return verModeResult; + } + + // Return E_SCHEMA_EQUAL_EXACTLY or E_SCHEMA_UNEQUAL_INCOMPATIBLE + int skipSizeResult = CompareSchemaSkipSize(inSchemaObject); + if (skipSizeResult == -E_SCHEMA_UNEQUAL_INCOMPATIBLE) { + return skipSizeResult; + } + + // Return E_SCHEMA_EQUAL_EXACTLY or E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE or E_SCHEMA_UNEQUAL_INCOMPATIBLE + int defineResult; + if (schemaType_ == SchemaType::JSON) { + defineResult = CompareSchemaDefine(inSchemaObject); + } else { + defineResult = flatbufferSchema_.CompareFlatBufferDefine(inSchemaObject.flatbufferSchema_); + } + if (defineResult == -E_SCHEMA_UNEQUAL_INCOMPATIBLE) { + return defineResult; + } + + // Return E_SCHEMA_EQUAL_EXACTLY or E_SCHEMA_UNEQUAL_COMPATIBLE + int indexResult = CompareSchemaIndexes(inSchemaObject, indexDiffer); + return ((defineResult == -E_SCHEMA_EQUAL_EXACTLY) ? indexResult : defineResult); +} + +int SchemaObject::CheckValueAndAmendIfNeed(ValueSource sourceType, ValueObject &inValue) const +{ + if (!isValid_ || schemaType_ != SchemaType::JSON) { // Currently this methed only support Json-Schema + return -E_NOT_PERMIT; + } + + std::set lackingPaths; + int errCode = CheckValue(inValue, lackingPaths); + if (errCode != -E_VALUE_MATCH) { + return errCode; + } + + bool amended = false; + errCode = AmendValueIfNeed(inValue, lackingPaths, amended); + if (errCode != E_OK) { // Unlikely + LOGE("[Schema][CheckAmend] Amend fail, errCode=%d, srcType=%d.", errCode, static_cast(sourceType)); + return -E_INTERNAL_ERROR; + } + return (amended ? -E_VALUE_MATCH_AMENDED : -E_VALUE_MATCH); +} + +int SchemaObject::VerifyValue(ValueSource sourceType, const Value &inValue) const +{ + return VerifyValue(sourceType, RawValue{inValue.data(), inValue.size()}); +} + +int SchemaObject::VerifyValue(ValueSource sourceType, const RawValue &inValue) const +{ + if (inValue.first == nullptr) { + return -E_INVALID_ARGS; + } + if (!isValid_ || schemaType_ != SchemaType::FLATBUFFER) { + return -E_NOT_PERMIT; + } + if (inValue.second <= schemaSkipSize_) { + LOGE("[Schema][Verify] Value length=%" PRIu32 " invalid, skipsize=%" PRIu32, inValue.second, schemaSkipSize_); + return -E_FLATBUFFER_VERIFY_FAIL; + } + + RawValue rawValue; + std::vector cache; + if (schemaSkipSize_ % SchemaConstant::SECURE_BYTE_ALIGN == 0) { + rawValue = {inValue.first + schemaSkipSize_, inValue.second - schemaSkipSize_}; + } else { + cache.assign(inValue.first + schemaSkipSize_, inValue.first + inValue.second); + rawValue = {cache.data(), cache.size()}; + } + + // Currently do not try no sizePrefix, future may depend on sourceType + int errCode = flatbufferSchema_.VerifyFlatBufferValue(rawValue, false); + if (errCode != E_OK) { + LOGE("[Schema][Verify] Value verify fail, srcType=%d.", static_cast(sourceType)); + return errCode; + } + return E_OK; +} + +int SchemaObject::ExtractValue(ValueSource sourceType, RawString inPath, const RawValue &inValue, + TypeValue &outExtract, std::vector *cache) const +{ + // NOTE!!! This function is performance sensitive !!! Carefully not to allocate memory often!!! + if (!isValid_ || schemaType_ != SchemaType::FLATBUFFER) { + return -E_NOT_PERMIT; + } + if (inPath == nullptr || inValue.first == nullptr) { + return -E_INVALID_ARGS; + } + if (inValue.second <= schemaSkipSize_) { + LOGE("[Schema][Extract] Value length=%u invalid, skipsize=%u.", inValue.second, schemaSkipSize_); + return -E_FLATBUFFER_VERIFY_FAIL; + } + + RawValue rawValue; + std::vector *tempCache = nullptr; // A temporary cache for use when input cache can not hold. + if (schemaSkipSize_ % SchemaConstant::SECURE_BYTE_ALIGN == 0) { + rawValue = {inValue.first + schemaSkipSize_, inValue.second - schemaSkipSize_}; + } else if ((cache != nullptr) && (cache->size() >= (inValue.second - schemaSkipSize_))) { + // Do not expand the cache if it can not hold + cache->assign(inValue.first + schemaSkipSize_, inValue.first + inValue.second); + rawValue = {cache->data(), inValue.second - schemaSkipSize_}; // Attention: Do not use cache.size() as second. + } else { + // Use a temporary cache, which will release its memory quickly + tempCache = new (std::nothrow) std::vector; + if (tempCache == nullptr) { + LOGE("[Schema][Extract] OOM."); + return -E_OUT_OF_MEMORY; + } + tempCache->resize(inValue.second - schemaSkipSize_); + tempCache->assign(inValue.first + schemaSkipSize_, inValue.first + inValue.second); + rawValue = {tempCache->data(), tempCache->size()}; + } + + // Currently do not try no sizePrefix, future may depend on sourceType + int errCode = flatbufferSchema_.ExtractFlatBufferValue(inPath, rawValue, outExtract, false); + if (errCode != E_OK) { + LOGE("[Schema][Extract] Fail, path=%s, srcType=%d.", inPath, static_cast(sourceType)); + } + delete tempCache; // delete nullptr is safe + tempCache = nullptr; + return errCode; +} + +int SchemaObject::ParseJsonSchema(const JsonObject &inJsonObject) +{ + // Parse and check mandatory metaField below + int errCode = CheckMetaFieldCountAndType(inJsonObject); + if (errCode != E_OK) { + return errCode; + } + errCode = ParseCheckSchemaVersionMode(inJsonObject); + if (errCode != E_OK) { + return errCode; + } + errCode = ParseCheckSchemaDefine(inJsonObject); + if (errCode != E_OK) { + return errCode; + } + // Parse and check optional metaField below + errCode = ParseCheckSchemaIndexes(inJsonObject); + if (errCode != E_OK) { + return errCode; + } + errCode = ParseCheckSchemaSkipSize(inJsonObject); + if (errCode != E_OK) { + return errCode; + } + return E_OK; +} + +namespace { +int CheckOptionalMetaFieldCountAndType(const std::map &metaFieldPathType) +{ + uint32_t indexMetaFieldCount = 0; + uint32_t skipSizeMetaFieldCount = 0; + if (metaFieldPathType.count(FieldPath{SchemaConstant::KEYWORD_SCHEMA_INDEXES}) != 0) { + indexMetaFieldCount++; + FieldType type = metaFieldPathType.at(FieldPath{SchemaConstant::KEYWORD_SCHEMA_INDEXES}); + if (type != FieldType::LEAF_FIELD_ARRAY) { + LOGE("[Schema][CheckMeta] Expect SCHEMA_INDEXES type ARRAY but %s.", + SchemaUtils::FieldTypeString(type).c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + } + if (metaFieldPathType.count(FieldPath{SchemaConstant::KEYWORD_SCHEMA_SKIPSIZE}) != 0) { + skipSizeMetaFieldCount++; + FieldType type = metaFieldPathType.at(FieldPath{SchemaConstant::KEYWORD_SCHEMA_SKIPSIZE}); + if (type != FieldType::LEAF_FIELD_INTEGER) { + LOGE("[Schema][CheckMeta] Expect SCHEMA_SKIPSIZE type INTEGER but %s.", + SchemaUtils::FieldTypeString(type).c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + } + if (metaFieldPathType.size() != (SchemaConstant::SCHEMA_META_FEILD_COUNT_MIN + indexMetaFieldCount + + skipSizeMetaFieldCount)) { + LOGE("[Schema][CheckMeta] Unrecognized metaField exist: total=%zu, indexField=%" PRIu32 ", skipSizeField=%" + PRIu32, metaFieldPathType.size(), indexMetaFieldCount, skipSizeMetaFieldCount); + return -E_SCHEMA_PARSE_FAIL; + } + return E_OK; +} +} + +int SchemaObject::CheckMetaFieldCountAndType(const JsonObject& inJsonObject) const +{ + std::map metaFieldPathType; + int errCode = inJsonObject.GetSubFieldPathAndType(FieldPath(), metaFieldPathType); + if (errCode != E_OK) { + LOGE("[Schema][CheckMeta] GetSubFieldPathAndType fail, errCode=%d.", errCode); + return errCode; + } + if (metaFieldPathType.size() < SchemaConstant::SCHEMA_META_FEILD_COUNT_MIN || + metaFieldPathType.size() > SchemaConstant::SCHEMA_META_FEILD_COUNT_MAX) { + LOGE("[Schema][CheckMeta] Unexpected metafield count=%zu.", metaFieldPathType.size()); + return -E_SCHEMA_PARSE_FAIL; + } + // Check KeyWord SCHEMA_VERSION + if (metaFieldPathType.count(FieldPath{SchemaConstant::KEYWORD_SCHEMA_VERSION}) == 0) { + LOGE("[Schema][CheckMeta] Expect metafield SCHEMA_VERSION but not find."); + return -E_SCHEMA_PARSE_FAIL; + } + FieldType type = metaFieldPathType.at(FieldPath{SchemaConstant::KEYWORD_SCHEMA_VERSION}); + if (type != FieldType::LEAF_FIELD_STRING) { + LOGE("[Schema][CheckMeta] Expect SCHEMA_VERSION type STRING but %s.", + SchemaUtils::FieldTypeString(type).c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + // Check KeyWord SCHEMA_MODE + if (metaFieldPathType.count(FieldPath{SchemaConstant::KEYWORD_SCHEMA_MODE}) == 0) { + LOGE("[Schema][CheckMeta] Expect metafield SCHEMA_MODE but not find."); + return -E_SCHEMA_PARSE_FAIL; + } + type = metaFieldPathType.at(FieldPath{SchemaConstant::KEYWORD_SCHEMA_MODE}); + if (type != FieldType::LEAF_FIELD_STRING) { + LOGE("[Schema][CheckMeta] Expect SCHEMA_MODE type STRING but %s.", SchemaUtils::FieldTypeString(type).c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + // Check KeyWord SCHEMA_DEFINE + if (metaFieldPathType.count(FieldPath{SchemaConstant::KEYWORD_SCHEMA_DEFINE}) == 0) { + LOGE("[Schema][CheckMeta] Expect metafield SCHEMA_DEFINE but not find."); + return -E_SCHEMA_PARSE_FAIL; + } + type = metaFieldPathType.at(FieldPath{SchemaConstant::KEYWORD_SCHEMA_DEFINE}); + if (type != FieldType::INTERNAL_FIELD_OBJECT) { // LEAF_FIELD_OBJECT indicate an empty object which is not allowed + LOGE("[Schema][CheckMeta] Expect SCHEMA_DEFINE type INTERNAL_OBJECT but %s.", + SchemaUtils::FieldTypeString(type).c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + // Check KeyWord SCHEMA_INDEXES If Need + return CheckOptionalMetaFieldCountAndType(metaFieldPathType); +} + +int SchemaObject::ParseCheckSchemaVersionMode(const JsonObject& inJsonObject) +{ + // Note: it has been checked in CheckMetaFieldCountAndType that SCHEMA_VERSION field exists and its type is string. + FieldValue versionValue; + int errCode = inJsonObject.GetFieldValueByFieldPath(FieldPath{SchemaConstant::KEYWORD_SCHEMA_VERSION}, + versionValue); + if (errCode != E_OK) { + return -E_INTERNAL_ERROR; + } + if (SchemaUtils::Strip(versionValue.stringValue) != SchemaConstant::SCHEMA_SUPPORT_VERSION) { + LOGE("[Schema][ParseVerMode] Unexpected SCHEMA_VERSION=%s.", versionValue.stringValue.c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + schemaVersion_ = SchemaConstant::SCHEMA_SUPPORT_VERSION; + + // Note: it has been checked in CheckMetaFieldCountAndType that SCHEMA_MODE field exists and its type is string. + FieldValue modeValue; + errCode = inJsonObject.GetFieldValueByFieldPath(FieldPath{SchemaConstant::KEYWORD_SCHEMA_MODE}, modeValue); + if (errCode != E_OK) { + return -E_INTERNAL_ERROR; + } + std::string modeStripped = SchemaUtils::Strip(modeValue.stringValue); + if (modeStripped != SchemaConstant::KEYWORD_MODE_STRICT && + modeStripped != SchemaConstant::KEYWORD_MODE_COMPATIBLE) { + LOGE("[Schema][ParseVerMode] Unexpected SCHEMA_MODE=%s.", modeValue.stringValue.c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + schemaMode_ = ((modeStripped == SchemaConstant::KEYWORD_MODE_STRICT) ? SchemaMode::STRICT : SchemaMode::COMPATIBLE); + return E_OK; +} + +int SchemaObject::ParseCheckSchemaDefine(const JsonObject& inJsonObject) +{ + // Clear schemaDefine_ to recover from a fail parse + schemaDefine_.clear(); + // Note: it has been checked in CheckMetaFieldCountAndType that SCHEMA_DEFINE field exists and its type is + // internal-object. Nest path refer to those field with type internal object that has sub field. + std::set nestPathCurDepth{FieldPath{SchemaConstant::KEYWORD_SCHEMA_DEFINE}}; + uint32_t fieldNameCount = 0; + for (uint32_t depth = 0; depth < SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX; depth++) { + std::map subPathType; + int errCode = inJsonObject.GetSubFieldPathAndType(nestPathCurDepth, subPathType); + if (errCode != E_OK) { // Unlikely + LOGE("[Schema][ParseDefine] Internal Error: GetSubFieldPathAndType Fail, Depth=%u.", depth); + return -E_INTERNAL_ERROR; + } + fieldNameCount += subPathType.size(); + nestPathCurDepth.clear(); // Clear it for collecting new nestPath + for (const auto &subField : subPathType) { + SchemaAttribute attribute; + errCode = CheckSchemaDefineItemDecideAttribute(inJsonObject, subField.first, subField.second, attribute); + if (errCode != E_OK) { + LOGE("[Schema][ParseDefine] CheckSchemaDefineItemDecideAttribute Fail, Path=%s.", + SchemaUtils::FieldPathString(subField.first).c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + // If everything ok, insert this schema item into schema define + // Remember to remove SCHEMA_DEFINE in the front of the fieldpath + schemaDefine_[depth][FieldPath(++(subField.first.begin()), subField.first.end())] = attribute; + // Deal with the nestpath and check depth limitation + if (subField.second == FieldType::INTERNAL_FIELD_OBJECT) { + if (depth == SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX - 1) { // Minus 1 to be the boundary + LOGE("[Schema][ParseDefine] Path=%s is INTERNAL_FIELD_OBJECT but reach schema depth limitation.", + SchemaUtils::FieldPathString(subField.first).c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + nestPathCurDepth.insert(subField.first); + } + } + // If no deeper schema define, quit loop in advance + if (nestPathCurDepth.empty()) { + break; + } + } + if (fieldNameCount > SchemaConstant::SCHEMA_FEILD_NAME_COUNT_MAX) { + // Check Field Count Here + LOGE("[Schema][ParseDefine] FieldName count=%u exceed the limitation.", fieldNameCount); + return -E_SCHEMA_PARSE_FAIL; + } + return E_OK; +} + +int SchemaObject::CheckSchemaDefineItemDecideAttribute(const JsonObject& inJsonObject, const FieldPath &inPath, + FieldType inType, SchemaAttribute &outAttr) const +{ + // Note: inPath will never be an empty vector, internal logic guarantee it, see the caller logic + if (inPath.empty()) { // Not Possible. Just For Clear CodeDEX. + return -E_INTERNAL_ERROR; + } + int errCode = SchemaUtils::CheckFieldName(inPath.back()); + if (errCode != E_OK) { + LOGE("[Schema][CheckItemDecideAttr] Invalid fieldName=%s, errCode=%d.", inPath.back().c_str(), errCode); + return -E_SCHEMA_PARSE_FAIL; + } + if (inType == FieldType::LEAF_FIELD_STRING) { + FieldValue subFieldValue; + errCode = inJsonObject.GetFieldValueByFieldPath(inPath, subFieldValue); + if (errCode != E_OK) { // Unlikely + LOGE("[Schema][CheckItemDecideAttr] Internal Error: GetFieldValueByFieldPath Fail."); + return -E_INTERNAL_ERROR; + } + errCode = SchemaUtils::ParseAndCheckSchemaAttribute(subFieldValue.stringValue, outAttr); + if (errCode != E_OK) { + LOGE("[Schema][CheckItemDecideAttr] ParseAndCheckSchemaAttribute Fail, errCode=%d.", errCode); + return -E_SCHEMA_PARSE_FAIL; + } + // The ParseAndCheckSchemaAttribute do not cope with isIndexable field. Need to set it true here + outAttr.isIndexable = true; + } else if (inType == FieldType::LEAF_FIELD_ARRAY) { + uint32_t arraySize = 0; + errCode = inJsonObject.GetArraySize(inPath, arraySize); + if (errCode != E_OK) { + LOGE("[Schema][CheckItemDecideAttr] Internal Error: GetArraySize Fail."); + return -E_INTERNAL_ERROR; + } + if (arraySize != 0) { + LOGE("[Schema][CheckItemDecideAttr] Expect array empty but size=%u.", arraySize); + return -E_SCHEMA_PARSE_FAIL; + } + outAttr = SchemaAttribute{inType, false, false, false, FieldValue()}; + } else if (inType == FieldType::LEAF_FIELD_OBJECT) { + outAttr = SchemaAttribute{inType, false, false, false, FieldValue()}; + } else if (inType == FieldType::INTERNAL_FIELD_OBJECT) { + outAttr = SchemaAttribute{inType, false, false, false, FieldValue()}; // hasNotNull set false is OK for this + } else { + LOGE("[Schema][CheckItemDecideAttr] Unexpected FieldType=%s.", SchemaUtils::FieldTypeString(inType).c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + return E_OK; +} + +int SchemaObject::ParseCheckSchemaIndexes(const JsonObject& inJsonObject) +{ + // Clear schemaIndexes_ to recover from a fail parse + schemaIndexes_.clear(); + // No SCHEMA_INDEXES field is allowed + if (!inJsonObject.IsFieldPathExist(FieldPath{SchemaConstant::KEYWORD_SCHEMA_INDEXES})) { + LOGD("[Schema][ParseIndex] No SCHEMA_INDEXES Field."); + return E_OK; + } + // The type of SCHEMA_INDEXES field has been checked in CheckMetaFieldCountAndType to be an array + // If not all members of the array are string type or string-array, this call will return error + std::vector> oriIndexArray; + int errCode = inJsonObject.GetArrayContentOfStringOrStringArray(FieldPath{SchemaConstant::KEYWORD_SCHEMA_INDEXES}, + oriIndexArray); + if (errCode != E_OK) { + LOGE("[Schema][ParseIndex] GetArrayContent Fail, errCode=%d.", errCode); + return -E_SCHEMA_PARSE_FAIL; + } + if (oriIndexArray.size() > SchemaConstant::SCHEMA_INDEX_COUNT_MAX) { + LOGE("[Schema][ParseIndex] Index(Ori) count=%zu exceed limitation.", oriIndexArray.size()); + return -E_SCHEMA_PARSE_FAIL; + } + for (const auto &entry : oriIndexArray) { + errCode = ParseCheckEachIndexFromStringArray(entry); + if (errCode != E_OK) { + return errCode; + } + } + return E_OK; +} + +int SchemaObject::ParseCheckSchemaSkipSize(const JsonObject& inJsonObject) +{ + // No SCHEMA_SKIPSIZE field is allowed + if (!inJsonObject.IsFieldPathExist(FieldPath{SchemaConstant::KEYWORD_SCHEMA_SKIPSIZE})) { + LOGD("[Schema][ParseSkipSize] No SCHEMA_SKIPSIZE Field."); + return E_OK; + } + // The type of SCHEMA_SKIPSIZE field has been checked in CheckMetaFieldCountAndType to be an INTEGER + FieldValue skipSizeValue; + int errCode = inJsonObject.GetFieldValueByFieldPath(FieldPath {SchemaConstant::KEYWORD_SCHEMA_SKIPSIZE}, + skipSizeValue); + if (errCode != E_OK) { + return -E_INTERNAL_ERROR; + } + if (skipSizeValue.integerValue < 0 || + static_cast(skipSizeValue.integerValue) > SchemaConstant::SCHEMA_SKIPSIZE_MAX) { + LOGE("[Schema][ParseSkipSize] Unexpected SCHEMA_SKIPSIZE=%d.", skipSizeValue.integerValue); + return -E_SCHEMA_PARSE_FAIL; + } + schemaSkipSize_ = static_cast(skipSizeValue.integerValue); + return E_OK; +} + +int SchemaObject::ParseCheckEachIndexFromStringArray(const std::vector &inStrArray) +{ + std::vector indexPathVec; + std::set indexPathSet; + // Parse each indexFieldPathString and check duplication + for (const auto &eachPathStr : inStrArray) { + FieldPath eachPath; + int errCode = SchemaUtils::ParseAndCheckFieldPath(eachPathStr, eachPath); + if (errCode != E_OK) { + LOGE("[Schema][ParseEachIndex] IndexPath=%s Invalid.", eachPathStr.c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + if (eachPath.size() == 0 || eachPath.size() > SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX) { + LOGE("[Schema][ParseEachIndex] Root not indexable or path=%s depth exceed limit.", eachPathStr.c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + if (indexPathSet.count(eachPath) != 0) { + LOGE("[Schema][ParseEachIndex] IndexPath=%s Duplicated.", eachPathStr.c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + indexPathVec.push_back(eachPath); + indexPathSet.insert(eachPath); + } + if (indexPathVec.empty()) { // Unlikely, empty JsonArray had been eliminated by GetArrayContent Method + return -E_INTERNAL_ERROR; + } + // Check indexDefine duplication, Use Sort-Column(the first fieldPath in index) as the indexName. + const IndexName &indexName = indexPathVec.front(); + if (schemaIndexes_.count(indexName) != 0) { + LOGE("[Schema][ParseEachIndex] IndexName=%s Already Defined.", SchemaUtils::FieldPathString(indexName).c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + // Create new indexInfo entry, then check indexable for each indexFieldPath against schemaDefine + return CheckFieldPathIndexableThenSave(indexPathVec, schemaIndexes_[indexName]); +} + +int SchemaObject::CheckFieldPathIndexableThenSave(const std::vector &inPathVec, IndexInfo &infoToSave) +{ + for (const auto &eachPath : inPathVec) { + // Previous logic guarantee eachPath.size greater than zero + uint32_t depth = eachPath.size() - 1; // minus 1 to change depth count from zero + std::string eachPathStr = SchemaUtils::FieldPathString(eachPath); + if (schemaDefine_.count(depth) == 0) { + LOGE("[Schema][CheckIndexable] No schema define of this depth, path=%s.", eachPathStr.c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + if (schemaDefine_[depth].count(eachPath) == 0) { + LOGE("[Schema][CheckIndexable] No such path in schema define, path=%s.", eachPathStr.c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + if (!schemaDefine_[depth][eachPath].isIndexable) { + LOGE("[Schema][CheckIndexable] Path=%s is not indexable.", eachPathStr.c_str()); + return -E_SCHEMA_PARSE_FAIL; + } + // Save this indexField to indexInfo + infoToSave.push_back({eachPath, schemaDefine_[depth][eachPath].type}); + } + return E_OK; +} + +int SchemaObject::CompareSchemaVersionMode(const SchemaObject &newSchema) const +{ + static std::map modeMapString = { + {SchemaMode::STRICT, "STRICT"}, + {SchemaMode::COMPATIBLE, "COMPATIBLE"}, + }; + if (schemaVersion_ != newSchema.schemaVersion_) { + LOGE("[Schema][CompareVerMode] OldVer=%s mismatch newVer=%s.", schemaVersion_.c_str(), + newSchema.schemaVersion_.c_str()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + // Only Json-Schema need to compare mode + if (schemaType_ == SchemaType::JSON && schemaMode_ != newSchema.schemaMode_) { + LOGE("[Schema][CompareVerMode] OldMode=%s mismatch newMode=%s.", modeMapString[schemaMode_].c_str(), + modeMapString[newSchema.schemaMode_].c_str()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + // Do not return E_OK here, E_OK is ambiguous. + return -E_SCHEMA_EQUAL_EXACTLY; +} + +int SchemaObject::CompareSchemaSkipSize(const SchemaObject &newSchema) const +{ + if (schemaSkipSize_ != newSchema.schemaSkipSize_) { + LOGE("[Schema][CompareSkipSize] OldSkip=%u mismatch newSkip=%u.", schemaSkipSize_, newSchema.schemaSkipSize_); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + // Do not return E_OK here, E_OK is ambiguous. + return -E_SCHEMA_EQUAL_EXACTLY; +} + +int SchemaObject::CompareSchemaDefine(const SchemaObject &newSchema) const +{ + bool isEqualExactly = true; + for (uint32_t depth = 0; depth < SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX; depth++) { + SchemaDefine emptyDefine; + const SchemaDefine &defineInOldSchema = + (schemaDefine_.count(depth) == 0 ? emptyDefine : schemaDefine_.at(depth)); + const SchemaDefine &defineInNewSchema = + (newSchema.schemaDefine_.count(depth) == 0 ? emptyDefine : newSchema.schemaDefine_.at(depth)); + + // No define at this depth for both schema + if (defineInNewSchema.empty() && defineInOldSchema.empty()) { + break; + } + // No matter strict or compatible mode, newSchema can't have less field than oldSchema + if (defineInNewSchema.size() < defineInOldSchema.size()) { + LOGE("[Schema][CompareDefine] newSize=%zu less than oldSize=%zu at depth=%" PRIu32, + defineInNewSchema.size(), defineInOldSchema.size(), depth); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + if (defineInNewSchema.size() > defineInOldSchema.size()) { + // Strict mode not support increase fieldDefine + if (schemaMode_ == SchemaMode::STRICT) { + LOGE("[Schema][CompareDefine] newSize=%zu more than oldSize=%zu at depth=%" PRIu32 " in STRICT mode.", + defineInNewSchema.size(), defineInOldSchema.size(), depth); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + isEqualExactly = false; + } + + // Compare schema define of this depth, looking for incompatible + int errCode = CompareSchemaDefineByDepth(defineInOldSchema, defineInNewSchema); + if (errCode == -E_SCHEMA_UNEQUAL_INCOMPATIBLE) { + return errCode; + } + } + // Do not return E_OK here, E_OK is ambiguous. + return (isEqualExactly ? -E_SCHEMA_EQUAL_EXACTLY : -E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE); +} + +namespace { +inline bool IsExtraFieldConformToCompatibility(const SchemaAttribute &inAttr) +{ + return (!inAttr.hasNotNullConstraint || inAttr.hasDefaultValue); +} +} + +int SchemaObject::CompareSchemaDefineByDepth(const SchemaDefine &oldDefine, const SchemaDefine &newDefine) const +{ + // Looking for incompatible : new define should at least contain all field the old define hold + for (auto &entry : oldDefine) { + if (newDefine.count(entry.first) == 0) { + LOGE("[Schema][CompareDefineDepth] fieldpath=%s not found in new schema.", + SchemaUtils::FieldPathString(entry.first).c_str()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + // SchemaAttribute require to be equal exactly + int errCode = CompareSchemaAttribute(entry.second, newDefine.at(entry.first)); + if (errCode != -E_SCHEMA_EQUAL_EXACTLY) { + LOGE("[Schema][CompareDefineDepth] Attribute mismatch at fieldpath=%s.", + SchemaUtils::FieldPathString(entry.first).c_str()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } + // Looking for incompatible : the extra field in new schema should has default or can be null + for (auto &entry : newDefine) { + if (oldDefine.count(entry.first) != 0) { + continue; + } + if (!IsExtraFieldConformToCompatibility(entry.second)) { + LOGE("[Schema][CompareDefineDepth] ExtraField=%s, {notnull=%d, default=%d}, not conform compatibility.", + SchemaUtils::FieldPathString(entry.first).c_str(), entry.second.hasNotNullConstraint, + entry.second.hasDefaultValue); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } + return -E_SCHEMA_EQUAL_EXACTLY; +} + +int SchemaObject::CompareSchemaAttribute(const SchemaAttribute &oldAttr, const SchemaAttribute &newAttr) const +{ + if (oldAttr.type != newAttr.type) { + // The exceptional case is that the field type changed from the leaf_object to internal_object, + // which indicate that sub fields are added to it. Changed from internal_object to leaf_object will + // sooner or later cause an incompatible detection in next depth, we discern this situation here in advance. + if (!(oldAttr.type == FieldType::LEAF_FIELD_OBJECT && newAttr.type == FieldType::INTERNAL_FIELD_OBJECT)) { + LOGE("[Schema][CompareAttr] OldType=%s mismatch newType=%s.", + SchemaUtils::FieldTypeString(oldAttr.type).c_str(), SchemaUtils::FieldTypeString(newAttr.type).c_str()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } + // Here we use isIndexable info to distinguish two categories of type. + // "BOOL, INTEGER, LONG, DOUBLE, STRING" are all indexable type, NULL type will not appear in Schema + // "ARRAY, LEAF_OBJECT, INTERNAL_OBJECT" are all not indexable type. + // They have been checked same type just above. No need to check more for not indexable type + if (oldAttr.isIndexable) { + if (oldAttr.hasNotNullConstraint != newAttr.hasNotNullConstraint) { + LOGE("[Schema][CompareAttr] OldNotNull=%d mismatch newNotNull=%d.", oldAttr.hasNotNullConstraint, + newAttr.hasNotNullConstraint); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + if (oldAttr.hasDefaultValue != newAttr.hasDefaultValue) { + LOGE("[Schema][CompareAttr] OldHasDefault=%d mismatch newHasDefault=%d.", oldAttr.hasDefaultValue, + newAttr.hasDefaultValue); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + if (oldAttr.hasDefaultValue) { + // DefaultValue require to be equal exactly + int errCode = CompareSchemaDefaultValue(oldAttr, newAttr); + if (errCode != -E_SCHEMA_EQUAL_EXACTLY) { + return errCode; + } + } + } + return -E_SCHEMA_EQUAL_EXACTLY; +} + +namespace { +inline bool IsDoubleBinaryEqual(double left, double right) +{ + return *(reinterpret_cast(&left)) == *(reinterpret_cast(&right)); +} +} + +int SchemaObject::CompareSchemaDefaultValue(const SchemaAttribute &oldAttr, const SchemaAttribute &newAttr) const +{ + // Value type has been check equal for both attribute in the caller + if (oldAttr.type == FieldType::LEAF_FIELD_BOOL) { + if (oldAttr.defaultValue.boolValue != newAttr.defaultValue.boolValue) { + LOGE("[Schema][CompareDefault] OldDefault=%d mismatch newDefault=%d.", oldAttr.defaultValue.boolValue, + newAttr.defaultValue.boolValue); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } else if (oldAttr.type == FieldType::LEAF_FIELD_INTEGER) { + if (oldAttr.defaultValue.integerValue != newAttr.defaultValue.integerValue) { + LOGE("[Schema][CompareDefault] OldDefault=%d mismatch newDefault=%d.", oldAttr.defaultValue.integerValue, + newAttr.defaultValue.integerValue); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } else if (oldAttr.type == FieldType::LEAF_FIELD_LONG) { + if (oldAttr.defaultValue.longValue != newAttr.defaultValue.longValue) { + LOGE("[Schema][CompareDefault] OldDefault=%" PRId64 " mismatch newDefault=%" PRId64 ".", + oldAttr.defaultValue.longValue, newAttr.defaultValue.longValue); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } else if (oldAttr.type == FieldType::LEAF_FIELD_DOUBLE) { + // ATTENTION: Here we should compare two double by their binary layout. We should not judge them equal when + // difference is small enough, since two different default double value may diff so little. The binary + // layout of the double value will be the same if the original string is the same, so we directly compare them. + if (!IsDoubleBinaryEqual(oldAttr.defaultValue.doubleValue, newAttr.defaultValue.doubleValue)) { + LOGE("[Schema][CompareDefault] OldDefault=%f mismatch newDefault=%f.", oldAttr.defaultValue.doubleValue, + newAttr.defaultValue.doubleValue); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } else if (oldAttr.type == FieldType::LEAF_FIELD_STRING) { + if (oldAttr.defaultValue.stringValue != newAttr.defaultValue.stringValue) { + LOGE("[Schema][CompareDefault] OldDefault=%s mismatch newDefault=%s.", + oldAttr.defaultValue.stringValue.c_str(), newAttr.defaultValue.stringValue.c_str()); + return -E_SCHEMA_UNEQUAL_INCOMPATIBLE; + } + } + // The caller logic guarantee that both attribute type will not be null, array, object + return -E_SCHEMA_EQUAL_EXACTLY; +} + +namespace { +inline void ClearIndexDifference(IndexDifference &indexDiffer) +{ + indexDiffer.change.clear(); + indexDiffer.increase.clear(); + indexDiffer.decrease.clear(); +} + +inline bool IsIndexInfoExactlyEqual(const IndexInfo &leftInfo, const IndexInfo &rightInfo) +{ + // Exactly equal require count, order and type of each indexField in the index be the same + return leftInfo == rightInfo; +} + +inline bool IsSchemaIndexesExactlyEqual(const IndexDifference &indexDiffer) +{ + return (indexDiffer.change.empty() && indexDiffer.increase.empty() && indexDiffer.decrease.empty()); +} +} + +int SchemaObject::CompareSchemaIndexes(const SchemaObject &newSchema, IndexDifference &indexDiffer) const +{ + ClearIndexDifference(indexDiffer); + // Find the increase and change index + for (const auto &entry : newSchema.schemaIndexes_) { + if (schemaIndexes_.count(entry.first) == 0) { + LOGD("[Schema][CompareIndex] Increase indexName=%s.", SchemaUtils::FieldPathString(entry.first).c_str()); + indexDiffer.increase[entry.first] = entry.second; + } else { + // Both schema have same IndexName, Check whether indexInfo differs + if (!IsIndexInfoExactlyEqual(entry.second, schemaIndexes_.at(entry.first))) { + LOGD("[Schema][CompareIndex] Change indexName=%s.", SchemaUtils::FieldPathString(entry.first).c_str()); + indexDiffer.change[entry.first] = entry.second; + } + } + } + // Find the decrease index + for (const auto &entry : schemaIndexes_) { + if (newSchema.schemaIndexes_.count(entry.first) == 0) { + LOGD("[Schema][CompareIndex] Decrease indexName=%s.", SchemaUtils::FieldPathString(entry.first).c_str()); + indexDiffer.decrease.insert(entry.first); + } + } + // Do not return E_OK here, E_OK is ambiguous. + return IsSchemaIndexesExactlyEqual(indexDiffer) ? -E_SCHEMA_EQUAL_EXACTLY : -E_SCHEMA_UNEQUAL_COMPATIBLE; +} + +namespace { +int CheckValueItemNumericType(FieldType typeInValue, FieldType typeInSchema) +{ + if (typeInValue == FieldType::LEAF_FIELD_DOUBLE) { + if (typeInSchema != FieldType::LEAF_FIELD_DOUBLE) { + return -E_VALUE_MISMATCH_FEILD_TYPE; + } + } else if (typeInValue == FieldType::LEAF_FIELD_LONG) { + if (typeInSchema != FieldType::LEAF_FIELD_LONG && + typeInSchema != FieldType::LEAF_FIELD_DOUBLE) { + return -E_VALUE_MISMATCH_FEILD_TYPE; + } + } else { + // LEAF_FIELD_INTEGER + if (typeInSchema != FieldType::LEAF_FIELD_INTEGER && + typeInSchema != FieldType::LEAF_FIELD_LONG && + typeInSchema != FieldType::LEAF_FIELD_DOUBLE) { + return -E_VALUE_MISMATCH_FEILD_TYPE; + } + } + return -E_VALUE_MATCH; +} + +inline bool IsTypeMustBeExactlyEqualBetweenSchemaAndValue(FieldType inType) +{ + return (inType == FieldType::LEAF_FIELD_BOOL || + inType == FieldType::LEAF_FIELD_STRING || + inType == FieldType::LEAF_FIELD_ARRAY); +} + +inline bool IsObjectType(FieldType inType) +{ + return (inType == FieldType::LEAF_FIELD_OBJECT || inType == FieldType::INTERNAL_FIELD_OBJECT); +} + +// Check in the value-view for convenience +int CheckValueItem(const SchemaAttribute &refAttr, FieldType typeInValue) +{ + FieldType typeInSchema = refAttr.type; + if (typeInSchema == FieldType::LEAF_FIELD_NULL) { // Unlikely + return -E_INTERNAL_ERROR; + } + // Check NotNull-Constraint first + if (typeInValue == FieldType::LEAF_FIELD_NULL) { + if (refAttr.hasNotNullConstraint) { + return -E_VALUE_MISMATCH_CONSTRAINT; + } + return -E_VALUE_MATCH; + } + // If typeInValue not NULL, check against schema. First check type that must be equal. + if (IsTypeMustBeExactlyEqualBetweenSchemaAndValue(typeInValue)) { + if (typeInValue != typeInSchema) { + return -E_VALUE_MISMATCH_FEILD_TYPE; + } + return -E_VALUE_MATCH; + } + // Check Object related type, lack or more field will be deal with at next depth + // typeInSchema/typeInValue LEAF_OBJECT INTERNAL_OBJECT + // LEAF_OBJECT MATCH MATCH(More field at next depth) + // INTERNAL_OBJECT MATCH(Lack field at next depth) MATCH + // ELSE(POSSIBLE) TYPE_MISMATCH TYPE_MISMATCH + if (IsObjectType(typeInValue)) { + if (!IsObjectType(typeInSchema)) { + return -E_VALUE_MISMATCH_FEILD_TYPE; + } + return -E_VALUE_MATCH; + } + // Check Numeric related type, at last + return CheckValueItemNumericType(typeInValue, typeInSchema); +} + +inline bool IsLackingFieldViolateNotNullConstraint(const SchemaAttribute &refAttr) +{ + return (refAttr.hasNotNullConstraint && !refAttr.hasDefaultValue); +} + +// Function only for split big function +int CheckValueBySchemaItem(const std::pair &schemaItem, + const std::map &subPathType, std::set &lackingPaths) +{ + if (subPathType.count(schemaItem.first) == 0) { // Value do not contain this field + if (IsLackingFieldViolateNotNullConstraint(schemaItem.second)) { + return -E_VALUE_MISMATCH_CONSTRAINT; + } + lackingPaths.insert(schemaItem.first); + return -E_VALUE_MATCH; + } + // Value contain this field, check its type + return CheckValueItem(schemaItem.second, subPathType.at(schemaItem.first)); +} + +inline std::string ValueFieldType(const std::map &subPathType, const FieldPath &inPath) +{ + if (subPathType.count(inPath) == 0) { + return "NotExist"; + } + return SchemaUtils::FieldTypeString(subPathType.at(inPath)); +} +} + +int SchemaObject::CheckValue(const ValueObject &inValue, std::set &lackingPaths) const +{ + std::set nestPathCurDepth{FieldPath()}; // Empty path represent root path + for (uint32_t depth = 0; depth < SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX; depth++) { + if (schemaDefine_.count(depth) == 0 || schemaDefine_.at(depth).empty()) { // No schema define in this depth + break; + } + + std::map subPathType; + int errCode = inValue.GetSubFieldPathAndType(nestPathCurDepth, subPathType); // Value field of current depth + if (errCode != E_OK && errCode != -E_INVALID_PATH) { // E_INVALID_PATH for path not exist + LOGE("[Schema][CheckValue] GetSubFieldPathAndType Fail=%d, Depth=%u.", errCode, depth); + return -E_VALUE_MISMATCH_FEILD_TYPE; + } + nestPathCurDepth.clear(); // Clear it for collecting new nestPath + + if ((schemaMode_ == SchemaMode::STRICT) && (subPathType.size() > schemaDefine_.at(depth).size())) { + LOGE("[Schema][CheckValue] ValueFieldCount=%zu more than SchemaFieldCount=%zu at depth=%u", + subPathType.size(), schemaDefine_.at(depth).size(), depth); + return -E_VALUE_MISMATCH_FEILD_COUNT; // Value contain more field than schema + } + + for (const auto &schemaItem : schemaDefine_.at(depth)) { // Check each field define in schema + if (schemaItem.second.type == FieldType::INTERNAL_FIELD_OBJECT) { + nestPathCurDepth.insert(schemaItem.first); // This field has subfield in schema + } + errCode = CheckValueBySchemaItem(schemaItem, subPathType, lackingPaths); + if (errCode != -E_VALUE_MATCH) { + LOGE("[Schema][CheckValue] Path=%s, schema{NotNull=%d,Default=%d,Type=%s}, Value{Type=%s}, errCode=%d.", + SchemaUtils::FieldPathString(schemaItem.first).c_str(), schemaItem.second.hasNotNullConstraint, + schemaItem.second.hasDefaultValue, SchemaUtils::FieldTypeString(schemaItem.second.type).c_str(), + ValueFieldType(subPathType, schemaItem.first).c_str(), errCode); + return errCode; + } + } + } + return -E_VALUE_MATCH; +} + +int SchemaObject::AmendValueIfNeed(ValueObject &inValue, const std::set &lackingPaths, bool &amended) const +{ + for (const auto &eachLackingPath : lackingPaths) { + // Note: The upper code logic guarantee that eachLackingPath won't be empty and must exist in schemaDefine_ + uint32_t depth = eachLackingPath.size() - 1; // Depth count from zero + const SchemaAttribute &lackingPathAttr = schemaDefine_.at(depth).at(eachLackingPath); + // If no default value, just ignore this lackingPath + if (!lackingPathAttr.hasDefaultValue) { + continue; + } + // If has default value, the ParseSchema logic guarantee that fieldType won't be NULL, ARRAY or OBJECT + // The lacking intermediate field will be automatically insert for this lackingPath + int errCode = inValue.InsertField(eachLackingPath, lackingPathAttr.type, lackingPathAttr.defaultValue); + if (errCode != E_OK) { // Unlikely + LOGE("[Schema][AmendValue] InsertField fail, errCode=%d, Path=%s, Type=%s.", errCode, + SchemaUtils::FieldPathString(eachLackingPath).c_str(), + SchemaUtils::FieldTypeString(lackingPathAttr.type).c_str()); + return -E_INTERNAL_ERROR; + } + amended = true; + } + return E_OK; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/schema_utils.cpp b/mock/distributeddb/common/src/schema_utils.cpp new file mode 100644 index 00000000..bb454dcd --- /dev/null +++ b/mock/distributeddb/common/src/schema_utils.cpp @@ -0,0 +1,502 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "schema_utils.h" +#include +#include +#include +#include +#include "db_errno.h" +#include "log_print.h" +#include "schema_constant.h" + +namespace DistributedDB { +namespace { + bool IsLegalFieldCharacter(char character) + { + return (std::isalnum(character) || character == '_'); + } + void TrimFiled(std::string &inString) + { + inString.erase(0, inString.find_first_not_of("\r\t ")); + size_t temp = inString.find_last_not_of("\r\t "); + if (temp < inString.size()) { + inString.erase(temp + 1); + } + } + + // TYPE, [NOT NULL,] [DEFAULT X] + // DEFAULT at last + // State transition matrix + const int STATE_TRANSFER[8][6] = { // 5 type input and 7 type state + // blank, NOT NULL, DEFAULT, OTHER AlNUM, COMMA + {0, -1, -1, 1, -1}, // state 0: empty + {1, -1, -1, 1, 2}, // state 1: only type + {2, 3, 5, -1, -1}, // state 2: alnum , + {3, -1, -1, -1, 4}, // state 3: alnum , notnull + {4, -1, 5, -1, -1}, // state 4: alnum , notnull , + {6, -1, -1, -1, -1}, // state 5: finish with DEFAULT + {6, -1, -1, 7, -1}, // state 6: finish with DEFAULT and blank + {7, 7, 7, 7, 7}, // state 7: finish with DEFAULT and blank and no matter what value + }; + enum StateTransferColNum { + COLUMN_ILLEGAL = -1, + COLUMN_BLANK, + COLUMN_NOT_NULL, + COLUMN_DEFAULT, + COLUMN_OTHER_ALNUM, + COLUMN_COMMA, + }; +} // namespace + +// compare function can make sure not to cross the border, pos < oriContent.size() - 1 +// Get symbol type and Converts to the corresponding column of the state transition matrix +int SchemaUtils::MakeTrans(const std::string &oriContent, size_t &pos) +{ + if (isspace(oriContent[pos])) { + return COLUMN_BLANK; + } else if (oriContent.compare(pos, SchemaConstant::KEYWORD_ATTR_NOT_NULL.size(), + SchemaConstant::KEYWORD_ATTR_NOT_NULL) == 0) { + pos = pos + SchemaConstant::KEYWORD_ATTR_NOT_NULL.size() - 1; + return COLUMN_NOT_NULL; + } else if (oriContent.compare(pos, SchemaConstant::KEYWORD_ATTR_DEFAULT.size(), + SchemaConstant::KEYWORD_ATTR_DEFAULT) == 0) { + pos = pos + SchemaConstant::KEYWORD_ATTR_DEFAULT.size() - 1; + return COLUMN_DEFAULT; + } else if (std::isalnum(oriContent[pos]) || oriContent[pos] == '\'' || + oriContent[pos] == '+' || oriContent[pos] == '-') { + return COLUMN_OTHER_ALNUM; + } else if (oriContent[pos] == ',') { + return COLUMN_COMMA; + } else { + return COLUMN_ILLEGAL; + } +} + +// Use DFA to check and Parsing +// You can get the corresponding state meaning in the state transition matrix STATE_TRANSFER +int SchemaUtils::SplitSchemaAttribute(const std::string &inAttrString, std::vector &outAttrString) +{ + int state = 0; + outAttrString.resize(3); // attribute have 3 type keywords + for (size_t i = 0; i < inAttrString.size(); i++) { + int id = MakeTrans(inAttrString, i); + if (id < 0) { + LOGD("Split Schema Attribute err, Contains unrecognized content [%c]", inAttrString[i]); + return -E_SCHEMA_PARSE_FAIL; + } + state = STATE_TRANSFER[state][id]; + if (state < 0) { + LOGD("Split Schema Attribute err, err state [%d]", state); + return -E_SCHEMA_PARSE_FAIL; + } + switch (state) { + case 1: // state 1 :Indicates that only type information is currently available + outAttrString[0].push_back(inAttrString[i]); + break; + case 3: // state 3 :Gets the NOT_NULL keyword + outAttrString[1] = SchemaConstant::KEYWORD_ATTR_NOT_NULL; + break; + case 7: // state 7 :Contains complete information + // Get default string. Now transfer matrix can ensure > 1, but you should pay attention when fix it + if (i <= 1) { + LOGE("default string size must be over 1."); + return -E_SCHEMA_PARSE_FAIL; + } + outAttrString[2] = inAttrString.substr(i - 1); + return E_OK; + default: + break; + } + } + // Only these states are legal, The meaning of the state can be seen in the matrix STATE_TRANSFER explanation + if (!(state == 1 || state == 3 || state == 7)) { + LOGD("Split Schema Attribute err, err state [%d]", state); + return -E_SCHEMA_PARSE_FAIL; + } + return E_OK; +} + +int SchemaUtils::TransToBool(const std::string &defaultContent, SchemaAttribute &outAttr) +{ + // Have been trim + if (defaultContent.compare(SchemaConstant::KEYWORD_ATTR_VALUE_TRUE) == 0) { + outAttr.defaultValue.boolValue = true; + return E_OK; + } else if (defaultContent.compare(SchemaConstant::KEYWORD_ATTR_VALUE_FALSE) == 0) { + outAttr.defaultValue.boolValue = false; + return E_OK; + } + LOGE("Default value can not transform to bool!!"); + return -E_SCHEMA_PARSE_FAIL; +} + +int SchemaUtils::TransToString(const std::string &defaultContent, SchemaAttribute &outAttr) +{ + // Have been trim, Strip leading and trailing ' + if (defaultContent.size() > 1 && defaultContent.front() == '\'' && defaultContent.back() == '\'') { + outAttr.defaultValue.stringValue = defaultContent.substr(1, defaultContent.size() - 2); + if (outAttr.defaultValue.stringValue.size() > SchemaConstant::SCHEMA_DEFAULT_STRING_SIZE_LIMIT) { + return -E_SCHEMA_PARSE_FAIL; + } + return E_OK; + } + LOGE("Substandard format! Default value can not transform to string!!"); + return -E_SCHEMA_PARSE_FAIL; +} + +int SchemaUtils::TransToInteger(const std::string &defaultContent, SchemaAttribute &outAttr) +{ + // defaultContent can not be null + if (defaultContent.empty()) { + return -E_SCHEMA_PARSE_FAIL; + } + int transRes = strtol(defaultContent.c_str(), nullptr, 10); // 10: decimal + std::string resReview = std::to_string(transRes); + if (defaultContent.compare(defaultContent.find_first_not_of("+- "), defaultContent.size(), + resReview, resReview.find_first_not_of("+- "), resReview.size()) == 0) { + // Check the sign of the number + if ((defaultContent[0] == '-' && resReview[0] == '-') || + (defaultContent[0] != '-' && resReview[0] != '-') || + transRes == 0) { + outAttr.defaultValue.integerValue = transRes; + return E_OK; + } + } + LOGE("Default value can not transform to Integer!!"); + return -E_SCHEMA_PARSE_FAIL; +} + +int SchemaUtils::TransToLong(const std::string &defaultContent, SchemaAttribute &outAttr) +{ + // defaultContent can not be null + if (defaultContent.empty()) { + return -E_SCHEMA_PARSE_FAIL; + } + int64_t transRes = strtoll(defaultContent.c_str(), nullptr, 10); // 10: decimal + std::string resReview = std::to_string(transRes); + if (defaultContent.compare(defaultContent.find_first_not_of("+- "), defaultContent.size(), + resReview, resReview.find_first_not_of("+- "), resReview.size()) == 0) { + // Check the sign of the number + if ((defaultContent[0] == '-' && resReview[0] == '-') || + (defaultContent[0] != '-' && resReview[0] != '-') || + transRes == 0) { + outAttr.defaultValue.longValue = transRes; + return E_OK; + } + } + + LOGE("Default value[%s] can not transform to LONG!!", resReview.c_str()); + return -E_SCHEMA_PARSE_FAIL; +} + +int SchemaUtils::TransToDouble(const std::string &defaultContent, SchemaAttribute &outAttr) +{ + // defaultContent can not be null + if (defaultContent.empty()) { + return -E_SCHEMA_PARSE_FAIL; + } + + // Disable scientific notation + int dotCount = 0; + for (const auto &iter : defaultContent) { + if (!(std::isdigit(iter) || iter == '.' || iter == '-' || iter == '+')) { + LOGE("Default value to double, exist invalid symbol[%c]", iter); + return -E_SCHEMA_PARSE_FAIL; + } + if (iter == '.') { + dotCount++; + } + if (dotCount > 1) { + LOGE("Default value to double, exist invalid extra dot"); + return -E_SCHEMA_PARSE_FAIL; + } + } + + char *end = nullptr; + double transRes = std::strtod(defaultContent.c_str(), &end); + // Double exist problems with accuracy, overflow is subject to the legality of the c++ conversion. + if (transRes > -HUGE_VAL && transRes < HUGE_VAL && std::isfinite(transRes)) { + // Cleared blank + if (end != &defaultContent.back() + 1) { + LOGD("Termination of parsing due to exception symbol"); + return -E_SCHEMA_PARSE_FAIL; + } + outAttr.defaultValue.doubleValue = transRes; + return E_OK; + } + LOGE("Default value can not transform to double, overflow double max!"); + return -E_SCHEMA_PARSE_FAIL; +} + +int SchemaUtils::TransformDefaultValue(std::string &defaultContent, SchemaAttribute &outAttr) +{ + TrimFiled(defaultContent); + if (defaultContent.compare(SchemaConstant::KEYWORD_ATTR_VALUE_NULL) == 0 && outAttr.hasNotNullConstraint) { + LOGE("NOT NULL and DEFAULT null Simultaneously"); + return -E_SCHEMA_PARSE_FAIL; + } else if (defaultContent.compare(SchemaConstant::KEYWORD_ATTR_VALUE_NULL) == 0) { + outAttr.hasDefaultValue = false; + return E_OK; + } + + int errCode = E_OK; + switch (outAttr.type) { + case FieldType::LEAF_FIELD_BOOL: + errCode = TransToBool(defaultContent, outAttr); + break; + case FieldType::LEAF_FIELD_INTEGER: + errCode = TransToInteger(defaultContent, outAttr); + break; + case FieldType::LEAF_FIELD_LONG: + errCode = TransToLong(defaultContent, outAttr); + break; + case FieldType::LEAF_FIELD_DOUBLE: + errCode = TransToDouble(defaultContent, outAttr); + break; + case FieldType::LEAF_FIELD_STRING: + errCode = TransToString(defaultContent, outAttr); + break; + default: + LOGE("Unrecognized or unsupported type, please check!!"); + errCode = -E_SCHEMA_PARSE_FAIL; + break; + } + + LOGD("SchemaAttribute type is [%d], transfer result is [%d]", static_cast(outAttr.type), errCode); + return errCode; +} + +int SchemaUtils::ParseAndCheckSchemaAttribute(const std::string &inAttrString, SchemaAttribute &outAttr, + bool useAffinity) +{ + if (inAttrString.empty()) { + return -E_SCHEMA_PARSE_FAIL; + } + std::string tempinAttrString = inAttrString; + TrimFiled(tempinAttrString); + + std::vector attrContext; + int errCode = SplitSchemaAttribute(inAttrString, attrContext); + if (errCode != E_OK) { + LOGD("Syntax error, please check!"); + return errCode; + } + errCode = ParseSchemaAttribute(attrContext, outAttr, useAffinity); + if (errCode != E_OK) { + LOGD("Grammatical error, please check!"); + return errCode; + } + + return E_OK; +} + +int SchemaUtils::ParseSchemaAttribute(std::vector &attrContext, SchemaAttribute &outAttr, bool useAffinity) +{ + // Currently supported types + static const std::map FIELD_TYPE_DIC = { + {SchemaConstant::KEYWORD_TYPE_BOOL, FieldType::LEAF_FIELD_BOOL}, + {SchemaConstant::KEYWORD_TYPE_INTEGER, FieldType::LEAF_FIELD_INTEGER}, + {SchemaConstant::KEYWORD_TYPE_LONG, FieldType::LEAF_FIELD_LONG}, + {SchemaConstant::KEYWORD_TYPE_DOUBLE, FieldType::LEAF_FIELD_DOUBLE}, + {SchemaConstant::KEYWORD_TYPE_STRING, FieldType::LEAF_FIELD_STRING}, + }; + + // After split attribute? attrContext include 3 type field + if (attrContext.size() < 3) { + LOGE("No parsing preprocessing!!"); + return -E_SCHEMA_PARSE_FAIL; + } + TrimFiled(attrContext[0]); + if (!useAffinity) { + if (FIELD_TYPE_DIC.find(attrContext[0]) == FIELD_TYPE_DIC.end()) { + LOGE("Errno schema field type [%s]!!", attrContext[0].c_str()); + return -E_SCHEMA_PARSE_FAIL; + } else { + outAttr.type = FIELD_TYPE_DIC.at(attrContext[0]); + } + } else { + outAttr.type = FieldType::LEAF_FIELD_NULL; + outAttr.customFieldType = attrContext[0]; + } + + outAttr.hasNotNullConstraint = !attrContext[1].empty(); + + // if DEFAULT value context exist, fix hasDefaultValue flag, 2nd represents the default value + if (attrContext[2].empty()) { + outAttr.hasDefaultValue = false; + } else { + outAttr.hasDefaultValue = true; + int errCode = TransformDefaultValue(attrContext[2], outAttr); // 2nd element is DEFAULT value + if (errCode != E_OK) { + LOGE("Default value is malformed!!"); + return -E_SCHEMA_PARSE_FAIL; + } + } + return E_OK; +} + +namespace { +// Check prefix and attempt to find any illegal, returns E_OK if nothing illegal and an hasPrefix indicator. +int CheckDollarDotPrefix(const std::string &inPathStr, bool &hasPrefix) +{ + if (inPathStr.empty()) { + return -E_SCHEMA_PARSE_FAIL; + } + if (inPathStr.size() >= std::string("$.").size()) { + // In this case, $. prefix may exist, but also may not exist. + if (inPathStr[0] == '$' && inPathStr[1] == '.') { // 1 for second char + // $. prefix may exist + hasPrefix = true; + return E_OK; + } + if (inPathStr[0] == '$' && inPathStr[1] != '.') { // 1 for second char + return -E_SCHEMA_PARSE_FAIL; + } + if (inPathStr[1] == '$') { // 1 for second char + return -E_SCHEMA_PARSE_FAIL; + } + } + // here, inPathStr not empty, has at least one char, should not begin with '.' + if (inPathStr[0] == '.') { + return -E_SCHEMA_PARSE_FAIL; + } + hasPrefix = false; + return E_OK; +} +} + +int SchemaUtils::ParseAndCheckFieldPath(const std::string &inPathString, FieldPath &outPath, bool permitPrefix) +{ + std::string tempInPathString = inPathString; + TrimFiled(tempInPathString); + bool hasPrefix = false; + int errCode = CheckDollarDotPrefix(tempInPathString, hasPrefix); + if (errCode != E_OK) { + LOGE("CheckDollarDotPrefix Fail."); + return errCode; + } + + if (!permitPrefix && hasPrefix) { + LOGE("Not permit $. prefix."); + return -E_SCHEMA_PARSE_FAIL; + } + + if (!hasPrefix) { + tempInPathString = std::string("$.") + tempInPathString; + } + + for (size_t curPos = 1; curPos < tempInPathString.size();) { + if (curPos + 1 == tempInPathString.size()) { + LOGE("Dot at end will generate empty illegal path!"); + return -E_SCHEMA_PARSE_FAIL; + } + size_t nextPointPos = tempInPathString.find_first_of(".", curPos + 1); + outPath.push_back(tempInPathString.substr(curPos + 1, nextPointPos - curPos - 1)); + curPos = nextPointPos; + } + + if (outPath.size() > SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX) { + LOGE("Parse Schema Index depth illegality!"); + return -E_SCHEMA_PARSE_FAIL; + } + + for (const auto &iter : outPath) { + if (CheckFieldName(iter) != E_OK) { + LOGE("Parse Schema Index field illegality!"); + return -E_SCHEMA_PARSE_FAIL; + } + } + return E_OK; +} + +int SchemaUtils::CheckFieldName(const FieldName &inName) +{ + if (inName.empty() || inName.size() > SchemaConstant::SCHEMA_FEILD_NAME_LENGTH_MAX) { + LOGE("Schema FieldName have invalid size!"); + return -E_SCHEMA_PARSE_FAIL; + } + + // The first letter must be a number or an underscore + if (!(std::isalpha(inName[0]) || inName[0] == '_')) { + LOGE("Schema FieldName begin with un support symbol!"); + return -E_SCHEMA_PARSE_FAIL; + } + + // Must consist of numeric underscore letters + for (const auto &iter : inName) { + if (!(IsLegalFieldCharacter(iter))) { + LOGE("Schema FieldName exist un support symbol!"); + return -E_SCHEMA_PARSE_FAIL; + } + } + + return E_OK; +} + +std::string SchemaUtils::Strip(const std::string &inString) +{ + std::string stripRes = inString; + TrimFiled(stripRes); + return stripRes; +} + +std::string SchemaUtils::StripNameSpace(const std::string &inFullName) +{ + auto pos = inFullName.find_last_of('.'); + if (pos == std::string::npos) { // No '.', so no namespace + return inFullName; + } + return inFullName.substr(pos + 1); +} + +std::string SchemaUtils::FieldTypeString(FieldType inType) +{ + static std::map fieldTypeMapString = { + {FieldType::LEAF_FIELD_NULL, "NULL"}, + {FieldType::LEAF_FIELD_BOOL, "BOOL"}, + {FieldType::LEAF_FIELD_INTEGER, "INTEGER"}, + {FieldType::LEAF_FIELD_LONG, "LONG"}, + {FieldType::LEAF_FIELD_DOUBLE, "DOUBLE"}, + {FieldType::LEAF_FIELD_STRING, "STRING"}, + {FieldType::LEAF_FIELD_ARRAY, "ARRAY"}, + {FieldType::LEAF_FIELD_OBJECT, "LEAF_OBJECT"}, + {FieldType::INTERNAL_FIELD_OBJECT, "INTERNAL_OBJECT"}, + }; + return fieldTypeMapString[inType]; +} + +std::string SchemaUtils::SchemaTypeString(SchemaType inType) +{ + static std::map schemaTypeMapString { + {SchemaType::NONE, "NONE"}, + {SchemaType::JSON, "JSON-SCHEMA"}, + {SchemaType::FLATBUFFER, "FLATBUFFER-SCHEMA"}, + {SchemaType::RELATIVE, "RELATIVE"}, + {SchemaType::UNRECOGNIZED, "UNRECOGNIZED"}, + }; + return schemaTypeMapString[inType]; +} + +std::string SchemaUtils::FieldPathString(const FieldPath &inPath) +{ + std::string outString = "$"; + for (const auto &entry : inPath) { + outString += "."; + outString += entry; + } + return outString; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/semaphore_utils.cpp b/mock/distributeddb/common/src/semaphore_utils.cpp new file mode 100644 index 00000000..05d99dd3 --- /dev/null +++ b/mock/distributeddb/common/src/semaphore_utils.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "semaphore_utils.h" + +#include + +namespace DistributedDB { +using std::unique_lock; +using std::lock_guard; +using std::mutex; +using std::condition_variable; + +SemaphoreUtils::SemaphoreUtils(int count) + : count_(count) +{} + +SemaphoreUtils::~SemaphoreUtils() +{} + +bool SemaphoreUtils::WaitSemaphore(int waitSecond) +{ + unique_lock lock(lockMutex_); + bool result = cv_.wait_for(lock, std::chrono::seconds(waitSecond), + std::bind(&SemaphoreUtils::CompareCount, this)); + if (result == true) { + --count_; + } + return result; +} + +void SemaphoreUtils::WaitSemaphore() +{ + unique_lock lock(lockMutex_); + cv_.wait(lock, std::bind(&SemaphoreUtils::CompareCount, this)); + --count_; +} + +void SemaphoreUtils::SendSemaphore() +{ + lock_guard lock(lockMutex_); + count_++; + cv_.notify_one(); +} + +bool SemaphoreUtils::CompareCount() const +{ + return count_ > 0; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/task_pool.cpp b/mock/distributeddb/common/src/task_pool.cpp new file mode 100644 index 00000000..2cdeb5d3 --- /dev/null +++ b/mock/distributeddb/common/src/task_pool.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "task_pool.h" +#include "db_errno.h" +#include "log_print.h" +#include "task_pool_impl.h" + +namespace DistributedDB { +TaskPool *TaskPool::Create(int maxThreads, int minThreads, int &errCode) +{ + TaskPool *taskPool = new (std::nothrow) TaskPoolImpl(maxThreads, minThreads); + if (taskPool == nullptr) { + LOGE("alloc task pool failed."); + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + errCode = E_OK; + return taskPool; +} + +void TaskPool::Release(TaskPool *&taskPool) +{ + if (taskPool != nullptr) { + delete taskPool; + taskPool = nullptr; + } +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/task_pool_impl.cpp b/mock/distributeddb/common/src/task_pool_impl.cpp new file mode 100644 index 00000000..40b66cb3 --- /dev/null +++ b/mock/distributeddb/common/src/task_pool_impl.cpp @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "task_pool_impl.h" +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +constexpr int TaskPoolImpl::IDLE_WAIT_PERIOD; + +TaskPoolImpl::TaskPoolImpl(int maxThreads, int minThreads) + : genericTasks_(false), + genericTaskCount_(0), + queuedTaskCount_(0), + isStarted_(false), + isStopping_(false), + maxThreads_(maxThreads), + minThreads_(minThreads), + curThreads_(0), + idleThreads_(0) +{} + +TaskPoolImpl::~TaskPoolImpl() +{} + +int TaskPoolImpl::Start() +{ + if (maxThreads_ < minThreads_) { + LOGE("Start task pool failed, maxThreads(%d) < minThreads(%d).", + maxThreads_, minThreads_); + return -E_INVALID_ARGS; + } + if (maxThreads_ <= 0) { + LOGE("Start task pool failed, maxThreads(%d) <= 0.", maxThreads_); + return -E_INVALID_ARGS; + } + if (minThreads_ < 0) { + LOGE("Start task pool failed, minThreads(%d) < 0.", minThreads_); + return -E_INVALID_ARGS; + } + LOGI("Start task pool min:%d, max:%d", minThreads_, maxThreads_); + std::lock_guard guard(tasksMutex_); + isStarted_ = true; // parameters checked ok. + isStopping_ = false; + int errCode = SpawnThreads(true); + if (errCode != E_OK) { + LOGW("Spawn threads failed when starting the task pool."); + // ignore the error, we will try when schedule(). + } + return E_OK; +} + +void TaskPoolImpl::Stop() +{ + std::unique_lock lock(tasksMutex_); + if (!isStarted_) { + return; + } + isStopping_ = true; + hasTasks_.notify_all(); + allThreadsExited_.wait(lock, [this]() { + return this->curThreads_ <= 0; + }); + isStarted_ = false; +} + +int TaskPoolImpl::Schedule(const Task &task) +{ + if (!task) { + return -E_INVALID_ARGS; + } + std::lock_guard guard(tasksMutex_); + if (!isStarted_) { + LOGE("Schedule failed, the task pool is not started."); + return -E_NOT_PERMIT; + } + if (isStopping_) { + LOGI("Schedule failed, the task pool is stopping."); + return -E_STALE; + } + genericTasks_.PutTask(task); + ++genericTaskCount_; + hasTasks_.notify_one(); + TryToSpawnThreads(); + return E_OK; +} + +int TaskPoolImpl::Schedule(const std::string &queueTag, const Task &task) +{ + if (!task) { + return -E_INVALID_ARGS; + } + std::lock_guard guard(tasksMutex_); + if (!isStarted_) { + LOGE("Schedule failed, the task pool is not started."); + return -E_NOT_PERMIT; + } + if (isStopping_) { + LOGI("Schedule failed, the task pool is stopping."); + return -E_STALE; + } + queuedTasks_[queueTag].PutTask(task); + ++queuedTaskCount_; + hasTasks_.notify_all(); + TryToSpawnThreads(); + return E_OK; +} + +void TaskPoolImpl::ShrinkMemory(const std::string &tag) +{ + std::lock_guard guard(tasksMutex_); + auto iter = queuedTasks_.find(tag); + if (iter != queuedTasks_.end()) { + if (iter->second.IsEmptyAndUnlocked()) { + queuedTasks_.erase(iter); + } + } +} + +bool TaskPoolImpl::IdleExit(std::unique_lock &lock) +{ + if (isStopping_) { + return true; + } + ++idleThreads_; + bool isGenericWorker = IsGenericWorker(); + if (!isGenericWorker && (curThreads_ > minThreads_)) { + std::cv_status status = hasTasks_.wait_for(lock, + std::chrono::seconds(IDLE_WAIT_PERIOD)); + if (status == std::cv_status::timeout && + genericTaskCount_ <= 0) { + --idleThreads_; + return true; + } + } else { + if (isGenericWorker) { + hasTasks_.notify_all(); + } + hasTasks_.wait(lock); + } + --idleThreads_; + return false; +} + +void TaskPoolImpl::SetThreadFree() +{ + for (auto &pair : queuedTasks_) { + TaskQueue *tq = &pair.second; + tq->ReleaseLock(); + } +} + +Task TaskPoolImpl::ReapTask(TaskQueue *&queue) +{ + Task task = genericTasks_.GetTaskAutoLock(); + if (task != nullptr) { + queue = nullptr; + return task; + } + + queue = nullptr; + if (IsGenericWorker() && (curThreads_ > 1)) { // 1 indicates self. + SetThreadFree(); + return nullptr; + } + for (auto &pair : queuedTasks_) { + TaskQueue *tq = &pair.second; + task = tq->GetTaskAutoLock(); + if (task != nullptr) { + queue = tq; + return task; + } + } + return nullptr; +} + +int TaskPoolImpl::GetTask(Task &task, TaskQueue *&queue) +{ + std::unique_lock lock(tasksMutex_); + + while (true) { + task = ReapTask(queue); + if (task != nullptr) { + return E_OK; + } + + if (IdleExit(lock)) { + break; + } + } + return E_OK; +} + +int TaskPoolImpl::SpawnThreads(bool isStart) +{ + if (!isStarted_) { + LOGE("Spawn task pool threads failed, pool is not started."); + return -E_NOT_PERMIT; + } + if (curThreads_ >= maxThreads_) { + // the pool is full of threads. + return E_OK; + } + + int limits = isStart ? minThreads_ : (curThreads_ + 1); + while (curThreads_ < limits) { + ++curThreads_; + std::thread thread([this]() { + TaskWorker(); + }); + LOGI("Task pool spawn cur:%d idle:%d.", curThreads_, idleThreads_); + thread.detach(); + } + return E_OK; +} + +bool TaskPoolImpl::IsGenericWorker() const +{ + return genericThread_ == std::this_thread::get_id(); +} + +void TaskPoolImpl::BecomeGenericWorker() +{ + std::lock_guard guard(tasksMutex_); + if (genericThread_ == std::thread::id()) { + genericThread_ = std::this_thread::get_id(); + } +} + +void TaskPoolImpl::ExitWorker() +{ + std::lock_guard guard(tasksMutex_); + if (IsGenericWorker()) { + genericThread_ = std::thread::id(); + } + --curThreads_; + allThreadsExited_.notify_all(); + LOGI("Task pool thread exit, cur:%d idle:%d, genericTaskCount:%d, queuedTaskCount:%d.", + curThreads_, idleThreads_, genericTaskCount_, queuedTaskCount_); +} + +void TaskPoolImpl::TaskWorker() +{ + BecomeGenericWorker(); + + while (true) { + TaskQueue *taskQueue = nullptr; + Task task = nullptr; + + int errCode = GetTask(task, taskQueue); + if (errCode != E_OK) { + LOGE("Thread worker gets task failed, err:'%d'.", errCode); + break; + } + if (task == nullptr) { + // Idle thread exit. + break; + } + + task(); + FinishExecuteTask(taskQueue); + } + + ExitWorker(); +} + +void TaskPoolImpl::FinishExecuteTask(TaskQueue *taskQueue) +{ + std::lock_guard guard(tasksMutex_); + if (taskQueue != nullptr) { + taskQueue->ReleaseLock(); + --queuedTaskCount_; + } else { + --genericTaskCount_; + } +} + +void TaskPoolImpl::TryToSpawnThreads() +{ + if ((curThreads_ >= maxThreads_) || + (curThreads_ >= (queuedTaskCount_ + genericTaskCount_))) { + return; + } + (void)(SpawnThreads(false)); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/task_pool_impl.h b/mock/distributeddb/common/src/task_pool_impl.h new file mode 100644 index 00000000..7e533a2d --- /dev/null +++ b/mock/distributeddb/common/src/task_pool_impl.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 TASK_POOL_IMPL_H +#define TASK_POOL_IMPL_H + +#include +#include +#include +#include +#include +#include "task_pool.h" +#include "task_queue.h" + +namespace DistributedDB { +class TaskPoolImpl : public TaskPool { +public: + // maxThreads > 0. + TaskPoolImpl(int maxThreads, int minThreads); + + // Start the task pool. + int Start() override; + + // Stop the task pool. + void Stop() override; + + // Schedule a task, the task can be ran in any thread. + int Schedule(const Task &task) override; + + // Schedule tasks one by one. + int Schedule(const std::string &queueTag, const Task &task) override; + + // Shrink memory associated with the given tag if possible. + void ShrinkMemory(const std::string &tag) override; + +protected: + ~TaskPoolImpl(); + +private: + int SpawnThreads(bool isStart); + bool IdleExit(std::unique_lock &lock); + void SetThreadFree(); + Task ReapTask(TaskQueue *&queue); + int GetTask(Task &task, TaskQueue *&queue); + bool IsGenericWorker() const; + void BecomeGenericWorker(); + void ExitWorker(); + void TaskWorker(); + void FinishExecuteTask(TaskQueue *taskQueue); + void TryToSpawnThreads(); + + // Member Variables. + static constexpr int IDLE_WAIT_PERIOD = 1; // wait 1 second before exiting. + std::mutex tasksMutex_; + std::condition_variable hasTasks_; + std::map queuedTasks_; + TaskQueue genericTasks_; + std::thread::id genericThread_; // execute generic task only. + int genericTaskCount_; + int queuedTaskCount_; + bool isStarted_; + bool isStopping_; // Stop() invoked. + std::condition_variable allThreadsExited_; + + // Thread counter. + int maxThreads_; + int minThreads_; + int curThreads_; + int idleThreads_; +}; +} // namespace DistributedDB + +#endif // TASK_POOL_IMPL_H diff --git a/mock/distributeddb/common/src/task_queue.cpp b/mock/distributeddb/common/src/task_queue.cpp new file mode 100644 index 00000000..764ff4a2 --- /dev/null +++ b/mock/distributeddb/common/src/task_queue.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "task_queue.h" + +namespace DistributedDB { +TaskQueue::TaskQueue(bool lockable) + :lockable_(lockable) +{} + +TaskQueue::~TaskQueue() +{} + +void TaskQueue::PutTask(const Task &task) +{ + if (!task) { + return; + } + tasks_.push(task); +} + +Task TaskQueue::GetTaskAutoLock() +{ + if (lockable_) { + std::thread::id thisId = std::this_thread::get_id(); + if (thisId != lockThread_) { + if (lockThread_ == std::thread::id()) { + lockThread_ = thisId; + } else { + return nullptr; + } + } + } + if (tasks_.empty()) { + ReleaseLock(); + return nullptr; + } + // copy and return + Task task = tasks_.front(); + tasks_.pop(); + return task; +} + +void TaskQueue::ReleaseLock() +{ + if (!lockable_) { + return; + } + if (lockThread_ == std::this_thread::get_id()) { + lockThread_ = std::thread::id(); + } +} + +bool TaskQueue::IsEmptyAndUnlocked() const +{ + if (lockable_) { + if (lockThread_ != std::thread::id()) { + return false; + } + } + return tasks_.empty(); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/task_queue.h b/mock/distributeddb/common/src/task_queue.h new file mode 100644 index 00000000..ea8337b7 --- /dev/null +++ b/mock/distributeddb/common/src/task_queue.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 TASK_QUEUE_H +#define TASK_QUEUE_H + +#include +#include +#include "task_pool.h" + +namespace DistributedDB { +class TaskQueue { +public: + explicit TaskQueue(bool lockable = true); + ~TaskQueue(); + void PutTask(const Task &task); + Task GetTaskAutoLock(); + void ReleaseLock(); + bool IsEmptyAndUnlocked() const; + +private: + bool lockable_; + std::thread::id lockThread_; + std::queue tasks_; +}; +} // namespace DistributedDB + +#endif // TASK_QUEUE_H diff --git a/mock/distributeddb/common/src/time_tick_monitor.cpp b/mock/distributeddb/common/src/time_tick_monitor.cpp new file mode 100644 index 00000000..1bb415e6 --- /dev/null +++ b/mock/distributeddb/common/src/time_tick_monitor.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "time_tick_monitor.h" + +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +TimeTickMonitor::TimeTickMonitor() + : timeChangedNotifier_(nullptr), + runtimeCxt_(nullptr), + monitorTimerId_(0), + monitorCallback_(0), + lastMonotonicTime_(0), + lastSystemTime_(0), + isStarted_(false) +{ +} + +TimeTickMonitor::~TimeTickMonitor() +{ + Stop(); + runtimeCxt_ = nullptr; +} + +int TimeTickMonitor::Start() +{ + if (isStarted_) { + return E_OK; + } + + int errCode = PrepareNotifierChain(); + if (errCode != E_OK) { + return errCode; + } + + lastMonotonicTime_ = GetMonotonicTime(); + lastSystemTime_ = GetSysCurrentTime(); + monitorCallback_ = std::bind(&TimeTickMonitor::TimeTick, this, std::placeholders::_1); + runtimeCxt_ = RuntimeContext::GetInstance(); + monitorTimerId_ = 0; + errCode = runtimeCxt_->SetTimer(MONITOR_INTERVAL, monitorCallback_, nullptr, monitorTimerId_); + if (errCode != E_OK) { + return errCode; + } + isStarted_ = true; + return E_OK; +} + +void TimeTickMonitor::Stop() +{ + if (!isStarted_) { + return; + } + + timeChangedNotifier_->UnRegisterEventType(TIME_CHANGE_EVENT); + RefObject::KillAndDecObjRef(timeChangedNotifier_); + timeChangedNotifier_ = nullptr; + runtimeCxt_->RemoveTimer(monitorTimerId_); + isStarted_ = false; +} + +NotificationChain::Listener *TimeTickMonitor::RegisterTimeChangedLister(const TimeChangedAction &action, int &errCode) +{ + if (timeChangedNotifier_ == nullptr) { + errCode = -E_NOT_INIT; + return nullptr; + } + + if (action == nullptr) { + errCode = -E_INVALID_ARGS; + return nullptr; + } + + return timeChangedNotifier_->RegisterListener(TIME_CHANGE_EVENT, action, nullptr, errCode); +} + +int TimeTickMonitor::PrepareNotifierChain() +{ + std::lock_guard autoLock(timeTickMonitorLock_); + if (timeChangedNotifier_ != nullptr) { + return E_OK; + } + + timeChangedNotifier_ = new (std::nothrow) NotificationChain(); + if (timeChangedNotifier_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + + int errCode = timeChangedNotifier_->RegisterEventType(TIME_CHANGE_EVENT); + if (errCode != E_OK) { + RefObject::KillAndDecObjRef(timeChangedNotifier_); + timeChangedNotifier_ = nullptr; + } + return errCode; +} + +int TimeTickMonitor::TimeTick(TimerId timerId) +{ + if (timerId != monitorTimerId_) { + return -E_INVALID_ARGS; + } + + uint64_t monotonicTime = GetMonotonicTime(); + uint64_t systemTime = GetSysCurrentTime(); + int64_t monotonicOffset = static_cast(monotonicTime - lastMonotonicTime_); + int64_t systemOffset = static_cast(systemTime - lastSystemTime_); + lastMonotonicTime_ = monotonicTime; + lastSystemTime_ = systemTime; + int64_t changedOffset = systemOffset - monotonicOffset; + if (std::abs(changedOffset) > MAX_NOISE) { + LOGI("Local system time may be changed! changedOffset %ld", changedOffset); + int ret = RuntimeContext::GetInstance()->ScheduleTask([this, changedOffset](){ + int64_t offset = changedOffset; + timeChangedNotifier_->NotifyEvent(TIME_CHANGE_EVENT, &offset); + }); + if (ret != E_OK) { + LOGE("TimeTickMonitor ScheduleTask failed %d", ret); + } + } + return E_OK; +} + +Timestamp TimeTickMonitor::GetSysCurrentTime() +{ + uint64_t curTime = 0; + int errCode = OS::GetCurrentSysTimeInMicrosecond(curTime); + if (errCode != E_OK) { + LOGE("TimeTickMonitor:get system time failed!"); + return INVALID_TIMESTAMP; + } + return curTime; +} + +Timestamp TimeTickMonitor::GetMonotonicTime() +{ + uint64_t time; + int errCode = OS::GetMonotonicRelativeTimeInMicrosecond(time); + if (errCode != E_OK) { + LOGE("GetMonotonicTime ERR! err = %d", errCode); + return INVALID_TIMESTAMP; + } + return time; +} + +void TimeTickMonitor::NotifyTimeChange(TimeOffset offset) const +{ + std::lock_guard lock(timeTickMonitorLock_); + if (timeChangedNotifier_ == nullptr) { + LOGD("NotifyTimeChange fail, timeChangedNotifier_ is null."); + return; + } + timeChangedNotifier_->NotifyEvent(TIME_CHANGE_EVENT, static_cast(&offset)); +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/common/src/time_tick_monitor.h b/mock/distributeddb/common/src/time_tick_monitor.h new file mode 100644 index 00000000..0bb42c5d --- /dev/null +++ b/mock/distributeddb/common/src/time_tick_monitor.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 TIME_TICK_MONITOR_H +#define TIME_TICK_MONITOR_H + +#include "runtime_context.h" +#include "db_types.h" +#include "platform_specific.h" +#include "macro_utils.h" + +namespace DistributedDB { +class TimeTickMonitor final { +public: + TimeTickMonitor(); + ~TimeTickMonitor(); + + DISABLE_COPY_ASSIGN_MOVE(TimeTickMonitor); + + // Start the TimeTickMonitor + int Start(); + + // Stop the TimeTickMonitor + void Stop(); + + // Register a time changed lister, it will be callback when local time changed. + NotificationChain::Listener *RegisterTimeChangedLister(const TimeChangedAction &action, int &errCode); + + // Notify TIME_CHANGE_EVENT. + void NotifyTimeChange(TimeOffset offset) const; +private: + static constexpr uint64_t MONITOR_INTERVAL = 1 * 1000; // 1s + static constexpr int64_t MAX_NOISE = 9 * 100 * 1000; // 900ms + static const EventType TIME_CHANGE_EVENT = 1; + static const uint64_t INVALID_TIMESTAMP = 0; + + // Get the current system time + static Timestamp GetSysCurrentTime(); + + // Get the Monotonic time + static Timestamp GetMonotonicTime(); + + // prepare notifier chain + int PrepareNotifierChain(); + + // Callback for the Timer + int TimeTick(TimerId timerId); + + mutable std::mutex timeTickMonitorLock_; + NotificationChain *timeChangedNotifier_; + RuntimeContext *runtimeCxt_; + TimerId monitorTimerId_ = 0; + TimerAction monitorCallback_; + Timestamp lastMonotonicTime_ = 0; + Timestamp lastSystemTime_ = 0; + bool isStarted_ = false; +}; +} // namespace DistributedDB + +#endif // TIME_TICK_MONITOR_H \ No newline at end of file diff --git a/mock/distributeddb/common/src/types_export.cpp b/mock/distributeddb/common/src/types_export.cpp new file mode 100644 index 00000000..5a4ee25e --- /dev/null +++ b/mock/distributeddb/common/src/types_export.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "types_export.h" + +#include +#include + +namespace DistributedDB { +const size_t CipherPassword::MAX_PASSWORD_SIZE; + +CipherPassword::CipherPassword() +{} + +CipherPassword::~CipherPassword() +{ + (void)Clear(); +} + +bool CipherPassword::operator==(const CipherPassword &input) const +{ + if (size_ != input.GetSize()) { + return false; + } + return memcmp(data_, input.GetData(), size_) == 0; +} + +bool CipherPassword::operator!=(const CipherPassword &input) const +{ + return !(*this == input); +} + +size_t CipherPassword::GetSize() const +{ + return size_; +} + +const uint8_t* CipherPassword::GetData() const +{ + return data_; +} + +int CipherPassword::SetValue(const uint8_t *inputData, size_t inputSize) +{ + if (inputSize > MAX_PASSWORD_SIZE) { + return ErrorCode::OVERSIZE; + } + if (inputSize != 0 && inputData == nullptr) { + return ErrorCode::INVALID_INPUT; + } + + if (inputSize != 0) { + std::copy(inputData, inputData + inputSize, data_); + } + + size_t filledSize = std::min(size_, MAX_PASSWORD_SIZE); + if (inputSize < filledSize) { + std::fill(data_ + inputSize, data_ + filledSize, UCHAR_MAX); + } + + size_ = inputSize; + return ErrorCode::OK; +} + +int CipherPassword::Clear() +{ + return SetValue(nullptr, 0); +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/common/src/user_change_monitor.cpp b/mock/distributeddb/common/src/user_change_monitor.cpp new file mode 100644 index 00000000..f670ce31 --- /dev/null +++ b/mock/distributeddb/common/src/user_change_monitor.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "user_change_monitor.h" + +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +UserChangeMonitor::UserChangeMonitor() + : userNotifier_(nullptr), + isStarted_(false) +{ +} + +UserChangeMonitor::~UserChangeMonitor() +{ + Stop(); +} + +int UserChangeMonitor::Start() +{ + if (isStarted_) { + return E_OK; + } + + int errCode = PrepareNotifierChain(); + if (errCode != E_OK) { + return errCode; + } + isStarted_ = true; + return E_OK; +} + +void UserChangeMonitor::Stop() +{ + if (!isStarted_) { + return; + } + if (userNotifier_ != nullptr) { + userNotifier_->UnRegisterEventType(USER_ACTIVE_EVENT); + userNotifier_->UnRegisterEventType(USER_NON_ACTIVE_EVENT); + userNotifier_->UnRegisterEventType(USER_ACTIVE_TO_NON_ACTIVE_EVENT); + RefObject::KillAndDecObjRef(userNotifier_); + userNotifier_ = nullptr; + } + isStarted_ = false; +} + +NotificationChain::Listener *UserChangeMonitor::RegisterUserChangedListerner(const UserChangedAction &action, + EventType event, int &errCode) +{ + std::shared_lock lockGuard(userChangeMonitorLock_); + if (action == nullptr) { + errCode = -E_INVALID_ARGS; + return nullptr; + } + if (userNotifier_ == nullptr) { + errCode = -E_NOT_INIT; + return nullptr; + } + LOGI("[UserChangeMonitor] RegisterUserChangedListerner event=%d", event); + return userNotifier_->RegisterListener(event, action, nullptr, errCode); +} + +int UserChangeMonitor::PrepareNotifierChain() +{ + int errCode = E_OK; + std::unique_lock lockGuard(userChangeMonitorLock_); + if (userNotifier_ != nullptr) { + return E_OK; + } + userNotifier_ = new (std::nothrow) NotificationChain(); + if (userNotifier_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + errCode = userNotifier_->RegisterEventType(USER_ACTIVE_EVENT); + if (errCode != E_OK) { + goto ERROR_HANDLE; + } + errCode = userNotifier_->RegisterEventType(USER_NON_ACTIVE_EVENT); + if (errCode != E_OK) { + userNotifier_->UnRegisterEventType(USER_ACTIVE_EVENT); + goto ERROR_HANDLE; + } + errCode = userNotifier_->RegisterEventType(USER_ACTIVE_TO_NON_ACTIVE_EVENT); + if (errCode != E_OK) { + userNotifier_->UnRegisterEventType(USER_ACTIVE_EVENT); + userNotifier_->UnRegisterEventType(USER_NON_ACTIVE_EVENT); + goto ERROR_HANDLE; + } + return errCode; +ERROR_HANDLE: + RefObject::KillAndDecObjRef(userNotifier_); + userNotifier_ = nullptr; + return errCode; +} + +void UserChangeMonitor::NotifyUserChanged() const +{ + std::shared_lock lockGuard(userChangeMonitorLock_); + if (userNotifier_ == nullptr) { + LOGD("NotifyUNotifyUserChangedserChange fail, userChangedNotifier is null."); + return; + } + LOGI("[UserChangeMonitor] begin to notify event"); + userNotifier_->NotifyEvent(USER_ACTIVE_EVENT, nullptr); + userNotifier_->NotifyEvent(USER_NON_ACTIVE_EVENT, nullptr); + userNotifier_->NotifyEvent(USER_ACTIVE_TO_NON_ACTIVE_EVENT, nullptr); +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/common/src/value_object.cpp b/mock/distributeddb/common/src/value_object.cpp new file mode 100644 index 00000000..3ec6ee99 --- /dev/null +++ b/mock/distributeddb/common/src/value_object.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "value_object.h" +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +ValueObject::ValueObject(const ValueObject &other) +{ + isValid_ = other.isValid_; + value_ = other.value_; + dataBeforeOffset_ = other.dataBeforeOffset_; +} + +ValueObject& ValueObject::operator=(const ValueObject &other) +{ + if (&other != this) { + isValid_ = other.isValid_; + value_ = other.value_; + dataBeforeOffset_ = other.dataBeforeOffset_; + } + return *this; +} + +int ValueObject::Parse(const std::string &inString) +{ + if (isValid_) { + return -E_NOT_PERMIT; + } + int errCode = value_.Parse(inString); + isValid_ = (errCode == E_OK); + return errCode; +} + +int ValueObject::Parse(const std::vector &inData) +{ + if (isValid_) { + return -E_NOT_PERMIT; + } + int errCode = value_.Parse(inData); + isValid_ = (errCode == E_OK); + return errCode; +} + +int ValueObject::Parse(const uint8_t *dataBegin, const uint8_t *dataEnd, uint32_t offset) +{ + if (isValid_) { + return -E_NOT_PERMIT; + } + if (dataBegin == nullptr || dataBegin >= dataEnd || offset >= static_cast(dataEnd - dataBegin)) { + LOGE("[Value][Parse] Data range invalid: dataEnd - dataBegin=%" PRId64 ", offset=%" PRIu32, + static_cast(dataEnd - dataBegin), offset); + return -E_INVALID_ARGS; + } + int errCode = value_.Parse(dataBegin + offset, dataEnd); + if (errCode != E_OK) { + return errCode; + } + dataBeforeOffset_.assign(dataBegin, dataBegin + offset); + isValid_ = true; + return E_OK; +} + +bool ValueObject::IsValid() const +{ + return isValid_; +} + +std::string ValueObject::ToString() const +{ + if (dataBeforeOffset_.empty()) { + return value_.ToString(); + } + // It is OK if '\0' exist in dataBeforeOffset_, when call string.size, '\0' will not disturb + std::string outString(dataBeforeOffset_.begin(), dataBeforeOffset_.end()); + outString += value_.ToString(); + return outString; +} + +void ValueObject::WriteIntoVector(std::vector &outData) const +{ + // If not valid, valueStr and dataBeforeOffset_ will be empty + std::string valueStr = value_.ToString(); + // If valid, dataBeforeOffset_ may be empty + outData.insert(outData.end(), dataBeforeOffset_.begin(), dataBeforeOffset_.end()); + outData.insert(outData.end(), valueStr.begin(), valueStr.end()); +} + +bool ValueObject::IsFieldPathExist(const FieldPath &inPath) const +{ + return value_.IsFieldPathExist(inPath); +} + +int ValueObject::GetFieldTypeByFieldPath(const FieldPath &inPath, FieldType &outType) const +{ + if (!isValid_) { + return -E_NOT_PERMIT; + } + return value_.GetFieldTypeByFieldPath(inPath, outType); +} + +int ValueObject::GetFieldValueByFieldPath(const FieldPath &inPath, FieldValue &outValue) const +{ + if (!isValid_) { + return -E_NOT_PERMIT; + } + return value_.GetFieldValueByFieldPath(inPath, outValue); +} + +int ValueObject::GetSubFieldPath(const FieldPath &inPath, std::set &outSubPath) const +{ + if (!isValid_) { + return -E_NOT_PERMIT; + } + return value_.GetSubFieldPath(inPath, outSubPath); +} + +int ValueObject::GetSubFieldPath(const std::set &inPath, std::set &outSubPath) const +{ + if (!isValid_) { + return -E_NOT_PERMIT; + } + return value_.GetSubFieldPath(inPath, outSubPath); +} + +int ValueObject::GetSubFieldPathAndType(const FieldPath &inPath, std::map &outSubPathType) const +{ + if (!isValid_) { + return -E_NOT_PERMIT; + } + return value_.GetSubFieldPathAndType(inPath, outSubPathType); +} + +int ValueObject::GetSubFieldPathAndType(const std::set &inPath, + std::map &outSubPathType) const +{ + if (!isValid_) { + return -E_NOT_PERMIT; + } + return value_.GetSubFieldPathAndType(inPath, outSubPathType); +} + +int ValueObject::InsertField(const FieldPath &inPath, FieldType inType, const FieldValue &inValue) +{ + int errCode = value_.InsertField(inPath, inType, inValue); + if (errCode == E_OK) { + isValid_ = true; + } + return errCode; +} + +int ValueObject::DeleteField(const FieldPath &inPath) +{ + if (!isValid_) { + return -E_NOT_PERMIT; + } + return value_.DeleteField(inPath); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/common/src/zlib_compression.cpp b/mock/distributeddb/common/src/zlib_compression.cpp new file mode 100644 index 00000000..28a24db4 --- /dev/null +++ b/mock/distributeddb/common/src/zlib_compression.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "zlib_compression.h" +#ifndef OMIT_ZLIB +#include + +#include "db_constant.h" +#include "db_errno.h" +#include "log_print.h" +#include "types_export.h" + +namespace DistributedDB { +static ZlibCompression g_zlibInstance; + +ZlibCompression::ZlibCompression() +{ + DataCompression::Register(CompressAlgorithm::ZLIB, this); +} + +int ZlibCompression::Compress(const std::vector &srcData, std::vector &destData) const +{ + auto srcLen = srcData.size(); + auto destLen = compressBound(srcLen); + if (srcLen > DBConstant::MAX_SYNC_BLOCK_SIZE || destLen > DBConstant::MAX_SYNC_BLOCK_SIZE) { + LOGE("Too long to compress, srcLen:%zu, destLen:%lu.", srcLen, destLen); + return -E_INVALID_ARGS; + } + + // Alloc memory. + destData.resize(destLen); + + // Compress. + int errCode = compress(destData.data(), &destLen, srcData.data(), srcLen); + if (errCode != Z_OK) { + LOGE("Compress parcel failed, errCode = %d", errCode); + return -E_SYSTEM_API_FAIL; + } + + destData.resize(destLen); + destData.shrink_to_fit(); + return E_OK; +} + +int ZlibCompression::Uncompress(const std::vector &srcData, std::vector &destData, + uint32_t destLen) const +{ + auto srcLen = srcData.size(); + if (srcLen > DBConstant::MAX_SYNC_BLOCK_SIZE || destLen > DBConstant::MAX_SYNC_BLOCK_SIZE) { + LOGE("Too long to uncompress, srcLen:%zu, destLen:%lu.", srcLen, destLen); + return -E_INVALID_ARGS; + } + + // Alloc dest memory. + destData.resize(destLen); + + // Uncompress. + uLongf destDataLen = destLen; + int errCode = uncompress(destData.data(), &destDataLen, srcData.data(), srcData.size()); + if (errCode != Z_OK) { + LOGE("Uncompress failed, errCode = %d", errCode); + return -E_SYSTEM_API_FAIL; + } + + destData.resize(destDataLen); + destData.shrink_to_fit(); + return E_OK; +} +} // namespace DistributedDB +#endif // OMIT_ZLIB diff --git a/mock/distributeddb/communicator/include/combine_status.h b/mock/distributeddb/communicator/include/combine_status.h new file mode 100644 index 00000000..931f84ce --- /dev/null +++ b/mock/distributeddb/communicator/include/combine_status.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 COMBINE_STATUS_H +#define COMBINE_STATUS_H + +#include +#include + +namespace DistributedDB { +/* + * Class CombineStatus does not support multi-thread. + * It should be protected by mutex in multi-thread environment + */ +class CombineStatus { +public: + void UpdateProgressId(uint64_t inProgressId); + uint64_t GetProgressId() const; + bool CheckProgress(); + + void SetFragmentLen(uint32_t inFragLen); + void SetLastFragmentLen(uint32_t inLastFragLen); + uint32_t GetThisFragmentLength(uint16_t inFragNo) const; + uint32_t GetThisFragmentOffset(uint16_t inFragNo) const; + + void SetFragmentCount(uint16_t inFragCount); + bool IsFragNoAlreadyExist(uint16_t inFragNo) const; + void CheckInFragmentNo(uint16_t inFragNo); + bool IsCombineDone() const; + +private: + uint64_t progressId_ = 0; + bool hasProgressFlag_ = true; + + uint32_t fragmentLen_ = 0; // Indicate the length of fragment that is split from a frame except the last one + uint32_t lastFragmentLen_ = 0; // Indicate the length of the last fragment that is split from a frame + + uint16_t fragmentCount_ = 0; + std::set combinedFragmentNo_; +}; +} // namespace DistributedDB + +#endif // COMBINE_STATUS_H diff --git a/mock/distributeddb/communicator/include/communicator_aggregator.h b/mock/distributeddb/communicator/include/communicator_aggregator.h new file mode 100644 index 00000000..30ca55f9 --- /dev/null +++ b/mock/distributeddb/communicator/include/communicator_aggregator.h @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 COMMUNICATORAGGREGATOR_H +#define COMMUNICATORAGGREGATOR_H + +#include +#include +#include +#include +#include +#include +#include +#include "iadapter.h" +#include "parse_result.h" +#include "icommunicator.h" +#include "frame_combiner.h" +#include "frame_retainer.h" +#include "send_task_scheduler.h" +#include "icommunicator_aggregator.h" + +namespace DistributedDB { +// Forward Declarations +class Communicator; +class SerialBuffer; +class CommunicatorLinker; + +struct TaskConfig { + bool nonBlock; + uint32_t timeout; + Priority prio; +}; + +/* + * Upper layer Module should comply with calling convention, Inner Module interface will not do excessive check + */ +class CommunicatorAggregator : public ICommunicatorAggregator { +public: + CommunicatorAggregator(); + ~CommunicatorAggregator() override; + + DISABLE_COPY_ASSIGN_MOVE(CommunicatorAggregator); + + // See ICommunicatorAggregator for detail + int Initialize(IAdapter *inAdapter) override; + + // Must not call any other functions if Finalize had been called. In fact, Finalize has no chance to be called. + void Finalize() override; + + ICommunicator *AllocCommunicator(uint64_t commLabel, int &outErrorNo) override; + ICommunicator *AllocCommunicator(const LabelType &commLabel, int &outErrorNo) override; + + void ReleaseCommunicator(ICommunicator *inCommunicator) override; + + int RegCommunicatorLackCallback(const CommunicatorLackCallback &onCommLack, const Finalizer &inOper) override; + int RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) override; + + // return optimal allowed data size(Some header is taken into account and subtract) + uint32_t GetCommunicatorAggregatorMtuSize() const; + uint32_t GetCommunicatorAggregatorMtuSize(const std::string &target) const; + + // return timeout in range [5s, 60s] + uint32_t GetCommunicatorAggregatorTimeout() const; + uint32_t GetCommunicatorAggregatorTimeout(const std::string &target) const; + bool IsDeviceOnline(const std::string &device) const; + int GetLocalIdentity(std::string &outTarget) const override; + + // Get the protocol version of remote target. Return -E_NOT_FOUND if no record. + int GetRemoteCommunicatorVersion(const std::string &target, uint16_t &outVersion) const; + + // Called by communicator to make itself really in work + void ActivateCommunicator(const LabelType &commLabel); + + // SerialBuffer surely is heap memory, CreateSendTask responsible for lifecycle + int CreateSendTask(const std::string &dstTarget, SerialBuffer *inBuff, FrameType inType, + const TaskConfig &inConfig, const OnSendEnd &onEnd = nullptr); + + static void EnableCommunicatorNotFoundFeedback(bool isEnable); + + std::shared_ptr GetExtendHeaderHandle(const ExtendInfo ¶mInfo); + +private: + // Working in a dedicated thread + void SendDataRoutine(); + void SendPacketsAndDisposeTask(const SendTask &inTask, + const std::vector>> &eachPacket); + + int RetryUntilTimeout(SendTask &inTask, uint32_t timeout, Priority inPrio); + void TaskFinalizer(const SendTask &inTask, int result); + void NotifySendableToAllCommunicator(); + + // Call from Adapter by register these function + void OnBytesReceive(const std::string &srcTarget, const uint8_t *bytes, uint32_t length, + const std::string &userId); + void OnTargetChange(const std::string &target, bool isConnect); + void OnSendable(const std::string &target); + + void OnFragmentReceive(const std::string &srcTarget, const uint8_t *bytes, uint32_t length, + const ParseResult &inResult, const std::string &userId); + + int OnCommLayerFrameReceive(const std::string &srcTarget, const ParseResult &inResult); + int OnAppLayerFrameReceive(const std::string &srcTarget, const uint8_t *bytes, + uint32_t length, const ParseResult &inResult, const std::string &userId); + int OnAppLayerFrameReceive(const std::string &srcTarget, SerialBuffer *&inFrameBuffer, + const ParseResult &inResult, const std::string &userId); + + // Function with suffix NoMutex should be called with mutex in the caller + int TryDeliverAppLayerFrameToCommunicatorNoMutex(const std::string &srcTarget, SerialBuffer *&inFrameBuffer, + const LabelType &toLabel); + + // Auxiliary function for cutting short primary function + int RegCallbackToAdapter(); + void UnRegCallbackFromAdapter(); + void GenerateLocalSourceId(); + bool ReGenerateLocalSourceIdIfNeed(); + + // Feedback related functions + void TriggerVersionNegotiation(const std::string &dstTarget); + void TryToFeedbackWhenCommunicatorNotFound(const std::string &dstTarget, const LabelType &dstLabel, + const SerialBuffer *inOriFrame); + void TriggerCommunicatorNotFoundFeedback(const std::string &dstTarget, const LabelType &dstLabel, Message* &oriMsg); + + // Record the protocol version of remote target. + void SetRemoteCommunicatorVersion(const std::string &target, uint16_t version); + + DECLARE_OBJECT_TAG(CommunicatorAggregator); + + static std::atomic isCommunicatorNotFoundFeedbackEnable_; + + std::atomic shutdown_; + std::atomic incFrameId_; + std::atomic localSourceId_; + + // Handle related + mutable std::mutex commMapMutex_; + std::map> commMap_; // bool true indicate communicator activated + FrameCombiner combiner_; + FrameRetainer retainer_; + SendTaskScheduler scheduler_; + IAdapter *adapterHandle_ = nullptr; + CommunicatorLinker *commLinker_ = nullptr; + + // Thread related + std::thread exclusiveThread_; + bool wakingSignal_ = false; + mutable std::mutex wakingMutex_; + std::condition_variable wakingCv_; + + // RetryCreateTask related + mutable std::mutex retryMutex_; + std::condition_variable retryCv_; + + // Remote target version related + mutable std::mutex versionMapMutex_; + std::map versionMap_; + + // CommLack Callback related + CommunicatorLackCallback onCommLackHandle_; + Finalizer onCommLackFinalizer_; + mutable std::mutex onCommLackMutex_; + + // Connect Callback related + OnConnectCallback onConnectHandle_; + Finalizer onConnectFinalizer_; + mutable std::mutex onConnectMutex_; +}; +} // namespace DistributedDB + +#endif // COMMUNICATORAGGREGATOR_H diff --git a/mock/distributeddb/communicator/include/communicator_type_define.h b/mock/distributeddb/communicator/include/communicator_type_define.h new file mode 100644 index 00000000..af35a296 --- /dev/null +++ b/mock/distributeddb/communicator/include/communicator_type_define.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 COMMUNICATOR_TYPE_DEFINE_H +#define COMMUNICATOR_TYPE_DEFINE_H + +#include +#include +#include +#include +#include "db_errno.h" + +namespace DistributedDB { +using LabelType = std::vector; +using Finalizer = std::function; +using OnSendEnd = std::function; +using OnConnectCallback = std::function; +constexpr unsigned int COMM_LABEL_LENGTH = 32; // Using SHA256 which length is 32 +constexpr uint32_t MAX_TOTAL_LEN = 104857600; // 100M Limitation For Max Total Length + +template +int RegCallBack(const T &newCallback, T &oldCallback, const Finalizer &newFinalizer, Finalizer &oldFinalizer) +{ + if (newCallback && oldCallback) { + // Already registered, not allowed + return -E_ALREADY_REGISTER; + } + if (newCallback && !oldCallback) { + // Do register + oldCallback = newCallback; + oldFinalizer = newFinalizer; + return E_OK; + } + if (!newCallback && oldCallback) { + // Do unregister + if (oldFinalizer) { + oldFinalizer(); + } + oldCallback = nullptr; + oldFinalizer = nullptr; + return E_OK; + } + return -E_NOT_PERMIT; +} + +enum class Priority { + LOW = 0, // Usually for datasync and its response + NORMAL = 1, // Usually for timesync and its response + HIGH = 2, // Only for communicator inside +}; + +enum class FrameType { + EMPTY = 0, // Used for gossip or help version negotiation + APPLICATION_MESSAGE = 1, + COMMUNICATION_LABEL_EXCHANGE = 2, + COMMUNICATION_LABEL_EXCHANGE_ACK = 3, + INVALID_MAX_FRAME_TYPE = 4, +}; +} // namespace DistributedDB + +#endif // COMMUNICATOR_TYPE_DEFINE_H diff --git a/mock/distributeddb/communicator/include/frame_combiner.h b/mock/distributeddb/communicator/include/frame_combiner.h new file mode 100644 index 00000000..33124c49 --- /dev/null +++ b/mock/distributeddb/communicator/include/frame_combiner.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 FRAME_COMBINER_H +#define FRAME_COMBINER_H + +#include +#include +#include +#include "semaphore_utils.h" +#include "macro_utils.h" +#include "parse_result.h" +#include "combine_status.h" +#include "runtime_context.h" + +namespace DistributedDB { +class SerialBuffer; // Forward Declarations + +struct CombineWork { + SerialBuffer *buffer; + CombineStatus status; + ParseResult frameInfo; +}; + +class FrameCombiner { +public: + FrameCombiner() = default; // Default constructor must be explicitly provided due to DISABLE_COPY_ASSIGN_MOVE + ~FrameCombiner() = default; // Since constructor must be provided, codedex demand deconstructor be provided as well + DISABLE_COPY_ASSIGN_MOVE(FrameCombiner); + + // Start the timer to supervise the progress + void Initialize(); + + // Clear the CombineWorkPool and stop the timer + void Finalize(); + + // outErrorNo is set E_OK if nothing error happened. + // Return nullptr if error happened or no combination is done. + // Return a valid buffer as well as a valid outFrameResult if combination done. + // The caller is responsible for release the buffer. + SerialBuffer *AssembleFrameFragment(const uint8_t *bytes, uint32_t length, const ParseResult &inPacketInfo, + ParseResult &outFrameInfo, int &outErrorNo); + +private: + // This methed called from timer, it has overallMutex_ protect itself inside the method + void PeriodicalSurveillance(); + + // Following method should be called under protection of overallMutex_ outside the method + int ContinueExistCombineWork(const uint8_t *bytes, uint32_t length, const ParseResult &inPacketInfo); + int CreateNewCombineWork(const uint8_t *bytes, uint32_t length, const ParseResult &inPacketInfo); + void AbortCombineWorkBySource(uint64_t inSourceId); + + bool CheckPacketWithOriWork(const ParseResult &inPacketInfo, const CombineWork &inWork); + SerialBuffer *CreateNewFrameBuffer(const ParseResult &inInfo); + + mutable std::mutex overallMutex_; + + TimerId timerId_ = 0; // 0 is invalid timerId + bool isTimerWork_ = false; + SemaphoreUtils timerRemovedIndicator_ {0}; + uint64_t incProgressId_ = 0; + uint64_t totalSizeByByte_ = 0; + std::map> combineWorkPool_; +}; +} // namespace DistributedDB + +#endif // FRAME_COMBINER_H diff --git a/mock/distributeddb/communicator/include/frame_retainer.h b/mock/distributeddb/communicator/include/frame_retainer.h new file mode 100644 index 00000000..25900736 --- /dev/null +++ b/mock/distributeddb/communicator/include/frame_retainer.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 FRAME_RETAINER_H +#define FRAME_RETAINER_H + +#include +#include +#include +#include +#include "macro_utils.h" +#include "runtime_context.h" + +namespace DistributedDB { +class SerialBuffer; // Forward Declarations + +struct FrameInfo { + SerialBuffer *buffer; + std::string srcTarget; + LabelType commLabel; + uint32_t frameId; +}; + +struct RetainWork { + SerialBuffer *buffer; + uint32_t frameId; + uint32_t remainTime; // in second +}; + +class FrameRetainer { +public: + FrameRetainer() = default; // Default constructor must be explicitly provided due to DISABLE_COPY_ASSIGN_MOVE + ~FrameRetainer() = default; // Since constructor must be provided, codedex demand deconstructor be provided as well + DISABLE_COPY_ASSIGN_MOVE(FrameRetainer); + + // Start the timer to clear up overtime frames + void Initialize(); + + // Stop the timer and clear the RetainWorkPool + void Finalize(); + + // Always accept the frame, which may be retained actually or perhaps discarded immediately. + void RetainFrame(const FrameInfo &inFrame); + + // Out frames will be in the order of retention. The retainer no longer in charge of the returned frames. + std::list FetchFramesForSpecificCommunicator(const LabelType &inCommLabel); + +private: + // This methed called from timer, it has overallMutex_ protect itself inside the method + void PeriodicalSurveillance(); + + // Following method should be called under protection of overallMutex_ outside the method + void DiscardObsoleteFramesIfNeed(); + void ShrinkRetainWorkPool(); + + mutable std::mutex overallMutex_; + + TimerId timerId_ = 0; // 0 is invalid timerId + bool isTimerWork_ = false; + + uint32_t totalSizeByByte_ = 0; + uint32_t totalRetainFrames_ = 0; + + uint64_t incRetainOrder_ = 0; + std::map>> retainWorkPool_; +}; +} // namespace DistributedDB + +#endif // FRAME_RETAINER_H diff --git a/mock/distributeddb/communicator/include/iadapter.h b/mock/distributeddb/communicator/include/iadapter.h new file mode 100644 index 00000000..f238114c --- /dev/null +++ b/mock/distributeddb/communicator/include/iadapter.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 IADAPTER_H +#define IADAPTER_H + +#include +#include +#include +#include +#include "communicator_type_define.h" +#include "iprocess_communicator.h" + +namespace DistributedDB { +// SendableCallback only notify when status changed from unsendable to sendable +using BytesReceiveCallback = std::function; +using TargetChangeCallback = std::function; +using SendableCallback = std::function; + +class IAdapter { +public: + // Register all callback before call StartAdapter. + // Return 0 as success. Return negative as error + // The StartAdapter should only be called by its user not owner + virtual int StartAdapter() = 0; + + // The StopAdapter may be called by its user in precondition of StartAdapter success + // The StopAdapter should only be called by its user not owner + virtual void StopAdapter() = 0; + + // Should returns the multiples of 8 + virtual uint32_t GetMtuSize() = 0; + virtual uint32_t GetMtuSize(const std::string &target) = 0; + + // Should returns timeout in range [5s, 60s] + virtual uint32_t GetTimeout() = 0; + virtual uint32_t GetTimeout(const std::string &target) = 0; + + // Get local target name for identify self + virtual int GetLocalIdentity(std::string &outTarget) = 0; + + // Not assume bytes to be heap memory. Not assume SendBytes to be not blocking + // Return 0 as success. Return negative as error + virtual int SendBytes(const std::string &dstTarget, const uint8_t *bytes, uint32_t length) = 0; + + // Pass nullptr as inHandle to do unReg if need (inDecRef also nullptr) + // Return 0 as success. Return negative as error + virtual int RegBytesReceiveCallback(const BytesReceiveCallback &onReceive, const Finalizer &inOper) = 0; + + // Pass nullptr as inHandle to do unReg if need (inDecRef also nullptr) + // Return 0 as success. Return negative as error + virtual int RegTargetChangeCallback(const TargetChangeCallback &onChange, const Finalizer &inOper) = 0; + + // Pass nullptr as inHandle to do unReg if need (inDecRef also nullptr) + // Return 0 as success. Return negative as error + virtual int RegSendableCallback(const SendableCallback &onSendable, const Finalizer &inOper) = 0; + + virtual bool IsDeviceOnline(const std::string &device) = 0; + + virtual std::shared_ptr GetExtendHeaderHandle(const ExtendInfo ¶mInfo) = 0; + + virtual ~IAdapter() {}; +}; +} // namespace DistributedDB + +#endif // IADAPTER_H diff --git a/mock/distributeddb/communicator/include/icommunicator.h b/mock/distributeddb/communicator/include/icommunicator.h new file mode 100644 index 00000000..0234c2dc --- /dev/null +++ b/mock/distributeddb/communicator/include/icommunicator.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 ICOMMUNICATOR_H +#define ICOMMUNICATOR_H + +#include +#include +#include "message.h" +#include "ref_object.h" +#include "communicator_type_define.h" +#include "iprocess_communicator.h" +#include "db_properties.h" + +namespace DistributedDB { +// inMsg is heap memory, its ownership transfers by calling OnMessageCallback +using OnMessageCallback = std::function; +constexpr uint32_t SEND_TIME_OUT = 3000; // 3s + +struct SendConfig { + bool nonBlock = false; + bool isNeedExtendHead = false; + uint32_t timeout = SEND_TIME_OUT; + ExtendInfo paramInfo; +}; + +inline void SetSendConfigParam(const DBProperties &dbProperty, const std::string &dstTarget, bool nonBlock, + uint32_t timeout, SendConfig &sendConf) +{ + sendConf.nonBlock = nonBlock; + sendConf.timeout = timeout; + sendConf.isNeedExtendHead = dbProperty.GetBoolProp(DBProperties::SYNC_DUAL_TUPLE_MODE, + false); + sendConf.paramInfo.appId = dbProperty.GetStringProp(DBProperties::APP_ID, ""); + sendConf.paramInfo.userId = dbProperty.GetStringProp(DBProperties::USER_ID, ""); + sendConf.paramInfo.storeId = dbProperty.GetStringProp(DBProperties::STORE_ID, ""); + sendConf.paramInfo.dstTarget = dstTarget; +} + +class ICommunicator : public virtual RefObject { +public: + // Message heap memory + // Return 0 as success. Return negative as error + virtual int RegOnMessageCallback(const OnMessageCallback &onMessage, const Finalizer &inOper) = 0; + virtual int RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) = 0; + virtual int RegOnSendableCallback(const std::function &onSendable, const Finalizer &inOper) = 0; + + virtual void Activate() = 0; + + // return optimal allowed data size(Some header is taken into account and subtract) + virtual uint32_t GetCommunicatorMtuSize() const = 0; + virtual uint32_t GetCommunicatorMtuSize(const std::string &target) const = 0; + + // return timeout in range [5s, 60s] + virtual uint32_t GetTimeout() const = 0; + virtual uint32_t GetTimeout(const std::string &target) const = 0; + + virtual bool IsDeviceOnline(const std::string &device) const = 0; + + // Get local target name for identify self + virtual int GetLocalIdentity(std::string &outTarget) const = 0; + + // Get the protocol version of remote target. Return -E_NOT_FOUND if no record. + virtual int GetRemoteCommunicatorVersion(const std::string &target, uint16_t &outVersion) const = 0; + + // inMsg is heap memory, its ownership transfers by calling SendMessage + // If send fail in SendMessage, nonBlock true will return, nonBlock false will block and retry + // timeout is ignore if nonBlock true. OnSendEnd won't always be called such as when in finalize stage. + // Return 0 as success. Return negative as error + virtual int SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config) = 0; + virtual int SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config, + const OnSendEnd &onEnd) = 0; // HW Code Regulation do not allow to use default parameters on virtual function + + virtual ~ICommunicator() {}; +}; +} // namespace DistributedDB + +#endif // ICOMMUNICATOR_H diff --git a/mock/distributeddb/communicator/include/icommunicator_aggregator.h b/mock/distributeddb/communicator/include/icommunicator_aggregator.h new file mode 100644 index 00000000..ee5c59df --- /dev/null +++ b/mock/distributeddb/communicator/include/icommunicator_aggregator.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 ICOMMUNICATORAGGREGATOR_H +#define ICOMMUNICATORAGGREGATOR_H + +#include +#include "iadapter.h" +#include "ref_object.h" +#include "communicator_type_define.h" + +namespace DistributedDB { +class ICommunicator; // Forward Declaration +// Return E_OK to indicate to retain received frame. Do not block during callback. +using CommunicatorLackCallback = std::function; + +class ICommunicatorAggregator : public virtual RefObject { +public: + // Return 0 as success. Return negative as error + // The caller is the owner of inAdapter and responsible for manage its lifecycle. + // The ICommunicatorAggregator is only the user of inAdapter + // If Initialize fail, the ICommunicatorAggregator will rollback what had done to inAdapter so it can be reuse. + virtual int Initialize(IAdapter *inAdapter) = 0; + + // Call this method after Initialize successfully and before destroy the ICommunicatorAggregator + // Emphasize again : DO NOT CALL Finalize IF Initialize FAIL. + // Must not call any other functions if Finalize had been called. + // More likely, The Finalize has no chance to be called. since it is process level. + virtual void Finalize() = 0; + + // If not success, return nullptr and set outErrorNo + virtual ICommunicator *AllocCommunicator(uint64_t commLabel, int &outErrorNo) = 0; + virtual ICommunicator *AllocCommunicator(const LabelType &commLabel, int &outErrorNo) = 0; + virtual void ReleaseCommunicator(ICommunicator *inCommunicator) = 0; + virtual int RegCommunicatorLackCallback(const CommunicatorLackCallback &onCommLack, const Finalizer &inOper) = 0; + virtual int RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) = 0; + virtual int GetLocalIdentity(std::string &outTarget) const = 0; + virtual ~ICommunicatorAggregator() {}; +}; +} // namespace DistributedDB + +#endif // ICOMMUNICATORAGGREGATOR_H diff --git a/mock/distributeddb/communicator/include/message.h b/mock/distributeddb/communicator/include/message.h new file mode 100644 index 00000000..416e0831 --- /dev/null +++ b/mock/distributeddb/communicator/include/message.h @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MESSAGE_H +#define MESSAGE_H + +#include +#include +#include +#include "db_errno.h" +#include "macro_utils.h" +#include "object_holder.h" +#include "object_holder_typed.h" +#include "communicator_type_define.h" + +namespace DistributedDB { +constexpr uint32_t INVALID_MESSAGE_ID = 0; +constexpr uint16_t TYPE_INVALID = 0; +constexpr uint16_t TYPE_REQUEST = 1; +constexpr uint16_t TYPE_RESPONSE = 2; +constexpr uint16_t TYPE_NOTIFY = 3; +constexpr uint32_t NO_ERROR = 0; +constexpr uint16_t MSG_VERSION_BASE = 0; +constexpr uint16_t MSG_VERSION_EXT = 1; + +class Message { +public: + Message() = default; + + explicit Message(uint32_t inMsgId) + { + messageId_ = inMsgId; + } + + ~Message() + { + if (holderPtr_ != nullptr) { + delete holderPtr_; + holderPtr_ = nullptr; + } + } + + DISABLE_COPY_ASSIGN_MOVE(Message); + + // For user convenience, inObj can be a stack object, provided that it supports copy construct + // Set Object again will delete object that set before if successfully, otherwise impact no change + template + int SetCopiedObject(const T &inObj) + { + T *copiedObject = new (std::nothrow) T(inObj); + if (copiedObject == nullptr) { + return -E_OUT_OF_MEMORY; + } + ObjectHolder *tmpHolderPtr = new (std::nothrow) ObjectHolderTyped(copiedObject); + if (tmpHolderPtr == nullptr) { + delete copiedObject; + return -E_OUT_OF_MEMORY; + } + if (holderPtr_ != nullptr) { + delete holderPtr_; + } + holderPtr_ = tmpHolderPtr; + return E_OK; + } + + // By calling this method successfully, The ownership of inObj will be taken up by this class + // Thus this class is responsible for delete the inObj + // If calling this method unsuccessfully, The ownership of inObj is not changed + // Set Object again will delete object that set before if successfully, otherwise impact no change + template + int SetExternalObject(T *&inObj) + { + if (inObj == nullptr) { + return -E_INVALID_ARGS; + } + ObjectHolder *tmpHolderPtr = new (std::nothrow) ObjectHolderTyped(inObj); + if (tmpHolderPtr == nullptr) { + return -E_OUT_OF_MEMORY; + } + if (holderPtr_ != nullptr) { + delete holderPtr_; + } + holderPtr_ = tmpHolderPtr; + inObj = nullptr; + return E_OK; + } + + // Calling this method in form of GetObject() to specify return type based on the MessageId + template + const T *GetObject() const + { + if (holderPtr_ == nullptr) { + return nullptr; + } + ObjectHolderTyped *realHolderPtr = static_cast *>(holderPtr_); + return realHolderPtr->GetObject(); + } + + int SetMessageType(uint16_t inMsgType) + { + if (inMsgType != TYPE_REQUEST && inMsgType != TYPE_RESPONSE && inMsgType != TYPE_NOTIFY) { + return -E_INVALID_ARGS; + } + messageType_ = inMsgType; + return E_OK; + } + + void SetMessageId(uint32_t inMessageId) + { + messageId_ = inMessageId; + } + + void SetSessionId(uint32_t inSessionId) + { + sessionId_ = inSessionId; + } + + void SetSequenceId(uint32_t inSequenceId) + { + sequenceId_ = inSequenceId; + } + + void SetErrorNo(uint32_t inErrorNo) + { + errorNo_ = inErrorNo; + } + + void SetTarget(const std::string &inTarget) + { + target_ = inTarget; + } + + void SetPriority(Priority inPriority) + { + prio_ = inPriority; + } + + void SetVersion(uint16_t inVersion) + { + if (inVersion != MSG_VERSION_BASE && inVersion != MSG_VERSION_EXT) { + return; + } + version_ = inVersion; + } + + uint16_t GetMessageType() const + { + return messageType_; + } + + uint32_t GetMessageId() const + { + return messageId_; + } + + uint32_t GetSessionId() const + { + return sessionId_; + } + + uint32_t GetSequenceId() const + { + return sequenceId_; + } + + uint32_t GetErrorNo() const + { + return errorNo_; + } + + std::string GetTarget() const + { + return target_; + } + + Priority GetPriority() const + { + return prio_; + } + + uint16_t GetVersion() const + { + return version_; + } + + bool IsFeedbackError() const + { + return (errorNo_ == E_FEEDBACK_UNKNOWN_MESSAGE || errorNo_ == E_FEEDBACK_COMMUNICATOR_NOT_FOUND); + } + +private: + // Field or content that will be serialized for bytes transfer + uint16_t version_ = MSG_VERSION_BASE; + uint16_t messageType_ = TYPE_INVALID; + uint32_t messageId_ = INVALID_MESSAGE_ID; + uint32_t sessionId_ = 0; // Distinguish different conversation + uint32_t sequenceId_ = 0; // Distinguish different message even in same session with same content in retry case + uint32_t errorNo_ = NO_ERROR; + ObjectHolder *holderPtr_ = nullptr; + + // Field carry supplemental info + std::string target_; + Priority prio_ = Priority::LOW; +}; +} // namespace DistributedDB + +#endif // MESSAGE_H diff --git a/mock/distributeddb/communicator/include/message_transform.h b/mock/distributeddb/communicator/include/message_transform.h new file mode 100644 index 00000000..59b45038 --- /dev/null +++ b/mock/distributeddb/communicator/include/message_transform.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MESSAGE_TRANSFORM_H +#define MESSAGE_TRANSFORM_H + +#include +#include +#include +#include "message.h" + +namespace DistributedDB { +using ComputeLengthFunc = std::function; +using SerializeFunc = std::function; +using DeserializeFunc = std::function; + +struct TransformFunc { + ComputeLengthFunc computeFunc; + SerializeFunc serializeFunc; + DeserializeFunc deserializeFunc; +}; + +class MessageTransform { +public: + // Must not be called in multi-thread + // Return E_ALREADY_REGISTER if msgId is already registered + // Return E_INVALID_ARGS if member of inFunc not all valid + // Calling ProtocolProto::RegTransformFunction + static int RegTransformFunction(uint32_t msgId, const TransformFunc &inFunc); +}; +} + +#endif // MESSAGE_TRANSFORM_H \ No newline at end of file diff --git a/mock/distributeddb/communicator/include/network_adapter.h b/mock/distributeddb/communicator/include/network_adapter.h new file mode 100644 index 00000000..580e48c0 --- /dev/null +++ b/mock/distributeddb/communicator/include/network_adapter.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 NETWORK_ADAPTER_H +#define NETWORK_ADAPTER_H + +#include +#include +#include +#include +#include +#include +#include "iadapter.h" +#include "iprocess_communicator.h" + +namespace DistributedDB { +class NetworkAdapter : public IAdapter { +public: + NetworkAdapter(); + explicit NetworkAdapter(const std::string &inProcessLabel); + NetworkAdapter(const std::string &inProcessLabel, const std::shared_ptr &inCommunicator); + + ~NetworkAdapter() override; + + int StartAdapter() override; + void StopAdapter() override; + + uint32_t GetMtuSize() override; + uint32_t GetMtuSize(const std::string &target) override; + + uint32_t GetTimeout() override; + uint32_t GetTimeout(const std::string &target) override; + int GetLocalIdentity(std::string &outTarget) override; + + int SendBytes(const std::string &dstTarget, const uint8_t *bytes, uint32_t length) override; + + int RegBytesReceiveCallback(const BytesReceiveCallback &onReceive, const Finalizer &inOper) override; + int RegTargetChangeCallback(const TargetChangeCallback &onChange, const Finalizer &inOper) override; + int RegSendableCallback(const SendableCallback &onSendable, const Finalizer &inOper) override; + + bool IsDeviceOnline(const std::string &device) override; + std::shared_ptr GetExtendHeaderHandle(const ExtendInfo ¶mInfo) override; + +private: + void OnDataReceiveHandler(const DeviceInfos &srcDevInfo, const uint8_t *data, uint32_t length); + void OnDeviceChangeHandler(const DeviceInfos &devInfo, bool isOnline); + + void SearchOnlineRemoteDeviceAtStartup(); + void CheckDeviceOnlineAfterReception(const DeviceInfos &devInfo); + void CheckDeviceOfflineAfterSendFail(const DeviceInfos &devInfo); + + std::string processLabel_; + std::shared_ptr processCommunicator_; + + // For protecting "LocalIdentity" and "MtuSize", these info only need to get from peripheral interface once + mutable std::mutex identityMutex_; + + std::string localIdentity_; + mutable std::mutex mtuSizeMutex_; + bool isMtuSizeValid_ = false; + uint32_t mtuSize_ = 0; + std::map devMapMtuSize_; + + mutable std::mutex onlineRemoteDevMutex_; + std::set onlineRemoteDev_; // Refer to devices that has peer process + + std::atomic pendingAsyncTaskCount_{0}; + mutable std::mutex asyncTaskDoneMutex_; + std::condition_variable asyncTaskDoneCv_; + + BytesReceiveCallback onReceiveHandle_; + TargetChangeCallback onChangeHandle_; + SendableCallback onSendableHandle_; + Finalizer onReceiveFinalizer_; + Finalizer onChangeFinalizer_; + Finalizer onSendableFinalizer_; + mutable std::mutex onReceiveMutex_; + mutable std::mutex onChangeMutex_; + mutable std::mutex onSendableMutex_; +}; +} // namespace DistributedDB + +#endif diff --git a/mock/distributeddb/communicator/include/object_holder.h b/mock/distributeddb/communicator/include/object_holder.h new file mode 100644 index 00000000..348eab4b --- /dev/null +++ b/mock/distributeddb/communicator/include/object_holder.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OBJECTHOLDER_H +#define OBJECTHOLDER_H + +namespace DistributedDB { +class ObjectHolder { +public: + virtual ~ObjectHolder() {}; +}; +} // namespace DistributedDB + +#endif // OBJECTHOLDER_H diff --git a/mock/distributeddb/communicator/include/object_holder_typed.h b/mock/distributeddb/communicator/include/object_holder_typed.h new file mode 100644 index 00000000..4156e3dc --- /dev/null +++ b/mock/distributeddb/communicator/include/object_holder_typed.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OBJECTHOLDERTYPED_H +#define OBJECTHOLDERTYPED_H + +#include "object_holder.h" + +namespace DistributedDB { +template +class ObjectHolderTyped : public ObjectHolder { +public: + // Accept a heap object + explicit ObjectHolderTyped(T *inObject) + { + objectPtr_ = inObject; + } + + ~ObjectHolderTyped() override + { + if (objectPtr_ != nullptr) { + delete objectPtr_; + objectPtr_ = nullptr; + } + } + + const T *GetObject() const + { + return objectPtr_; + } +private: + T *objectPtr_ = nullptr; +}; +} // namespace DistributedDB + +#endif // OBJECTHOLDERTYPED_H diff --git a/mock/distributeddb/communicator/include/parse_result.h b/mock/distributeddb/communicator/include/parse_result.h new file mode 100644 index 00000000..0e127882 --- /dev/null +++ b/mock/distributeddb/communicator/include/parse_result.h @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 PARSE_RESULT_H +#define PARSE_RESULT_H + +#include +#include +#include "communicator_type_define.h" + +namespace DistributedDB { +class ParseResult { +public: + void SetFrameId(uint32_t inFrameId) + { + frameId_ = inFrameId; + } + void SetSourceId(uint64_t inSourceId) + { + sourceId_ = inSourceId; + } + void SetPacketLen(uint32_t inPacketLen) + { + packetLen_ = inPacketLen; + } + void SetPaddingLen(uint32_t inPaddingLen) + { + paddingLen_ = inPaddingLen; + } + void SetFragmentFlag(bool inFlag) + { + isFragment_ = inFlag; + } + void SetFrameTypeInfo(FrameType inFrameType) + { + frameType_ = inFrameType; + } + void SetFrameLen(uint32_t inFrameLen) + { + frameLen_ = inFrameLen; + } + void SetFragCount(uint16_t inFragCount) + { + fragCount_ = inFragCount; + } + void SetFragNo(uint16_t inFragNo) + { + fragNo_ = inFragNo; + } + void SetPayloadLen(uint32_t inPayloadLen) + { + payloadLen_ = inPayloadLen; + } + void SetCommLabel(const LabelType &inCommLabel) + { + commLabel_ = inCommLabel; + } + void SetLabelExchangeDistinctValue(uint64_t inDistinctValue) + { + labelExchangeDistinctValue_ = inDistinctValue; + } + void SetLabelExchangeSequenceId(uint64_t inSequenceId) + { + labelExchangeSequenceId_ = inSequenceId; + } + void SetLatestCommLabels(const std::set &inLatestCommLabels) + { + latestCommLabels_ = inLatestCommLabels; + } + + uint32_t GetFrameId() const + { + return frameId_; + } + uint64_t GetSourceId() const + { + return sourceId_; + } + uint32_t GetPacketLen() const + { + return packetLen_; + } + uint32_t GetPaddingLen() const + { + return paddingLen_; + } + bool IsFragment() const + { + return isFragment_; + } + FrameType GetFrameTypeInfo() const + { + return frameType_; + } + uint32_t GetFrameLen() const + { + return frameLen_; + } + uint16_t GetFragCount() const + { + return fragCount_; + } + uint16_t GetFragNo() const + { + return fragNo_; + } + uint32_t GetPayloadLen() const + { + return payloadLen_; + } + LabelType GetCommLabel() const + { + return commLabel_; + } + uint64_t GetLabelExchangeDistinctValue() const + { + return labelExchangeDistinctValue_; + } + uint64_t GetLabelExchangeSequenceId() const + { + return labelExchangeSequenceId_; + } + const std::set& GetLatestCommLabels() const + { + return latestCommLabels_; + } + + void SetDbVersion(uint16_t dbVersion) + { + dbVersion_ = dbVersion; + } + + uint16_t GetDbVersion() const + { + return dbVersion_; + } +private: + // For CommPhyHeader + uint32_t frameId_ = 0; + uint64_t sourceId_ = 0; + uint32_t packetLen_ = 0; + uint8_t paddingLen_ = 0; + bool isFragment_ = false; + FrameType frameType_ = FrameType::INVALID_MAX_FRAME_TYPE; + + // For CommPhyOptHeader + uint32_t frameLen_ = 0; + uint16_t fragCount_ = 0; + uint16_t fragNo_ = 0; + + // For Application Layer Frame + uint32_t payloadLen_ = 0; + LabelType commLabel_; + + // For Communication Layer Frame + uint64_t labelExchangeDistinctValue_ = 0; // For Both LabelExchange And LabelExchangeAck Frame + uint64_t labelExchangeSequenceId_ = 0; // For Both LabelExchange And LabelExchangeAck Frame + std::set latestCommLabels_; // For Only LabelExchange Frame + uint16_t dbVersion_ = 0; +}; +} + +#endif // PARSE_RESULT_H diff --git a/mock/distributeddb/communicator/include/send_task_scheduler.h b/mock/distributeddb/communicator/include/send_task_scheduler.h new file mode 100644 index 00000000..8c1e3e12 --- /dev/null +++ b/mock/distributeddb/communicator/include/send_task_scheduler.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SEND_TASK_SCHEDULER_H +#define SEND_TASK_SCHEDULER_H + +#include +#include +#include +#include +#include +#include +#include "macro_utils.h" +#include "communicator_type_define.h" + +namespace DistributedDB { +enum class TargetPolicy { + NO_DELAY = 0, + DELAY = 1, +}; + +class SerialBuffer; // Forward Declaration + +struct SendTask { + SerialBuffer *buffer; + std::string dstTarget; + OnSendEnd onEnd; +}; + +struct SendTaskInfo { + bool delayFlag; + Priority taskPrio; +}; + +using TaskListByTarget = std::map>; + +class SendTaskScheduler { +public: + SendTaskScheduler() = default; // Default constructor must be explicitly provided due to DISABLE_COPY_ASSIGN_MOVE + ~SendTaskScheduler(); + + DISABLE_COPY_ASSIGN_MOVE(SendTaskScheduler); + + void Initialize(); + + // This method for consumer + void Finalize(); + + // This method for producer, support multiple thread + int AddSendTaskIntoSchedule(const SendTask &inTask, Priority inPrio); + + // This method for consumer, not recommend for multiple thread + int ScheduleOutSendTask(SendTask &outTask); + int ScheduleOutSendTask(SendTask &outTask, SendTaskInfo &outTaskInfo); + + // This method for consumer, call ScheduleOutSendTask at least one time before each calling this + int FinalizeLastScheduleTask(); + + // These two mothods influence the task that will be schedule out next time + int DelayTaskByTarget(const std::string &inTarget); + int NoDelayTaskByTarget(const std::string &inTarget); + + uint32_t GetTotalTaskCount() const; + uint32_t GetNoDelayTaskCount() const; + +private: + int ScheduleDelayTask(SendTask &outTask, SendTaskInfo &outTaskInfo); + int ScheduleNoDelayTask(SendTask &outTask, SendTaskInfo &outTaskInfo); + + mutable std::mutex overallMutex_; + uint32_t curTotalSizeByByte_ = 0; + uint32_t curTotalSizeByTask_ = 0; + uint32_t delayTaskCount_ = 0; + + std::vector priorityOrder_; + std::map extraCapacityInByteByPrio_; + std::map policyMap_; + + std::map taskCountByPrio_; + std::map taskDelayCountByPrio_; + std::map> taskOrderByPrio_; + std::map taskGroupByPrio_; + + bool scheduledFlag_ = false; + std::string lastScheduleTarget_; + Priority lastSchedulePriority_ = Priority::LOW; +}; +} + +#endif \ No newline at end of file diff --git a/mock/distributeddb/communicator/src/combine_status.cpp b/mock/distributeddb/communicator/src/combine_status.cpp new file mode 100644 index 00000000..b3ecf81e --- /dev/null +++ b/mock/distributeddb/communicator/src/combine_status.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "combine_status.h" + +namespace DistributedDB { +void CombineStatus::UpdateProgressId(uint64_t inProgressId) +{ + progressId_ = inProgressId; + hasProgressFlag_ = true; +} + +uint64_t CombineStatus::GetProgressId() const +{ + return progressId_; +} + +bool CombineStatus::CheckProgress() +{ + bool preFlag = hasProgressFlag_; + hasProgressFlag_ = false; + return preFlag; +} + +void CombineStatus::SetFragmentLen(uint32_t inFragLen) +{ + fragmentLen_ = inFragLen; +} + +void CombineStatus::SetLastFragmentLen(uint32_t inLastFragLen) +{ + lastFragmentLen_ = inLastFragLen; +} + +uint32_t CombineStatus::GetThisFragmentLength(uint16_t inFragNo) const +{ + // It had already been checked outside that inFragNo smaller than fragmentCount_ + return ((inFragNo != fragmentCount_ - 1) ? fragmentLen_ : lastFragmentLen_); // subtract by 1 for index +} + +uint32_t CombineStatus::GetThisFragmentOffset(uint16_t inFragNo) const +{ + // It had already been checked outside that inFragNo smaller than fragmentCount_ + return fragmentLen_ * inFragNo; // It can be guaranteed no overflow will happen by multiply +} + +void CombineStatus::SetFragmentCount(uint16_t inFragCount) +{ + fragmentCount_ = inFragCount; +} + +bool CombineStatus::IsFragNoAlreadyExist(uint16_t inFragNo) const +{ + return (combinedFragmentNo_.count(inFragNo) != 0) ? true : false; +} + +void CombineStatus::CheckInFragmentNo(uint16_t inFragNo) +{ + if (inFragNo >= fragmentCount_) { + return; + } + combinedFragmentNo_.insert(inFragNo); +} + +bool CombineStatus::IsCombineDone() const +{ + return (combinedFragmentNo_.size() >= fragmentCount_); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/communicator/src/communicator.cpp b/mock/distributeddb/communicator/src/communicator.cpp new file mode 100644 index 00000000..e5c97f41 --- /dev/null +++ b/mock/distributeddb/communicator/src/communicator.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "communicator.h" +#include "db_common.h" +#include "log_print.h" +#include "protocol_proto.h" + +namespace DistributedDB { +Communicator::Communicator(CommunicatorAggregator *inCommAggregator, const LabelType &inLabel) + : commAggrHandle_(inCommAggregator), commLabel_(inLabel) +{ + RefObject::IncObjRef(commAggrHandle_); // Rely on CommunicatorAggregator, hold its reference. +} + +Communicator:: ~Communicator() +{ + RefObject::DecObjRef(commAggrHandle_); // Communicator no longer hold the reference of CommunicatorAggregator. + onMessageHandle_ = nullptr; + onConnectHandle_ = nullptr; + onSendableHandle_ = nullptr; + commAggrHandle_ = nullptr; +} + +int Communicator::RegOnMessageCallback(const OnMessageCallback &onMessage, const Finalizer &inOper) +{ + std::lock_guard messageHandleLockGuard(messageHandleMutex_); + return RegCallBack(onMessage, onMessageHandle_, inOper, onMessageFinalizer_); +} + +int Communicator::RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) +{ + std::lock_guard connectHandleLockGuard(connectHandleMutex_); + int errCode = RegCallBack(onConnect, onConnectHandle_, inOper, onConnectFinalizer_); + if (onConnect && errCode == E_OK) { + // Register action and success + for (auto &entry : onlineTargets_) { + LOGI("[Comm][RegConnect] Label=%.6s, online target=%s{private}.", VEC_TO_STR(commLabel_), entry.c_str()); + onConnectHandle_(entry, true); + } + } + return errCode; +} + +int Communicator::RegOnSendableCallback(const std::function &onSendable, const Finalizer &inOper) +{ + std::lock_guard sendableHandleLockGuard(sendableHandleMutex_); + return RegCallBack(onSendable, onSendableHandle_, inOper, onSendableFinalizer_); +} + +void Communicator::Activate() +{ + commAggrHandle_->ActivateCommunicator(commLabel_); +} + +uint32_t Communicator::GetCommunicatorMtuSize() const +{ + return commAggrHandle_->GetCommunicatorAggregatorMtuSize(); +} + +uint32_t Communicator::GetCommunicatorMtuSize(const std::string &target) const +{ + return commAggrHandle_->GetCommunicatorAggregatorMtuSize(target); +} + +int Communicator::GetLocalIdentity(std::string &outTarget) const +{ + return commAggrHandle_->GetLocalIdentity(outTarget); +} + +uint32_t Communicator::GetTimeout() const +{ + return commAggrHandle_->GetCommunicatorAggregatorTimeout(); +} + +uint32_t Communicator::GetTimeout(const std::string &target) const +{ + return commAggrHandle_->GetCommunicatorAggregatorTimeout(target); +} + +bool Communicator::IsDeviceOnline(const std::string &device) const +{ + return commAggrHandle_->IsDeviceOnline(device); +} + +int Communicator::SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config) +{ + return SendMessage(dstTarget, inMsg, config, nullptr); +} + +int Communicator::SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config, + const OnSendEnd &onEnd) +{ + if (dstTarget.empty() || inMsg == nullptr) { + return -E_INVALID_ARGS; + } + std::shared_ptr extendHandle = nullptr; + if (config.isNeedExtendHead) { + extendHandle = commAggrHandle_->GetExtendHeaderHandle(config.paramInfo); + if (extendHandle == nullptr) { + LOGE("[Comm][Send] get extendHandle failed"); + return -E_FEEDBACK_COMMUNICATOR_NOT_FOUND; + } + } + int error = E_OK; + // if error is not E_OK , null pointer will be returned + SerialBuffer *buffer = ProtocolProto::ToSerialBuffer(inMsg, error, extendHandle, false); + extendHandle = nullptr; + if (error != E_OK) { + LOGE("[Comm][Send] Serial fail, label=%s, error=%d.", VEC_TO_STR(commLabel_), error); + return error; + } + int errCode = ProtocolProto::SetDivergeHeader(buffer, commLabel_); + if (errCode != E_OK) { + LOGE("[Comm][Send] Set header fail, label=%s, errCode=%d.", VEC_TO_STR(commLabel_), errCode); + delete buffer; + buffer = nullptr; + return errCode; + } + + TaskConfig taskConfig {config.nonBlock, config.timeout, inMsg->GetPriority()}; + errCode = commAggrHandle_->CreateSendTask(dstTarget, buffer, FrameType::APPLICATION_MESSAGE, taskConfig, onEnd); + if (errCode == E_OK) { + // if ok, free inMsg, otherwise the caller should take over inMsg + delete inMsg; + inMsg = nullptr; + } else { + // if send fails, free buffer, otherwise buffer should be taked over by comminucator aggregator + delete buffer; + buffer = nullptr; + } + return errCode; +} + +void Communicator::OnBufferReceive(const std::string &srcTarget, const SerialBuffer *inBuf) +{ + std::lock_guard messageHandleLockGuard(messageHandleMutex_); + if (srcTarget.size() != 0 && inBuf != nullptr && onMessageHandle_) { + int error = E_OK; + // if error is not E_OK, null pointer will be returned + Message *message = ProtocolProto::ToMessage(inBuf, error); + delete inBuf; + inBuf = nullptr; + // message is not nullptr if error is E_OK or error is E_NOT_REGISTER. + // for the former case the message will be handled and release by sync module. + // for the latter case the message is released in TriggerUnknownMessageFeedback. + if (error != E_OK) { + LOGE("[Comm][Receive] ToMessage fail, label=%s, error=%d.", VEC_TO_STR(commLabel_), error); + if (error == -E_VERSION_NOT_SUPPORT) { + TriggerVersionNegotiation(srcTarget); + } else if (error == -E_NOT_REGISTER) { + TriggerUnknownMessageFeedback(srcTarget, message); + } + return; + } + LOGI("[Comm][Receive] label=%s, srcTarget=%s{private}.", VEC_TO_STR(commLabel_), srcTarget.c_str()); + onMessageHandle_(srcTarget, message); + } else { + LOGE("[Comm][Receive] label=%s, src.size=%zu or buf or handle invalid.", VEC_TO_STR(commLabel_), + srcTarget.size()); + if (inBuf != nullptr) { + delete inBuf; + inBuf = nullptr; + } + } +} + +void Communicator::OnConnectChange(const std::string &target, bool isConnect) +{ + std::lock_guard connectHandleLockGuard(connectHandleMutex_); + if (target.size() == 0) { + LOGE("[Comm][Connect] Target size zero, label=%s.", VEC_TO_STR(commLabel_)); + return; + } + if (isConnect) { + onlineTargets_.insert(target); + } else { + onlineTargets_.erase(target); + } + LOGI("[Comm][Connect] Label=%s, target=%s{private}, Online=%d", VEC_TO_STR(commLabel_), target.c_str(), isConnect); + if (onConnectHandle_) { + onConnectHandle_(target, isConnect); + } else { + LOGI("[Comm][Connect] Handle invalid currently."); + } +} + +void Communicator::OnSendAvailable() +{ + std::lock_guard sendableHandleLockGuard(sendableHandleMutex_); + if (onSendableHandle_) { + onSendableHandle_(); + } +} + +LabelType Communicator::GetCommunicatorLabel() const +{ + return commLabel_; +} + +int Communicator::GetRemoteCommunicatorVersion(const std::string &target, uint16_t &outVersion) const +{ + return commAggrHandle_->GetRemoteCommunicatorVersion(target, outVersion); +} + +void Communicator::TriggerVersionNegotiation(const std::string &dstTarget) +{ + LOGI("[Comm][TrigVer] Do version negotiate with target=%s{private}.", dstTarget.c_str()); + int errCode = E_OK; + SerialBuffer *buffer = ProtocolProto::BuildEmptyFrameForVersionNegotiate(errCode); + if (errCode != E_OK) { + LOGE("[Comm][TrigVer] Build empty frame fail, errCode=%d", errCode); + return; + } + + TaskConfig config{true, 0, Priority::HIGH}; + errCode = commAggrHandle_->CreateSendTask(dstTarget, buffer, FrameType::EMPTY, config); + if (errCode != E_OK) { + LOGE("[Comm][TrigVer] Send empty frame fail, errCode=%d", errCode); + // if send fails, free buffer, otherwise buffer will be taked over by comminucator aggregator + delete buffer; + buffer = nullptr; + } +} + +void Communicator::TriggerUnknownMessageFeedback(const std::string &dstTarget, Message* &oriMsg) +{ + if (oriMsg == nullptr || oriMsg->GetMessageType() != TYPE_REQUEST) { + LOGI("[Comm][TrigFeedback] Do nothing for unknown message with type not request."); + // Do not have to do feedback if the message is not a request type message + delete oriMsg; + oriMsg = nullptr; + return; + } + + LOGI("[Comm][TrigFeedback] Do unknown message feedback with target=%s{private}.", dstTarget.c_str()); + oriMsg->SetMessageType(TYPE_RESPONSE); + oriMsg->SetErrorNo(E_FEEDBACK_UNKNOWN_MESSAGE); + + int errCode = E_OK; + SerialBuffer *buffer = ProtocolProto::BuildFeedbackMessageFrame(oriMsg, commLabel_, errCode); + delete oriMsg; + oriMsg = nullptr; + if (errCode != E_OK) { + LOGE("[Comm][TrigFeedback] Build unknown message feedback frame fail, errCode=%d", errCode); + return; + } + + TaskConfig config{true, 0, Priority::HIGH}; + errCode = commAggrHandle_->CreateSendTask(dstTarget, buffer, FrameType::APPLICATION_MESSAGE, config); + if (errCode != E_OK) { + LOGE("[Comm][TrigFeedback] Send unknown message feedback frame fail, errCode=%d", errCode); + // if send fails, free buffer, otherwise buffer will be taked over by comminucator aggregator + delete buffer; + buffer = nullptr; + } +} + +DEFINE_OBJECT_TAG_FACILITIES(Communicator) +} // namespace DistributedDB diff --git a/mock/distributeddb/communicator/src/communicator.h b/mock/distributeddb/communicator/src/communicator.h new file mode 100644 index 00000000..c1f2ce39 --- /dev/null +++ b/mock/distributeddb/communicator/src/communicator.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 COMMUNICATOR_H +#define COMMUNICATOR_H + +#include +#include +#include +#include +#include +#include +#include +#include "serial_buffer.h" +#include "icommunicator.h" +#include "communicator_aggregator.h" + +namespace DistributedDB { +class Communicator : public ICommunicator { +public: + Communicator(CommunicatorAggregator *inCommAggregator, const LabelType &inLabel); + ~Communicator() override; + + DISABLE_COPY_ASSIGN_MOVE(Communicator); + + int RegOnMessageCallback(const OnMessageCallback &onMessage, const Finalizer &inOper) override; + int RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) override; + int RegOnSendableCallback(const std::function &onSendable, const Finalizer &inOper) override; + + void Activate() override; + + uint32_t GetCommunicatorMtuSize() const override; + uint32_t GetCommunicatorMtuSize(const std::string &target) const override; + + uint32_t GetTimeout() const override; + uint32_t GetTimeout(const std::string &target) const override; + bool IsDeviceOnline(const std::string &device) const override; + int GetLocalIdentity(std::string &outTarget) const override; + // Get the protocol version of remote target. Return -E_NOT_FOUND if no record. + int GetRemoteCommunicatorVersion(const std::string &target, uint16_t &outVersion) const override; + + int SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config) override; + int SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config, + const OnSendEnd &onEnd) override; + + // Call by CommunicatorAggregator directly + void OnBufferReceive(const std::string &srcTarget, const SerialBuffer *inBuf); + + // Call by CommunicatorAggregator directly + void OnConnectChange(const std::string &target, bool isConnect); + + // Call by CommunicatorAggregator directly + void OnSendAvailable(); + + // Call by CommunicatorAggregator directly + LabelType GetCommunicatorLabel() const; + +private: + void TriggerVersionNegotiation(const std::string &dstTarget); + void TriggerUnknownMessageFeedback(const std::string &dstTarget, Message* &oriMsg); + + DECLARE_OBJECT_TAG(Communicator); + + CommunicatorAggregator *commAggrHandle_ = nullptr; + LabelType commLabel_; + + std::set onlineTargets_; // Actually protected by connectHandleMutex_ + + OnMessageCallback onMessageHandle_; + OnConnectCallback onConnectHandle_; + std::function onSendableHandle_; + Finalizer onMessageFinalizer_; + Finalizer onConnectFinalizer_; + Finalizer onSendableFinalizer_; + std::mutex messageHandleMutex_; + std::mutex connectHandleMutex_; + std::mutex sendableHandleMutex_; +}; +} // namespace DistributedDB + +#endif // COMMUNICATOR_H diff --git a/mock/distributeddb/communicator/src/communicator_aggregator.cpp b/mock/distributeddb/communicator/src/communicator_aggregator.cpp new file mode 100644 index 00000000..1ec0fa24 --- /dev/null +++ b/mock/distributeddb/communicator/src/communicator_aggregator.cpp @@ -0,0 +1,888 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "communicator_aggregator.h" +#include +#include +#include +#include +#include "hash.h" +#include "log_print.h" +#include "db_common.h" +#include "communicator.h" +#include "endian_convert.h" +#include "protocol_proto.h" +#include "communicator_linker.h" + +namespace DistributedDB { +namespace { +inline std::string GetThreadId() +{ + std::stringstream stream; + stream << std::this_thread::get_id(); + return stream.str(); +} +} + +std::atomic CommunicatorAggregator::isCommunicatorNotFoundFeedbackEnable_{true}; + +CommunicatorAggregator::CommunicatorAggregator() + : shutdown_(false), + incFrameId_(0), + localSourceId_(0) +{ +} + +CommunicatorAggregator::~CommunicatorAggregator() +{ + scheduler_.Finalize(); // Clear residual frame dumped by linker after CommunicatorAggregator finalize + adapterHandle_ = nullptr; + commLinker_ = nullptr; +} + +int CommunicatorAggregator::Initialize(IAdapter *inAdapter) +{ + if (inAdapter == nullptr) { + return -E_INVALID_ARGS; + } + adapterHandle_ = inAdapter; + + combiner_.Initialize(); + retainer_.Initialize(); + scheduler_.Initialize(); + + int errCode; + commLinker_ = new (std::nothrow) CommunicatorLinker(this); + if (commLinker_ == nullptr) { + errCode = -E_OUT_OF_MEMORY; + goto ROLL_BACK; + } + commLinker_->Initialize(); + + errCode = RegCallbackToAdapter(); + if (errCode != E_OK) { + goto ROLL_BACK; + } + + errCode = adapterHandle_->StartAdapter(); + if (errCode != E_OK) { + LOGE("[CommAggr][Init] Start Adapter Fail, errCode=%d.", errCode); + goto ROLL_BACK; + } + GenerateLocalSourceId(); + + shutdown_ = false; + exclusiveThread_ = std::thread(&CommunicatorAggregator::SendDataRoutine, this); + return E_OK; +ROLL_BACK: + UnRegCallbackFromAdapter(); + if (commLinker_ != nullptr) { + RefObject::DecObjRef(commLinker_); // Refcount of linker is 1 when created, here to unref linker + commLinker_ = nullptr; + } + // Scheduler do not need to do finalize in this roll_back + retainer_.Finalize(); + combiner_.Finalize(); + return errCode; +} + +void CommunicatorAggregator::Finalize() +{ + shutdown_ = true; + retryCv_.notify_all(); + { + std::lock_guard wakingLockGuard(wakingMutex_); + wakingSignal_ = true; + wakingCv_.notify_one(); + } + exclusiveThread_.join(); // Waiting thread to thoroughly quit + LOGI("[CommAggr][Final] Sub Thread Exit."); + scheduler_.Finalize(); // scheduler_ must finalize here to make space for linker to dump residual frame + + adapterHandle_->StopAdapter(); + UnRegCallbackFromAdapter(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait 100 ms to make sure all callback thread quit + + // No callback now and later, so combiner, retainer and linker can finalize or delete safely + RefObject::DecObjRef(commLinker_); // Refcount of linker is 1 when created, here to unref linker + commLinker_ = nullptr; + retainer_.Finalize(); + combiner_.Finalize(); +} + +ICommunicator *CommunicatorAggregator::AllocCommunicator(uint64_t commLabel, int &outErrorNo) +{ + uint64_t netOrderLabel = HostToNet(commLabel); + uint8_t *eachByte = reinterpret_cast(&netOrderLabel); + std::vector realLabel(COMM_LABEL_LENGTH, 0); + for (int i = 0; i < static_cast(sizeof(uint64_t)); i++) { + realLabel[i] = eachByte[i]; + } + return AllocCommunicator(realLabel, outErrorNo); +} + +ICommunicator *CommunicatorAggregator::AllocCommunicator(const std::vector &commLabel, int &outErrorNo) +{ + std::lock_guard commMapLockGuard(commMapMutex_); + LOGI("[CommAggr][Alloc] Label=%.6s.", VEC_TO_STR(commLabel)); + if (commLabel.size() != COMM_LABEL_LENGTH) { + outErrorNo = -E_INVALID_ARGS; + return nullptr; + } + + if (commMap_.count(commLabel) != 0) { + outErrorNo = -E_ALREADY_ALLOC; + return nullptr; + } + + Communicator *commPtr = new (std::nothrow) Communicator(this, commLabel); + if (commPtr == nullptr) { + outErrorNo = -E_OUT_OF_MEMORY; + return nullptr; + } + commMap_[commLabel] = {commPtr, false}; // Communicator is not activated when allocated + return commPtr; +} + +void CommunicatorAggregator::ReleaseCommunicator(ICommunicator *inCommunicator) +{ + if (inCommunicator == nullptr) { + return; + } + Communicator *commPtr = static_cast(inCommunicator); + LabelType commLabel = commPtr->GetCommunicatorLabel(); + LOGI("[CommAggr][Release] Label=%.6s.", VEC_TO_STR(commLabel)); + + std::lock_guard commMapLockGuard(commMapMutex_); + if (commMap_.count(commLabel) == 0) { + LOGE("[CommAggr][Release] Not Found."); + return; + } + commMap_.erase(commLabel); + RefObject::DecObjRef(commPtr); // Refcount of Communicator is 1 when created, here to unref Communicator + + int errCode = commLinker_->DecreaseLocalLabel(commLabel); + if (errCode != E_OK) { + LOGE("[CommAggr][Release] DecreaseLocalLabel Fail, Just Log, errCode=%d.", errCode); + } +} + +int CommunicatorAggregator::RegCommunicatorLackCallback(const CommunicatorLackCallback &onCommLack, + const Finalizer &inOper) +{ + std::lock_guard onCommLackLockGuard(onCommLackMutex_); + return RegCallBack(onCommLack, onCommLackHandle_, inOper, onCommLackFinalizer_); +} + +int CommunicatorAggregator::RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) +{ + std::lock_guard onConnectLockGuard(onConnectMutex_); + int errCode = RegCallBack(onConnect, onConnectHandle_, inOper, onConnectFinalizer_); + if (onConnect && errCode == E_OK) { + // Register action and success + std::set onlineTargets = commLinker_->GetOnlineRemoteTarget(); + for (auto &entry : onlineTargets) { + LOGI("[CommAggr][RegConnect] Online target=%s{private}.", entry.c_str()); + onConnectHandle_(entry, true); + } + } + return errCode; +} + +uint32_t CommunicatorAggregator::GetCommunicatorAggregatorMtuSize() const +{ + return adapterHandle_->GetMtuSize() - ProtocolProto::GetLengthBeforeSerializedData(); +} + +uint32_t CommunicatorAggregator::GetCommunicatorAggregatorMtuSize(const std::string &target) const +{ + return adapterHandle_->GetMtuSize(target) - ProtocolProto::GetLengthBeforeSerializedData(); +} + +uint32_t CommunicatorAggregator::GetCommunicatorAggregatorTimeout() const +{ + return adapterHandle_->GetTimeout(); +} + +uint32_t CommunicatorAggregator::GetCommunicatorAggregatorTimeout(const std::string &target) const +{ + return adapterHandle_->GetTimeout(target); +} + +bool CommunicatorAggregator::IsDeviceOnline(const std::string &device) const +{ + return adapterHandle_->IsDeviceOnline(device); +} + +int CommunicatorAggregator::GetLocalIdentity(std::string &outTarget) const +{ + return adapterHandle_->GetLocalIdentity(outTarget); +} + +void CommunicatorAggregator::ActivateCommunicator(const LabelType &commLabel) +{ + std::lock_guard commMapLockGuard(commMapMutex_); + LOGI("[CommAggr][Activate] Label=%.6s.", VEC_TO_STR(commLabel)); + if (commMap_.count(commLabel) == 0) { + LOGW("[CommAggr][Activate] Communicator of this label not allocated."); + return; + } + if (commMap_.at(commLabel).second) { + LOGW("[CommAggr][Activate] Communicator of this label had been activated."); + return; + } + commMap_.at(commLabel).second = true; // Mark this communicator as activated + + // IncreaseLocalLabel below and DecreaseLocalLabel in ReleaseCommunicator should all be protected by commMapMutex_ + // To avoid disordering probably caused by concurrent call to ActivateCommunicator and ReleaseCommunicator + std::set onlineTargets; + int errCode = commLinker_->IncreaseLocalLabel(commLabel, onlineTargets); + if (errCode != E_OK) { + LOGE("[CommAggr][Activate] IncreaseLocalLabel Fail, Just Log, errCode=%d.", errCode); + // Do not return here + } + for (auto &entry : onlineTargets) { + LOGI("[CommAggr][Activate] Already Online Target=%s{private}.", entry.c_str()); + commMap_.at(commLabel).first->OnConnectChange(entry, true); + } + // Do Redeliver, the communicator is responsible to deal with the frame + std::list framesToRedeliver = retainer_.FetchFramesForSpecificCommunicator(commLabel); + for (auto &entry : framesToRedeliver) { + commMap_.at(commLabel).first->OnBufferReceive(entry.srcTarget, entry.buffer); + } +} + +namespace { +void DoOnSendEndByTaskIfNeed(const OnSendEnd &onEnd, int result) +{ + if (onEnd) { + TaskAction onSendEndTask = [onEnd, result]() { + LOGD("[CommAggr][SendEndTask] Before On Send End."); + onEnd(result); + LOGD("[CommAggr][SendEndTask] After On Send End."); + }; + int errCode = RuntimeContext::GetInstance()->ScheduleTask(onSendEndTask); + if (errCode != E_OK) { + LOGE("[CommAggr][SendEndTask] ScheduleTask failed, errCode = %d.", errCode); + } + } +} +} + +int CommunicatorAggregator::CreateSendTask(const std::string &dstTarget, SerialBuffer *inBuff, + FrameType inType, const TaskConfig &inConfig, const OnSendEnd &onEnd) +{ + if (inBuff == nullptr) { + return -E_INVALID_ARGS; + } + LOGI("[CommAggr][Create] Enter, thread=%s, target=%s{private}, type=%d, nonBlock=%d, timeout=%u, prio=%d.", + GetThreadId().c_str(), dstTarget.c_str(), static_cast(inType), inConfig.nonBlock, inConfig.timeout, + static_cast(inConfig.prio)); + + if (!ReGenerateLocalSourceIdIfNeed()) { + delete inBuff; + inBuff = nullptr; + DoOnSendEndByTaskIfNeed(onEnd, -E_PERIPHERAL_INTERFACE_FAIL); + LOGE("[CommAggr][Create] Exit ok but discard since localSourceId zero, thread=%s.", GetThreadId().c_str()); + return E_OK; // Returns E_OK here to indicate this buffer was accepted though discard immediately + } + PhyHeaderInfo info{localSourceId_, incFrameId_.fetch_add(1, std::memory_order_seq_cst), inType}; + int errCode = ProtocolProto::SetPhyHeader(inBuff, info); + if (errCode != E_OK) { + LOGE("[CommAggr][Create] Set phyHeader fail, thread=%s, errCode=%d", GetThreadId().c_str(), errCode); + return errCode; + } + + SendTask task{inBuff, dstTarget, onEnd}; + if (inConfig.nonBlock) { + errCode = scheduler_.AddSendTaskIntoSchedule(task, inConfig.prio); + } else { + errCode = RetryUntilTimeout(task, inConfig.timeout, inConfig.prio); + } + if (errCode != E_OK) { + LOGW("[CommAggr][Create] Exit failed, thread=%s, errCode=%d", GetThreadId().c_str(), errCode); + return errCode; + } + + std::lock_guard wakingLockGuard(wakingMutex_); + wakingSignal_ = true; + wakingCv_.notify_one(); + LOGI("[CommAggr][Create] Exit ok, thread=%s, frameId=%u", GetThreadId().c_str(), info.frameId); // Delete In Future + return E_OK; +} + +void CommunicatorAggregator::EnableCommunicatorNotFoundFeedback(bool isEnable) +{ + isCommunicatorNotFoundFeedbackEnable_ = isEnable; +} + +int CommunicatorAggregator::GetRemoteCommunicatorVersion(const std::string &target, uint16_t &outVersion) const +{ + std::lock_guard versionMapLockGuard(versionMapMutex_); + auto pair = versionMap_.find(target); + if (pair == versionMap_.end()) { + return -E_NOT_FOUND; + } + outVersion = pair->second; + return E_OK; +} + +void CommunicatorAggregator::SendDataRoutine() +{ + while (!shutdown_) { + if (scheduler_.GetNoDelayTaskCount() == 0) { + std::unique_lock wakingUniqueLock(wakingMutex_); + LOGI("[CommAggr][Routine] Send done and sleep."); // Delete In Future + wakingCv_.wait(wakingUniqueLock, [this] { return this->wakingSignal_; }); + LOGI("[CommAggr][Routine] Send continue."); // Delete In Future + wakingSignal_ = false; + continue; + } + + SendTask taskToSend; + int errCode = scheduler_.ScheduleOutSendTask(taskToSend); + if (errCode != E_OK) { + continue; // Not possible to happen + } + // + std::vector, uint32_t>> piecePackets; + errCode = ProtocolProto::SplitFrameIntoPacketsIfNeed(taskToSend.buffer, + adapterHandle_->GetMtuSize(taskToSend.dstTarget), piecePackets); + if (errCode != E_OK) { + LOGE("[CommAggr][Routine] Split frame fail, errCode=%d.", errCode); + TaskFinalizer(taskToSend, errCode); + continue; + } + // > + std::vector>> eachPacket; + if (piecePackets.size() == 0) { + // Case that no need to split a frame, just use original buffer as a packet + std::pair tmpEntry = taskToSend.buffer->GetReadOnlyBytesForEntireBuffer(); + std::pair> entry; + entry.first = tmpEntry.first - taskToSend.buffer->GetExtendHeadLength(); + entry.second.first = taskToSend.buffer->GetExtendHeadLength(); + entry.second.second = tmpEntry.second + entry.second.first; + eachPacket.push_back(entry); + } else { + for (auto &entry : piecePackets) { + std::pair> tmpEntry = {&(entry.first[0]), + {entry.second, entry.first.size()}}; + eachPacket.push_back(tmpEntry); + } + } + + SendPacketsAndDisposeTask(taskToSend, eachPacket); + } +} + +void CommunicatorAggregator::SendPacketsAndDisposeTask(const SendTask &inTask, + const std::vector>> &eachPacket) +{ + bool taskNeedFinalize = true; + int errCode = E_OK; + for (auto &entry : eachPacket) { + LOGI("[CommAggr][SendPackets] DoSendBytes, dstTarget=%s{private}, extendHeadLength=%u, totalLength=%u.", + inTask.dstTarget.c_str(), entry.second.first, entry.second.second); + ProtocolProto::DisplayPacketInformation(entry.first + entry.second.first, entry.second.second); + errCode = adapterHandle_->SendBytes(inTask.dstTarget, entry.first, entry.second.second); + if (errCode == -E_WAIT_RETRY) { + LOGE("[CommAggr][SendPackets] SendBytes temporally fail."); + scheduler_.DelayTaskByTarget(inTask.dstTarget); + taskNeedFinalize = false; + break; + } else if (errCode != E_OK) { + LOGE("[CommAggr][SendPackets] SendBytes totally fail, errCode=%d.", errCode); + break; + } + } + if (taskNeedFinalize) { + TaskFinalizer(inTask, errCode); + } +} + +int CommunicatorAggregator::RetryUntilTimeout(SendTask &inTask, uint32_t timeout, Priority inPrio) +{ + int errCode = scheduler_.AddSendTaskIntoSchedule(inTask, inPrio); + if (errCode != E_OK) { + bool notTimeout = true; + auto retryFunc = [this, inPrio, &inTask]()->bool { + if (this->shutdown_) { + delete inTask.buffer; + inTask.buffer = nullptr; + return true; + } + int retCode = scheduler_.AddSendTaskIntoSchedule(inTask, inPrio); + if (retCode != E_OK) { + return false; + } + return true; + }; + + if (timeout == 0) { // Unlimited retry + std::unique_lock retryUniqueLock(retryMutex_); + retryCv_.wait(retryUniqueLock, retryFunc); + } else { + std::unique_lock retryUniqueLock(retryMutex_); + notTimeout = retryCv_.wait_for(retryUniqueLock, std::chrono::milliseconds(timeout), retryFunc); + } + + if (shutdown_) { + return E_OK; + } + if (!notTimeout) { + return -E_TIMEOUT; + } + } + return E_OK; +} + +void CommunicatorAggregator::TaskFinalizer(const SendTask &inTask, int result) +{ + // Call the OnSendEnd if need + if (inTask.onEnd) { + LOGD("[CommAggr][TaskFinal] On Send End."); + inTask.onEnd(result); + } + // Finalize the task that just scheduled + int errCode = scheduler_.FinalizeLastScheduleTask(); + // Notify Sendable To All Communicator If Need + if (errCode == -E_CONTAINER_FULL_TO_NOTFULL) { + retryCv_.notify_all(); + } + if (errCode == -E_CONTAINER_NOTEMPTY_TO_EMPTY) { + NotifySendableToAllCommunicator(); + } +} + +void CommunicatorAggregator::NotifySendableToAllCommunicator() +{ + std::lock_guard commMapLockGuard(commMapMutex_); + for (auto &entry : commMap_) { + // Ignore nonactivated communicator + if (entry.second.second) { + entry.second.first->OnSendAvailable(); + } + } +} + +void CommunicatorAggregator::OnBytesReceive(const std::string &srcTarget, const uint8_t *bytes, uint32_t length, + const std::string &userId) +{ + ProtocolProto::DisplayPacketInformation(bytes, length); // For debug, delete in the future + ParseResult packetResult; + int errCode = ProtocolProto::CheckAndParsePacket(srcTarget, bytes, length, packetResult); + if (errCode != E_OK) { + LOGE("[CommAggr][Receive] Parse packet fail, errCode=%d.", errCode); + if (errCode == -E_VERSION_NOT_SUPPORT) { + TriggerVersionNegotiation(srcTarget); + } + return; + } + + // Update version of remote target + SetRemoteCommunicatorVersion(srcTarget, packetResult.GetDbVersion()); + if (packetResult.GetFrameTypeInfo() == FrameType::EMPTY) { // Empty frame will never be fragmented + LOGI("[CommAggr][Receive] Empty frame, just ignore in this version of distributeddb."); + return; + } + + if (packetResult.IsFragment()) { + OnFragmentReceive(srcTarget, bytes, length, packetResult, userId); + } else if (packetResult.GetFrameTypeInfo() != FrameType::APPLICATION_MESSAGE) { + errCode = OnCommLayerFrameReceive(srcTarget, packetResult); + if (errCode != E_OK) { + LOGE("[CommAggr][Receive] CommLayer receive fail, errCode=%d.", errCode); + } + } else { + errCode = OnAppLayerFrameReceive(srcTarget, bytes, length, packetResult, userId); + if (errCode != E_OK) { + LOGE("[CommAggr][Receive] AppLayer receive fail, errCode=%d.", errCode); + } + } +} + +void CommunicatorAggregator::OnTargetChange(const std::string &target, bool isConnect) +{ + if (target.empty()) { + LOGE("[CommAggr][OnTarget] Target empty string."); + return; + } + // For process level target change + { + std::lock_guard onConnectLockGuard(onConnectMutex_); + if (onConnectHandle_) { + onConnectHandle_(target, isConnect); + LOGI("[CommAggr][OnTarget] On Connect End."); // Log in case callback block this thread + } else { + LOGI("[CommAggr][OnTarget] ConnectHandle invalid currently."); + } + } + std::set relatedLabels; + // For communicator level target change + if (isConnect) { + int errCode = commLinker_->TargetOnline(target, relatedLabels); + if (errCode != E_OK) { + LOGE("[CommAggr][OnTarget] TargetOnline fail, target=%s{private}, errCode=%d.", target.c_str(), errCode); + } + } else { + int errCode = commLinker_->TargetOffline(target, relatedLabels); + if (errCode != E_OK) { + LOGE("[CommAggr][OnTarget] TargetOffline fail, target=%s{private}, errCode=%d.", target.c_str(), errCode); + } + } + // All related communicator online or offline this target, no matter TargetOnline or TargetOffline fail or not + std::lock_guard commMapLockGuard(commMapMutex_); + for (auto &entry : commMap_) { + // Ignore nonactivated communicator + if (entry.second.second && (!isConnect || (relatedLabels.count(entry.first) != 0))) { + entry.second.first->OnConnectChange(target, isConnect); + } + } +} + +void CommunicatorAggregator::OnSendable(const std::string &target) +{ + int errCode = scheduler_.NoDelayTaskByTarget(target); + if (errCode != E_OK) { + LOGE("[CommAggr][Sendable] NoDelay target=%s{private} fail, errCode=%d.", target.c_str(), errCode); + return; + } + std::lock_guard wakingLockGuard(wakingMutex_); + wakingSignal_ = true; + wakingCv_.notify_one(); +} + +void CommunicatorAggregator::OnFragmentReceive(const std::string &srcTarget, const uint8_t *bytes, uint32_t length, + const ParseResult &inResult, const std::string &userId) +{ + int errorNo = E_OK; + ParseResult frameResult; + SerialBuffer *frameBuffer = combiner_.AssembleFrameFragment(bytes, length, inResult, frameResult, errorNo); + if (errorNo != E_OK) { + LOGE("[CommAggr][Receive] Combine fail, errCode=%d.", errorNo); + return; + } + if (frameBuffer == nullptr) { + LOGW("[CommAggr][Receive] Combine undone."); + return; + } + + int errCode = ProtocolProto::CheckAndParseFrame(frameBuffer, frameResult); + if (errCode != E_OK) { + LOGE("[CommAggr][Receive] Parse frame fail, errCode=%d.", errCode); + delete frameBuffer; + frameBuffer = nullptr; + if (errCode == -E_VERSION_NOT_SUPPORT) { + TriggerVersionNegotiation(srcTarget); + } + return; + } + + if (frameResult.GetFrameTypeInfo() != FrameType::APPLICATION_MESSAGE) { + errCode = OnCommLayerFrameReceive(srcTarget, frameResult); + if (errCode != E_OK) { + LOGE("[CommAggr][Receive] CommLayer receive fail after combination, errCode=%d.", errCode); + } + delete frameBuffer; + frameBuffer = nullptr; + } else { + errCode = OnAppLayerFrameReceive(srcTarget, frameBuffer, frameResult, userId); + if (errCode != E_OK) { + LOGE("[CommAggr][Receive] AppLayer receive fail after combination, errCode=%d.", errCode); + } + } +} + +int CommunicatorAggregator::OnCommLayerFrameReceive(const std::string &srcTarget, const ParseResult &inResult) +{ + if (inResult.GetFrameTypeInfo() == FrameType::COMMUNICATION_LABEL_EXCHANGE_ACK) { + int errCode = commLinker_->ReceiveLabelExchangeAck(srcTarget, inResult.GetLabelExchangeDistinctValue(), + inResult.GetLabelExchangeSequenceId()); + if (errCode != E_OK) { + LOGE("[CommAggr][CommReceive] Receive LabelExchangeAck Fail."); + return errCode; + } + } else { + std::map changedLabels; + int errCode = commLinker_->ReceiveLabelExchange(srcTarget, inResult.GetLatestCommLabels(), + inResult.GetLabelExchangeDistinctValue(), inResult.GetLabelExchangeSequenceId(), changedLabels); + if (errCode != E_OK) { + LOGE("[CommAggr][CommReceive] Receive LabelExchange Fail."); + return errCode; + } + if (!commLinker_->IsRemoteTargetOnline(srcTarget)) { + LOGW("[CommAggr][CommReceive] Receive LabelExchange from offline target=%s{private}.", srcTarget.c_str()); + for (const auto &entry : changedLabels) { + LOGW("[CommAggr][CommReceive] REMEMBER: label=%s, inOnline=%d.", VEC_TO_STR(entry.first), entry.second); + } + return E_OK; + } + // Do target change notify + std::lock_guard commMapLockGuard(commMapMutex_); + for (auto &entry : changedLabels) { + // Ignore nonactivated communicator + if (commMap_.count(entry.first) != 0 && commMap_.at(entry.first).second) { + LOGI("[CommAggr][CommReceive] label=%s, srcTarget=%s{private}, isOnline=%d.", + VEC_TO_STR(entry.first), srcTarget.c_str(), entry.second); + commMap_.at(entry.first).first->OnConnectChange(srcTarget, entry.second); + } + } + } + return E_OK; +} + +int CommunicatorAggregator::OnAppLayerFrameReceive(const std::string &srcTarget, const uint8_t *bytes, + uint32_t length, const ParseResult &inResult, const std::string &userId) +{ + SerialBuffer *buffer = new (std::nothrow) SerialBuffer(); + if (buffer == nullptr) { + LOGE("[CommAggr][AppReceive] New SerialBuffer fail."); + return -E_OUT_OF_MEMORY; + } + int errCode = buffer->SetExternalBuff(bytes, length - inResult.GetPaddingLen(), + ProtocolProto::GetAppLayerFrameHeaderLength()); + if (errCode != E_OK) { + LOGE("[CommAggr][AppReceive] SetExternalBuff fail, errCode=%d.", errCode); + delete buffer; + buffer = nullptr; + return -E_INTERNAL_ERROR; + } + return OnAppLayerFrameReceive(srcTarget, buffer, inResult, userId); +} + +// In early time, we cover "OnAppLayerFrameReceive" totally by commMapMutex_, then search communicator, if not found, +// we call onCommLackHandle_ if exist to ask whether to retain this frame or not, if the answer is yes we retain this +// frame, otherwise we discard this frame and send out CommunicatorNotFound feedback. +// We design so(especially cover this function totally by commMapMutex_) to avoid current situation described below +// 1:This func find that target communicator not allocated or activated, so decide to retain this frame. +// 2:Thread switch out, the target communicator is allocated and activated, previous retained frame is fetched out. +// 3:Thread switch back, this frame is then retained into the retainer, no chance to be fetched out. +// In conclusion: the decision to retain a frame and the action to retain a frame should not be separated. +// Otherwise, at the action time, the retain decision may be obsolete and wrong. +// #### BUT #### since onCommLackHandle_ callback is go beyond DistributedDB and there is the risk that the final upper +// user may do something such as GetKvStore(we can prevent them to so) which could result in calling AllocCommunicator +// in the same callback thread finally causing DeadLock on commMapMutex_. +// #### SO #### we have to make a change described below +// 1:Search communicator under commMapMutex_, if found then deliver frame to that communicator and end. +// 2:Call onCommLackHandle_ if exist to ask whether to retain this frame or not, without commMapMutex_. +// Note: during this period, commMap_ maybe changed, and communicator not found before may exist now. +// 3:Search communicator under commMapMutex_ again, if found then deliver frame to that communicator and end. +// 4:If still not found, retain this frame if need or otherwise send CommunicatorNotFound feedback. +int CommunicatorAggregator::OnAppLayerFrameReceive(const std::string &srcTarget, SerialBuffer *&inFrameBuffer, + const ParseResult &inResult, const std::string &userId) +{ + LabelType toLabel = inResult.GetCommLabel(); + { + std::lock_guard commMapLockGuard(commMapMutex_); + int errCode = TryDeliverAppLayerFrameToCommunicatorNoMutex(srcTarget, inFrameBuffer, toLabel); + if (errCode == E_OK) { // Attention: Here is equal to E_OK + return E_OK; + } + } + LOGI("[CommAggr][AppReceive] Communicator of %s not found or nonactivated.", VEC_TO_STR(toLabel)); + int errCode = -E_NOT_FOUND; + { + std::lock_guard onCommLackLockGuard(onCommLackMutex_); + if (onCommLackHandle_) { + errCode = onCommLackHandle_(toLabel, userId); + LOGI("[CommAggr][AppReceive] On CommLack End."); // Log in case callback block this thread + } else { + LOGI("[CommAggr][AppReceive] CommLackHandle invalid currently."); + } + } + // Here we have to lock commMapMutex_ and search communicator again. + std::lock_guard commMapLockGuard(commMapMutex_); + int errCodeAgain = TryDeliverAppLayerFrameToCommunicatorNoMutex(srcTarget, inFrameBuffer, toLabel); + if (errCodeAgain == E_OK) { // Attention: Here is equal to E_OK. + LOGI("[CommAggr][AppReceive] Communicator of %s found after try again(rare case).", VEC_TO_STR(toLabel)); + return E_OK; + } + // Here, communicator is still not found, retain or discard according to the result of onCommLackHandle_ + if (errCode != E_OK) { + TryToFeedbackWhenCommunicatorNotFound(srcTarget, toLabel, inFrameBuffer); + delete inFrameBuffer; + inFrameBuffer = nullptr; + return errCode; // The caller will display errCode in log + } + // Do Retention, the retainer is responsible to deal with the frame + retainer_.RetainFrame(FrameInfo{inFrameBuffer, srcTarget, toLabel, inResult.GetFrameId()}); + inFrameBuffer = nullptr; + return E_OK; +} + +int CommunicatorAggregator::TryDeliverAppLayerFrameToCommunicatorNoMutex(const std::string &srcTarget, + SerialBuffer *&inFrameBuffer, const LabelType &toLabel) +{ + // Ignore nonactivated communicator, which is regarded as inexistent + if (commMap_.count(toLabel) != 0 && commMap_.at(toLabel).second) { + commMap_.at(toLabel).first->OnBufferReceive(srcTarget, inFrameBuffer); + // Frame handed over to communicator who is responsible to delete it. The frame is deleted here after return. + inFrameBuffer = nullptr; + return E_OK; + } + return -E_NOT_FOUND; +} + +int CommunicatorAggregator::RegCallbackToAdapter() +{ + RefObject::IncObjRef(this); // Reference to be hold by adapter + int errCode = adapterHandle_->RegBytesReceiveCallback( + std::bind(&CommunicatorAggregator::OnBytesReceive, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4), + [this]() { RefObject::DecObjRef(this); }); + if (errCode != E_OK) { + RefObject::DecObjRef(this); // Rollback in case reg failed + return errCode; + } + + RefObject::IncObjRef(this); // Reference to be hold by adapter + errCode = adapterHandle_->RegTargetChangeCallback( + std::bind(&CommunicatorAggregator::OnTargetChange, this, std::placeholders::_1, std::placeholders::_2), + [this]() { RefObject::DecObjRef(this); }); + if (errCode != E_OK) { + RefObject::DecObjRef(this); // Rollback in case reg failed + return errCode; + } + + RefObject::IncObjRef(this); // Reference to be hold by adapter + errCode = adapterHandle_->RegSendableCallback( + std::bind(&CommunicatorAggregator::OnSendable, this, std::placeholders::_1), + [this]() { RefObject::DecObjRef(this); }); + if (errCode != E_OK) { + RefObject::DecObjRef(this); // Rollback in case reg failed + return errCode; + } + + return E_OK; +} + +void CommunicatorAggregator::UnRegCallbackFromAdapter() +{ + adapterHandle_->RegBytesReceiveCallback(nullptr, nullptr); + adapterHandle_->RegTargetChangeCallback(nullptr, nullptr); + adapterHandle_->RegSendableCallback(nullptr, nullptr); +} + +void CommunicatorAggregator::GenerateLocalSourceId() +{ + std::string identity; + adapterHandle_->GetLocalIdentity(identity); + // When GetLocalIdentity fail, the identity be an empty string, the localSourceId be zero, need regenerate + // The localSourceId is std::atomic, so there is no concurrency risk + uint64_t identityHash = Hash::HashFunc(identity); + if (identityHash != localSourceId_) { + LOGI("[CommAggr][GenSrcId] identity=%s{private}, localSourceId=%llu.", identity.c_str(), ULL(identityHash)); + } + localSourceId_ = identityHash; +} + +bool CommunicatorAggregator::ReGenerateLocalSourceIdIfNeed() +{ + // The deviceId will change when switch user from A to B + // We can't listen to the user change, because it's hard to ensure the timing is correct. + // So we regenerate to make sure the deviceId and localSourceId is correct when we create send task. + // The localSourceId is std::atomic, so there is no concurrency risk, no need lockguard here. + GenerateLocalSourceId(); + return (localSourceId_ != 0); +} + +void CommunicatorAggregator::TriggerVersionNegotiation(const std::string &dstTarget) +{ + LOGI("[CommAggr][TrigVer] Do version negotiate with target=%s{private}.", dstTarget.c_str()); + int errCode = E_OK; + SerialBuffer *buffer = ProtocolProto::BuildEmptyFrameForVersionNegotiate(errCode); + if (errCode != E_OK) { + LOGE("[CommAggr][TrigVer] Build empty frame fail, errCode=%d", errCode); + return; + } + + TaskConfig config{true, 0, Priority::HIGH}; + errCode = CreateSendTask(dstTarget, buffer, FrameType::EMPTY, config); + if (errCode != E_OK) { + LOGE("[CommAggr][TrigVer] Send empty frame fail, errCode=%d", errCode); + // if send fails, free buffer, otherwise buffer will be taked over by SendTaskScheduler + delete buffer; + buffer = nullptr; + } +} + +void CommunicatorAggregator::TryToFeedbackWhenCommunicatorNotFound(const std::string &dstTarget, + const LabelType &dstLabel, const SerialBuffer *inOriFrame) +{ + if (!isCommunicatorNotFoundFeedbackEnable_ || dstTarget.empty() || inOriFrame == nullptr) { + return; + } + int errCode = E_OK; + Message *message = ProtocolProto::ToMessage(inOriFrame, errCode, true); + if (message == nullptr) { + if (errCode == -E_VERSION_NOT_SUPPORT) { + TriggerVersionNegotiation(dstTarget); + } + return; + } + // Message is release in TriggerCommunicatorNotFoundFeedback + TriggerCommunicatorNotFoundFeedback(dstTarget, dstLabel, message); +} + +void CommunicatorAggregator::TriggerCommunicatorNotFoundFeedback(const std::string &dstTarget, + const LabelType &dstLabel, Message* &oriMsg) +{ + if (oriMsg == nullptr || oriMsg->GetMessageType() != TYPE_REQUEST) { + LOGI("[CommAggr][TrigNotFound] Do nothing for message with type not request."); + // Do not have to do feedback if the message is not a request type message + delete oriMsg; + oriMsg = nullptr; + return; + } + + LOGI("[CommAggr][TrigNotFound] Do communicator not found feedback with target=%s{private}.", dstTarget.c_str()); + oriMsg->SetMessageType(TYPE_RESPONSE); + oriMsg->SetErrorNo(E_FEEDBACK_COMMUNICATOR_NOT_FOUND); + + int errCode = E_OK; + SerialBuffer *buffer = ProtocolProto::BuildFeedbackMessageFrame(oriMsg, dstLabel, errCode); + delete oriMsg; + oriMsg = nullptr; + if (errCode != E_OK) { + LOGE("[CommAggr][TrigNotFound] Build communicator not found feedback frame fail, errCode=%d", errCode); + return; + } + + TaskConfig config{true, 0, Priority::HIGH}; + errCode = CreateSendTask(dstTarget, buffer, FrameType::APPLICATION_MESSAGE, config); + if (errCode != E_OK) { + LOGE("[CommAggr][TrigNotFound] Send communicator not found feedback frame fail, errCode=%d", errCode); + // if send fails, free buffer, otherwise buffer will be taked over by CreateSendTask + delete buffer; + buffer = nullptr; + } +} + +void CommunicatorAggregator::SetRemoteCommunicatorVersion(const std::string &target, uint16_t version) +{ + std::lock_guard versionMapLockGuard(versionMapMutex_); + versionMap_[target] = version; +} + +std::shared_ptr CommunicatorAggregator::GetExtendHeaderHandle(const ExtendInfo ¶mInfo) +{ + if (adapterHandle_ == nullptr) { + return nullptr; + } + return adapterHandle_->GetExtendHeaderHandle(paramInfo); +} + +DEFINE_OBJECT_TAG_FACILITIES(CommunicatorAggregator) +} // namespace DistributedDB diff --git a/mock/distributeddb/communicator/src/communicator_linker.cpp b/mock/distributeddb/communicator/src/communicator_linker.cpp new file mode 100644 index 00000000..bb9fde6f --- /dev/null +++ b/mock/distributeddb/communicator/src/communicator_linker.cpp @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "communicator_linker.h" +#include "hash.h" +#include "db_errno.h" +#include "log_print.h" +#include "protocol_proto.h" +#include "platform_specific.h" +#include "communicator_aggregator.h" + +namespace DistributedDB { +namespace { +constexpr uint32_t TIME_LAPSE_FOR_WAITING_ACK = 5000; // 5s +constexpr uint32_t TIME_LAPSE_FOR_RETRY_SEND = 1000; // 1s +constexpr uint32_t RETRANSMIT_LIMIT = 20; // Currently we do at most 20 retransmission if no ack received +constexpr uint32_t RETRANSMIT_LIMIT_EQUAL_INTERVAL = 5; // First 5 retransmission will be equal interval +} + +CommunicatorLinker::CommunicatorLinker(CommunicatorAggregator *inAggregator) + : incSequenceId_(0), incAckTriggerId_(0) +{ + aggregator_ = inAggregator; + RefObject::IncObjRef(aggregator_); // The linker rely on CommunicatorAggregator +} + +CommunicatorLinker::~CommunicatorLinker() +{ + RefObject::DecObjRef(aggregator_); // The linker no longer rely on CommunicatorAggregator + aggregator_ = nullptr; +} + +void CommunicatorLinker::Initialize() +{ + uint64_t curTime = 0; + int errCode = OS::GetCurrentSysTimeInMicrosecond(curTime); + if (errCode != E_OK) { + LOGW("[Linker][Init] Get systime fail, use default, errCode=%d.", errCode); + } + std::string curTimeStr = std::to_string(curTime); + localDistinctValue_ = Hash::HashFunc(curTimeStr); + LOGI("[Linker][Init] curTime=%llu, distinct=%llu.", ULL(curTime), ULL(localDistinctValue_)); +} + +// Create async task to send out label_exchange and waiting for label_exchange_ack. +// If waiting timeout, pass the send&wait task to overrall timing retry task. +int CommunicatorLinker::TargetOnline(const std::string &inTarget, std::set &outRelatedLabels) +{ + { + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + // if inTarget is offline before, use the remembered previous online labels to decide which communicator to be + // notified online. Such handling is in case for abnormal unilateral offline, which A and B is notified online + // mutually, then B is notified A offline and for a while B is notified A online again, but A feels no notify. + if (remoteOnlineTarget_.count(inTarget) == 0) { + outRelatedLabels = targetMapOnlineLabels_[inTarget]; + remoteOnlineTarget_.insert(inTarget); + } + } + return TriggerLabelExchangeEvent(inTarget); +} + +// Clear all labels related to this target. Let no longer waiting for ack of this target. +// The caller should notify all related communicator about this target offline. +int CommunicatorLinker::TargetOffline(const std::string &inTarget, std::set &outRelatedLabels) +{ + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + outRelatedLabels = targetMapOnlineLabels_[inTarget]; + // Do not erase the Labels of inTarget from targetMapOnlineLabels_, remember it for using when TargetOnline + remoteOnlineTarget_.erase(inTarget); + // Note: The process of remote target may quit, when remote target restart, + // the distinctValue of this remote target may be changed, and the sequenceId may start from zero + targetDistinctValue_.erase(inTarget); + topRecvLabelSeq_.erase(inTarget); + return E_OK; +} + +// Add local label. Create async task to send out label_exchange and waiting for label_exchange_ack. +// If waiting timeout, pass the send&wait task to overrall timing retry task. +// Find out targets for this label that is already online. +// The caller should notify communicator of this label about already online target. +int CommunicatorLinker::IncreaseLocalLabel(const LabelType &inLabel, std::set &outOnlineTarget) +{ + std::set totalOnlineTargets; + { + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + localOnlineLabels_.insert(inLabel); + totalOnlineTargets = remoteOnlineTarget_; + for (auto &entry : targetMapOnlineLabels_) { + if (remoteOnlineTarget_.count(entry.first) == 0) { // Ignore offline target + continue; + } + if (entry.second.count(inLabel) != 0) { // This online target had opened then same Label + outOnlineTarget.insert(entry.first); + } + } + } + bool everFail = false; + for (auto &entry : totalOnlineTargets) { + int errCode = TriggerLabelExchangeEvent(entry); + if (errCode != E_OK) { + everFail = true; + } + } + return everFail ? -E_INTERNAL_ERROR : E_OK; +} + +// Del local label. Create async task to send out label_exchange and waiting for label_exchange_ack. +// If waiting timeout, pass the send&wait task to overrall timing retry task. +int CommunicatorLinker::DecreaseLocalLabel(const LabelType &inLabel) +{ + std::set totalOnlineTargets; + { + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + localOnlineLabels_.erase(inLabel); + totalOnlineTargets = remoteOnlineTarget_; + } + bool everFail = false; + for (auto &entry : totalOnlineTargets) { + int errCode = TriggerLabelExchangeEvent(entry); + if (errCode != E_OK) { + everFail = true; + } + } + return everFail ? -E_INTERNAL_ERROR : E_OK; +} + +// Compare the latest labels with previous Label, find out label changes. +// The caller should notify the target changes according to label changes. +// Update the online labels of this target. Send out label_exchange_ack. +int CommunicatorLinker::ReceiveLabelExchange(const std::string &inTarget, const std::set &inLatestLabels, + uint64_t inDistinctValue, uint64_t inSequenceId, std::map &outChangeLabels) +{ + { + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + DetectDistinctValueChange(inTarget, inDistinctValue); + if (topRecvLabelSeq_.count(inTarget) == 0) { + // Firstly receive LabelExchange from this target + topRecvLabelSeq_[inTarget] = inSequenceId; + } else if (inSequenceId < topRecvLabelSeq_[inTarget]) { + // inSequenceId can be equal to topRecvLabelSeq, in this case, the ack of this sequence send to this target + // may be lost, this target resend LabelExchange, and we should resend ack to this target + LOGW("[Linker][RecvLabel] inSequenceId=%llu smaller than topRecvLabelSeq=%llu. Frame Ignored.", + ULL(inSequenceId), ULL(topRecvLabelSeq_[inTarget])); + return -E_OUT_OF_DATE; + } else { + // Update top sequenceId of received LabelExchange + topRecvLabelSeq_[inTarget] = inSequenceId; + } + // Find out online labels by check difference + for (auto &entry : inLatestLabels) { + if (targetMapOnlineLabels_[inTarget].count(entry) == 0) { + outChangeLabels[entry] = true; + } + } + // Find out offline labels by check difference + for (auto &entry : targetMapOnlineLabels_[inTarget]) { + if (inLatestLabels.count(entry) == 0) { + outChangeLabels[entry] = false; + } + } + // Update target online labels + targetMapOnlineLabels_[inTarget] = inLatestLabels; + } + // Trigger sending ack + int errCode = TriggerLabelExchangeAckEvent(inTarget, inSequenceId); + if (errCode != E_OK) { + LOGE("[Linker][RecvLabel] TriggerAckEvent Fail, Just Log, errCode=%d.", errCode); + // Do not return error here + } + return E_OK; +} + +// Waiting finish if the ack is what linker wait by check inSequenceId +// Similarly, stop the retry task of this Target. +int CommunicatorLinker::ReceiveLabelExchangeAck(const std::string &inTarget, uint64_t inDistinctValue, + uint64_t inSequenceId) +{ + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + DetectDistinctValueChange(inTarget, inDistinctValue); + // This two judge is for detecting case that local device process restart so incSequenceId_ restart from 0 + // The remote device may send an ack cause by previous process, which may destroy the functionality of this process + if (waitAckSeq_.count(inTarget) == 0) { + LOGW("[Linker][RecvAck] Not waiting any ack now, inSequenceId=%llu", ULL(inSequenceId)); + return -E_NOT_FOUND; + } + if (waitAckSeq_[inTarget] < inSequenceId) { + LOGW("[Linker][RecvAck] Not waiting this ack now, inSequenceId=%llu, waitAckSeq_=%llu", + ULL(inSequenceId), ULL(waitAckSeq_[inTarget])); + return -E_NOT_FOUND; + } + // An valid ack received + if (recvAckSeq_.count(inTarget) == 0) { + // Firstly receive LabelExchangeAck from this target + recvAckSeq_[inTarget] = inSequenceId; + } else if (inSequenceId <= recvAckSeq_[inTarget]) { + LOGW("[Linker][RecvAck] inSequenceId=%llu not greater than recvAckSeq_=%llu. Frame Ignored.", + ULL(inSequenceId), ULL(recvAckSeq_[inTarget])); + return -E_OUT_OF_DATE; + } else { + // Update top sequenceId of received LabelExchangeAck + recvAckSeq_[inTarget] = inSequenceId; + } + return E_OK; +} + +std::set CommunicatorLinker::GetOnlineRemoteTarget() const +{ + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + return remoteOnlineTarget_; +} + +bool CommunicatorLinker::IsRemoteTargetOnline(const std::string &inTarget) const +{ + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + if (remoteOnlineTarget_.count(inTarget) != 0) { + return true; + } + return false; +} + +// inCountDown is in millisecond +void CommunicatorLinker::SuspendByOnceTimer(const std::function &inAction, uint32_t inCountDown) +{ + TimerId thisTimerId = 0; + RuntimeContext *context = RuntimeContext::GetInstance(); + int errCode = context->SetTimer(static_cast(inCountDown), [inAction](TimerId inTimerId)->int{ + // Note: inAction should be captured by value (must not by reference) + LOGI("[Linker][Suspend] Timer Due : inTimerId=%llu.", ULL(inTimerId)); + inAction(); + return -E_END_TIMER; + }, nullptr, thisTimerId); + if (errCode == E_OK) { + LOGI("[Linker][Suspend] SetTimer Success : thisTimerId=%llu, wait=%u(ms).", ULL(thisTimerId), inCountDown); + } else { + LOGI("[Linker][Suspend] SetTimer Fail Raise Thread Instead : errCode=%d, wait=%u(ms).", errCode, inCountDown); + std::thread timerThread([inAction, inCountDown]() { + // Note: inAction and inCountDown should be captured by value (must not by reference) + std::this_thread::sleep_for(std::chrono::milliseconds(inCountDown)); + inAction(); + }); + timerThread.detach(); + } +} + +// This function should be called under protection of entireInfoMutex_ +void CommunicatorLinker::DetectDistinctValueChange(const std::string &inTarget, uint64_t inDistinctValue) +{ + // Firstly received distinctValue from this target ever or after offline + if (targetDistinctValue_.count(inTarget) == 0) { + targetDistinctValue_[inTarget] = inDistinctValue; + return; + } + + // DistinctValue is the same as before + if (targetDistinctValue_[inTarget] == inDistinctValue) { + return; + } + + // DistinctValue change detected !!! This must be caused by malfunctioning of underlayer communication component. + LOGE("[Linker][Detect] ######## DISTINCT VALUE CHANGE DETECTED : %llu VS %llu ########", + ULL(inDistinctValue), ULL(targetDistinctValue_[inTarget])); + targetDistinctValue_[inTarget] = inDistinctValue; + // The process of remote target must have undergone a quit and restart, the remote sequenceId will start from zero. + topRecvLabelSeq_.erase(inTarget); +} + +int CommunicatorLinker::TriggerLabelExchangeEvent(const std::string &toTarget) +{ + // Apply for a latest sequenceId + uint64_t sequenceId = incSequenceId_.fetch_add(1, std::memory_order_seq_cst); + // Get a snapshot of current online labels + std::set onlineLabels; + { + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + onlineLabels = localOnlineLabels_; + } + // Build LabelExchange Frame + int error = E_OK; + SerialBuffer *buffer = ProtocolProto::BuildLabelExchange(localDistinctValue_, sequenceId, onlineLabels, error); + if (error != E_OK) { + LOGE("[Linker][TriggerLabel] BuildLabel fail, error=%d", error); + return error; + } + // Update waitAckSeq, Check whether new event be triggered in other thread + { + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + if (waitAckSeq_.count(toTarget) == 0) { + // Firstly send LabelExchange to this target + waitAckSeq_[toTarget] = sequenceId; + } else if (waitAckSeq_[toTarget] > sequenceId) { + // New LabelExchangeEvent had been trigger for this target, so this event can be abort + LOGI("[Linker][TriggerLabel] Detect newSeqId=%llu than thisSeqId=%llu be triggered for target=%s{private}", + ULL(waitAckSeq_[toTarget]), ULL(sequenceId), toTarget.c_str()); + delete buffer; + buffer = nullptr; + return E_OK; + } else { + waitAckSeq_[toTarget] = sequenceId; + } + } + // Synchronously call SendLabelExchange and hand over buffer to it + RefObject::IncObjRef(this); // SendLabelExchange will only DecRef when total done if no need to send + SendLabelExchange(toTarget, buffer, sequenceId, 0); // Initially retransmitCount is 0 + return E_OK; +} + +int CommunicatorLinker::TriggerLabelExchangeAckEvent(const std::string &toTarget, uint64_t inSequenceId) +{ + // Build LabelExchangeAck Frame + int errCode = E_OK; + SerialBuffer *buffer = ProtocolProto::BuildLabelExchangeAck(localDistinctValue_, inSequenceId, errCode); + if (errCode != E_OK) { + LOGE("[Linker][TriggerAck] BuildAck fail, error=%d", errCode); + return errCode; + } + // Apply for a latest ackId and update ackTriggerId_ + uint64_t ackId; + { + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + ackId = incAckTriggerId_.fetch_add(1, std::memory_order_seq_cst); + ackTriggerId_[toTarget] = ackId; + } + // Synchronously call SendLabelExchangeAck and hand over buffer to it + RefObject::IncObjRef(this); // SendLabelExchangeAck will only DecRef when total done if no need to send + SendLabelExchangeAck(toTarget, buffer, inSequenceId, ackId); + return E_OK; +} + +namespace { +inline uint32_t GetDynamicTimeLapseForWaitingAck(uint32_t inRetransmitCount) +{ + if (inRetransmitCount <= RETRANSMIT_LIMIT_EQUAL_INTERVAL) { + return TIME_LAPSE_FOR_WAITING_ACK; + } + uint32_t subsequentRetransmit = inRetransmitCount - RETRANSMIT_LIMIT_EQUAL_INTERVAL; + return subsequentRetransmit * subsequentRetransmit * TIME_LAPSE_FOR_WAITING_ACK; +} +} + +void CommunicatorLinker::SendLabelExchange(const std::string &toTarget, SerialBuffer *inBuff, uint64_t inSequenceId, + uint32_t inRetransmitCount) +{ + // Check whether have the need to send + bool noNeedToSend = ((inRetransmitCount <= RETRANSMIT_LIMIT) ? false : true); + { + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + if (remoteOnlineTarget_.count(toTarget) == 0) { + // Target offline + noNeedToSend = true; + } + if (waitAckSeq_[toTarget] > inSequenceId) { + // New LabelExchangeEvent had been trigger for this target, so this event can be abort + noNeedToSend = true; + } + if (recvAckSeq_.count(toTarget) != 0 && recvAckSeq_[toTarget] >= inSequenceId) { + // Ack of this sequenceId had been received or even later ack had been received + noNeedToSend = true; + } + if (noNeedToSend) { // ATTENTION: This Log should be inside the protection of entireInfoLockGuard!!! + LOGI("[Linker][SendLabel] NoNeedSend:target=%s{private}, thisSeqId=%llu, waitAckSeq=%llu, recvAckSeq=%llu," + "retrans=%u.", toTarget.c_str(), ULL(inSequenceId), ULL(waitAckSeq_[toTarget]), + ULL((recvAckSeq_.count(toTarget) != 0) ? recvAckSeq_[toTarget] : ~ULL(0)), inRetransmitCount); + } // ~0 indicate no ack ever recv + } + if (noNeedToSend) { + delete inBuff; + inBuff = nullptr; + RefObject::DecObjRef(this); // ATTENTION: The DecObjRef should be outside entireInfoLockGuard!!! + return; + } + + int error = E_OK; + SerialBuffer *cloneBuffer = inBuff->Clone(error); + TaskConfig config{true, 0, Priority::HIGH}; + int errCode = aggregator_->CreateSendTask(toTarget, inBuff, FrameType::COMMUNICATION_LABEL_EXCHANGE, config); + if (errCode == E_OK) { + // Send ok, go on to wait ack, and maybe resend + if (error == E_OK) { + SuspendByOnceTimer([this, toTarget, cloneBuffer, inSequenceId, inRetransmitCount]() { + // Note: toTarget and cloneBuffer and inSequenceId should be captured by value (must not by reference) + SendLabelExchange(toTarget, cloneBuffer, inSequenceId, inRetransmitCount + 1); // Do retransmission + }, GetDynamicTimeLapseForWaitingAck(inRetransmitCount)); + } else { + LOGE("[Linker][SendLabel] CloneFail: target=%s{private}, SeqId=%llu.", toTarget.c_str(), ULL(inSequenceId)); + } + } else { + // Send fail, go on to retry send + SuspendByOnceTimer([this, toTarget, inBuff, inSequenceId, inRetransmitCount]() { + // Note: toTarget and inBuff and inSequenceId should be captured by value (must not by reference) + SendLabelExchange(toTarget, inBuff, inSequenceId, inRetransmitCount); // Just do retry send + }, TIME_LAPSE_FOR_RETRY_SEND); + if (error == E_OK) { + delete cloneBuffer; + cloneBuffer = nullptr; + } + } +} + +void CommunicatorLinker::SendLabelExchangeAck(const std::string &toTarget, SerialBuffer *inBuff, + uint64_t inSequenceId, uint64_t inAckTriggerId) +{ + // Check whether have the need to send + bool noNeedToSend = false; + { + std::lock_guard entireInfoLockGuard(entireInfoMutex_); + // Now that LabelExchange is received, LabelExchangeAck should be send no matter target online or not + if (topRecvLabelSeq_.count(toTarget) != 0 && topRecvLabelSeq_[toTarget] > inSequenceId) { + // topRecvLabelSeq for this target may have been erased, detect it for avoid creating an entry + // New LabelExchange had been received for this target, so this event can be abort + noNeedToSend = true; + } + if (ackTriggerId_[toTarget] > inAckTriggerId) { + // New LabelExchangeAck had been trigger for this target, so this event can be abort + noNeedToSend = true; + } + if (noNeedToSend) { // ATTENTION: This Log should be inside the protection of entireInfoLockGuard!!! + LOGI("[Linker][SendAck] NoNeedSend:target=%s{private}, thisSeqId=%llu, topRecLabelSeq=%llu, thisAckId=%llu," + "ackTriggerId=%llu.", toTarget.c_str(), ULL(inSequenceId), // ~0 indacate no label ever recv + ULL((topRecvLabelSeq_.count(toTarget) != 0) ? topRecvLabelSeq_[toTarget] : ~ULL(0)), + ULL(inAckTriggerId), ULL(ackTriggerId_[toTarget])); + } + } + if (noNeedToSend) { + delete inBuff; + inBuff = nullptr; + RefObject::DecObjRef(this); // ATTENTION: The DecObjRef should be outside entireInfoLockGuard!!! + return; + } + + TaskConfig config{true, 0, Priority::HIGH}; + int errCode = aggregator_->CreateSendTask(toTarget, inBuff, FrameType::COMMUNICATION_LABEL_EXCHANGE_ACK, config); + if (errCode == E_OK) { + // Send ok, finish event + RefObject::DecObjRef(this); // ATTENTION: The DecObjRef should be outside entireInfoLockGuard!!! + } else { + // Send fail, go on to retry send + SuspendByOnceTimer([this, toTarget, inBuff, inSequenceId, inAckTriggerId]() { + // Note: toTarget, inBuff, inSequenceId, inAckTriggerId should be captured by value (must not by reference) + SendLabelExchangeAck(toTarget, inBuff, inSequenceId, inAckTriggerId); + }, TIME_LAPSE_FOR_RETRY_SEND); + } +} + +DEFINE_OBJECT_TAG_FACILITIES(CommunicatorLinker) +} // namespace DistributedDB diff --git a/mock/distributeddb/communicator/src/communicator_linker.h b/mock/distributeddb/communicator/src/communicator_linker.h new file mode 100644 index 00000000..d8d0740f --- /dev/null +++ b/mock/distributeddb/communicator/src/communicator_linker.h @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 COMMUNICATOR_LINKER_H +#define COMMUNICATOR_LINKER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ref_object.h" +#include "serial_buffer.h" +#include "communicator_type_define.h" + +namespace DistributedDB { +class CommunicatorAggregator; // Forward Declaration + +class CommunicatorLinker : public virtual RefObject { +public: + explicit CommunicatorLinker(CommunicatorAggregator *inAggregator); + ~CommunicatorLinker(); + + DISABLE_COPY_ASSIGN_MOVE(CommunicatorLinker); + + void Initialize(); + + // Create async task to send out label_exchange and waiting for label_exchange_ack. + // If waiting timeout, pass the send&wait task to overrall timing retry task. + int TargetOnline(const std::string &inTarget, std::set &outRelatedLabels); + + // Clear all labels related to this target. Let no longer waiting for ack of this target. + // The caller should notify all related communicator about this target offline. + int TargetOffline(const std::string &inTarget, std::set &outRelatedLabels); + + // Add local label. Create async task to send out label_exchange and waiting for label_exchange_ack. + // If waiting timeout, pass the send&wait task to overrall timing retry task. + // Find out targets for this label that is already online. + // The caller should notify communicator of this label about already online target. + int IncreaseLocalLabel(const LabelType &inLabel, std::set &outOnlineTarget); + + // Del local label. Create async task to send out label_exchange and waiting for label_exchange_ack. + // If waiting timeout, pass the send&wait task to overrall timing retry task. + int DecreaseLocalLabel(const LabelType &inLabel); + + // Compare the latest labels with previous Label, find out label changes. + // The caller should notify the target changes according to label changes. + // Update the online labels of this target. Send out label_exchange_ack. + int ReceiveLabelExchange(const std::string &inTarget, const std::set &inLatestLabels, + uint64_t inDistinctValue, uint64_t inSequenceId, std::map &outChangeLabels); + + // Waiting finish if the ack is what linker wait by check inSequenceId + // Similarly, stop the retry task of this Target. + int ReceiveLabelExchangeAck(const std::string &inTarget, uint64_t inDistinctValue, uint64_t inSequenceId); + + std::set GetOnlineRemoteTarget() const; + + bool IsRemoteTargetOnline(const std::string &inTarget) const; +private: + DECLARE_OBJECT_TAG(CommunicatorLinker); + + // inCountDown is in millisecond + void SuspendByOnceTimer(const std::function &inAction, uint32_t inCountDown); + + // This function should be called under protection of entireInfoMutex_ + void DetectDistinctValueChange(const std::string &inTarget, uint64_t inDistinctValue); + + int TriggerLabelExchangeEvent(const std::string &toTarget); + int TriggerLabelExchangeAckEvent(const std::string &toTarget, uint64_t inSequenceId); + + void SendLabelExchange(const std::string &toTarget, SerialBuffer *inBuff, uint64_t inSequenceId, + uint32_t inRetransmitCount); + void SendLabelExchangeAck(const std::string &toTarget, SerialBuffer *inBuff, uint64_t inSequenceId, + uint64_t inAckTriggerId); + + uint64_t localDistinctValue_ = 0; + std::atomic incSequenceId_; + std::atomic incAckTriggerId_; + CommunicatorAggregator *aggregator_ = nullptr; + + mutable std::mutex entireInfoMutex_; + + // Point out the distinctValue for each target in order to detect malfunctioning "target offline" + std::map targetDistinctValue_; + + // Point out the largest sequenceId of LabelExchange that ever received for each target + std::map topRecvLabelSeq_; + + // Point out currently which sequenceId of ack is being waited for each target + std::map waitAckSeq_; + + // Point out the largest sequenceId of LabelExchangeAck that ever received for each target + std::map recvAckSeq_; + + // Point out the latest ackTriggerId for each target in order to abort outdated triggered event + std::map ackTriggerId_; + + // Core Info : Online Labels + std::set localOnlineLabels_; + std::set remoteOnlineTarget_; + + // remember the opened labels no matter target now online or offline + std::map> targetMapOnlineLabels_; +}; +} + +#endif \ No newline at end of file diff --git a/mock/distributeddb/communicator/src/frame_combiner.cpp b/mock/distributeddb/communicator/src/frame_combiner.cpp new file mode 100644 index 00000000..bbca2164 --- /dev/null +++ b/mock/distributeddb/communicator/src/frame_combiner.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "frame_combiner.h" +#include +#include "log_print.h" +#include "protocol_proto.h" + +namespace DistributedDB { +static const uint32_t MAX_WORK_PER_SRC_TARGET = 1; // Only allow 1 CombineWork for each target +static const int SURVAIL_PERIOD_IN_MILLISECOND = 10000; // Period is 10 s + +void FrameCombiner::Initialize() +{ + RuntimeContext *context = RuntimeContext::GetInstance(); + TimerAction action = [this](TimerId inTimerId)->int{ + PeriodicalSurveillance(); + return E_OK; + }; + TimerFinalizer finalizer = [this]() { + timerRemovedIndicator_.SendSemaphore(); + }; + int errCode = context->SetTimer(SURVAIL_PERIOD_IN_MILLISECOND, action, finalizer, timerId_); + if (errCode != E_OK) { + LOGE("[Combiner][Init] Set timer fail, errCode=%d.", errCode); + return; + } + isTimerWork_ = true; +} + +void FrameCombiner::Finalize() +{ + // First: Stop the timer + if (isTimerWork_) { + RuntimeContext *context = RuntimeContext::GetInstance(); + context->RemoveTimer(timerId_); + timerRemovedIndicator_.WaitSemaphore(); + } + + // Second: Clear the combineWorkPool_ + for (auto &eachSource : combineWorkPool_) { + for (auto &eachFrame : eachSource.second) { + delete eachFrame.second.buffer; + eachFrame.second.buffer = nullptr; + } + } +} + +SerialBuffer *FrameCombiner::AssembleFrameFragment(const uint8_t *bytes, uint32_t length, + const ParseResult &inPacketInfo, ParseResult &outFrameInfo, int &outErrorNo) +{ + uint64_t sourceId = inPacketInfo.GetSourceId(); + uint32_t frameId = inPacketInfo.GetFrameId(); + std::lock_guard overallLockGuard(overallMutex_); + if (combineWorkPool_[sourceId].count(frameId) != 0) { + // CombineWork already exist + int errCode = ContinueExistCombineWork(bytes, length, inPacketInfo); + if (errCode != E_OK) { + LOGE("[Combiner][Assemble] Continue work fail, errCode=%d.", errCode); + outErrorNo = errCode; + return nullptr; + } + + if (combineWorkPool_[sourceId][frameId].status.IsCombineDone()) { + // We can parse the combined frame here, or outside this class. + LOGI("[Combiner][Assemble] Combine done, sourceId=%llu, frameId=%u.", ULL(sourceId), frameId); + SerialBuffer *outFrame = combineWorkPool_[sourceId][frameId].buffer; + outFrameInfo = combineWorkPool_[sourceId][frameId].frameInfo; + outErrorNo = E_OK; + combineWorkPool_[sourceId].erase(frameId); + return outFrame; // The caller is responsible for release the outFrame + } + } else { + // CombineWork not exist and even existing work number reaches the limitation. Try create work first. + int errCode = CreateNewCombineWork(bytes, length, inPacketInfo); + if (errCode != E_OK) { + LOGE("[Combiner][Assemble] Create work fail, errCode=%d.", errCode); + outErrorNo = errCode; + return nullptr; + } + // After successfully create work, the existing work number may exceed the limitation + // If so, choose one from works of this target with lowest progressId and abort it + if (combineWorkPool_[sourceId].size() > MAX_WORK_PER_SRC_TARGET) { + AbortCombineWorkBySource(sourceId); + } + } + outErrorNo = E_OK; + return nullptr; +} + +void FrameCombiner::PeriodicalSurveillance() +{ + std::lock_guard overallLockGuard(overallMutex_); + for (auto &eachSource : combineWorkPool_) { + std::set frameToAbort; + for (auto &eachFrame : eachSource.second) { + if (!eachFrame.second.status.CheckProgress()) { + LOGW("[Combiner][Surveil] Source=%llu, frame=%u has no progress, this combine work will be aborted.", + ULL(eachSource.first), eachFrame.first); + // Free this combine work first + delete eachFrame.second.buffer; + eachFrame.second.buffer = nullptr; + // Record this frame in abort list + frameToAbort.insert(eachFrame.first); + } + } + // Remove the combine work from map + for (auto &entry : frameToAbort) { + eachSource.second.erase(entry); + } + } +} + +int FrameCombiner::ContinueExistCombineWork(const uint8_t *bytes, uint32_t length, const ParseResult &inPacketInfo) +{ + uint64_t sourceId = inPacketInfo.GetSourceId(); + uint32_t frameId = inPacketInfo.GetFrameId(); + CombineWork &oriWork = combineWorkPool_[sourceId][frameId]; // Be care here must be reference + if (!CheckPacketWithOriWork(inPacketInfo, oriWork)) { + LOGE("[Combiner][ContinueWork] Check packet fail, sourceId=%" PRIu64 ", frameId=%" PRIu32, sourceId, frameId); + return -E_COMBINE_FAIL; + } + + uint32_t fragOffset = oriWork.status.GetThisFragmentOffset(inPacketInfo.GetFragNo()); + uint32_t fragLength = oriWork.status.GetThisFragmentLength(inPacketInfo.GetFragNo()); + int errCode = ProtocolProto::CombinePacketIntoFrame(oriWork.buffer, bytes, length, fragOffset, fragLength); + if (errCode != E_OK) { + // We can consider abort this work, but here we choose not to affect it + LOGE("[Combiner][ContinueWork] Combine packet fail, sourceId=%" PRIu64 ", frameId=%" PRIu32, sourceId, frameId); + return -E_COMBINE_FAIL; + } + + oriWork.status.UpdateProgressId(incProgressId_++); + oriWork.status.CheckInFragmentNo(inPacketInfo.GetFragNo()); + return E_OK; +} + +int FrameCombiner::CreateNewCombineWork(const uint8_t *bytes, uint32_t length, const ParseResult &inPacketInfo) +{ + uint32_t fragLen = 0; + uint32_t lastFragLen = 0; + int errCode = ProtocolProto::AnalyzeSplitStructure(inPacketInfo, fragLen, lastFragLen); + if (errCode != E_OK) { + LOGE("[Combiner][CreateWork] Analyze fail, errCode=%d.", errCode); + return errCode; + } + + CombineWork work; + + work.frameInfo.SetPacketLen(inPacketInfo.GetFrameLen()); + work.frameInfo.SetSourceId(inPacketInfo.GetSourceId()); + work.frameInfo.SetFrameId(inPacketInfo.GetFrameId()); + work.frameInfo.SetFrameTypeInfo(inPacketInfo.GetFrameTypeInfo()); + work.frameInfo.SetFrameLen(inPacketInfo.GetFrameLen()); + work.frameInfo.SetFragCount(inPacketInfo.GetFragCount()); + + work.status.SetFragmentLen(fragLen); + work.status.SetLastFragmentLen(lastFragLen); + work.status.SetFragmentCount(inPacketInfo.GetFragCount()); + + work.buffer = CreateNewFrameBuffer(inPacketInfo); + if (work.buffer == nullptr) { + return -E_OUT_OF_MEMORY; + } + + uint32_t fragOffset = work.status.GetThisFragmentOffset(inPacketInfo.GetFragNo()); + uint32_t fragLength = work.status.GetThisFragmentLength(inPacketInfo.GetFragNo()); + errCode = ProtocolProto::CombinePacketIntoFrame(work.buffer, bytes, length, fragOffset, fragLength); + if (errCode != E_OK) { + delete work.buffer; + work.buffer = nullptr; + return errCode; + } + + totalSizeByByte_ += work.buffer->GetSize(); + work.status.UpdateProgressId(incProgressId_++); + work.status.CheckInFragmentNo(inPacketInfo.GetFragNo()); + combineWorkPool_[inPacketInfo.GetSourceId()][inPacketInfo.GetFrameId()] = work; + return E_OK; +} + +void FrameCombiner::AbortCombineWorkBySource(uint64_t inSourceId) +{ + if (combineWorkPool_[inSourceId].size() == 0) { + return; + } + uint32_t toBeAbortFrameId = 0; + uint64_t toBeAbortProgressId = UINT64_MAX; + for (auto &entry : combineWorkPool_[inSourceId]) { + if (entry.second.status.GetProgressId() < toBeAbortProgressId) { + toBeAbortProgressId = entry.second.status.GetProgressId(); + toBeAbortFrameId = entry.first; + } + } + // Do Abort! + LOGW("[Combiner][AbortWork] Abort Incomplete CombineWork, sourceId=%llu, frameId=%u.", + ULL(inSourceId), toBeAbortFrameId); + delete combineWorkPool_[inSourceId][toBeAbortFrameId].buffer; + combineWorkPool_[inSourceId][toBeAbortFrameId].buffer = nullptr; + combineWorkPool_[inSourceId].erase(toBeAbortFrameId); +} + +bool FrameCombiner::CheckPacketWithOriWork(const ParseResult &inPacketInfo, const CombineWork &inWork) +{ + if (inPacketInfo.GetFrameLen() != inWork.frameInfo.GetFrameLen()) { + LOGE("[Combiner][CheckPacket] FrameLen mismatch %u vs %u.", inPacketInfo.GetFrameLen(), + inWork.frameInfo.GetFrameLen()); + return false; + } + if (inPacketInfo.GetFragCount() != inWork.frameInfo.GetFragCount()) { + LOGE("[Combiner][CheckPacket] FragCount mismatch %u vs %u.", inPacketInfo.GetFragCount(), + inWork.frameInfo.GetFragCount()); + return false; + } + if (inPacketInfo.GetFragNo() >= inPacketInfo.GetFragCount()) { + LOGE("[Combiner][CheckPacket] FragNo=%u illegal vs FragCount=%u.", inPacketInfo.GetFragNo(), + inPacketInfo.GetFragCount()); + return false; + } + if (inWork.status.IsFragNoAlreadyExist(inPacketInfo.GetFragNo())) { + LOGE("[Combiner][CheckPacket] FragNo=%u already exist.", inPacketInfo.GetFragNo()); + return false; + } + return true; +} + +SerialBuffer *FrameCombiner::CreateNewFrameBuffer(const ParseResult &inInfo) +{ + SerialBuffer *buffer = new (std::nothrow) SerialBuffer(); + if (buffer == nullptr) { + return nullptr; + } + uint32_t frameHeaderLength = (inInfo.GetFrameTypeInfo() != FrameType::APPLICATION_MESSAGE) ? + ProtocolProto::GetCommLayerFrameHeaderLength() : ProtocolProto::GetAppLayerFrameHeaderLength(); + int errCode = buffer->AllocBufferByTotalLength(inInfo.GetFrameLen(), frameHeaderLength); + if (errCode != E_OK) { + LOGE("[Combiner][CreateBuffer] Alloc Buffer Fail."); + delete buffer; + buffer = nullptr; + return nullptr; + } + return buffer; +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/communicator/src/frame_header.h b/mock/distributeddb/communicator/src/frame_header.h new file mode 100644 index 00000000..b861a3e1 --- /dev/null +++ b/mock/distributeddb/communicator/src/frame_header.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 FRAMEHEADER_H +#define FRAMEHEADER_H + +#include +#include "communicator_type_define.h" + +namespace DistributedDB { +/* + * packetType: Bit0: FragmentFlag: 1: Fragmented 0: Not Fragmented + * Bit1~3: Reserved + * Bit4~7: FrameType + */ +struct CommPhyHeader { + uint16_t magic = 0; // Magic code to discern byte stream + uint16_t version = 0; // Version to differentiate fields layout + uint32_t packetLen = 0; // Length of total packet, include CommHeader and Padding + uint64_t checkSum = 0; // Check sum of data that follows CommPhyHeader + uint64_t sourceId = 0; // Indicate where this packet from + uint32_t frameId = 0; // FrameId to identify frame + uint8_t packetType = 0; // Some bits works individually, the high four bits indicates frameType + uint8_t paddingLen = 0; // Unit byte, range from 0 to 7. + uint16_t dbIntVer = 0; // Auxiliary info to help recognize db version in the future +}; + +/* + * Whether a physical packet contains CommPhyOptHeader depend on FragmentFlag of packetType in CommPhyHeader + */ +struct CommPhyOptHeader { + uint32_t frameLen = 0; // Indicate length of frame before fragmentation. Frame include CommHeader no padding + uint16_t fragCount = 0; // Indicate how many fragments this frame is divided into + uint16_t fragNo = 0; // Indicate which fragment this packet is. start from 0. +}; + +/* + * Whether a physical packet contains CommDivergeHeader depend on FrameType of packetType in CommPhyHeader + */ +struct CommDivergeHeader { + uint16_t version = 0; // Version to differentiate fields layout + uint16_t reserved = 0; // Reserved for future usage + uint32_t payLoadLen = 0; // Indicate length of data that follows CommDivergeHeader + uint8_t commLabel[COMM_LABEL_LENGTH] = {0}; // Indicate which communicator to hand out this frame +}; + +/* + * MessageHeader used to describe a message + */ +struct MessageHeader { + uint16_t version = 0; // Version to differentiate fields layout + uint16_t messageType = 0; // Distinguish request/response/notify + uint32_t messageId = 0; // Indicate message command + uint32_t sessionId = 0; // For matching request and response + uint32_t sequenceId = 0; // Sequence of message + uint32_t errorNo = 0; // Indicate no error when zero + uint32_t dataLen = 0; // Indicate length of data that follows MessageHeader +}; +} // namespace DistributedDB + +#endif // FRAMEHEADER_H diff --git a/mock/distributeddb/communicator/src/frame_retainer.cpp b/mock/distributeddb/communicator/src/frame_retainer.cpp new file mode 100644 index 00000000..e8d6db64 --- /dev/null +++ b/mock/distributeddb/communicator/src/frame_retainer.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "frame_retainer.h" +#include "db_common.h" +#include "log_print.h" +#include "serial_buffer.h" + +namespace DistributedDB { +namespace { +const uint32_t MAX_CAPACITY = 67108864; // 64 M bytes +const uint32_t MAX_RETAIN_TIME = 10; // 10 s +const uint32_t MAX_RETAIN_FRAME_SIZE = 33554432; // 32 M bytes +const uint32_t MAX_RETAIN_FRAME_PER_LABEL_PER_TARGET = 5; // Allow 5 frame per communicator per source target +const int SURVAIL_PERIOD_IN_MILLISECOND = 1000; // Period is 1 s +inline void LogRetainInfo(const std::string &logPrefix, const LabelType &label, const std::string &target, + uint64_t order, const RetainWork &work) +{ + LOGI("%s : Label=%s, target=%s{private}, retainOrder=%llu, frameId=%u, remainTime=%u, frameSize=%u.", + logPrefix.c_str(), VEC_TO_STR(label), target.c_str(), ULL(order), + work.frameId, work.remainTime, work.buffer->GetSize()); +} +} + +void FrameRetainer::Initialize() +{ + RuntimeContext *context = RuntimeContext::GetInstance(); + if (context == nullptr) { + return; // Never gonna happen, context always be valid. + } + TimerAction action = [this](TimerId inTimerId)->int { + PeriodicalSurveillance(); + return E_OK; + }; + int errCode = context->SetTimer(SURVAIL_PERIOD_IN_MILLISECOND, action, nullptr, timerId_); + if (errCode != E_OK) { + LOGE("[Retainer][Init] Set timer fail, errCode=%d.", errCode); + return; + } + isTimerWork_ = true; +} + +void FrameRetainer::Finalize() +{ + RuntimeContext *context = RuntimeContext::GetInstance(); + if (context == nullptr) { + return; // Never gonna happen, context always be valid. + } + // First: Stop the timer + if (isTimerWork_) { + // After return, the timer rely no more on retainer. + context->RemoveTimer(timerId_, true); + isTimerWork_ = false; + } + // Second: Clear the retainWorkPool_ + for (auto &eachLabel : retainWorkPool_) { + for (auto &eachTarget : eachLabel.second) { + for (auto &eachFrame : eachTarget.second) { + LogRetainInfo("[Retainer][Final] DISCARD", eachLabel.first, eachTarget.first, eachFrame.first, + eachFrame.second); + delete eachFrame.second.buffer; + eachFrame.second.buffer = nullptr; + } + } + } + retainWorkPool_.clear(); + totalSizeByByte_ = 0; + totalRetainFrames_ = 0; +} + +void FrameRetainer::RetainFrame(const FrameInfo &inFrame) +{ + if (inFrame.buffer == nullptr) { + return; // Never gonna happen + } + RetainWork work{inFrame.buffer, inFrame.frameId, MAX_RETAIN_TIME}; + if (work.buffer->GetSize() > MAX_RETAIN_FRAME_SIZE) { + LOGE("[Retainer][Retain] Frame size=%u over limit=%u.", work.buffer->GetSize(), MAX_RETAIN_FRAME_SIZE); + delete work.buffer; + work.buffer = nullptr; + return; + } + int errCode = work.buffer->ConvertForCrossThread(); + if (errCode != E_OK) { + LOGE("[Retainer][Retain] ConvertForCrossThread fail, errCode=%d.", errCode); + delete work.buffer; + work.buffer = nullptr; + return; + } + + std::lock_guard overallLockGuard(overallMutex_); + std::map &perLabelPerTarget = retainWorkPool_[inFrame.commLabel][inFrame.srcTarget]; + if (perLabelPerTarget.size() >= MAX_RETAIN_FRAME_PER_LABEL_PER_TARGET) { + // Discard the oldest and obsolete one, update the statistics, free the buffer and remove from the map + auto iter = perLabelPerTarget.begin(); + LogRetainInfo("[Retainer][Retain] DISCARD", inFrame.commLabel, inFrame.srcTarget, iter->first, iter->second); + totalSizeByByte_ -= iter->second.buffer->GetSize(); + totalRetainFrames_--; + delete iter->second.buffer; + iter->second.buffer = nullptr; + perLabelPerTarget.erase(iter); + } + // Retain the new frame, update the statistics + perLabelPerTarget[incRetainOrder_++] = work; + totalSizeByByte_ += inFrame.buffer->GetSize(); + totalRetainFrames_++; + // Discard obsolete frames until totalSize under capacity. + DiscardObsoleteFramesIfNeed(); + // Display the final statistics + LOGI("[Retainer][Retain] Order=%llu. Statistics: TOTAL_BYTE=%u, TOTAL_FRAME=%u.", ULL(incRetainOrder_ - 1), + totalSizeByByte_, totalRetainFrames_); +} + +std::list FrameRetainer::FetchFramesForSpecificCommunicator(const LabelType &inCommLabel) +{ + std::lock_guard overallLockGuard(overallMutex_); + std::list outFrameList; + if (retainWorkPool_.count(inCommLabel) == 0) { + return outFrameList; + } + auto &perLabel = retainWorkPool_[inCommLabel]; + std::map fetchOrder; + for (auto &eachTarget : perLabel) { + for (auto &eachFrame : eachTarget.second) { + fetchOrder[eachFrame.first] = eachTarget.first; + } + } + for (auto &entry : fetchOrder) { + RetainWork &work = perLabel[entry.second][entry.first]; + LogRetainInfo("[Retainer][Fetch] FETCH-OUT", inCommLabel, entry.second, entry.first, work); + outFrameList.emplace_back(FrameInfo{work.buffer, entry.second, inCommLabel, work.frameId}); + // Update statistics + totalSizeByByte_ -= work.buffer->GetSize(); + totalRetainFrames_--; + } + retainWorkPool_.erase(inCommLabel); + return outFrameList; +} + +void FrameRetainer::PeriodicalSurveillance() +{ + std::lock_guard overallLockGuard(overallMutex_); + // First: Discard overtime frames. + for (auto &eachLabel : retainWorkPool_) { + for (auto &eachTarget : eachLabel.second) { + std::set frameToDiscard; + for (auto &eachFrame : eachTarget.second) { + // Decrease remainTime and discard if need. The remainTime will not be zero before decrease. + eachFrame.second.remainTime--; + if (eachFrame.second.remainTime == 0) { + LogRetainInfo("[Retainer][Surveil] DISCARD", eachLabel.first, eachTarget.first, eachFrame.first, + eachFrame.second); + totalSizeByByte_ -= eachFrame.second.buffer->GetSize(); + totalRetainFrames_--; + // Free this retain work first + delete eachFrame.second.buffer; + eachFrame.second.buffer = nullptr; + // Record this frame in discard list + frameToDiscard.insert(eachFrame.first); + } + } + // Remove the retain work from frameMap. + for (auto &entry : frameToDiscard) { + eachTarget.second.erase(entry); + } + } + } + // Second: Shrink the retainWorkPool_ + ShrinkRetainWorkPool(); +} + +void FrameRetainer::DiscardObsoleteFramesIfNeed() +{ + if (totalSizeByByte_ <= MAX_CAPACITY) { + return; + } + std::map> discardOrder; + // Sort all the frames by their retain order ascendingly + for (auto &eachLabel : retainWorkPool_) { + for (auto &eachTarget : eachLabel.second) { + for (auto &eachFrame : eachTarget.second) { + discardOrder[eachFrame.first] = {eachLabel.first, eachTarget.first}; + } + } + } + // Discard obsolete frames until totalSize under capacity. + while (totalSizeByByte_ > MAX_CAPACITY) { + if (discardOrder.empty()) { // Unlikely to happen + LOGE("[Retainer][Discard] Internal Error: Byte=%u, Frames=%u.", totalSizeByByte_, totalRetainFrames_); + return; + } + auto iter = discardOrder.begin(); + RetainWork &workRef = retainWorkPool_[iter->second.first][iter->second.second][iter->first]; + LogRetainInfo("[Retainer][Discard] DISCARD", iter->second.first, iter->second.second, iter->first, workRef); + // Discard the oldest and obsolete one, update the statistics, free the buffer and remove from the map + totalSizeByByte_ -= workRef.buffer->GetSize(); + totalRetainFrames_--; + delete workRef.buffer; + workRef.buffer = nullptr; + retainWorkPool_[iter->second.first][iter->second.second].erase(iter->first); + // Remove from the discardOrder + discardOrder.erase(iter); + } + // Shrink the retainWorkPool_ to remove out empty node on the map + ShrinkRetainWorkPool(); +} + +void FrameRetainer::ShrinkRetainWorkPool() +{ + std::set emptyLabel; + for (auto &eachLabel : retainWorkPool_) { + std::set emptyTarget; + for (auto &eachTarget : eachLabel.second) { + // Record corresponding target if its frameMap empty. + if (eachTarget.second.empty()) { + emptyTarget.insert(eachTarget.first); + } + } + // Remove the empty frameMap from the targetMap. Record corresponding label if its targetMap empty. + for (auto &entry : emptyTarget) { + eachLabel.second.erase(entry); + } + if (eachLabel.second.empty()) { + emptyLabel.insert(eachLabel.first); + } + } + // Remove the empty targetMap from retainWorkPool_ + for (auto &entry : emptyLabel) { + retainWorkPool_.erase(entry); + } +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/communicator/src/header_converter.cpp b/mock/distributeddb/communicator/src/header_converter.cpp new file mode 100644 index 00000000..4b8fe88e --- /dev/null +++ b/mock/distributeddb/communicator/src/header_converter.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "header_converter.h" +#include "endian_convert.h" +#include "communicator_type_define.h" + +namespace DistributedDB { +void HeaderConverter::ConvertHostToNet(const CommPhyHeader &headerOriginal, CommPhyHeader &headerConverted) +{ + headerConverted.magic = HostToNet(headerOriginal.magic); + headerConverted.version = HostToNet(headerOriginal.version); + headerConverted.packetLen = HostToNet(headerOriginal.packetLen); + headerConverted.checkSum = HostToNet(headerOriginal.checkSum); + headerConverted.sourceId = HostToNet(headerOriginal.sourceId); + headerConverted.frameId = HostToNet(headerOriginal.frameId); + headerConverted.packetType = HostToNet(headerOriginal.packetType); + headerConverted.paddingLen = HostToNet(headerOriginal.paddingLen); + headerConverted.dbIntVer = HostToNet(headerOriginal.dbIntVer); +} + +void HeaderConverter::ConvertHostToNet(const CommPhyOptHeader &headerOriginal, CommPhyOptHeader &headerConverted) +{ + headerConverted.frameLen = HostToNet(headerOriginal.frameLen); + headerConverted.fragCount = HostToNet(headerOriginal.fragCount); + headerConverted.fragNo = HostToNet(headerOriginal.fragNo); +} + +void HeaderConverter::ConvertHostToNet(const CommDivergeHeader &headerOriginal, CommDivergeHeader &headerConverted) +{ + ConvertNetToHost(headerOriginal, headerConverted); +} + +void HeaderConverter::ConvertHostToNet(const MessageHeader &headerOriginal, MessageHeader &headerConverted) +{ + ConvertNetToHost(headerOriginal, headerConverted); +} + +void HeaderConverter::ConvertNetToHost(const CommPhyHeader &headerOriginal, CommPhyHeader &headerConverted) +{ + ConvertHostToNet(headerOriginal, headerConverted); +} + +void HeaderConverter::ConvertNetToHost(const CommPhyOptHeader &headerOriginal, CommPhyOptHeader &headerConverted) +{ + ConvertHostToNet(headerOriginal, headerConverted); +} + +void HeaderConverter::ConvertNetToHost(const CommDivergeHeader &headerOriginal, CommDivergeHeader &headerConverted) +{ + headerConverted.version = NetToHost(headerOriginal.version); + headerConverted.reserved = NetToHost(headerOriginal.reserved); + headerConverted.payLoadLen = NetToHost(headerOriginal.payLoadLen); + // commLabel now is array of uint8_t, so no need to do endian convert, but we need to copy it here + for (unsigned int i = 0; i < COMM_LABEL_LENGTH; i++) { + headerConverted.commLabel[i] = headerOriginal.commLabel[i]; + } +} + +void HeaderConverter::ConvertNetToHost(const MessageHeader &headerOriginal, MessageHeader &headerConverted) +{ + headerConverted.version = NetToHost(headerOriginal.version); + headerConverted.messageType = NetToHost(headerOriginal.messageType); + headerConverted.messageId = NetToHost(headerOriginal.messageId); + headerConverted.sessionId = NetToHost(headerOriginal.sessionId); + headerConverted.sequenceId = NetToHost(headerOriginal.sequenceId); + headerConverted.errorNo = NetToHost(headerOriginal.errorNo); + headerConverted.dataLen = NetToHost(headerOriginal.dataLen); +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/communicator/src/header_converter.h b/mock/distributeddb/communicator/src/header_converter.h new file mode 100644 index 00000000..7cccfde4 --- /dev/null +++ b/mock/distributeddb/communicator/src/header_converter.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 HEADER_CONVERTER_H +#define HEADER_CONVERTER_H + +#include "frame_header.h" + +namespace DistributedDB { +class HeaderConverter { +public: + static void ConvertHostToNet(const CommPhyHeader &headerOriginal, CommPhyHeader &headerConverted); + static void ConvertHostToNet(const CommPhyOptHeader &headerOriginal, CommPhyOptHeader &headerConverted); + static void ConvertHostToNet(const CommDivergeHeader &headerOriginal, CommDivergeHeader &headerConverted); + static void ConvertHostToNet(const MessageHeader &headerOriginal, MessageHeader &headerConverted); + + static void ConvertNetToHost(const CommPhyHeader &headerOriginal, CommPhyHeader &headerConverted); + static void ConvertNetToHost(const CommPhyOptHeader &headerOriginal, CommPhyOptHeader &headerConverted); + static void ConvertNetToHost(const CommDivergeHeader &headerOriginal, CommDivergeHeader &headerConverted); + static void ConvertNetToHost(const MessageHeader &headerOriginal, MessageHeader &headerConverted); +}; +} // namespace DistributedDB + +#endif // HEADER_CONVERTER_H \ No newline at end of file diff --git a/mock/distributeddb/communicator/src/message_transform.cpp b/mock/distributeddb/communicator/src/message_transform.cpp new file mode 100644 index 00000000..dfcfc516 --- /dev/null +++ b/mock/distributeddb/communicator/src/message_transform.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "message_transform.h" +#include "protocol_proto.h" + +namespace DistributedDB { +int MessageTransform::RegTransformFunction(uint32_t msgId, const TransformFunc &inFunc) +{ + return ProtocolProto::RegTransformFunction(msgId, inFunc); +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/communicator/src/network_adapter.cpp b/mock/distributeddb/communicator/src/network_adapter.cpp new file mode 100644 index 00000000..061fe1a5 --- /dev/null +++ b/mock/distributeddb/communicator/src/network_adapter.cpp @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "network_adapter.h" +#include "db_constant.h" +#include "db_errno.h" +#include "log_print.h" +#include "runtime_context.h" + +namespace DistributedDB { +namespace { +const std::string DEFAULT_PROCESS_LABEL = "Distributeddb_Anonymous_Process"; +const std::string SCHEDULE_QUEUE_TAG = "NetworkAdapter"; +} + +NetworkAdapter::NetworkAdapter() + : processLabel_(DEFAULT_PROCESS_LABEL), processCommunicator_(nullptr) +{ +} + +NetworkAdapter::NetworkAdapter(const std::string &inProcessLabel) + : processLabel_(inProcessLabel), processCommunicator_(nullptr) +{ +} + +NetworkAdapter::NetworkAdapter(const std::string &inProcessLabel, + const std::shared_ptr &inCommunicator) + : processLabel_(inProcessLabel), processCommunicator_(inCommunicator) +{ +} + +NetworkAdapter::~NetworkAdapter() +{ +} + +int NetworkAdapter::StartAdapter() +{ + LOGI("[NAdapt][Start] Enter, ProcessLabel=%s.", processLabel_.c_str()); + if (processLabel_.empty()) { + return -E_INVALID_ARGS; + } + if (!processCommunicator_) { + LOGE("[NAdapt][Start] ProcessCommunicator not be designated yet."); + return -E_INVALID_ARGS; + } + DBStatus errCode = processCommunicator_->Start(processLabel_); + if (errCode != DBStatus::OK) { + LOGE("[NAdapt][Start] Start Fail, errCode=%d.", static_cast(errCode)); + return -E_PERIPHERAL_INTERFACE_FAIL; + } + errCode = processCommunicator_->RegOnDataReceive(std::bind(&NetworkAdapter::OnDataReceiveHandler, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + if (errCode != DBStatus::OK) { + LOGE("[NAdapt][Start] RegOnDataReceive Fail, errCode=%d.", static_cast(errCode)); + // DO ROLLBACK + errCode = processCommunicator_->Stop(); + LOGI("[NAdapt][Start] ROLLBACK: Stop errCode=%d.", static_cast(errCode)); + return -E_PERIPHERAL_INTERFACE_FAIL; + } + errCode = processCommunicator_->RegOnDeviceChange(std::bind(&NetworkAdapter::OnDeviceChangeHandler, this, + std::placeholders::_1, std::placeholders::_2)); + if (errCode != DBStatus::OK) { + LOGE("[NAdapt][Start] RegOnDeviceChange Fail, errCode=%d.", static_cast(errCode)); + // DO ROLLBACK + errCode = processCommunicator_->RegOnDataReceive(nullptr); + LOGI("[NAdapt][Start] ROLLBACK: UnRegOnDataReceive errCode=%d.", static_cast(errCode)); + errCode = processCommunicator_->Stop(); + LOGI("[NAdapt][Start] ROLLBACK: Stop errCode=%d.", static_cast(errCode)); + return -E_PERIPHERAL_INTERFACE_FAIL; + } + // These code is compensation for the probable defect of IProcessCommunicator implementation. + // As described in the agreement, for the missed online situation, we search for the online devices at beginning. + // OnDeviceChangeHandler is reused to check the existence of peer process. + // Since at this point, the CommunicatorAggregator had not been fully initialized, + // We need an async task which bring about dependency on the lifecycle of this NetworkAdapter Object. + SearchOnlineRemoteDeviceAtStartup(); + LOGI("[NAdapt][Start] Exit."); + return E_OK; +} + +// StartAdapter and StopAdapter are all innerly called by ICommunicatorAggregator +// If StopAdapter is called, the StartAdapter must have been called successfully before, +// so processCommunicator_ won't be null +void NetworkAdapter::StopAdapter() +{ + LOGI("[NAdapt][Stop] Enter, ProcessLabel=%s.", processLabel_.c_str()); + DBStatus errCode = processCommunicator_->RegOnDeviceChange(nullptr); + if (errCode != DBStatus::OK) { + LOGE("[NAdapt][Stop] UnRegOnDeviceChange Fail, errCode=%d.", static_cast(errCode)); + } + errCode = processCommunicator_->RegOnDataReceive(nullptr); + if (errCode != DBStatus::OK) { + LOGE("[NAdapt][Stop] UnRegOnDataReceive Fail, errCode=%d.", static_cast(errCode)); + } + errCode = processCommunicator_->Stop(); + if (errCode != DBStatus::OK) { + LOGE("[NAdapt][Stop] Stop Fail, errCode=%d.", static_cast(errCode)); + } + // We don't reset the shared_ptr of commProvider here, the release of commProvider is done by deconstruct of adapter + // In this way, the adapter can be start again after stop it, since it still hold the an valid commProvider + // The async task is dependent on this Object. we have to wait until all async task finished. + LOGI("[NAdapt][Stop] Wait all async task done."); + std::unique_lock asyncTaskDoneLock(asyncTaskDoneMutex_); + asyncTaskDoneCv_.wait(asyncTaskDoneLock, [this]{ return pendingAsyncTaskCount_ <= 0; }); + LOGI("[NAdapt][Stop] Exit."); +} + +namespace { +uint32_t CheckAndAdjustMtuSize(uint32_t inMtuSize) +{ + if (inMtuSize < DBConstant::MIN_MTU_SIZE) { + return DBConstant::MIN_MTU_SIZE; + } else if (inMtuSize > DBConstant::MAX_MTU_SIZE) { + return DBConstant::MAX_MTU_SIZE; + } else { + return (inMtuSize - (inMtuSize % sizeof(uint64_t))); // Octet alignment + } +} + +uint32_t CheckAndAdjustTimeout(uint32_t inTimeout) +{ + if (inTimeout < DBConstant::MIN_TIMEOUT) { + return DBConstant::MIN_TIMEOUT; + } else if (inTimeout > DBConstant::MAX_TIMEOUT) { + return DBConstant::MAX_TIMEOUT; + } else { + return inTimeout; + } +} +} + +uint32_t NetworkAdapter::GetMtuSize() +{ + std::lock_guard mtuSizeLockGuard(mtuSizeMutex_); + if (!isMtuSizeValid_) { + mtuSize_ = processCommunicator_->GetMtuSize(); + LOGI("[NAdapt][GetMtu] mtuSize=%u.", mtuSize_); + mtuSize_ = CheckAndAdjustMtuSize(mtuSize_); + isMtuSizeValid_ = true; + } + return mtuSize_; +} + +uint32_t NetworkAdapter::GetMtuSize(const std::string &target) +{ +#ifndef OMIT_MTU_CACHE + DeviceInfos devInfo; + devInfo.identifier = target; + uint32_t oriMtuSize = processCommunicator_->GetMtuSize(devInfo); + return CheckAndAdjustMtuSize(oriMtuSize); +#else + std::lock_guard mtuSizeLockGuard(mtuSizeMutex_); + if (devMapMtuSize_.count(target) == 0) { + DeviceInfos devInfo; + devInfo.identifier = target; + uint32_t oriMtuSize = processCommunicator_->GetMtuSize(devInfo); + LOGI("[NAdapt][GetMtu] mtuSize=%u of target=%s{private}.", oriMtuSize, target.c_str()); + devMapMtuSize_[target] = CheckAndAdjustMtuSize(oriMtuSize); + } + return devMapMtuSize_[target]; +#endif +} + +uint32_t NetworkAdapter::GetTimeout() +{ + uint32_t timeout = processCommunicator_->GetTimeout(); + LOGI("[NAdapt][GetTimeout] timeout_=%u ms.", timeout); + return CheckAndAdjustTimeout(timeout); +} + +uint32_t NetworkAdapter::GetTimeout(const std::string &target) +{ + DeviceInfos devInfos; + devInfos.identifier = target; + uint32_t timeout = processCommunicator_->GetTimeout(devInfos); + LOGI("[NAdapt][GetTimeout] timeout=%u ms of target=%s{private}.", timeout, target.c_str()); + return CheckAndAdjustTimeout(timeout); +} + +int NetworkAdapter::GetLocalIdentity(std::string &outTarget) +{ + std::lock_guard identityLockGuard(identityMutex_); + DeviceInfos devInfo = processCommunicator_->GetLocalDeviceInfos(); + if (devInfo.identifier.empty()) { + return -E_PERIPHERAL_INTERFACE_FAIL; + } + if (devInfo.identifier != localIdentity_) { + LOGI("[NAdapt][GetLocal] localIdentity=%s{private}.", devInfo.identifier.c_str()); + } + localIdentity_ = devInfo.identifier; + outTarget = localIdentity_; + return E_OK; +} + +int NetworkAdapter::SendBytes(const std::string &dstTarget, const uint8_t *bytes, uint32_t length) +{ + if (bytes == nullptr || length == 0) { + return -E_INVALID_ARGS; + } + LOGI("[NAdapt][SendBytes] Enter, to=%s{private}, length=%u", dstTarget.c_str(), length); + DeviceInfos dstDevInfo; + dstDevInfo.identifier = dstTarget; + DBStatus errCode = processCommunicator_->SendData(dstDevInfo, bytes, length); + if (errCode != DBStatus::OK) { + LOGE("[NAdapt][SendBytes] SendData Fail, errCode=%d.", static_cast(errCode)); + // These code is compensation for the probable defect of IProcessCommunicator implementation. + // As described in the agreement, for the missed offline situation, we check if still online at send fail. + // OnDeviceChangeHandler is reused but check the existence of peer process is done outerly. + // Since this thread is the sending_thread of the CommunicatorAggregator, + // We need an async task which bring about dependency on the lifecycle of this NetworkAdapter Object. + CheckDeviceOfflineAfterSendFail(dstDevInfo); + return -E_PERIPHERAL_INTERFACE_FAIL; + } + return E_OK; +} + +int NetworkAdapter::RegBytesReceiveCallback(const BytesReceiveCallback &onReceive, const Finalizer &inOper) +{ + std::lock_guard onReceiveLockGard(onReceiveMutex_); + return RegCallBack(onReceive, onReceiveHandle_, inOper, onReceiveFinalizer_); +} + +int NetworkAdapter::RegTargetChangeCallback(const TargetChangeCallback &onChange, const Finalizer &inOper) +{ + std::lock_guard onChangeLockGard(onChangeMutex_); + return RegCallBack(onChange, onChangeHandle_, inOper, onChangeFinalizer_); +} + +int NetworkAdapter::RegSendableCallback(const SendableCallback &onSendable, const Finalizer &inOper) +{ + std::lock_guard onSendableLockGard(onSendableMutex_); + return RegCallBack(onSendable, onSendableHandle_, inOper, onSendableFinalizer_); +} + +void NetworkAdapter::OnDataReceiveHandler(const DeviceInfos &srcDevInfo, const uint8_t *data, uint32_t length) +{ + if (data == nullptr || length == 0) { + LOGE("[NAdapt][OnDataRecv] data nullptr or length = %u.", length); + return; + } + uint32_t headLength = 0; + std::vector userId; + std::string currentUserId; + DBStatus errCode = processCommunicator_->CheckAndGetDataHeadInfo(data, length, headLength, userId); + LOGI("[NAdapt][OnDataRecv] Enter, from=%s{private}, extendHeadLength=%u, totalLength=%u", + srcDevInfo.identifier.c_str(), headLength, length); + if (errCode == NO_PERMISSION) { + LOGI("[NAdapt][OnDataRecv] userId dismatched, drop packet"); + return; + } + { + std::lock_guard onReceiveLockGard(onReceiveMutex_); + if (!onReceiveHandle_) { + LOGE("[NAdapt][OnDataRecv] onReceiveHandle invalid."); + return; + } + if (userId.size() >= 1) { + currentUserId = userId[0]; + } + onReceiveHandle_(srcDevInfo.identifier, data + headLength, length - headLength, currentUserId); + } + // These code is compensation for the probable defect of IProcessCommunicator implementation. + // As described in the agreement, for the missed online situation, we check the source dev when received. + // OnDeviceChangeHandler is reused to check the existence of peer process. + // Since this thread is the callback_thread of IProcessCommunicator, we do this check task directly in this thread. + CheckDeviceOnlineAfterReception(srcDevInfo); +} + +void NetworkAdapter::OnDeviceChangeHandler(const DeviceInfos &devInfo, bool isOnline) +{ + LOGI("[NAdapt][OnDeviceChange] Enter, dev=%s{private}, isOnline=%d", devInfo.identifier.c_str(), isOnline); + // These code is compensation for the probable defect of IProcessCommunicator implementation. + // As described in the agreement, for the mistake online situation, we check the existence of peer process. + // The IProcessCommunicator implementation guarantee that no mistake offline will happen. + if (isOnline) { + if (!processCommunicator_->IsSameProcessLabelStartedOnPeerDevice(devInfo)) { + LOGI("[NAdapt][OnDeviceChange] ######## Detect Not Really Online ########."); + std::lock_guard onlineRemoteDevLockGuard(onlineRemoteDevMutex_); + onlineRemoteDev_.erase(devInfo.identifier); + return; + } + std::lock_guard onlineRemoteDevLockGuard(onlineRemoteDevMutex_); + onlineRemoteDev_.insert(devInfo.identifier); + } else { + std::lock_guard onlineRemoteDevLockGuard(onlineRemoteDevMutex_); + onlineRemoteDev_.erase(devInfo.identifier); + } + // End compensation, do callback. + std::lock_guard onChangeLockGard(onChangeMutex_); + if (!onChangeHandle_) { + LOGE("[NAdapt][OnDeviceChange] onChangeHandle_ invalid."); + return; + } + onChangeHandle_(devInfo.identifier, isOnline); +} + +void NetworkAdapter::SearchOnlineRemoteDeviceAtStartup() +{ + std::vector onlineDev = processCommunicator_->GetRemoteOnlineDeviceInfosList(); + LOGE("[NAdapt][SearchOnline] onlineDev count = %zu.", onlineDev.size()); + if (!onlineDev.empty()) { + pendingAsyncTaskCount_.fetch_add(1); + // Note: onlineDev should be captured by value (must not by reference) + TaskAction callbackTask = [onlineDev, this]() { + LOGI("[NAdapt][SearchOnline] Begin Callback In Async Task."); + std::string localIdentity; + GetLocalIdentity(localIdentity); // It doesn't matter if getlocal fail and localIdentity be an empty string + for (auto &entry : onlineDev) { + if (entry.identifier == localIdentity) { + LOGW("[NAdapt][SearchOnline] ######## Detect Local Device in Remote Device List ########."); + continue; + } + OnDeviceChangeHandler(entry, true); + } + pendingAsyncTaskCount_.fetch_sub(1); + asyncTaskDoneCv_.notify_all(); + LOGI("[NAdapt][SearchOnline] End Callback In Async Task."); + }; + // Use ScheduleQueuedTask to keep order + int errCode = RuntimeContext::GetInstance()->ScheduleQueuedTask(SCHEDULE_QUEUE_TAG, callbackTask); + if (errCode != E_OK) { + LOGE("[NAdapt][SearchOnline] ScheduleQueuedTask failed, errCode = %d.", errCode); + pendingAsyncTaskCount_.fetch_sub(1); + asyncTaskDoneCv_.notify_all(); + } + } +} + +void NetworkAdapter::CheckDeviceOnlineAfterReception(const DeviceInfos &devInfo) +{ + bool isAlreadyOnline = true; + { + std::lock_guard onlineRemoteDevLockGuard(onlineRemoteDevMutex_); + if (onlineRemoteDev_.count(devInfo.identifier) == 0) { + isAlreadyOnline = false; + } + } + + // Seem offline but receive data from it, let OnDeviceChangeHandler check whether it is really online + if (!isAlreadyOnline) { + OnDeviceChangeHandler(devInfo, true); + } +} + +void NetworkAdapter::CheckDeviceOfflineAfterSendFail(const DeviceInfos &devInfo) +{ + // Note: only the identifier field of devInfo is valid, enough to call IsSameProcessLabelStartedOnPeerDevice + bool isAlreadyOffline = true; + { + std::lock_guard onlineRemoteDevLockGuard(onlineRemoteDevMutex_); + if (onlineRemoteDev_.count(devInfo.identifier) != 0) { + isAlreadyOffline = false; + } + } + + // Seem online but send fail, we have to check whether still online + if (!isAlreadyOffline) { + if (!processCommunicator_->IsSameProcessLabelStartedOnPeerDevice(devInfo)) { + LOGW("[NAdapt][CheckAfterSend] ######## Missed Offline Detected ########."); + { + // Mark this device not online immediately to avoid repeatedly miss-offline detect when send continually + std::lock_guard onlineRemoteDevLockGuard(onlineRemoteDevMutex_); + onlineRemoteDev_.erase(devInfo.identifier); + } + pendingAsyncTaskCount_.fetch_add(1); + // Note: devInfo should be captured by value (must not by reference) + TaskAction callbackTask = [devInfo, this]() { + LOGI("[NAdapt][CheckAfterSend] In Async Task, devInfo=%s{private}.", devInfo.identifier.c_str()); + OnDeviceChangeHandler(devInfo, false); + pendingAsyncTaskCount_.fetch_sub(1); + asyncTaskDoneCv_.notify_all(); + }; + // Use ScheduleQueuedTask to keep order + int errCode = RuntimeContext::GetInstance()->ScheduleQueuedTask(SCHEDULE_QUEUE_TAG, callbackTask); + if (errCode != E_OK) { + LOGE("[NAdapt][CheckAfterSend] ScheduleQueuedTask failed, errCode = %d.", errCode); + pendingAsyncTaskCount_.fetch_sub(1); + asyncTaskDoneCv_.notify_all(); + } + } + } +} + +bool NetworkAdapter::IsDeviceOnline(const std::string &device) +{ + std::lock_guard onlineRemoteDevLockGuard(onlineRemoteDevMutex_); + return (onlineRemoteDev_.find(device) != onlineRemoteDev_.end()); +} + +std::shared_ptr NetworkAdapter::GetExtendHeaderHandle(const ExtendInfo ¶mInfo) +{ + return processCommunicator_->GetExtendHeaderHandle(paramInfo); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/communicator/src/protocol_proto.cpp b/mock/distributeddb/communicator/src/protocol_proto.cpp new file mode 100644 index 00000000..8d27be2a --- /dev/null +++ b/mock/distributeddb/communicator/src/protocol_proto.cpp @@ -0,0 +1,1083 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "protocol_proto.h" +#include +#include +#include "hash.h" +#include "securec.h" +#include "version.h" +#include "db_common.h" +#include "log_print.h" +#include "macro_utils.h" +#include "endian_convert.h" +#include "header_converter.h" + +namespace DistributedDB { +namespace { +const uint16_t MAGIC_CODE = 0xAAAA; +const uint16_t PROTOCOL_VERSION = 0; +// Compatibility Final Method. 3 Correspond To Version 1.1.4(104) +const uint16_t DB_GLOBAL_VERSION = SOFTWARE_VERSION_CURRENT - SOFTWARE_VERSION_EARLIEST; +const uint8_t PACKET_TYPE_FRAGMENTED = BITX(0); // Use bit 0 +const uint8_t PACKET_TYPE_NOT_FRAGMENTED = 0; +const uint8_t MAX_PADDING_LEN = 7; +const uint32_t LENGTH_BEFORE_SUM_RANGE = sizeof(uint64_t) + sizeof(uint64_t); +const uint32_t MAX_FRAME_LEN = 32 * 1024 * 1024; // Max 32 MB, 1024 is scale +const uint16_t MIN_FRAGMENT_COUNT = 2; // At least a frame will be splited into 2 parts +// LabelExchange(Ack) Frame Field Length +const uint32_t LABEL_VER_LEN = sizeof(uint64_t); +const uint32_t DISTINCT_VALUE_LEN = sizeof(uint64_t); +const uint32_t SEQUENCE_ID_LEN = sizeof(uint64_t); +// Note: COMM_LABEL_LENGTH is defined in communicator_type_define.h +const uint32_t COMM_LABEL_COUNT_LEN = sizeof(uint64_t); +// Local func to set and get frame Type from packet Type field +void SetFrameType(uint8_t &inPacketType, FrameType inFrameType) +{ + inPacketType &= 0x0F; // Use 0x0F to clear high for bits + inPacketType |= (static_cast(inFrameType) << 4); // frame type is on high 4 bits +} +FrameType GetFrameType(uint8_t inPacketType) +{ + uint8_t frameType = ((inPacketType & 0xF0) >> 4); // Use 0xF0 to get high 4 bits + if (frameType >= static_cast(FrameType::INVALID_MAX_FRAME_TYPE)) { + return FrameType::INVALID_MAX_FRAME_TYPE; + } + return static_cast(frameType); +} +} + +std::map ProtocolProto::msgIdMapFunc_; + +uint32_t ProtocolProto::GetAppLayerFrameHeaderLength() +{ + uint32_t length = sizeof(CommPhyHeader) + sizeof(CommDivergeHeader); + return length; +} + +uint32_t ProtocolProto::GetLengthBeforeSerializedData() +{ + uint32_t length = sizeof(CommPhyHeader) + sizeof(CommDivergeHeader) + sizeof(MessageHeader); + return length; +} + +uint32_t ProtocolProto::GetCommLayerFrameHeaderLength() +{ + uint32_t length = sizeof(CommPhyHeader); + return length; +} + +SerialBuffer *ProtocolProto::ToSerialBuffer(const Message *inMsg, int &outErrorNo, + std::shared_ptr &extendHandle, bool onlyMsgHeader) +{ + if (inMsg == nullptr) { + outErrorNo = -E_INVALID_ARGS; + return nullptr; + } + + uint32_t serializeLen = 0; + if (!onlyMsgHeader) { + int errCode = CalculateDataSerializeLength(inMsg, serializeLen); + if (errCode != E_OK) { + outErrorNo = errCode; + return nullptr; + } + } + uint32_t headSize = 0; + int errCode = GetExtendHeadDataSize(extendHandle, headSize); + if (errCode != E_OK) { + outErrorNo = errCode; + return nullptr; + } + + SerialBuffer *buffer = new (std::nothrow) SerialBuffer(); + if (buffer == nullptr) { + outErrorNo = -E_OUT_OF_MEMORY; + return nullptr; + } + if (headSize > 0) { + buffer->SetExtendHeadLength(headSize); + } + // serializeLen maybe not 8-bytes aligned, let SerialBuffer deal with the padding. + uint32_t payLoadLength = serializeLen + sizeof(MessageHeader); + errCode = buffer->AllocBufferByPayloadLength(payLoadLength, GetAppLayerFrameHeaderLength()); + if (errCode != E_OK) { + LOGE("[Proto][ToSerial] Alloc Fail, errCode=%d.", errCode); + goto ERROR_HANDLE; + } + errCode = FillExtendHeadDataIfNeed(extendHandle, buffer, headSize); + if (errCode != E_OK) { + goto ERROR_HANDLE; + } + + // Serialize the MessageHeader and data if need + errCode = SerializeMessage(buffer, inMsg); + if (errCode != E_OK) { + LOGE("[Proto][ToSerial] Serialize Fail, errCode=%d.", errCode); + goto ERROR_HANDLE; + } + outErrorNo = E_OK; + return buffer; +ERROR_HANDLE: + outErrorNo = errCode; + delete buffer; + buffer = nullptr; + return nullptr; +} + +Message *ProtocolProto::ToMessage(const SerialBuffer *inBuff, int &outErrorNo, bool onlyMsgHeader) +{ + if (inBuff == nullptr) { + outErrorNo = -E_INVALID_ARGS; + return nullptr; + } + Message *outMsg = new (std::nothrow) Message(); + if (outMsg == nullptr) { + outErrorNo = -E_OUT_OF_MEMORY; + return nullptr; + } + int errCode = DeSerializeMessage(inBuff, outMsg, onlyMsgHeader); + if (errCode != E_OK && errCode != -E_NOT_REGISTER) { + LOGE("[Proto][ToMessage] DeSerialize Fail, errCode=%d.", errCode); + outErrorNo = errCode; + delete outMsg; + outMsg = nullptr; + return nullptr; + } + // If messageId not register in this software version, we return errCode and the Message without an object. + outErrorNo = errCode; + return outMsg; +} + +SerialBuffer *ProtocolProto::BuildEmptyFrameForVersionNegotiate(int &outErrorNo) +{ + SerialBuffer *buffer = new (std::nothrow) SerialBuffer(); + if (buffer == nullptr) { + outErrorNo = -E_OUT_OF_MEMORY; + return nullptr; + } + + // Empty frame has no payload, only header + int errCode = buffer->AllocBufferByPayloadLength(0, GetCommLayerFrameHeaderLength()); + if (errCode != E_OK) { + LOGE("[Proto][BuildEmpty] Alloc Fail, errCode=%d.", errCode); + outErrorNo = errCode; + delete buffer; + buffer = nullptr; + return nullptr; + } + outErrorNo = E_OK; + return buffer; +} + +SerialBuffer *ProtocolProto::BuildFeedbackMessageFrame(const Message *inMsg, const LabelType &inLabel, + int &outErrorNo) +{ + std::shared_ptr extendHandle = nullptr; + SerialBuffer *buffer = ToSerialBuffer(inMsg, outErrorNo, extendHandle, true); + if (buffer == nullptr) { + // outErrorNo had already been set in ToSerialBuffer + return nullptr; + } + int errCode = ProtocolProto::SetDivergeHeader(buffer, inLabel); + if (errCode != E_OK) { + LOGE("[Proto][BuildFeedback] Set DivergeHeader fail, label=%s, errCode=%d.", VEC_TO_STR(inLabel), errCode); + outErrorNo = errCode; + delete buffer; + buffer = nullptr; + return nullptr; + } + outErrorNo = E_OK; + return buffer; +} + +SerialBuffer *ProtocolProto::BuildLabelExchange(uint64_t inDistinctValue, uint64_t inSequenceId, + const std::set &inLabels, int &outErrorNo) +{ + // Size of inLabels won't be too large. + // The upper layer code(inside this communicator module) guarantee that size of each Label equals COMM_LABEL_LENGTH + uint32_t payloadLen = LABEL_VER_LEN + DISTINCT_VALUE_LEN + SEQUENCE_ID_LEN + COMM_LABEL_COUNT_LEN + + inLabels.size() * COMM_LABEL_LENGTH; + SerialBuffer *buffer = new (std::nothrow) SerialBuffer(); + if (buffer == nullptr) { + outErrorNo = -E_OUT_OF_MEMORY; + return nullptr; + } + int errCode = buffer->AllocBufferByPayloadLength(payloadLen, GetCommLayerFrameHeaderLength()); + if (errCode != E_OK) { + LOGE("[Proto][BuildLabel] Alloc Fail, errCode=%d.", errCode); + outErrorNo = errCode; + delete buffer; + buffer = nullptr; + return nullptr; + } + + auto payloadByteLen = buffer->GetWritableBytesForPayload(); + auto fieldPtr = reinterpret_cast(payloadByteLen.first); + *fieldPtr++ = HostToNet(static_cast(PROTOCOL_VERSION)); + *fieldPtr++ = HostToNet(inDistinctValue); + *fieldPtr++ = HostToNet(inSequenceId); + *fieldPtr++ = HostToNet(static_cast(inLabels.size())); + // Note: don't worry, memory length had been carefully calculated above + auto bytePtr = reinterpret_cast(fieldPtr); + for (auto &eachLabel : inLabels) { + for (auto &eachByte : eachLabel) { + *bytePtr++ = eachByte; + } + } + outErrorNo = E_OK; + return buffer; +} + +SerialBuffer *ProtocolProto::BuildLabelExchangeAck(uint64_t inDistinctValue, uint64_t inSequenceId, int &outErrorNo) +{ + uint32_t payloadLen = LABEL_VER_LEN + DISTINCT_VALUE_LEN + SEQUENCE_ID_LEN; + SerialBuffer *buffer = new (std::nothrow) SerialBuffer(); + if (buffer == nullptr) { + outErrorNo = -E_OUT_OF_MEMORY; + return nullptr; + } + int errCode = buffer->AllocBufferByPayloadLength(payloadLen, GetCommLayerFrameHeaderLength()); + if (errCode != E_OK) { + LOGE("[Proto][BuildLabelAck] Alloc Fail, errCode=%d.", errCode); + outErrorNo = errCode; + delete buffer; + buffer = nullptr; + return nullptr; + } + + auto payloadByteLen = buffer->GetWritableBytesForPayload(); + auto fieldPtr = reinterpret_cast(payloadByteLen.first); + *fieldPtr++ = HostToNet(static_cast(PROTOCOL_VERSION)); + *fieldPtr++ = HostToNet(inDistinctValue); + *fieldPtr++ = HostToNet(inSequenceId); + outErrorNo = E_OK; + return buffer; +} + +int ProtocolProto::SplitFrameIntoPacketsIfNeed(const SerialBuffer *inBuff, uint32_t inMtuSize, + std::vector, uint32_t>> &outPieces) +{ + auto bufferBytesLen = inBuff->GetReadOnlyBytesForEntireBuffer(); + if ((bufferBytesLen.second + inBuff->GetExtendHeadLength()) <= inMtuSize) { + return E_OK; + } + uint32_t modifyMtuSize = inMtuSize - inBuff->GetExtendHeadLength(); + // Do Fragmentaion! This function aims at calculate how many fragments to be split into. + auto frameBytesLen = inBuff->GetReadOnlyBytesForEntireFrame(); // Padding not in the range of fragmentation. + uint32_t lengthToSplit = frameBytesLen.second - sizeof(CommPhyHeader); // The former is always larger than latter. + // The inMtuSize pass from CommunicatorAggregator is large enough to be subtract by the latter two. + uint32_t maxFragmentLen = modifyMtuSize - sizeof(CommPhyHeader) - sizeof(CommPhyOptHeader); + // It can be proved that lengthToSplit is always larger than maxFragmentLen, so quotient won't be zero. + // The maxFragmentLen won't be zero and in fact large enough to make sure no precision loss during division + uint16_t quotient = lengthToSplit / maxFragmentLen; + uint32_t remainder = lengthToSplit % maxFragmentLen; + // Finally we get the fragCount for this frame + uint16_t fragCount = ((remainder == 0) ? quotient : (quotient + 1)); + // Get CommPhyHeader of this frame to be modified for each packets (Header in network endian) + auto oriPhyHeader = reinterpret_cast(frameBytesLen.first); + FrameFragmentInfo fragInfo = {inBuff->GetOringinalAddr(), inBuff->GetExtendHeadLength(), lengthToSplit, fragCount}; + return FrameFragmentation(frameBytesLen.first + sizeof(CommPhyHeader), fragInfo, *oriPhyHeader, outPieces); +} + +int ProtocolProto::AnalyzeSplitStructure(const ParseResult &inResult, uint32_t &outFragLen, uint32_t &outLastFragLen) +{ + uint32_t frameLen = inResult.GetFrameLen(); + uint16_t fragCount = inResult.GetFragCount(); + uint16_t fragNo = inResult.GetFragNo(); + + // Firstly: Check frameLen + if (frameLen <= sizeof(CommPhyHeader) || frameLen > MAX_FRAME_LEN) { + LOGE("[Proto][ParsePhyOpt] FrameLen=%u illegal.", frameLen); + return -E_PARSE_FAIL; + } + + // Secondly: Check fragCount and fragNo + uint32_t lengthBeSplit = frameLen - sizeof(CommPhyHeader); + if (fragCount == 0 || fragCount < MIN_FRAGMENT_COUNT || fragCount > lengthBeSplit || fragNo >= fragCount) { + LOGE("[Proto][ParsePhyOpt] FragCount=%u or fragNo=%u illegal.", fragCount, fragNo); + return -E_PARSE_FAIL; + } + + // Finally: Check length relation deeply + uint32_t quotient = lengthBeSplit / fragCount; + uint16_t remainder = lengthBeSplit % fragCount; + outFragLen = quotient; + outLastFragLen = quotient + remainder; + uint32_t thisFragLen = ((fragNo != fragCount - 1) ? outFragLen : outLastFragLen); // subtract by 1 for index + if (sizeof(CommPhyHeader) + sizeof(CommPhyOptHeader) + thisFragLen + + inResult.GetPaddingLen() != inResult.GetPacketLen()) { + LOGE("[Proto][ParsePhyOpt] Length Error: FrameLen=%u, FragCount=%u, fragNo=%u, PaddingLen=%u, PacketLen=%u", + frameLen, fragCount, fragNo, inResult.GetPaddingLen(), inResult.GetPacketLen()); + return -E_PARSE_FAIL; + } + + return E_OK; +} + +int ProtocolProto::CombinePacketIntoFrame(SerialBuffer *inFrame, const uint8_t *pktBytes, uint32_t pktLength, + uint32_t fragOffset, uint32_t fragLength) +{ + // inFrame is the destination, pktBytes and pktLength are the source, fragOffset and fragLength give the boundary + // Firstly: Check the length relation of source, even this check is not supposed to fail + if (sizeof(CommPhyHeader) + sizeof(CommPhyOptHeader) + fragLength > pktLength) { + return -E_LENGTH_ERROR; + } + // Secondly: Check the length relation of destination, even this check is not supposed to fail + auto frameByteLen = inFrame->GetWritableBytesForEntireFrame(); + if (sizeof(CommPhyHeader) + fragOffset + fragLength > frameByteLen.second) { + return -E_LENGTH_ERROR; + } + // Finally: Do Combination! + const uint8_t *srcByteHead = pktBytes + sizeof(CommPhyHeader) + sizeof(CommPhyOptHeader); + uint8_t *dstByteHead = frameByteLen.first + sizeof(CommPhyHeader) + fragOffset; + uint32_t dstLeftLen = frameByteLen.second - sizeof(CommPhyHeader) - fragOffset; + errno_t errCode = memcpy_s(dstByteHead, dstLeftLen, srcByteHead, fragLength); + if (errCode != EOK) { + return -E_SECUREC_ERROR; + } + return E_OK; +} + +int ProtocolProto::RegTransformFunction(uint32_t msgId, const TransformFunc &inFunc) +{ + if (msgIdMapFunc_.count(msgId) != 0) { + return -E_ALREADY_REGISTER; + } + if (!inFunc.computeFunc || !inFunc.serializeFunc || !inFunc.deserializeFunc) { + return -E_INVALID_ARGS; + } + msgIdMapFunc_[msgId] = inFunc; + return E_OK; +} + +void ProtocolProto::UnRegTransformFunction(uint32_t msgId) +{ + if (msgIdMapFunc_.count(msgId) != 0) { + msgIdMapFunc_.erase(msgId); + } +} + +int ProtocolProto::SetDivergeHeader(SerialBuffer *inBuff, const LabelType &inCommLabel) +{ + if (inBuff == nullptr) { + return -E_INVALID_ARGS; + } + auto headerByteLen = inBuff->GetWritableBytesForHeader(); + if (headerByteLen.second != GetAppLayerFrameHeaderLength()) { + return -E_INVALID_ARGS; + } + auto payloadByteLen = inBuff->GetReadOnlyBytesForPayload(); + + CommDivergeHeader divergeHeader; + divergeHeader.version = PROTOCOL_VERSION; + divergeHeader.reserved = 0; + divergeHeader.payLoadLen = payloadByteLen.second; + // The upper layer code(inside this communicator module) guarantee that size of inCommLabel equal COMM_LABEL_LENGTH + for (unsigned int i = 0; i < COMM_LABEL_LENGTH; i++) { + divergeHeader.commLabel[i] = inCommLabel[i]; + } + HeaderConverter::ConvertHostToNet(divergeHeader, divergeHeader); + + errno_t errCode = memcpy_s(headerByteLen.first + sizeof(CommPhyHeader), + headerByteLen.second - sizeof(CommPhyHeader), &divergeHeader, sizeof(CommDivergeHeader)); + if (errCode != EOK) { + return -E_SECUREC_ERROR; + } + return E_OK; +} + +namespace { +void FillPhyHeaderLenInfo(CommPhyHeader &header, uint32_t packetLen, uint64_t sum, uint8_t type, uint8_t paddingLen) +{ + header.packetLen = packetLen; + header.checkSum = sum; + header.packetType |= type; + header.paddingLen = paddingLen; +} +} + +int ProtocolProto::SetPhyHeader(SerialBuffer *inBuff, const PhyHeaderInfo &inInfo) +{ + if (inBuff == nullptr) { + return -E_INVALID_ARGS; + } + auto headerByteLen = inBuff->GetWritableBytesForHeader(); + if (headerByteLen.second < sizeof(CommPhyHeader)) { + return -E_INVALID_ARGS; + } + auto bufferByteLen = inBuff->GetReadOnlyBytesForEntireBuffer(); + auto frameByteLen = inBuff->GetReadOnlyBytesForEntireFrame(); + + uint32_t packetLen = bufferByteLen.second; + uint8_t paddingLen = static_cast(bufferByteLen.second - frameByteLen.second); + uint8_t packetType = PACKET_TYPE_NOT_FRAGMENTED; + if (inInfo.frameType != FrameType::INVALID_MAX_FRAME_TYPE) { + SetFrameType(packetType, inInfo.frameType); + } else { + return -E_INVALID_ARGS; + } + + CommPhyHeader phyHeader; + phyHeader.magic = MAGIC_CODE; + phyHeader.version = PROTOCOL_VERSION; + phyHeader.sourceId = inInfo.sourceId; + phyHeader.frameId = inInfo.frameId; + phyHeader.packetType = 0; + phyHeader.dbIntVer = DB_GLOBAL_VERSION; + FillPhyHeaderLenInfo(phyHeader, packetLen, 0, packetType, paddingLen); // Sum is calculated afterwards + HeaderConverter::ConvertHostToNet(phyHeader, phyHeader); + + errno_t retCode = memcpy_s(headerByteLen.first, headerByteLen.second, &phyHeader, sizeof(CommPhyHeader)); + if (retCode != EOK) { + return -E_SECUREC_ERROR; + } + + uint64_t sumResult = 0; + int errCode = CalculateXorSum(bufferByteLen.first + LENGTH_BEFORE_SUM_RANGE, + bufferByteLen.second - LENGTH_BEFORE_SUM_RANGE, sumResult); + if (errCode != E_OK) { + return -E_SUM_CALCULATE_FAIL; + } + + auto ptrPhyHeader = reinterpret_cast(headerByteLen.first); + ptrPhyHeader->checkSum = HostToNet(sumResult); + + return E_OK; +} + +int ProtocolProto::CheckAndParsePacket(const std::string &srcTarget, const uint8_t *bytes, uint32_t length, + ParseResult &outResult) +{ + if (bytes == nullptr || length > MAX_TOTAL_LEN) { + return -E_INVALID_ARGS; + } + int errCode = ParseCommPhyHeader(srcTarget, bytes, length, outResult); + if (errCode != E_OK) { + LOGE("[Proto][ParsePacket] Parse PhyHeader Fail, errCode=%d.", errCode); + return errCode; + } + + if (outResult.GetFrameTypeInfo() == FrameType::EMPTY) { + return E_OK; // Do nothing more for empty frame + } + + if (outResult.IsFragment()) { + errCode = ParseCommPhyOptHeader(bytes, length, outResult); + if (errCode != E_OK) { + LOGE("[Proto][ParsePacket] Parse CommPhyOptHeader Fail, errCode=%d.", errCode); + return errCode; + } + } else if (outResult.GetFrameTypeInfo() != FrameType::APPLICATION_MESSAGE) { + errCode = ParseCommLayerPayload(bytes, length, outResult); + if (errCode != E_OK) { + LOGE("[Proto][ParsePacket] Parse CommLayerPayload Fail, errCode=%d.", errCode); + return errCode; + } + } else { + errCode = ParseCommDivergeHeader(bytes, length, outResult); + if (errCode != E_OK) { + LOGE("[Proto][ParsePacket] Parse DivergeHeader Fail, errCode=%d.", errCode); + return errCode; + } + } + return E_OK; +} + +int ProtocolProto::CheckAndParseFrame(const SerialBuffer *inBuff, ParseResult &outResult) +{ + if (inBuff == nullptr || outResult.IsFragment()) { + return -E_INTERNAL_ERROR; + } + auto frameBytesLen = inBuff->GetReadOnlyBytesForEntireFrame(); + if (outResult.GetFrameTypeInfo() != FrameType::APPLICATION_MESSAGE) { + int errCode = ParseCommLayerPayload(frameBytesLen.first, frameBytesLen.second, outResult); + if (errCode != E_OK) { + LOGE("[Proto][ParseFrame] Parse CommLayerPayload Fail, errCode=%d.", errCode); + return errCode; + } + } else { + int errCode = ParseCommDivergeHeader(frameBytesLen.first, frameBytesLen.second, outResult); + if (errCode != E_OK) { + LOGE("[Proto][ParseFrame] Parse DivergeHeader Fail, errCode=%d.", errCode); + return errCode; + } + } + return E_OK; +} + +void ProtocolProto::DisplayPacketInformation(const uint8_t *bytes, uint32_t length) +{ + static std::map frameTypeStr{ + {FrameType::EMPTY, "EmptyFrame"}, + {FrameType::APPLICATION_MESSAGE, "AppLayerFrame"}, + {FrameType::COMMUNICATION_LABEL_EXCHANGE, "CommLayerFrame_LabelExchange"}, + {FrameType::COMMUNICATION_LABEL_EXCHANGE_ACK, "CommLayerFrame_LabelExchangeAck"}}; + + if (length < sizeof(CommPhyHeader)) { + return; + } + auto phyHeader = reinterpret_cast(bytes); + uint32_t frameId = NetToHost(phyHeader->frameId); + uint8_t pktType = NetToHost(phyHeader->packetType); + bool isFragment = ((pktType & PACKET_TYPE_FRAGMENTED) != 0); + FrameType frameType = GetFrameType(pktType); + if (frameType == FrameType::INVALID_MAX_FRAME_TYPE) { + LOGW("[Proto][Display] This is unrecognized frame, pktType=%" PRIu8 ".", pktType); + return; + } + if (isFragment) { + if (length < sizeof(CommPhyHeader) + sizeof(CommPhyOptHeader)) { + return; + } + auto phyOpt = reinterpret_cast(bytes + sizeof(CommPhyHeader)); + LOGI("[Proto][Display] This is %s, frameId=%u, frameLen=%u, fragCount=%u, fragNo=%u.", + frameTypeStr[frameType].c_str(), frameId, NetToHost(phyOpt->frameLen), + NetToHost(phyOpt->fragCount), NetToHost(phyOpt->fragNo)); + } else { + LOGI("[Proto][Display] This is %s, frameId=%u.", frameTypeStr[frameType].c_str(), frameId); + } +} + +int ProtocolProto::CalculateXorSum(const uint8_t *bytes, uint32_t length, uint64_t &outSum) +{ + if (length % sizeof(uint64_t) != 0) { + LOGE("[Proto][CalcuXorSum] Length=%d not multiple of eight.", length); + return -E_LENGTH_ERROR; + } + int count = length / sizeof(uint64_t); + auto array = reinterpret_cast(bytes); + outSum = 0; + for (int i = 0; i < count; i++) { + outSum ^= array[i]; + } + return E_OK; +} + +int ProtocolProto::CalculateDataSerializeLength(const Message *inMsg, uint32_t &outLength) +{ + uint32_t messageId = inMsg->GetMessageId(); + if (msgIdMapFunc_.count(messageId) == 0) { + LOGE("[Proto][CalcuDataSerialLen] Not registered for messageId=%u.", messageId); + return -E_NOT_REGISTER; + } + + TransformFunc function = msgIdMapFunc_[messageId]; + uint32_t serializeLen = function.computeFunc(inMsg); + uint32_t alignedLen = BYTE_8_ALIGN(serializeLen); + // Currently not allowed the upper module to send a message without data. Regard serializeLen zero as abnormal. + if (serializeLen == 0 || alignedLen > MAX_FRAME_LEN - GetLengthBeforeSerializedData()) { + LOGE("[Proto][CalcuDataSerialLen] Length too large, msgId=%u, serializeLen=%u, alignedLen=%u.", + messageId, serializeLen, alignedLen); + return -E_LENGTH_ERROR; + } + // Attention: return the serializeLen nor the alignedLen. Let SerialBuffer to deal with the padding + outLength = serializeLen; + return E_OK; +} + +int ProtocolProto::SerializeMessage(SerialBuffer *inBuff, const Message *inMsg) +{ + auto payloadByteLen = inBuff->GetWritableBytesForPayload(); + if (payloadByteLen.second < sizeof(MessageHeader)) { // For equal, only msgHeader case + LOGE("[Proto][Serialize] Length error, payload length=%u.", payloadByteLen.second); + return -E_LENGTH_ERROR; + } + uint32_t dataLen = payloadByteLen.second - sizeof(MessageHeader); + + auto messageHdr = reinterpret_cast(payloadByteLen.first); + messageHdr->version = inMsg->GetVersion(); + messageHdr->messageType = inMsg->GetMessageType(); + messageHdr->messageId = inMsg->GetMessageId(); + messageHdr->sessionId = inMsg->GetSessionId(); + messageHdr->sequenceId = inMsg->GetSequenceId(); + messageHdr->errorNo = inMsg->GetErrorNo(); + messageHdr->dataLen = dataLen; + HeaderConverter::ConvertHostToNet(*messageHdr, *messageHdr); + + if (dataLen == 0) { + // For zero dataLen, we don't need to serialize data part + return E_OK; + } + // If dataLen not zero, the TransformFunc of this messageId must exist, the caller's logic guarantee it + uint32_t messageId = inMsg->GetMessageId(); + TransformFunc function = msgIdMapFunc_[messageId]; + int result = function.serializeFunc(payloadByteLen.first + sizeof(MessageHeader), dataLen, inMsg); + if (result != E_OK) { + LOGE("[Proto][Serialize] SerializeFunc Fail, result=%d.", result); + return -E_SERIALIZE_ERROR; + } + return E_OK; +} + +int ProtocolProto::DeSerializeMessage(const SerialBuffer *inBuff, Message *inMsg, bool onlyMsgHeader) +{ + auto payloadByteLen = inBuff->GetReadOnlyBytesForPayload(); + // Check version before parse field + if (payloadByteLen.second < sizeof(uint16_t)) { + return -E_LENGTH_ERROR; + } + uint16_t version = NetToHost(*(reinterpret_cast(payloadByteLen.first))); + if (!IsSupportMessageVersion(version)) { + LOGE("[Proto][DeSerialize] Version=%u not support.", version); + return -E_VERSION_NOT_SUPPORT; + } + + if (payloadByteLen.second < sizeof(MessageHeader)) { + LOGE("[Proto][DeSerialize] Length error, payload length=%u.", payloadByteLen.second); + return -E_LENGTH_ERROR; + } + auto oriMsgHeader = reinterpret_cast(payloadByteLen.first); + MessageHeader messageHdr; + HeaderConverter::ConvertNetToHost(*oriMsgHeader, messageHdr); + inMsg->SetVersion(version); + inMsg->SetMessageType(messageHdr.messageType); + inMsg->SetMessageId(messageHdr.messageId); + inMsg->SetSessionId(messageHdr.sessionId); + inMsg->SetSequenceId(messageHdr.sequenceId); + inMsg->SetErrorNo(messageHdr.errorNo); + uint32_t dataLen = payloadByteLen.second - sizeof(MessageHeader); + if (dataLen != messageHdr.dataLen) { + LOGE("[Proto][DeSerialize] dataLen=%u, msgDataLen=%u.", dataLen, messageHdr.dataLen); + return -E_LENGTH_ERROR; + } + // It is better to check FeedbackMessage first and check onlyMsgHeader flag later + if (IsFeedbackErrorMessage(messageHdr.errorNo)) { + LOGI("[Proto][DeSerialize] Feedback Message with errorNo=%u.", messageHdr.errorNo); + return E_OK; + } + if (onlyMsgHeader || dataLen == 0) { // Do not need to deserialize data + return E_OK; + } + uint32_t messageId = inMsg->GetMessageId(); + if (msgIdMapFunc_.count(messageId) == 0) { + LOGE("[Proto][DeSerialize] Not register, messageId=%u.", messageId); + return -E_NOT_REGISTER; + } + TransformFunc function = msgIdMapFunc_[messageId]; + int result = function.deserializeFunc(payloadByteLen.first + sizeof(MessageHeader), dataLen, inMsg); + if (result != E_OK) { + LOGE("[Proto][DeSerialize] DeserializeFunc Fail, result=%d.", result); + return -E_DESERIALIZE_ERROR; + } + return E_OK; +} + +bool ProtocolProto::IsSupportMessageVersion(uint16_t version) +{ + return (version == MSG_VERSION_BASE || version == MSG_VERSION_EXT); +} + +bool ProtocolProto::IsFeedbackErrorMessage(uint32_t errorNo) +{ + return (errorNo == E_FEEDBACK_UNKNOWN_MESSAGE || errorNo == E_FEEDBACK_COMMUNICATOR_NOT_FOUND); +} + +int ProtocolProto::ParseCommPhyHeaderCheckMagicAndVersion(const uint8_t *bytes, uint32_t length) +{ + // At least magic and version should exist + if (length < sizeof(uint16_t) + sizeof(uint16_t)) { + LOGE("[Proto][ParsePhyCheckVer] Length of Bytes Error."); + return -E_LENGTH_ERROR; + } + auto fieldPtr = reinterpret_cast(bytes); + uint16_t magic = NetToHost(*fieldPtr++); + uint16_t version = NetToHost(*fieldPtr++); + + if (magic != MAGIC_CODE) { + LOGE("[Proto][ParsePhyCheckVer] MagicCode=%u Error.", magic); + return -E_PARSE_FAIL; + } + if (version != PROTOCOL_VERSION) { + LOGE("[Proto][ParsePhyCheckVer] Version=%u Error.", version); + return -E_VERSION_NOT_SUPPORT; + } + return E_OK; +} + +int ProtocolProto::ParseCommPhyHeaderCheckField(const std::string &srcTarget, const CommPhyHeader &phyHeader, + const uint8_t *bytes, uint32_t length) +{ + if (phyHeader.sourceId != Hash::HashFunc(srcTarget)) { + LOGE("[Proto][ParsePhyCheck] SourceId Error: inSourceId=%llu, srcTarget=%s{private}, hashId=%llu.", + ULL(phyHeader.sourceId), srcTarget.c_str(), ULL(Hash::HashFunc(srcTarget))); + return -E_PARSE_FAIL; + } + if (phyHeader.packetLen != length) { + LOGE("[Proto][ParsePhyCheck] PacketLen=%u Mismatch length=%u.", phyHeader.packetLen, length); + return -E_PARSE_FAIL; + } + if (phyHeader.paddingLen > MAX_PADDING_LEN) { + LOGE("[Proto][ParsePhyCheck] PaddingLen=%u Error.", phyHeader.paddingLen); + return -E_PARSE_FAIL; + } + if (sizeof(CommPhyHeader) + phyHeader.paddingLen > phyHeader.packetLen) { + LOGE("[Proto][ParsePhyCheck] PaddingLen Add PhyHeader Greater Than PacketLen."); + return -E_PARSE_FAIL; + } + uint64_t sumResult = 0; + int errCode = CalculateXorSum(bytes + LENGTH_BEFORE_SUM_RANGE, length - LENGTH_BEFORE_SUM_RANGE, sumResult); + if (errCode != E_OK) { + LOGE("[Proto][ParsePhyCheck] Calculate Sum Fail."); + return -E_SUM_CALCULATE_FAIL; + } + if (phyHeader.checkSum != sumResult) { + LOGE("[Proto][ParsePhyCheck] Sum Mismatch, checkSum=%llu, sumResult=%llu.", + ULL(phyHeader.checkSum), ULL(sumResult)); + return -E_SUM_MISMATCH; + } + return E_OK; +} + +int ProtocolProto::ParseCommPhyHeader(const std::string &srcTarget, const uint8_t *bytes, uint32_t length, + ParseResult &inResult) +{ + int errCode = ParseCommPhyHeaderCheckMagicAndVersion(bytes, length); + if (errCode != E_OK) { + LOGE("[Proto][ParsePhy] Check Magic And Version Fail."); + return errCode; + } + + if (length < sizeof(CommPhyHeader)) { + LOGE("[Proto][ParsePhy] Length of Bytes Error."); + return -E_PARSE_FAIL; + } + auto phyHeaderOri = reinterpret_cast(bytes); + CommPhyHeader phyHeader; + HeaderConverter::ConvertNetToHost(*phyHeaderOri, phyHeader); + errCode = ParseCommPhyHeaderCheckField(srcTarget, phyHeader, bytes, length); + if (errCode != E_OK) { + LOGE("[Proto][ParsePhy] Check Field Fail."); + return errCode; + } + + inResult.SetFrameId(phyHeader.frameId); + inResult.SetSourceId(phyHeader.sourceId); + inResult.SetPacketLen(phyHeader.packetLen); + inResult.SetPaddingLen(phyHeader.paddingLen); + inResult.SetDbVersion(phyHeader.dbIntVer); + if ((phyHeader.packetType & PACKET_TYPE_FRAGMENTED) != 0) { + inResult.SetFragmentFlag(true); + } // FragmentFlag default is false + FrameType frameType = GetFrameType(phyHeader.packetType); + if (frameType == FrameType::INVALID_MAX_FRAME_TYPE) { + LOGW("[Proto][ParsePhy] Unrecognized frame, pktType=%u.", phyHeader.packetType); + return -E_FRAME_TYPE_NOT_SUPPORT; + } + inResult.SetFrameTypeInfo(frameType); + return E_OK; +} + +int ProtocolProto::ParseCommPhyOptHeader(const uint8_t *bytes, uint32_t length, ParseResult &inResult) +{ + if (length < sizeof(CommPhyHeader) + sizeof(CommPhyOptHeader)) { + LOGE("[Proto][ParsePhyOpt] Length of Bytes Error."); + return -E_LENGTH_ERROR; + } + auto headerOri = reinterpret_cast(bytes + sizeof(CommPhyHeader)); + CommPhyOptHeader phyOptHeader; + HeaderConverter::ConvertNetToHost(*headerOri, phyOptHeader); + + // Check of CommPhyOptHeader field will be done in the procedure of FrameCombiner + inResult.SetFrameLen(phyOptHeader.frameLen); + inResult.SetFragCount(phyOptHeader.fragCount); + inResult.SetFragNo(phyOptHeader.fragNo); + return E_OK; +} + +int ProtocolProto::ParseCommDivergeHeader(const uint8_t *bytes, uint32_t length, ParseResult &inResult) +{ + // Check version before parse field + if (length < sizeof(CommPhyHeader) + sizeof(uint16_t)) { + return -E_LENGTH_ERROR; + } + uint16_t version = NetToHost(*(reinterpret_cast(bytes + sizeof(CommPhyHeader)))); + if (version != PROTOCOL_VERSION) { + LOGE("[Proto][ParseDiverge] Version=%" PRIu16 " not support.", version); + return -E_VERSION_NOT_SUPPORT; + } + + if (length < sizeof(CommPhyHeader) + sizeof(CommDivergeHeader)) { + LOGE("[Proto][ParseDiverge] Length of Bytes Error."); + return -E_PARSE_FAIL; + } + auto headerOri = reinterpret_cast(bytes + sizeof(CommPhyHeader)); + CommDivergeHeader divergeHeader; + HeaderConverter::ConvertNetToHost(*headerOri, divergeHeader); + if (sizeof(CommPhyHeader) + sizeof(CommDivergeHeader) + divergeHeader.payLoadLen + + inResult.GetPaddingLen() != inResult.GetPacketLen()) { + LOGE("[Proto][ParseDiverge] Total Length Mismatch."); + return -E_PARSE_FAIL; + } + inResult.SetPayloadLen(divergeHeader.payLoadLen); + inResult.SetCommLabel(LabelType(std::begin(divergeHeader.commLabel), std::end(divergeHeader.commLabel))); + return E_OK; +} + +int ProtocolProto::ParseCommLayerPayload(const uint8_t *bytes, uint32_t length, ParseResult &inResult) +{ + if (inResult.GetFrameTypeInfo() == FrameType::COMMUNICATION_LABEL_EXCHANGE_ACK) { + int errCode = ParseLabelExchangeAck(bytes, length, inResult); + if (errCode != E_OK) { + LOGE("[Proto][ParseCommPayload] Total Length Mismatch."); + return errCode; + } + } else { + int errCode = ParseLabelExchange(bytes, length, inResult); + if (errCode != E_OK) { + LOGE("[Proto][ParseCommPayload] Total Length Mismatch."); + return errCode; + } + } + return E_OK; +} + +int ProtocolProto::ParseLabelExchange(const uint8_t *bytes, uint32_t length, ParseResult &inResult) +{ + // Check version at very first + if (length < sizeof(CommPhyHeader) + LABEL_VER_LEN) { + return -E_LENGTH_ERROR; + } + auto fieldPtr = reinterpret_cast(bytes + sizeof(CommPhyHeader)); + uint64_t version = NetToHost(*fieldPtr++); + if (version != PROTOCOL_VERSION) { + LOGE("[Proto][ParseLabel] Version=%llu not support.", ULL(version)); + return -E_VERSION_NOT_SUPPORT; + } + + // Version, DistinctValue, SequenceId and CommLabelCount field must be exist. + if (length < sizeof(CommPhyHeader) + LABEL_VER_LEN + DISTINCT_VALUE_LEN + SEQUENCE_ID_LEN + COMM_LABEL_COUNT_LEN) { + LOGE("[Proto][ParseLabel] Length of Bytes Error."); + return -E_LENGTH_ERROR; + } + uint64_t distinctValue = NetToHost(*fieldPtr++); + inResult.SetLabelExchangeDistinctValue(distinctValue); + uint64_t sequenceId = NetToHost(*fieldPtr++); + inResult.SetLabelExchangeSequenceId(sequenceId); + uint64_t commLabelCount = NetToHost(*fieldPtr++); + if (length < commLabelCount || (UINT32_MAX / COMM_LABEL_LENGTH) < commLabelCount) { + LOGE("[Proto][ParseLabel] commLabelCount=%llu invalid.", ULL(commLabelCount)); + return -E_PARSE_FAIL; + } + // commLabelCount is expected to be not very large + if (length < sizeof(CommPhyHeader) + LABEL_VER_LEN + DISTINCT_VALUE_LEN + SEQUENCE_ID_LEN + COMM_LABEL_COUNT_LEN + + commLabelCount * COMM_LABEL_LENGTH) { + LOGE("[Proto][ParseLabel] Length of Bytes Error, commLabelCount=%llu", ULL(commLabelCount)); + return -E_LENGTH_ERROR; + } + + // Get each commLabel + std::set commLabels; + auto bytePtr = reinterpret_cast(fieldPtr); + for (uint64_t i = 0; i < commLabelCount; i++) { + // the length is checked just above + LabelType commLabel(bytePtr + i * COMM_LABEL_LENGTH, bytePtr + (i + 1) * COMM_LABEL_LENGTH); + if (commLabels.count(commLabel) != 0) { + LOGW("[Proto][ParseLabel] Duplicate Label Detected, commLabel=%s.", VEC_TO_STR(commLabel)); + } else { + commLabels.insert(commLabel); + } + } + inResult.SetLatestCommLabels(commLabels); + return E_OK; +} + +int ProtocolProto::ParseLabelExchangeAck(const uint8_t *bytes, uint32_t length, ParseResult &inResult) +{ + // Check version at very first + if (length < sizeof(CommPhyHeader) + LABEL_VER_LEN) { + return -E_LENGTH_ERROR; + } + auto fieldPtr = reinterpret_cast(bytes + sizeof(CommPhyHeader)); + uint64_t version = NetToHost(*fieldPtr++); + if (version != PROTOCOL_VERSION) { + LOGE("[Proto][ParseLabelAck] Version=%llu not support.", ULL(version)); + return -E_VERSION_NOT_SUPPORT; + } + + if (length < sizeof(CommPhyHeader) + LABEL_VER_LEN + DISTINCT_VALUE_LEN + SEQUENCE_ID_LEN) { + LOGE("[Proto][ParseLabelAck] Length of Bytes Error."); + return -E_LENGTH_ERROR; + } + uint64_t distinctValue = NetToHost(*fieldPtr++); + inResult.SetLabelExchangeDistinctValue(distinctValue); + uint64_t sequenceId = NetToHost(*fieldPtr++); + inResult.SetLabelExchangeSequenceId(sequenceId); + return E_OK; +} + +// Note: framePhyHeader is in network endian +// This function aims at calculating and preparing each part of each packets +int ProtocolProto::FrameFragmentation(const uint8_t *splitStartBytes, const FrameFragmentInfo &fragmentInfo, + const CommPhyHeader &framePhyHeader, std::vector, uint32_t>> &outPieces) +{ + // It can be guaranteed that fragCount >= 2 and also won't be too large + if (fragmentInfo.fragCount < MIN_FRAGMENT_COUNT) { + return -E_INVALID_ARGS; + } + outPieces.resize(fragmentInfo.fragCount); // Note: should use resize other than reserve + uint32_t quotient = fragmentInfo.splitLength / fragmentInfo.fragCount; + uint16_t remainder = fragmentInfo.splitLength % fragmentInfo.fragCount; + uint16_t fragNo = 0; // Fragment index start from 0 + uint32_t byteOffset = 0; + + for (auto &entry : outPieces) { + // subtract 1 for index + uint32_t pieceFragLen = (fragNo != fragmentInfo.fragCount - 1) ? quotient : (quotient + remainder); + uint32_t alignedFragLen = BYTE_8_ALIGN(pieceFragLen); // Add padding length + uint32_t pieceTotalLen = alignedFragLen + sizeof(CommPhyHeader) + sizeof(CommPhyOptHeader); + + // Since exception is disabled, we have to check the vector size to assure that memory is truly allocated + entry.first.resize(pieceTotalLen + fragmentInfo.extendHeadSize); // Note: should use resize other than reserve + if (entry.first.size() != (pieceTotalLen + fragmentInfo.extendHeadSize)) { + LOGE("[Proto][FrameFrag] Resize failed for length=%u", pieceTotalLen); + return -E_OUT_OF_MEMORY; + } + + CommPhyHeader pktPhyHeader; + HeaderConverter::ConvertNetToHost(framePhyHeader, pktPhyHeader); // Restore to host endian + + // The sum value need to be recalculated, and the packet is fragmented. + // The alignedFragLen is always larger than pieceFragLen + FillPhyHeaderLenInfo(pktPhyHeader, pieceTotalLen, 0, PACKET_TYPE_FRAGMENTED, alignedFragLen - pieceFragLen); + HeaderConverter::ConvertHostToNet(pktPhyHeader, pktPhyHeader); + + CommPhyOptHeader pktPhyOptHeader = {static_cast(fragmentInfo.splitLength + sizeof(CommPhyHeader)), + fragmentInfo.fragCount, fragNo}; + HeaderConverter::ConvertHostToNet(pktPhyOptHeader, pktPhyOptHeader); + int err; + FragmentPacket packet; + uint8_t *ptrPacket = &(entry.first[0]); + if (fragmentInfo.extendHeadSize > 0) { + packet = {ptrPacket, fragmentInfo.extendHeadSize}; + err = FillFragmentPacketExtendHead(fragmentInfo.oringinalBytesAddr, fragmentInfo.extendHeadSize, packet); + if (err != E_OK) { + return err; + } + ptrPacket += fragmentInfo.extendHeadSize; + } + packet = {ptrPacket, static_cast(entry.first.size()) - fragmentInfo.extendHeadSize}; + err = FillFragmentPacket(pktPhyHeader, pktPhyOptHeader, splitStartBytes + byteOffset, + pieceFragLen, packet); + entry.second = fragmentInfo.extendHeadSize; + if (err != E_OK) { + LOGE("[Proto][FrameFrag] Fill packet fail, fragCount=%" PRIu16 ", fragNo=%" PRIu16, fragmentInfo.fragCount, + fragNo); + return err; + } + + fragNo++; + byteOffset += pieceFragLen; + } + + return E_OK; +} + +int ProtocolProto::FillFragmentPacketExtendHead(uint8_t *headBytesAddr, uint32_t headLen, FragmentPacket &outPacket) +{ + if (headLen > outPacket.leftLength) { + LOGE("[Proto][FrameFrag] headLen less than leftLength"); + return -E_INVALID_ARGS; + } + errno_t retCode = memcpy_s(outPacket.ptrPacket, outPacket.leftLength, headBytesAddr, headLen); + if (retCode != EOK) { + LOGE("memcpy error:%d", retCode); + return -E_SECUREC_ERROR; + } + return E_OK; +} + +// Note: phyHeader and phyOptHeader is in network endian +int ProtocolProto::FillFragmentPacket(const CommPhyHeader &phyHeader, const CommPhyOptHeader &phyOptHeader, + const uint8_t *fragBytes, uint32_t fragLen, FragmentPacket &outPacket) +{ + if (outPacket.leftLength == 0) { + return -E_INVALID_ARGS; + } + uint8_t *ptrPacket = outPacket.ptrPacket; + uint32_t leftLength = outPacket.leftLength; + + // leftLength is guaranteed to be no smaller than the sum of phyHeaderLen + phyOptHeaderLen + fragLen + // So, there will be no redundant check during subtraction + errno_t retCode = memcpy_s(ptrPacket, leftLength, &phyHeader, sizeof(CommPhyHeader)); + if (retCode != EOK) { + return -E_SECUREC_ERROR; + } + ptrPacket += sizeof(CommPhyHeader); + leftLength -= sizeof(CommPhyHeader); + + retCode = memcpy_s(ptrPacket, leftLength, &phyOptHeader, sizeof(CommPhyOptHeader)); + if (retCode != EOK) { + return -E_SECUREC_ERROR; + } + ptrPacket += sizeof(CommPhyOptHeader); + leftLength -= sizeof(CommPhyOptHeader); + + retCode = memcpy_s(ptrPacket, leftLength, fragBytes, fragLen); + if (retCode != EOK) { + return -E_SECUREC_ERROR; + } + + // Calculate sum and set sum field + uint64_t sumResult = 0; + int errCode = CalculateXorSum(outPacket.ptrPacket + LENGTH_BEFORE_SUM_RANGE, + outPacket.leftLength - LENGTH_BEFORE_SUM_RANGE, sumResult); + if (errCode != E_OK) { + return -E_SUM_CALCULATE_FAIL; + } + auto ptrPhyHeader = reinterpret_cast(outPacket.ptrPacket); + if (ptrPhyHeader == nullptr) { + return -E_INVALID_ARGS; + } + ptrPhyHeader->checkSum = HostToNet(sumResult); + + return E_OK; +} + +int ProtocolProto::GetExtendHeadDataSize(std::shared_ptr &extendHandle, uint32_t &headSize) +{ + if (extendHandle != nullptr) { + DBStatus status = extendHandle->GetHeadDataSize(headSize); + if (status != DBStatus::OK) { + LOGI("[Proto][ToSerial] get head data size failed,not permit to send"); + return -E_FEEDBACK_COMMUNICATOR_NOT_FOUND; + } + if (headSize > SerialBuffer::MAX_EXTEND_HEAD_LENGTH || headSize != BYTE_8_ALIGN(headSize)) { + LOGI("[Proto][ToSerial] head data size is larger than 512 or not 8 byte align"); + return -E_FEEDBACK_COMMUNICATOR_NOT_FOUND; + } + return E_OK; + } + return E_OK; +} + +int ProtocolProto::FillExtendHeadDataIfNeed(std::shared_ptr &extendHandle, SerialBuffer *buffer, + uint32_t headSize) +{ + if (extendHandle != nullptr && headSize > 0) { + if (buffer == nullptr) { + return -E_INVALID_ARGS; + } + DBStatus status = extendHandle->FillHeadData(buffer->GetOringinalAddr(), headSize, + buffer->GetSize() + headSize); + if (status != DBStatus::OK) { + LOGI("[Proto][ToSerial] fill head data failed"); + return -E_FEEDBACK_COMMUNICATOR_NOT_FOUND; + } + return E_OK; + } + return E_OK; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/communicator/src/protocol_proto.h b/mock/distributeddb/communicator/src/protocol_proto.h new file mode 100644 index 00000000..64ce126c --- /dev/null +++ b/mock/distributeddb/communicator/src/protocol_proto.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 PROTOCOLPROTO_H +#define PROTOCOLPROTO_H + +#include +#include +#include "message.h" +#include "frame_header.h" +#include "parse_result.h" +#include "serial_buffer.h" +#include "message_transform.h" +#include "communicator_type_define.h" +#include "iprocess_communicator.h" + +namespace DistributedDB { +struct PhyHeaderInfo { + uint64_t sourceId; + uint32_t frameId; + FrameType frameType; +}; + +struct FrameFragmentInfo { + uint8_t *oringinalBytesAddr; + uint32_t extendHeadSize; + uint32_t splitLength; + uint16_t fragCount; +}; + +struct FragmentPacket { + uint8_t *ptrPacket; + uint32_t leftLength; +}; + +class ProtocolProto { +public: + // For application layer frame + static uint32_t GetAppLayerFrameHeaderLength(); + static uint32_t GetLengthBeforeSerializedData(); + + // For communication layer frame + static uint32_t GetCommLayerFrameHeaderLength(); + + // For handling application layer message. Return a heap object. + static SerialBuffer *ToSerialBuffer(const Message *inMsg, int &outErrorNo, + std::shared_ptr &extendHandle, bool onlyMsgHeader = false); + static Message *ToMessage(const SerialBuffer *inBuff, int &outErrorNo, bool onlyMsgHeader = false); + + // For handling communication layer frame. Return a heap object. + static SerialBuffer *BuildEmptyFrameForVersionNegotiate(int &outErrorNo); + static SerialBuffer *BuildFeedbackMessageFrame(const Message *inMsg, const LabelType &inLabel, int &outErrorNo); + static SerialBuffer *BuildLabelExchange(uint64_t inDistinctValue, uint64_t inSequenceId, + const std::set &inLabels, int &outErrorNo); + static SerialBuffer *BuildLabelExchangeAck(uint64_t inDistinctValue, uint64_t inSequenceId, int &outErrorNo); + + // Return E_OK if no error happened. outPieces.size equal zero means not split, in this case, use ori buff. + static int SplitFrameIntoPacketsIfNeed(const SerialBuffer *inBuff, uint32_t inMtuSize, + std::vector, uint32_t>> &outPieces); + static int AnalyzeSplitStructure(const ParseResult &inResult, uint32_t &outFragLen, uint32_t &outLastFragLen); + + // inFrame is the destination, pktBytes and pktLength are the source, fragOffset and fragLength give the boundary + static int CombinePacketIntoFrame(SerialBuffer *inFrame, const uint8_t *pktBytes, uint32_t pktLength, + uint32_t fragOffset, uint32_t fragLength); + + // Must not be called in multi-thread + // Return E_ALREADY_REGISTER if msgId is already registered + // Return E_INVALID_ARGS if member of inFunc not all valid + static int RegTransformFunction(uint32_t msgId, const TransformFunc &inFunc); + + static void UnRegTransformFunction(uint32_t msgId); + + // For application layer frame. In send case. Focus on frame. + static int SetDivergeHeader(SerialBuffer *inBuff, const LabelType &inCommLabel); + + // For both application and communication layer frame. In send case. Focus on frame. + static int SetPhyHeader(SerialBuffer *inBuff, const PhyHeaderInfo &inInfo); + + // In receive case, return error if parse fail. + static int CheckAndParsePacket(const std::string &srcTarget, const uint8_t *bytes, uint32_t length, + ParseResult &outResult); + + // The CommPhyHeader had already been parsed into outResult + static int CheckAndParseFrame(const SerialBuffer *inBuff, ParseResult &outResult); + + // Dfx method for helping debugging + static void DisplayPacketInformation(const uint8_t *bytes, uint32_t length); + + ProtocolProto() = delete; + ~ProtocolProto() = delete; +private: + static int CalculateXorSum(const uint8_t *bytes, uint32_t length, uint64_t &outSum); + + // For handling application layer message + static int CalculateDataSerializeLength(const Message *inMsg, uint32_t &outLength); + static int SerializeMessage(SerialBuffer *inBuff, const Message *inMsg); + static int DeSerializeMessage(const SerialBuffer *inBuff, Message *inMsg, bool onlyMsgHeader); + static bool IsSupportMessageVersion(uint16_t version); + static bool IsFeedbackErrorMessage(uint32_t errorNo); + + static int ParseCommPhyHeader(const std::string &srcTarget, const uint8_t *bytes, uint32_t length, + ParseResult &inResult); + static int ParseCommPhyHeaderCheckMagicAndVersion(const uint8_t *bytes, uint32_t length); + static int ParseCommPhyHeaderCheckField(const std::string &srcTarget, const CommPhyHeader &phyHeader, + const uint8_t *bytes, uint32_t length); + static int ParseCommPhyOptHeader(const uint8_t *bytes, uint32_t length, ParseResult &inResult); + static int ParseCommDivergeHeader(const uint8_t *bytes, uint32_t length, ParseResult &inResult); + static int ParseCommLayerPayload(const uint8_t *bytes, uint32_t length, ParseResult &inResult); + static int ParseLabelExchange(const uint8_t *bytes, uint32_t length, ParseResult &inResult); + static int ParseLabelExchangeAck(const uint8_t *bytes, uint32_t length, ParseResult &inResult); + + static int FrameFragmentation(const uint8_t *splitStartBytes, const FrameFragmentInfo &fragmentInfo, + const CommPhyHeader &framePhyHeader, std::vector, uint32_t>> &outPieces); + static int FillFragmentPacket(const CommPhyHeader &phyHeader, const CommPhyOptHeader &phyOptHeader, + const uint8_t *fragBytes, uint32_t fragLen, FragmentPacket &outPacket); + static int FillFragmentPacketExtendHead(uint8_t *headBytesAddr, uint32_t headLen, FragmentPacket &outPacket); + static int GetExtendHeadDataSize(std::shared_ptr &extendHandle, uint32_t &headSize); + static int FillExtendHeadDataIfNeed(std::shared_ptr &extendHandle, SerialBuffer *buffer, + uint32_t headSize); + + static std::map msgIdMapFunc_; +}; +} // namespace DistributedDB + +#endif // PROTOCOLPROTO_H diff --git a/mock/distributeddb/communicator/src/send_task_scheduler.cpp b/mock/distributeddb/communicator/src/send_task_scheduler.cpp new file mode 100644 index 00000000..25a60561 --- /dev/null +++ b/mock/distributeddb/communicator/src/send_task_scheduler.cpp @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "send_task_scheduler.h" +#include +#include "db_errno.h" +#include "log_print.h" +#include "serial_buffer.h" + +namespace DistributedDB { +// In current parameters, the scheduler will hold 160 MB in extreme situation. +// In actual runtime situation, the scheduler will hold no more than 100 MB. +static constexpr uint32_t MAX_CAPACITY = 67108864; // 64 M bytes +static constexpr uint32_t EXTRA_CAPACITY_FOR_NORMAL_PRIORITY = 33554432; // 32 M bytes +static constexpr uint32_t EXTRA_CAPACITY_FOR_HIGH_PRIORITY = 67108864; // 64 M bytes + +SendTaskScheduler::~SendTaskScheduler() +{ + Finalize(); +} + +void SendTaskScheduler::Initialize() +{ + priorityOrder_.clear(); + priorityOrder_.push_back(Priority::HIGH); + priorityOrder_.push_back(Priority::NORMAL); + priorityOrder_.push_back(Priority::LOW); + for (auto &prio : priorityOrder_) { + extraCapacityInByteByPrio_[prio] = 0; + taskCountByPrio_[prio] = 0; + taskDelayCountByPrio_[prio] = 0; + taskGroupByPrio_[prio] = TaskListByTarget(); + } + extraCapacityInByteByPrio_[Priority::NORMAL] = EXTRA_CAPACITY_FOR_NORMAL_PRIORITY; + extraCapacityInByteByPrio_[Priority::HIGH] = EXTRA_CAPACITY_FOR_HIGH_PRIORITY; +} + +void SendTaskScheduler::Finalize() +{ + while (GetTotalTaskCount() != 0) { + SendTask task; + SendTaskInfo taskInfo; + int errCode = ScheduleOutSendTask(task, taskInfo); + if (errCode != E_OK) { + LOGE("[Scheduler][Final] INTERNAL ERROR."); + break; // Not possible to happen + } + LOGW("[Scheduler][Finalize] dstTarget=%s{private}, delayFlag=%d, taskPrio=%d", task.dstTarget.c_str(), + taskInfo.delayFlag, static_cast(taskInfo.taskPrio)); + FinalizeLastScheduleTask(); + } +} + +int SendTaskScheduler::AddSendTaskIntoSchedule(const SendTask &inTask, Priority inPrio) +{ + std::lock_guard overallLockGuard(overallMutex_); + if (curTotalSizeByByte_ >= MAX_CAPACITY + extraCapacityInByteByPrio_[inPrio]) { + return -E_CONTAINER_FULL; + } + + uint32_t taskSizeByByte = inTask.buffer->GetSize(); + curTotalSizeByByte_ += taskSizeByByte; + curTotalSizeByTask_++; + if (policyMap_.count(inTask.dstTarget) == 0) { + policyMap_[inTask.dstTarget] = TargetPolicy::NO_DELAY; + } + if (policyMap_[inTask.dstTarget] == TargetPolicy::DELAY) { + delayTaskCount_++; + taskDelayCountByPrio_[inPrio]++; + } + + taskCountByPrio_[inPrio]++; + taskOrderByPrio_[inPrio].push_back(inTask.dstTarget); + taskGroupByPrio_[inPrio][inTask.dstTarget].push_back(inTask); + return E_OK; +} + +int SendTaskScheduler::ScheduleOutSendTask(SendTask &outTask) +{ + SendTaskInfo taskInfo; + int errCode = ScheduleOutSendTask(outTask, taskInfo); + if (errCode == E_OK) { + LOGI("[Scheduler][OutTask] dstTarget=%s{private}, delayFlag=%d, taskPrio=%d", outTask.dstTarget.c_str(), + taskInfo.delayFlag, static_cast(taskInfo.taskPrio)); + } + return errCode; +} + +int SendTaskScheduler::ScheduleOutSendTask(SendTask &outTask, SendTaskInfo &outTaskInfo) +{ + std::lock_guard overallLockGuard(overallMutex_); + if (curTotalSizeByTask_ == 0) { + return -E_CONTAINER_EMPTY; + } + + if (delayTaskCount_ == curTotalSizeByTask_) { + // Tasks are all in delay status + int errCode = ScheduleDelayTask(outTask, outTaskInfo); + if (errCode == E_OK) { + // Update last schedule location + lastScheduleTarget_ = outTask.dstTarget; + lastSchedulePriority_ = outTaskInfo.taskPrio; + scheduledFlag_ = true; + } + return errCode; + } else { + // There are some tasks not in delay status + int errCode = ScheduleNoDelayTask(outTask, outTaskInfo); + if (errCode == E_OK) { + // Update last schedule location + lastScheduleTarget_ = outTask.dstTarget; + lastSchedulePriority_ = outTaskInfo.taskPrio; + scheduledFlag_ = true; + } + return errCode; + } +} + +int SendTaskScheduler::FinalizeLastScheduleTask() +{ + std::lock_guard overallLockGuard(overallMutex_); + if (curTotalSizeByTask_ == 0) { + return -E_CONTAINER_EMPTY; + } + if (!scheduledFlag_) { + return -E_NOT_PERMIT; + } + + // Retrieve last scheduled task + SendTask task = taskGroupByPrio_[lastSchedulePriority_][lastScheduleTarget_].front(); + + bool isFullBefore = (curTotalSizeByByte_ >= MAX_CAPACITY); + uint32_t taskSize = task.buffer->GetSize(); + curTotalSizeByByte_ -= taskSize; + bool isFullAfter = (curTotalSizeByByte_ >= MAX_CAPACITY); + + curTotalSizeByTask_--; + taskCountByPrio_[lastSchedulePriority_]--; + if (policyMap_[lastScheduleTarget_] == TargetPolicy::DELAY) { + delayTaskCount_--; + taskDelayCountByPrio_[lastSchedulePriority_]--; + } + + for (auto iter = taskOrderByPrio_[lastSchedulePriority_].begin(); + iter != taskOrderByPrio_[lastSchedulePriority_].end(); ++iter) { + if (*iter == lastScheduleTarget_) { + taskOrderByPrio_[lastSchedulePriority_].erase(iter); + break; + } + } + + taskGroupByPrio_[lastSchedulePriority_][lastScheduleTarget_].pop_front(); + delete task.buffer; + task.buffer = nullptr; + scheduledFlag_ = false; + + if (isFullBefore && !isFullAfter) { + return -E_CONTAINER_FULL_TO_NOTFULL; + } + if (curTotalSizeByTask_ == 0) { + return -E_CONTAINER_NOTEMPTY_TO_EMPTY; + } + if (curTotalSizeByTask_ == delayTaskCount_) { + return -E_CONTAINER_ONLY_DELAY_TASK; + } + + return E_OK; +} + +int SendTaskScheduler::DelayTaskByTarget(const std::string &inTarget) +{ + std::lock_guard overallLockGuard(overallMutex_); + if (policyMap_.count(inTarget) == 0) { + LOGE("[Scheduler][DelayTask] Not found inTarget=%s{private}", inTarget.c_str()); + return -E_NOT_FOUND; + } + if (policyMap_[inTarget] == TargetPolicy::DELAY) { + return E_OK; + } + + policyMap_[inTarget] = TargetPolicy::DELAY; + for (auto &prio : priorityOrder_) { + size_t count = taskGroupByPrio_[prio][inTarget].size(); + taskDelayCountByPrio_[prio] += static_cast(count); + delayTaskCount_ += static_cast(count); + } + return E_OK; +} + +int SendTaskScheduler::NoDelayTaskByTarget(const std::string &inTarget) +{ + std::lock_guard overallLockGuard(overallMutex_); + if (policyMap_.count(inTarget) == 0) { + LOGE("[Scheduler][NoDelayTask] Not found inTarget=%s{private}", inTarget.c_str()); + return -E_NOT_FOUND; + } + if (policyMap_[inTarget] == TargetPolicy::NO_DELAY) { + return E_OK; + } + + policyMap_[inTarget] = TargetPolicy::NO_DELAY; + for (auto &prio : priorityOrder_) { + size_t count = taskGroupByPrio_[prio][inTarget].size(); + // Logic guarantee that former not smaller than latter + taskDelayCountByPrio_[prio] -= static_cast(count); + delayTaskCount_ -= static_cast(count); + } + return E_OK; +} + +uint32_t SendTaskScheduler::GetTotalTaskCount() const +{ + std::lock_guard overallLockGuard(overallMutex_); + return curTotalSizeByTask_; +} + +uint32_t SendTaskScheduler::GetNoDelayTaskCount() const +{ + std::lock_guard overallLockGuard(overallMutex_); + // delayTaskCount_ never greater than curTotalSizeByTask_ + return curTotalSizeByTask_ - delayTaskCount_; +} + +int SendTaskScheduler::ScheduleDelayTask(SendTask &outTask, SendTaskInfo &outTaskInfo) +{ + for (auto &prio : priorityOrder_) { + if (taskCountByPrio_[prio] == 0) { + // No task of this priority + continue; + } + // Logic guarantee that lists access below will not be empty + std::string dstTarget = taskOrderByPrio_[prio].front(); + outTask = taskGroupByPrio_[prio][dstTarget].front(); + outTaskInfo.delayFlag = true; + outTaskInfo.taskPrio = prio; + return E_OK; + } + LOGE("[Scheduler][ScheduleDelay] INTERNAL ERROR : NO TASK."); + return -E_INTERNAL_ERROR; +} + +int SendTaskScheduler::ScheduleNoDelayTask(SendTask &outTask, SendTaskInfo &outTaskInfo) +{ + for (auto &prio : priorityOrder_) { + if (taskCountByPrio_[prio] == 0 || taskCountByPrio_[prio] == taskDelayCountByPrio_[prio]) { + // No no_delay_task of this priority + continue; + } + // Logic guarantee that lists accessed below will not be empty + std::string dstTarget; + bool findFlag = false; // Not necessary in fact + for (auto iter = taskOrderByPrio_[prio].begin(); iter != taskOrderByPrio_[prio].end(); ++iter) { + // Logic guarantee that there is at least one target in orderList that is NO_DELAY + dstTarget = *iter; + if (policyMap_[dstTarget] == TargetPolicy::NO_DELAY) { + findFlag = true; + break; + } + } + if (!findFlag) { + LOGE("[Scheduler][ScheduleNoDelay] INTERNAL ERROR : NO_DELAY NOT FOUND."); + return -E_INTERNAL_ERROR; + } + + outTask = taskGroupByPrio_[prio][dstTarget].front(); + outTaskInfo.delayFlag = false; + outTaskInfo.taskPrio = prio; + return E_OK; + } + LOGE("[Scheduler][ScheduleNoDelay] INTERNAL ERROR : NO TASK."); + return -E_INTERNAL_ERROR; +} +} diff --git a/mock/distributeddb/communicator/src/serial_buffer.cpp b/mock/distributeddb/communicator/src/serial_buffer.cpp new file mode 100644 index 00000000..4fbcd89a --- /dev/null +++ b/mock/distributeddb/communicator/src/serial_buffer.cpp @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "serial_buffer.h" +#include +#include "securec.h" +#include "db_errno.h" +#include "communicator_type_define.h" +#include "log_print.h" + +namespace DistributedDB { +SerialBuffer::~SerialBuffer() +{ + if (!isExternalStackMemory_ && oringinalBytes_ != nullptr) { + delete[] oringinalBytes_; + } + oringinalBytes_ = nullptr; + bytes_ = nullptr; + externalBytes_ = nullptr; +} + +void SerialBuffer::SetExtendHeadLength(uint32_t extendHeaderLen) +{ + extendHeadLen_ = extendHeaderLen; +} + +uint32_t SerialBuffer::GetExtendHeadLength() const +{ + return extendHeadLen_; +} + +// In case buffer be directly send out, so padding is needed +int SerialBuffer::AllocBufferByPayloadLength(uint32_t inPayloadLen, uint32_t inHeaderLen) +{ + if (oringinalBytes_ != nullptr || bytes_ != nullptr || externalBytes_ != nullptr) { + return -E_NOT_PERMIT; + } + + payloadLen_ = inPayloadLen; + headerLen_ = inHeaderLen; + totalLen_ = BYTE_8_ALIGN(payloadLen_ + headerLen_); + paddingLen_ = totalLen_ - payloadLen_ - headerLen_; + if (totalLen_ == 0 || totalLen_ > MAX_TOTAL_LEN) { + return -E_INVALID_ARGS; + } + oringinalBytes_ = new (std::nothrow) uint8_t[totalLen_ + extendHeadLen_]; + if (oringinalBytes_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + bytes_ = oringinalBytes_ + extendHeadLen_; + return E_OK; +} + +// In case assemble fragment to frame, so no padding is needed, using frameLen as inTotalLen +int SerialBuffer::AllocBufferByTotalLength(uint32_t inTotalLen, uint32_t inHeaderLen) +{ + if (bytes_ != nullptr || externalBytes_ != nullptr) { + return -E_NOT_PERMIT; + } + if (inTotalLen == 0 || inTotalLen > MAX_TOTAL_LEN || inTotalLen < inHeaderLen) { + return -E_INVALID_ARGS; + } + + totalLen_ = inTotalLen; + headerLen_ = inHeaderLen; + payloadLen_ = totalLen_ - headerLen_; + paddingLen_ = 0; + bytes_ = new (std::nothrow) uint8_t[inTotalLen]; + if (bytes_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + oringinalBytes_ = bytes_; + return E_OK; +} + +// In case directly received, inTotalLen not include the padding, using frameLen as inTotalLen +int SerialBuffer::SetExternalBuff(const uint8_t *buff, uint32_t inTotalLen, uint32_t inHeaderLen) +{ + if (bytes_ != nullptr || externalBytes_ != nullptr) { + return -E_NOT_PERMIT; + } + if (buff == nullptr || inTotalLen == 0 || inTotalLen > MAX_TOTAL_LEN || inTotalLen < inHeaderLen) { + return -E_INVALID_ARGS; + } + + totalLen_ = inTotalLen; + headerLen_ = inHeaderLen; + payloadLen_ = totalLen_ - headerLen_; + paddingLen_ = 0; + isExternalStackMemory_ = true; + externalBytes_ = buff; + return E_OK; +} + +SerialBuffer *SerialBuffer::Clone(int &outErrorNo) +{ + SerialBuffer *twinBuffer = new (std::nothrow) SerialBuffer(); + if (twinBuffer == nullptr) { + outErrorNo = -E_OUT_OF_MEMORY; + return nullptr; + } + if (bytes_ == nullptr) { + twinBuffer->bytes_ = nullptr; + } else { + twinBuffer->bytes_ = new (std::nothrow) uint8_t[totalLen_]; + if (twinBuffer->bytes_ == nullptr) { + outErrorNo = -E_OUT_OF_MEMORY; + delete twinBuffer; + twinBuffer = nullptr; + return nullptr; + } + errno_t errCode = memcpy_s(twinBuffer->bytes_, totalLen_, bytes_, totalLen_); + if (errCode != EOK) { + outErrorNo = -E_SECUREC_ERROR; + delete twinBuffer; + twinBuffer = nullptr; + return nullptr; + } + } + twinBuffer->oringinalBytes_ = twinBuffer->bytes_; + twinBuffer->externalBytes_ = externalBytes_; + twinBuffer->totalLen_ = totalLen_; + twinBuffer->headerLen_ = headerLen_; + twinBuffer->payloadLen_ = payloadLen_; + twinBuffer->paddingLen_ = paddingLen_; + twinBuffer->isExternalStackMemory_ = isExternalStackMemory_; + twinBuffer->extendHeadLen_ = extendHeadLen_; + outErrorNo = E_OK; + return twinBuffer; +} + +int SerialBuffer::ConvertForCrossThread() +{ + if (externalBytes_ == nullptr) { + // No associated external stack memory. Do nothing and return E_OK. + return E_OK; + } + // Logic guarantee all the member value: isExternalStackMemory_ is true; bytes_ is nullptr; totalLen_ is correct. + bytes_ = new (std::nothrow) uint8_t[totalLen_]; + if (bytes_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + errno_t errCode = memcpy_s(bytes_, totalLen_, externalBytes_, totalLen_); + if (errCode != EOK) { + delete[] bytes_; + bytes_ = nullptr; + return -E_SECUREC_ERROR; + } + // Reset external related info + externalBytes_ = nullptr; + isExternalStackMemory_ = false; + oringinalBytes_ = bytes_; + extendHeadLen_ = 0; + return E_OK; +} + +uint32_t SerialBuffer::GetSize() const +{ + if (bytes_ == nullptr && externalBytes_ == nullptr) { + return 0; + } + return totalLen_; +} + +uint8_t *SerialBuffer::GetOringinalAddr() const +{ + return oringinalBytes_; +} + +std::pair SerialBuffer::GetWritableBytesForEntireBuffer() +{ + if (bytes_ == nullptr) { + return std::make_pair(nullptr, 0); + } else { + return std::make_pair(bytes_, totalLen_); + } +} + +std::pair SerialBuffer::GetWritableBytesForEntireFrame() +{ + if (bytes_ == nullptr) { + return std::make_pair(nullptr, 0); + } else { + return std::make_pair(bytes_, totalLen_ - paddingLen_); + } +} + +std::pair SerialBuffer::GetWritableBytesForHeader() +{ + if (bytes_ == nullptr) { + return std::make_pair(nullptr, 0); + } else { + return std::make_pair(bytes_, headerLen_); + } +} + +std::pair SerialBuffer::GetWritableBytesForPayload() +{ + if (bytes_ == nullptr) { + return std::make_pair(nullptr, 0); + } else { + return std::make_pair(bytes_ + headerLen_, payloadLen_); + } +} + +// For receive case, using Const Function +std::pair SerialBuffer::GetReadOnlyBytesForEntireBuffer() const +{ + if (isExternalStackMemory_) { + return std::make_pair(externalBytes_, totalLen_); + } else if (bytes_ != nullptr) { + return std::make_pair(bytes_, totalLen_); + } else { + return std::make_pair(nullptr, 0); + } +} + +std::pair SerialBuffer::GetReadOnlyBytesForEntireFrame() const +{ + if (isExternalStackMemory_) { + return std::make_pair(externalBytes_, totalLen_ - paddingLen_); + } else if (bytes_ != nullptr) { + return std::make_pair(bytes_, totalLen_ - paddingLen_); + } else { + return std::make_pair(nullptr, 0); + } +} + +std::pair SerialBuffer::GetReadOnlyBytesForHeader() const +{ + if (isExternalStackMemory_) { + return std::make_pair(externalBytes_, headerLen_); + } else if (bytes_ != nullptr) { + return std::make_pair(bytes_, headerLen_); + } else { + return std::make_pair(nullptr, 0); + } +} + +std::pair SerialBuffer::GetReadOnlyBytesForPayload() const +{ + if (isExternalStackMemory_) { + return std::make_pair(externalBytes_ + headerLen_, payloadLen_); + } else if (bytes_ != nullptr) { + return std::make_pair(bytes_ + headerLen_, payloadLen_); + } else { + return std::make_pair(nullptr, 0); + } +} +} // namespace DistributedDB diff --git a/mock/distributeddb/communicator/src/serial_buffer.h b/mock/distributeddb/communicator/src/serial_buffer.h new file mode 100644 index 00000000..01fb39a6 --- /dev/null +++ b/mock/distributeddb/communicator/src/serial_buffer.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SERIALBUFFER_H +#define SERIALBUFFER_H + +#include +#include +#include +#include "macro_utils.h" + +namespace DistributedDB { +class SerialBuffer { +public: + SerialBuffer() = default; // Default constructor must be explicitly provided due to DISABLE_COPY_ASSIGN_MOVE + ~SerialBuffer(); + + DISABLE_COPY_ASSIGN_MOVE(SerialBuffer); + + // will call the func before alloc buff, calculate the head len which is used for data service + void SetExtendHeadLength(uint32_t extendHeaderLen); + uint32_t GetExtendHeadLength() const; + // May be directly send out, so padding is needed + int AllocBufferByPayloadLength(uint32_t inPayloadLen, uint32_t inHeaderLen); + + // In case assemble fragment to frame, so no padding is needed, using frameLen as inTotalLen + int AllocBufferByTotalLength(uint32_t inTotalLen, uint32_t inHeaderLen); + + // In case directly received, inTotalLen not include the padding, using frameLen as inTotalLen + int SetExternalBuff(const uint8_t *buff, uint32_t inTotalLen, uint32_t inHeaderLen); + + // Create a SerialBuffer that has a independent bytes_ and point to the same externalBytes_ + SerialBuffer *Clone(int &outErrorNo); + + // After return E_OK, this SerialBuffer can cross thread. Do nothing indeed if it already able to cross thread. + int ConvertForCrossThread(); + + uint32_t GetSize() const; + + uint8_t *GetOringinalAddr() const; + + std::pair GetWritableBytesForEntireBuffer(); + std::pair GetWritableBytesForEntireFrame(); + std::pair GetWritableBytesForHeader(); + std::pair GetWritableBytesForPayload(); + + std::pair GetReadOnlyBytesForEntireBuffer() const; + std::pair GetReadOnlyBytesForEntireFrame() const; + std::pair GetReadOnlyBytesForHeader() const; + std::pair GetReadOnlyBytesForPayload() const; + + static const uint32_t MAX_EXTEND_HEAD_LENGTH = 512; +private: + uint8_t *oringinalBytes_ = nullptr; // all beytes start addr + uint8_t *bytes_ = nullptr; // distributeddb start addr + const uint8_t *externalBytes_ = nullptr; + uint32_t totalLen_ = 0; + uint32_t headerLen_ = 0; + uint32_t payloadLen_ = 0; + uint32_t paddingLen_ = 0; + // only apply message will use extend header + uint32_t extendHeadLen_ = 0; + bool isExternalStackMemory_ = false; +}; +} // namespace DistributedDB + +#endif // SERIALBUFFER_H diff --git a/mock/distributeddb/include/auto_launch_export.h b/mock/distributeddb/include/auto_launch_export.h new file mode 100644 index 00000000..7a1b86ce --- /dev/null +++ b/mock/distributeddb/include/auto_launch_export.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_AUTO_LAUNCH_EXPORT_H +#define DISTRIBUTEDDB_AUTO_LAUNCH_EXPORT_H + +#include "types_export.h" +#include "kv_store_observer.h" +#include "kv_store_nb_delegate.h" +#include "store_observer.h" + +namespace DistributedDB { +struct AutoLaunchOption { + bool createIfNecessary = true; + bool isEncryptedDb = false; + CipherType cipher = CipherType::DEFAULT; + CipherPassword passwd; + std::string schema; + bool createDirByStoreIdOnly = false; + std::string dataDir; + KvStoreObserver *observer = nullptr; + int conflictType = 0; + KvStoreNbConflictNotifier notifier; + SecurityOption secOption; + bool isNeedIntegrityCheck = false; + bool isNeedRmCorruptedDb = false; + bool isNeedCompressOnSync = false; + uint8_t compressionRate = 100; // valid in [1, 100]. + bool isAutoSync = true; + StoreObserver *storeObserver = nullptr; + bool syncDualTupleMode = false; // communicator label use dualTuple hash or not +}; + +struct AutoLaunchParam { + std::string userId; + std::string appId; + std::string storeId; + AutoLaunchOption option; + AutoLaunchNotifier notifier; + std::string path; +}; + +using AutoLaunchRequestCallback = std::function; +} // namespace DistributedDB + +#endif // DISTRIBUTEDDB_AUTO_LAUNCH_EXPORT_H diff --git a/mock/distributeddb/include/query.h b/mock/distributeddb/include/query.h new file mode 100644 index 00000000..b1243316 --- /dev/null +++ b/mock/distributeddb/include/query.h @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_QUERY_H +#define DISTRIBUTEDDB_QUERY_H + +#include +#include +#include +#include + +#include "query_expression.h" +#include "types_export.h" + +namespace DistributedDB { +class GetQueryInfo; +class Query { +public: + + // Do not support concurrent use of query objects + DB_API static Query Select(); + DB_API static Query Select(const std::string &tableName); + + template + DB_API Query &EqualTo(const std::string &field, const T &value) + { + FieldValue fieldValue; + QueryValueType type = GetFieldTypeAndValue(value, fieldValue); + ExecuteCompareOperation(QueryObjType::EQUALTO, field, type, fieldValue); + return *this; + } + + template + DB_API Query &NotEqualTo(const std::string &field, const T &value) + { + FieldValue fieldValue; + QueryValueType type = GetFieldTypeAndValue(value, fieldValue); + ExecuteCompareOperation(QueryObjType::NOT_EQUALTO, field, type, fieldValue); + return *this; + } + + template + DB_API Query &GreaterThan(const std::string &field, const T &value) + { + FieldValue fieldValue; + QueryValueType type = GetFieldTypeAndValue(value, fieldValue); + ExecuteCompareOperation(QueryObjType::GREATER_THAN, field, type, fieldValue); + return *this; + } + + template + DB_API Query &LessThan(const std::string &field, const T &value) + { + FieldValue fieldValue; + QueryValueType type = GetFieldTypeAndValue(value, fieldValue); + ExecuteCompareOperation(QueryObjType::LESS_THAN, field, type, fieldValue); + return *this; + } + + template + DB_API Query &GreaterThanOrEqualTo(const std::string &field, const T &value) + { + FieldValue fieldValue; + QueryValueType type = GetFieldTypeAndValue(value, fieldValue); + ExecuteCompareOperation(QueryObjType::GREATER_THAN_OR_EQUALTO, field, type, fieldValue); + return *this; + } + + template + DB_API Query &LessThanOrEqualTo(const std::string &field, const T &value) + { + FieldValue fieldValue; + QueryValueType type = GetFieldTypeAndValue(value, fieldValue); + ExecuteCompareOperation(QueryObjType::LESS_THAN_OR_EQUALTO, field, type, fieldValue); + return *this; + } + + DB_API Query &OrderBy(const std::string &field, bool isAsc = true); + + DB_API Query &Limit(int number, int offset = 0); + + DB_API Query &Like(const std::string &field, const std::string &value); + + DB_API Query &NotLike(const std::string &field, const std::string &value); + + template + DB_API Query &In(const std::string &field, const std::vector &values) + { + std::vector fieldValues; + QueryValueType type = QueryValueType::VALUE_TYPE_NULL; + for (const auto &value : values) { + FieldValue fieldValue; + type = GetFieldTypeAndValue(value, fieldValue); + fieldValues.push_back(fieldValue); + } + + ExecuteCompareOperation(QueryObjType::IN, field, type, fieldValues); + return *this; + } + + template + DB_API Query &NotIn(const std::string &field, const std::vector &values) + { + std::vector fieldValues; + QueryValueType type = QueryValueType::VALUE_TYPE_NULL; + for (const auto &value : values) { + FieldValue fieldValue; + type = GetFieldTypeAndValue(value, fieldValue); + fieldValues.push_back(fieldValue); + } + + ExecuteCompareOperation(QueryObjType::NOT_IN, field, type, fieldValues); + return *this; + } + + DB_API Query &IsNull(const std::string &field); + + DB_API Query &And(); + + DB_API Query &Or(); + + DB_API Query &IsNotNull(const std::string &field); + + DB_API Query &BeginGroup(); + + DB_API Query &EndGroup(); + + DB_API Query &PrefixKey(const std::vector &key); + + DB_API Query &SuggestIndex(const std::string &indexName); + + DB_API Query &InKeys(const std::set &keys); + + friend class GetQueryInfo; + DB_API ~Query() = default; + DB_API Query() = default; +private: + explicit Query(const std::string &tableName); + + DB_SYMBOL void ExecuteCompareOperation(QueryObjType operType, const std::string &field, + const QueryValueType type, const FieldValue &fieldValue); + DB_SYMBOL void ExecuteCompareOperation(QueryObjType operType, const std::string &field, + const QueryValueType type, const std::vector &fieldValue); + + template + QueryValueType GetFieldTypeAndValue(const T &queryValue, FieldValue &fieldValue) + { + return GetQueryValueType::GetFieldTypeAndValue(queryValue, fieldValue); + } + + QueryExpression queryExpression_; +}; +} // namespace DistributedDB +#endif // DISTRIBUTEDDB_QUERY_H diff --git a/mock/distributeddb/include/query_expression.h b/mock/distributeddb/include/query_expression.h new file mode 100644 index 00000000..0ee1467e --- /dev/null +++ b/mock/distributeddb/include/query_expression.h @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_QUERY_EXPRESSION_H +#define DISTRIBUTEDDB_QUERY_EXPRESSION_H + +#include +#include +#include +#include + +#include "types_export.h" + +namespace DistributedDB { +enum class QueryValueType: int32_t { + VALUE_TYPE_INVALID = -1, + VALUE_TYPE_NULL, + VALUE_TYPE_BOOL, + VALUE_TYPE_INTEGER, + VALUE_TYPE_LONG, + VALUE_TYPE_DOUBLE, + VALUE_TYPE_STRING, +}; + +// value will const, it will influence query object id +// use high pos bit to distinguish operator type +enum class QueryObjType : uint32_t { + OPER_ILLEGAL = 0x0000, + EQUALTO = 0x0101, + NOT_EQUALTO, + GREATER_THAN, + LESS_THAN, + GREATER_THAN_OR_EQUALTO, + LESS_THAN_OR_EQUALTO, + LIKE = 0x0201, + NOT_LIKE, + IS_NULL, + IS_NOT_NULL, + IN = 0x0301, + NOT_IN, + QUERY_BY_KEY_PREFIX = 0x0401, + BEGIN_GROUP = 0x0501, + END_GROUP, + AND = 0x0601, + OR, + LIMIT = 0x0701, + ORDERBY, + SUGGEST_INDEX = 0x0801, + IN_KEYS = 0x0901, +}; + +struct QueryObjNode { + QueryObjType operFlag = QueryObjType::OPER_ILLEGAL; + std::string fieldName {}; + QueryValueType type = QueryValueType::VALUE_TYPE_INVALID; + std::vector fieldValue = {}; + bool IsValid() + { + return operFlag != QueryObjType::OPER_ILLEGAL && + type != QueryValueType::VALUE_TYPE_INVALID; + } +}; + +class QueryExpression final { +public: + DB_SYMBOL QueryExpression(); + DB_SYMBOL ~QueryExpression() {}; + + void EqualTo(const std::string &field, const QueryValueType type, const FieldValue &value); + + void NotEqualTo(const std::string &field, const QueryValueType type, const FieldValue &value); + + void GreaterThan(const std::string &field, const QueryValueType type, const FieldValue &value); + + void LessThan(const std::string &field, const QueryValueType type, const FieldValue &value); + + void GreaterThanOrEqualTo(const std::string &field, const QueryValueType type, const FieldValue &value); + + void LessThanOrEqualTo(const std::string &field, const QueryValueType type, const FieldValue &value); + + void OrderBy(const std::string &field, bool isAsc); + + void Limit(int number, int offset); + + void Like(const std::string &field, const std::string &value); + void NotLike(const std::string &field, const std::string &value); + + void In(const std::string &field, const QueryValueType type, const std::vector &values); + void NotIn(const std::string &field, const QueryValueType type, const std::vector &values); + + void IsNull(const std::string &field); + void IsNotNull(const std::string &field); + + void And(); + + void Or(); + + void BeginGroup(); + + void EndGroup(); + + void Reset(); + + void QueryByPrefixKey(const std::vector &key); + + void QueryBySuggestIndex(const std::string &indexName); + + std::vector GetPreFixKey() const; + + void SetTableName(const std::string &tableName); + const std::string &GetTableName(); + bool IsTableNameSpecified() const; + + std::string GetSuggestIndex() const; + + const std::set &GetKeys() const; + void InKeys(const std::set &keys); + + const std::list &GetQueryExpression(); + + void SetErrFlag(bool flag); + bool GetErrFlag(); + +private: + void AssemblyQueryInfo(const QueryObjType queryOperType, const std::string &field, + const QueryValueType type, const std::vector &value, bool isNeedFieldPath); + + std::list queryInfo_; + bool errFlag_ = true; + std::vector prefixKey_; + std::string suggestIndex_; + std::string tableName_; + bool isTableNameSpecified_; + std::set keys_; +}; + +// specialize for double +class GetQueryValueType { +public: + static QueryValueType GetFieldTypeAndValue(const double &queryValue, FieldValue &fieldValue) + { + fieldValue.doubleValue = queryValue; + return QueryValueType::VALUE_TYPE_DOUBLE; + } + static QueryValueType GetFieldTypeAndValue(const int &queryValue, FieldValue &fieldValue) + { + fieldValue.integerValue = queryValue; + return QueryValueType::VALUE_TYPE_INTEGER; + } + static QueryValueType GetFieldTypeAndValue(const int64_t &queryValue, FieldValue &fieldValue) + { + fieldValue.longValue = queryValue; + return QueryValueType::VALUE_TYPE_LONG; + } + static QueryValueType GetFieldTypeAndValue(const bool &queryValue, FieldValue &fieldValue) + { + fieldValue.boolValue = queryValue; + return QueryValueType::VALUE_TYPE_BOOL; + } + static QueryValueType GetFieldTypeAndValue(const std::string &queryValue, FieldValue &fieldValue) + { + fieldValue.stringValue = queryValue; + return QueryValueType::VALUE_TYPE_STRING; + } + static QueryValueType GetFieldTypeAndValue(const char *queryValue, FieldValue &fieldValue) + { + if (queryValue == nullptr) { + return QueryValueType::VALUE_TYPE_STRING; + } + fieldValue.stringValue = queryValue; + return QueryValueType::VALUE_TYPE_STRING; + } +}; +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/include/types_export.h b/mock/distributeddb/include/types_export.h new file mode 100644 index 00000000..b084156c --- /dev/null +++ b/mock/distributeddb/include/types_export.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_TYPES_EXPORT_H +#define DISTRIBUTEDDB_TYPES_EXPORT_H + +#include +#include +#include +#include +#include + +namespace DistributedDB { +#ifdef _WIN32 + #ifdef DB_DLL_EXPORT + #define DB_API __declspec(dllexport) + #else + #define DB_API + #endif +#else + #define DB_API __attribute__ ((visibility ("default"))) +#endif + +#define DB_SYMBOL DB_API + +using Key = std::vector; +using Value = std::vector; + +struct Entry { + Key key; + Value value; +}; + +enum class CipherType { + DEFAULT, + AES_256_GCM, // AES-256-GCM +}; + +class CipherPassword final { +public: + enum ErrorCode { + OK = 0, + OVERSIZE, + INVALID_INPUT, + SECUREC_ERROR, + }; + + DB_API CipherPassword(); + DB_API ~CipherPassword(); + + DB_API bool operator==(const CipherPassword &input) const; + DB_API bool operator!=(const CipherPassword &input) const; + + DB_API size_t GetSize() const; + DB_API const uint8_t *GetData() const; + DB_API int SetValue(const uint8_t *inputData, size_t inputSize); + DB_API int Clear(); + +private: + static const size_t MAX_PASSWORD_SIZE = 128; + uint8_t data_[MAX_PASSWORD_SIZE] = {UCHAR_MAX}; + size_t size_ = 0; +}; + +using PragmaData = void *; + +struct PragmaEntryDeviceIdentifier { + Key key; + bool origDevice = true; + std::string deviceIdentifier; +}; + +using KvStoreNbPublishAction = std::function; + +struct PragmaDeviceIdentifier { + std::string deviceID; + std::string deviceIdentifier; +}; + +enum WipePolicy { + RETAIN_STALE_DATA = 1, // remote stale data will be retained in syncing when remote db rebuiled. + WIPE_STALE_DATA // remote stale data will be wiped when in syncing remote db rebuiled. +}; + +// We don't parse, read or modify the array type, so there are not a corresponding array value +// The leaf object is empty, an internal object always composed by other type values. +struct FieldValue { + union { + bool boolValue; + int32_t integerValue; + int64_t longValue = 0; + double doubleValue; + }; + std::string stringValue; +}; + +enum PermissionCheckFlag { + CHECK_FLAG_SEND = 1, // send + CHECK_FLAG_RECEIVE = 2, // receive + CHECK_FLAG_AUTOSYNC = 4, // autosync flag + CHECK_FLAG_SPONSOR = 8, // sync sponsor +}; + +using PermissionCheckCallback = std::function; + +using PermissionCheckCallbackV2 = std::function; + +using StoreStatusNotifier = std::function; // status, 1: online, 0: offline + +using SyncActivationCheckCallback = std::function; + +enum AutoLaunchStatus { + WRITE_OPENED = 1, + WRITE_CLOSED = 2, + INVALID_PARAM = 3, // AutoLaunchRequestCallback, if param check failed +}; + +using AutoLaunchNotifier = std::function; + +enum SecurityLabel : int { + INVALID_SEC_LABEL = -1, + NOT_SET = 0, + S0, + S1, + S2, + S3, + S4 +}; + +// security flag type +enum SecurityFlag : int { + INVALID_SEC_FLAG = -1, + ECE = 0, + SECE +}; + +struct SecurityOption { + int securityLabel = 0; // the securityLabel is the class of data sensitive, see enum SecurityLabel + int securityFlag = 0; // the securityFlag is the encryption method of the file only used for S3 like 0:ECE, 1:SECE + bool operator==(const SecurityOption &rhs) const + { + return securityLabel == rhs.securityLabel && securityFlag == rhs.securityFlag; + } +}; + +enum class ResultSetCacheMode : int { + CACHE_FULL_ENTRY = 0, // Ordinary mode efficient when sequential access, the default mode + CACHE_ENTRY_ID_ONLY = 1, // Special mode efficient when random access +}; + +struct RemotePushNotifyInfo { + std::string deviceId; +}; +using RemotePushFinishedNotifier = std::function; +using RemotePushFinisheNotifier = RemotePushFinishedNotifier; // To correct spelling errors in the previous version + +enum class CompressAlgorithm : uint8_t { + NONE = 0, + ZLIB = 1 +}; +} // namespace DistributedDB +#endif // DISTRIBUTEDDB_TYPES_EXPORT_H diff --git a/mock/distributeddb/interfaces/include/get_query_info.h b/mock/distributeddb/interfaces/include/get_query_info.h new file mode 100644 index 00000000..5adaf9d9 --- /dev/null +++ b/mock/distributeddb/interfaces/include/get_query_info.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_GET_QUERY_INFO_H +#define DISTRIBUTEDDB_GET_QUERY_INFO_H + +#include "query.h" + +namespace DistributedDB { +class GetQueryInfo { +public: + static QueryExpression GetQueryExpression(const Query &query) + { + return query.queryExpression_; + } +}; +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/interfaces/include/intercepted_data.h b/mock/distributeddb/interfaces/include/intercepted_data.h new file mode 100644 index 00000000..9d6fa2cf --- /dev/null +++ b/mock/distributeddb/interfaces/include/intercepted_data.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 INTERCEPTED_DATA_H +#define INTERCEPTED_DATA_H + +#include +#include "store_types.h" +#include "types_export.h" + +namespace DistributedDB { +struct KVEntry { + const Key &key; + const Value &value; +}; + +class InterceptedData { +public: + InterceptedData() {} + DB_API virtual ~InterceptedData() {} + + // Interface for getting the intercepted entries. + DB_API virtual std::vector GetEntries() = 0; + + // Interface for modifying key. Index is the index of GetEntries(). + DB_API virtual DBStatus ModifyKey(size_t index, const Key &newKey) = 0; + + // Interface for modifying value. Index is the index of GetEntries(). + DB_API virtual DBStatus ModifyValue(size_t index, const Value &newValue) = 0; +}; + +// The callback function works on the send data from device "sourceID" to device "targetID". +using PushDataInterceptor = std::function; +} // namespace DistributedDB +#endif // INTERCEPTED_DATA_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/include/iprocess_communicator.h b/mock/distributeddb/interfaces/include/iprocess_communicator.h new file mode 100644 index 00000000..5abbd306 --- /dev/null +++ b/mock/distributeddb/interfaces/include/iprocess_communicator.h @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 IPROCESSCOMMUNICATOR_H +#define IPROCESSCOMMUNICATOR_H + +#include +#include +#include +#include +#include +#include "store_types.h" + +namespace DistributedDB { +// The DeviceInfos may contain other fields(Can only be auxiliary information) besides identifier field in the future. +struct DeviceInfos { + std::string identifier; // An unique and fixed identifier representing a device, such as UUID. +}; + +struct ExtendInfo { + std::string appId; + std::string storeId; + std::string userId; + std::string dstTarget; +}; + +class ExtendHeaderHandle { +public: + ExtendHeaderHandle() {}; + virtual ~ExtendHeaderHandle() {}; + // headSize should be 8 byte align + // return OK and headSize = 0 if no need to fill Head Data + // return OK and headSize > 0 if permit sync and will call FillHeadData + // return NO_PERMISSION if not permit sync + virtual DBStatus GetHeadDataSize(uint32_t &headSize) + { + headSize = 0; + return OK; + }; + // return OK if fill data ok + // return not OK if fill data failed + virtual DBStatus FillHeadData(uint8_t *data, uint32_t headSize, uint32_t totalLen) + { + (void)data; + (void)headSize; + (void)totalLen; + return OK; + }; +}; + +// In OnDeviceChange, all field of devInfo should be valid, isOnline true for online and false for offline. +// The concept of online or offline: +// 1: Can be at the physical device level, which means the remote device can be visible and communicable by local device +// 2: Can also be at the process level, which means the same ProcessCommunicator(with same processLabel) had been +// started on the remote device and thus visible and communicable by this local ProcessCommunicator. +using OnDeviceChange = std::function; + +// In OnDataReceive, all field of srcDevInfo should be valid +using OnDataReceive = std::function; + +// For all functions with returnType DBStatus: +// return DBStatus::OK if successful, otherwise DBStatus::DB_ERROR if anything wrong. +// Additional information of reason why failed can be present in the log by the implementation. +// For "Get" or "Is" functions, implementation should notice that concurrent call is possible. +class IProcessCommunicator { +public: + // The distributeddb in one process can only use one ProcessCommunicator at the same time + // The ProcessCommunicator can only Start one processLabel at the same time + // The ProcessCommunicator can Start again after stop + // The processLabel should not be an empty string + virtual DBStatus Start(const std::string &processLabel) = 0; + + // The Stop should only be called after Start successfully + virtual DBStatus Stop() = 0; + + // The register function can be called anytime regardless of whether started or stopped. + // There will only be one callback at the same time for each function + // If register again, the latter callback replace the former callback. + // Register nullptr as callback to do unregister semantic. + // For concurrency security of implementation, there should be lock between register_operation and callback_event. + virtual DBStatus RegOnDeviceChange(const OnDeviceChange &callback) = 0; + virtual DBStatus RegOnDataReceive(const OnDataReceive &callback) = 0; + + // The SendData function should only be called after Start successfully + // Only the identifier field of dstDevInfo must be valid, no requirement for other field. + virtual DBStatus SendData(const DeviceInfos &dstDevInfo, const uint8_t *data, uint32_t length) = 0; + + // The GetMtuSize function can be called anytime regardless of whether started or stopped. + // The mtuSize should not less than 1K otherwise it will be regard as 1K. + // For run on OHOS, there is agreement that the mtuSize should be nearly 5M. + virtual uint32_t GetMtuSize() = 0; + + // The GetLocalDeviceInfos function should only be called after Start successfully + // All field of returned DeviceInfos must be valid, the identifier must not be empty and changed between time. + virtual DeviceInfos GetLocalDeviceInfos() = 0; + + // The GetRemoteOnlineDeviceInfosList function should only be called after Start successfully + // All field of returned DeviceInfos must be valid, should not contain duplicate device or local device + virtual std::vector GetRemoteOnlineDeviceInfosList() = 0; + + // The IsSameProcessLabelStartedOnPeerDevice function should only be called after Start successfully + // Only the identifier field of peerDevInfo must be valid, no requirement for other field. + // If the peer device is offline, then return false. + // If the peer device is online but no ProcessCommunicator with same processLabel had started on it, return false. + // If the peer device is online and ProcessCommunicator with same processLabel had started on it, return true. + virtual bool IsSameProcessLabelStartedOnPeerDevice(const DeviceInfos &peerDevInfo) = 0; + + virtual ~IProcessCommunicator() {}; + + // For ABI compatibility reason, temporarily place this method at last and offer a fake implementation. + // The valid mtuSize range from 1K to 5M, value beyond this range will be set to the upper or lower limit. + virtual uint32_t GetMtuSize(const DeviceInfos &devInfo) + { + if (devInfo.identifier.empty()) { + // Error case(would never happen actually) to avoid "unused-parameter" warning. + return 0; + } + return GetMtuSize(); + } + + // The valid timeout range from 5s to 60s, value beyond this range will be set to the upper or lower limit. + virtual uint32_t GetTimeout() + { + return 5 * 1000; // 5 * 1000ms + }; + + // The valid timeout range from 5s to 60s, value beyond this range will be set to the upper or lower limit. + virtual uint32_t GetTimeout(const DeviceInfos &devInfo) + { + if (devInfo.identifier.empty()) { + // Error case(would never happen actually) to avoid "unused-parameter" warning. + return 5 * 1000; // 5 * 1000ms + } + return GetTimeout(); + } + + virtual std::shared_ptr GetExtendHeaderHandle(const ExtendInfo ¶mInfo) + { + (void)paramInfo; + return nullptr; + } + // called after OnDataReceive + // return NO_PERMISSION while no need to handle the dataBuff if remote device userId is not mate with local userId + // return INVALID_FORMAT and headLength = 0 if data service can not deSerialize the buff + // return OK if deSerialize ok and get HeadLength/localUserId successfully + virtual DBStatus CheckAndGetDataHeadInfo(const uint8_t *data, uint32_t totalLen, uint32_t &headLength, + std::vector &userId) + { + (void)data; + (void)totalLen; + (void)userId; + headLength = 0; + return OK; + } +}; +} // namespace DistributedDB + +#endif // IPROCESSCOMMUNICATOR_H diff --git a/mock/distributeddb/interfaces/include/iprocess_system_api_adapter.h b/mock/distributeddb/interfaces/include/iprocess_system_api_adapter.h new file mode 100644 index 00000000..3a3b14de --- /dev/null +++ b/mock/distributeddb/interfaces/include/iprocess_system_api_adapter.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 IPROCESS_SYSTEM_API_ADAPTER_H +#define IPROCESS_SYSTEM_API_ADAPTER_H + +#include +#include +#include "store_types.h" + +namespace DistributedDB { +using OnAccessControlledEvent = std::function; + +// For all functions with returnType DBStatus: +// return DBStatus::OK if successful, otherwise DBStatus::DB_ERROR if anything wrong. +// Additional information of reason why failed can be present in the log by the implementation. +// For "Get" or "Is" functions, implementation should notice that concurrent call is possible. +// The distributeddb in one process can only use one ProcessSystemApiAdapter at the same time +class IProcessSystemApiAdapter { +public: + // Function used to register a AccessControlled listener, like screen locked. + // There will only be one callback at the same time for each function + // If register again, the latter callback replace the former callback. + // Register nullptr as callback to do unregister semantic. + // For concurrency security of implementation, there should be lock between register_operation and callback_event. + virtual DBStatus RegOnAccessControlledEvent(const OnAccessControlledEvent &callback) = 0; + + // Check is the access of this device in locked state + virtual bool IsAccessControlled() const = 0; + + // Set the SecurityOption to the targe filepath. + // If the filePath is a directory, All the files and directories in the filePath should be effective. + virtual DBStatus SetSecurityOption(const std::string &filePath, const SecurityOption &option) = 0; + + // Get the SecurityOption of the targe filepath. + virtual DBStatus GetSecurityOption(const std::string &filePath, SecurityOption &option) const = 0; + + // Check if the target device can save the data at the give sensitive class. + virtual bool CheckDeviceSecurityAbility(const std::string &devId, const SecurityOption &option) const = 0; + + virtual ~IProcessSystemApiAdapter() {}; +}; +} // namespace DistributedDB +#endif // IPROCESS_SYSTEM_API_ADAPTER_H diff --git a/mock/distributeddb/interfaces/include/kv_store_changed_data.h b/mock/distributeddb/interfaces/include/kv_store_changed_data.h new file mode 100644 index 00000000..65f84a41 --- /dev/null +++ b/mock/distributeddb/interfaces/include/kv_store_changed_data.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_CHANGED_DATA_H +#define KV_STORE_CHANGED_DATA_H + +#include +#include "store_types.h" + +namespace DistributedDB { +class KvStoreChangedData { +public: + KvStoreChangedData() {} + DB_API virtual ~KvStoreChangedData() {} + + // Interface for Getting the inserted, updated, delete entries. + DB_API virtual const std::list &GetEntriesInserted() const = 0; + + DB_API virtual const std::list &GetEntriesUpdated() const = 0; + + DB_API virtual const std::list &GetEntriesDeleted() const = 0; + + DB_API virtual bool IsCleared() const = 0; +}; +} // namespace DistributedDB + +#endif // KV_STORE_CHANGED_DATA diff --git a/mock/distributeddb/interfaces/include/kv_store_delegate.h b/mock/distributeddb/interfaces/include/kv_store_delegate.h new file mode 100644 index 00000000..5194ac45 --- /dev/null +++ b/mock/distributeddb/interfaces/include/kv_store_delegate.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_DELEGATE_H +#define KV_STORE_DELEGATE_H + +#include +#include + +#include "store_types.h" +#include "kv_store_observer.h" +#include "kv_store_snapshot_delegate.h" + +namespace DistributedDB { +class KvStoreDelegate { +public: + using ConflictResolution = std::function; + + struct Option { + bool createIfNecessary = true; + bool localOnly = false; + bool isEncryptedDb = false; + CipherType cipher = CipherType::DEFAULT; + CipherPassword passwd; + bool createDirByStoreIdOnly = false; + }; + + DB_API virtual ~KvStoreDelegate() {} + + // Used to Put a k-v pair to the kvstore. + // Return OK if operation is successful. + DB_API virtual DBStatus Put(const Key &key, const Value &value) = 0; + + // Used to Put a vector contains k-v pairs to the kvstore. + // Return OK if operation is successful.. + DB_API virtual DBStatus PutBatch(const std::vector &entries) = 0; + + // Delete a record with the given key. + // Return OK if operation is successful. + DB_API virtual DBStatus Delete(const Key &key) = 0; + + // Batch delete records with the given keys. + // Return OK if operation is successful. + DB_API virtual DBStatus DeleteBatch(const std::vector &keys) = 0; + + // Delete all record of the kvstore. + // Return OK if operation is successful. + DB_API virtual DBStatus Clear() = 0; + + // Return a storeId of the KvStore instance + DB_API virtual std::string GetStoreId() const = 0; + + // Get a snapshot of the kvstore. The observer is used to notify data changed, it can be null. + // Return value is DBStatus and KvStoreSnapshotDelegate*, these values will be passed to the callback. + DB_API virtual void GetKvStoreSnapshot(KvStoreObserver *observer, + const std::function &callback) = 0; + + // Release a snapshot, it will return OK if operation is successful. + DB_API virtual DBStatus ReleaseKvStoreSnapshot(KvStoreSnapshotDelegate *&snapshotDelegate) = 0; + + // Register a data change observer + DB_API virtual DBStatus RegisterObserver(KvStoreObserver *observer) = 0; + + // Unregister a data change observer + DB_API virtual DBStatus UnRegisterObserver(const KvStoreObserver *observer) = 0; + + // Start a transaction + DB_API virtual DBStatus StartTransaction() = 0; + + // Commit a transaction + DB_API virtual DBStatus Commit() = 0; + + // Rollback a transaction + DB_API virtual DBStatus Rollback() = 0; + + // Used to set the resolution policy for conflicts. + // Return OK if operation is successful. + DB_API virtual DBStatus SetConflictResolutionPolicy(ResolutionPolicyType type, + const ConflictResolution &resolution) = 0; + + // Used to rekey the database. + DB_API virtual DBStatus Rekey(const CipherPassword &password) = 0; + + // Special pragma interface, see PragmaCmd and PragmaData, + DB_API virtual DBStatus Pragma(PragmaCmd cmd, PragmaData ¶mData) = 0; + + // Empty passwords represent non-encrypted files. + // Export existing database files to a specified database file in the specified directory. + DB_API virtual DBStatus Export(const std::string &filePath, const CipherPassword &passwd) = 0; + + // Import the existing database files to the specified database file in the specified directory. + DB_API virtual DBStatus Import(const std::string &filePath, const CipherPassword &passwd) = 0; +}; +} // namespace DistributedDB + +#endif // KV_STORE_DELEGATE_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/include/kv_store_delegate_manager.h b/mock/distributeddb/interfaces/include/kv_store_delegate_manager.h new file mode 100644 index 00000000..69631d4d --- /dev/null +++ b/mock/distributeddb/interfaces/include/kv_store_delegate_manager.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_DELEGATE_MANAGER_H +#define KV_STORE_DELEGATE_MANAGER_H + +#include +#include +#include +#include + +#ifndef OMIT_MULTI_VER +#include "kv_store_delegate.h" +#endif +#include "kv_store_nb_delegate.h" +#include "store_types.h" +#include "iprocess_communicator.h" +#include "iprocess_system_api_adapter.h" +#include "auto_launch_export.h" + +namespace DistributedDB { +class KvStoreDelegateManager final { +public: + DB_API KvStoreDelegateManager(const std::string &appId, const std::string &userId); + DB_API ~KvStoreDelegateManager(); + + KvStoreDelegateManager(const KvStoreDelegateManager &) = delete; + KvStoreDelegateManager(KvStoreDelegateManager &&) = delete; + KvStoreDelegateManager &operator=(const KvStoreDelegateManager &) = delete; + KvStoreDelegateManager &operator=(KvStoreDelegateManager &&) = delete; + + // Used to set global config of the KvStores, such dataDir, return OK if set config success. + DB_API DBStatus SetKvStoreConfig(const KvStoreConfig &kvStoreConfig); + +#ifndef OMIT_MULTI_VER + // Used to open or create a KvStore. + // Return OK and a KvStoreDelegate* if there is no error. else return ERROR and nullptr; + DB_API void GetKvStore(const std::string &storeId, const KvStoreDelegate::Option &option, + const std::function &callback); +#endif + // Used to open or create a KvStore(Natural store). + // Suggest: Not to use encrypted database in S3 SECE access controlled; + // Warning: Access controlled prevents access to files so cannot verify passwords, + // So that a cacheDb with incorrect passwd will be created or opened and lose these data. + DB_API void GetKvStore(const std::string &storeId, const KvStoreNbDelegate::Option &option, + const std::function &callback); + +#ifndef OMIT_MULTI_VER + // Close a KvStore, return OK if close success. + DB_API DBStatus CloseKvStore(KvStoreDelegate *kvStore); +#endif + + DB_API DBStatus CloseKvStore(KvStoreNbDelegate *kvStore); + + // Used to delete a KvStore, return OK if delete success. + DB_API DBStatus DeleteKvStore(const std::string &storeId); + + // Get the database size. + DB_API DBStatus GetKvStoreDiskSize(const std::string &storeId, uint64_t &size); + + // Used to set the process userid and appId + DB_API static DBStatus SetProcessLabel(const std::string &appId, const std::string &userId); + + // Set process communicator. + DB_API static DBStatus SetProcessCommunicator(const std::shared_ptr &inCommunicator); + + DB_API static void SetKvStoreCorruptionHandler(const KvStoreCorruptionHandler &handler); + + // Get database directory by storeId + appId + userId + DB_API static DBStatus GetDatabaseDir(const std::string &storeId, const std::string &appId, + const std::string &userId, std::string &directory); + + // Get database directory by storeId + DB_API static DBStatus GetDatabaseDir(const std::string &storeId, std::string &directory); + + DB_API static DBStatus SetPermissionCheckCallback(const PermissionCheckCallback &callback); + + DB_API static DBStatus SetPermissionCheckCallback(const PermissionCheckCallbackV2 &callback); + + DB_API static DBStatus EnableKvStoreAutoLaunch(const std::string &userId, const std::string &appId, + const std::string &storeId, const AutoLaunchOption &option, const AutoLaunchNotifier ¬ifier); + + DB_API static DBStatus DisableKvStoreAutoLaunch(const std::string &userId, const std::string &appId, + const std::string &storeId); + + DB_API static void SetAutoLaunchRequestCallback(const AutoLaunchRequestCallback &callback); + + DB_API static std::string GetKvStoreIdentifier(const std::string &userId, const std::string &appId, + const std::string &storeId, bool syncDualTupleMode = false); + + DB_API static DBStatus SetProcessSystemAPIAdapter(const std::shared_ptr &adapter); + + DB_API static void SetStoreStatusNotifier(const StoreStatusNotifier ¬ifier); + + DB_API static DBStatus SetSyncActivationCheckCallback(const SyncActivationCheckCallback &callback); + + DB_API static DBStatus NotifyUserChanged(); +private: + + // Check if the dataDir is safe arg. + bool IsDataDirSafe(const std::string &dataDir, std::string &canonicalDir) const; + bool GetKvStoreParamCheck(const std::string &storeId, const KvStoreNbDelegate::Option &option, + const std::function &callback) const; + DBStatus SetObserverNotifier(KvStoreNbDelegate *kvStore, const KvStoreNbDelegate::Option &option); + + const std::string &GetKvStorePath() const; + static const std::string DEFAULT_PROCESS_APP_ID; + static std::mutex communicatorMutex_; + static std::shared_ptr processCommunicator_; + static std::mutex multiUserMutex_; + + KvStoreConfig kvStoreConfig_; + std::string appId_; + std::string userId_; + + mutable std::mutex mutex_; +}; +} // namespace DistributedDB + +#endif // KV_STORE_DELEGATE_MANAGER_H diff --git a/mock/distributeddb/interfaces/include/kv_store_errno.h b/mock/distributeddb/interfaces/include/kv_store_errno.h new file mode 100644 index 00000000..4cb150f5 --- /dev/null +++ b/mock/distributeddb/interfaces/include/kv_store_errno.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_ERRNO_H +#define KV_STORE_ERRNO_H + +#include "store_types.h" + +namespace DistributedDB { +// Transfer the db error code to the DBStatus. +DBStatus TransferDBErrno(int err); +}; +#endif \ No newline at end of file diff --git a/mock/distributeddb/interfaces/include/kv_store_nb_conflict_data.h b/mock/distributeddb/interfaces/include/kv_store_nb_conflict_data.h new file mode 100644 index 00000000..9ce4317d --- /dev/null +++ b/mock/distributeddb/interfaces/include/kv_store_nb_conflict_data.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_NB_CONFLICT_DATA_H +#define KV_STORE_NB_CONFLICT_DATA_H + +#include "store_types.h" + +namespace DistributedDB { +enum KvStoreNbConflictType { + CONFLICT_FOREIGN_KEY_ONLY = 0x01, // sync conflict for same origin dev + CONFIICT_FOREIGN_KEY_ONLY = CONFLICT_FOREIGN_KEY_ONLY, // sync conflict for same origin dev(compatible for mistake) + CONFLICT_FOREIGN_KEY_ORIG = 0x02, // sync conflict for different origin dev + CONFLICT_NATIVE_ALL = 0x0c, // native conflict. +}; + +class KvStoreNbConflictData { +public: + enum class ValueType { + OLD_VALUE = 0, + NEW_VALUE, + }; + + DB_API virtual ~KvStoreNbConflictData() {}; + + DB_API virtual KvStoreNbConflictType GetType() const = 0; + + DB_API virtual void GetKey(Key &key) const = 0; + + DB_API virtual DBStatus GetValue(ValueType type, Value &value) const = 0; + + DB_API virtual bool IsDeleted(ValueType type) const = 0; + + DB_API virtual bool IsNative(ValueType type) const = 0; +}; +} // namespace DistributedDB + +#endif // KV_STORE_NB_CONFLICT_DATA_H diff --git a/mock/distributeddb/interfaces/include/kv_store_nb_delegate.h b/mock/distributeddb/interfaces/include/kv_store_nb_delegate.h new file mode 100644 index 00000000..5a15e466 --- /dev/null +++ b/mock/distributeddb/interfaces/include/kv_store_nb_delegate.h @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_NB_DELEGATE_H +#define KV_STORE_NB_DELEGATE_H + +#include +#include +#include + +#include "store_types.h" +#include "kv_store_observer.h" +#include "kv_store_nb_conflict_data.h" +#include "kv_store_result_set.h" +#include "query.h" +#include "iprocess_system_api_adapter.h" +#include "intercepted_data.h" + +namespace DistributedDB { +using KvStoreNbPublishOnConflict = std::function; +using KvStoreNbConflictNotifier = std::function; + +class KvStoreNbDelegate { +public: + struct Option { + bool createIfNecessary = true; + bool isMemoryDb = false; + bool isEncryptedDb = false; + CipherType cipher = CipherType::DEFAULT; + CipherPassword passwd; + std::string schema = ""; + bool createDirByStoreIdOnly = false; + SecurityOption secOption; // Add data security level parameter + KvStoreObserver *observer = nullptr; + Key key; // The key that needs to be subscribed on obsever, empty means full subscription + unsigned int mode = 0; // obsever mode + int conflictType = 0; + KvStoreNbConflictNotifier notifier = nullptr; + int conflictResolvePolicy = LAST_WIN; + bool isNeedIntegrityCheck = false; + bool isNeedRmCorruptedDb = false; + bool isNeedCompressOnSync = false; + uint8_t compressionRate = 100; // Valid in [1, 100]. + bool syncDualTupleMode = false; // communicator label use dualTuple hash or not + }; + + DB_API virtual ~KvStoreNbDelegate() {} + + // Public zone interfaces + // Get value from the public zone of this store according to the key. + DB_API virtual DBStatus Get(const Key &key, Value &value) const = 0; + + // Get entries from the public zone of this store by key prefix. + // If 'keyPrefix' is empty, It would return all the entries in the zone. + DB_API virtual DBStatus GetEntries(const Key &keyPrefix, std::vector &entries) const = 0; + + // Get entries from the public zone of this store by key prefix. + // If 'keyPrefix' is empty, It would return all the entries in the zone. + DB_API virtual DBStatus GetEntries(const Key &keyPrefix, KvStoreResultSet *&resultSet) const = 0; + + // Get entries from the public zone of this store by query. + // If 'query' is empty, It would return all the entries in the zone. + DB_API virtual DBStatus GetEntries(const Query &query, std::vector &entries) const = 0; + + // Get entries from the public zone of this store by query. + // If query is empty, It would return all the entries in the zone. + DB_API virtual DBStatus GetEntries(const Query &query, KvStoreResultSet *&resultSet) const = 0; + + // Get count from the public zone of this store those meet conditions. + // If query is empty, It would return all the entries count in the zone. + DB_API virtual DBStatus GetCount(const Query &query, int &count) const = 0; + + // Close the result set returned by GetEntries(). + DB_API virtual DBStatus CloseResultSet(KvStoreResultSet *&resultSet) = 0; + + // Put one key-value entry into the public zone of this store. + DB_API virtual DBStatus Put(const Key &key, const Value &value) = 0; + + // Put a batch of entries into the public zone of this store. + DB_API virtual DBStatus PutBatch(const std::vector &entries) = 0; + + // Delete a batch of entries from the public zone of this store. + DB_API virtual DBStatus DeleteBatch(const std::vector &keys) = 0; + + // Delete one key-value entry from the public zone of this store according to the key. + DB_API virtual DBStatus Delete(const Key &key) = 0; + + // Local zone interfaces + // Get value from the local zone of this store according to the key. + DB_API virtual DBStatus GetLocal(const Key &key, Value &value) const = 0; + + // Get key-value entries from the local zone of this store by key prefix. + // If keyPrefix is empty, It would return all the entries in the local zone. + DB_API virtual DBStatus GetLocalEntries(const Key &keyPrefix, std::vector &entries) const = 0; + + // Put one key-value entry into the local zone of this store. + DB_API virtual DBStatus PutLocal(const Key &key, const Value &value) = 0; + + // Delete one key-value entry from the local zone of this store according to the key. + DB_API virtual DBStatus DeleteLocal(const Key &key) = 0; + + // Migrating(local zone <-> public zone) interfaces + // Publish a local key-value entry. + // Migrate the entry from the local zone to public zone. + DB_API virtual DBStatus PublishLocal(const Key &key, bool deleteLocal, bool updateTimestamp, + const KvStoreNbPublishOnConflict &onConflict) = 0; + + // Unpublish a public key-value entry. + // Migrate the entry from the public zone to local zone. + DB_API virtual DBStatus UnpublishToLocal(const Key &key, bool deletePublic, bool updateTimestamp) = 0; + + // Observer interfaces + // Register one observer which concerns the key and the changed data mode. + // If key is empty, observer would get all the changed data of the mode. + // There are three mode: native changes of nb syncable kv store, + // synced data changes from remote devices, + // local changes of local kv store. + DB_API virtual DBStatus RegisterObserver(const Key &key, unsigned int mode, KvStoreObserver *observer) = 0; + + // UnRegister the registered observer. + DB_API virtual DBStatus UnRegisterObserver(const KvStoreObserver *observer) = 0; + + // Remove the device data synced from remote. + DB_API virtual DBStatus RemoveDeviceData(const std::string &device) = 0; + + // Other interfaces + DB_API virtual std::string GetStoreId() const = 0; + + // Sync function interface, if wait set true, this function will be blocked until sync finished + DB_API virtual DBStatus Sync(const std::vector &devices, SyncMode mode, + const std::function &devicesMap)> &onComplete, + bool wait = false) = 0; + + // Special pragma interface, see PragmaCmd and PragmaData, + DB_API virtual DBStatus Pragma(PragmaCmd cmd, PragmaData ¶mData) = 0; + + // Set the conflict notifier for getting the specified type conflict data. + DB_API virtual DBStatus SetConflictNotifier(int conflictType, + const KvStoreNbConflictNotifier ¬ifier) = 0; + + // Used to rekey the database. + // Warning rekey may reopen database file, file handle may lose while locked + DB_API virtual DBStatus Rekey(const CipherPassword &password) = 0; + + // Empty passwords represent non-encrypted files. + // Export existing database files to a specified database file in the specified directory. + DB_API virtual DBStatus Export(const std::string &filePath, const CipherPassword &passwd) = 0; + + // Import the existing database files to the specified database file in the specified directory. + // Warning Import may reopen database file in locked state + DB_API virtual DBStatus Import(const std::string &filePath, const CipherPassword &passwd) = 0; + + // Start a transaction + DB_API virtual DBStatus StartTransaction() = 0; + + // Commit a transaction + DB_API virtual DBStatus Commit() = 0; + + // Rollback a transaction + DB_API virtual DBStatus Rollback() = 0; + + // Put a batch of entries into the local zone of this store. + DB_API virtual DBStatus PutLocalBatch(const std::vector &entries) = 0; + + // Delete a batch of entries from the local zone of this store according to the keys. + DB_API virtual DBStatus DeleteLocalBatch(const std::vector &keys) = 0; + + // Get the SecurityOption of this kvStore. + DB_API virtual DBStatus GetSecurityOption(SecurityOption &option) const = 0; + + // Set a notify callback, it will be called when remote push or push_pull finished. + // If Repeat set, subject to the last time. + // If set nullptr, means unregister the notify. + DB_API virtual DBStatus SetRemotePushFinishedNotify(const RemotePushFinishedNotifier ¬ifier) = 0; + + // Sync function interface, if wait set true, this function will be blocked until sync finished. + // Param query used to filter the records to be synchronized. + // Now just support push mode and query by prefixKey. + // If Query.limit is used, its query cache will not be recorded, In the same way below, + // the synchronization will still take the full amount. + DB_API virtual DBStatus Sync(const std::vector &devices, SyncMode mode, + const std::function &devicesMap)> &onComplete, + const Query &query, bool wait) = 0; + + // Check the integrity of this kvStore. + DB_API virtual DBStatus CheckIntegrity() const = 0; + + // Set an equal identifier for this database, After this called, send msg to the target will use this identifier + DB_API virtual DBStatus SetEqualIdentifier(const std::string &identifier, + const std::vector &targets) = 0; + + // This API is not recommended. Before using this API, you need to understand the API usage rules. + // Set pushdatainterceptor. The interceptor works when send data. + DB_API virtual DBStatus SetPushDataInterceptor(const PushDataInterceptor &interceptor) = 0; + + // Register a subscriber query on peer devices. The data in the peer device meets the subscriber query condition + // will automatically push to the local device when it's changed. + DB_API virtual DBStatus SubscribeRemoteQuery(const std::vector &devices, + const std::function &devicesMap)> &onComplete, + const Query &query, bool wait) = 0; + + // Unregister a subscriber query on peer devices. + DB_API virtual DBStatus UnSubscribeRemoteQuery(const std::vector &devices, + const std::function &devicesMap)> &onComplete, + const Query &query, bool wait) = 0; +}; +} // namespace DistributedDB + +#endif // KV_STORE_NB_DELEGATE_H diff --git a/mock/distributeddb/interfaces/include/kv_store_observer.h b/mock/distributeddb/interfaces/include/kv_store_observer.h new file mode 100644 index 00000000..ea74c0c4 --- /dev/null +++ b/mock/distributeddb/interfaces/include/kv_store_observer.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_OBSERVER_H +#define KV_STORE_OBSERVER_H + +#include "kv_store_changed_data.h" + +namespace DistributedDB { +class KvStoreObserver { +public: + virtual ~KvStoreObserver() {} + + // Databa change callback + virtual void OnChange(const KvStoreChangedData &data) = 0; +}; +} // namespace DistributedDB + +#endif // KV_STORE_OBSERVER_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/include/kv_store_result_set.h b/mock/distributeddb/interfaces/include/kv_store_result_set.h new file mode 100644 index 00000000..68a72de1 --- /dev/null +++ b/mock/distributeddb/interfaces/include/kv_store_result_set.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_RESULT_SET_H +#define KV_STORE_RESULT_SET_H + +#include "store_types.h" + +namespace DistributedDB { +class KvStoreResultSet { +public: + DB_API virtual ~KvStoreResultSet() {}; + + // Returns the count of rows in the result set. + DB_API virtual int GetCount() const = 0; + + // Returns the current read position of the result set. + DB_API virtual int GetPosition() const = 0; + + // Move the read position to the first row, return false if the result set is empty. + DB_API virtual bool MoveToFirst() = 0; + + // Move the read position to the last row, return false if the result set is empty. + DB_API virtual bool MoveToLast() = 0; + + // Move the read position to the next row, return false if the result set is empty + // or the read position is already past the last entry in the result set. + DB_API virtual bool MoveToNext() = 0; + + // Move the read position to the previous row, return false if the result set is empty + // or the read position is already before the first entry in the result set. + DB_API virtual bool MoveToPrevious() = 0; + + // Move the read position by a relative amount from the current position. + DB_API virtual bool Move(int offset) = 0; + + // Move the read position to an absolute position value. + DB_API virtual bool MoveToPosition(int position) = 0; + + // Returns whether the read position is pointing to the first row. + DB_API virtual bool IsFirst() const = 0; + + // Returns whether the read position is pointing to the last row. + DB_API virtual bool IsLast() const = 0; + + // Returns whether the read position is before the first row. + DB_API virtual bool IsBeforeFirst() const = 0; + + // Returns whether the read position is after the last row + DB_API virtual bool IsAfterLast() const = 0; + + // Get a key-value entry. + DB_API virtual DBStatus GetEntry(Entry &entry) const = 0; +}; +} // namespace DistributedDB + +#endif // KV_STORE_RESULT_SET_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/include/kv_store_snapshot_delegate.h b/mock/distributeddb/interfaces/include/kv_store_snapshot_delegate.h new file mode 100644 index 00000000..3053e7ff --- /dev/null +++ b/mock/distributeddb/interfaces/include/kv_store_snapshot_delegate.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_SNAPSHOT_DELEGATE_H +#define KV_STORE_SNAPSHOT_DELEGATE_H + +#include +#include + +#include "store_types.h" + +namespace DistributedDB { +class KvStoreSnapshotDelegate { +public: + DB_API virtual ~KvStoreSnapshotDelegate() {} + + // Get a value from the snapshot with the given key. + // The return value is DBStatus and Value, these values will be passed to the callback. + DB_API virtual void Get(const Key &key, const std::function &callback) const = 0; + + // Get entries from the snapshot which keys start with keyPrefix. + // The return value is DBStatus and Value, these values will be passed to the callback. + DB_API virtual void GetEntries(const Key &keyPrefix, + const std::function &)> &callback) const = 0; +}; +} // namespace DistributedDB + +#endif // KV_STORE_SNAPSHOT_DELEGATE_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/include/relational/relational_store_delegate.h b/mock/distributeddb/interfaces/include/relational/relational_store_delegate.h new file mode 100644 index 00000000..448d2264 --- /dev/null +++ b/mock/distributeddb/interfaces/include/relational/relational_store_delegate.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RELATIONAL_STORE_DELEGATE_H +#define RELATIONAL_STORE_DELEGATE_H + +#include + +#include "query.h" +#include "store_types.h" +#include "store_observer.h" + +namespace DistributedDB { +class RelationalStoreDelegate { +public: + DB_API virtual ~RelationalStoreDelegate() = default; + + struct Option { + StoreObserver *observer = nullptr; + // split mode + }; + + DB_API virtual DBStatus CreateDistributedTable(const std::string &tableName) = 0; + + DB_API virtual DBStatus Sync(const std::vector &devices, SyncMode mode, + const Query &query, const SyncStatusCallback &onComplete, bool wait) = 0; + + DB_API virtual DBStatus RemoveDeviceData(const std::string &device) = 0; + + DB_API virtual DBStatus RemoveDeviceData(const std::string &device, const std::string &tableName) = 0; +}; +} // namespace DistributedDB +#endif // RELATIONAL_STORE_DELEGATE_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/include/relational/relational_store_manager.h b/mock/distributeddb/interfaces/include/relational/relational_store_manager.h new file mode 100644 index 00000000..9be7b6e6 --- /dev/null +++ b/mock/distributeddb/interfaces/include/relational/relational_store_manager.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RELATIONAL_STORE_MANAGER_H +#define RELATIONAL_STORE_MANAGER_H +#include +#include +#include + +#include "auto_launch_export.h" +#include "relational_store_delegate.h" +#include "store_types.h" + +namespace DistributedDB { +class RelationalStoreManager final { +public: + // Only calculate the table name with device hash, no guarantee for the table exists + DB_API static std::string GetDistributedTableName(const std::string &device, const std::string &tableName); + + DB_API RelationalStoreManager(const std::string &appId, const std::string &userId); + DB_API ~RelationalStoreManager() = default; + + RelationalStoreManager(const RelationalStoreManager &) = delete; + RelationalStoreManager(RelationalStoreManager &&) = delete; + RelationalStoreManager &operator=(const RelationalStoreManager &) = delete; + RelationalStoreManager &operator=(RelationalStoreManager &&) = delete; + + DB_API DBStatus OpenStore(const std::string &path, const std::string &storeId, + const RelationalStoreDelegate::Option &option, RelationalStoreDelegate *&delegate); + + DB_API DBStatus CloseStore(RelationalStoreDelegate *store); + + DB_API static void SetAutoLaunchRequestCallback(const AutoLaunchRequestCallback &callback); + + DB_API static std::string GetRelationalStoreIdentifier(const std::string &userId, const std::string &appId, + const std::string &storeId); + +private: + std::string appId_; + std::string userId_; +}; +} // namespace DistributedDB +#endif // RELATIONAL_STORE_MANAGER_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/include/relational/relational_store_sqlite_ext.h b/mock/distributeddb/interfaces/include/relational/relational_store_sqlite_ext.h new file mode 100644 index 00000000..59f4d534 --- /dev/null +++ b/mock/distributeddb/interfaces/include/relational/relational_store_sqlite_ext.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RELATIONAL_STORE_EXT_H +#define RELATIONAL_STORE_EXT_H + +#define SQLITE3_HW_EXPORT_SYMBOLS + +// using the "sqlite3sym.h" in OHOS +#ifndef USE_SQLITE_SYMBOLS +#include "sqlite3.h" +#else +#include "sqlite3sym.h" +#endif + +// We extend the original purpose of the "sqlite3ext.h". +struct sqlite3_api_routines_relational { + int (*open)(const char *, sqlite3 **); + int (*open16)(const void *, sqlite3 **); + int (*open_v2)(const char *, sqlite3 **, int, const char *); +}; + +extern const struct sqlite3_api_routines_relational *sqlite3_export_relational_symbols; + +#ifdef sqlite3_open +#undef sqlite3_open +#endif +#define sqlite3_open sqlite3_export_relational_symbols->open + +#ifdef sqlite3_open16 +#undef sqlite3_open16 +#endif +#define sqlite3_open16 sqlite3_export_relational_symbols->open16 + +#ifdef sqlite3_open_v2 +#undef sqlite3_open_v2 +#endif +#define sqlite3_open_v2 sqlite3_export_relational_symbols->open_v2 + +#endif // RELATIONAL_STORE_EXT_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/include/relational/runtime_config.h b/mock/distributeddb/interfaces/include/relational/runtime_config.h new file mode 100644 index 00000000..e6173f00 --- /dev/null +++ b/mock/distributeddb/interfaces/include/relational/runtime_config.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RUNTIME_CONFIG_H +#define RUNTIME_CONFIG_H + +#include +#include + +#include "iprocess_communicator.h" +#include "iprocess_system_api_adapter.h" +#include "store_types.h" +namespace DistributedDB { +class RuntimeConfig final { +public: + DB_API RuntimeConfig() = default; + DB_API ~RuntimeConfig() = default; + + DB_API static DBStatus SetProcessLabel(const std::string &appId, const std::string &userId); + + DB_API static DBStatus SetProcessCommunicator(const std::shared_ptr &inCommunicator); + + DB_API static DBStatus SetPermissionCheckCallback(const PermissionCheckCallbackV2 &callback); + + DB_API static DBStatus SetProcessSystemAPIAdapter(const std::shared_ptr &adapter); + + DB_API static void Dump(int fd, const std::vector &args); + +private: + static std::mutex communicatorMutex_; + static std::shared_ptr processCommunicator_; +}; +} // namespace DistributedDB + +#endif // RUNTIME_CONFIG_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/include/relational/store_changed_data.h b/mock/distributeddb/interfaces/include/relational/store_changed_data.h new file mode 100644 index 00000000..3077d8d9 --- /dev/null +++ b/mock/distributeddb/interfaces/include/relational/store_changed_data.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 STORE_CHANGED_DATA_H +#define STORE_CHANGED_DATA_H + +#include +#include "store_types.h" + +namespace DistributedDB { +struct StoreProperty { + std::string userId; + std::string appId; + std::string storeId; +}; +class StoreChangedData { +public: + StoreChangedData() {} + DB_API virtual ~StoreChangedData() {} + + // Interface for Getting the device whose data changed. + DB_API virtual std::string GetDataChangeDevice() const = 0; + + // Interface for Getting the store whose data changed. + DB_API virtual void GetStoreProperty(StoreProperty &storeProperty) const = 0; +}; +} // namespace DistributedDB + +#endif // STORE_CHANGED_DATA_H diff --git a/mock/distributeddb/interfaces/include/relational/store_observer.h b/mock/distributeddb/interfaces/include/relational/store_observer.h new file mode 100644 index 00000000..6072bbc6 --- /dev/null +++ b/mock/distributeddb/interfaces/include/relational/store_observer.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 STORE_OBSERVER_H +#define STORE_OBSERVER_H + +#include "store_changed_data.h" + +namespace DistributedDB { +class StoreObserver { +public: + virtual ~StoreObserver() {} + + // Databa change callback + virtual void OnChange(const StoreChangedData &data) = 0; +}; +} // namespace DistributedDB + +#endif // STORE_OBSERVER_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/include/store_types.h b/mock/distributeddb/interfaces/include/store_types.h new file mode 100644 index 00000000..62fc4fa1 --- /dev/null +++ b/mock/distributeddb/interfaces/include/store_types.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_TYPE_H +#define KV_STORE_TYPE_H + +#include +#include +#include + +#include "types_export.h" + +namespace DistributedDB { +enum DBStatus { + DB_ERROR = -1, + OK = 0, + BUSY, + NOT_FOUND, + INVALID_ARGS, + TIME_OUT, + NOT_SUPPORT, + INVALID_PASSWD_OR_CORRUPTED_DB, + OVER_MAX_LIMITS, + INVALID_FILE, + NO_PERMISSION, + FILE_ALREADY_EXISTED, + SCHEMA_MISMATCH, + INVALID_SCHEMA, + READ_ONLY, + INVALID_VALUE_FIELDS, // invalid put value for json schema. + INVALID_FIELD_TYPE, // invalid put value field type for json schema. + CONSTRAIN_VIOLATION, // invalid put value constrain for json schema. + INVALID_FORMAT, // invalid put value format for json schema. + STALE, // new record is staler compared to the same key existed in db. + LOCAL_DELETED, // local data is deleted by the unpublish. + LOCAL_DEFEAT, // local data defeat the sync data while unpublish. + LOCAL_COVERED, // local data is covered by the sync data while unpublish. + INVALID_QUERY_FORMAT, + INVALID_QUERY_FIELD, + PERMISSION_CHECK_FORBID_SYNC, // permission check result , forbid sync. + ALREADY_SET, // already set. + COMM_FAILURE, // communicator may get some error. + EKEYREVOKED_ERROR, // EKEYREVOKED error when operating db file + SECURITY_OPTION_CHECK_ERROR, // such as remote device's SecurityOption not equal to local + SCHEMA_VIOLATE_VALUE, // Values already exist in dbFile do not match new schema + INTERCEPT_DATA_FAIL, // Interceptor push data failed. + LOG_OVER_LIMITS, // Log size is over the limits. + DISTRIBUTED_SCHEMA_NOT_FOUND, // the sync table is not a relational table + DISTRIBUTED_SCHEMA_CHANGED, // the schema was changed + MODE_MISMATCH, + NOT_ACTIVE, + USER_CHANGED, +}; + +struct KvStoreConfig { + std::string dataDir; +}; + +enum PragmaCmd { + AUTO_SYNC = 1, + SYNC_DEVICES = 2, + RM_DEVICE_DATA = 3, // remove the device data synced from remote by device name + PERFORMANCE_ANALYSIS_GET_REPORT, + PERFORMANCE_ANALYSIS_OPEN, + PERFORMANCE_ANALYSIS_CLOSE, + PERFORMANCE_ANALYSIS_SET_REPORTFILENAME, + GET_IDENTIFIER_OF_DEVICE, + GET_DEVICE_IDENTIFIER_OF_ENTRY, + GET_QUEUED_SYNC_SIZE, + SET_QUEUED_SYNC_LIMIT, + GET_QUEUED_SYNC_LIMIT, + SET_WIPE_POLICY, // set the policy of wipe remote stale data + RESULT_SET_CACHE_MODE, // Accept ResultSetCacheMode Type As PragmaData + RESULT_SET_CACHE_MAX_SIZE, // Allowed Int Type Range [1,16], Unit MB + SET_SYNC_RETRY, + SET_MAX_LOG_LIMIT, + EXEC_CHECKPOINT, +}; + +enum ResolutionPolicyType { + AUTO_LAST_WIN = 0, // resolve conflicts by timestamp(default value) + CUSTOMER_RESOLUTION = 1 // resolve conflicts by user +}; + +enum ObserverMode { + OBSERVER_CHANGES_NATIVE = 1, + OBSERVER_CHANGES_FOREIGN = 2, + OBSERVER_CHANGES_LOCAL_ONLY = 4, +}; + +enum SyncMode { + SYNC_MODE_PUSH_ONLY, + SYNC_MODE_PULL_ONLY, + SYNC_MODE_PUSH_PULL, +}; + +enum ConflictResolvePolicy { + LAST_WIN = 0, + DEVICE_COLLABORATION, +}; + +struct TableStatus { + std::string tableName; + DBStatus status; +}; +using KvStoreCorruptionHandler = std::function; +using StoreCorruptionHandler = std::function; +using SyncStatusCallback = std::function> &devicesMap)>; +} // namespace DistributedDB +#endif // KV_STORE_TYPE_H diff --git a/mock/distributeddb/interfaces/src/intercepted_data_impl.cpp b/mock/distributeddb/interfaces/src/intercepted_data_impl.cpp new file mode 100644 index 00000000..3bfb06d1 --- /dev/null +++ b/mock/distributeddb/interfaces/src/intercepted_data_impl.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "intercepted_data_impl.h" +#include "db_common.h" +#include "db_constant.h" +#include "generic_single_ver_kv_entry.h" +#include "parcel.h" +#include "version.h" + +namespace DistributedDB { +namespace { +bool CheckKey(const Key &key) +{ + if (key.empty() || key.size() > DBConstant::MAX_KEY_SIZE) { + LOGE("Key is too large:%zu.", key.size()); + return false; + } + return true; +} + +bool CheckValue(const Value &value, const std::function &checkSchema) +{ + if (value.size() > DBConstant::MAX_VALUE_SIZE) { + LOGE("Value is too large:%zu.", value.size()); + return false; + } + + if (checkSchema == nullptr) { + LOGE("Check schema failed, no check func."); + return false; + } + + int errCode = checkSchema(value); + if (errCode != E_OK) { + LOGE("Check schema failed, value is invalid:%d.", errCode); + return false; + } + return true; +} + +bool CheckLength(size_t len, size_t maxPacketSize) +{ + if (len > maxPacketSize) { + LOGE("Packet is too large:%zu.", len); + return false; + } + return true; +} +} // anonymous namespace + +InterceptedDataImpl::InterceptedDataImpl(std::vector dataItems, + const std::function &checkSchema) + : kvEntriesReady_(false), + isError_(false), + totalLength_(), + maxPacketSize_(), + checkSchema_(checkSchema), + dataItems_(dataItems), + kvEntries_(), + indexes_() +{ + totalLength_ = GenericSingleVerKvEntry::CalculateLens(dataItems, SOFTWARE_VERSION_CURRENT); + // New packet cannot exceed both twice the MTU and twice the original size. + // Besides, it cannot exceed 30 MB. + maxPacketSize_ = std::min(DBConstant::MAX_SYNC_BLOCK_SIZE, + std::max(totalLength_, static_cast(DBConstant::MAX_MTU_SIZE)) * 2); +} + +InterceptedDataImpl::~InterceptedDataImpl() +{} + +std::vector InterceptedDataImpl::GetEntries() +{ + if (!kvEntriesReady_) { + GetKvEntries(); + } + return kvEntries_; +} + +bool InterceptedDataImpl::CheckIndex(size_t index) +{ + if (!kvEntriesReady_) { + GetKvEntries(); + } + + if (index >= kvEntries_.size()) { + LOGE("Index is too large:%zu, size:%zu.", index, kvEntries_.size()); + return false; + } + return true; +} + +DBStatus InterceptedDataImpl::ModifyKey(size_t index, const Key &newKey) +{ + // Check index. + if (!CheckIndex(index)) { + isError_ = true; + return INVALID_ARGS; + } + + // Check key. + if (!CheckKey(newKey)) { + isError_ = true; + return INVALID_ARGS; + } + + // Check length. + const auto &oldKey = dataItems_[indexes_[index]]->GetKey(); + size_t newLength = totalLength_ - Parcel::GetVectorCharLen(oldKey) + Parcel::GetVectorCharLen(newKey); + if (!CheckLength(newLength, maxPacketSize_)) { + isError_ = true; + return INVALID_ARGS; + } + totalLength_ = newLength; + + // Modify data + auto entry = dataItems_[indexes_[index]]; + entry->SetKey(newKey); + Key hashKey; + int errCode = DBCommon::CalcValueHash(newKey, hashKey); + if (errCode != E_OK) { + LOGE("Calc hashkey failed."); + isError_ = true; + return INVALID_ARGS; + } + entry->SetHashKey(hashKey); + return OK; +} + +DBStatus InterceptedDataImpl::ModifyValue(size_t index, const Value &newValue) +{ + // Check index. + if (!CheckIndex(index)) { + isError_ = true; + return INVALID_ARGS; + } + + // Check value. + if (!CheckValue(newValue, checkSchema_)) { + isError_ = true; + return INVALID_ARGS; + } + + // Check length. + const auto &oldValue = dataItems_[indexes_[index]]->GetValue(); + size_t newLength = totalLength_ - Parcel::GetVectorCharLen(oldValue) + Parcel::GetVectorCharLen(newValue); + if (!CheckLength(newLength, maxPacketSize_)) { + isError_ = true; + return INVALID_ARGS; + } + totalLength_ = newLength; + + // Modify data + auto entry = dataItems_[indexes_[index]]; + entry->SetValue(newValue); + return OK; +} + +bool InterceptedDataImpl::IsError() const +{ + return isError_; +} + +void InterceptedDataImpl::GetKvEntries() +{ + for (size_t i = 0; i < dataItems_.size(); ++i) { + const auto &kvEntry = dataItems_[i]; + if ((kvEntry->GetFlag() & DataItem::DELETE_FLAG) == 0) { // For deleted data, do not modify. + kvEntries_.push_back({ kvEntry->GetKey(), kvEntry->GetValue() }); + indexes_.push_back(i); + } + } + kvEntriesReady_ = true; +} +} // namespace DistributedDB + diff --git a/mock/distributeddb/interfaces/src/intercepted_data_impl.h b/mock/distributeddb/interfaces/src/intercepted_data_impl.h new file mode 100644 index 00000000..30fefe4b --- /dev/null +++ b/mock/distributeddb/interfaces/src/intercepted_data_impl.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 INTERCEPTED_DATA_IMPL_H +#define INTERCEPTED_DATA_IMPL_H + +#include +#include +#include + +#include "intercepted_data.h" +#include "macro_utils.h" +#include "single_ver_kv_entry.h" +#include "store_types.h" +#include "types_export.h" + +namespace DistributedDB { +class InterceptedDataImpl : public InterceptedData { +public: + InterceptedDataImpl(std::vector dataItems, const std::function &checkSchema); + virtual ~InterceptedDataImpl(); + DISABLE_COPY_ASSIGN_MOVE(InterceptedDataImpl); + + std::vector GetEntries() override; + DBStatus ModifyKey(size_t index, const Key &newKey) override; + DBStatus ModifyValue(size_t index, const Value &newValue) override; + + bool IsError() const; + +private: + bool CheckIndex(size_t index); + void GetKvEntries(); + + bool kvEntriesReady_; + bool isError_; + size_t totalLength_; + size_t maxPacketSize_; + std::function checkSchema_; + std::vector dataItems_; + std::vector kvEntries_; + std::vector indexes_; +}; +} // namespace DistributedDB +#endif // INTERCEPTED_DATA_IMPL_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/src/kv_store_changed_data_impl.cpp b/mock/distributeddb/interfaces/src/kv_store_changed_data_impl.cpp new file mode 100644 index 00000000..3f57e9c7 --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_changed_data_impl.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kv_store_changed_data_impl.h" + +namespace DistributedDB { +KvStoreChangedDataImpl::~KvStoreChangedDataImpl() +{ + observerData_ = nullptr; +} + +const std::list &KvStoreChangedDataImpl::GetEntriesInserted() const +{ + std::lock_guard lock(mutex_); + if (insertedEntries_.empty() && observerData_ != nullptr) { + int errCode; + insertedEntries_ = observerData_->GetInsertedEntries(errCode); + } + + return insertedEntries_; +} + +const std::list &KvStoreChangedDataImpl::GetEntriesUpdated() const +{ + std::lock_guard lock(mutex_); + if (updatedEntries_.empty() && observerData_ != nullptr) { + int errCode; + updatedEntries_ = observerData_->GetUpdatedEntries(errCode); + } + + return updatedEntries_; +} + +const std::list &KvStoreChangedDataImpl::GetEntriesDeleted() const +{ + std::lock_guard lock(mutex_); + if (deletedEntries_.empty() && observerData_ != nullptr) { + int errCode; + deletedEntries_ = observerData_->GetDeletedEntries(errCode); + } + + return deletedEntries_; +} + +bool KvStoreChangedDataImpl::IsCleared() const +{ + if (observerData_ != nullptr) { + return observerData_->IsCleared(); + } + + return false; +} +} // namespace DistributedDB + diff --git a/mock/distributeddb/interfaces/src/kv_store_changed_data_impl.h b/mock/distributeddb/interfaces/src/kv_store_changed_data_impl.h new file mode 100644 index 00000000..417dbd2b --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_changed_data_impl.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_CHANGED_DATA_IMPL_H +#define KV_STORE_CHANGED_DATA_IMPL_H + +#include + +#include "kv_store_changed_data.h" +#include "kvdb_commit_notify_data.h" + +namespace DistributedDB { +class KvStoreChangedDataImpl : public KvStoreChangedData { +public: + explicit KvStoreChangedDataImpl(const KvDBCommitNotifyData *observerData) : observerData_(observerData) {} + virtual ~KvStoreChangedDataImpl(); + + DISABLE_COPY_ASSIGN_MOVE(KvStoreChangedDataImpl); + + const std::list &GetEntriesInserted() const override; + + const std::list &GetEntriesUpdated() const override; + + const std::list &GetEntriesDeleted() const override; + + bool IsCleared() const override; + +private: + const KvDBCommitNotifyData *observerData_; + mutable std::mutex mutex_; + mutable std::list insertedEntries_; + mutable std::list updatedEntries_; + mutable std::list deletedEntries_; +}; +} // namespace DistributedDB + +#endif // KV_STORE_CHANGED_DATA_IMPL_H + diff --git a/mock/distributeddb/interfaces/src/kv_store_delegate_impl.cpp b/mock/distributeddb/interfaces/src/kv_store_delegate_impl.cpp new file mode 100644 index 00000000..d1a0be44 --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_delegate_impl.cpp @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "kv_store_delegate_impl.h" + +#include +#include + +#include "platform_specific.h" +#include "log_print.h" +#include "param_check_utils.h" +#include "db_constant.h" +#include "db_errno.h" +#include "db_types.h" +#include "kv_store_errno.h" +#include "kvdb_pragma.h" +#include "kv_store_observer.h" +#include "kvdb_manager.h" +#include "kv_store_snapshot_delegate_impl.h" +#include "kv_store_changed_data_impl.h" + +namespace DistributedDB { +namespace { + const std::string INVALID_CONNECTION = "[KvStoreDelegate] Invalid connection for operation"; +} +KvStoreDelegateImpl::KvStoreDelegateImpl(IKvDBConnection *conn, const std::string &storeId) + : conn_(conn), + storeId_(storeId), + releaseFlag_(false) +{} + +KvStoreDelegateImpl::~KvStoreDelegateImpl() +{ + if (!releaseFlag_) { + LOGF("[KvStoreDelegate] can not release object directly"); + return; + } + + LOGI("[KvStoreDelegate] deconstruct"); + conn_ = nullptr; +} + +DBStatus KvStoreDelegateImpl::Put(const Key &key, const Value &value) +{ + if (conn_ != nullptr) { + IOption option; + int errCode = conn_->Put(option, key, value); + if (errCode == E_OK) { + return OK; + } + + LOGE("[KvStoreDelegate] Put data failed:%d", errCode); + return TransferDBErrno(errCode); + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; +} + +DBStatus KvStoreDelegateImpl::PutBatch(const std::vector &entries) +{ + if (conn_ != nullptr) { + IOption option; + int errCode = conn_->PutBatch(option, entries); + if (errCode == E_OK) { + return OK; + } + + LOGE("[KvStoreDelegate] Put batch data failed:%d", errCode); + return TransferDBErrno(errCode); + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; +} + +DBStatus KvStoreDelegateImpl::Delete(const Key &key) +{ + if (conn_ != nullptr) { + IOption option; + int errCode = conn_->Delete(option, key); + if (errCode == E_OK || errCode == -E_NOT_FOUND) { + return OK; + } + + LOGE("[KvStoreDelegate] Delete data failed:%d", errCode); + return TransferDBErrno(errCode); + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; +} + +DBStatus KvStoreDelegateImpl::DeleteBatch(const std::vector &keys) +{ + if (conn_ != nullptr) { + IOption option; + int errCode = conn_->DeleteBatch(option, keys); + if (errCode == E_OK || errCode == -E_NOT_FOUND) { + return OK; + } + + LOGE("[KvStoreDelegate] Delete batch data failed:%d", errCode); + return TransferDBErrno(errCode); + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; +} + +DBStatus KvStoreDelegateImpl::Clear() +{ + if (conn_ != nullptr) { + IOption option; + int errCode = conn_->Clear(option); + if (errCode == E_OK) { + return OK; + } + + LOGE("[KvStoreDelegate] Clear data failed:%d", errCode); + return TransferDBErrno(errCode); + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; +} + +std::string KvStoreDelegateImpl::GetStoreId() const +{ + return storeId_; +} + +void KvStoreDelegateImpl::GetKvStoreSnapshot(KvStoreObserver *observer, + const std::function &callback) +{ + if (!callback) { + LOGE("[KvStoreDelegate] Invalid callback for snapshot!"); + return; + } + + if (conn_ != nullptr) { + if (observer != nullptr && RegisterObserver(observer) != E_OK) { + LOGE("[KvStoreDelegate][GetSnapshot] Register observer failed!"); + callback(DB_ERROR, nullptr); + return; + } + + IKvDBSnapshot *snapshot = nullptr; + int errCode = conn_->GetSnapshot(snapshot); + if (errCode == E_OK) { + auto snapshotDelegate = new (std::nothrow) KvStoreSnapshotDelegateImpl(snapshot, observer); + if (snapshotDelegate != nullptr) { + callback(OK, snapshotDelegate); + return; + } + conn_->ReleaseSnapshot(snapshot); + snapshot = nullptr; + } + + // UnRegister the registered observer. + errCode = UnRegisterObserver(observer); + if (errCode != E_OK) { + LOGE("[KvStoreDelegate][GetSnapshot] UnRegister observer failed:%d!", errCode); + } + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + callback(DB_ERROR, nullptr); +} + +DBStatus KvStoreDelegateImpl::ReleaseKvStoreSnapshot(KvStoreSnapshotDelegate *&snapshotDelegate) +{ + if (conn_ != nullptr && snapshotDelegate != nullptr) { + KvStoreObserver *observer = nullptr; + (static_cast(snapshotDelegate))->GetObserver(observer); + if (observer != nullptr && UnRegisterObserver(observer) != E_OK) { + LOGE("[KvStoreDelegate][ReleaseSnapshot] UnRegistObserver failed!"); + return DB_ERROR; + } + + IKvDBSnapshot *snapshot = nullptr; + (static_cast(snapshotDelegate))->GetSnapshot(snapshot); + conn_->ReleaseSnapshot(snapshot); + snapshot = nullptr; + delete snapshotDelegate; + snapshotDelegate = nullptr; + return OK; + } + + return DB_ERROR; +} + +DBStatus KvStoreDelegateImpl::RegisterObserver(KvStoreObserver *observer) +{ + if (observer == nullptr) { + return INVALID_ARGS; + } + + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + std::lock_guard lockGuard(observerMapLock_); + if (observerMap_.find(observer) != observerMap_.end()) { + LOGE("[KvStoreDelegate] Observer has been already registered!"); + return DB_ERROR; + } + + Key key; + int errCode = E_OK; + KvDBObserverHandle *observerHandle = conn_->RegisterObserver( + static_cast(DATABASE_COMMIT_EVENT), + key, + [observer](const KvDBCommitNotifyData &ptr) { + KvStoreChangedDataImpl data(&ptr); + observer->OnChange(data); + }, + errCode); + + if (errCode != E_OK || observerHandle == nullptr) { + LOGE("[KvStoreDelegate] Register listener failed:%d!", errCode); + return DB_ERROR; + } + + observerMap_.insert(std::pair(observer, observerHandle)); + return OK; +} + +// Unregister a data change observer +DBStatus KvStoreDelegateImpl::UnRegisterObserver(const KvStoreObserver *observer) +{ + if (observer == nullptr) { + return INVALID_ARGS; + } + + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + std::lock_guard lockGuard(observerMapLock_); + auto iter = observerMap_.find(observer); + if (iter == observerMap_.end()) { + LOGE("[KvStoreDelegate] observer has not been registered!"); + return NOT_FOUND; + } + + const KvDBObserverHandle *observerHandle = iter->second; + int errCode = conn_->UnRegisterObserver(observerHandle); + if (errCode != E_OK) { + LOGE("[KvStoreDelegate] UnRegister observer failed:%d!", errCode); + return DB_ERROR; + } + observerMap_.erase(iter); + return OK; +} + +DBStatus KvStoreDelegateImpl::StartTransaction() +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + int errCode = conn_->StartTransaction(); + if (errCode != E_OK) { + LOGE("[KvStoreDelegate] StartTransaction failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus KvStoreDelegateImpl::Commit() +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + int errCode = conn_->Commit(); + if (errCode != E_OK) { + LOGE("[KvStoreDelegate] Commit failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus KvStoreDelegateImpl::Rollback() +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + int errCode = conn_->RollBack(); + if (errCode != E_OK) { + LOGE("[KvStoreDelegate] Rollback failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus KvStoreDelegateImpl::SetConflictResolutionPolicy(ResolutionPolicyType type, + const ConflictResolution &resolution) +{ + if (type == AUTO_LAST_WIN) { + return OK; + } + + if (type == CUSTOMER_RESOLUTION && resolution != nullptr) { + return OK; + } + LOGE("[KvStoreDelegate] Invalid conflict resolution policy:%d", type); + return DB_ERROR; +} + +DBStatus KvStoreDelegateImpl::Rekey(const CipherPassword &password) +{ + if (conn_ != nullptr) { + int errCode = conn_->Rekey(password); + if (errCode == E_OK) { + return OK; + } + + LOGE("[KvStoreDelegate] rekey failed:%d", errCode); + return TransferDBErrno(errCode); + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; +} + +DBStatus KvStoreDelegateImpl::Export(const std::string &filePath, const CipherPassword &passwd) +{ + std::string fileDir; + std::string fileName; + OS::SplitFilePath(filePath, fileDir, fileName); + + std::string canonicalUrl; + if (!ParamCheckUtils::CheckDataDir(fileDir, canonicalUrl)) { + return INVALID_ARGS; + } + + if (!OS::CheckPathExistence(canonicalUrl)) { + return NO_PERMISSION; + } + + canonicalUrl = canonicalUrl + "/" + fileName; + if (OS::CheckPathExistence(canonicalUrl)) { + LOGE("[KvStoreDelegate] The exported file has already been existed"); + return FILE_ALREADY_EXISTED; + } + + if (conn_ != nullptr) { + int errCode = conn_->Export(canonicalUrl, passwd); + if (errCode == E_OK) { + return OK; + } + LOGE("[KvStoreDelegate] Export failed:%d", errCode); + return TransferDBErrno(errCode); + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; +} + +DBStatus KvStoreDelegateImpl::Import(const std::string &filePath, const CipherPassword &passwd) +{ + std::string fileDir; + std::string fileName; + OS::SplitFilePath(filePath, fileDir, fileName); + + std::string canonicalUrl; + if (!ParamCheckUtils::CheckDataDir(fileDir, canonicalUrl)) { + return INVALID_ARGS; + } + + canonicalUrl = canonicalUrl + "/" + fileName; + if (!OS::CheckPathExistence(canonicalUrl)) { + LOGE("[KvStoreDelegate] The imported file not existed:%d", errno); + return INVALID_FILE; + } + + if (conn_ != nullptr) { + int errCode = conn_->Import(canonicalUrl, passwd); + if (errCode == E_OK) { + return OK; + } + LOGE("[KvStoreDelegate] Import failed:%d", errCode); + return TransferDBErrno(errCode); + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; +} + +void KvStoreDelegateImpl::SetReleaseFlag(bool flag) +{ + releaseFlag_ = flag; +} + +DBStatus KvStoreDelegateImpl::Close() +{ + if (conn_ != nullptr) { + int errCode = KvDBManager::ReleaseDatabaseConnection(conn_); + if (errCode == -E_BUSY) { + LOGW("[KvStoreDelegate] busy for close"); + return BUSY; + } + + LOGI("[KvStoreDelegate] Close"); + conn_ = nullptr; + } + return OK; +} + +DBStatus KvStoreDelegateImpl::Pragma(PragmaCmd cmd, PragmaData ¶mData) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + int errCode; + switch (cmd) { + case PERFORMANCE_ANALYSIS_GET_REPORT: + errCode = conn_->Pragma(PRAGMA_PERFORMANCE_ANALYSIS_GET_REPORT, paramData); + break; + case PERFORMANCE_ANALYSIS_OPEN: + errCode = conn_->Pragma(PRAGMA_PERFORMANCE_ANALYSIS_OPEN, paramData); + break; + case PERFORMANCE_ANALYSIS_CLOSE: + errCode = conn_->Pragma(PRAGMA_PERFORMANCE_ANALYSIS_CLOSE, paramData); + break; + case PERFORMANCE_ANALYSIS_SET_REPORTFILENAME: + errCode = conn_->Pragma(PRAGMA_PERFORMANCE_ANALYSIS_SET_REPORTFILENAME, paramData); + break; + default: + errCode = -E_NOT_SUPPORT; + break; + } + + if (errCode != E_OK) { + LOGE("[KvStoreDelegate] Pragma failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/interfaces/src/kv_store_delegate_impl.h b/mock/distributeddb/interfaces/src/kv_store_delegate_impl.h new file mode 100644 index 00000000..b2fcdea7 --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_delegate_impl.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_DELEGATE_IMPL_H +#define KV_STORE_DELEGATE_IMPL_H + +#ifndef OMIT_MULTI_VER +#include +#include +#include + +#include "store_types.h" +#include "ikvdb_connection.h" +#include "ikvdb_factory.h" +#include "kv_store_delegate.h" + +namespace DistributedDB { +class KvStoreDelegateImpl final : public KvStoreDelegate { +public: + KvStoreDelegateImpl(IKvDBConnection *conn, const std::string &storeId); + ~KvStoreDelegateImpl(); + + DISABLE_COPY_ASSIGN_MOVE(KvStoreDelegateImpl); + + // Used to Put a k-v pair to the kvstore. + // Return OK if the operation is successful. + DBStatus Put(const Key &key, const Value &value) override; + + // Used to Put a vector contains k-v pairs to the kvstore. + // Return OK if the operation is successful. + DBStatus PutBatch(const std::vector &entries) override; + + // Delete a record with the given key. + // Return OK if the operation is successful. + DBStatus Delete(const Key &key) override; + + // Batch delete records with the given keys. + // Return OK if the operation is successful. + DBStatus DeleteBatch(const std::vector &keys) override; + + // Delete all record of th kvstore. + // Return OK if the operation is successful. + DBStatus Clear() override; + + // Return a storeId of the KvStore instance + std::string GetStoreId() const override; + + // Get a snapshot of the kvstore. the observer is used to notify data changed, it can be null. + // return value is DBStatus and KvStoreSnapshotDelegate*, these values will be passed to the callback. + void GetKvStoreSnapshot(KvStoreObserver *observer, + const std::function &callback) override; + + // Release a snapshot, it will return OK if the operation is successful. + DBStatus ReleaseKvStoreSnapshot(KvStoreSnapshotDelegate *&snapshotDelegate) override; + + // Register a data change observer + DBStatus RegisterObserver(KvStoreObserver *observer) override; + + // Unregister a data change observer + DBStatus UnRegisterObserver(const KvStoreObserver *observer) override; + + // Start a transaction + DBStatus StartTransaction() override; + + // Commit a transaction + DBStatus Commit() override; + + // Rollback a transaction + DBStatus Rollback() override; + + // Used to set the resolution policy for conflicts. + // Return OK if operation is successful. + DBStatus SetConflictResolutionPolicy(ResolutionPolicyType type, const ConflictResolution &resolution) override; + + // Rekey the database. + DBStatus Rekey(const CipherPassword &password) override; + + // Empty passwords represent non-encrypted files. + // Export existing database files to a specified database file in the specified directory. + DBStatus Export(const std::string &filePath, const CipherPassword &passwd) override; + + // Import the existing database files to the specified database file in the specified directory. + DBStatus Import(const std::string &filePath, const CipherPassword &passwd) override; + + // Set release flag, KvStoreManagerDelegate will set when release the kvstore + void SetReleaseFlag(bool flag); + + // Close the KvStoreDelegateImpl + DBStatus Close(); + + // Special pragma interface, see PragmaCmd and PragmaData, + DBStatus Pragma(PragmaCmd cmd, PragmaData ¶mData) override; + +private: + IKvDBConnection *conn_; + std::string storeId_; + bool releaseFlag_; + std::mutex observerMapLock_; + std::map observerMap_; +}; +} // namespace DistributedDB + +#endif // KV_STORE_DELEGATE_IMPL_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/interfaces/src/kv_store_delegate_manager.cpp b/mock/distributeddb/interfaces/src/kv_store_delegate_manager.cpp new file mode 100644 index 00000000..add0fadb --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_delegate_manager.cpp @@ -0,0 +1,614 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kv_store_delegate_manager.h" + +#include +#include +#include +#include +#include + +#include "db_constant.h" +#include "platform_specific.h" +#include "log_print.h" +#include "db_common.h" +#include "db_dfx_adapter.h" +#include "kv_store_errno.h" +#include "kvdb_pragma.h" +#include "kvdb_properties.h" +#include "kvdb_manager.h" +#include "kv_store_nb_delegate_impl.h" +#include "network_adapter.h" +#include "runtime_context.h" +#include "param_check_utils.h" +#include "auto_launch.h" +#ifndef OMIT_MULTI_VER +#include "kv_store_delegate_impl.h" +#endif + +namespace DistributedDB { +const std::string KvStoreDelegateManager::DEFAULT_PROCESS_APP_ID = "default"; +std::mutex KvStoreDelegateManager::communicatorMutex_; +std::shared_ptr KvStoreDelegateManager::processCommunicator_ = nullptr; +std::mutex KvStoreDelegateManager::multiUserMutex_; + +namespace { + const int GET_CONNECT_RETRY = 3; + const int RETRY_GET_CONN_INTER = 30; + + IKvDBConnection *GetOneConnectionWithRetry(const KvDBProperties &properties, int &errCode) + { + for (int i = 0; i < GET_CONNECT_RETRY; i++) { + auto conn = KvDBManager::GetDatabaseConnection(properties, errCode); + if (conn != nullptr) { + return conn; + } + if (errCode == -E_STALE) { + std::this_thread::sleep_for(std::chrono::milliseconds(RETRY_GET_CONN_INTER)); + } else { + return nullptr; + } + } + return nullptr; + } + + DBStatus CheckAndGetSchema(bool isMemoryDb, const std::string &schema, SchemaObject &schemaObj) + { + if (isMemoryDb && !schema.empty()) { + LOGW("[KvStoreDelegateManager] memory database doesn't support the schema."); + return NOT_SUPPORT; + } + if (schema.empty()) { + return OK; + } + schemaObj.ParseFromSchemaString(schema); + if (!schemaObj.IsSchemaValid()) { + return INVALID_SCHEMA; + } + return OK; + } + + void InitPropWithNbOption(KvDBProperties &properties, const std::string &storePath, + const SchemaObject &schema, const KvStoreNbDelegate::Option &option) + { + properties.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, option.createIfNecessary); + properties.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + properties.SetBoolProp(KvDBProperties::MEMORY_MODE, option.isMemoryDb); + properties.SetBoolProp(KvDBProperties::ENCRYPTED_MODE, option.isEncryptedDb); + if (!option.isMemoryDb) { // memory db ignore store path + properties.SetStringProp(KvDBProperties::DATA_DIR, storePath); + } + properties.SetBoolProp(KvDBProperties::CREATE_DIR_BY_STORE_ID_ONLY, option.createDirByStoreIdOnly); + properties.SetSchema(schema); + properties.SetBoolProp(KvDBProperties::CHECK_INTEGRITY, option.isNeedIntegrityCheck); + properties.SetBoolProp(KvDBProperties::RM_CORRUPTED_DB, option.isNeedRmCorruptedDb); + if (RuntimeContext::GetInstance()->IsProcessSystemApiAdapterValid()) { + properties.SetIntProp(KvDBProperties::SECURITY_LABEL, option.secOption.securityLabel); + properties.SetIntProp(KvDBProperties::SECURITY_FLAG, option.secOption.securityFlag); + } + properties.SetIntProp(KvDBProperties::CONFLICT_RESOLVE_POLICY, option.conflictResolvePolicy); + + if (option.isEncryptedDb) { + properties.SetPassword(option.cipher, option.passwd); + } + properties.SetBoolProp(KvDBProperties::COMPRESS_ON_SYNC, option.isNeedCompressOnSync); + if (option.isNeedCompressOnSync) { + properties.SetIntProp(KvDBProperties::COMPRESSION_RATE, + ParamCheckUtils::GetValidCompressionRate(option.compressionRate)); + } + properties.SetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, option.syncDualTupleMode); + } + + bool CheckObserverConflictParam(const KvStoreNbDelegate::Option &option) + { + if ((option.notifier && !ParamCheckUtils::CheckConflictNotifierType(option.conflictType)) || + (!option.notifier && option.conflictType != 0)) { + LOGE("Invalid conflict type, conflict type is [%d]", option.conflictType); + return false; + } + if ((option.observer != nullptr && !ParamCheckUtils::CheckObserver(option.key, option.mode)) || + (option.observer == nullptr && (!option.key.empty() || option.mode != 0))) { + LOGE("Invalid observer param, observer mode is [%u]", option.mode); + return false; + } + return true; + } + +#ifndef OMIT_MULTI_VER + void InitPropWithOption(KvDBProperties &properties, const std::string &storePath, + const KvStoreDelegate::Option &option) + { + properties.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, option.createIfNecessary); + properties.SetBoolProp(KvDBProperties::CREATE_DIR_BY_STORE_ID_ONLY, option.createDirByStoreIdOnly); + properties.SetIntProp(KvDBProperties::DATABASE_TYPE, + ((option.localOnly == true) ? KvDBProperties::LOCAL_TYPE : KvDBProperties::MULTI_VER_TYPE)); + properties.SetBoolProp(KvDBProperties::MEMORY_MODE, false); + properties.SetBoolProp(KvDBProperties::ENCRYPTED_MODE, option.isEncryptedDb); + properties.SetStringProp(KvDBProperties::DATA_DIR, storePath); + if (option.isEncryptedDb) { + properties.SetPassword(option.cipher, option.passwd); + } + } +#endif +} + +KvStoreDelegateManager::KvStoreDelegateManager(const std::string &appId, const std::string &userId) + : appId_(appId), + userId_(userId) +{} + +KvStoreDelegateManager::~KvStoreDelegateManager() {} + +DBStatus KvStoreDelegateManager::SetKvStoreConfig(const KvStoreConfig &kvStoreConfig) +{ + std::string canonicalDir; + if (!IsDataDirSafe(kvStoreConfig.dataDir, canonicalDir)) { + return INVALID_ARGS; + } + if (!OS::CheckPathExistence(canonicalDir)) { + LOGE("[KvStoreMgr] Data dir doesn't exist or no perm"); + return INVALID_ARGS; + } + { + std::lock_guard lock(mutex_); + kvStoreConfig_ = kvStoreConfig; + kvStoreConfig_.dataDir = canonicalDir; + } + return OK; +} + +#ifndef OMIT_MULTI_VER +void KvStoreDelegateManager::GetKvStore(const std::string &storeId, const KvStoreDelegate::Option &option, + const std::function &callback) +{ + if (!callback) { + LOGE("[KvStoreMgr] Invalid callback for kv store!"); + return; + } + + // Multi version and local database mode not allow the creation of a memory database + if (!ParamCheckUtils::CheckStoreParameter(storeId, appId_, userId_) || GetKvStorePath().empty()) { + callback(INVALID_ARGS, nullptr); + return; + } + + if (option.isEncryptedDb) { + if (!ParamCheckUtils::CheckEncryptedParameter(option.cipher, option.passwd)) { + callback(INVALID_ARGS, nullptr); + return; + } + } + + KvDBProperties properties; + InitPropWithOption(properties, GetKvStorePath(), option); + DBCommon::SetDatabaseIds(properties, appId_, userId_, storeId); + + int errCode; + IKvDBConnection *conn = GetOneConnectionWithRetry(properties, errCode); + if (errCode == -E_INVALID_PASSWD_OR_CORRUPTED_DB) { + DBDfxAdapter::ReportFault( { DBDfxAdapter::EVENT_OPEN_DATABASE_FAILED, userId_, appId_, storeId, errCode } ); + } + if (conn == nullptr) { + DBStatus status = TransferDBErrno(errCode); + callback(status, nullptr); + return; + } + + auto kvStore = new (std::nothrow) KvStoreDelegateImpl(conn, storeId); + if (kvStore == nullptr) { + LOGE("[KvStoreMgr] Failed to alloc the delegate"); + conn->Close(); + conn = nullptr; + callback(DB_ERROR, nullptr); + return; + } + callback(OK, kvStore); +} +#endif + +DBStatus KvStoreDelegateManager::SetObserverNotifier(KvStoreNbDelegate *kvStore, + const KvStoreNbDelegate::Option &option) +{ + DBStatus status; + if (option.observer != nullptr) { + status = kvStore->RegisterObserver(option.key, option.mode, option.observer); + if (status != OK) { + LOGE("[KvStoreMgr] RegisterObserver failed."); + return status; + } + } + if (option.notifier != nullptr) { + status = kvStore->SetConflictNotifier(option.conflictType, option.notifier); + if (status != OK) { + LOGE("[KvStoreMgr] SetConflictNotifier failed."); + return status; + } + } + return OK; +} + +bool KvStoreDelegateManager::GetKvStoreParamCheck(const std::string &storeId, const KvStoreNbDelegate::Option &option, + const std::function &callback) const +{ + if (!callback) { + LOGE("[KvStoreMgr] Invalid callback for kv store"); + return false; + } + if (!ParamCheckUtils::CheckStoreParameter(storeId, appId_, userId_) || + (GetKvStorePath().empty() && !option.isMemoryDb)) { + LOGE("[KvStoreMgr] Invalid id or path info for the store"); + callback(INVALID_ARGS, nullptr); + return false; + } + + // check if want an encrypted db + if (option.isEncryptedDb) { + if (option.isMemoryDb) { + LOGE("Memory db not support encrypt!"); + callback(NOT_SUPPORT, nullptr); + return false; + } + if (!ParamCheckUtils::CheckEncryptedParameter(option.cipher, option.passwd)) { + callback(INVALID_ARGS, nullptr); + return false; + } + } + // check secOption + if (!option.isMemoryDb) { + if (!ParamCheckUtils::CheckSecOption(option.secOption)) { + callback(INVALID_ARGS, nullptr); + return false; + } + } else { + if (option.secOption.securityLabel != SecurityLabel::NOT_SET || + option.secOption.securityFlag != 0) { + LOGE("Memory db has no physical files, Is not controlled by security labels, so not support set labels"); + callback(INVALID_ARGS, nullptr); + return false; + } + } + + if (!CheckObserverConflictParam(option)) { + callback(INVALID_ARGS, nullptr); + return false; + } + return true; +} + +void KvStoreDelegateManager::GetKvStore(const std::string &storeId, const KvStoreNbDelegate::Option &option, + const std::function &callback) +{ + if (!GetKvStoreParamCheck(storeId, option, callback)) { + return; + } + // check if schema is supported and valid + SchemaObject schema; + DBStatus retCode = CheckAndGetSchema(option.isMemoryDb, option.schema, schema); + if (retCode != OK) { + callback(retCode, nullptr); + return; + } + KvDBProperties properties; + InitPropWithNbOption(properties, GetKvStorePath(), schema, option); + DBCommon::SetDatabaseIds(properties, appId_, userId_, storeId); + + int errCode; + IKvDBConnection *conn = GetOneConnectionWithRetry(properties, errCode); + if (errCode == -E_INVALID_PASSWD_OR_CORRUPTED_DB) { + DBDfxAdapter::ReportFault( { DBDfxAdapter::EVENT_OPEN_DATABASE_FAILED, userId_, appId_, storeId, errCode } ); + } + DBStatus status = TransferDBErrno(errCode); + if (conn == nullptr) { + callback(status, nullptr); + return; + } + + auto kvStore = new (std::nothrow) KvStoreNbDelegateImpl(conn, storeId); + if (kvStore == nullptr) { + conn->Close(); + conn = nullptr; + callback(DB_ERROR, nullptr); + return; + } + + status = SetObserverNotifier(kvStore, option); + if (status != OK) { + CloseKvStore(kvStore); + callback(status, nullptr); + return; + } + + bool enAutoSync = false; + (void)conn->Pragma(PRAGMA_AUTO_SYNC, static_cast(&enAutoSync)); + + SecurityOption secOption = option.secOption; + (void)conn->Pragma(PRAGMA_TRIGGER_TO_MIGRATE_DATA, &secOption); + + callback(OK, kvStore); +} + +#ifndef OMIT_MULTI_VER +DBStatus KvStoreDelegateManager::CloseKvStore(KvStoreDelegate *kvStore) +{ + if (kvStore == nullptr) { + return INVALID_ARGS; + } + + auto kvStoreImpl = static_cast(kvStore); + DBStatus status = kvStoreImpl->Close(); + if (status == BUSY) { + LOGD("DelegateImpl is busy now."); + return BUSY; + } + + kvStoreImpl->SetReleaseFlag(true); + delete kvStore; + kvStore = nullptr; + return OK; +} +#endif + +DBStatus KvStoreDelegateManager::CloseKvStore(KvStoreNbDelegate *kvStore) +{ + if (kvStore == nullptr) { + return INVALID_ARGS; + } + + auto kvStoreImpl = static_cast(kvStore); + DBStatus status = kvStoreImpl->Close(); + if (status == BUSY) { + LOGD("NbDelegateImpl is busy now."); + return BUSY; + } + kvStoreImpl->SetReleaseFlag(true); + delete kvStore; + kvStore = nullptr; + return OK; +} + +DBStatus KvStoreDelegateManager::DeleteKvStore(const std::string &storeId) +{ + if (!ParamCheckUtils::IsStoreIdSafe(storeId) || GetKvStorePath().empty()) { + LOGE("Invalid store info for deleting"); + return INVALID_ARGS; + } + + KvDBProperties properties; + properties.SetStringProp(KvDBProperties::DATA_DIR, GetKvStorePath()); + DBCommon::SetDatabaseIds(properties, appId_, userId_, storeId); + int errCode = KvDBManager::RemoveDatabase(properties); + if (errCode == E_OK) { + LOGI("Database deleted successfully!"); + return OK; + } + LOGE("Delete the kv store error:%d", errCode); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreDelegateManager::SetProcessLabel(const std::string &appId, const std::string &userId) +{ + if (appId.size() > DBConstant::MAX_APP_ID_LENGTH || appId.empty() || + userId.size() > DBConstant::MAX_USER_ID_LENGTH || userId.empty()) { + LOGE("Invalid app or user info[%zu]-[%zu]", appId.length(), userId.length()); + return INVALID_ARGS; + } + + int errCode = KvDBManager::SetProcessLabel(appId, userId); + if (errCode != E_OK) { + LOGE("Failed to set the process label:%d", errCode); + return DB_ERROR; + } + return OK; +} + +DBStatus KvStoreDelegateManager::SetProcessCommunicator(const std::shared_ptr &inCommunicator) +{ + std::lock_guard lock(communicatorMutex_); + if (processCommunicator_ != nullptr) { + LOGE("processCommunicator_ is not null!"); + return DB_ERROR; + } + + std::string processLabel = RuntimeContext::GetInstance()->GetProcessLabel(); + if (processLabel.empty()) { + LOGE("ProcessLabel is not set!"); + return DB_ERROR; + } + + NetworkAdapter *adapter = new (std::nothrow) NetworkAdapter(processLabel, inCommunicator); + if (adapter == nullptr) { + LOGE("New NetworkAdapter failed!"); + return DB_ERROR; + } + processCommunicator_ = inCommunicator; + if (RuntimeContext::GetInstance()->SetCommunicatorAdapter(adapter) != E_OK) { + LOGE("SetProcessCommunicator not support!"); + delete adapter; + return DB_ERROR; + } + KvDBManager::RestoreSyncableKvStore(); + return OK; +} + +DBStatus KvStoreDelegateManager::GetKvStoreDiskSize(const std::string &storeId, uint64_t &size) +{ + std::string dataDir = GetKvStorePath(); + if (!ParamCheckUtils::CheckStoreParameter(storeId, appId_, userId_)) { + LOGE("[KvStoreMgr] Invalid store info for size"); + return INVALID_ARGS; + } + KvDBProperties properties; + properties.SetStringProp(KvDBProperties::DATA_DIR, dataDir); + DBCommon::SetDatabaseIds(properties, appId_, userId_, storeId); + int errCode = KvDBManager::CalculateKvStoreSize(properties, size); + if (errCode != E_OK) { + if (errCode == -E_NOT_FOUND) { + return NOT_FOUND; + } + + LOGE("[KvStoreMgr] Get the file size failed[%d]", errCode); + return DB_ERROR; + } + return OK; +} + +void KvStoreDelegateManager::SetKvStoreCorruptionHandler(const KvStoreCorruptionHandler &handler) +{ + KvDBManager::SetDatabaseCorruptionHandler(handler); +} + +DBStatus KvStoreDelegateManager::GetDatabaseDir(const std::string &storeId, const std::string &appId, + const std::string &userId, std::string &directory) +{ + if (!ParamCheckUtils::CheckStoreParameter(storeId, appId, userId)) { + return INVALID_ARGS; + } + + std::string identifier = DBCommon::GenerateIdentifierId(storeId, appId, userId); + std::string dir = DBCommon::TransferHashString(identifier); + if (dir.empty()) { + return DB_ERROR; + } + directory = DBCommon::TransferStringToHex(dir); + return OK; +} + +DBStatus KvStoreDelegateManager::GetDatabaseDir(const std::string &storeId, std::string &directory) +{ + if (!ParamCheckUtils::IsStoreIdSafe(storeId)) { + return INVALID_ARGS; + } + + if (storeId.find(DBConstant::ID_CONNECTOR) != std::string::npos) { + return INVALID_ARGS; + } + + std::string dir = DBCommon::TransferHashString(storeId); + if (dir.empty()) { + return DB_ERROR; + } + directory = DBCommon::TransferStringToHex(dir); + return OK; +} + +// private +bool KvStoreDelegateManager::IsDataDirSafe(const std::string &dataDir, std::string &canonicalDir) const +{ + return ParamCheckUtils::CheckDataDir(dataDir, canonicalDir); +} + +const std::string &KvStoreDelegateManager::GetKvStorePath() const +{ + std::lock_guard lock(mutex_); + return kvStoreConfig_.dataDir; +} + +DBStatus KvStoreDelegateManager::SetPermissionCheckCallback(const PermissionCheckCallback &callback) +{ + int errCode = RuntimeContext::GetInstance()->SetPermissionCheckCallback(callback); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreDelegateManager::SetPermissionCheckCallback(const PermissionCheckCallbackV2 &callback) +{ + int errCode = RuntimeContext::GetInstance()->SetPermissionCheckCallback(callback); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreDelegateManager::EnableKvStoreAutoLaunch(const std::string &userId, const std::string &appId, + const std::string &storeId, const AutoLaunchOption &option, const AutoLaunchNotifier ¬ifier) +{ + if (RuntimeContext::GetInstance() == nullptr) { + return DB_ERROR; + } + AutoLaunchParam param{ userId, appId, storeId, option, notifier, {}}; + std::shared_ptr ptr = std::make_shared(); + int errCode = AutoLaunch::GetAutoLaunchProperties(param, DBType::DB_KV, true, ptr); + if (errCode != E_OK) { + LOGE("[KvStoreManager] Enable auto launch failed:%d", errCode); + return TransferDBErrno(errCode); + } + + std::shared_ptr kvPtr = std::static_pointer_cast(ptr); + errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(*kvPtr, notifier, option); + if (errCode != E_OK) { + LOGE("[KvStoreManager] Enable auto launch failed:%d", errCode); + return TransferDBErrno(errCode); + } + LOGI("[KvStoreManager] Enable auto launch"); + return OK; +} + +DBStatus KvStoreDelegateManager::DisableKvStoreAutoLaunch(const std::string &userId, const std::string &appId, + const std::string &storeId) +{ + if (RuntimeContext::GetInstance() == nullptr) { + return DB_ERROR; + } + + std::string syncIdentifier = DBCommon::GenerateIdentifierId(storeId, appId, userId); + std::string hashIdentifier = DBCommon::TransferHashString(syncIdentifier); + std::string dualIdentifier = DBCommon::TransferHashString(DBCommon::GenerateDualTupleIdentifierId(storeId, appId)); + int errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(hashIdentifier, dualIdentifier, userId); + if (errCode != E_OK) { + LOGE("[KvStoreManager] Disable auto launch failed:%d", errCode); + return TransferDBErrno(errCode); + } + LOGI("[KvStoreManager] Disable auto launch"); + return OK; +} + +void KvStoreDelegateManager::SetAutoLaunchRequestCallback(const AutoLaunchRequestCallback &callback) +{ + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback(callback, DBType::DB_KV); +} + +std::string KvStoreDelegateManager::GetKvStoreIdentifier(const std::string &userId, const std::string &appId, + const std::string &storeId, bool syncDualTupleMode) +{ + if (!ParamCheckUtils::CheckStoreParameter(storeId, appId, userId, syncDualTupleMode)) { + return ""; + } + if (syncDualTupleMode) { + return DBCommon::TransferHashString(appId + "-" + storeId); + } + return DBCommon::TransferHashString(userId + "-" + appId + "-" + storeId); +} + +DBStatus KvStoreDelegateManager::SetProcessSystemAPIAdapter(const std::shared_ptr &adapter) +{ + return TransferDBErrno(RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(adapter)); +} + +void KvStoreDelegateManager::SetStoreStatusNotifier(const StoreStatusNotifier ¬ifier) +{ + RuntimeContext::GetInstance()->SetStoreStatusNotifier(notifier); +} + +DBStatus KvStoreDelegateManager::SetSyncActivationCheckCallback(const SyncActivationCheckCallback &callback) +{ + std::lock_guard lock(multiUserMutex_); + int errCode = RuntimeContext::GetInstance()->SetSyncActivationCheckCallback(callback); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreDelegateManager::NotifyUserChanged() +{ + std::lock_guard lock(multiUserMutex_); + int errCode = RuntimeContext::GetInstance()->NotifyUserChanged(); + return TransferDBErrno(errCode); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/interfaces/src/kv_store_errno.cpp b/mock/distributeddb/interfaces/src/kv_store_errno.cpp new file mode 100644 index 00000000..77384a55 --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_errno.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kv_store_errno.h" +#include "db_errno.h" + +namespace DistributedDB { +struct DBErrnoPair { + int errCode; + DBStatus status; +}; + +namespace { + const DBErrnoPair ERRNO_MAP[] = { + { E_OK, OK }, + { -E_BUSY, BUSY }, + { -E_NOT_FOUND, NOT_FOUND }, + { -E_INVALID_ARGS, INVALID_ARGS }, + { -E_TIMEOUT, TIME_OUT }, + { -E_NOT_SUPPORT, NOT_SUPPORT }, + { -E_INVALID_PASSWD_OR_CORRUPTED_DB, INVALID_PASSWD_OR_CORRUPTED_DB }, + { -E_MAX_LIMITS, OVER_MAX_LIMITS }, + { -E_INVALID_FILE, INVALID_FILE }, + { -E_INVALID_PATH, NO_PERMISSION }, + { -E_READ_ONLY, READ_ONLY }, + { -E_INVALID_SCHEMA, INVALID_SCHEMA }, + { -E_SCHEMA_MISMATCH, SCHEMA_MISMATCH }, + { -E_SCHEMA_VIOLATE_VALUE, SCHEMA_VIOLATE_VALUE }, + { -E_VALUE_MISMATCH_FEILD_COUNT, INVALID_VALUE_FIELDS }, + { -E_VALUE_MISMATCH_FEILD_TYPE, INVALID_FIELD_TYPE }, + { -E_VALUE_MISMATCH_CONSTRAINT, CONSTRAIN_VIOLATION }, + { -E_INVALID_FORMAT, INVALID_FORMAT }, + { -E_STALE, STALE }, + { -E_LOCAL_DELETED, LOCAL_DELETED }, + { -E_LOCAL_DEFEAT, LOCAL_DEFEAT }, + { -E_LOCAL_COVERED, LOCAL_COVERED }, + { -E_INVALID_QUERY_FORMAT, INVALID_QUERY_FORMAT }, + { -E_INVALID_QUERY_FIELD, INVALID_QUERY_FIELD }, + { -E_ALREADY_SET, ALREADY_SET }, + { -E_EKEYREVOKED, EKEYREVOKED_ERROR }, + { -E_SECURITY_OPTION_CHECK_ERROR, SECURITY_OPTION_CHECK_ERROR }, + { -E_INTERCEPT_DATA_FAIL, INTERCEPT_DATA_FAIL }, + { -E_LOG_OVER_LIMITS, LOG_OVER_LIMITS }, + { -E_DISTRIBUTED_SCHEMA_NOT_FOUND, DISTRIBUTED_SCHEMA_NOT_FOUND}, + { -E_DISTRIBUTED_SCHEMA_CHANGED, DISTRIBUTED_SCHEMA_CHANGED}, + { -E_MODE_MISMATCH, MODE_MISMATCH}, + { -E_NO_NEED_ACTIVE, NOT_ACTIVE}, + }; +} + +DBStatus TransferDBErrno(int err) +{ + for (const auto &item : ERRNO_MAP) { + if (item.errCode == err) { + return item.status; + } + } + return DB_ERROR; +} +}; diff --git a/mock/distributeddb/interfaces/src/kv_store_nb_conflict_data_impl.cpp b/mock/distributeddb/interfaces/src/kv_store_nb_conflict_data_impl.cpp new file mode 100644 index 00000000..36e8b0bd --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_nb_conflict_data_impl.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kv_store_nb_conflict_data_impl.h" + +namespace DistributedDB { +KvStoreNbConflictDataImpl::KvStoreNbConflictDataImpl() {} + +KvStoreNbConflictDataImpl::~KvStoreNbConflictDataImpl() {} + +KvStoreNbConflictType KvStoreNbConflictDataImpl::GetType() const +{ + return static_cast(data_.type); +} + +void KvStoreNbConflictDataImpl::GetKey(Key &key) const +{ + key = data_.key; +} + +DBStatus KvStoreNbConflictDataImpl::GetValue(ValueType type, Value &value) const +{ + if (IsDeleted(type)) { + return DB_ERROR; + } + + if (type == ValueType::OLD_VALUE) { + value = data_.oldData.value; + } else { + value = data_.newData.value; + } + + return OK; +} + +bool KvStoreNbConflictDataImpl::IsDeleted(ValueType type) const +{ + if (type == ValueType::OLD_VALUE) { + return data_.oldData.isDeleted; + } else { + return data_.newData.isDeleted; + } +} + +bool KvStoreNbConflictDataImpl::IsNative(ValueType type) const +{ + if (type == ValueType::OLD_VALUE) { + return data_.oldData.isLocal; + } else { + return data_.newData.isLocal; + } +} + +void KvStoreNbConflictDataImpl::SetConflictData(const KvDBConflictEntry &conflictData) +{ + data_ = conflictData; +} +} diff --git a/mock/distributeddb/interfaces/src/kv_store_nb_conflict_data_impl.h b/mock/distributeddb/interfaces/src/kv_store_nb_conflict_data_impl.h new file mode 100644 index 00000000..ff382f7c --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_nb_conflict_data_impl.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_NB_CONFLICT_DATA_IMPL_H +#define KV_STORE_NB_CONFLICT_DATA_IMPL_H + +#include "db_types.h" +#include "kvdb_conflict_entry.h" +#include "kv_store_nb_conflict_data.h" + +namespace DistributedDB { +class KvStoreNbConflictDataImpl final : public KvStoreNbConflictData { +public: + KvStoreNbConflictDataImpl(); + ~KvStoreNbConflictDataImpl(); + + KvStoreNbConflictType GetType() const override; + + void GetKey(Key &key) const override; + + DBStatus GetValue(ValueType type, Value &value) const override; + + bool IsDeleted(ValueType type) const override; + + bool IsNative(ValueType type) const override; + + void SetConflictData(const KvDBConflictEntry &conflictData); + +private: + KvDBConflictEntry data_; +}; +} // namespace DistributedDB + +#endif // KV_STORE_NB_CONFLICT_DATA_IMPL_H diff --git a/mock/distributeddb/interfaces/src/kv_store_nb_delegate_impl.cpp b/mock/distributeddb/interfaces/src/kv_store_nb_delegate_impl.cpp new file mode 100644 index 00000000..2cabf7ac --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_nb_delegate_impl.cpp @@ -0,0 +1,917 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kv_store_nb_delegate_impl.h" + +#include +#include + +#include "platform_specific.h" +#include "log_print.h" +#include "db_constant.h" +#include "db_errno.h" +#include "db_types.h" +#include "param_check_utils.h" +#include "store_types.h" +#include "kvdb_pragma.h" +#include "kvdb_manager.h" +#include "kv_store_errno.h" +#include "kv_store_observer.h" +#include "kv_store_changed_data_impl.h" +#include "kv_store_nb_conflict_data_impl.h" +#include "kv_store_result_set_impl.h" +#include "sync_operation.h" +#include "performance_analysis.h" + +namespace DistributedDB { +namespace { + struct PragmaCmdPair { + int externCmd = 0; + int innerCmd = 0; + }; + + const PragmaCmdPair g_pragmaMap[] = { + {GET_DEVICE_IDENTIFIER_OF_ENTRY, PRAGMA_GET_DEVICE_IDENTIFIER_OF_ENTRY}, + {AUTO_SYNC, PRAGMA_AUTO_SYNC}, + {PERFORMANCE_ANALYSIS_GET_REPORT, PRAGMA_PERFORMANCE_ANALYSIS_GET_REPORT}, + {PERFORMANCE_ANALYSIS_OPEN, PRAGMA_PERFORMANCE_ANALYSIS_OPEN}, + {PERFORMANCE_ANALYSIS_CLOSE, PRAGMA_PERFORMANCE_ANALYSIS_CLOSE}, + {PERFORMANCE_ANALYSIS_SET_REPORTFILENAME, PRAGMA_PERFORMANCE_ANALYSIS_SET_REPORTFILENAME}, + {GET_IDENTIFIER_OF_DEVICE, PRAGMA_GET_IDENTIFIER_OF_DEVICE}, + {GET_QUEUED_SYNC_SIZE, PRAGMA_GET_QUEUED_SYNC_SIZE}, + {SET_QUEUED_SYNC_LIMIT, PRAGMA_SET_QUEUED_SYNC_LIMIT}, + {GET_QUEUED_SYNC_LIMIT, PRAGMA_GET_QUEUED_SYNC_LIMIT}, + {SET_WIPE_POLICY, PRAGMA_SET_WIPE_POLICY}, + {RESULT_SET_CACHE_MODE, PRAGMA_RESULT_SET_CACHE_MODE}, + {RESULT_SET_CACHE_MAX_SIZE, PRAGMA_RESULT_SET_CACHE_MAX_SIZE}, + {SET_SYNC_RETRY, PRAGMA_SET_SYNC_RETRY}, + {SET_MAX_LOG_LIMIT, PRAGMA_SET_MAX_LOG_LIMIT}, + {EXEC_CHECKPOINT, PRAGMA_EXEC_CHECKPOINT}, + }; + + const std::string INVALID_CONNECTION = "[KvStoreNbDelegate] Invalid connection for operation"; +} + +KvStoreNbDelegateImpl::KvStoreNbDelegateImpl(IKvDBConnection *conn, const std::string &storeId) + : conn_(conn), + storeId_(storeId), + releaseFlag_(false) +{} + +KvStoreNbDelegateImpl::~KvStoreNbDelegateImpl() +{ + if (!releaseFlag_) { + LOGF("[KvStoreNbDelegate] Can't release directly"); + return; + } + + conn_ = nullptr; +} + +DBStatus KvStoreNbDelegateImpl::Get(const Key &key, Value &value) const +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + return GetInner(option, key, value); +} + +DBStatus KvStoreNbDelegateImpl::GetEntries(const Key &keyPrefix, std::vector &entries) const +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + return GetEntriesInner(option, keyPrefix, entries); +} + +DBStatus KvStoreNbDelegateImpl::GetEntries(const Key &keyPrefix, KvStoreResultSet *&resultSet) const +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + IOption option; + option.dataType = IOption::SYNC_DATA; + IKvDBResultSet *kvDbResultSet = nullptr; + int errCode = conn_->GetResultSet(option, keyPrefix, kvDbResultSet); + if (errCode == E_OK) { + resultSet = new (std::nothrow) KvStoreResultSetImpl(kvDbResultSet); + if (resultSet != nullptr) { + return OK; + } + + LOGE("[KvStoreNbDelegate] Alloc result set failed."); + conn_->ReleaseResultSet(kvDbResultSet); + kvDbResultSet = nullptr; + return DB_ERROR; + } + + LOGE("[KvStoreNbDelegate] Get result set failed: %d", errCode); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::GetEntries(const Query &query, std::vector &entries) const +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + if (conn_ != nullptr) { + int errCode = conn_->GetEntries(option, query, entries); + if (errCode == E_OK) { + return OK; + } else if (errCode == -E_NOT_FOUND) { + LOGD("[KvStoreNbDelegate] Not found the data by query"); + return NOT_FOUND; + } + + LOGE("[KvStoreNbDelegate] Get the batch data by query err:%d", errCode); + return TransferDBErrno(errCode); + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; +} + +DBStatus KvStoreNbDelegateImpl::GetEntries(const Query &query, KvStoreResultSet *&resultSet) const +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + IOption option; + option.dataType = IOption::SYNC_DATA; + IKvDBResultSet *kvDbResultSet = nullptr; + int errCode = conn_->GetResultSet(option, query, kvDbResultSet); + if (errCode == E_OK) { + resultSet = new (std::nothrow) KvStoreResultSetImpl(kvDbResultSet); + if (resultSet != nullptr) { + return OK; + } + + LOGE("[KvStoreNbDelegate] Alloc result set failed."); + conn_->ReleaseResultSet(kvDbResultSet); + kvDbResultSet = nullptr; + return DB_ERROR; + } + + LOGE("[KvStoreNbDelegate] Get result set for query failed: %d", errCode); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::GetCount(const Query &query, int &count) const +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + IOption option; + option.dataType = IOption::SYNC_DATA; + int errCode = conn_->GetCount(option, query, count); + if (errCode == E_OK) { + if (count == 0) { + return NOT_FOUND; + } + return OK; + } + + LOGE("[KvStoreNbDelegate] Get count for query failed: %d", errCode); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::CloseResultSet(KvStoreResultSet *&resultSet) +{ + if (resultSet == nullptr) { + return INVALID_ARGS; + } + + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + // release inner result set + IKvDBResultSet *kvDbResultSet = nullptr; + (static_cast(resultSet))->GetResultSet(kvDbResultSet); + conn_->ReleaseResultSet(kvDbResultSet); + // release external result set + delete resultSet; + resultSet = nullptr; + return OK; +} + +DBStatus KvStoreNbDelegateImpl::Put(const Key &key, const Value &value) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + return PutInner(option, key, value); +} + +DBStatus KvStoreNbDelegateImpl::PutBatch(const std::vector &entries) +{ + if (conn_ != nullptr) { + IOption option; + option.dataType = IOption::SYNC_DATA; + int errCode = conn_->PutBatch(option, entries); + if (errCode == E_OK) { + return OK; + } + + LOGE("[KvStoreNbDelegate] Put batch data failed:%d", errCode); + return TransferDBErrno(errCode); + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; +} + +DBStatus KvStoreNbDelegateImpl::DeleteBatch(const std::vector &keys) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + IOption option; + option.dataType = IOption::SYNC_DATA; + int errCode = conn_->DeleteBatch(option, keys); + if (errCode == E_OK || errCode == -E_NOT_FOUND) { + return OK; + } + + LOGE("[KvStoreNbDelegate] Delete batch data failed:%d", errCode); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::Delete(const Key &key) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + return DeleteInner(option, key); +} + +DBStatus KvStoreNbDelegateImpl::GetLocal(const Key &key, Value &value) const +{ + IOption option; + option.dataType = IOption::LOCAL_DATA; + return GetInner(option, key, value); +} + +DBStatus KvStoreNbDelegateImpl::GetLocalEntries(const Key &keyPrefix, std::vector &entries) const +{ + IOption option; + option.dataType = IOption::LOCAL_DATA; + return GetEntriesInner(option, keyPrefix, entries); +} + +DBStatus KvStoreNbDelegateImpl::PutLocal(const Key &key, const Value &value) +{ + IOption option; + option.dataType = IOption::LOCAL_DATA; + return PutInner(option, key, value); +} + +DBStatus KvStoreNbDelegateImpl::DeleteLocal(const Key &key) +{ + IOption option; + option.dataType = IOption::LOCAL_DATA; + return DeleteInner(option, key); +} + +DBStatus KvStoreNbDelegateImpl::PublishLocal(const Key &key, bool deleteLocal, bool updateTimestamp, + const KvStoreNbPublishOnConflict &onConflict) +{ + if (key.empty() || key.size() > DBConstant::MAX_KEY_SIZE) { + LOGW("[KvStoreNbDelegate][Publish] Invalid para"); + return INVALID_ARGS; + } + + if (conn_ != nullptr) { + PragmaPublishInfo publishInfo{ key, deleteLocal, updateTimestamp, onConflict }; + int errCode = conn_->Pragma(PRAGMA_PUBLISH_LOCAL, static_cast(&publishInfo)); + if (errCode != E_OK) { + LOGD("[KvStoreNbDelegate] Publish local err:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; +} + +DBStatus KvStoreNbDelegateImpl::UnpublishToLocal(const Key &key, bool deletePublic, bool updateTimestamp) +{ + if (key.empty() || key.size() > DBConstant::MAX_KEY_SIZE) { + LOGW("[KvStoreNbDelegate][Unpublish] Invalid para"); + return INVALID_ARGS; + } + + if (conn_ != nullptr) { + PragmaUnpublishInfo unpublishInfo{ key, deletePublic, updateTimestamp }; + int errCode = conn_->Pragma(PRAGMA_UNPUBLISH_SYNC, static_cast(&unpublishInfo)); + if (errCode != E_OK) { + LOGD("[KvStoreNbDelegate] Unpublish result:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; + } + + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; +} + +DBStatus KvStoreNbDelegateImpl::PutLocalBatch(const std::vector &entries) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + IOption option; + option.dataType = IOption::LOCAL_DATA; + int errCode = conn_->PutBatch(option, entries); + if (errCode != E_OK) { + LOGE("[KvStoreNbDelegate] Put local batch data failed:%d", errCode); + return TransferDBErrno(errCode); + } + + return OK; +} + +DBStatus KvStoreNbDelegateImpl::DeleteLocalBatch(const std::vector &keys) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + IOption option; + option.dataType = IOption::LOCAL_DATA; + int errCode = conn_->DeleteBatch(option, keys); + if (errCode == E_OK || errCode == -E_NOT_FOUND) { + return OK; + } + + LOGE("[KvStoreNbDelegate] Delete local batch data failed:%d", errCode); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::RegisterObserver(const Key &key, unsigned int mode, KvStoreObserver *observer) +{ + if (key.size() > DBConstant::MAX_KEY_SIZE) { + return INVALID_ARGS; + } + + if (!ParamCheckUtils::CheckObserver(key, mode)) { + LOGE("Register nb observer by illegal mode or key size!"); + return INVALID_ARGS; + } + + if (observer == nullptr) { + return INVALID_ARGS; + } + + std::lock_guard lockGuard(observerMapLock_); + if (observerMap_.find(observer) != observerMap_.end()) { + LOGE("[KvStoreNbDelegate] Observer has been already registered!"); + return DB_ERROR; + } + + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + if (conn_->IsTransactionStarted()) { + return BUSY; + } + + int errCode = E_OK; + KvDBObserverHandle *observerHandle = conn_->RegisterObserver( + mode, key, + [observer](const KvDBCommitNotifyData ¬ifyData) { + KvStoreChangedDataImpl data(¬ifyData); + observer->OnChange(data); + }, + errCode); + + if (errCode != E_OK || observerHandle == nullptr) { + LOGE("[KvStoreNbDelegate] RegisterListener failed:%d!", errCode); + return DB_ERROR; + } + + observerMap_.insert(std::pair(observer, observerHandle)); + LOGI("[KvStoreNbDelegate] RegisterObserver ok mode:%u", mode); + return OK; +} + +DBStatus KvStoreNbDelegateImpl::UnRegisterObserver(const KvStoreObserver *observer) +{ + if (observer == nullptr) { + return INVALID_ARGS; + } + + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + std::lock_guard lockGuard(observerMapLock_); + auto iter = observerMap_.find(observer); + if (iter == observerMap_.end()) { + LOGE("[KvStoreNbDelegate] Observer has not been registered!"); + return NOT_FOUND; + } + + const KvDBObserverHandle *observerHandle = iter->second; + int errCode = conn_->UnRegisterObserver(observerHandle); + if (errCode != E_OK) { + LOGE("[KvStoreNbDelegate] UnRegistObserver failed:%d!", errCode); + return DB_ERROR; + } + observerMap_.erase(iter); + return OK; +} + +DBStatus KvStoreNbDelegateImpl::RemoveDeviceData(const std::string &device) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + int errCode = conn_->Pragma(PRAGMA_RM_DEVICE_DATA, + const_cast(static_cast(&device))); + if (errCode != E_OK) { + LOGE("[KvStoreNbDelegate] Remove device data failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +std::string KvStoreNbDelegateImpl::GetStoreId() const +{ + return storeId_; +} + +DBStatus KvStoreNbDelegateImpl::Sync(const std::vector &devices, SyncMode mode, + const std::function &devicesMap)> &onComplete, + bool wait = false) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + PragmaSync pragmaData(devices, mode, std::bind(&KvStoreNbDelegateImpl::OnSyncComplete, + this, std::placeholders::_1, onComplete), wait); + int errCode = conn_->Pragma(PRAGMA_SYNC_DEVICES, &pragmaData); + if (errCode < E_OK) { + LOGE("[KvStoreNbDelegate] Sync data failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus KvStoreNbDelegateImpl::Sync(const std::vector &devices, SyncMode mode, + const std::function &devicesMap)> &onComplete, + const Query &query, bool wait) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + QuerySyncObject querySyncObj(query); + PragmaSync pragmaData(devices, mode, querySyncObj, std::bind(&KvStoreNbDelegateImpl::OnSyncComplete, + this, std::placeholders::_1, onComplete), wait); + int errCode = conn_->Pragma(PRAGMA_SYNC_DEVICES, &pragmaData); + if (errCode < E_OK) { + LOGE("[KvStoreNbDelegate] QuerySync data failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus KvStoreNbDelegateImpl::Pragma(PragmaCmd cmd, PragmaData ¶mData) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + int errCode = -E_NOT_SUPPORT; + for (const auto &item : g_pragmaMap) { + if (item.externCmd == cmd) { + errCode = conn_->Pragma(item.innerCmd, paramData); + break; + } + } + + if (errCode != E_OK) { + LOGE("[KvStoreNbDelegate] Pragma failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus KvStoreNbDelegateImpl::SetConflictNotifier(int conflictType, const KvStoreNbConflictNotifier ¬ifier) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + if (!ParamCheckUtils::CheckConflictNotifierType(conflictType)) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return INVALID_ARGS; + } + + int errCode; + if (!notifier) { + errCode = conn_->SetConflictNotifier(conflictType, nullptr); + goto END; + } + + errCode = conn_->SetConflictNotifier(conflictType, + [conflictType, notifier](const KvDBCommitNotifyData &data) { + int resultCode; + const std::list entries = data.GetCommitConflicts(resultCode); + if (resultCode != E_OK) { + LOGE("Get commit conflicted entries failed:%d!", resultCode); + return; + } + + for (const auto &entry : entries) { + // Prohibit signed numbers to perform bit operations + uint32_t entryType = static_cast(entry.type); + uint32_t type = static_cast(conflictType); + if (entryType & type) { + KvStoreNbConflictDataImpl dataImpl; + dataImpl.SetConflictData(entry); + notifier(dataImpl); + } + } + }); + +END: + if (errCode != E_OK) { + LOGE("[KvStoreNbDelegate] Register conflict failed:%d!", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus KvStoreNbDelegateImpl::Rekey(const CipherPassword &password) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + int errCode = conn_->Rekey(password); + if (errCode == E_OK) { + return OK; + } + + LOGE("[KvStoreNbDelegate] Rekey failed:%d", errCode); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::Export(const std::string &filePath, const CipherPassword &passwd) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + std::string fileDir; + std::string fileName; + OS::SplitFilePath(filePath, fileDir, fileName); + + std::string canonicalUrl; + if (!ParamCheckUtils::CheckDataDir(fileDir, canonicalUrl)) { + return INVALID_ARGS; + } + + if (!OS::CheckPathExistence(canonicalUrl)) { + return NO_PERMISSION; + } + + canonicalUrl = canonicalUrl + "/" + fileName; + if (OS::CheckPathExistence(canonicalUrl)) { + return FILE_ALREADY_EXISTED; + } + + int errCode = conn_->Export(canonicalUrl, passwd); + if (errCode == E_OK) { + return OK; + } + LOGE("[KvStoreNbDelegate] Export failed:%d", errCode); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::Import(const std::string &filePath, const CipherPassword &passwd) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + std::string fileDir; + std::string fileName; + OS::SplitFilePath(filePath, fileDir, fileName); + + std::string canonicalUrl; + if (!ParamCheckUtils::CheckDataDir(fileDir, canonicalUrl)) { + return INVALID_ARGS; + } + + canonicalUrl = canonicalUrl + "/" + fileName; + if (!OS::CheckPathExistence(canonicalUrl)) { + LOGE("Import file path err, DBStatus = INVALID_FILE errno = [%d]", errno); + return INVALID_FILE; + } + + int errCode = conn_->Import(canonicalUrl, passwd); + if (errCode == E_OK) { + LOGI("[KvStoreNbDelegate] Import ok"); + return OK; + } + + LOGE("[KvStoreNbDelegate] Import failed:%d", errCode); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::StartTransaction() +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + int errCode = conn_->StartTransaction(); + if (errCode != E_OK) { + LOGE("[KvStoreNbDelegate] StartTransaction failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus KvStoreNbDelegateImpl::Commit() +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + int errCode = conn_->Commit(); + if (errCode != E_OK) { + LOGE("[KvStoreNbDelegate] Commit failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus KvStoreNbDelegateImpl::Rollback() +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + int errCode = conn_->RollBack(); + if (errCode != E_OK) { + LOGE("[KvStoreNbDelegate] Rollback failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +void KvStoreNbDelegateImpl::SetReleaseFlag(bool flag) +{ + releaseFlag_ = flag; +} + +DBStatus KvStoreNbDelegateImpl::Close() +{ + if (conn_ != nullptr) { + int errCode = KvDBManager::ReleaseDatabaseConnection(conn_); + if (errCode == -E_BUSY) { + LOGI("[KvStoreNbDelegate] Busy for close"); + return BUSY; + } + + LOGI("[KvStoreNbDelegateImpl] Database connection Close"); + conn_ = nullptr; + } + return OK; +} + +DBStatus KvStoreNbDelegateImpl::CheckIntegrity() const +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + return TransferDBErrno(conn_->CheckIntegrity()); +} + +DBStatus KvStoreNbDelegateImpl::GetSecurityOption(SecurityOption &option) const +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + return TransferDBErrno(conn_->GetSecurityOption(option.securityLabel, option.securityFlag)); +} + +DBStatus KvStoreNbDelegateImpl::SetRemotePushFinishedNotify(const RemotePushFinishedNotifier ¬ifier) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + PragmaRemotePushNotify notify(notifier); + int errCode = conn_->Pragma(PRAGMA_REMOTE_PUSH_FINISHED_NOTIFY, reinterpret_cast(¬ify)); + if (errCode != E_OK) { + LOGE("[KvStoreNbDelegate] Set remote push finished notify failed : %d", errCode); + } + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::GetInner(const IOption &option, const Key &key, Value &value) const +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + int errCode = conn_->Get(option, key, value); + if (errCode == E_OK) { + return OK; + } + LOGW("[KvStoreNbDelegate] Get the data failed:%d", errCode); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::GetEntriesInner(const IOption &option, + const Key &keyPrefix, std::vector &entries) const +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + int errCode = conn_->GetEntries(option, keyPrefix, entries); + if (errCode == E_OK) { + return OK; + } + LOGW("[KvStoreNbDelegate] Get the batch data failed:%d", errCode); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::PutInner(const IOption &option, const Key &key, const Value &value) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(PT_TEST_RECORDS::RECORD_PUT_DATA); + } + + int errCode = conn_->Put(option, key, value); + if (performance != nullptr) { + performance->StepTimeRecordEnd(PT_TEST_RECORDS::RECORD_PUT_DATA); + } + + if (errCode == E_OK) { + return OK; + } + LOGE("[KvStoreNbDelegate] Put the data failed:%d", errCode); + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::DeleteInner(const IOption &option, const Key &key) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + int errCode = conn_->Delete(option, key); + if (errCode == E_OK || errCode == -E_NOT_FOUND) { + return OK; + } + + LOGE("[KvStoreNbDelegate] Delete the data failed:%d", errCode); + return TransferDBErrno(errCode); +} + +void KvStoreNbDelegateImpl::OnSyncComplete(const std::map &statuses, + const std::function &devicesMap)> &onComplete) const +{ + const auto &statusMap = SyncOperation::DBStatusTransMap(); + std::map result; + for (const auto &pair : statuses) { + DBStatus status = DB_ERROR; + auto iter = statusMap.find(pair.second); + if (iter != statusMap.end()) { + status = iter->second; + } + result.insert(std::pair(pair.first, status)); + } + if (onComplete) { + onComplete(result); + } +} + +DBStatus KvStoreNbDelegateImpl::SetEqualIdentifier(const std::string &identifier, + const std::vector &targets) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + PragmaSetEqualIdentifier pragma(identifier, targets); + int errCode = conn_->Pragma(PRAGMA_ADD_EQUAL_IDENTIFIER, reinterpret_cast(&pragma)); + if (errCode != E_OK) { + LOGE("[KvStoreNbDelegate] Set store equal identifier failed : %d", errCode); + } + + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::SetPushDataInterceptor(const PushDataInterceptor &interceptor) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + PushDataInterceptor notify = interceptor; + int errCode = conn_->Pragma(PRAGMA_INTERCEPT_SYNC_DATA, static_cast(¬ify)); + if (errCode != E_OK) { + LOGE("[KvStoreNbDelegate] Set data interceptor notify failed : %d", errCode); + } + return TransferDBErrno(errCode); +} + +DBStatus KvStoreNbDelegateImpl::SubscribeRemoteQuery(const std::vector &devices, + const std::function &devicesMap)> &onComplete, + const Query &query, bool wait) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + QuerySyncObject querySyncObj(query); + PragmaSync pragmaData(devices, SyncModeType::SUBSCRIBE_QUERY, querySyncObj, + std::bind(&KvStoreNbDelegateImpl::OnSyncComplete, this, std::placeholders::_1, onComplete), wait); + int errCode = conn_->Pragma(PRAGMA_SUBSCRIBE_QUERY, &pragmaData); + if (errCode < E_OK) { + LOGE("[KvStoreNbDelegate] Subscribe remote data with query failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus KvStoreNbDelegateImpl::UnSubscribeRemoteQuery(const std::vector &devices, + const std::function &devicesMap)> &onComplete, + const Query &query, bool wait) +{ + if (conn_ == nullptr) { + LOGE("%s", INVALID_CONNECTION.c_str()); + return DB_ERROR; + } + + QuerySyncObject querySyncObj(query); + PragmaSync pragmaData(devices, SyncModeType::UNSUBSCRIBE_QUERY, querySyncObj, + std::bind(&KvStoreNbDelegateImpl::OnSyncComplete, this, std::placeholders::_1, onComplete), wait); + int errCode = conn_->Pragma(PRAGMA_SUBSCRIBE_QUERY, &pragmaData); + if (errCode < E_OK) { + LOGE("[KvStoreNbDelegate] Unsubscribe remote data with query failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/interfaces/src/kv_store_nb_delegate_impl.h b/mock/distributeddb/interfaces/src/kv_store_nb_delegate_impl.h new file mode 100644 index 00000000..cac5fbaa --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_nb_delegate_impl.h @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_NB_DELEGATE_IMPL_H +#define KV_STORE_NB_DELEGATE_IMPL_H + +#include +#include +#include +#include + +#include "store_types.h" +#include "db_types.h" +#include "ikvdb_connection.h" +#include "kv_store_nb_conflict_data.h" +#include "kv_store_nb_delegate.h" + +namespace DistributedDB { +class KvStoreNbDelegateImpl final : public KvStoreNbDelegate { +public: + KvStoreNbDelegateImpl(IKvDBConnection *conn, const std::string &storeId); + ~KvStoreNbDelegateImpl() override; + + DISABLE_COPY_ASSIGN_MOVE(KvStoreNbDelegateImpl); + + // Public zone interfaces + DBStatus Get(const Key &key, Value &value) const override; + + DBStatus GetEntries(const Key &keyPrefix, std::vector &entries) const override; + + DBStatus GetEntries(const Key &keyPrefix, KvStoreResultSet *&resultSet) const override; + + DBStatus GetEntries(const Query &query, std::vector &entries) const override; + + DBStatus GetEntries(const Query &query, KvStoreResultSet *&resultSet) const override; + + DBStatus GetCount(const Query &query, int &count) const override; + + DBStatus CloseResultSet(KvStoreResultSet *&resultSet) override; + + DBStatus Put(const Key &key, const Value &value) override; + + DBStatus PutBatch(const std::vector &entries) override; + + DBStatus DeleteBatch(const std::vector &keys) override; + + DBStatus Delete(const Key &key) override; + + // Local zone interfaces + DBStatus GetLocal(const Key &key, Value &value) const override; + + DBStatus GetLocalEntries(const Key &keyPrefix, std::vector &entries) const override; + + DBStatus PutLocal(const Key &key, const Value &value) override; + + DBStatus DeleteLocal(const Key &key) override; + + DBStatus PublishLocal(const Key &key, bool deleteLocal, bool updateTimestamp, + const KvStoreNbPublishOnConflict &onConflict) override; + + DBStatus UnpublishToLocal(const Key &key, bool deletePublic, bool updateTimestamp) override; + + // Observer interfaces + DBStatus RegisterObserver(const Key &key, unsigned int mode, KvStoreObserver *observer) override; + + DBStatus UnRegisterObserver(const KvStoreObserver *observer) override; + + DBStatus RemoveDeviceData(const std::string &device) override; + + // Other interfaces + std::string GetStoreId() const override; + + // Sync function interface, if wait set true, this function will be blocked until sync finished + DBStatus Sync(const std::vector &devices, SyncMode mode, + const std::function &devicesMap)> &onComplete, + bool wait) override; + + // Special pragma interface, see PragmaCmd and PragmaData, + DBStatus Pragma(PragmaCmd cmd, PragmaData ¶mData) override; + + // Set the conflict notifier for getting the specified type conflict data. + DBStatus SetConflictNotifier(int conflictType, const KvStoreNbConflictNotifier ¬ifier) override; + + // Rekey the database. + DBStatus Rekey(const CipherPassword &password) override; + + // Empty passwords represent non-encrypted files. + // Export existing database files to a specified database file in the specified directory. + DBStatus Export(const std::string &filePath, const CipherPassword &passwd) override; + + // Import the existing database files to the specified database file in the specified directory. + DBStatus Import(const std::string &filePath, const CipherPassword &passwd) override; + + // Start a transaction + DBStatus StartTransaction() override; + + // Commit a transaction + DBStatus Commit() override; + + // Rollback a transaction + DBStatus Rollback() override; + + DBStatus PutLocalBatch(const std::vector &entries) override; + + DBStatus DeleteLocalBatch(const std::vector &keys) override; + + // Get the SecurityOption of this kvStore. + DBStatus GetSecurityOption(SecurityOption &option) const override; + + DBStatus SetRemotePushFinishedNotify(const RemotePushFinishedNotifier ¬ifier) override; + + void SetReleaseFlag(bool flag); + + DBStatus Close(); + + // Sync function interface, if wait set true, this function will be blocked until sync finished. + // Param query used to filter the records to be synchronized. + // Now just support push mode and query by prefixKey. + DBStatus Sync(const std::vector &devices, SyncMode mode, + const std::function &devicesMap)> &onComplete, + const Query &query, bool wait) override; + + DBStatus CheckIntegrity() const override; + + // Set an equal identifier for this database, After this called, send msg to the target will use this identifier + DBStatus SetEqualIdentifier(const std::string &identifier, const std::vector &targets) override; + + DBStatus SetPushDataInterceptor(const PushDataInterceptor &interceptor) override; + + // Register a subscriber query on peer devices. The data in the peer device meets the subscriber query condition + // will automatically push to the local device when it's changed. + DBStatus SubscribeRemoteQuery(const std::vector &devices, + const std::function &devicesMap)> &onComplete, + const Query &query, bool wait) override; + + // Unregister a subscriber query on peer devices. + DBStatus UnSubscribeRemoteQuery(const std::vector &devices, + const std::function &devicesMap)> &onComplete, + const Query &query, bool wait) override; + +private: + DBStatus GetInner(const IOption &option, const Key &key, Value &value) const; + DBStatus PutInner(const IOption &option, const Key &key, const Value &value); + DBStatus DeleteInner(const IOption &option, const Key &key); + DBStatus GetEntriesInner(const IOption &option, const Key &keyPrefix, std::vector &entries) const; + + void OnSyncComplete(const std::map &statuses, + const std::function &devicesMap)> &onComplete) const; + + IKvDBConnection *conn_; + std::string storeId_; + bool releaseFlag_; + std::mutex observerMapLock_; + std::map observerMap_; +}; +} // namespace DistributedDB + +#endif // KV_STORE_NB_DELEGATE_IMPL_H diff --git a/mock/distributeddb/interfaces/src/kv_store_result_set_impl.cpp b/mock/distributeddb/interfaces/src/kv_store_result_set_impl.cpp new file mode 100644 index 00000000..18f8b662 --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_result_set_impl.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kv_store_result_set_impl.h" + +#include "db_errno.h" + +namespace DistributedDB { +const int KvStoreResultSetImpl::INIT_POSTION = -1; + +KvStoreResultSetImpl::KvStoreResultSetImpl(IKvDBResultSet *resultSet) + : resultSet_(resultSet) +{ +} + +int KvStoreResultSetImpl::GetCount() const +{ + if (resultSet_ == nullptr) { + return 0; + } + return resultSet_->GetCount(); +} + +int KvStoreResultSetImpl::GetPosition() const +{ + if (resultSet_ == nullptr) { + return INIT_POSTION; + } + return resultSet_->GetPosition(); +} + +bool KvStoreResultSetImpl::Move(int offset) +{ + int64_t position = GetPosition(); + int64_t aimPos = position + offset; + if (aimPos > INT_MAX) { + return MoveToPosition(INT_MAX); + } + if (aimPos < INIT_POSTION) { + return MoveToPosition(INIT_POSTION); + } + return MoveToPosition(aimPos); +} + +bool KvStoreResultSetImpl::MoveToPosition(int position) +{ + if (resultSet_ == nullptr) { + return false; + } + if (resultSet_->MoveTo(position) == E_OK) { + return true; + } + return false; +} + +bool KvStoreResultSetImpl::MoveToFirst() +{ + return MoveToPosition(0); +} + +bool KvStoreResultSetImpl::MoveToLast() +{ + return MoveToPosition(GetCount() - 1); +} + +bool KvStoreResultSetImpl::MoveToNext() +{ + // move 1 step forward in this result set + return Move(1); +} + +bool KvStoreResultSetImpl::MoveToPrevious() +{ + // move 1 step backward in this result set + return Move(-1); +} + +bool KvStoreResultSetImpl::IsFirst() const +{ + if (resultSet_ == nullptr) { + return false; + } + int position = resultSet_->GetPosition(); + if (GetCount() == 0) { + return false; + } + if (position == 0) { + return true; + } + return false; +} + +bool KvStoreResultSetImpl::IsLast() const +{ + if (resultSet_ == nullptr) { + return false; + } + int position = resultSet_->GetPosition(); + int count = GetCount(); + if (count == 0) { + return false; + } + if (position == (count - 1)) { + return true; + } + return false; +} + +bool KvStoreResultSetImpl::IsBeforeFirst() const +{ + if (resultSet_ == nullptr) { + return false; + } + int position = resultSet_->GetPosition(); + + if (GetCount() == 0) { + return true; + } + if (position <= INIT_POSTION) { + return true; + } + return false; +} + +bool KvStoreResultSetImpl::IsAfterLast() const +{ + if (resultSet_ == nullptr) { + return false; + } + int position = resultSet_->GetPosition(); + int count = GetCount(); + if (count == 0) { + return true; + } + if (position >= count) { + return true; + } + return false; +} + +DBStatus KvStoreResultSetImpl::GetEntry(Entry &entry) const +{ + if (resultSet_ == nullptr) { + return DB_ERROR; + } + if (GetCount() == 0) { + return NOT_FOUND; + } + + if (resultSet_->GetEntry(entry) == E_OK) { + return OK; + } + return NOT_FOUND; +} + +void KvStoreResultSetImpl::GetResultSet(IKvDBResultSet *&resultSet) const +{ + resultSet = resultSet_; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/interfaces/src/kv_store_result_set_impl.h b/mock/distributeddb/interfaces/src/kv_store_result_set_impl.h new file mode 100644 index 00000000..700434b1 --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_result_set_impl.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_RESULT_SET_IMPL_H +#define KV_STORE_RESULT_SET_IMPL_H + +#include + +#include "kv_store_result_set.h" +#include "ikvdb_result_set.h" + +namespace DistributedDB { +class KvStoreResultSetImpl final : public KvStoreResultSet { +public: + explicit KvStoreResultSetImpl(IKvDBResultSet *resultSet); + ~KvStoreResultSetImpl() override {}; + + DISABLE_COPY_ASSIGN_MOVE(KvStoreResultSetImpl); + + // Returns the numbers of rows in the result set. + int GetCount() const override; + + // Returns the current position of the result set in the row set. + int GetPosition() const override; + + // Move the result set to the first row, return false if the result set is empty. + bool MoveToFirst() override; + + // Move the result set to the last row, return false if the result set is empty. + bool MoveToLast() override; + + // Move the result set to the next row, return false if the result set is already past + // the last entry in the result set. + bool MoveToNext() override; + + // Move the result set to the previous row, return false if the result set is already before + // the first entry in the result set + bool MoveToPrevious() override; + + // Move the result set by a relative amount, forward or backward, from the current position. + bool Move(int offset) override; + + // Move the result set to an absolute position, the valid range of value is [-1, count] + bool MoveToPosition(int position) override; + + // Returns whether the result set is pointing to the first row. + bool IsFirst() const override; + + // Returns whether the result set is pointing to the last row. + bool IsLast() const override; + + // Returns whether the result set is pointing to the position before the first row. + bool IsBeforeFirst() const override; + + // Returns whether the result set is pointing to the position after the last row + bool IsAfterLast() const override; + + // Get a key-value entry. + DBStatus GetEntry(Entry &entry) const override; + + // Get the result set obj + void GetResultSet(IKvDBResultSet *&resultSet) const; + +private: + static const int INIT_POSTION; + IKvDBResultSet * const resultSet_; +}; +} // namespace DistributedDB + +#endif // KV_STORE_RESULT_SET_IMPL_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/src/kv_store_snapshot_delegate_impl.cpp b/mock/distributeddb/interfaces/src/kv_store_snapshot_delegate_impl.cpp new file mode 100644 index 00000000..17ce4a8f --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_snapshot_delegate_impl.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "kv_store_snapshot_delegate_impl.h" + +#include "kv_store_errno.h" +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +KvStoreSnapshotDelegateImpl::KvStoreSnapshotDelegateImpl(IKvDBSnapshot *snapshot, KvStoreObserver *observer) + : snapShot_(snapshot), + observer_(observer) +{} + +void KvStoreSnapshotDelegateImpl::Get( + const Key &key, const std::function &callback) const +{ + if (!callback) { + LOGE("[KvStoreSnapshot] Invalid callback!"); + return; + } + + DBStatus status = DB_ERROR; + Value value; + if (snapShot_ != nullptr) { + int errCode = snapShot_->Get(key, value); + if (errCode == E_OK) { + status = OK; + } else { + if (errCode != -E_NOT_FOUND) { + LOGE("[KvStoreSnapshot] Get data failed:%d", errCode); + } + status = TransferDBErrno(errCode); + } + } + + callback(status, value); +} + +void KvStoreSnapshotDelegateImpl::GetEntries( + const Key &keyPrefix, const std::function &)> &callback) const +{ + if (!callback) { + LOGE("[KvStoreSnapshot] Invalid callback!"); + return; + } + + DBStatus status = DB_ERROR; + std::vector entries; + if (snapShot_ != nullptr) { + int errCode = snapShot_->GetEntries(keyPrefix, entries); + if (errCode == E_OK) { + status = OK; + } else { + if (errCode != -E_NOT_FOUND) { + LOGE("[KvStoreSnapshot] Get entries failed:%d", errCode); + } + status = TransferDBErrno(errCode); + } + } + + callback(status, entries); +} + +void KvStoreSnapshotDelegateImpl::GetSnapshot(IKvDBSnapshot *&snapshot) const +{ + snapshot = snapShot_; +} + +void KvStoreSnapshotDelegateImpl::GetObserver(KvStoreObserver *&observer) const +{ + observer = observer_; +} +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/interfaces/src/kv_store_snapshot_delegate_impl.h b/mock/distributeddb/interfaces/src/kv_store_snapshot_delegate_impl.h new file mode 100644 index 00000000..71d8ed5a --- /dev/null +++ b/mock/distributeddb/interfaces/src/kv_store_snapshot_delegate_impl.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_STORE_SNAPSHOT_DELEGATE_IMPL_H +#define KV_STORE_SNAPSHOT_DELEGATE_IMPL_H + +#ifndef OMIT_MULTI_VER +#include "kv_store_delegate_impl.h" + +#include "ikvdb_snapshot.h" + +namespace DistributedDB { +class KvStoreSnapshotDelegateImpl final : public KvStoreSnapshotDelegate { +public: + KvStoreSnapshotDelegateImpl(IKvDBSnapshot *snapshot, KvStoreObserver *observer); + ~KvStoreSnapshotDelegateImpl() override {}; + + DISABLE_COPY_ASSIGN_MOVE(KvStoreSnapshotDelegateImpl); + + // Get a value from the snapshot with the given key. + // The return value is DBStatus and Value, these values will be passed to the callback. + void Get(const Key &key, const std::function &callback) const override; + + // Get entries from the snapshot which keys start with keyPrefix. + // The return value is DBStatus and Entries, these values will be passed to the callback. + void GetEntries(const Key &keyPrefix, + const std::function &)> &callback) const override; + + // Get the snapshot + void GetSnapshot(IKvDBSnapshot *&snapshot) const; + + // Get the observer + void GetObserver(KvStoreObserver *&observer) const; + +private: + IKvDBSnapshot * const snapShot_; + KvStoreObserver * const observer_; +}; +} // namespace DistributedDB + +#endif // KV_STORE_SNAPSHOT_DELEGATE_IMPL_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/interfaces/src/relational/relational_store_changed_data_impl.cpp b/mock/distributeddb/interfaces/src/relational/relational_store_changed_data_impl.cpp new file mode 100644 index 00000000..4afb818b --- /dev/null +++ b/mock/distributeddb/interfaces/src/relational/relational_store_changed_data_impl.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "relational_store_changed_data_impl.h" + +namespace DistributedDB { +RelationalStoreChangedDataImpl::~RelationalStoreChangedDataImpl() +{ +} + +DB_API std::string RelationalStoreChangedDataImpl::GetDataChangeDevice() const +{ + std::lock_guard lock(mutex_); + // add get changedDevice_ code; + return changedDevice_; +} + +DB_API void RelationalStoreChangedDataImpl::GetStoreProperty(StoreProperty &storeProperty) const +{ + std::lock_guard lock(mutex_); + storeProperty = storeProperty_; +} + +void RelationalStoreChangedDataImpl::SetStoreProperty(const StoreProperty &storeProperty) +{ + std::lock_guard lock(mutex_); + storeProperty_ = storeProperty; +} +} // namespace DistributedDB + diff --git a/mock/distributeddb/interfaces/src/relational/relational_store_changed_data_impl.h b/mock/distributeddb/interfaces/src/relational/relational_store_changed_data_impl.h new file mode 100644 index 00000000..9544b202 --- /dev/null +++ b/mock/distributeddb/interfaces/src/relational/relational_store_changed_data_impl.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 RELATION_STORE_CHANGED_DATA_IMPL_H +#define RELATION_STORE_CHANGED_DATA_IMPL_H + +#include +#include "macro_utils.h" +#include "store_changed_data.h" + +namespace DistributedDB { +class RelationalStoreChangedDataImpl : public StoreChangedData { +public: + explicit RelationalStoreChangedDataImpl(const std::string &changedDevice) : changedDevice_(changedDevice) {} + virtual ~RelationalStoreChangedDataImpl(); + + DISABLE_COPY_ASSIGN_MOVE(RelationalStoreChangedDataImpl); + + std::string GetDataChangeDevice() const override; + + void GetStoreProperty(StoreProperty &storeProperty) const override; + + void SetStoreProperty(const StoreProperty &storeProperty); +private: + mutable std::mutex mutex_; + mutable std::string changedDevice_; + StoreProperty storeProperty_; +}; +} // namespace DistributedDB + +#endif // RELATION_STORE_CHANGED_DATA_IMPL_H + diff --git a/mock/distributeddb/interfaces/src/relational/relational_store_delegate_impl.cpp b/mock/distributeddb/interfaces/src/relational/relational_store_delegate_impl.cpp new file mode 100644 index 00000000..d79127f5 --- /dev/null +++ b/mock/distributeddb/interfaces/src/relational/relational_store_delegate_impl.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "relational_store_delegate_impl.h" + +#include "db_errno.h" +#include "kv_store_errno.h" +#include "log_print.h" +#include "param_check_utils.h" +#include "relational_store_instance.h" +#include "sync_operation.h" + +namespace DistributedDB { +RelationalStoreDelegateImpl::RelationalStoreDelegateImpl(RelationalStoreConnection *conn, const std::string &path) + : conn_(conn), + storePath_(path) +{} + +RelationalStoreDelegateImpl::~RelationalStoreDelegateImpl() +{ + if (!releaseFlag_) { + LOGF("[RelationalStore Delegate] Can't release directly"); + return; + } + + conn_ = nullptr; +}; + +DBStatus RelationalStoreDelegateImpl::RemoveDeviceData(const std::string &device) +{ + return RemoveDeviceData(device, {}); +} + +DBStatus RelationalStoreDelegateImpl::CreateDistributedTable(const std::string &tableName) +{ + if (!ParamCheckUtils::CheckRelationalTableName(tableName)) { + LOGE("invalid table name."); + return INVALID_ARGS; + } + + if (conn_ == nullptr) { + LOGE("[RelationalStore Delegate] Invalid connection for operation!"); + return DB_ERROR; + } + + int errCode = conn_->CreateDistributedTable(tableName); + if (errCode != E_OK) { + LOGE("[RelationalStore Delegate] Create Distributed table failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus RelationalStoreDelegateImpl::Sync(const std::vector &devices, SyncMode mode, + const Query &query, const SyncStatusCallback &onComplete, bool wait) +{ + if (conn_ == nullptr) { + LOGE("Invalid connection for operation!"); + return DB_ERROR; + } + + RelationalStoreConnection::SyncInfo syncInfo{devices, mode, + std::bind(&RelationalStoreDelegateImpl::OnSyncComplete, std::placeholders::_1, onComplete), query, wait}; + int errCode = conn_->SyncToDevice(syncInfo); + if (errCode != E_OK) { + LOGW("[RelationalStore Delegate] sync data to device failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus RelationalStoreDelegateImpl::RemoveDeviceData(const std::string &device, const std::string &tableName) +{ + if (conn_ == nullptr) { + LOGE("Invalid connection for operation!"); + return DB_ERROR; + } + + if (device.empty() || device.length() > DBConstant::MAX_DEV_LENGTH || + !ParamCheckUtils::CheckRelationalTableName(tableName)) { + LOGE("[RelationalStore Delegate] Remove device data with invalid device name or table name."); + return INVALID_ARGS; + } + + int errCode = conn_->RemoveDeviceData(device, tableName); + if (errCode != E_OK) { + LOGW("[RelationalStore Delegate] remove device data failed:%d", errCode); + return TransferDBErrno(errCode); + } + return OK; +} + +DBStatus RelationalStoreDelegateImpl::Close() +{ + if (conn_ == nullptr) { + return OK; + } + + int errCode = RelationalStoreInstance::ReleaseDataBaseConnection(conn_); + if (errCode == -E_BUSY) { + LOGW("[RelationalStore Delegate] busy for close"); + return BUSY; + } + if (errCode != E_OK) { + LOGE("Release db connection error:%d", errCode); + return TransferDBErrno(errCode); + } + + LOGI("[RelationalStore Delegate] Close"); + conn_ = nullptr; + return OK; +} + +void RelationalStoreDelegateImpl::SetReleaseFlag(bool flag) +{ + releaseFlag_ = flag; +} + +void RelationalStoreDelegateImpl::OnSyncComplete(const std::map> &devicesStatus, + SyncStatusCallback &onComplete) +{ + const auto &statusMap = SyncOperation::DBStatusTransMap(); + std::map> res; + for (const auto &[device, tablesStatus] : devicesStatus) { + for (const auto &tableStatus : tablesStatus) { + TableStatus table; + table.tableName = tableStatus.tableName; + DBStatus status = DB_ERROR; + auto iterator = statusMap.find(tableStatus.status); + if (iterator != statusMap.end()) { + status = iterator->second; + } + table.status = status; + res[device].push_back(table); + } + } + if (onComplete) { + onComplete(res); + } +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/interfaces/src/relational/relational_store_delegate_impl.h b/mock/distributeddb/interfaces/src/relational/relational_store_delegate_impl.h new file mode 100644 index 00000000..2a151f20 --- /dev/null +++ b/mock/distributeddb/interfaces/src/relational/relational_store_delegate_impl.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RELATIONAL_STORE_DELEGATE_IMPL_H +#define RELATIONAL_STORE_DELEGATE_IMPL_H +#ifdef RELATIONAL_STORE + +#include "macro_utils.h" +#include "relational_store_connection.h" + +namespace DistributedDB { +class RelationalStoreDelegateImpl final : public RelationalStoreDelegate { +public: + RelationalStoreDelegateImpl() = default; + ~RelationalStoreDelegateImpl() override; + + RelationalStoreDelegateImpl(RelationalStoreConnection *conn, const std::string &path); + + DISABLE_COPY_ASSIGN_MOVE(RelationalStoreDelegateImpl); + + DBStatus Sync(const std::vector &devices, SyncMode mode, + const Query &query, const SyncStatusCallback &onComplete, bool wait) override; + + DBStatus RemoveDeviceData(const std::string &device) override; + + DBStatus CreateDistributedTable(const std::string &tableName) override; + + DBStatus RemoveDeviceData(const std::string &device, const std::string &tableName) override; + + // For connection + DBStatus Close(); + + void SetReleaseFlag(bool flag); + +private: + static void OnSyncComplete(const std::map> &devicesStatus, + SyncStatusCallback &onComplete); + + RelationalStoreConnection *conn_ = nullptr; + std::string storePath_; + std::atomic releaseFlag_ = false; +}; +} // namespace DistributedDB +#endif +#endif // RELATIONAL_STORE_DELEGATE_IMPL_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/src/relational/relational_store_instance.h b/mock/distributeddb/interfaces/src/relational/relational_store_instance.h new file mode 100644 index 00000000..c2be0738 --- /dev/null +++ b/mock/distributeddb/interfaces/src/relational/relational_store_instance.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RELATIONAL_STORE_INSTANCE_H +#define RELATIONAL_STORE_INSTANCE_H +#ifdef RELATIONAL_STORE + +#include +#include + +#include "irelational_store.h" +#include "relationaldb_properties.h" + +namespace DistributedDB { +class RelationalStoreInstance final { +public: + RelationalStoreInstance(); + ~RelationalStoreInstance() = default; + + static RelationalStoreConnection *GetDatabaseConnection(const RelationalDBProperties &properties, int &errCode); + static RelationalStoreInstance *GetInstance(); + + static int ReleaseDataBaseConnection(RelationalStoreConnection *connection); + + int CheckDatabaseFileStatus(const std::string &id); + + // public for test mock + static IRelationalStore *GetDataBase(const RelationalDBProperties &properties, int &errCode); + + void Dump(int fd); +private: + + IRelationalStore *OpenDatabase(const RelationalDBProperties &properties, int &errCode); + + void RemoveKvDBFromCache(const RelationalDBProperties &properties); + void SaveRelationalDBToCache(IRelationalStore *store, const RelationalDBProperties &properties); + + void EnterDBOpenCloseProcess(const std::string &identifier); + void ExitDBOpenCloseProcess(const std::string &identifier); + + static RelationalStoreInstance *instance_; + static std::mutex instanceLock_; + + std::string appId_; + std::string userId_; + + std::mutex relationalDBOpenMutex_; + std::condition_variable relationalDBOpenCondition_; + std::set relationalDBOpenSet_; +}; +} // namespace DistributedDB + +#endif +#endif // RELATIONAL_STORE_INSTANCE_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/src/relational/relational_store_manager.cpp b/mock/distributeddb/interfaces/src/relational/relational_store_manager.cpp new file mode 100644 index 00000000..60e3d9f2 --- /dev/null +++ b/mock/distributeddb/interfaces/src/relational/relational_store_manager.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "relational_store_manager.h" + +#include + +#include "auto_launch.h" +#include "relational_store_instance.h" +#include "db_common.h" +#include "db_dfx_adapter.h" +#include "param_check_utils.h" +#include "log_print.h" +#include "db_errno.h" +#include "kv_store_errno.h" +#include "relational_store_changed_data_impl.h" +#include "relational_store_delegate_impl.h" +#include "runtime_context.h" +#include "platform_specific.h" + +namespace DistributedDB { +namespace { +const int GET_CONNECT_RETRY = 3; +const int RETRY_GET_CONN_INTER = 30; + +void InitStoreProp(const std::string &storePath, const std::string &appId, const std::string &userId, + const std::string &storeId, RelationalDBProperties &properties) +{ + properties.SetStringProp(RelationalDBProperties::DATA_DIR, storePath); + properties.SetIdentifier(userId, appId, storeId); +} +} + +RelationalStoreManager::RelationalStoreManager(const std::string &appId, const std::string &userId) + : appId_(appId), + userId_(userId) +{} + +static RelationalStoreConnection *GetOneConnectionWithRetry(const RelationalDBProperties &properties, int &errCode) +{ + for (int i = 0; i < GET_CONNECT_RETRY; i++) { + auto conn = RelationalStoreInstance::GetDatabaseConnection(properties, errCode); + if (conn != nullptr) { + return conn; + } + if (errCode == -E_STALE) { + std::this_thread::sleep_for(std::chrono::milliseconds(RETRY_GET_CONN_INTER)); + } else { + return nullptr; + } + } + return nullptr; +} + +DB_API DBStatus RelationalStoreManager::OpenStore(const std::string &path, const std::string &storeId, + const RelationalStoreDelegate::Option &option, RelationalStoreDelegate *&delegate) +{ + if (delegate != nullptr) { + LOGE("[RelationalStoreMgr] Invalid delegate!"); + return INVALID_ARGS; + } + + std::string canonicalDir; + if (!ParamCheckUtils::CheckDataDir(path, canonicalDir)) { + return INVALID_ARGS; + } + + if (!ParamCheckUtils::CheckStoreParameter(storeId, appId_, userId_) || path.empty()) { + return INVALID_ARGS; + } + + RelationalDBProperties properties; + InitStoreProp(canonicalDir, appId_, userId_, storeId, properties); + + int errCode = E_OK; + auto *conn = GetOneConnectionWithRetry(properties, errCode); + if (errCode == -E_INVALID_PASSWD_OR_CORRUPTED_DB) { + DBDfxAdapter::ReportFault( { DBDfxAdapter::EVENT_OPEN_DATABASE_FAILED, userId_, appId_, storeId, errCode } ); + } + if (conn == nullptr) { + return TransferDBErrno(errCode); + } + + delegate = new (std::nothrow) RelationalStoreDelegateImpl(conn, path); + if (delegate == nullptr) { + conn->Close(); + return DB_ERROR; + } + conn->RegisterObserverAction([option, storeId, this](const std::string &changedDevice) { + RelationalStoreChangedDataImpl data(changedDevice); + data.SetStoreProperty({userId_, appId_, storeId}); + if (option.observer) { + LOGD("begin to observer on changed, changedDevice=%s", STR_MASK(changedDevice)); + option.observer->OnChange(data); + } + }); + return OK; +} + +DBStatus RelationalStoreManager::CloseStore(RelationalStoreDelegate *store) +{ + if (store == nullptr) { + return INVALID_ARGS; + } + + auto storeImpl = static_cast(store); + DBStatus status = storeImpl->Close(); + if (status == BUSY) { + LOGD("NbDelegateImpl is busy now."); + return BUSY; + } + storeImpl->SetReleaseFlag(true); + delete store; + store = nullptr; + return OK; +} + +std::string RelationalStoreManager::GetDistributedTableName(const std::string &device, const std::string &tableName) +{ + if (device.empty() || tableName.empty()) { + return {}; + } + return DBCommon::GetDistributedTableName(device, tableName); +} + +void RelationalStoreManager::SetAutoLaunchRequestCallback(const AutoLaunchRequestCallback &callback) +{ + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback(callback, DBType::DB_RELATION); +} + +std::string RelationalStoreManager::GetRelationalStoreIdentifier(const std::string &userId, const std::string &appId, + const std::string &storeId) +{ + if (!ParamCheckUtils::CheckStoreParameter(storeId, appId, userId)) { + return ""; + } + return DBCommon::TransferHashString(DBCommon::GenerateIdentifierId(storeId, appId, userId)); +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/interfaces/src/relational/relational_store_sqlite_ext.cpp b/mock/distributeddb/interfaces/src/relational/relational_store_sqlite_ext.cpp new file mode 100644 index 00000000..54b9bc04 --- /dev/null +++ b/mock/distributeddb/interfaces/src/relational/relational_store_sqlite_ext.cpp @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 the "sqlite3sym.h" in OHOS +#ifndef USE_SQLITE_SYMBOLS +#include "sqlite3.h" +#else +#include "sqlite3sym.h" +#endif + +namespace { +constexpr int E_OK = 0; +constexpr int E_ERROR = 1; + +class ValueHashCalc { +public: + ValueHashCalc() {}; + ~ValueHashCalc() + { + delete context_; + context_ = nullptr; + } + + int Initialize() + { + context_ = new (std::nothrow) SHA256_CTX; + if (context_ == nullptr) { + return -E_ERROR; + } + + int errCode = SHA256_Init(context_); + if (errCode == 0) { + return -E_ERROR; + } + return E_OK; + } + + int Update(const std::vector &value) + { + if (context_ == nullptr) { + return -E_ERROR; + } + int errCode = SHA256_Update(context_, value.data(), value.size()); + if (errCode == 0) { + return -E_ERROR; + } + return E_OK; + } + + int GetResult(std::vector &value) + { + if (context_ == nullptr) { + return -E_ERROR; + } + + value.resize(SHA256_DIGEST_LENGTH); + int errCode = SHA256_Final(value.data(), context_); + if (errCode == 0) { + return -E_ERROR; + } + + return E_OK; + } + +private: + SHA256_CTX *context_ = nullptr; +}; + + +const uint64_t MULTIPLES_BETWEEN_SECONDS_AND_MICROSECONDS = 1000000; + +using Timestamp = uint64_t; +using TimeOffset = int64_t; + +class TimeHelper { +public: + constexpr static int64_t BASE_OFFSET = 10000LL * 365LL * 24LL * 3600LL * 1000LL * 1000LL * 10L; // 10000 year 100ns + + constexpr static int64_t MAX_VALID_TIME = BASE_OFFSET * 2; // 20000 year 100ns + + constexpr static uint64_t TO_100_NS = 10; // 1us to 100ns + + constexpr static Timestamp INVALID_TIMESTAMP = 0; + + // Get current system time + static Timestamp GetSysCurrentTime() + { + uint64_t curTime = 0; + int errCode = GetCurrentSysTimeInMicrosecond(curTime); + if (errCode != E_OK) { + return INVALID_TIMESTAMP; + } + + std::lock_guard lock(systemTimeLock_); + // If GetSysCurrentTime in 1us, we need increase the currentIncCount_ + if (curTime == lastSystemTimeUs_) { + // if the currentIncCount_ has been increased MAX_INC_COUNT, keep the currentIncCount_ + if (currentIncCount_ < MAX_INC_COUNT) { + currentIncCount_++; + } + } else { + lastSystemTimeUs_ = curTime; + currentIncCount_ = 0; + } + return (curTime * TO_100_NS) + currentIncCount_; // Currently Timestamp is uint64_t + } + + // Init the TimeHelper + static void Initialize(Timestamp maxTimestamp) + { + std::lock_guard lock(lastLocalTimeLock_); + if (lastSystemTimeUs_ < maxTimestamp) { + lastSystemTimeUs_ = maxTimestamp; + } + } + + static Timestamp GetTime(TimeOffset timeOffset) + { + Timestamp currentSysTime = GetSysCurrentTime(); + Timestamp currentLocalTime = currentSysTime + timeOffset; + std::lock_guard lock(lastLocalTimeLock_); + if (currentLocalTime <= lastLocalTime_ || currentLocalTime > MAX_VALID_TIME) { + lastLocalTime_++; + currentLocalTime = lastLocalTime_; + } else { + lastLocalTime_ = currentLocalTime; + } + return currentLocalTime; + } + +private: + static int GetCurrentSysTimeInMicrosecond(uint64_t &outTime) + { + struct timeval rawTime; + int errCode = gettimeofday(&rawTime, nullptr); + if (errCode < 0) { + return -E_ERROR; + } + outTime = static_cast(rawTime.tv_sec) * MULTIPLES_BETWEEN_SECONDS_AND_MICROSECONDS + + static_cast(rawTime.tv_usec); + return E_OK; + } + + static std::mutex systemTimeLock_; + static Timestamp lastSystemTimeUs_; + static Timestamp currentIncCount_; + static const uint64_t MAX_INC_COUNT = 9; // last bit from 0-9 + + static Timestamp lastLocalTime_; + static std::mutex lastLocalTimeLock_; +}; + +std::mutex TimeHelper::systemTimeLock_; +Timestamp TimeHelper::lastSystemTimeUs_ = 0; +Timestamp TimeHelper::currentIncCount_ = 0; +Timestamp TimeHelper::lastLocalTime_ = 0; +std::mutex TimeHelper::lastLocalTimeLock_; + +struct TransactFunc { + void (*xFunc)(sqlite3_context*, int, sqlite3_value**) = nullptr; + void (*xStep)(sqlite3_context*, int, sqlite3_value**) = nullptr; + void (*xFinal)(sqlite3_context*) = nullptr; + void(*xDestroy)(void*) = nullptr; +}; + +int RegisterFunction(sqlite3 *db, const std::string &funcName, int nArg, void *uData, TransactFunc &func) +{ + if (db == nullptr) { + return -E_ERROR; + } + return sqlite3_create_function_v2(db, funcName.c_str(), nArg, SQLITE_UTF8 | SQLITE_DETERMINISTIC, uData, + func.xFunc, func.xStep, func.xFinal, func.xDestroy); +} + +int CalcValueHash(const std::vector &value, std::vector &hashValue) +{ + ValueHashCalc hashCalc; + int errCode = hashCalc.Initialize(); + if (errCode != E_OK) { + return -E_ERROR; + } + + errCode = hashCalc.Update(value); + if (errCode != E_OK) { + return -E_ERROR; + } + + errCode = hashCalc.GetResult(hashValue); + if (errCode != E_OK) { + return -E_ERROR; + } + + return E_OK; +} + +void CalcHashKey(sqlite3_context *ctx, int argc, sqlite3_value **argv) +{ + // 1 means that the function only needs one parameter, namely key + if (ctx == nullptr || argc != 1 || argv == nullptr) { + return; + } + auto keyBlob = static_cast(sqlite3_value_blob(argv[0])); + if (keyBlob == nullptr) { + sqlite3_result_error(ctx, "Parameters is invalid.", -1); + return; + } + int blobLen = sqlite3_value_bytes(argv[0]); + std::vector value(keyBlob, keyBlob + blobLen); + std::vector hashValue; + int errCode = CalcValueHash(value, hashValue); + if (errCode != E_OK) { + sqlite3_result_error(ctx, "Get hash value error.", -1); + return; + } + sqlite3_result_blob(ctx, hashValue.data(), hashValue.size(), SQLITE_TRANSIENT); + return; +} + +int RegisterCalcHash(sqlite3 *db) +{ + TransactFunc func; + func.xFunc = &CalcHashKey; + return RegisterFunction(db, "calc_hash", 1, nullptr, func); +} + +void GetSysTime(sqlite3_context *ctx, int argc, sqlite3_value **argv) +{ + if (ctx == nullptr || argc != 1 || argv == nullptr) { // 1: function need one parameter + return; + } + int timeOffset = static_cast(sqlite3_value_int64(argv[0])); + sqlite3_result_int64(ctx, (sqlite3_int64)TimeHelper::GetTime(timeOffset)); +} + +int RegisterGetSysTime(sqlite3 *db) +{ + TransactFunc func; + func.xFunc = &GetSysTime; + return RegisterFunction(db, "get_sys_time", 1, nullptr, func); +} + +int ResetStatement(sqlite3_stmt *&stmt) +{ + if (stmt == nullptr || sqlite3_finalize(stmt) != SQLITE_OK) { + return -E_ERROR; + } + stmt = nullptr; + return E_OK; +} + +int GetStatement(sqlite3 *db, const std::string &sql, sqlite3_stmt *&stmt) +{ + int errCode = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr); + if (errCode != SQLITE_OK) { + (void)ResetStatement(stmt); + return -E_ERROR; + } + return E_OK; +} + +int ExecuteRawSQL(sqlite3 *db, const std::string &sql) +{ + if (db == nullptr) { + return -E_ERROR; + } + char *errMsg = nullptr; + int errCode = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errMsg); + if (errCode != SQLITE_OK) { + errCode = -E_ERROR; + } + + if (errMsg != nullptr) { + sqlite3_free(errMsg); + errMsg = nullptr; + } + return errCode; +} + +int StepWithRetry(sqlite3_stmt *stmt) +{ + if (stmt == nullptr) { + return -E_ERROR; + } + int errCode = sqlite3_step(stmt); + if (errCode != SQLITE_DONE && errCode != SQLITE_ROW) { + return -E_ERROR; + } + return errCode; +} + +int GetColumnTestValue(sqlite3_stmt *stmt, int index, std::string &value) +{ + if (stmt == nullptr) { + return -E_ERROR; + } + const unsigned char *val = sqlite3_column_text(stmt, index); + value = (val != nullptr) ? std::string(reinterpret_cast(val)) : std::string(); + return E_OK; +} + +int GetCurrentMaxTimestamp(sqlite3 *db, Timestamp &maxTimestamp) +{ + if (db == nullptr) { + return -E_ERROR; + } + std::string checkTableSql = "SELECT name FROM sqlite_master WHERE type = 'table' AND " \ + "name LIKE 'naturalbase_rdb_aux_%_log';"; + sqlite3_stmt *checkTableStmt = nullptr; + int errCode = GetStatement(db, checkTableSql, checkTableStmt); + if (errCode != E_OK) { + return -E_ERROR; + } + while ((errCode = StepWithRetry(checkTableStmt)) != SQLITE_DONE) { + std::string logTablename; + GetColumnTestValue(checkTableStmt, 0, logTablename); + if (logTablename.empty()) { + continue; + } + + std::string getMaxTimestampSql = "SELECT MAX(timestamp) FROM " + logTablename + ";"; + sqlite3_stmt *getTimeStmt = nullptr; + errCode = GetStatement(db, getMaxTimestampSql, getTimeStmt); + if (errCode != E_OK) { + continue; + } + errCode = StepWithRetry(getTimeStmt); + if (errCode != SQLITE_ROW) { + ResetStatement(getTimeStmt); + continue; + } + auto tableMaxTimestamp = static_cast(sqlite3_column_int64(getTimeStmt, 0)); + maxTimestamp = (maxTimestamp > tableMaxTimestamp) ? maxTimestamp : tableMaxTimestamp; + ResetStatement(getTimeStmt); + } + ResetStatement(checkTableStmt); + return E_OK; +} + +void PostHandle(sqlite3 *db) +{ + Timestamp currentMaxTimestamp = 0; + (void)GetCurrentMaxTimestamp(db, currentMaxTimestamp); + TimeHelper::Initialize(currentMaxTimestamp); + RegisterCalcHash(db); + RegisterGetSysTime(db); + + std::string recursiveTrigger = "PRAGMA recursive_triggers = ON;"; + (void)ExecuteRawSQL(db, recursiveTrigger); +} +} + +SQLITE_API int sqlite3_open_relational(const char *filename, sqlite3 **ppDb) +{ + int err = sqlite3_open(filename, ppDb); + if (err != SQLITE_OK) { + return err; + } + PostHandle(*ppDb); + return err; +} + +SQLITE_API int sqlite3_open16_relational(const void *filename, sqlite3 **ppDb) +{ + int err = sqlite3_open16(filename, ppDb); + if (err != SQLITE_OK) { + return err; + } + PostHandle(*ppDb); + return err; +} + +SQLITE_API int sqlite3_open_v2_relational(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) +{ + int err = sqlite3_open_v2(filename, ppDb, flags, zVfs); + if (err != SQLITE_OK) { + return err; + } + PostHandle(*ppDb); + return err; +} + +// hw export the symbols +#ifdef SQLITE_DISTRIBUTE_RELATIONAL +#if defined(__GNUC__) +# define EXPORT_SYMBOLS __attribute__ ((visibility ("default"))) +#elif defined(_MSC_VER) + # define EXPORT_SYMBOLS __declspec(dllexport) +#else +# define EXPORT_SYMBOLS +#endif + +struct sqlite3_api_routines_relational { + int (*open)(const char *, sqlite3 **); + int (*open16)(const void *, sqlite3 **); + int (*open_v2)(const char *, sqlite3 **, int, const char *); +}; + +typedef struct sqlite3_api_routines_relational sqlite3_api_routines_relational; +static const sqlite3_api_routines_relational sqlite3HwApis = { +#ifdef SQLITE_DISTRIBUTE_RELATIONAL + sqlite3_open_relational, + sqlite3_open16_relational, + sqlite3_open_v2_relational +#else + 0, + 0, + 0 +#endif +}; + +EXPORT_SYMBOLS const sqlite3_api_routines_relational *sqlite3_export_relational_symbols = &sqlite3HwApis; +#endif \ No newline at end of file diff --git a/mock/distributeddb/interfaces/src/relational/relational_sync_able_storage.h b/mock/distributeddb/interfaces/src/relational/relational_sync_able_storage.h new file mode 100644 index 00000000..067bffc3 --- /dev/null +++ b/mock/distributeddb/interfaces/src/relational/relational_sync_able_storage.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RELATIONAL_SYNC_ABLE_STORAGE_H +#define RELATIONAL_SYNC_ABLE_STORAGE_H +#ifdef RELATIONAL_STORE + +#include "relational_db_sync_interface.h" +#include "relationaldb_properties.h" +#include "runtime_context.h" +#include "sqlite_single_relational_storage_engine.h" + +#include "sqlite_single_ver_relational_continue_token.h" + +namespace DistributedDB { +using RelationalObserverAction = std::function; +class RelationalSyncAbleStorage : public RelationalDBSyncInterface, public virtual RefObject { +public: + explicit RelationalSyncAbleStorage(StorageEngine *engine); + ~RelationalSyncAbleStorage() override; + + // Get interface type of this kvdb. + int GetInterfaceType() const override; + + // Get the interface ref-count, in order to access asynchronously. + void IncRefCount() override; + + // Drop the interface ref-count. + void DecRefCount() override; + + // Get the identifier of this kvdb. + std::vector GetIdentifier() const override; + + // Get the max timestamp of all entries in database. + void GetMaxTimestamp(Timestamp &stamp) const override; + + // Get the max timestamp of one table. + int GetMaxTimestamp(const std::string &tableName, Timestamp &stamp) const override; + + // Get meta data associated with the given key. + int GetMetaData(const Key &key, Value &value) const override; + + // Put meta data as a key-value entry. + int PutMetaData(const Key &key, const Value &value) override; + + // Delete multiple meta data records in a transaction. + int DeleteMetaData(const std::vector &keys) override; + + // Delete multiple meta data records with key prefix in a transaction. + int DeleteMetaDataByPrefixKey(const Key &keyPrefix) const override; + + // Get all meta data keys. + int GetAllMetaKeys(std::vector &keys) const override; + + const KvDBProperties &GetDbProperties() const override; + + // Get the data which would be synced with query condition + int GetSyncData(QueryObject &query, const SyncTimeRange &timeRange, + const DataSizeSpecInfo &dataSizeInfo, ContinueToken &continueStmtToken, + std::vector &entries) const override; + + int GetSyncDataNext(std::vector &entries, ContinueToken &continueStmtToken, + const DataSizeSpecInfo &dataSizeInfo) const override; + + int PutSyncDataWithQuery(const QueryObject &object, const std::vector &entries, + const DeviceID &deviceName) override; + + int RemoveDeviceData(const std::string &deviceName, bool isNeedNotify) override; + + RelationalSchemaObject GetSchemaInfo() const override; + + int GetSecurityOption(SecurityOption &option) const override; + + void NotifyRemotePushFinished(const std::string &deviceId) const override; + + // Get the timestamp when database created or imported + int GetDatabaseCreateTimestamp(Timestamp &outTime) const override; + + // Get batch meta data associated with the given key. + int GetBatchMetaData(const std::vector &keys, std::vector &entries) const override; + // Put batch meta data as a key-value entry vector + int PutBatchMetaData(std::vector &entries) override; + + std::vector GetTablesQuery() override; + + int LocalDataChanged(int notifyEvent, std::vector &queryObj) override; + + int InterceptData(std::vector &entries, const std::string &sourceID, + const std::string &targetID) const override + { + return E_OK; + } + + int CheckAndInitQueryCondition(QueryObject &query) const override; + void RegisterObserverAction(const RelationalObserverAction &action); + void TriggerObserverAction(const std::string &deviceName); + + int CreateDistributedDeviceTable(const std::string &device, const RelationalSyncStrategy &syncStrategy) override; + + int RegisterSchemaChangedCallback(const std::function &callback) override; + + void NotifySchemaChanged(); + + void RegisterHeartBeatListener(const std::function &listener); + + int GetCompressionAlgo(std::set &algorithmSet) const override; + + bool CheckCompatible(const std::string &schema, uint8_t type) const override; + +private: + SQLiteSingleVerRelationalStorageExecutor *GetHandle(bool isWrite, int &errCode, + OperatePerm perm = OperatePerm::NORMAL_PERM) const; + void ReleaseHandle(SQLiteSingleVerRelationalStorageExecutor *&handle) const; + + // get + int GetSyncDataForQuerySync(std::vector &dataItems, SQLiteSingleVerRelationalContinueToken *&token, + const DataSizeSpecInfo &dataSizeInfo) const; + + // put + int PutSyncData(const QueryObject &object, std::vector &dataItems, const std::string &deviceName); + int SaveSyncDataItems(const QueryObject &object, std::vector &dataItems, const std::string &deviceName); + + // data + SQLiteSingleRelationalStorageEngine *storageEngine_ = nullptr; + KvDBProperties properties_; + + std::function onSchemaChanged_; + mutable std::mutex onSchemaChangedMutex_; + std::mutex dataChangeDeviceMutex_; + RelationalObserverAction dataChangeDeviceCallback_; + std::function heartBeatListener_; + mutable std::mutex heartBeatMutex_; +}; +} // namespace DistributedDB +#endif +#endif // RELATIONAL_SYNC_ABLE_STORAGE_H \ No newline at end of file diff --git a/mock/distributeddb/interfaces/src/relational/runtime_config.cpp b/mock/distributeddb/interfaces/src/relational/runtime_config.cpp new file mode 100644 index 00000000..0e899339 --- /dev/null +++ b/mock/distributeddb/interfaces/src/relational/runtime_config.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "runtime_config.h" + +#include "db_constant.h" +#include "db_dfx_adapter.h" +#include "kvdb_manager.h" +#include "kv_store_errno.h" +#include "log_print.h" +#include "network_adapter.h" +#include "runtime_context.h" + +namespace DistributedDB { +std::mutex RuntimeConfig::communicatorMutex_; +std::shared_ptr RuntimeConfig::processCommunicator_ = nullptr; + +// Used to set the process userid and appId +DBStatus RuntimeConfig::SetProcessLabel(const std::string &appId, const std::string &userId) +{ + if (appId.size() > DBConstant::MAX_APP_ID_LENGTH || appId.empty() || + userId.size() > DBConstant::MAX_USER_ID_LENGTH || userId.empty()) { + LOGE("Invalid app or user info[%zu]-[%zu]", appId.length(), userId.length()); + return INVALID_ARGS; + } + + int errCode = KvDBManager::SetProcessLabel(appId, userId); + if (errCode != E_OK) { + LOGE("Failed to set the process label:%d", errCode); + return DB_ERROR; + } + return OK; +} + +// Set process communicator. +DBStatus RuntimeConfig::SetProcessCommunicator(const std::shared_ptr &inCommunicator) +{ + std::lock_guard lock(communicatorMutex_); + if (processCommunicator_ != nullptr) { + LOGE("processCommunicator_ is not null!"); + return DB_ERROR; + } + + std::string processLabel = RuntimeContext::GetInstance()->GetProcessLabel(); + if (processLabel.empty()) { + LOGE("ProcessLabel is not set!"); + return DB_ERROR; + } + + auto *adapter = new (std::nothrow) NetworkAdapter(processLabel, inCommunicator); + if (adapter == nullptr) { + LOGE("New NetworkAdapter failed!"); + return DB_ERROR; + } + processCommunicator_ = inCommunicator; + if (RuntimeContext::GetInstance()->SetCommunicatorAdapter(adapter) != E_OK) { + LOGE("SetProcessCommunicator not support!"); + delete adapter; + return DB_ERROR; + } + KvDBManager::RestoreSyncableKvStore(); + return OK; +} + +DBStatus RuntimeConfig::SetPermissionCheckCallback(const PermissionCheckCallbackV2 &callback) +{ + int errCode = RuntimeContext::GetInstance()->SetPermissionCheckCallback(callback); + return TransferDBErrno(errCode); +} + +DBStatus RuntimeConfig::SetProcessSystemAPIAdapter(const std::shared_ptr &adapter) +{ + return TransferDBErrno(RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(adapter)); +} + +void RuntimeConfig::Dump(int fd, const std::vector &args) +{ + DBDfxAdapter::Dump(fd, args); +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/include/db_properties.h b/mock/distributeddb/storage/include/db_properties.h new file mode 100644 index 00000000..6b7be4a0 --- /dev/null +++ b/mock/distributeddb/storage/include/db_properties.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DB_PROPERTIES_H +#define DB_PROPERTIES_H + +#include +#include + +namespace DistributedDB { +class DBProperties { +public: + // Get the string property according the name + std::string GetStringProp(const std::string &name, const std::string &defaultValue) const; + + // Set the string property for the name + void SetStringProp(const std::string &name, const std::string &value); + + // Get the bool property according the name + bool GetBoolProp(const std::string &name, bool defaultValue) const; + + // Set the bool property for the name + void SetBoolProp(const std::string &name, bool value); + + // Get the bool property according the name + int GetIntProp(const std::string &name, int defaultValue) const; + + // Set the integer property for the name + void SetIntProp(const std::string &name, int value); + + // Set all indentifers + void SetIdentifier(const std::string &userId, const std::string &appId, const std::string &storeId); + + static const std::string CREATE_IF_NECESSARY; + static const std::string DATABASE_TYPE; + static const std::string DATA_DIR; + static const std::string USER_ID; + static const std::string APP_ID; + static const std::string STORE_ID; + static const std::string IDENTIFIER_DATA; + static const std::string IDENTIFIER_DIR; + static const std::string DUAL_TUPLE_IDENTIFIER_DATA; + static const std::string SYNC_DUAL_TUPLE_MODE; + +protected: + DBProperties() = default; + virtual ~DBProperties() = default; + + std::map stringProperties_; + std::map boolProperties_; + std::map intProperties_; +}; +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/include/iconnection.h b/mock/distributeddb/storage/include/iconnection.h new file mode 100644 index 00000000..dbe63aac --- /dev/null +++ b/mock/distributeddb/storage/include/iconnection.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 I_CONNECTION_H +#define I_CONNECTION_H + +#include +#include + +#include "macro_utils.h" + +namespace DistributedDB { +class IConnection { +public: + IConnection(); + virtual ~IConnection() {}; + + DISABLE_COPY_ASSIGN_MOVE(IConnection); + +protected: + uint64_t GetConnectionId(); + + std::mutex connectionIdLock_; + std::atomic connectionId_; +}; +} // namespace DistributedDB + +#endif // I_KV_DB_CONNECTION_H diff --git a/mock/distributeddb/storage/include/ikvdb.h b/mock/distributeddb/storage/include/ikvdb.h new file mode 100644 index 00000000..05de9409 --- /dev/null +++ b/mock/distributeddb/storage/include/ikvdb.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_KV_DB_H +#define I_KV_DB_H + +#include +#include + +#include "ref_object.h" +#include "macro_utils.h" +#include "kvdb_properties.h" +#include "ikvdb_connection.h" + +namespace DistributedDB { +class IKvDB : public virtual RefObject { +public: + IKvDB() = default; + ~IKvDB() override {} + DISABLE_COPY_ASSIGN_MOVE(IKvDB); + + // Open the database. + virtual int Open(const KvDBProperties &kvDBProp) = 0; + + // Get the properties object of this database. + virtual const KvDBProperties &GetMyProperties() const = 0; + + // Create a db connection. + virtual IKvDBConnection *GetDBConnection(int &errCode) = 0; + + // Register callback invoked when all connections released. + virtual void OnClose(const std::function &func) = 0; + + virtual void OpenPerformanceAnalysis() = 0; + + virtual void ClosePerformanceAnalysis() = 0; + + virtual void WakeUpSyncer() = 0; + + virtual void SetCorruptHandler(const DatabaseCorruptHandler &handler) = 0; + + virtual int RemoveKvDB(const KvDBProperties &properties) = 0; + virtual int GetKvDBSize(const KvDBProperties &properties, uint64_t &size) const = 0; + + virtual void EnableAutonomicUpgrade() = 0; + + virtual int CheckIntegrity() const = 0; + + virtual std::string GetStorePath() const = 0; + + virtual void Dump(int fd) = 0; +}; +} // namespace DistributedDB + +#endif // I_KV_DB_H diff --git a/mock/distributeddb/storage/include/ikvdb_connection.h b/mock/distributeddb/storage/include/ikvdb_connection.h new file mode 100644 index 00000000..b051f95b --- /dev/null +++ b/mock/distributeddb/storage/include/ikvdb_connection.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_KV_DB_CONNECTION_H +#define I_KV_DB_CONNECTION_H + +#include +#include + +#include "db_types.h" +#include "iconnection.h" +#include "macro_utils.h" +#include "query.h" +#include "store_types.h" + +namespace DistributedDB { +class IKvDB; +class IKvDBSnapshot; +class KvDBObserverHandle; +class KvDBCommitNotifyData; +class IKvDBResultSet; + +using KvDBObserverAction = std::function; +using KvDBConflictAction = std::function; + +class IKvDBConnection : public IConnection { +public: + IKvDBConnection() = default; + virtual ~IKvDBConnection() {}; + + DISABLE_COPY_ASSIGN_MOVE(IKvDBConnection); + + // Get the value from the database. + virtual int Get(const IOption &option, const Key &key, Value &value) const = 0; + + // Put the value to the database. + virtual int Put(const IOption &option, const Key &key, const Value &value) = 0; + + // Delete the value from the database. + virtual int Delete(const IOption &option, const Key &key) = 0; + + // Clear all the data from the database. + virtual int Clear(const IOption &option) = 0; + + // Get all the data from the database. + virtual int GetEntries(const IOption &option, const Key &keyPrefix, std::vector &entries) const = 0; + + virtual int GetEntries(const IOption &option, const Query &query, std::vector &entries) const = 0; + + virtual int GetCount(const IOption &option, const Query &query, int &count) const = 0; + + // Put the batch values to the database. + virtual int PutBatch(const IOption &option, const std::vector &entries) = 0; + + // Delete the batch values from the database. + virtual int DeleteBatch(const IOption &option, const std::vector &keys) = 0; + + // Get the snapshot. + virtual int GetSnapshot(IKvDBSnapshot *&snapshot) const = 0; + + // Release the created snapshot. + virtual void ReleaseSnapshot(IKvDBSnapshot *&snapshot) = 0; + + // Start the transaction. + virtual int StartTransaction() = 0; + + // Commit the transaction. + virtual int Commit() = 0; + + // Roll back the transaction. + virtual int RollBack() = 0; + + // Check if the transaction already started manually + virtual bool IsTransactionStarted() const = 0; + + // Register observer. + virtual KvDBObserverHandle *RegisterObserver(unsigned mode, const Key &key, + const KvDBObserverAction &action, int &errCode) = 0; + + // Unregister observer. + virtual int UnRegisterObserver(const KvDBObserverHandle *observerHandle) = 0; + + // Register a conflict notifier. + virtual int SetConflictNotifier(int conflictType, const KvDBConflictAction &action) = 0; + + // Close and release the connection. + virtual int Close() = 0; + + virtual std::string GetIdentifier() const = 0; + + // Pragma interface. + virtual int Pragma(int cmd, void *parameter) = 0; + + // Rekey the database. + virtual int Rekey(const CipherPassword &passwd) = 0; + + // Empty passwords represent non-encrypted files. + // Export existing database files to a specified database file in the specified directory. + virtual int Export(const std::string &filePath, const CipherPassword &passwd) = 0; + + // Import the existing database files to the specified database file in the specified directory. + virtual int Import(const std::string &filePath, const CipherPassword &passwd) = 0; + + // Get the result set + virtual int GetResultSet(const IOption &option, const Key &keyPrefix, IKvDBResultSet *&resultSet) const = 0; + + virtual int GetResultSet(const IOption &option, const Query &query, IKvDBResultSet *&resultSet) const = 0; + + // Release the result set + virtual void ReleaseResultSet(IKvDBResultSet *&resultSet) = 0; + + virtual int RegisterLifeCycleCallback(const DatabaseLifeCycleNotifier ¬ifier) = 0; + + // Get the securityLabel and securityFlag + virtual int GetSecurityOption(int &securityLabel, int &securityFlag) const = 0; + + virtual int CheckIntegrity() const = 0; +}; +} // namespace DistributedDB + +#endif // I_KV_DB_CONNECTION_H diff --git a/mock/distributeddb/storage/include/ikvdb_factory.h b/mock/distributeddb/storage/include/ikvdb_factory.h new file mode 100644 index 00000000..eaf5600b --- /dev/null +++ b/mock/distributeddb/storage/include/ikvdb_factory.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_KV_DB_FACTORY_H +#define I_KV_DB_FACTORY_H + +#include + +#include "ikvdb.h" +#include "db_types.h" +#ifndef OMIT_MULTI_VER +#include "ikvdb_multi_ver_data_storage.h" +#include "ikvdb_commit_storage.h" +#endif + +namespace DistributedDB { +enum KvDBType { + LOCAL_KVDB = 0, + SINGER_VER_KVDB, + MULTI_VER_KVDB, + UNSUPPORT_KVDB_TYPE, +}; + +class IKvDBFactory { +public: + virtual ~IKvDBFactory() {} + + // Get current factory object. + static IKvDBFactory *GetCurrent(); + + // Set the factory object to 'current' + static void Register(IKvDBFactory *factory); + + virtual IKvDB *CreateKvDb(KvDBType kvDbType, int &errCode) = 0; + + // Create a key-value database for commit storage module. + virtual IKvDB *CreateCommitStorageDB(int &errCode) = 0; + +#ifndef OMIT_MULTI_VER + // Create the multi version storage for multi version natural store + virtual IKvDBMultiVerDataStorage *CreateMultiVerStorage(int &errCode) = 0; + + // Create the commit storage. The object can be deleted directly when it is not needed. + virtual IKvDBCommitStorage *CreateMultiVerCommitStorage(int &errCode) = 0; +#endif +private: + static IKvDBFactory *factory_; + static std::mutex instanceLock_; +}; +} // namespace DistributedDB + +#endif // I_KV_DB_FACTORY_H diff --git a/mock/distributeddb/storage/include/ikvdb_result_set.h b/mock/distributeddb/storage/include/ikvdb_result_set.h new file mode 100644 index 00000000..b0aa5de3 --- /dev/null +++ b/mock/distributeddb/storage/include/ikvdb_result_set.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_KV_DB_RESULT_SET_H +#define I_KV_DB_RESULT_SET_H + +#include "macro_utils.h" +#include "db_types.h" + +namespace DistributedDB { +class IKvDBResultSet { +public: + IKvDBResultSet() = default; + virtual ~IKvDBResultSet() {} + + DISABLE_COPY_ASSIGN_MOVE(IKvDBResultSet); + + // Initialize logic + virtual int Open(bool isMemDb) = 0; + + // Get total entries count. + // >= 0: count, < 0: errCode. + virtual int GetCount() const = 0; + + // Get current read position. + // >= 0: position, < 0: errCode + virtual int GetPosition() const = 0; + + // Move the read position to an absolute position value. + virtual int MoveTo(int position) const = 0; + + // Get the entry of current position. + virtual int GetEntry(Entry &entry) const = 0; + + // Finalize logic + virtual void Close() = 0; +}; +} // namespace DistributedDB + +#endif // I_KV_DB_RESULT_SET_H diff --git a/mock/distributeddb/storage/include/ikvdb_snapshot.h b/mock/distributeddb/storage/include/ikvdb_snapshot.h new file mode 100644 index 00000000..17d3fc82 --- /dev/null +++ b/mock/distributeddb/storage/include/ikvdb_snapshot.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_KV_DB_SNAP_SHOT_H +#define I_KV_DB_SNAP_SHOT_H + +#include "db_types.h" +#include "kv_store_changed_data.h" + +namespace DistributedDB { +class IKvDBSnapshot { +public: + virtual ~IKvDBSnapshot() {} + + // Get the value according the key in the snapshot + virtual int Get(const Key &key, Value &value) const = 0; + + // Get the data according the prefix key in the snapshot + virtual int GetEntries(const Key &keyPrefix, std::vector &entries) const = 0; +}; +} // namespace DistributedDB + +#endif // I_KV_DB_SNAP_SHOT_H diff --git a/mock/distributeddb/storage/include/ikvdb_sync_interface.h b/mock/distributeddb/storage/include/ikvdb_sync_interface.h new file mode 100644 index 00000000..572add41 --- /dev/null +++ b/mock/distributeddb/storage/include/ikvdb_sync_interface.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_KVDB_SYNC_INTERFACE_H +#define I_KVDB_SYNC_INTERFACE_H + +#include + +#include "db_types.h" +#include "kvdb_properties.h" +#include "sync_generic_interface.h" + +namespace DistributedDB { +class IKvDBSyncInterface : public SyncGenericInterface { +public: + + // Constructor/Destructor. + IKvDBSyncInterface() = default; + ~IKvDBSyncInterface() override = default; +}; +} // namespace DistributedDB + +#endif // I_KVDB_SYNC_INTERFACE_H diff --git a/mock/distributeddb/storage/include/isync_interface.h b/mock/distributeddb/storage/include/isync_interface.h new file mode 100644 index 00000000..0a4e0e70 --- /dev/null +++ b/mock/distributeddb/storage/include/isync_interface.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_SYNC_INTERFACE_H +#define I_SYNC_INTERFACE_H + +#include + +#include "db_types.h" +#include "kvdb_properties.h" + +namespace DistributedDB { +class ISyncInterface { +public: + enum { + SYNC_SVD = 1, // Single version data + SYNC_MVD, // Multi version data + SYNC_RELATION, // Relation version data + }; + + // Constructor/Destructor. + ISyncInterface() = default; + virtual ~ISyncInterface() = default; + + // Get interface type of this kvdb. + virtual int GetInterfaceType() const = 0; + + // Get the interface ref-count, in order to access asynchronously. + virtual void IncRefCount() = 0; + + // Drop the interface ref-count. + virtual void DecRefCount() = 0; + + // Get the identifier of this kvdb. + virtual std::vector GetIdentifier() const = 0; + // Get the dual tuple identifier of this kvdb. + virtual std::vector GetDualTupleIdentifier() const = 0; + + // Get the max timestamp of all entries in database. + virtual void GetMaxTimestamp(Timestamp &stamp) const = 0; + + // Get meta data associated with the given key. + virtual int GetMetaData(const Key &key, Value &value) const = 0; + + // Put meta data as a key-value entry. + virtual int PutMetaData(const Key &key, const Value &value) = 0; + + // Delete multiple meta data records in a transaction. + virtual int DeleteMetaData(const std::vector &keys) = 0; + + // Delete multiple meta data records with key prefix in a transaction. + virtual int DeleteMetaDataByPrefixKey(const Key &keyPrefix) const = 0; + + // Get all meta data keys. + virtual int GetAllMetaKeys(std::vector &keys) const = 0; + + virtual const KvDBProperties &GetDbProperties() const = 0; +}; +} // namespace DistributedDB + +#endif // I_SYNC_INTERFACE_H \ No newline at end of file diff --git a/mock/distributeddb/storage/include/kvdb_commit_notify_data.h b/mock/distributeddb/storage/include/kvdb_commit_notify_data.h new file mode 100644 index 00000000..2f82539a --- /dev/null +++ b/mock/distributeddb/storage/include/kvdb_commit_notify_data.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KVDB_COMMIT_NOTIFY_DATA_H +#define KVDB_COMMIT_NOTIFY_DATA_H + +#include + +#include "db_types.h" +#include "ref_object.h" +#include "macro_utils.h" +#include "kvdb_conflict_entry.h" + +namespace DistributedDB { +// Data from local commit or syncer commit. +class KvDBCommitNotifyData : public RefObject { +public: + KvDBCommitNotifyData() = default; + virtual ~KvDBCommitNotifyData() {} + DISABLE_COPY_ASSIGN_MOVE(KvDBCommitNotifyData); + + // get the new inserted entries. + virtual const std::list GetInsertedEntries(int &errCode) const = 0; + + // get the new updated entries. + virtual const std::list GetUpdatedEntries(int &errCode) const = 0; + + // get the new deleted entries. + virtual const std::list GetDeletedEntries(int &errCode) const = 0; + + // get all conflict entries when commit. + virtual const std::list GetCommitConflicts(int &errCode) const = 0; + + // database is cleared by user in the commit. + virtual bool IsCleared() const = 0; + + // test if the inserted/updated/deleted data is empty or not. + virtual bool IsChangedDataEmpty() const = 0; + + // test if the conflict data is empty or not. + virtual bool IsConflictedDataEmpty() const = 0; +}; +} // namespace DistributedDB + +#endif // KVDB_COMMIT_NOTIFY_DATA_H diff --git a/mock/distributeddb/storage/include/kvdb_conflict_entry.h b/mock/distributeddb/storage/include/kvdb_conflict_entry.h new file mode 100644 index 00000000..79f83c3e --- /dev/null +++ b/mock/distributeddb/storage/include/kvdb_conflict_entry.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KVDB_CONFLICT_ENTRY_H +#define KVDB_CONFLICT_ENTRY_H + +#include "db_types.h" + +namespace DistributedDB { +struct ConflictData { + Value value; + bool isDeleted = false; + bool isLocal = false; +}; + +struct KvDBConflictEntry { + int type = 1; + Key key; + ConflictData oldData; + ConflictData newData; +}; +} // namespace DistributedDB + +#endif // KVDB_CONFLICT_ENTRY_H diff --git a/mock/distributeddb/storage/include/kvdb_manager.h b/mock/distributeddb/storage/include/kvdb_manager.h new file mode 100644 index 00000000..34dbad5b --- /dev/null +++ b/mock/distributeddb/storage/include/kvdb_manager.h @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_DB_MANAGER_H +#define KV_DB_MANAGER_H + +#include +#include +#include +#include +#include + +#include "db_errno.h" +#include "ikvdb.h" +#include "ikvdb_factory.h" +#include "platform_specific.h" + +namespace DistributedDB { +class KvDBManager final { +public: + // used to generate process label + static const std::string PROCESS_LABEL_CONNECTOR; + + // used to open a kvdb with the given property + static IKvDB *OpenDatabase(const KvDBProperties &property, int &errCode); + + // used to open a kvdb with the given property + static IKvDBConnection *GetDatabaseConnection(const KvDBProperties &property, int &errCode, + bool isNeedIfOpened = true); + + // used to close the connection. + static int ReleaseDatabaseConnection(IKvDBConnection *connection); + + // used to delete a kvdb with the given property. + static int RemoveDatabase(const KvDBProperties &property); + + // Used to set the process userid and appid + static int SetProcessLabel(const std::string &appId, const std::string &userId); + + static int CalculateKvStoreSize(const KvDBProperties &property, uint64_t &size); + + // used to restore the sync module of the store. + static void RestoreSyncableKvStore(); + + // used to set the corruption handler. + static void SetDatabaseCorruptionHandler(const KvStoreCorruptionHandler &handler); + + // Attention. After call FindKvDB and kvdb is not null, you need to call DecObjRef. + IKvDB* FindKvDB(const std::string &identifier) const; + + // Get a KvDBManager instance, Singleton mode + static KvDBManager *GetInstance(); + + // Dump all db message in cache + void Dump(int fd); +private: + // Generate a KvDB unique Identifier + static std::string GenerateKvDBIdentifier(const KvDBProperties &property); + + // used to judge Db opened, can not remove Db file + static int CheckDatabaseFileStatus(const KvDBProperties &properties); + + IKvDB *OpenNewDatabase(const KvDBProperties &property, int &errCode); + + // Save to IKvDB to the global map + IKvDB *SaveKvDBToCache(IKvDB *kvDB); + + // Get IKvdb From global map + IKvDB *FindAndGetKvDBFromCache(const KvDBProperties &property, int &errCode) const; + + // Get IKvdb From global map + void RemoveKvDBFromCache(const IKvDB *kvDB); + + // Find a IKvdb From the given cache. the IKvDB will IncObjRef if found. + IKvDB *FindKvDBFromCache(const KvDBProperties &property, + const std::map &cache, bool isNeedCheckPasswd, int &errCode) const; + + bool IsOpenMemoryDb(const KvDBProperties &properties, const std::map &cache) const; + + void RestoreSyncerOfAllKvStore(); + + void SetAllDatabaseCorruptionHander(const KvStoreCorruptionHandler &handler); + + IKvDB *CreateDataBase(const KvDBProperties &property, int &errCode); + + IKvDB *GetDataBase(const KvDBProperties &property, int &errCode, bool isNeedIfOpened); + + void DataBaseCorruptNotify(const std::string &appId, const std::string &userId, const std::string &storeId); + + void DataBaseCorruptNotifyAsync(const std::string &appId, const std::string &userId, const std::string &storeId); + + void EnterDBOpenCloseProcess(const std::string &identifier); + + void ExitDBOpenCloseProcess(const std::string &identifier); + + void SetCorruptHandlerForDatabases(const std::map &kvDBMap); + + // Compare two schema objects and return true if both are empty, + // or both are not empty and the schemas are equal, otherwise return false. + static bool CompareSchemaObject(const SchemaObject &newSchema, const SchemaObject &oldSchema); + + // check schema is valid + static int CheckSchema(const IKvDB *kvDB, const KvDBProperties &properties); + + static int ExecuteRemoveDatabase(const KvDBProperties &properties); + + static void RemoveDBDirectory(const KvDBProperties &properties); + + int CheckKvDBProperties(const IKvDB *kvDB, const KvDBProperties &properties, + bool isNeedCheckPasswd) const; + + IKvDB *GetKvDBFromCacheByIdentify(const std::string &identifier, const std::map &cache) const; + + static int CheckRemoveStateAndRetry(const KvDBProperties &property); + + static int TryLockDB(const KvDBProperties &kvDBProp, int retryTimes); + static int UnlockDB(const KvDBProperties &kvDBProp); + + static std::atomic instance_; + static std::mutex kvDBLock_; + static std::mutex instanceLock_; + static std::map locks_; + + std::map localKvDBs_; + std::map multiVerNaturalStores_; + std::map singleVerNaturalStores_; + + std::mutex corruptMutex_; + std::mutex kvDBOpenMutex_; + std::condition_variable kvDBOpenCondition_; + std::set kvDBOpenSet_; + KvStoreCorruptionHandler corruptHandler_; +}; +} // namespace DistributedDB + +#endif // KV_DB_MANAGER_H diff --git a/mock/distributeddb/storage/include/kvdb_pragma.h b/mock/distributeddb/storage/include/kvdb_pragma.h new file mode 100644 index 00000000..649ae2c1 --- /dev/null +++ b/mock/distributeddb/storage/include/kvdb_pragma.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_DB_PRAGMA_H +#define KV_DB_PRAGMA_H + +#include +#include +#include + +#include "store_types.h" +#include "query_sync_object.h" + +namespace DistributedDB { +enum : int { + PRAGMA_AUTO_SYNC = 1, + PRAGMA_SYNC_DEVICES, + PRAGMA_RM_DEVICE_DATA, // remove the device data synced from remote by device name + PRAGMA_PERFORMANCE_ANALYSIS_GET_REPORT, + PRAGMA_PERFORMANCE_ANALYSIS_OPEN, + PRAGMA_PERFORMANCE_ANALYSIS_CLOSE, + PRAGMA_PERFORMANCE_ANALYSIS_SET_REPORTFILENAME, + PRAGMA_GET_IDENTIFIER_OF_DEVICE, + PRAGMA_GET_DEVICE_IDENTIFIER_OF_ENTRY, + PRAGMA_GET_QUEUED_SYNC_SIZE, + PRAGMA_SET_QUEUED_SYNC_LIMIT, + PRAGMA_GET_QUEUED_SYNC_LIMIT, + PRAGMA_SET_WIPE_POLICY, + PRAGMA_PUBLISH_LOCAL, + PRAGMA_UNPUBLISH_SYNC, + PRAGMA_SET_AUTO_LIFE_CYCLE, + PRAGMA_RESULT_SET_CACHE_MODE, + PRAGMA_RESULT_SET_CACHE_MAX_SIZE, + PRAGMA_TRIGGER_TO_MIGRATE_DATA, + PRAGMA_REMOTE_PUSH_FINISHED_NOTIFY, + PRAGMA_SET_SYNC_RETRY, + PRAGMA_ADD_EQUAL_IDENTIFIER, + PRAGMA_INTERCEPT_SYNC_DATA, + PRAGMA_SUBSCRIBE_QUERY, + PRAGMA_SET_MAX_LOG_LIMIT, + PRAGMA_EXEC_CHECKPOINT, +}; + +struct PragmaSync { + PragmaSync(const std::vector &devices, int mode, const QuerySyncObject &query, + const std::function &devicesMap)> &onComplete, + bool wait = false) + : devices_(devices), + mode_(mode), + onComplete_(onComplete), + wait_(wait), + isQuerySync_(true), + query_(query) + { + } + + PragmaSync(const std::vector &devices, int mode, + const std::function &devicesMap)> &onComplete, + bool wait = false) + : devices_(devices), + mode_(mode), + onComplete_(onComplete), + wait_(wait), + isQuerySync_(false), + query_(Query::Select()) + { + } + + std::vector devices_; + int mode_; + std::function &devicesMap)> onComplete_; + bool wait_; + bool isQuerySync_; + QuerySyncObject query_; +}; + +struct PragmaRemotePushNotify { + explicit PragmaRemotePushNotify(RemotePushFinishedNotifier notifier) : notifier_(notifier) {} + + RemotePushFinishedNotifier notifier_; +}; + +struct PragmaSetEqualIdentifier { + PragmaSetEqualIdentifier(const std::string &identifier, const std::vector &targets) + : identifier_(identifier), + targets_(targets) {} + + std::string identifier_; + std::vector targets_; +}; +} // namespace DistributedDB + +#endif // KV_DB_PRAGMA_H diff --git a/mock/distributeddb/storage/include/kvdb_properties.h b/mock/distributeddb/storage/include/kvdb_properties.h new file mode 100644 index 00000000..c985b0d8 --- /dev/null +++ b/mock/distributeddb/storage/include/kvdb_properties.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_DB_PROPERTIES_H +#define KV_DB_PROPERTIES_H + +#include +#include +#include + +#include "db_properties.h" +#include "schema_object.h" + +namespace DistributedDB { +class KvDBProperties final : public DBProperties { +public: + KvDBProperties(); + ~KvDBProperties() override; + + // Get the sub directory for different type database. + static std::string GetStoreSubDirectory(int type); + + // Get the password + void GetPassword(CipherType &type, CipherPassword &password) const; + + // Set the password + void SetPassword(CipherType type, const CipherPassword &password); + + // is schema exist + bool IsSchemaExist() const; + + // set schema + void SetSchema(const SchemaObject &schema); + + // get schema + SchemaObject GetSchema() const; + + // If it does not exist, use the int map default value 0 + int GetSecLabel() const; + + int GetSecFlag() const; + + // Get schema const reference if you can guarantee the lifecycle of this KvDBProperties + // The upper code will not change the schema if it is already set + const SchemaObject &GetSchemaConstRef() const; + + static const std::string FILE_NAME; + static const std::string SYNC_MODE; + static const std::string MEMORY_MODE; + static const std::string ENCRYPTED_MODE; + static const std::string FIRST_OPEN_IS_READ_ONLY; + static const std::string CREATE_DIR_BY_STORE_ID_ONLY; + static const std::string SECURITY_LABEL; + static const std::string SECURITY_FLAG; + static const std::string CONFLICT_RESOLVE_POLICY; + static const std::string CHECK_INTEGRITY; + static const std::string RM_CORRUPTED_DB; + static const std::string COMPRESS_ON_SYNC; + static const std::string COMPRESSION_RATE; + + static const int LOCAL_TYPE = 1; + static const int MULTI_VER_TYPE = 2; + static const int SINGLE_VER_TYPE = 3; + +private: + CipherType cipherType_; + CipherPassword password_; + SchemaObject schema_; +}; +} // namespace DistributedDB + +#endif // KV_DB_PROPERTIES_H diff --git a/mock/distributeddb/storage/include/multi_ver_def.h b/mock/distributeddb/storage/include/multi_ver_def.h new file mode 100644 index 00000000..64dd3c6b --- /dev/null +++ b/mock/distributeddb/storage/include/multi_ver_def.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_DEF_H +#define MULTI_VER_DEF_H + +#include +#include +#include + +#include "db_types.h" + +namespace DistributedDB { +using CommitID = std::vector; +using Version = uint64_t; +static const size_t MULTI_VER_TAG_SIZE = 8; + +struct MultiVerCommitNode { + static const uint64_t LOCAL_FLAG = 1; + static const uint64_t NON_LOCAL_FLAG = 0; + std::vector commitId; + std::vector leftParent; + std::vector rightParent; + uint64_t timestamp = 0; + uint64_t version = 0; // version for storage + uint64_t isLocal = 0; // merge node or native node + std::string deviceInfo; // device name +}; + +struct MultiVerEntryAuxData { + uint64_t operFlag = 0; + uint64_t timestamp = 0; + uint64_t oriTimestamp = 0; +}; + +struct MultiVerEntryData { + Key key; + Value value; + MultiVerEntryAuxData auxData; // auxiliaries +}; + +struct MultiVerTrimedVersionData { + Key key; // hash key + uint64_t operFlag = 0; + uint64_t version = 0; +}; + +struct MultiVerDiffData { + std::list inserted; + std::list updated; + std::list deleted; + bool isCleared = false; + void Reset() + { + inserted.clear(); + updated.clear(); + deleted.clear(); + isCleared = false; + } +}; + +enum class MultiVerDataType { + NATIVE_TYPE, + ALL_TYPE, +}; +} + +#endif // MULTI_VER_DEF_H diff --git a/mock/distributeddb/storage/include/multi_ver_kvdb_sync_interface.h b/mock/distributeddb/storage/include/multi_ver_kvdb_sync_interface.h new file mode 100644 index 00000000..83e985b5 --- /dev/null +++ b/mock/distributeddb/storage/include/multi_ver_kvdb_sync_interface.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_KVDB_SYNC_INTERFACE_H +#define MULTI_VER_KVDB_SYNC_INTERFACE_H + +#include +#include + +#include "multi_ver_def.h" +#include "multi_ver_kv_entry.h" +#include "ikvdb_sync_interface.h" + +namespace DistributedDB { +class MultiVerKvDBSyncInterface : public IKvDBSyncInterface { +public: + // Judge whether the commit existed. + virtual bool IsCommitExisted(const MultiVerCommitNode &commit) const = 0; + + // Get the latest commits of all devices in the current device. + virtual int GetDeviceLatestCommit(std::map &commitMap) const = 0; + + // Get the commit tree and exclude the existed commits from the remote device. + virtual int GetCommitTree(const std::map &commitMap, + std::vector &commits) const = 0; + + // Get all the data from one commit. + virtual int GetCommitData(const MultiVerCommitNode &commit, std::vector &entries) const = 0; + + // Create one kv entry from the serialized data from remote device. + virtual MultiVerKvEntry *CreateKvEntry(const std::vector &data) = 0; + + // Release the kv entry created from the interface of CreateKvEntry. + virtual void ReleaseKvEntry(const MultiVerKvEntry *entry) = 0; + + // Judge whether the slice hash-value existed. + virtual bool IsValueSliceExisted(const ValueSliceHash &value) const = 0; + + // Get the value according the slice hash value, and push the value to the remote. + virtual int GetValueSlice(const ValueSliceHash &hashValue, ValueSlice &sliceValue) const = 0; + + // Put the value when put the remote data into the local database. + virtual int PutValueSlice(const ValueSliceHash &hashValue, const ValueSlice &sliceValue) const = 0; + + // Put all the kv entries of one commit received from the remote. + virtual int PutCommitData(const MultiVerCommitNode &commit, const std::vector &entries, + const std::string &deviceName) = 0; + + // Merge the remote commit into the local tree. + virtual int MergeSyncCommit(const MultiVerCommitNode &commit, const std::vector &commits) = 0; + + virtual void NotifyStartSyncOperation() = 0; + + virtual void NotifyFinishSyncOperation() = 0; + + virtual int TransferSyncCommitDevInfo(MultiVerCommitNode &commit, const std::string &devId, + bool isSyncedIn) const = 0; +}; +} + +#endif // MULTI_VER_KVDB_SYNC_INTERFACE_H diff --git a/mock/distributeddb/storage/include/multi_ver_vacuum.h b/mock/distributeddb/storage/include/multi_ver_vacuum.h new file mode 100644 index 00000000..fbc70e7b --- /dev/null +++ b/mock/distributeddb/storage/include/multi_ver_vacuum.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_VACUUM_H +#define MULTI_VER_VACUUM_H + +#ifndef OMIT_MULTI_VER +#include +#include +#include +#include +#include +#include +#include +#include +#include "macro_utils.h" +#include "multi_ver_vacuum_executor.h" + +namespace DistributedDB { +enum class VacuumTaskStatus { + RUN_WAIT, + RUN_NING, + PAUSE_WAIT, + PAUSE_DONE, + ABORT_WAIT, + ABORT_DONE, + FINISH, +}; + +struct VacuumTaskContext { + VacuumTaskStatus status = VacuumTaskStatus::RUN_WAIT; + bool launchErrorHappen = false; + bool autoRelaunchOnce = false; + bool immediatelyRelaunchable = true; + uint64_t runWaitOrder = 0; + uint64_t pauseNeedCount = 0; + MultiVerVacuumExecutor *databaseHandle = nullptr; + // Information to conduct the vacuum task and record the progress. + // When in RUN_NING, PAUSE_WAIT, ABORT_WAIT status, no other thread except the only one background task thread + // will access and change these field, so there is no concurrency risk accessing and changing it without a lock. + std::list leftBranchCommits; + std::list rightBranchCommits; + std::list vacuumNeedRecords; + std::list shadowRecords; + bool isTransactionStarted = false; +}; + +// Pause and Continue should be call in pair for the same database. If Pause called more than Continue, then the task +// will stay in "inactive status". If Continue called more than Pause, then the excessive Continue will be neglected. +// It expected that every Pause following by a Continue sooner or later before Abort is called. +class MultiVerVacuum { +public: + // Default is enable, should be called at very first to change it, take effect only on instances created after it. + static void Enable(bool isEnable); + + DISABLE_COPY_ASSIGN_MOVE(MultiVerVacuum); + + // Call it when database firstly open + int Launch(const std::string &dbIdentifier, MultiVerVacuumExecutor *dbHandle); + + // Call it before database do write operation, it may block for a while if task is running + // It is guaranteed that no write transaction of this database will be used by vacuum after pause return + int Pause(const std::string &dbIdentifier); + + // Call it after database do write operation, and write transaction of this database may be used by vacuum. + // If autoRelaunchOnce true, the task will relaunch itself when finish, set false will not override previous true + int Continue(const std::string &dbIdentifier, bool autoRelaunchOnce); + + // Call it when database is about to close, it may block for a while if task is running + int Abort(const std::string &dbIdentifier); + + // Call it when observer_callback done or release an snapshot, to relaunch with some newer vacuumable commits + int AutoRelaunchOnce(const std::string &dbIdentifier); + + int QueryStatus(const std::string &dbIdentifier, VacuumTaskStatus &outStatus) const; + + MultiVerVacuum() = default; + ~MultiVerVacuum(); +private: + void VacuumTaskExecutor(); + void ExecuteSpecificVacuumTask(VacuumTaskContext &inTask); + + int DealWithLeftBranchCommit(VacuumTaskContext &inTask); + int DealWithLeftBranchVacuumNeedRecord(VacuumTaskContext &inTask); + int DealWithLeftBranchShadowRecord(VacuumTaskContext &inTask); + int DealWithRightBranchCommit(VacuumTaskContext &inTask); + int DealWithRightBranchVacuumNeedRecord(VacuumTaskContext &inTask); + + // Reducing duplicated code by merging similar code procedure of "DealLeftCommit" and "DealRightCommit" + int DoDealCommitOfLeftOrRight(VacuumTaskContext &inTask, std::list &commitList, bool isLeft); + + // Reducing duplicated code by merging similar code procedure of "DealLeftShadow" and "DealRightVacuumNeed" + int DoDeleteRecordOfLeftShadowOrRightVacuumNeed(VacuumTaskContext &inTask, + std::list &recordList); + + // Only for reducing duplicated code + void DoRollBackAndFinish(VacuumTaskContext &inTask); + int DoCommitAndQuitIfWaitStatusObserved(VacuumTaskContext &inTask); // Return E_OK continue otherwise quit + + // Call this immediately before changing the database + int StartTransactionIfNotYet(VacuumTaskContext &inTask); + + // Call this immediately before normally quit + int CommitTransactionIfNeed(VacuumTaskContext &inTask); + + // Call this immediately before abnormally quit, return void since already in abnormal. + void RollBackTransactionIfNeed(VacuumTaskContext &inTask); + + // All these following functions should be protected by the vacuumTaskMutex_ when called + void FinishVaccumTask(VacuumTaskContext &inTask); + void RelaunchVacuumTask(VacuumTaskContext &inTask); + void AbortVacuumTask(VacuumTaskContext &inTask); + void ResetNodeAndRecordContextInfo(VacuumTaskContext &inTask); + int SearchVacuumTaskToExecute(std::string &outDbIdentifier); + void ActivateBackgroundVacuumTaskExecution(); + void IncPauseNeedCount(VacuumTaskContext &inTask); + void DecPauseNeedCount(VacuumTaskContext &inTask); + bool IsPauseNotNeed(VacuumTaskContext &inTask); + + static std::atomic enabled_; + + mutable std::mutex vacuumTaskMutex_; + std::condition_variable vacuumTaskCv_; + uint64_t incRunWaitOrder_ = 0; + std::map dbMapVacuumTask_; + + // the search of available vacuumtask, the change of isBackgroundVacuumTaskInExecution_, and the activation of + // background execution, should all be protected by vacuumTaskMutex_, In order to avoid malfunction caused by + // concurrency situation which is described below: + // 1:Background search vacuumtask return none so decided to exit. + // 2:Foreground make vacuumtask available. + // 3:Foreground check isBackgroundVacuumTaskInExecution_ true so decided to nothing. + // 4:Background set isBackgroundVacuumTaskInExecution_ to false and exit. + // In this situation, no background execution running with available vacuumtask needs to be done. + bool isBackgroundVacuumTaskInExecution_ = false; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_VACUUM_H +#endif diff --git a/mock/distributeddb/storage/include/multi_ver_vacuum_executor.h b/mock/distributeddb/storage/include/multi_ver_vacuum_executor.h new file mode 100644 index 00000000..01126065 --- /dev/null +++ b/mock/distributeddb/storage/include/multi_ver_vacuum_executor.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_VACUUM_EXECUTOR_H +#define MULTI_VER_VACUUM_EXECUTOR_H + +#include +#include +#include +#include + +namespace DistributedDB { +enum class RecordType { + CLEAR, + DELETE, + VALID, // Not clear nor delete +}; + +struct MultiVerRecordInfo { + RecordType type; + uint64_t version; + std::vector hashKey; +}; + +struct MultiVerCommitInfo { + uint64_t version; + std::vector commitId; +}; + +// All functions will not be concurrently called +class MultiVerVacuumExecutor { +public: + // Call this always beyond transaction + virtual int GetVacuumAbleCommits(std::list &leftBranchCommits, + std::list &rightBranchCommits) const = 0; + + // Call this within or beyond transaction + virtual int GetVacuumNeedRecordsByVersion(uint64_t version, std::list &vacuumNeedRecords) = 0; + + // Call this within or beyond transaction + virtual int GetShadowRecordsOfClearTypeRecord(uint64_t version, const std::vector &hashKey, + std::list &shadowRecords) = 0; + + // Call this within or beyond transaction + virtual int GetShadowRecordsOfNonClearTypeRecord(uint64_t version, const std::vector &hashKey, + std::list &shadowRecords) = 0; + + // Call this before change the database + virtual int StartTransactionForVacuum() = 0; + + // Call this if nothing error happened, if this itself failed, do not need to call rollback + virtual int CommitTransactionForVacuum() = 0; + + // Call this if anything wrong happened after start transaction except commit fail + virtual int RollBackTransactionForVacuum() = 0; + + // Call this always within transaction + virtual int DeleteRecordTotally(uint64_t version, const std::vector &hashKey) = 0; + + // Call this always within transaction + virtual int MarkRecordAsVacuumDone(uint64_t version, const std::vector &hashKey) = 0; + + // Call this always within transaction + virtual int MarkCommitAsVacuumDone(const std::vector &commitId) = 0; + + virtual ~MultiVerVacuumExecutor() {}; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_VACUUM_EXECUTOR_H diff --git a/mock/distributeddb/storage/include/relational_db_sync_interface.h b/mock/distributeddb/storage/include/relational_db_sync_interface.h new file mode 100644 index 00000000..a94488e9 --- /dev/null +++ b/mock/distributeddb/storage/include/relational_db_sync_interface.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RELATIONAL_DB_SYNC_INTERFACE_H +#define RELATIONAL_DB_SYNC_INTERFACE_H +#ifdef RELATIONAL_STORE + +#include "query_sync_object.h" +#include "relational_schema_object.h" +#include "single_ver_kv_entry.h" +#include "sync_generic_interface.h" +#include "schema_negotiate.h" + +namespace DistributedDB { +class RelationalDBSyncInterface : public SyncGenericInterface { +public: + ~RelationalDBSyncInterface() override {}; + + virtual RelationalSchemaObject GetSchemaInfo() const = 0; + + // Get batch meta data associated with the given key. + virtual int GetBatchMetaData(const std::vector &keys, std::vector &entries) const = 0; + // Put batch meta data as a key-value entry vector + virtual int PutBatchMetaData(std::vector &entries) = 0; + + virtual std::vector GetTablesQuery() = 0; + + virtual int LocalDataChanged(int notifyEvent, std::vector &queryObj) = 0; + + virtual int CreateDistributedDeviceTable(const std::string &device, const RelationalSyncStrategy &syncStrategy) = 0; + + virtual int RegisterSchemaChangedCallback(const std::function &callback) = 0; + + using ISyncInterface::GetMaxTimestamp; + virtual int GetMaxTimestamp(const std::string &tableName, Timestamp ×tamp) const = 0; +}; +} +#endif // RELATIONAL_STORE +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/include/relational_store_connection.h b/mock/distributeddb/storage/include/relational_store_connection.h new file mode 100644 index 00000000..4c9ab7b6 --- /dev/null +++ b/mock/distributeddb/storage/include/relational_store_connection.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RELATIONAL_STORE_CONNECTION_H +#define RELATIONAL_STORE_CONNECTION_H +#ifdef RELATIONAL_STORE + +#include +#include + +#include "db_types.h" +#include "iconnection.h" +#include "macro_utils.h" +#include "ref_object.h" +#include "relational_store_delegate.h" + +namespace DistributedDB { +class IRelationalStore; +using RelationalObserverAction = std::function; +class RelationalStoreConnection : public IConnection, public virtual RefObject { +public: + struct SyncInfo { + const std::vector &devices; + SyncMode mode = SYNC_MODE_PUSH_PULL; + const SyncStatusCallback &onComplete; + const Query &query; + bool wait = true; + }; + + RelationalStoreConnection(); + + explicit RelationalStoreConnection(IRelationalStore *store); + + virtual ~RelationalStoreConnection() = default; + + DISABLE_COPY_ASSIGN_MOVE(RelationalStoreConnection); + + // Close and release the connection. + virtual int Close() = 0; + virtual int TriggerAutoSync() = 0; + virtual int SyncToDevice(SyncInfo &info) = 0; + virtual std::string GetIdentifier() = 0; + virtual int CreateDistributedTable(const std::string &tableName) = 0; + virtual int RegisterLifeCycleCallback(const DatabaseLifeCycleNotifier ¬ifier) = 0; + + virtual int RemoveDeviceData(const std::string &device) = 0; + virtual int RemoveDeviceData(const std::string &device, const std::string &tableName) = 0; + virtual void RegisterObserverAction(const RelationalObserverAction &action) = 0; + +protected: + // Get the stashed 'RelationalDB_ pointer' without ref. + template + DerivedDBType *GetDB() const + { + return static_cast(store_); + } + + virtual int Pragma(int cmd, void *parameter); + IRelationalStore *store_ = nullptr; + std::atomic isExclusive_; +}; +} // namespace DistributedDB +#endif +#endif // RELATIONAL_STORE_CONNECTION_H \ No newline at end of file diff --git a/mock/distributeddb/storage/include/relationaldb_properties.h b/mock/distributeddb/storage/include/relationaldb_properties.h new file mode 100644 index 00000000..2b60fede --- /dev/null +++ b/mock/distributeddb/storage/include/relationaldb_properties.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RELATIONALDB_PROPERTIES_H +#define RELATIONALDB_PROPERTIES_H +#ifdef RELATIONAL_STORE +#include +#include +#include + +#include "db_properties.h" +#include "relational_schema_object.h" + +namespace DistributedDB { +class RelationalDBProperties final : public DBProperties { +public: + RelationalDBProperties(); + ~RelationalDBProperties() override; + + // is schema exist + bool IsSchemaExist() const; + + // set schema + void SetSchema(const RelationalSchemaObject &schema); + + // get schema + RelationalSchemaObject GetSchema() const; + +private: + RelationalSchemaObject schema_; +}; +} +#endif +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/include/single_ver_kv_entry.h b/mock/distributeddb/storage/include/single_ver_kv_entry.h new file mode 100644 index 00000000..626ad237 --- /dev/null +++ b/mock/distributeddb/storage/include/single_ver_kv_entry.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_KV_ENTRY_H +#define SINGLE_VER_KV_ENTRY_H + +#include +#include "parcel.h" + +namespace DistributedDB { +class SingleVerKvEntry { +public: + virtual ~SingleVerKvEntry() {}; + virtual std::string GetOrigDevice() const = 0; + virtual void SetOrigDevice(const std::string &device) = 0; + virtual Timestamp GetTimestamp() const = 0; + virtual void SetTimestamp(Timestamp timestamp) = 0; + virtual Timestamp GetWriteTimestamp() const = 0; + virtual void SetWriteTimestamp(Timestamp timestamp) = 0; + virtual uint64_t GetFlag() const = 0; + virtual int SerializeData(Parcel &parcel, uint32_t softWareVersion) = 0; + virtual int DeSerializeData(Parcel &parcel) = 0; + virtual uint32_t CalculateLen(uint32_t softWareVersion) = 0; + virtual const Key &GetKey() const = 0; + virtual const Value &GetValue() const = 0; + virtual void SetKey(const Key &key) = 0; + virtual void SetValue(const Value &value) = 0; + virtual void SetHashKey(const Key &hashKey) = 0; + + static void Release(std::vector &entries) + { + for (auto &entry : entries) { + delete entry; + entry = nullptr; + } + entries.clear(); + }; +}; +} // namespace DistributedDB + +#endif // SINGLE_VER_KV_ENTRY_H diff --git a/mock/distributeddb/storage/include/single_ver_kvdb_sync_interface.h b/mock/distributeddb/storage/include/single_ver_kvdb_sync_interface.h new file mode 100644 index 00000000..dc9cdf29 --- /dev/null +++ b/mock/distributeddb/storage/include/single_ver_kvdb_sync_interface.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_KVDB_SYNC_INTERFACE_H +#define SINGLE_VER_KVDB_SYNC_INTERFACE_H + +#include "ikvdb_sync_interface.h" +#include "single_ver_kv_entry.h" +#include "iprocess_system_api_adapter.h" +#include "query_object.h" +#include "intercepted_data.h" + +namespace DistributedDB { +using MulDevTimeRanges = std::map>; +using MulDevSinVerKvEntry = std::map>; +using MulDevDataItems = std::map>; + +class SingleVerKvDBSyncInterface : public IKvDBSyncInterface { +public: + SingleVerKvDBSyncInterface() = default; + ~SingleVerKvDBSyncInterface() override = default; + + virtual SchemaObject GetSchemaInfo() const = 0; +}; +} + +#endif // SINGLE_VER_KVDB_SYNC_INTERFACE_H diff --git a/mock/distributeddb/storage/include/storage_engine_manager.h b/mock/distributeddb/storage/include/storage_engine_manager.h new file mode 100644 index 00000000..717cc0ed --- /dev/null +++ b/mock/distributeddb/storage/include/storage_engine_manager.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 STORAGE_ENGINE_MANAGER_H +#define STORAGE_ENGINE_MANAGER_H + +#include +#include +#include +#include +#include + +#include "storage_engine.h" + +namespace DistributedDB { +class StorageEngineManager final { +public: + static StorageEngine *GetStorageEngine(const KvDBProperties &property, int &errCode); + + static int ReleaseStorageEngine(StorageEngine *storageEngine); + + static int ForceReleaseStorageEngine(const std::string &identifier); + + static int ExecuteMigration(StorageEngine *storageEngine); + + DISABLE_COPY_ASSIGN_MOVE(StorageEngineManager); + +private: + StorageEngineManager(); + ~StorageEngineManager(); + + // Get a StorageEngineManager instance, Singleton mode + static StorageEngineManager *GetInstance(); + + int RegisterLockStatusListener(); + + void LockStatusNotifier(bool isAccessControlled); + + void RemoveEngineFromCache(const std::string &identifier); + + StorageEngine *CreateStorageEngine(const KvDBProperties &property, int &errCode); + + StorageEngine *FindStorageEngine(const std::string &identifier); + + void InsertStorageEngine(const std::string &identifier, StorageEngine *&storageEngine); + + void EraseStorageEngine(const std::string &identifier); + + void ReleaseResources(const std::string &identifier); + + int ReleaseEngine(StorageEngine *releaseEngine); + + void EnterGetEngineProcess(const std::string &identifier); + + void ExitGetEngineProcess(const std::string &identifier); + + static std::mutex instanceLock_; + static std::atomic instance_; + static bool isRegLockStatusListener_; + + static std::mutex storageEnginesLock_; + std::map storageEngines_; + + std::mutex getEngineMutex_; + std::condition_variable getEngineCondition_; + std::set getEngineSet_; + + NotificationChain::Listener *lockStatusListener_; +}; +} // namespace DistributedDB + +#endif // STORAGE_ENGINE_MANAGER_H diff --git a/mock/distributeddb/storage/include/sync_generic_interface.h b/mock/distributeddb/storage/include/sync_generic_interface.h new file mode 100644 index 00000000..1fc895c4 --- /dev/null +++ b/mock/distributeddb/storage/include/sync_generic_interface.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNC_GENERIC_INTERFACE_H +#define SYNC_GENERIC_INTERFACE_H + +#include "isync_interface.h" +#include "single_ver_kv_entry.h" +#include "query_object.h" + +namespace DistributedDB { +class SyncGenericInterface : public ISyncInterface { +public: + // Constructor/Destructor. + SyncGenericInterface() = default; + ~SyncGenericInterface() override = default; + + virtual int GetSyncData(Timestamp begin, Timestamp end, std::vector &dataItems, + ContinueToken &continueStmtToken, const DataSizeSpecInfo &dataSizeInfo) const + { + LOGE("GetSyncData not support!"); + return -E_NOT_SUPPORT; + } + + // Get the data which would be synced to other devices according the timestamp. + // if the data size is over than the blockSize, It would alloc one token and assign to continueStmtToken, + // it should be released when the read operation terminate. + virtual int GetSyncData(Timestamp begin, Timestamp end, std::vector &entries, + ContinueToken &continueStmtToken, const DataSizeSpecInfo &dataSizeInfo) const + { + LOGE("GetSyncData not support!"); + return -E_NOT_SUPPORT; + } + + // Get the data which would be synced with query condition + virtual int GetSyncData(QueryObject &query, const SyncTimeRange &timeRange, + const DataSizeSpecInfo &dataSizeInfo, ContinueToken &continueStmtToken, + std::vector &entries) const + { + return -E_NOT_SUPPORT; + } + + virtual int GetSyncDataNext(std::vector &dataItems, ContinueToken &continueStmtToken, + const DataSizeSpecInfo &dataSizeInfo) const + { + return -E_NOT_SUPPORT; + } + + virtual int GetSyncDataNext(std::vector &entries, ContinueToken &continueStmtToken, + const DataSizeSpecInfo &dataSizeInfo) const + { + return -E_NOT_SUPPORT; + } + + virtual int GetCompressionOption(bool &needCompressOnSync, uint8_t &compressionRate) const + { + return -E_NOT_SUPPORT; + } + + // Release the continue token of getting data. + virtual void ReleaseContinueToken(ContinueToken &continueStmtToken) const + { + } + + virtual int RemoveDeviceData(const std::string &deviceName, bool isNeedNotify) + { + return -E_NOT_SUPPORT; + } + + virtual bool IsReadable() const + { + return true; + } + + virtual int GetSecurityOption(SecurityOption &option) const + { + return -E_NOT_SUPPORT; + } + + virtual void NotifyRemotePushFinished(const std::string &targetId) const + { + } + + // Get the timestamp when database created or imported + virtual int GetDatabaseCreateTimestamp(Timestamp &outTime) const + { + return -E_NOT_SUPPORT; + } + + virtual int PutSyncDataWithQuery(const QueryObject &query, const std::vector &entries, + const std::string &deviceName) + { + return -E_NOT_SUPPORT; + } + + virtual int CheckAndInitQueryCondition(QueryObject &query) const + { + return -E_NOT_SUPPORT; + } + + virtual int InterceptData(std::vector &entries, const std::string &sourceID, + const std::string &targetID) const + { + return -E_NOT_SUPPORT; + } + + virtual int AddSubscribe(const std::string &subscribeId, const QueryObject &query, bool needCacheSubscribe) + { + return -E_NOT_SUPPORT; + } + + virtual int RemoveSubscribe(const std::string &subscribeId) + { + return -E_NOT_SUPPORT; + } + + virtual int RemoveSubscribe(const std::vector &subscribeIds) + { + return -E_NOT_SUPPORT; + } + + virtual int GetCompressionAlgo(std::set &algorithmSet) const + { + return -E_NOT_SUPPORT; + } + + virtual bool CheckCompatible(const std::string &schema, uint8_t type) const + { + return false; + } + + std::vector GetDualTupleIdentifier() const override + { + return {}; + } +}; +} +#endif // SYNC_GENERIC_INTERFACE_H diff --git a/mock/distributeddb/storage/src/data_transformer.cpp b/mock/distributeddb/storage/src/data_transformer.cpp new file mode 100644 index 00000000..5d1ff990 --- /dev/null +++ b/mock/distributeddb/storage/src/data_transformer.cpp @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "data_transformer.h" + +#include "db_common.h" +#include "db_errno.h" +#include "log_print.h" +#include "parcel.h" +#include "relational_schema_object.h" + +namespace DistributedDB { +int DataTransformer::TransformTableData(const TableDataWithLog &tableDataWithLog, + const std::vector &fieldInfoList, std::vector &dataItems) +{ + if (tableDataWithLog.dataList.empty()) { + return E_OK; + } + for (const RowDataWithLog& data : tableDataWithLog.dataList) { + DataItem dataItem; + int errCode = SerializeDataItem(data, fieldInfoList, dataItem); + if (errCode != E_OK) { + return errCode; + } + dataItems.push_back(std::move(dataItem)); + } + return E_OK; +} + +int DataTransformer::TransformDataItem(const std::vector &dataItems, + const std::vector &remoteFieldInfo, const std::vector &localFieldInfo, + OptTableDataWithLog &tableDataWithLog) +{ + if (dataItems.empty()) { + return E_OK; + } + std::vector indexMapping; + ReduceMapping(remoteFieldInfo, localFieldInfo); + for (const DataItem &dataItem : dataItems) { + OptRowDataWithLog dataWithLog; + int errCode = DeSerializeDataItem(dataItem, dataWithLog, remoteFieldInfo); + if (errCode != E_OK) { + return errCode; + } + tableDataWithLog.dataList.push_back(std::move(dataWithLog)); + } + return E_OK; +} + +int DataTransformer::SerializeDataItem(const RowDataWithLog &data, + const std::vector &fieldInfo, DataItem &dataItem) +{ + int errCode = SerializeValue(dataItem.value, data.rowData, fieldInfo); + if (errCode != E_OK) { + return errCode; + } + const LogInfo &logInfo = data.logInfo; + dataItem.timestamp = logInfo.timestamp; + dataItem.dev = logInfo.device; + dataItem.origDev = logInfo.originDev; + dataItem.writeTimestamp = logInfo.wTimestamp; + dataItem.flag = logInfo.flag; + dataItem.hashKey = logInfo.hashKey; + return E_OK; +} + +int DataTransformer::DeSerializeDataItem(const DataItem &dataItem, OptRowDataWithLog &data, + const std::vector &remoteFieldInfo) +{ + if ((dataItem.flag & DataItem::DELETE_FLAG) == 0 && + (dataItem.flag & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) == 0) { + int errCode = DeSerializeValue(dataItem.value, data.optionalData, remoteFieldInfo); + if (errCode != E_OK) { + return errCode; + } + } + + LogInfo &logInfo = data.logInfo; + logInfo.timestamp = dataItem.timestamp; + logInfo.device = dataItem.dev; + logInfo.originDev = dataItem.origDev; + logInfo.wTimestamp = dataItem.writeTimestamp; + logInfo.flag = dataItem.flag; + logInfo.hashKey = dataItem.hashKey; + return E_OK; +} + +uint32_t DataTransformer::CalDataValueLength(const DataValue &dataValue) +{ + static std::map lengthMap = { + { StorageType::STORAGE_TYPE_NULL, Parcel::GetUInt32Len()}, + { StorageType::STORAGE_TYPE_INTEGER, Parcel::GetInt64Len()}, + { StorageType::STORAGE_TYPE_REAL, Parcel::GetDoubleLen()} + }; + if (lengthMap.find(dataValue.GetType()) != lengthMap.end()) { + return lengthMap[dataValue.GetType()]; + } + if (dataValue.GetType() != StorageType::STORAGE_TYPE_BLOB && + dataValue.GetType() != StorageType::STORAGE_TYPE_TEXT) { + return 0u; + } + uint32_t length = 0; + switch (dataValue.GetType()) { + case StorageType::STORAGE_TYPE_BLOB: + case StorageType::STORAGE_TYPE_TEXT: + (void)dataValue.GetBlobLength(length); + length = Parcel::GetEightByteAlign(length); + length += Parcel::GetUInt32Len(); // record data length + break; + default: + break; + } + return length; +} + +void DataTransformer::ReduceMapping(const std::vector &remoteFieldInfo, + const std::vector &localFieldInfo) +{ + std::map fieldMap; + for (int i = 0; i < static_cast(remoteFieldInfo.size()); ++i) { + const auto &fieldInfo = remoteFieldInfo[i]; + fieldMap[fieldInfo.GetFieldName()] = i; + } +} + +namespace { +int SerializeNullValue(const DataValue &dataValue, Parcel &parcel) +{ + return parcel.WriteUInt32(0u); +} + +int DeSerializeNullValue(DataValue &dataValue, Parcel &parcel) +{ + uint32_t dataLength = -1; + (void)parcel.ReadUInt32(dataLength); + if (parcel.IsError() || dataLength != 0) { + return -E_PARSE_FAIL; + } + dataValue.ResetValue(); + return E_OK; +} + +int SerializeIntValue(const DataValue &dataValue, Parcel &parcel) +{ + int64_t val = 0; + (void)dataValue.GetInt64(val); + return parcel.WriteInt64(val); +} + +int DeSerializeIntValue(DataValue &dataValue, Parcel &parcel) +{ + int64_t val = 0; + (void)parcel.ReadInt64(val); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + dataValue = val; + return E_OK; +} + +int SerializeDoubleValue(const DataValue &dataValue, Parcel &parcel) +{ + double val = 0; + (void)dataValue.GetDouble(val); + return parcel.WriteDouble(val); +} + +int DeSerializeDoubleValue(DataValue &dataValue, Parcel &parcel) +{ + double val = 0; + (void)parcel.ReadDouble(val); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + dataValue = val; + return E_OK; +} + +int SerializeBlobValue(const DataValue &dataValue, Parcel &parcel) +{ + Blob val; + (void)dataValue.GetBlob(val); + uint32_t size = val.GetSize(); + if (size == 0) { + return SerializeNullValue(dataValue, parcel); + } + int errCode = parcel.WriteUInt32(size); + if (errCode != E_OK) { + return errCode; + } + return parcel.WriteBlob(reinterpret_cast(val.GetData()), size); +} + +int DeSerializeBlobByType(DataValue &dataValue, Parcel &parcel, StorageType type) +{ + uint32_t blobLength = 0; + (void)parcel.ReadUInt32(blobLength); + if (blobLength == 0) { + dataValue.ResetValue(); + return E_OK; + } + if (blobLength >= DBConstant::MAX_VALUE_SIZE || parcel.IsError()) { // One blob cannot be over one value size. + return -E_PARSE_FAIL; + } + auto array = new (std::nothrow) char[blobLength](); + if (array == nullptr) { + return -E_OUT_OF_MEMORY; + } + (void)parcel.ReadBlob(array, blobLength); + if (parcel.IsError()) { + delete []array; + return -E_PARSE_FAIL; + } + int errCode = -E_NOT_SUPPORT; + if (type == StorageType::STORAGE_TYPE_TEXT) { + errCode = dataValue.SetText(reinterpret_cast(array), blobLength); + } else if (type == StorageType::STORAGE_TYPE_BLOB) { + Blob val; + errCode = val.WriteBlob(reinterpret_cast(array), blobLength); + if (errCode == E_OK) { + errCode = dataValue.SetBlob(val); + } + } + delete []array; + return errCode; +} + +int DeSerializeBlobValue(DataValue &dataValue, Parcel &parcel) +{ + return DeSerializeBlobByType(dataValue, parcel, StorageType::STORAGE_TYPE_BLOB); +} + +int SerializeTextValue(const DataValue &dataValue, Parcel &parcel) +{ + return SerializeBlobValue(dataValue, parcel); +} + +int DeSerializeTextValue(DataValue &dataValue, Parcel &parcel) +{ + return DeSerializeBlobByType(dataValue, parcel, StorageType::STORAGE_TYPE_TEXT); +} + +int SerializeDataValue(const DataValue &dataValue, Parcel &parcel) +{ + static const std::function funcs[] = { + SerializeNullValue, SerializeIntValue, + SerializeDoubleValue, SerializeTextValue, SerializeBlobValue, + }; + StorageType type = dataValue.GetType(); + parcel.WriteUInt32(static_cast(type)); + if (type < StorageType::STORAGE_TYPE_NULL || type > StorageType::STORAGE_TYPE_BLOB) { + LOGE("Cannot serialize %u", static_cast(type)); + return -E_NOT_SUPPORT; + } + return funcs[static_cast(type) - 1](dataValue, parcel); +} + +int DeserializeDataValue(DataValue &dataValue, Parcel &parcel) +{ + static const std::function funcs[] = { + DeSerializeNullValue, DeSerializeIntValue, + DeSerializeDoubleValue, DeSerializeTextValue, DeSerializeBlobValue, + }; + uint32_t type = 0; + parcel.ReadUInt32(type); + if (type < static_cast(StorageType::STORAGE_TYPE_NULL) || + type > static_cast(StorageType::STORAGE_TYPE_BLOB)) { + LOGE("Cannot deserialize %u", type); + return -E_PARSE_FAIL; + } + return funcs[type - 1](dataValue, parcel); +} +} + +int DataTransformer::SerializeValue(Value &value, const RowData &rowData, const std::vector &fieldInfoList) +{ + if (rowData.size() != fieldInfoList.size()) { + LOGE("[DataTransformer][SerializeValue] unequal field counts!"); + return -E_INVALID_ARGS; + } + + uint32_t totalLength = Parcel::GetUInt64Len(); // first record field count + for (uint32_t i = 0; i < rowData.size(); ++i) { + const auto &dataValue = rowData[i]; + totalLength += Parcel::GetUInt32Len(); // For save the dataValue's type. + uint32_t dataLength = CalDataValueLength(dataValue); + totalLength += dataLength; + } + value.resize(totalLength); + if (value.size() != totalLength) { + return -E_OUT_OF_MEMORY; + } + Parcel parcel(value.data(), value.size()); + (void)parcel.WriteUInt64(rowData.size()); + for (const auto &dataValue : rowData) { + int errCode = SerializeDataValue(dataValue, parcel); + if (errCode != E_OK) { + value.clear(); + return errCode; + } + } + return E_OK; +} + +int DataTransformer::DeSerializeValue(const Value &value, OptRowData &optionalData, + const std::vector &remoteFieldInfo) +{ + Parcel parcel(const_cast(value.data()), value.size()); + uint64_t fieldCount = 0; + (void)parcel.ReadUInt64(fieldCount); + if (fieldCount > DBConstant::MAX_COLUMN || parcel.IsError()) { + return -E_PARSE_FAIL; + } + for (size_t i = 0; i < fieldCount; ++i) { + DataValue dataValue; + int errCode = DeserializeDataValue(dataValue, parcel); + if (errCode != E_OK) { + LOGD("[DataTransformer][DeSerializeValue] deSerialize failed"); + return errCode; + } + optionalData.push_back(std::move(dataValue)); + } + return E_OK; +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/data_transformer.h b/mock/distributeddb/storage/src/data_transformer.h new file mode 100644 index 00000000..7a48276f --- /dev/null +++ b/mock/distributeddb/storage/src/data_transformer.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DATA_TRANSFORMER_H +#define DATA_TRANSFORMER_H +#ifdef RELATIONAL_STORE + +#include +#include "data_value.h" +#include "db_types.h" +#include "relational_schema_object.h" + +namespace DistributedDB { +using RowData = std::vector; +using OptRowData = std::vector; + +struct LogInfo { + int64_t dataKey = -1; + std::string device; + std::string originDev; + Timestamp timestamp = 0; + Timestamp wTimestamp = 0; + uint64_t flag = 0; + Key hashKey; // primary key hash value +}; + +struct RowDataWithLog { + LogInfo logInfo; + RowData rowData; +}; + +struct OptRowDataWithLog { + LogInfo logInfo; + OptRowData optionalData; +}; + +struct TableDataWithLog { + std::string tableName; + std::vector dataList; +}; + +struct OptTableDataWithLog { + std::string tableName; + std::vector dataList; +}; + +class DataTransformer { +public: + static int TransformTableData(const TableDataWithLog &tableDataWithLog, + const std::vector &fieldInfoList, std::vector &dataItems); + static int TransformDataItem(const std::vector &dataItems, const std::vector &remoteFieldInfo, + const std::vector &localFieldInfo, OptTableDataWithLog &tableDataWithLog); + + static int SerializeDataItem(const RowDataWithLog &data, const std::vector &fieldInfo, + DataItem &dataItem); + static int DeSerializeDataItem(const DataItem &dataItem, OptRowDataWithLog &data, + const std::vector &remoteFieldInfo); + static void ReduceMapping(const std::vector &remoteFieldInfo, + const std::vector &localFieldInfo); + +private: + static int SerializeValue(Value &value, const RowData &rowData, const std::vector &fieldInfoList); + static int DeSerializeValue(const Value &value, OptRowData &optionalData, + const std::vector &remoteFieldInfo); + + static uint32_t CalDataValueLength(const DataValue &dataValue); +}; +} + +#endif +#endif // DATA_TRANSFORMER_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/db_properties.cpp b/mock/distributeddb/storage/src/db_properties.cpp new file mode 100644 index 00000000..86fd161b --- /dev/null +++ b/mock/distributeddb/storage/src/db_properties.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_properties.h" + +namespace DistributedDB { +const std::string DBProperties::CREATE_IF_NECESSARY = "createIfNecessary"; +const std::string DBProperties::DATABASE_TYPE = "databaseType"; +const std::string DBProperties::DATA_DIR = "dataDir"; +const std::string DBProperties::USER_ID = "userId"; +const std::string DBProperties::APP_ID = "appId"; +const std::string DBProperties::STORE_ID = "storeId"; +const std::string DBProperties::IDENTIFIER_DATA = "identifier"; +const std::string DBProperties::IDENTIFIER_DIR = "identifierDir"; +const std::string DBProperties::DUAL_TUPLE_IDENTIFIER_DATA = "dualTupleIdentifier"; +const std::string DBProperties::SYNC_DUAL_TUPLE_MODE = "syncDualTuple"; + +std::string DBProperties::GetStringProp(const std::string &name, const std::string &defaultValue) const +{ + auto iter = stringProperties_.find(name); + if (iter != stringProperties_.end()) { + return iter->second; + } + return defaultValue; +} + +void DBProperties::SetStringProp(const std::string &name, const std::string &value) +{ + stringProperties_[name] = value; +} + +bool DBProperties::GetBoolProp(const std::string &name, bool defaultValue) const +{ + auto iter = boolProperties_.find(name); + if (iter != boolProperties_.end()) { + return iter->second; + } + return defaultValue; +} + +void DBProperties::SetBoolProp(const std::string &name, bool value) +{ + boolProperties_[name] = value; +} + +int DBProperties::GetIntProp(const std::string &name, int defaultValue) const +{ + auto iter = intProperties_.find(name); + if (iter != intProperties_.end()) { + return iter->second; + } + return defaultValue; +} + +void DBProperties::SetIntProp(const std::string &name, int value) +{ + intProperties_[name] = value; +} + +void DBProperties::SetIdentifier(const std::string &userId, const std::string &appId, const std::string &storeId) +{ + SetStringProp(DBProperties::APP_ID, appId); + SetStringProp(DBProperties::USER_ID, userId); + SetStringProp(DBProperties::STORE_ID, storeId); + std::string hashIdentifier = DBCommon::TransferHashString(DBCommon::GenerateIdentifierId(storeId, appId, userId)); + SetStringProp(DBProperties::IDENTIFIER_DATA, hashIdentifier); + std::string dualIdentifier = DBCommon::TransferHashString(DBCommon::GenerateDualTupleIdentifierId(storeId, appId)); + SetStringProp(DBProperties::DUAL_TUPLE_IDENTIFIER_DATA, dualIdentifier); +} +} \ No newline at end of file diff --git a/mock/distributeddb/storage/src/default_factory.cpp b/mock/distributeddb/storage/src/default_factory.cpp new file mode 100644 index 00000000..0667c552 --- /dev/null +++ b/mock/distributeddb/storage/src/default_factory.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "default_factory.h" + +#include + +#include "db_errno.h" +#include "sqlite_local_kvdb.h" +#ifndef OMIT_MULTI_VER +#include "multi_ver_natural_store.h" +#include "multi_ver_natural_store_commit_storage.h" +#endif +#include "sqlite_single_ver_natural_store.h" +#ifndef OMIT_MULTI_VER +#include "sqlite_multi_ver_data_storage.h" +#endif + +namespace DistributedDB { +IKvDB *DefaultFactory::CreateKvDb(KvDBType kvDbType, int &errCode) +{ + switch (kvDbType) { + case LOCAL_KVDB: + return CreateLocalKvDB(errCode); + case SINGER_VER_KVDB: + return CreateSingleVerNaturalStore(errCode); +#ifndef OMIT_MULTI_VER + case MULTI_VER_KVDB: + return CreateMultiVerNaturalStore(errCode); +#endif + default: + errCode = -E_INVALID_ARGS; + return nullptr; + } +} + +IKvDB *DefaultFactory::CreateLocalKvDB(int &errCode) +{ + IKvDB *kvDb = new (std::nothrow) SQLiteLocalKvDB(); + errCode = ((kvDb == nullptr) ? -E_OUT_OF_MEMORY : E_OK); + return kvDb; +} + +#ifndef OMIT_MULTI_VER +// Create the multi-version natural store, it contains a commit version and commit storage kvdb. +IKvDB *DefaultFactory::CreateMultiVerNaturalStore(int &errCode) +{ + IKvDB *kvDb = new (std::nothrow) MultiVerNaturalStore(); + errCode = ((kvDb == nullptr) ? -E_OUT_OF_MEMORY : E_OK); + return kvDb; +} +#endif + +// Create the single version natural store. +IKvDB *DefaultFactory::CreateSingleVerNaturalStore(int &errCode) +{ + IKvDB *kvDb = new (std::nothrow) SQLiteSingleVerNaturalStore(); + errCode = ((kvDb == nullptr) ? -E_OUT_OF_MEMORY : E_OK); + return kvDb; +} + +// Create a key-value database for commit storage module. +IKvDB *DefaultFactory::CreateCommitStorageDB(int &errCode) +{ + return CreateLocalKvDB(errCode); +} + +#ifndef OMIT_MULTI_VER +IKvDBMultiVerDataStorage *DefaultFactory::CreateMultiVerStorage(int &errCode) +{ + IKvDBMultiVerDataStorage *multiStorage = new (std::nothrow) SQLiteMultiVerDataStorage(); + errCode = ((multiStorage == nullptr) ? -E_OUT_OF_MEMORY : E_OK); + return multiStorage; +} + +// Create the commit storage. The object can be deleted directly when it is not needed. +IKvDBCommitStorage *DefaultFactory::CreateMultiVerCommitStorage(int &errCode) +{ + IKvDBCommitStorage *commitStorage = new (std::nothrow) MultiVerNaturalStoreCommitStorage(); + errCode = ((commitStorage == nullptr) ? -E_OUT_OF_MEMORY : E_OK); + return commitStorage; +} +#endif +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/default_factory.h b/mock/distributeddb/storage/src/default_factory.h new file mode 100644 index 00000000..975a0067 --- /dev/null +++ b/mock/distributeddb/storage/src/default_factory.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DEFAULT_FACTORY_H +#define DEFAULT_FACTORY_H + +#include "ikvdb.h" +#include "ikvdb_factory.h" + +namespace DistributedDB { +class DefaultFactory final : public IKvDBFactory { +public: + DefaultFactory() noexcept {} + ~DefaultFactory() override {} + + DISABLE_COPY_ASSIGN_MOVE(DefaultFactory); + IKvDB *CreateKvDb(KvDBType kvDbType, int &errCode) override; + + // Create a key-value database for commit storage module. + IKvDB *CreateCommitStorageDB(int &errCode) override; + +#ifndef OMIT_MULTI_VER + // Create the multi version storage for multi version natural store + IKvDBMultiVerDataStorage *CreateMultiVerStorage(int &errCode) override; + + // Create the commit storage. The object can be deleted directly when it is not needed. + IKvDBCommitStorage *CreateMultiVerCommitStorage(int &errCode) override; +#endif +private: + // Create the a local kv db + IKvDB *CreateLocalKvDB(int &errCode); + +#ifndef OMIT_MULTI_VER + // Create the a natural store, it contains a commit version and commit storage kvdb. + IKvDB *CreateMultiVerNaturalStore(int &errCode); +#endif + + IKvDB *CreateSingleVerNaturalStore(int &errCode); +}; +} // namespace DistributedDB +#endif // DEFAULT_FACTORY_H diff --git a/mock/distributeddb/storage/src/generic_kvdb.cpp b/mock/distributeddb/storage/src/generic_kvdb.cpp new file mode 100644 index 00000000..556a3fdc --- /dev/null +++ b/mock/distributeddb/storage/src/generic_kvdb.cpp @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "generic_kvdb.h" +#include "platform_specific.h" +#include "log_print.h" +#include "db_common.h" +#include "db_constant.h" +#include "db_errno.h" +#include "package_file.h" +#include "runtime_context.h" +#include "kvdb_utils.h" +#include "kvdb_commit_notify_filterable_data.h" + +namespace DistributedDB { +DEFINE_OBJECT_TAG_FACILITIES(GenericKvDB); + +GenericKvDB::GenericKvDB() + : performance_(nullptr), + eventNotifyCounter_(0), + connectionCount_(0), + notificationChain_(nullptr), + operatePerm_(OperatePerm::NORMAL_PERM) +{} + +GenericKvDB::~GenericKvDB() +{ + if (connectionCount_ > 0) { + LOGF("KvDB destructed with connection count > 0."); + } + + if (notificationChain_ != nullptr) { + RefObject::KillAndDecObjRef(notificationChain_); + notificationChain_ = nullptr; + } +} + +const KvDBProperties &GenericKvDB::GetMyProperties() const +{ + return MyProp(); +} + +IKvDBConnection *GenericKvDB::GetDBConnection(int &errCode) +{ + std::lock_guard lock(connectMutex_); + if (operatePerm_ != OperatePerm::NORMAL_PERM) { + errCode = (operatePerm_ == OperatePerm::DISABLE_PERM) ? -E_STALE : -E_BUSY; + return nullptr; + } + + GenericKvDBConnection *connection = NewConnection(errCode); + if (connection != nullptr) { + IncObjRef(this); + IncreaseConnectionCounter(); + } + return connection; +} + +void GenericKvDB::OnClose(const std::function ¬ifier) +{ + AutoLock lockGuard(this); + if (notifier) { + closeNotifiers_.push_back(notifier); + } else { + LOGW("Register kvdb 'Close()' notifier failed, notifier is null."); + } +} + +std::string GenericKvDB::GetStoreId() const +{ + return MyProp().GetStringProp(KvDBProperties::STORE_ID, ""); +} + +void GenericKvDB::DelConnection(GenericKvDBConnection *connection) +{ + delete connection; + connection = nullptr; +} + +void GenericKvDB::ReleaseDBConnection(GenericKvDBConnection *connection) +{ + if (connectionCount_.load() == 1) { + SetConnectionFlag(false); + } + + connectMutex_.lock(); + if (connection != nullptr) { + connection->SetSafeDeleted(); + DelConnection(connection); + DecreaseConnectionCounter(); + connectMutex_.unlock(); + DecObjRef(this); + } else { + connectMutex_.unlock(); + } +} + +void GenericKvDB::CommitNotify(int notifyEvent, KvDBCommitNotifyFilterAbleData *data) +{ + if (notificationChain_ == nullptr) { + LOGE("Failed to do commit notify, notificationChain_ is nullptr."); + return; + } + ++eventNotifyCounter_; + if (data == nullptr) { + notificationChain_->NotifyEvent(static_cast(notifyEvent), nullptr); + } else { + data->SetMyDb(this, eventNotifyCounter_); + data->IncObjRef(data); + int errCode = RuntimeContext::GetInstance()->ScheduleQueuedTask(GetStoreId(), + std::bind(&GenericKvDB::CommitNotifyAsync, this, notifyEvent, data)); + if (errCode != E_OK) { + LOGE("Failed to do commit notify, schedule task err:%d.", errCode); + data->DecObjRef(data); + data = nullptr; + } + } +} + +void GenericKvDB::CorruptNotifyAsync() const +{ + { + std::lock_guard lock(corruptMutex_); + if (corruptHandler_) { + corruptHandler_(); + } + } + + DecObjRef(this); +} + +void GenericKvDB::CorruptNotify() const +{ + IncObjRef(this); + int errCode = RuntimeContext::GetInstance()->ScheduleQueuedTask(GetStoreId(), + std::bind(&GenericKvDB::CorruptNotifyAsync, this)); + if (errCode != E_OK) { + LOGE("Failed to do the corrupt notify, schedule task err:%d.", errCode); + DecObjRef(this); + } +} + +int GenericKvDB::TryToDisableConnection(OperatePerm perm) +{ + std::lock_guard lock(connectMutex_); + if (operatePerm_ != OperatePerm::NORMAL_PERM) { + return -E_BUSY; + } + // more than one connection, should prevent the rekey operation. + if (connectionCount_ > 1) { + return -E_BUSY; + } + + operatePerm_ = perm; + return E_OK; +} + +void GenericKvDB::ReEnableConnection(OperatePerm perm) +{ + std::lock_guard lock(connectMutex_); + if (perm == operatePerm_) { + operatePerm_ = OperatePerm::NORMAL_PERM; + } +} + +NotificationChain::Listener *GenericKvDB::RegisterEventListener(EventType type, + const NotificationChain::Listener::OnEvent &onEvent, + const NotificationChain::Listener::OnFinalize &onFinalize, int &errCode) +{ + NotificationChain::Listener *listener = nullptr; + if (notificationChain_ != nullptr) { + listener = notificationChain_->RegisterListener(type, onEvent, onFinalize, errCode); + } else { + errCode = -E_NOT_PERMIT; + } + return listener; +} + +uint64_t GenericKvDB::GetEventNotifyCounter() const +{ + return eventNotifyCounter_; +} + +// Called when a new connection created. +void GenericKvDB::IncreaseConnectionCounter() +{ + connectionCount_.fetch_add(1, std::memory_order_seq_cst); + if (connectionCount_.load() > 0) { + SetConnectionFlag(true); + } +} + +// Called when a connection released. +void GenericKvDB::DecreaseConnectionCounter() +{ + int count = connectionCount_.fetch_sub(1, std::memory_order_seq_cst); + if (count <= 0) { + LOGF("Decrease kvdb connection counter failed, count <= 0."); + return; + } + if (count != 1) { + return; + } + + operatePerm_ = OperatePerm::DISABLE_PERM; + LockObj(); + auto notifiers = std::move(closeNotifiers_); + UnlockObj(); + + for (auto ¬ifier : notifiers) { + if (notifier) { + notifier(); + } + } + + Close(); +} + +// Register a new notification event type. +int GenericKvDB::RegisterNotificationEventType(int eventType) +{ + if (notificationChain_ == nullptr) { + // Lazy init. + notificationChain_ = new (std::nothrow) NotificationChain; + if (notificationChain_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + } + // We DON'T release 'notificationChain_' here event if RegisterEventType() + // is failed, it belongs to the class object and is released in the destructor. + return notificationChain_->RegisterEventType(static_cast(eventType)); +} + +// Unregister a notification event type. +void GenericKvDB::UnRegisterNotificationEventType(int eventType) +{ + if (notificationChain_ != nullptr) { + notificationChain_->UnRegisterEventType(static_cast(eventType)); + } +} + +const KvDBProperties &GenericKvDB::MyProp() const +{ + return properties_; +} + +KvDBProperties &GenericKvDB::MyProp() +{ + return properties_; +} + +int GenericKvDB::GetWorkDir(const KvDBProperties &kvDBProp, std::string &workDir) +{ + std::string origDataDir = kvDBProp.GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string identifierDir = kvDBProp.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + if (origDataDir.empty()) { + return -E_INVALID_ARGS; + } + + workDir = origDataDir + "/" + identifierDir; + return E_OK; +} + +void GenericKvDB::SetCorruptHandler(const DatabaseCorruptHandler &handler) +{ + std::lock_guard lock(corruptMutex_); + corruptHandler_ = handler; +} + +void GenericKvDB::OpenPerformanceAnalysis() +{ + if (performance_ != nullptr) { + performance_->OpenPerformanceAnalysis(); + } +} + +void GenericKvDB::ClosePerformanceAnalysis() +{ + if (performance_ != nullptr) { + performance_->ClosePerformanceAnalysis(); + } +} + +void GenericKvDB::CommitNotifyAsync(int notifyEvent, KvDBCommitNotifyFilterAbleData *data) +{ + notificationChain_->NotifyEvent(static_cast(notifyEvent), data); + data->DecObjRef(data); + data = nullptr; +} + +int GenericKvDB::RegisterFunction(RegisterFuncType type) +{ + if (type >= REGISTER_FUNC_TYPE_MAX) { + return -E_NOT_SUPPORT; + } + std::lock_guard lock(regFuncCountMutex_); + if (registerFunctionCount_.empty()) { + registerFunctionCount_.resize(static_cast(REGISTER_FUNC_TYPE_MAX), 0); + if (registerFunctionCount_.size() != static_cast(REGISTER_FUNC_TYPE_MAX)) { + return -E_OUT_OF_MEMORY; + } + } + registerFunctionCount_[type]++; + return E_OK; +} + +int GenericKvDB::UnregisterFunction(RegisterFuncType type) +{ + if (type >= REGISTER_FUNC_TYPE_MAX) { + return -E_NOT_SUPPORT; + } + std::lock_guard lock(regFuncCountMutex_); + if (registerFunctionCount_.size() != static_cast(REGISTER_FUNC_TYPE_MAX) || + registerFunctionCount_[type] == 0) { + return -E_UNEXPECTED_DATA; + } + registerFunctionCount_[type]--; + return E_OK; +} + +uint32_t GenericKvDB::GetRegisterFunctionCount(RegisterFuncType type) const +{ + std::lock_guard lock(regFuncCountMutex_); + if (type >= REGISTER_FUNC_TYPE_MAX || + registerFunctionCount_.size() != static_cast(REGISTER_FUNC_TYPE_MAX)) { + return 0; + } + return registerFunctionCount_[type]; +} + +int GenericKvDB::TransObserverTypeToRegisterFunctionType(int observerType, RegisterFuncType &type) const +{ + (void)observerType; + (void)type; + return -E_NOT_SUPPORT; +} + +int GenericKvDB::TransConflictTypeToRegisterFunctionType(int conflictType, RegisterFuncType &type) const +{ + (void)conflictType; + (void)type; + return -E_NOT_SUPPORT; +} + +int GenericKvDB::CheckDataStatus(const Key &key, const Value &value, bool isDeleted) const +{ + if (key.empty() || key.size() > DBConstant::MAX_KEY_SIZE || + value.size() > DBConstant::MAX_VALUE_SIZE) { + return -E_INVALID_ARGS; + } + return E_OK; +} + +bool GenericKvDB::CheckWritePermission() const +{ + return true; +} + +bool GenericKvDB::IsDataMigrating() const +{ + return false; +} + +void GenericKvDB::SetConnectionFlag(bool isExisted) const +{ + (void)isExisted; + return; +} + +int GenericKvDB::CheckIntegrity() const +{ + return E_OK; +} + +void GenericKvDB::GetStoreDirectory(const KvDBProperties &properties, int dbType, + std::string &storeDir, std::string &storeOnlyDir) const +{ + std::string identifierDir = properties.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + std::string dataDir = properties.GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string subDir = KvDBProperties::GetStoreSubDirectory(dbType); + + std::string storeOnlyIdentifier = GetStoreIdOnlyIdentifier(properties); + storeOnlyDir = dataDir + "/" + storeOnlyIdentifier + "/" + subDir + "/"; + storeDir = dataDir + "/" + identifierDir + "/" + subDir + "/"; +} + +std::string GenericKvDB::GetStoreIdOnlyIdentifier(const KvDBProperties &properties) const +{ + std::string storeId = properties.GetStringProp(KvDBProperties::STORE_ID, ""); + std::string hashStoreId = DBCommon::TransferHashString(storeId); + std::string hashStoreDir = DBCommon::TransferStringToHex(hashStoreId); + return hashStoreDir; +} + +std::string GenericKvDB::GetStorePath() const +{ + return properties_.GetStringProp(KvDBProperties::DATA_DIR, ""); +} + +void GenericKvDB::Dump(int fd) +{ +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/generic_kvdb.h b/mock/distributeddb/storage/src/generic_kvdb.h new file mode 100644 index 00000000..13218efc --- /dev/null +++ b/mock/distributeddb/storage/src/generic_kvdb.h @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 GENERIC_KVDB_H +#define GENERIC_KVDB_H + +#include +#include +#include +#include + +#include "store_types.h" +#include "version.h" +#include "ikvdb.h" +#include "generic_kvdb_connection.h" +#include "performance_analysis.h" +#include "kvdb_conflict_entry.h" +#include "db_types.h" + +namespace DistributedDB { +class KvDBCommitNotifyFilterAbleData; + +struct ImportFileInfo { + std::string backupDir; // the directory of the current database backup + std::string unpackedDir; // the directory of the unpacked import file + std::string currentDir; // the directory of the current database + std::string curValidFile; // the file imply that the current directory is valid + std::string backValidFile; // the file imply that the backup directory is valid +}; + +enum RegisterFuncType { + OBSERVER_SINGLE_VERSION_NS_PUT_EVENT = 0, + OBSERVER_SINGLE_VERSION_NS_SYNC_EVENT, + OBSERVER_SINGLE_VERSION_NS_LOCAL_EVENT, + OBSERVER_SINGLE_VERSION_NS_CONFLICT_EVENT, + OBSERVER_MULTI_VERSION_NS_COMMIT_EVENT, + CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ONLY, + CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ORIG, + CONFLICT_SINGLE_VERSION_NS_NATIVE_ALL, + REGISTER_FUNC_TYPE_MAX +}; + +class GenericKvDB : public IKvDB { +public: + GenericKvDB(); + ~GenericKvDB() override; + + DISABLE_COPY_ASSIGN_MOVE(GenericKvDB); + + // Get properties of this database. + const KvDBProperties &GetMyProperties() const override; + + // Create a db connection. + IKvDBConnection *GetDBConnection(int &errCode) final; + + // Called when all connections of this database closed. + void OnClose(const std::function ¬ifier) final; + + // Publish event when a commit action happened. + virtual void CommitNotify(int notifyEvent, KvDBCommitNotifyFilterAbleData *data); + + // Invoked automatically when connection count is zero + virtual void Close() = 0; + + virtual int TryToDisableConnection(OperatePerm perm); + + virtual void ReEnableConnection(OperatePerm perm); + + virtual int Rekey(const CipherPassword &passwd) = 0; + + // Empty passwords represent non-encrypted files. + // Export existing database files to a specified database file in the specified directory. + virtual int Export(const std::string &filePath, const CipherPassword &passwd) = 0; + + // Import the existing database files to the specified database file in the specified directory. + virtual int Import(const std::string &filePath, const CipherPassword &passwd) = 0; + + // Release a db connection. + void ReleaseDBConnection(GenericKvDBConnection *connection); + + // Register an event listener. + NotificationChain::Listener *RegisterEventListener(EventType type, + const NotificationChain::Listener::OnEvent &onEvent, + const NotificationChain::Listener::OnFinalize &onFinalize, int &errCode); + + // Get event notify counter. + uint64_t GetEventNotifyCounter() const; + + void OpenPerformanceAnalysis() override; + + void ClosePerformanceAnalysis() override; + + void WakeUpSyncer() override {}; + + void EnableAutonomicUpgrade() override {}; + + void SetCorruptHandler(const DatabaseCorruptHandler &handler) override; + + int RegisterFunction(RegisterFuncType type); + + int UnregisterFunction(RegisterFuncType type); + + uint32_t GetRegisterFunctionCount(RegisterFuncType type) const; + + virtual int TransObserverTypeToRegisterFunctionType(int observerType, RegisterFuncType &type) const; + + virtual int TransConflictTypeToRegisterFunctionType(int conflictType, RegisterFuncType &type) const; + + virtual int CheckDataStatus(const Key &key, const Value &value, bool isDeleted) const; + + virtual bool CheckWritePermission() const; + + virtual bool IsDataMigrating() const; + + virtual void SetConnectionFlag(bool isExisted) const; + + int CheckIntegrity() const override; + + std::string GetStorePath() const override; + + void Dump(int fd) override; + +protected: + // Create a connection object, no DB ref increased. + virtual GenericKvDBConnection *NewConnection(int &errCode) = 0; + + // Delete a connection object. + virtual void DelConnection(GenericKvDBConnection *connection); + + // Called when a new connection created. + void IncreaseConnectionCounter(); + + // Called when a connection released. + void DecreaseConnectionCounter(); + + // Register a new notification event type. + int RegisterNotificationEventType(int eventType); + + // Unregister a notification event type. + void UnRegisterNotificationEventType(int eventType); + + // Access 'properties_' for derived class. + const KvDBProperties &MyProp() const; + KvDBProperties &MyProp(); + + static int GetWorkDir(const KvDBProperties &kvDBProp, std::string &workDir); + + void CorruptNotify() const; + + std::string GetStoreIdOnlyIdentifier(const KvDBProperties &properties) const; + + void GetStoreDirectory(const KvDBProperties &properties, int dbType, + std::string &storeDir, std::string &storeOnlyDir) const; + + PerformanceAnalysis *performance_; + DatabaseCorruptHandler corruptHandler_; + DeviceID devId_; + +private: + // Do commit notify in task pool. + void CommitNotifyAsync(int notifyEvent, KvDBCommitNotifyFilterAbleData *data); + + void CorruptNotifyAsync() const; + + // Get the ID of this kvdb. + std::string GetStoreId() const; + + DECLARE_OBJECT_TAG(GenericKvDB); + + // Databasse event notify counter. + std::atomic eventNotifyCounter_; + + // Fields for tracking the connection count and invoking callbacks. + std::atomic connectionCount_; + std::vector> closeNotifiers_; + NotificationChain *notificationChain_; + KvDBProperties properties_; + std::mutex connectMutex_; + mutable std::mutex corruptMutex_; + OperatePerm operatePerm_; + mutable std::mutex regFuncCountMutex_; + std::vector registerFunctionCount_; +}; +} // namespace DistributedDB + +#endif // GENERIC_KVDB_H diff --git a/mock/distributeddb/storage/src/generic_kvdb_connection.cpp b/mock/distributeddb/storage/src/generic_kvdb_connection.cpp new file mode 100644 index 00000000..464d9850 --- /dev/null +++ b/mock/distributeddb/storage/src/generic_kvdb_connection.cpp @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "generic_kvdb_connection.h" + +#include + +#include "log_print.h" +#include "db_constant.h" +#include "db_errno.h" +#include "generic_kvdb.h" +#include "kvdb_observer_handle.h" +#include "kvdb_commit_notify_filterable_data.h" + +namespace DistributedDB { +GenericKvDBConnection::GenericKvDBConnection(GenericKvDB *kvDB) + : kvDB_(kvDB), + isExclusive_(false), + isSafeDeleted_(false) +{ +} + +GenericKvDBConnection::~GenericKvDBConnection() +{ + if (!isSafeDeleted_) { + LOGF("The connection is deleted directly by user."); + } + + for (auto &observer : observerList_) { + delete observer; + observer = nullptr; + } +} + +int GenericKvDBConnection::RegisterObserverForOneType(int type, const Key &key, const KvDBObserverAction &action, + NotificationChain::Listener *&listener) +{ + if (kvDB_ == nullptr) { + return -E_INVALID_CONNECTION; + } + RegisterFuncType funcType = REGISTER_FUNC_TYPE_MAX; + int errCode = kvDB_->TransObserverTypeToRegisterFunctionType(type, funcType); + if (errCode != E_OK) { + return errCode; + } + errCode = kvDB_->RegisterFunction(funcType); + if (errCode != E_OK) { + return errCode; + } + listener = RegisterSpecialListener(type, key, action, false, errCode); + if (listener == nullptr) { + (void)(kvDB_->UnregisterFunction(funcType)); + return errCode; + } + return E_OK; +} + +KvDBObserverHandle *GenericKvDBConnection::RegisterObserver(unsigned mode, + const Key &key, const KvDBObserverAction &action, int &errCode) +{ + if (!action || key.size() > DBConstant::MAX_KEY_SIZE) { + errCode = -E_INVALID_ARGS; + return nullptr; + } + std::list eventTypes; + errCode = GetEventType(mode, eventTypes); + if (errCode != E_OK) { + return nullptr; + } + + std::lock_guard lockGuard(observerListLock_); + if (observerList_.size() >= MAX_OBSERVER_COUNT) { + errCode = -E_MAX_LIMITS; + LOGE("The number of observers has been larger than 'MAX_OBSERVER_COUNT'!"); + return nullptr; + } + if (isExclusive_.load()) { + errCode = -E_BUSY; + return nullptr; + } + auto observerHandle = new (std::nothrow) KvDBObserverHandle(mode); + if (observerHandle == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + + std::list listenerList; + for (const auto &type : eventTypes) { + NotificationChain::Listener *listenerObj = nullptr; + // Register function count in db is also protected by observer list lock. + errCode = RegisterObserverForOneType(type, key, action, listenerObj); + if (errCode != E_OK) { + for (auto &listener : listenerList) { + listener->Drop(); + } + LOGE("Register observer failed, register listener failed, err:'%d'.", errCode); + delete observerHandle; + observerHandle = nullptr; + return nullptr; + } + listenerList.push_back(listenerObj); + } + + for (auto &listener : listenerList) { + observerHandle->InsertListener(listener); + } + observerList_.push_back(observerHandle); + errCode = E_OK; + return observerHandle; +} + +int GenericKvDBConnection::UnRegisterObserver(const KvDBObserverHandle *observerHandle) +{ + if (observerHandle == nullptr) { + return -E_INVALID_ARGS; + } + + if (kvDB_ == nullptr) { + return -E_INVALID_CONNECTION; + } + + std::list eventTypes; + int errCode = GetEventType(observerHandle->GetObserverMode(), eventTypes); + if (errCode != E_OK) { + return errCode; + } + + { + std::lock_guard lockGuard(observerListLock_); + auto observerIter = std::find(observerList_.begin(), observerList_.end(), observerHandle); + if (observerIter == observerList_.end()) { + LOGE("Unregister observer failed, no such entry."); + return -E_NO_SUCH_ENTRY; + } + observerList_.erase(observerIter); + // Register function count in db is also protected by observer list lock. + RegisterFuncType funcType = REGISTER_FUNC_TYPE_MAX; + for (auto type : eventTypes) { + errCode = kvDB_->TransObserverTypeToRegisterFunctionType(type, funcType); + if (errCode != E_OK) { + LOGE("Get register function type failed, err:'%d'.", errCode); + continue; + } + errCode = kvDB_->UnregisterFunction(funcType); + if (errCode != E_OK) { + LOGE("Unregister function failed, err:'%d'.", errCode); + continue; + } + } + } + + delete observerHandle; + observerHandle = nullptr; + return E_OK; +} + +int GenericKvDBConnection::SetConflictNotifier(int conflictType, const KvDBConflictAction &action) +{ + (void)conflictType; + (void)action; + return -E_NOT_SUPPORT; +} + +int GenericKvDBConnection::Close() +{ + if (kvDB_ == nullptr) { + return -E_INVALID_CONNECTION; + } + + if (isExclusive_.load()) { + return -E_BUSY; + } + if (kvDB_->IsDataMigrating()) { + return -E_BUSY; + } + + int errCode = PreClose(); + if (errCode != E_OK) { + LOGE("Close connection failed, err:'%d'.", errCode); + return errCode; + } + kvDB_->ReleaseDBConnection(this); + return E_OK; +} + +std::string GenericKvDBConnection::GetIdentifier() const +{ + if (kvDB_ == nullptr) { + return ""; + } + return kvDB_->GetMyProperties().GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); +} + +int GenericKvDBConnection::Pragma(int cmd, void *parameter) +{ + (void)cmd; + (void)parameter; + return -E_NOT_SUPPORT; +} + +int GenericKvDBConnection::PreClose() +{ + return E_OK; +} + +void GenericKvDBConnection::SetSafeDeleted() +{ + isSafeDeleted_ = true; +} + +int GenericKvDBConnection::GetEntries(const IOption &option, const Key &keyPrefix, std::vector &entries) const +{ + (void)option; + (void)keyPrefix; + (void)entries; + return -E_NOT_SUPPORT; +} + +int GenericKvDBConnection::GetEntries(const IOption &option, const Query &query, std::vector &entries) const +{ + (void)option; + (void)query; + (void)entries; + return -E_NOT_SUPPORT; +} + +int GenericKvDBConnection::GetResultSet(const IOption &option, const Key &keyPrefix, IKvDBResultSet *&resultSet) const +{ + (void)option; + (void)keyPrefix; + (void)resultSet; + return -E_NOT_SUPPORT; +} + +int GenericKvDBConnection::GetResultSet(const IOption &option, const Query &query, IKvDBResultSet *&resultSet) const +{ + (void)option; + (void)query; + (void)resultSet; + return -E_NOT_SUPPORT; +} + +int GenericKvDBConnection::GetCount(const IOption &option, const Query &query, int &count) const +{ + (void)option; + (void)query; + (void)count; + return -E_NOT_SUPPORT; +} + +void GenericKvDBConnection::ReleaseResultSet(IKvDBResultSet *&resultSet) +{ + (void)resultSet; + return; +} + +int GenericKvDBConnection::RegisterLifeCycleCallback(const DatabaseLifeCycleNotifier ¬ifier) +{ + (void)notifier; + return -E_NOT_SUPPORT; +} + +int GenericKvDBConnection::GetSecurityOption(int &securityLabel, int &securityFlag) const +{ + if (kvDB_ == nullptr) { + return -E_INVALID_CONNECTION; + } + securityLabel = kvDB_->GetMyProperties().GetIntProp(KvDBProperties::SECURITY_LABEL, 0); + securityFlag = kvDB_->GetMyProperties().GetIntProp(KvDBProperties::SECURITY_FLAG, 0); + return E_OK; +} + +NotificationChain::Listener *GenericKvDBConnection::RegisterSpecialListener(int type, + const Key &key, const KvDBObserverAction &action, bool conflict, int &errCode) +{ + if (!action) { + errCode = -E_INVALID_ARGS; + return nullptr; + } + + if (kvDB_ == nullptr) { + errCode = -E_INVALID_CONNECTION; + return nullptr; + } + + uint64_t notifyBarrier = kvDB_->GetEventNotifyCounter(); + return kvDB_->RegisterEventListener(static_cast(type), + [key, action, conflict, notifyBarrier](void *ptr) { + if (ptr == nullptr) { + return; + } + KvDBCommitNotifyFilterAbleData *data = static_cast(ptr); + if (data->GetNotifyID() <= notifyBarrier) { + return; + } + data->SetFilterKey(key); + if (conflict) { + if (!data->IsConflictedDataEmpty()) { + action(*data); + } + } else { + if (!data->IsChangedDataEmpty()) { + action(*data); + } + } + }, nullptr, errCode); +} + +int GenericKvDBConnection::PreCheckExclusiveStatus() +{ + std::lock_guard lockGuard(observerListLock_); + if (observerList_.empty()) { + isExclusive_.store(true); + return E_OK; + } + return -E_BUSY; +} + +void GenericKvDBConnection::ResetExclusiveStatus() +{ + isExclusive_.store(false); +} + +int GenericKvDBConnection::GetEventType(unsigned mode, std::list &eventTypes) const +{ + if (kvDB_ == nullptr) { + return -E_INVALID_CONNECTION; + } + + return TranslateObserverModeToEventTypes(mode, eventTypes); +} + +int GenericKvDBConnection::CheckIntegrity() const +{ + return E_OK; +} +} \ No newline at end of file diff --git a/mock/distributeddb/storage/src/generic_kvdb_connection.h b/mock/distributeddb/storage/src/generic_kvdb_connection.h new file mode 100644 index 00000000..e5e2378e --- /dev/null +++ b/mock/distributeddb/storage/src/generic_kvdb_connection.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 GENERIC_KV_DB_CONNECTION_H +#define GENERIC_KV_DB_CONNECTION_H + +#include +#include +#include + +#include "ikvdb_connection.h" +#include "notification_chain.h" + +namespace DistributedDB { +class GenericKvDB; + +class GenericKvDBConnection : public IKvDBConnection { +public: + explicit GenericKvDBConnection(GenericKvDB *kvDB); + ~GenericKvDBConnection() override; + + DISABLE_COPY_ASSIGN_MOVE(GenericKvDBConnection); + + // Register observer. + KvDBObserverHandle *RegisterObserver(unsigned mode, const Key &key, + const KvDBObserverAction &action, int &errCode) override; + + // Unregister observer. + int UnRegisterObserver(const KvDBObserverHandle *observerHandle) override; + + // Register a conflict notifier. + int SetConflictNotifier(int conflictType, const KvDBConflictAction &action) override; + + // Close and release the connection. + int Close() final; + + std::string GetIdentifier() const override; + + // Pragma interface. + int Pragma(int cmd, void *parameter) override; + + // Parse event types(from observer mode). + virtual int TranslateObserverModeToEventTypes(unsigned mode, std::list &eventTypes) const = 0; + + // Set it to 'safe' state to delete the connection + void SetSafeDeleted(); + + int GetEntries(const IOption &option, const Key &keyPrefix, std::vector &entries) const override; + + int GetEntries(const IOption &option, const Query &query, std::vector &entries) const override; + + int GetResultSet(const IOption &option, const Key &keyPrefix, IKvDBResultSet *&resultSet) const override; + + int GetResultSet(const IOption &option, const Query &query, IKvDBResultSet *&resultSet) const override; + + int GetCount(const IOption &option, const Query &query, int &count) const override; + + void ReleaseResultSet(IKvDBResultSet *&resultSet) override; + + int RegisterLifeCycleCallback(const DatabaseLifeCycleNotifier ¬ifier) override; + + int GetSecurityOption(int &securityLabel, int &securityFlag) const override; + + int CheckIntegrity() const override; +protected: + // Get the stashed 'KvDB_ pointer' without ref. + template + DerivedDBType *GetDB() const + { + return static_cast(kvDB_); + } + + // Register an event listener with observer action data. + NotificationChain::Listener *RegisterSpecialListener(int type, const Key &key, + const KvDBObserverAction &action, bool conflict, int &errCode); + + virtual int PreCheckExclusiveStatus(); + + void ResetExclusiveStatus(); + + // Called in Close(), overriding of Close() is forbidden. + virtual int PreClose(); + + GenericKvDB *kvDB_; + std::atomic isExclusive_; + +private: + int GetEventType(unsigned mode, std::list &eventTypes) const; + + int RegisterObserverForOneType(int type, const Key &key, const KvDBObserverAction &action, + NotificationChain::Listener *&listener); + + // Soft limit of a connection observer count. + static constexpr int MAX_OBSERVER_COUNT = 8; + + bool isSafeDeleted_; + std::mutex observerListLock_; + std::list observerList_; +}; +} + +#endif // GENERIC_KV_DB_CONNECTION_H diff --git a/mock/distributeddb/storage/src/generic_single_ver_kv_entry.cpp b/mock/distributeddb/storage/src/generic_single_ver_kv_entry.cpp new file mode 100644 index 00000000..c0963616 --- /dev/null +++ b/mock/distributeddb/storage/src/generic_single_ver_kv_entry.cpp @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "generic_single_ver_kv_entry.h" + +#include +#include "data_compression.h" +#include "db_errno.h" +#include "parcel.h" +#include "version.h" + +namespace DistributedDB { +GenericSingleVerKvEntry::GenericSingleVerKvEntry() +{ +} + +GenericSingleVerKvEntry::~GenericSingleVerKvEntry() +{ +} + +std::string GenericSingleVerKvEntry::GetOrigDevice() const +{ + return dataItem_.origDev; +} + +void GenericSingleVerKvEntry::SetOrigDevice(const std::string &dev) +{ + dataItem_.origDev = dev; +} + +Timestamp GenericSingleVerKvEntry::GetTimestamp() const +{ + return dataItem_.timestamp; +} + +void GenericSingleVerKvEntry::SetTimestamp(Timestamp time) +{ + dataItem_.timestamp = time; +} + +Timestamp GenericSingleVerKvEntry::GetWriteTimestamp() const +{ + return dataItem_.writeTimestamp; +} + +void GenericSingleVerKvEntry::SetWriteTimestamp(Timestamp time) +{ + dataItem_.writeTimestamp = time; +} + +void GenericSingleVerKvEntry::SetEntryData(DataItem &&dataItem) +{ + dataItem_ = dataItem; +} + +void GenericSingleVerKvEntry::GetKey(Key &key) const +{ + key = dataItem_.key; +} + +void GenericSingleVerKvEntry::GetHashKey(Key &key) const +{ + key = dataItem_.hashKey; +} + +const Key &GenericSingleVerKvEntry::GetKey() const +{ + return dataItem_.key; +} + +void GenericSingleVerKvEntry::GetValue(Value &value) const +{ + value = dataItem_.value; +} + +const Value &GenericSingleVerKvEntry::GetValue() const +{ + return dataItem_.value; +} + +uint64_t GenericSingleVerKvEntry::GetFlag() const +{ + return dataItem_.flag; +} + +void GenericSingleVerKvEntry::SetKey(const Key &key) +{ + dataItem_.key = key; +} + +void GenericSingleVerKvEntry::SetValue(const Value &value) +{ + dataItem_.value = value; +} + +void GenericSingleVerKvEntry::SetHashKey(const Key &hashKey) +{ + dataItem_.hashKey = hashKey; +} + +// this func should do compatible +int GenericSingleVerKvEntry::SerializeData(Parcel &parcel, uint32_t targetVersion) +{ + uint64_t len = 0; + int errCode = parcel.WriteUInt32(targetVersion); + if (errCode != E_OK) { + return errCode; + } + errCode = AdaptToVersion(OperType::SERIALIZE, targetVersion, parcel, len); + if (errCode != E_OK) { + return errCode; + } + return errCode; +} + +int GenericSingleVerKvEntry::SerializeDatas(const std::vector &kvEntries, Parcel &parcel, + uint32_t targetVersion) +{ + uint32_t size = kvEntries.size(); + int errCode = parcel.WriteUInt32(size); + if (errCode != E_OK) { + LOGE("[SerializeDatas] write entries size failed, errCode=%d.", errCode); + return errCode; + } + parcel.EightByteAlign(); + for (const auto &kvEntry : kvEntries) { + if (kvEntry == nullptr) { + continue; + } + errCode = kvEntry->SerializeData(parcel, targetVersion); + if (errCode != E_OK) { + LOGE("[SerializeDatas] write kvEntry failed, errCode=%d.", errCode); + return errCode; + } + } + return errCode; +} + +// this func should do compatible +uint32_t GenericSingleVerKvEntry::CalculateLen(uint32_t targetVersion) +{ + uint64_t len = 0; + int errCode = AdaptToVersion(OperType::CAL_LEN, targetVersion, len); + if ((len > INT32_MAX) || (errCode != E_OK)) { + return 0; + } + return len; +} + +uint32_t GenericSingleVerKvEntry::CalculateLens(const std::vector &kvEntries, + uint32_t targetVersion) +{ + uint64_t len = 0; + len += Parcel::GetUInt32Len(); + len = BYTE_8_ALIGN(len); + for (const auto &kvEntry : kvEntries) { + if (kvEntry == nullptr) { + continue; + } + len += kvEntry->CalculateLen(targetVersion); + if (len > INT32_MAX) { + return 0; + } + } + return len; +} + +// this func should do compatible +int GenericSingleVerKvEntry::DeSerializeData(Parcel &parcel) +{ + uint32_t version = VERSION_INVALID; + uint64_t len = parcel.ReadUInt32(version); + if (parcel.IsError()) { + return 0; + } + int errCode = AdaptToVersion(OperType::DESERIALIZE, version, parcel, len); + if (errCode != E_OK) { + len = 0; + } + return len; +} + +int GenericSingleVerKvEntry::DeSerializeDatas(std::vector &kvEntries, Parcel &parcel) +{ + uint64_t len = 0; + uint32_t size = 0; + len += parcel.ReadUInt32(size); + parcel.EightByteAlign(); + len = BYTE_8_ALIGN(len); + for (uint32_t i = 0; i < size; i++) { + auto kvEntry = new (std::nothrow) GenericSingleVerKvEntry(); + if (kvEntry == nullptr) { + LOGE("Create kvEntry failed."); + len = 0; + goto END; + } + len += kvEntry->DeSerializeData(parcel); + kvEntries.push_back(kvEntry); + if (len > INT32_MAX) { + len = 0; + goto END; + } + } +END: + if (len == 0) { + for (auto &kvEntry : kvEntries) { + delete kvEntry; + kvEntry = nullptr; + } + } + return len; +} + +int GenericSingleVerKvEntry::AdaptToVersion(OperType operType, uint32_t targetVersion, Parcel &parcel, + uint64_t &datalen) +{ + if (targetVersion < SOFTWARE_VERSION_EARLIEST || targetVersion > SOFTWARE_VERSION_CURRENT) { + return -E_VERSION_NOT_SUPPORT; + } + int errCode = E_OK; + switch (operType) { + case OperType::SERIALIZE: + errCode = SerializeDataByVersion(targetVersion, parcel); + break; + case OperType::DESERIALIZE: + errCode = DeSerializeByVersion(targetVersion, parcel, datalen); + break; + default: + LOGE("Unknown upgrade serialize oper!"); + return -E_UPGRADE_FAILED; + } + return errCode; +} + +int GenericSingleVerKvEntry::AdaptToVersion(OperType operType, uint32_t targetVersion, uint64_t &datalen) +{ + if (targetVersion < SOFTWARE_VERSION_EARLIEST || targetVersion > SOFTWARE_VERSION_CURRENT) { + return -E_VERSION_NOT_SUPPORT; + } + + if (operType == OperType::CAL_LEN) { + return CalLenByVersion(targetVersion, datalen); + } else { + LOGE("Unknown upgrade serialize oper!"); + return -E_UPGRADE_FAILED; + } +} + +int GenericSingleVerKvEntry::SerializeDataByFirstVersion(Parcel &parcel) const +{ + int errCode = parcel.WriteVectorChar(dataItem_.key); + if (errCode != E_OK) { + return errCode; + } + errCode = parcel.WriteVectorChar(dataItem_.value); + if (errCode != E_OK) { + return errCode; + } + errCode = parcel.WriteUInt64(dataItem_.timestamp); + if (errCode != E_OK) { + return errCode; + } + errCode = parcel.WriteUInt64(dataItem_.flag); + if (errCode != E_OK) { + return errCode; + } + + return parcel.WriteString(dataItem_.origDev); +} + +int GenericSingleVerKvEntry::SerializeDataByLaterVersion(Parcel &parcel, uint32_t targetVersion) const +{ + Timestamp writeTimestamp = dataItem_.writeTimestamp; + if (writeTimestamp == 0) { + writeTimestamp = dataItem_.timestamp; + } + int errCode = parcel.WriteUInt64(writeTimestamp); + if (errCode != E_OK) { + return errCode; + } + if (targetVersion >= SOFTWARE_VERSION_RELEASE_6_0) { + errCode = parcel.WriteVector(dataItem_.hashKey); + } + return errCode; +} + +int GenericSingleVerKvEntry::SerializeDataByVersion(uint32_t targetVersion, Parcel &parcel) const +{ + int errCode = SerializeDataByFirstVersion(parcel); + if (targetVersion == SOFTWARE_VERSION_EARLIEST || errCode != E_OK) { + return errCode; + } + return SerializeDataByLaterVersion(parcel, targetVersion); +} + +void GenericSingleVerKvEntry::CalLenByFirstVersion(uint64_t &len) const +{ + len += Parcel::GetUInt32Len(); + len += Parcel::GetVectorCharLen(dataItem_.key); + len += Parcel::GetVectorCharLen(dataItem_.value); + len += Parcel::GetUInt64Len(); + len += Parcel::GetUInt64Len(); + len += Parcel::GetStringLen(dataItem_.origDev); +} + +void GenericSingleVerKvEntry::CalLenByLaterVersion(uint64_t &len, uint32_t targetVersion) const +{ + len += Parcel::GetUInt64Len(); + if (targetVersion >= SOFTWARE_VERSION_RELEASE_6_0) { + len += Parcel::GetVectorLen(dataItem_.hashKey); + } +} + +int GenericSingleVerKvEntry::CalLenByVersion(uint32_t targetVersion, uint64_t &len) const +{ + CalLenByFirstVersion(len); + if (targetVersion == SOFTWARE_VERSION_EARLIEST) { + return E_OK; + } + CalLenByLaterVersion(len, targetVersion); + return E_OK; +} + +void GenericSingleVerKvEntry::DeSerializeByFirstVersion(uint64_t &len, Parcel &parcel) +{ + len += parcel.ReadVectorChar(dataItem_.key); + len += parcel.ReadVectorChar(dataItem_.value); + len += parcel.ReadUInt64(dataItem_.timestamp); + len += parcel.ReadUInt64(dataItem_.flag); + len += parcel.ReadString(dataItem_.origDev); + dataItem_.writeTimestamp = dataItem_.timestamp; +} + +void GenericSingleVerKvEntry::DeSerializeByLaterVersion(uint64_t &len, Parcel &parcel, uint32_t targetVersion) +{ + len += parcel.ReadUInt64(dataItem_.writeTimestamp); + if (targetVersion >= SOFTWARE_VERSION_RELEASE_6_0) { + len += parcel.ReadVector(dataItem_.hashKey); + } +} + +int GenericSingleVerKvEntry::DeSerializeByVersion(uint32_t targetVersion, Parcel &parcel, uint64_t &len) +{ + DeSerializeByFirstVersion(len, parcel); + if (targetVersion == SOFTWARE_VERSION_EARLIEST) { + return E_OK; + } + DeSerializeByLaterVersion(len, parcel, targetVersion); + return E_OK; +} + +uint32_t GenericSingleVerKvEntry::CalculateCompressedLens(const std::vector &compressedData) +{ + // No compressed data in sync. + if (compressedData.empty()) { + return 0; + } + + // Calculate compressed data length. + uint64_t len = 0; + len += Parcel::GetUInt32Len(); // srcLen. + len += Parcel::GetUInt32Len(); // compression algorithm type. + len += Parcel::GetVectorLen(compressedData); // compressed data. + return (len > INT32_MAX) ? 0 : len; +} + +int GenericSingleVerKvEntry::Compress(const std::vector &kvEntries, std::vector &destData, + const CompressInfo &compressInfo) +{ + // Calculate length. + auto srcLen = CalculateLens(kvEntries, compressInfo.targetVersion); + if (srcLen == 0) { + LOGE("Over limit size, cannot compress."); + return -E_INVALID_ARGS; + } + + // Serialize data. + std::vector srcData(srcLen, 0); + Parcel parcel(srcData.data(), srcData.size()); + int errCode = SerializeDatas(kvEntries, parcel, compressInfo.targetVersion); + if (errCode != E_OK) { + return errCode; + } + + // Compress data. + auto inst = DataCompression::GetInstance(compressInfo.compressAlgo); + if (inst == nullptr) { + return -E_INVALID_COMPRESS_ALGO; + } + return inst->Compress(srcData, destData); +} + +int GenericSingleVerKvEntry::Uncompress(const std::vector &srcData, std::vector &kvEntries, + uint32_t destLen, CompressAlgorithm algo) +{ + // Uncompress data. + std::vector destData(destLen, 0); + auto inst = DataCompression::GetInstance(algo); + if (inst == nullptr) { + return -E_INVALID_COMPRESS_ALGO; + } + int errCode = inst->Uncompress(srcData, destData, destLen); + if (errCode != E_OK) { + return errCode; + } + + // Deserialize data. + Parcel parcel(destData.data(), destData.size()); + if (DeSerializeDatas(kvEntries, parcel) == 0) { + return -E_PARSE_FAIL; + } + return E_OK; +} + +int GenericSingleVerKvEntry::SerializeCompressedDatas(const std::vector &kvEntries, + const std::vector &compressedEntries, Parcel &parcel, uint32_t targetVersion, CompressAlgorithm algo) +{ + uint32_t srcLen = CalculateLens(kvEntries, targetVersion); + (void)parcel.WriteUInt32(static_cast(algo)); + (void)parcel.WriteUInt32(srcLen); + (void)parcel.WriteVector(compressedEntries); + return parcel.IsError() ? -E_PARSE_FAIL : E_OK; +} + +int GenericSingleVerKvEntry::DeSerializeCompressedDatas(std::vector &kvEntries, Parcel &parcel) +{ + // Get compression algo type. + uint32_t algoType = 0; + (void)parcel.ReadUInt32(algoType); + CompressAlgorithm compressAlgo = CompressAlgorithm::NONE; + int errCode = DataCompression::TransferCompressionAlgo(algoType, compressAlgo); + if (errCode != E_OK) { + return errCode; + } + + // Get buffer length. + uint32_t destLen = 0; + (void)parcel.ReadUInt32(destLen); + + // Get compressed data. + std::vector srcData; + (void)parcel.ReadVector(srcData); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + + // Uncompress data. + return GenericSingleVerKvEntry::Uncompress(srcData, kvEntries, destLen, compressAlgo); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/generic_single_ver_kv_entry.h b/mock/distributeddb/storage/src/generic_single_ver_kv_entry.h new file mode 100644 index 00000000..8fa82b00 --- /dev/null +++ b/mock/distributeddb/storage/src/generic_single_ver_kv_entry.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 GENERIC_SINGLE_VER_KV_ENTRY_H +#define GENERIC_SINGLE_VER_KV_ENTRY_H + +#include +#include +#include + +#include "db_types.h" +#include "single_ver_kv_entry.h" + +namespace DistributedDB { +struct CompressInfo { + CompressAlgorithm compressAlgo; + uint32_t targetVersion; +}; + +class GenericSingleVerKvEntry : public SingleVerKvEntry { +public: + GenericSingleVerKvEntry(); + ~GenericSingleVerKvEntry() override; + DISABLE_COPY_ASSIGN_MOVE(GenericSingleVerKvEntry); + + std::string GetOrigDevice() const override; + + void SetOrigDevice(const std::string &dev) override; + + Timestamp GetTimestamp() const override; + + void SetTimestamp(Timestamp time) override; + + Timestamp GetWriteTimestamp() const override; + + void SetWriteTimestamp(Timestamp time) override; + + void GetKey(Key &key) const; + const Key &GetKey() const override; + + void GetHashKey(Key &key) const; + + void GetValue(Value &value) const; + const Value &GetValue() const override; + + uint64_t GetFlag() const override; + + void SetEntryData(DataItem &&dataItem); + + int SerializeData(Parcel &parcel, uint32_t targetVersion) override; + + int DeSerializeData(Parcel &parcel) override; + + uint32_t CalculateLen(uint32_t targetVersion) override; + + static int SerializeDatas(const std::vector &kvEntries, Parcel &parcel, uint32_t targetVersion); + + static int DeSerializeDatas(std::vector &kvEntries, Parcel &parcel); + + void SetKey(const Key &key) override; + void SetValue(const Value &value) override; + void SetHashKey(const Key &hashKey) override; + + static uint32_t CalculateLens(const std::vector &kvEntries, uint32_t targetVersion); + static uint32_t CalculateCompressedLens(const std::vector &compressedData); + static int Compress(const std::vector &kvEntries, std::vector &destData, + const CompressInfo &compressInfo); + static int Uncompress(const std::vector &srcData, std::vector &kvEntries, + uint32_t destLen, CompressAlgorithm algo); + static int SerializeCompressedDatas(const std::vector &kvEntries, + const std::vector &compressedEntries, Parcel &parcel, uint32_t targetVersion, CompressAlgorithm algo); + static int DeSerializeCompressedDatas(std::vector &kvEntries, Parcel &parcel); + +private: + enum class OperType { + SERIALIZE, + DESERIALIZE, + CAL_LEN, + }; + int AdaptToVersion(OperType operType, uint32_t targetVersion, Parcel &parcel, uint64_t &datalen); + int AdaptToVersion(OperType operType, uint32_t targetVersion, uint64_t &datalen); + + int SerializeDataByVersion(uint32_t targetVersion, Parcel &parcel) const; + int SerializeDataByFirstVersion(Parcel &parcel) const; + int SerializeDataByLaterVersion(Parcel &parcel, uint32_t targetVersion) const; + + int CalLenByVersion(uint32_t targetVersion, uint64_t &len) const; + void CalLenByFirstVersion(uint64_t &len) const; + void CalLenByLaterVersion(uint64_t &len, uint32_t targetVersion) const; + + int DeSerializeByVersion(uint32_t targetVersion, Parcel &parcel, uint64_t &len); + void DeSerializeByFirstVersion(uint64_t &len, Parcel &parcel); + void DeSerializeByLaterVersion(uint64_t &len, Parcel &parcel, uint32_t targetVersion); + + DataItem dataItem_; +}; +} // namespace DistributedDB + +#endif // GENERIC_SINGLE_VER_KV_ENTRY_H diff --git a/mock/distributeddb/storage/src/iconnection.cpp b/mock/distributeddb/storage/src/iconnection.cpp new file mode 100644 index 00000000..620a9a31 --- /dev/null +++ b/mock/distributeddb/storage/src/iconnection.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "iconnection.h" + +#include "runtime_context.h" + +namespace DistributedDB { +IConnection::IConnection() + : connectionId_(0) +{ +} + +uint64_t IConnection::GetConnectionId() +{ + if (connectionId_ != 0) { + return connectionId_; + } + std::lock_guard autoLock(connectionIdLock_); + // check again here, may be generated after get lock + if (connectionId_ == 0) { + connectionId_ = static_cast(RuntimeContext::GetInstance()->GenerateSessionId()); + } + return connectionId_; +} +} \ No newline at end of file diff --git a/mock/distributeddb/storage/src/ikvdb_commit.h b/mock/distributeddb/storage/src/ikvdb_commit.h new file mode 100644 index 00000000..75dfe2a4 --- /dev/null +++ b/mock/distributeddb/storage/src/ikvdb_commit.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 IKVDB_COMMIT_H +#define IKVDB_COMMIT_H + +#include "db_types.h" +#include "multi_ver_def.h" + +namespace DistributedDB { +class IKvDBCommit { +public: + virtual ~IKvDBCommit() {}; + virtual Version GetCommitVersion() const = 0; + virtual void SetCommitVersion(const Version &versionInfo) = 0; + virtual CommitID GetCommitId() const = 0; + virtual void SetCommitId(const CommitID &id) = 0; + virtual CommitID GetLeftParentId() const = 0; + virtual void SetLeftParentId(const CommitID &id) = 0; + virtual CommitID GetRightParentId() const = 0; + virtual void SetRightParentId(const CommitID &id) = 0; + virtual Timestamp GetTimestamp() const = 0; + virtual void SetTimestamp(Timestamp timestamp) = 0; + virtual bool GetLocalFlag() const = 0; + virtual void SetLocalFlag(bool localFlag) = 0; + virtual DeviceID GetDeviceInfo() const = 0; + virtual void SetDeviceInfo(const DeviceID &deviceInfo) = 0; +}; +} + +#endif diff --git a/mock/distributeddb/storage/src/ikvdb_commit_storage.h b/mock/distributeddb/storage/src/ikvdb_commit_storage.h new file mode 100644 index 00000000..c6e7aeef --- /dev/null +++ b/mock/distributeddb/storage/src/ikvdb_commit_storage.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_KV_DB_COMMIT_STORAGE_H +#define I_KV_DB_COMMIT_STORAGE_H + +#include +#include +#include +#include + +#include "db_types.h" +#include "ikvdb_commit.h" +#include "multi_ver_def.h" + +namespace DistributedDB { +class IKvDBCommitStorage { +public: + struct Property final { + std::string path; + std::string identifierName; + bool isNeedCreate = true; + CipherType cipherType = CipherType::AES_256_GCM; + CipherPassword passwd; + }; + + virtual ~IKvDBCommitStorage() {}; + virtual int CheckVersion(const Property &property, bool &isDbExist) const = 0; + virtual int Open(const Property &property) = 0; + virtual void Close() = 0; + virtual int Remove(const Property &property) = 0; + virtual IKvDBCommit *AllocCommit(int &errCode) const = 0; + virtual IKvDBCommit *GetCommit(const CommitID &commitId, int &errCode) const = 0; + virtual int AddCommit(const IKvDBCommit &commitEntry, bool isHeader) = 0; + virtual int RemoveCommit(const CommitID &commitId) = 0; + virtual void ReleaseCommit(const IKvDBCommit *commit) const = 0; + virtual int SetHeader(const CommitID &commitId) = 0; + virtual CommitID GetHeader(int &errCode) const = 0; + virtual bool CommitExist(const CommitID &commitId, int &errCode) const = 0; + virtual int GetLatestCommits(std::map &latestCommits) const = 0; + virtual int GetCommitTree(const std::map &latestCommits, + std::list &commits) const = 0; + virtual Version GetMaxCommitVersion(int &errCode) const = 0; + virtual int BackupCurrentDatabase(const Property &property, const std::string &dir) = 0; + virtual int ImportDatabase(const Property &property, const std::string &dir, const CipherPassword &passwd) = 0; + virtual int StartVacuum() = 0; + virtual int CancelVacuum() = 0; + virtual int FinishlVacuum() = 0; + virtual int GetAllCommitsInTree(std::list &commits) const = 0; +}; +} // namespace DistributedDB + +#endif // I_KV_DB_COMMIT_STORAGE_H diff --git a/mock/distributeddb/storage/src/ikvdb_factory.cpp b/mock/distributeddb/storage/src/ikvdb_factory.cpp new file mode 100644 index 00000000..cfa42739 --- /dev/null +++ b/mock/distributeddb/storage/src/ikvdb_factory.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "ikvdb_factory.h" + +namespace DistributedDB { +IKvDBFactory *IKvDBFactory::factory_ = nullptr; +std::mutex IKvDBFactory::instanceLock_; + +// Get current factory object. +IKvDBFactory *IKvDBFactory::GetCurrent() +{ + std::lock_guard lockGuard(IKvDBFactory::instanceLock_); + return IKvDBFactory::factory_; +} + +// Set the factory object to 'current' +void IKvDBFactory::Register(IKvDBFactory *factory) +{ + std::lock_guard lockGuard(IKvDBFactory::instanceLock_); + IKvDBFactory::factory_ = factory; +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/storage/src/ikvdb_raw_cursor.h b/mock/distributeddb/storage/src/ikvdb_raw_cursor.h new file mode 100644 index 00000000..b306bf3e --- /dev/null +++ b/mock/distributeddb/storage/src/ikvdb_raw_cursor.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_KV_DB_RAW_CURSOR_H +#define I_KV_DB_RAW_CURSOR_H + +#include "macro_utils.h" +#include "db_types.h" + +namespace DistributedDB { +class IKvDBRawCursor { +public: + IKvDBRawCursor() = default; + virtual ~IKvDBRawCursor() {} + + DISABLE_COPY_ASSIGN_MOVE(IKvDBRawCursor); + + // Open the raw cursor. + virtual int Open() = 0; + + // Close the raw cursor before invoking operator delete. + virtual void Close() = 0; + + // Reload all data. + virtual int Reload() = 0; + + // Get total entries count. + virtual int GetCount() const = 0; + + // Get next entry, return errCode if it fails. + virtual int GetNext(Entry &entry, bool isCopy) const = 0; +}; +} // namespace DistributedDB + +#endif // I_KV_DB_RAW_CURSOR_H diff --git a/mock/distributeddb/storage/src/irelational_store.h b/mock/distributeddb/storage/src/irelational_store.h new file mode 100644 index 00000000..5e3c1e6a --- /dev/null +++ b/mock/distributeddb/storage/src/irelational_store.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_RELATIONAL_STORE_H +#define I_RELATIONAL_STORE_H +#ifdef RELATIONAL_STORE + +#include +#include + +#include "ref_object.h" +#include "relationaldb_properties.h" +#include "relational_store_connection.h" + +namespace DistributedDB { +class IRelationalStore : public virtual RefObject { +public: + IRelationalStore() = default; + ~IRelationalStore() override = default; + DISABLE_COPY_ASSIGN_MOVE(IRelationalStore); + + // Open the database. + virtual int Open(const RelationalDBProperties &properties) = 0; + + virtual void WakeUpSyncer() = 0; + + // Create a db connection. + virtual RelationalStoreConnection *GetDBConnection(int &errCode) = 0; + + virtual std::string GetStorePath() const = 0; + + virtual RelationalDBProperties GetProperties() const = 0; + + virtual void Dump(int fd) = 0; +}; +} // namespace DistributedDB + +#endif +#endif // I_RELATIONAL_STORE_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/kvdb_commit_notify_filterable_data.cpp b/mock/distributeddb/storage/src/kvdb_commit_notify_filterable_data.cpp new file mode 100644 index 00000000..3007d856 --- /dev/null +++ b/mock/distributeddb/storage/src/kvdb_commit_notify_filterable_data.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kvdb_commit_notify_filterable_data.h" +#include "db_errno.h" + +namespace DistributedDB { +KvDBCommitNotifyFilterAbleData::KvDBCommitNotifyFilterAbleData() + : genericKvDB_(nullptr), + notifyID_(0) +{} + +KvDBCommitNotifyFilterAbleData::~KvDBCommitNotifyFilterAbleData() +{ + if (genericKvDB_ != nullptr) { + genericKvDB_->DecObjRef(genericKvDB_); + genericKvDB_ = nullptr; + } +} + +const std::list KvDBCommitNotifyFilterAbleData::GetInsertedEntries(int &errCode) const +{ + std::list entries; + errCode = E_OK; + return entries; +} + +const std::list KvDBCommitNotifyFilterAbleData::GetUpdatedEntries(int &errCode) const +{ + std::list entries; + errCode = E_OK; + return entries; +} + +const std::list KvDBCommitNotifyFilterAbleData::GetDeletedEntries(int &errCode) const +{ + std::list entries; + errCode = E_OK; + return entries; +} + +const std::list KvDBCommitNotifyFilterAbleData::GetCommitConflicts(int &errCode) const +{ + std::list entries; + errCode = E_OK; + return entries; +} + +bool KvDBCommitNotifyFilterAbleData::IsCleared() const +{ + return false; +} + +bool KvDBCommitNotifyFilterAbleData::IsChangedDataEmpty() const +{ + return true; +} + +bool KvDBCommitNotifyFilterAbleData::IsConflictedDataEmpty() const +{ + return true; +} + +void KvDBCommitNotifyFilterAbleData::SetFilterKey(const Key &key) +{ + (void)key; + return; +} + +void KvDBCommitNotifyFilterAbleData::SetMyDb(GenericKvDB *db, uint64_t notifyID) +{ + if (genericKvDB_ == db) { + notifyID_ = notifyID; + return; + } + if (genericKvDB_ != nullptr) { + genericKvDB_->DecObjRef(genericKvDB_); + } + genericKvDB_ = db; + if (genericKvDB_ != nullptr) { + genericKvDB_->IncObjRef(genericKvDB_); + } + notifyID_ = notifyID; +} + +uint64_t KvDBCommitNotifyFilterAbleData::GetNotifyID() const +{ + return notifyID_; +} + +DEFINE_OBJECT_TAG_FACILITIES(KvDBCommitNotifyFilterAbleData) +} diff --git a/mock/distributeddb/storage/src/kvdb_commit_notify_filterable_data.h b/mock/distributeddb/storage/src/kvdb_commit_notify_filterable_data.h new file mode 100644 index 00000000..4db1c8a8 --- /dev/null +++ b/mock/distributeddb/storage/src/kvdb_commit_notify_filterable_data.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KVDB_COMMIT_NOTIFY_FILTERABLE_DATA_H +#define KVDB_COMMIT_NOTIFY_FILTERABLE_DATA_H + +#include "generic_kvdb.h" +#include "kvdb_conflict_entry.h" +#include "kvdb_commit_notify_data.h" + +namespace DistributedDB { +class KvDBCommitNotifyFilterAbleData : public KvDBCommitNotifyData { +public: + KvDBCommitNotifyFilterAbleData(); + ~KvDBCommitNotifyFilterAbleData() override; + DISABLE_COPY_ASSIGN_MOVE(KvDBCommitNotifyFilterAbleData); + + // get the new inserted entries. + const std::list GetInsertedEntries(int &errCode) const override; + + // get the new updated entries. + const std::list GetUpdatedEntries(int &errCode) const override; + + // get the new deleted entries. + const std::list GetDeletedEntries(int &errCode) const override; + + // get all conflict entries when commit. + const std::list GetCommitConflicts(int &errCode) const override; + + // database is cleared by user in the commit. + bool IsCleared() const override; + + // test if the data is empty or not after filtered. + bool IsChangedDataEmpty() const override; + + // test if the conflict data is empty or not. + bool IsConflictedDataEmpty() const override; + + // set the filter key. + virtual void SetFilterKey(const Key &key); + + // set and ref the db that we belong to. + void SetMyDb(GenericKvDB *db, uint64_t notifyID); + + // get ID of this notify. + uint64_t GetNotifyID() const; + +private: + DECLARE_OBJECT_TAG(KvDBCommitNotifyFilterAbleData); + + GenericKvDB *genericKvDB_; + uint64_t notifyID_; +}; +} // namespace DistributedDB + +#endif // KVDB_COMMIT_NOTIFY_FILTERABLE_DATA_H diff --git a/mock/distributeddb/storage/src/kvdb_manager.cpp b/mock/distributeddb/storage/src/kvdb_manager.cpp new file mode 100644 index 00000000..761f56ff --- /dev/null +++ b/mock/distributeddb/storage/src/kvdb_manager.cpp @@ -0,0 +1,972 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kvdb_manager.h" +#include "log_print.h" +#include "db_common.h" +#include "runtime_context.h" +#include "schema_object.h" +#include "default_factory.h" +#include "generic_kvdb.h" +#include "db_constant.h" +#include "res_finalizer.h" + +namespace DistributedDB { +const std::string KvDBManager::PROCESS_LABEL_CONNECTOR = "-"; +std::atomic KvDBManager::instance_{nullptr}; +std::mutex KvDBManager::kvDBLock_; +std::mutex KvDBManager::instanceLock_; +std::map KvDBManager::locks_; + +namespace { + DefaultFactory g_defaultFactory; + + int CreateDataBaseInstance(const KvDBProperties &property, IKvDB *&kvDB) + { + IKvDBFactory *factory = IKvDBFactory::GetCurrent(); + if (factory == nullptr) { + return -E_OUT_OF_MEMORY; + } + int errCode = E_OK; + int databaseType = property.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + if (databaseType == KvDBProperties::LOCAL_TYPE) { + kvDB = factory->CreateKvDb(LOCAL_KVDB, errCode); + if (kvDB != nullptr) { + kvDB->EnableAutonomicUpgrade(); + } + } else if (databaseType == KvDBProperties::SINGLE_VER_TYPE) { + kvDB = factory->CreateKvDb(SINGER_VER_KVDB, errCode); + } else { + kvDB = factory->CreateKvDb(MULTI_VER_KVDB, errCode); + } + return errCode; + } + + int CreateRemoveStateFlagFile(const KvDBProperties &properties) + { + std::string dataDir = properties.GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string identifier = properties.GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + std::string identifierName = DBCommon::TransferStringToHex(identifier); + std::string storeDir = dataDir + "/" + identifierName + DBConstant::DELETE_KVSTORE_REMOVING; + if (OS::CheckPathExistence(storeDir)) { + return E_OK; + } + // create the pre flag file. + int errCode = OS::CreateFileByFileName(storeDir); + if (errCode != E_OK) { + LOGE("Create remove state flag file failed:%d.", errCode); + } + return errCode; + } +} + +int KvDBManager::CheckRemoveStateAndRetry(const KvDBProperties &property) +{ + std::string dataDir = property.GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string identifier = property.GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + std::string identifierName = DBCommon::TransferStringToHex(identifier); + std::string storeDir = dataDir + "/" + identifierName + DBConstant::DELETE_KVSTORE_REMOVING; + + if (OS::CheckPathExistence(storeDir)) { + KvDBManager::ExecuteRemoveDatabase(property); + } + // Re-detection deleted had been finish + if (OS::CheckPathExistence(storeDir)) { + LOGD("Deletekvstore unfinished, can not create new same identifier kvstore!"); + return -E_REMOVE_FILE; + } + return E_OK; +} + +int KvDBManager::ExecuteRemoveDatabase(const KvDBProperties &properties) +{ + int errCode = CheckDatabaseFileStatus(properties); + if (errCode != E_OK) { + return errCode; + } + IKvDBFactory *factory = IKvDBFactory::GetCurrent(); + if (factory == nullptr) { + return -E_INVALID_DB; + } + + errCode = CreateRemoveStateFlagFile(properties); + if (errCode != E_OK) { + LOGE("create ctrl file failed:%d.", errCode); + return errCode; + } + + errCode = -E_NOT_FOUND; + for (KvDBType kvDbType = LOCAL_KVDB; kvDbType < UNSUPPORT_KVDB_TYPE; kvDbType = (KvDBType)(kvDbType + 1)) { + int innerErrCode = E_OK; + IKvDB *kvdb = factory->CreateKvDb(kvDbType, innerErrCode); + if (innerErrCode != E_OK) { + return innerErrCode; + } + innerErrCode = kvdb->RemoveKvDB(properties); + RefObject::DecObjRef(kvdb); + if (innerErrCode != -E_NOT_FOUND) { + if (innerErrCode != E_OK) { + return innerErrCode; + } + errCode = E_OK; + } + } + + if (errCode == -E_NOT_FOUND) { + LOGE("DataBase file Not exist! return NOT_FOUND."); + } + + RemoveDBDirectory(properties); + return errCode; +} + +void KvDBManager::RemoveDBDirectory(const KvDBProperties &properties) +{ + std::string dataDir = properties.GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string identifier = properties.GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + std::string identifierName = DBCommon::TransferStringToHex(identifier); + std::string storeDir = dataDir + "/" + identifierName; + std::string removingFlag = dataDir + "/" + identifierName + DBConstant::DELETE_KVSTORE_REMOVING; + (void)OS::RemoveDBDirectory(storeDir); + + std::string storeId = properties.GetStringProp(KvDBProperties::STORE_ID, ""); + identifier = DBCommon::TransferHashString(storeId); + identifierName = DBCommon::TransferStringToHex(identifier); + storeDir = dataDir + "/" + identifierName; + (void)OS::RemoveDBDirectory(storeDir); + + (void)OS::RemoveFile(removingFlag); +} + +// Used to open a kvdb with the given property +IKvDB *KvDBManager::OpenDatabase(const KvDBProperties &property, int &errCode) +{ + KvDBManager *manager = GetInstance(); + if (manager == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + return manager->GetDataBase(property, errCode, true); +} + +void KvDBManager::EnterDBOpenCloseProcess(const std::string &identifier) +{ + std::unique_lock lock(kvDBOpenMutex_); + kvDBOpenCondition_.wait(lock, [this, &identifier]() { + return this->kvDBOpenSet_.count(identifier) == 0; + }); + (void)kvDBOpenSet_.insert(identifier); +} + +void KvDBManager::ExitDBOpenCloseProcess(const std::string &identifier) +{ + std::unique_lock lock(kvDBOpenMutex_); + (void)kvDBOpenSet_.erase(identifier); + kvDBOpenCondition_.notify_all(); +} + +// one time 100ms +// In order to prevent long-term blocking of the process, a retry method is used +// The dimensions of the lock by appid-userid-storeid +int KvDBManager::TryLockDB(const KvDBProperties &kvDBProp, int retryTimes) +{ + std::string dataDir = kvDBProp.GetStringProp(KvDBProperties::DATA_DIR, ""); + bool isMemoryDb = kvDBProp.GetBoolProp(KvDBProperties::MEMORY_MODE, false); + std::string id = KvDBManager::GenerateKvDBIdentifier(kvDBProp); + if (dataDir.back() != '/') { + dataDir += "/"; + } + + if (isMemoryDb) { + LOGI("MemoryDb not need lock!"); + return E_OK; + } + + if (locks_.count(id) != 0) { + LOGI("db has been locked!"); + return E_OK; + } + + std::string hexHashId = DBCommon::TransferStringToHex((id)); + OS::FileHandle handle; + int errCode = OS::OpenFile(dataDir + hexHashId + DBConstant::DB_LOCK_POSTFIX, handle); + if (errCode != E_OK) { + LOGE("Open lock file fail errCode = [%d], errno:%d", errCode, errno); + return errCode; + } + + while (retryTimes-- > 0) { + errCode = OS::FileLock(handle, false); // not block process + if (errCode == E_OK) { + LOGI("[%s]locked!", STR_MASK(DBCommon::TransferStringToHex(KvDBManager::GenerateKvDBIdentifier(kvDBProp)))); + locks_[id] = handle; + return errCode; + } else if (errCode == -E_BUSY) { + LOGD("DB already held by process lock!"); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // wait for 100ms + continue; + } else { + LOGE("Try lock db failed, errCode = [%d] errno:%d", errCode, errno); + OS::CloseFile(handle); + return errCode; + } + } + OS::CloseFile(handle); + return -E_BUSY; +} + +int KvDBManager::UnlockDB(const KvDBProperties &kvDBProp) +{ + bool isMemoryDb = kvDBProp.GetBoolProp(KvDBProperties::MEMORY_MODE, false); + if (isMemoryDb) { + return E_OK; + } + std::string identifierDir = KvDBManager::GenerateKvDBIdentifier(kvDBProp); + if (locks_.count(identifierDir) == 0) { + return E_OK; + } + int errCode = OS::FileUnlock(locks_[identifierDir]); + LOGI("DB unlocked! errCode = [%d]", errCode); + if (errCode != E_OK) { + return errCode; + } + locks_.erase(identifierDir); + return E_OK; +} + +// Used to open a kvdb with the given property +IKvDBConnection *KvDBManager::GetDatabaseConnection(const KvDBProperties &properties, int &errCode, + bool isNeedIfOpened) +{ + auto manager = GetInstance(); + if (manager == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + IKvDBConnection *connection = nullptr; + std::string identifier = properties.GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + LOGD("Begin to get [%s] database connection.", STR_MASK(DBCommon::TransferStringToHex(identifier))); + manager->EnterDBOpenCloseProcess(identifier); + + IKvDB *kvDB = manager->GetDataBase(properties, errCode, isNeedIfOpened); + if (kvDB == nullptr) { + if (isNeedIfOpened) { + LOGE("Failed to open the db:%d", errCode); + } + } else { + bool isMemoryDb = properties.GetBoolProp(KvDBProperties::MEMORY_MODE, false); + std::string canonicalDir = properties.GetStringProp(KvDBProperties::DATA_DIR, ""); + if (!isMemoryDb && (canonicalDir.empty() || canonicalDir != kvDB->GetStorePath())) { + LOGE("Failed to check store path, the input path does not match with cached store."); + errCode = -E_INVALID_ARGS; + } else { + connection = kvDB->GetDBConnection(errCode); + if (connection == nullptr) { // not kill kvdb, Other operations like import may be used concurrently + LOGE("Failed to get the db connect for delegate:%d", errCode); + } + } + RefObject::DecObjRef(kvDB); // restore the reference increased by the cache. + kvDB = nullptr; + } + + manager->ExitDBOpenCloseProcess(identifier); + if (errCode == -E_INVALID_PASSWD_OR_CORRUPTED_DB) { + std::string appId = properties.GetStringProp(KvDBProperties::APP_ID, ""); + std::string userId = properties.GetStringProp(KvDBProperties::USER_ID, ""); + std::string storeId = properties.GetStringProp(KvDBProperties::STORE_ID, ""); + manager->DataBaseCorruptNotify(appId, userId, storeId); + LOGE("Database [%s] is corrupted:%d", STR_MASK(DBCommon::TransferStringToHex(identifier)), errCode); + } + + return connection; +} + +int KvDBManager::ReleaseDatabaseConnection(IKvDBConnection *connection) +{ + if (connection == nullptr) { + return -E_INVALID_DB; + } + + std::string identifier = connection->GetIdentifier(); + auto manager = GetInstance(); + if (manager == nullptr) { + return -E_OUT_OF_MEMORY; + } + manager->EnterDBOpenCloseProcess(identifier); + int errCode = connection->Close(); + manager->ExitDBOpenCloseProcess(identifier); + + if (errCode != E_OK) { + LOGE("[KvDBManager] Release db connection:%d", errCode); + } + LOGI("[Connection] db[%s] conn Close", STR_MASK(DBCommon::TransferStringToHex(identifier))); + return errCode; +} + +IKvDB *KvDBManager::CreateDataBase(const KvDBProperties &property, int &errCode) +{ + IKvDB *kvDB = OpenNewDatabase(property, errCode); + if (kvDB == nullptr) { + LOGE("Failed to open the new database."); + if (errCode == -E_INVALID_PASSWD_OR_CORRUPTED_DB && + property.GetBoolProp(KvDBProperties::RM_CORRUPTED_DB, false)) { + LOGI("Remove the corrupted database while open"); + ExecuteRemoveDatabase(property); + kvDB = OpenNewDatabase(property, errCode); + } + return kvDB; + } + + if (property.GetBoolProp(KvDBProperties::CHECK_INTEGRITY, false)) { + int integrityStatus = kvDB->CheckIntegrity(); + if (integrityStatus == -E_INVALID_PASSWD_OR_CORRUPTED_DB) { + RemoveKvDBFromCache(kvDB); + RefObject::KillAndDecObjRef(kvDB); + kvDB = nullptr; + errCode = -E_INVALID_PASSWD_OR_CORRUPTED_DB; + if (property.GetBoolProp(KvDBProperties::RM_CORRUPTED_DB, false)) { + LOGI("Remove the corrupted database for the integrity check"); + ExecuteRemoveDatabase(property); + kvDB = OpenNewDatabase(property, errCode); + } + } + } + return kvDB; +} + +IKvDB *KvDBManager::GetDataBase(const KvDBProperties &property, int &errCode, bool isNeedIfOpened) +{ + bool isMemoryDb = property.GetBoolProp(KvDBProperties::MEMORY_MODE, false); + bool isCreateNecessary = property.GetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + IKvDB *kvDB = FindAndGetKvDBFromCache(property, errCode); + if (kvDB != nullptr) { + if (!isNeedIfOpened) { + LOGI("[KvDBManager] Database has already been opened."); + RefObject::DecObjRef(kvDB); + errCode = -E_ALREADY_OPENED; + kvDB = nullptr; + } + return kvDB; + } + if (isMemoryDb && !isCreateNecessary) { + LOGI("IsCreateNecessary is false, Not need create database"); + return nullptr; + } + if (errCode != -E_NOT_FOUND) { + return nullptr; + } + + // Taking into account the compatibility of version delivery, + // temporarily use isNeedIntegrityCheck this field to avoid multi-process concurrency + bool isNeedIntegrityCheck = property.GetBoolProp(KvDBProperties::CHECK_INTEGRITY, false); + if (isNeedIntegrityCheck) { + LOGI("db need lock, need check integrity is [%d]", isNeedIntegrityCheck); + errCode = KvDBManager::TryLockDB(property, 10); // default 10 times retry + if (errCode != E_OK) { + return nullptr; + } + } + + ResFinalizer unlock([&errCode, &property, &kvDB]() { + int err = KvDBManager::UnlockDB(property); + if (err != E_OK) { + LOGE("GetDataBase unlock failed! err [%d] errCode [%d]", err, errCode); + errCode = err; + RefObject::KillAndDecObjRef(kvDB); + kvDB = nullptr; + } + }); + + kvDB = CreateDataBase(property, errCode); + if (errCode != E_OK) { + LOGE("Create data base failed, errCode = [%d]", errCode); + } + return kvDB; +} + +bool KvDBManager::IsOpenMemoryDb(const KvDBProperties &properties, const std::map &cache) const +{ + std::string identifier = GenerateKvDBIdentifier(properties); + auto iter = cache.find(identifier); + if (iter != cache.end()) { + IKvDB *kvDB = iter->second; + if (kvDB != nullptr && kvDB->GetMyProperties().GetBoolProp(KvDBProperties::MEMORY_MODE, false)) { + return true; + } + } + return false; +} + +// used to get kvdb size with the given property. +int KvDBManager::CalculateKvStoreSize(const KvDBProperties &properties, uint64_t &size) +{ + KvDBManager *manager = GetInstance(); + if (manager == nullptr) { + LOGE("Failed to get KvDBManager instance!"); + return -E_OUT_OF_MEMORY; + } + + std::lock_guard lockGuard(kvDBLock_); + if (manager->IsOpenMemoryDb(properties, manager->singleVerNaturalStores_)) { + size = 0; + return E_OK; + } + + IKvDBFactory *factory = IKvDBFactory::GetCurrent(); + if (factory == nullptr) { + return -E_INVALID_DB; + } + + uint64_t totalSize = 0; + for (KvDBType kvDbType = LOCAL_KVDB; kvDbType < UNSUPPORT_KVDB_TYPE; kvDbType = (KvDBType)(kvDbType + 1)) { + int innerErrCode = E_OK; + IKvDB *kvDB = factory->CreateKvDb(kvDbType, innerErrCode); + if (innerErrCode != E_OK) { + return innerErrCode; + } + uint64_t dbSize = 0; + innerErrCode = kvDB->GetKvDBSize(properties, dbSize); + RefObject::DecObjRef(kvDB); + if (innerErrCode != E_OK && innerErrCode != -E_NOT_FOUND) { + return innerErrCode; + } + LOGD("DB type [%u], size[%" PRIu64 "]", static_cast(kvDbType), dbSize); + totalSize = totalSize + dbSize; + } + // This represent Db file size(Unit is byte), It is small than max size(max uint64_t represent 2^64B) + if (totalSize != 0ull) { + size = totalSize; + return E_OK; + } + return -E_NOT_FOUND; +} + +IKvDB *KvDBManager::GetKvDBFromCacheByIdentify(const std::string &identifier, + const std::map &cache) const +{ + auto iter = cache.find(identifier); + if (iter != cache.end()) { + IKvDB *kvDB = iter->second; + if (kvDB == nullptr) { + LOGE("Kvstore cache is nullptr, there may be a logic error"); + return nullptr; + } + return kvDB; + } + return nullptr; +} + +int KvDBManager::CheckDatabaseFileStatus(const KvDBProperties &properties) +{ + KvDBManager *manager = GetInstance(); + if (manager == nullptr) { + LOGE("Failed to get KvDBManager instance!"); + return -E_OUT_OF_MEMORY; + } + + std::string identifier = GenerateKvDBIdentifier(properties); + std::lock_guard lockGuard(kvDBLock_); + IKvDB *kvDB = manager->GetKvDBFromCacheByIdentify(identifier, manager->localKvDBs_); + if (kvDB != nullptr) { + LOGE("The local KvDB is busy!"); + return -E_BUSY; + } + + kvDB = manager->GetKvDBFromCacheByIdentify(identifier, manager->multiVerNaturalStores_); + if (kvDB != nullptr) { + LOGE("The multi ver natural store is busy!"); + return -E_BUSY; + } + + kvDB = manager->GetKvDBFromCacheByIdentify(identifier, manager->singleVerNaturalStores_); + if (kvDB != nullptr) { + LOGE("The single version natural store is busy!"); + return -E_BUSY; + } + return E_OK; +} + +IKvDB *KvDBManager::OpenNewDatabase(const KvDBProperties &property, int &errCode) +{ + errCode = CheckRemoveStateAndRetry(property); + if (errCode != E_OK) { + LOGE("Failed to open IKvDB! Because delete kvstore unfinished err:%d", errCode); + return nullptr; + } + + IKvDB *kvDB = nullptr; + errCode = CreateDataBaseInstance(property, kvDB); + if (errCode != E_OK) { + LOGE("Failed to get IKvDB! err:%d", errCode); + return nullptr; + } + + errCode = kvDB->Open(property); + if (errCode != E_OK) { + LOGE("Failed to open IKvDB! err:%d", errCode); + RefObject::KillAndDecObjRef(kvDB); + kvDB = nullptr; + return nullptr; + } + auto identifier = DBCommon::TransferStringToHex(property.GetStringProp(KvDBProperties::IDENTIFIER_DATA, "")); + auto dbDir = property.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + LOGI("Database identifier:%.6s, dir:%.6s", identifier.c_str(), dbDir.c_str()); + // Register the callback function when the database is closed, triggered when kvdb free + kvDB->OnClose([kvDB, this]() { + LOGI("Remove from the cache"); + this->RemoveKvDBFromCache(kvDB); + }); + + IKvDB *kvDBTmp = SaveKvDBToCache(kvDB); + if (kvDBTmp != kvDB) { + RefObject::KillAndDecObjRef(kvDB); + kvDB = nullptr; + return kvDBTmp; + } + return kvDB; +} + +// used to delete a kvdb with the given property. +// return BUSY if in use +int KvDBManager::RemoveDatabase(const KvDBProperties &properties) +{ + KvDBManager *manager = GetInstance(); + if (manager == nullptr) { + LOGE("Failed to get kvdb manager while removing the db!"); + return -E_OUT_OF_MEMORY; + } + std::string identifier = GenerateKvDBIdentifier(properties); + manager->EnterDBOpenCloseProcess(identifier); + + LOGI("KvDBManager::RemoveDatabase begin try lock the database!"); + std::string lockFile = properties.GetStringProp(KvDBProperties::DATA_DIR, "") + "/" + + DBCommon::TransferStringToHex(identifier) + DBConstant::DB_LOCK_POSTFIX; + int errCode = E_OK; + if (OS::CheckPathExistence(lockFile)) { + errCode = KvDBManager::TryLockDB(properties, 10); // default 10 times retry + if (errCode != E_OK) { + manager->ExitDBOpenCloseProcess(identifier); + return errCode; + } + } + + errCode = ExecuteRemoveDatabase(properties); + if (errCode != E_OK) { + LOGE("[KvDBManager] Remove database failed:%d", errCode); + } + int err = KvDBManager::UnlockDB(properties); // unlock and delete lock file before delete dir + if (err != E_OK) { + LOGE("[KvDBManager][RemoveDatabase] UnlockDB failed:%d, errno:%d", err, errno); + errCode = err; + } + + manager->ExitDBOpenCloseProcess(identifier); + return errCode; +} + +std::string KvDBManager::GenerateKvDBIdentifier(const KvDBProperties &property) +{ + return property.GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); +} + +KvDBManager *KvDBManager::GetInstance() +{ + // For Double-Checked Locking, we need check instance_ twice + if (instance_ == nullptr) { + std::lock_guard lockGuard(instanceLock_); + if (instance_ == nullptr) { + instance_ = new (std::nothrow) KvDBManager(); + if (instance_ == nullptr) { + LOGE("failed to new KvDBManager!"); + return nullptr; + } + } + } + if (IKvDBFactory::GetCurrent() == nullptr) { + IKvDBFactory::Register(&g_defaultFactory); + } + return instance_; +} + +// Save to IKvDB to the global map +IKvDB *KvDBManager::SaveKvDBToCache(IKvDB *kvDB) +{ + if (kvDB == nullptr) { + return nullptr; + } + + { + KvDBProperties properties = kvDB->GetMyProperties(); + std::string identifier = GenerateKvDBIdentifier(properties); + int databaseType = properties.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + std::lock_guard lockGuard(kvDBLock_); + int errCode = E_OK; + if (databaseType == KvDBProperties::LOCAL_TYPE) { + IKvDB *kvDBTmp = FindKvDBFromCache(properties, localKvDBs_, true, errCode); + if (kvDBTmp != nullptr) { + kvDBTmp->IncObjRef(kvDBTmp); + return kvDBTmp; + } + localKvDBs_.insert(std::pair(identifier, kvDB)); + } else if (databaseType == KvDBProperties::MULTI_VER_TYPE) { + IKvDB *kvDBTmp = FindKvDBFromCache(properties, multiVerNaturalStores_, true, errCode); + if (kvDBTmp != nullptr) { + kvDBTmp->IncObjRef(kvDBTmp); + return kvDBTmp; + } + kvDB->WakeUpSyncer(); + multiVerNaturalStores_.insert(std::pair(identifier, kvDB)); + } else { + IKvDB *kvDBTmp = FindKvDBFromCache(properties, singleVerNaturalStores_, true, errCode); + if (kvDBTmp != nullptr) { + kvDBTmp->IncObjRef(kvDBTmp); + return kvDBTmp; + } + kvDB->WakeUpSyncer(); + singleVerNaturalStores_.insert(std::pair(identifier, kvDB)); + } + } + kvDB->SetCorruptHandler([kvDB, this]() { + std::string appId = kvDB->GetMyProperties().GetStringProp(KvDBProperties::APP_ID, ""); + std::string userId = kvDB->GetMyProperties().GetStringProp(KvDBProperties::USER_ID, ""); + std::string storeId = kvDB->GetMyProperties().GetStringProp(KvDBProperties::STORE_ID, ""); + this->DataBaseCorruptNotifyAsync(appId, userId, storeId); + }); + return kvDB; +} + +// Save to IKvDB to the global map +void KvDBManager::RemoveKvDBFromCache(const IKvDB *kvDB) +{ + KvDBProperties properties = kvDB->GetMyProperties(); + std::string identifier = GenerateKvDBIdentifier(properties); + int databaseType = properties.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + std::lock_guard lockGuard(kvDBLock_); + if (databaseType == KvDBProperties::LOCAL_TYPE) { + localKvDBs_.erase(identifier); + } else if (databaseType == KvDBProperties::MULTI_VER_TYPE) { + multiVerNaturalStores_.erase(identifier); + } else { + singleVerNaturalStores_.erase(identifier); + } +} + +// Get IKvDB from the global map +IKvDB *KvDBManager::FindAndGetKvDBFromCache(const KvDBProperties &properties, int &errCode) const +{ + std::lock_guard lockGuard(kvDBLock_); + + IKvDB *kvDB = FindKvDBFromCache(properties, localKvDBs_, true, errCode); + if (kvDB != nullptr) { + kvDB->IncObjRef(kvDB); + return kvDB; + } + if (errCode != -E_NOT_FOUND) { + return nullptr; + } + + kvDB = FindKvDBFromCache(properties, multiVerNaturalStores_, true, errCode); + if (kvDB != nullptr) { + kvDB->IncObjRef(kvDB); + return kvDB; + } + if (errCode != -E_NOT_FOUND) { + return nullptr; + } + + kvDB = FindKvDBFromCache(properties, singleVerNaturalStores_, true, errCode); + if (kvDB != nullptr) { + kvDB->IncObjRef(kvDB); + return kvDB; + } + return nullptr; +} + +IKvDB *KvDBManager::FindKvDBFromCache(const KvDBProperties &properties, const std::map &cache, + bool isNeedCheckPasswd, int &errCode) const +{ + errCode = E_OK; + std::string identifier = GenerateKvDBIdentifier(properties); + auto iter = cache.find(identifier); + if (iter != cache.end()) { + IKvDB *kvDB = iter->second; + if (kvDB == nullptr) { + LOGE("KVSTORE cache is nullptr, there may be a logic error"); + errCode = -E_INTERNAL_ERROR; + return nullptr; + } + int newType = properties.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + int oldType = kvDB->GetMyProperties().GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + if (oldType == newType) { + errCode = CheckKvDBProperties(kvDB, properties, isNeedCheckPasswd); + if (errCode != E_OK) { + return nullptr; + } + return kvDB; + } else { + errCode = -E_INVALID_ARGS; + LOGE("Database [%s] type not matched, type [%d] vs [%d]", + STR_MASK(DBCommon::TransferStringToHex(identifier)), newType, oldType); + return nullptr; + } + } + + errCode = -E_NOT_FOUND; + return nullptr; +} + +int KvDBManager::SetProcessLabel(const std::string &appId, const std::string &userId) +{ + std::string label = appId + PROCESS_LABEL_CONNECTOR + userId; + RuntimeContext::GetInstance()->SetProcessLabel(label); + return E_OK; +} + +void KvDBManager::RestoreSyncableKvStore() +{ + KvDBManager *manager = GetInstance(); + if (manager == nullptr) { + return; + } + + manager->RestoreSyncerOfAllKvStore(); +} + +void KvDBManager::SetDatabaseCorruptionHandler(const KvStoreCorruptionHandler &handler) +{ + KvDBManager *manager = GetInstance(); + if (manager == nullptr) { + return; + } + + manager->SetAllDatabaseCorruptionHander(handler); +} + +void KvDBManager::SetAllDatabaseCorruptionHander(const KvStoreCorruptionHandler &handler) +{ + { + std::lock_guard lock(corruptMutex_); + corruptHandler_ = handler; + } + std::lock_guard lockGuard(kvDBLock_); + SetCorruptHandlerForDatabases(singleVerNaturalStores_); + SetCorruptHandlerForDatabases(localKvDBs_); + SetCorruptHandlerForDatabases(multiVerNaturalStores_); +} + +void KvDBManager::DataBaseCorruptNotify(const std::string &appId, const std::string &userId, const std::string &storeId) +{ + KvStoreCorruptionHandler corruptHandler = nullptr; + { + std::lock_guard lock(corruptMutex_); + corruptHandler = corruptHandler_; + } + + if (corruptHandler != nullptr) { + corruptHandler(appId, userId, storeId); + } +} + +void KvDBManager::DataBaseCorruptNotifyAsync(const std::string &appId, const std::string &userId, + const std::string &storeId) +{ + int errCode = RuntimeContext::GetInstance()->ScheduleTask( + std::bind(&KvDBManager::DataBaseCorruptNotify, this, appId, userId, storeId)); + if (errCode != E_OK) { + LOGE("[KvDBManager][CorruptNotify] ScheduleTask failed, errCode = %d.", errCode); + return; + } +} + +void KvDBManager::SetCorruptHandlerForDatabases(const std::map &dbMaps) +{ + for (const auto &item : dbMaps) { + if (item.second == nullptr) { + continue; + } + + item.second->SetCorruptHandler([item, this]() { + std::string appId = item.second->GetMyProperties().GetStringProp(KvDBProperties::APP_ID, ""); + std::string userId = item.second->GetMyProperties().GetStringProp(KvDBProperties::USER_ID, ""); + std::string storeId = item.second->GetMyProperties().GetStringProp(KvDBProperties::STORE_ID, ""); + this->DataBaseCorruptNotifyAsync(appId, userId, storeId); + }); + } +} + +void KvDBManager::RestoreSyncerOfAllKvStore() +{ + std::lock_guard lockGuard(kvDBLock_); + for (auto &item : singleVerNaturalStores_) { + if (item.second != nullptr) { + item.second->WakeUpSyncer(); + } + } + + for (auto &item : multiVerNaturalStores_) { + if (item.second != nullptr) { + item.second->WakeUpSyncer(); + } + } +} + +bool KvDBManager::CompareSchemaObject(const SchemaObject &newSchema, const SchemaObject &oldSchema) +{ + if (!newSchema.IsSchemaValid() && !oldSchema.IsSchemaValid()) { + return true; + } + if (!newSchema.IsSchemaValid() || !oldSchema.IsSchemaValid()) { + return false; + } + int errCode = oldSchema.CompareAgainstSchemaObject(newSchema); + if (errCode == -E_SCHEMA_EQUAL_EXACTLY) { + return true; + } + return false; +} + +int KvDBManager::CheckSchema(const IKvDB *kvDB, const KvDBProperties &properties) +{ + if (kvDB == nullptr) { + LOGE("input kvdb is nullptr"); + return -E_INVALID_ARGS; + } + SchemaObject inputSchema = properties.GetSchema(); + SchemaObject cacheSchema = kvDB->GetMyProperties().GetSchema(); + bool isFirstOpenReadOnly = + kvDB->GetMyProperties().GetBoolProp(KvDBProperties::FIRST_OPEN_IS_READ_ONLY, false); + if (isFirstOpenReadOnly) { + if (inputSchema.IsSchemaValid()) { + LOGE("schema not matched"); + return -E_SCHEMA_MISMATCH; + } else { + return E_OK; + } + } + if (!CompareSchemaObject(inputSchema, cacheSchema)) { + LOGE("schema not matched"); + return -E_SCHEMA_MISMATCH; + } + return E_OK; +} + +namespace { + bool IsSameCipher(CipherType srcType, CipherType inputType) + { + // At present, the default type is AES-256-GCM. + // So when src is default and input is AES-256-GCM, + // or when src is AES-256-GCM and input is default, + // we think they are the same type. + if (((srcType == CipherType::DEFAULT || srcType == CipherType::AES_256_GCM) && + (inputType == CipherType::DEFAULT || inputType == CipherType::AES_256_GCM)) || + srcType == inputType) { + return true; + } + return false; + } + + bool CheckSecOptions(const KvDBProperties &input, const KvDBProperties &existed) + { + // If any has NO_SET, skip the check and using the existed option. + if (input.GetIntProp(KvDBProperties::SECURITY_LABEL, 0) != 0 && + existed.GetIntProp(KvDBProperties::SECURITY_LABEL, 0) != 0) { + if (existed.GetIntProp(KvDBProperties::SECURITY_LABEL, 0) != + input.GetIntProp(KvDBProperties::SECURITY_LABEL, 0)) { + LOGE("Security label mismatch: existed[%d] vs input[%d]", + existed.GetIntProp(KvDBProperties::SECURITY_LABEL, 0), + input.GetIntProp(KvDBProperties::SECURITY_LABEL, 0)); + return false; + } + if (existed.GetIntProp(KvDBProperties::SECURITY_FLAG, 0) != + input.GetIntProp(KvDBProperties::SECURITY_FLAG, 0)) { + LOGE("Security flag mismatch: existed[%d] vs input[%d]", + existed.GetIntProp(KvDBProperties::SECURITY_FLAG, 0), + input.GetIntProp(KvDBProperties::SECURITY_FLAG, 0)); + return false; + } + } + return true; + } +} + +int KvDBManager::CheckKvDBProperties(const IKvDB *kvDB, const KvDBProperties &properties, + bool isNeedCheckPasswd) const +{ + // if get from cache is not memoryDb, do not support open or create memory DB + bool isMemoryDb = properties.GetBoolProp(KvDBProperties::MEMORY_MODE, false); + if (isMemoryDb != kvDB->GetMyProperties().GetBoolProp(KvDBProperties::MEMORY_MODE, false)) { + LOGE("Already open same id physical DB, so do not support open or create memory DB"); + return -E_INVALID_ARGS; + } + + if (kvDB->GetMyProperties().GetBoolProp(KvDBProperties::CREATE_DIR_BY_STORE_ID_ONLY, false) != + properties.GetBoolProp(KvDBProperties::CREATE_DIR_BY_STORE_ID_ONLY, false)) { + LOGE("Different ways to create dir."); + return -E_INVALID_ARGS; + } + + if (kvDB->GetMyProperties().GetIntProp(KvDBProperties::CONFLICT_RESOLVE_POLICY, 0) != + properties.GetIntProp(KvDBProperties::CONFLICT_RESOLVE_POLICY, 0)) { + LOGE("Different conflict resolve policy."); + return -E_INVALID_ARGS; + } + + if (kvDB->GetMyProperties().GetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, false) != + properties.GetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, false)) { + LOGE("Different dual tuple sync mode"); + return -E_MODE_MISMATCH; + } + + if (!CheckSecOptions(properties, kvDB->GetMyProperties())) { + return -E_INVALID_ARGS; + } + + CipherType cacheType; + CipherType inputType; + CipherPassword cachePasswd; + CipherPassword inputPasswd; + kvDB->GetMyProperties().GetPassword(cacheType, cachePasswd); + properties.GetPassword(inputType, inputPasswd); + if (isNeedCheckPasswd && (cachePasswd != inputPasswd || !IsSameCipher(cacheType, inputType))) { + LOGE("Identification not matched"); + return -E_INVALID_PASSWD_OR_CORRUPTED_DB; + } + + return CheckSchema(kvDB, properties); +} + +// Attention. After call FindKvDB and kvdb is not null, you need to call DecObjRef. +IKvDB* KvDBManager::FindKvDB(const std::string &identifier) const +{ + std::lock_guard lockGuard(kvDBLock_); + auto kvdb = singleVerNaturalStores_.find(identifier); + if (kvdb != singleVerNaturalStores_.end()) { + // Increase ref counter here. + RefObject::IncObjRef(kvdb->second); + return kvdb->second; + } + return nullptr; +} + +void KvDBManager::Dump(int fd) +{ + std::lock_guard lockGuard(kvDBLock_); + for (auto &entry : singleVerNaturalStores_) { + RefObject::IncObjRef(entry.second); + entry.second->Dump(fd); + RefObject::DecObjRef(entry.second); + } +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/kvdb_observer_handle.cpp b/mock/distributeddb/storage/src/kvdb_observer_handle.cpp new file mode 100644 index 00000000..9c089da5 --- /dev/null +++ b/mock/distributeddb/storage/src/kvdb_observer_handle.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kvdb_observer_handle.h" +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +KvDBObserverHandle::KvDBObserverHandle(uint32_t mode) + : mode_(mode) +{} + +KvDBObserverHandle::~KvDBObserverHandle() +{ + for (auto &listener : listeners_) { + int errCode = listener->Drop(true); + if (errCode != E_OK) { + LOGE("Drop listener failed!"); + } + listener = nullptr; + } +} + +void KvDBObserverHandle::InsertListener(NotificationChain::Listener *listener) +{ + listeners_.push_back(listener); + return; +} + +uint32_t KvDBObserverHandle::GetObserverMode() const +{ + return mode_; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/kvdb_observer_handle.h b/mock/distributeddb/storage/src/kvdb_observer_handle.h new file mode 100644 index 00000000..2833d10c --- /dev/null +++ b/mock/distributeddb/storage/src/kvdb_observer_handle.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_DB_OBSERVER_HANDLE_H +#define KV_DB_OBSERVER_HANDLE_H + +#include + +#include "macro_utils.h" +#include "notification_chain.h" + +namespace DistributedDB { +class KvDBObserverHandle { +public: + explicit KvDBObserverHandle(uint32_t mode); + ~KvDBObserverHandle(); + DISABLE_COPY_ASSIGN_MOVE(KvDBObserverHandle); + void InsertListener(NotificationChain::Listener *listener); + uint32_t GetObserverMode() const; +private: + std::list listeners_; + uint32_t mode_; +}; +} // namespace DistributedDB + +#endif // KV_DB_OBSERVER_H diff --git a/mock/distributeddb/storage/src/kvdb_properties.cpp b/mock/distributeddb/storage/src/kvdb_properties.cpp new file mode 100644 index 00000000..c10569d8 --- /dev/null +++ b/mock/distributeddb/storage/src/kvdb_properties.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kvdb_properties.h" + +#include "db_constant.h" + +namespace DistributedDB { +const std::string KvDBProperties::FILE_NAME = "fileName"; +const std::string KvDBProperties::MEMORY_MODE = "memoryMode"; +const std::string KvDBProperties::ENCRYPTED_MODE = "isEncryptedDb"; +const std::string KvDBProperties::FIRST_OPEN_IS_READ_ONLY = "firstOpenIsReadOnly"; +const std::string KvDBProperties::CREATE_DIR_BY_STORE_ID_ONLY = "createDirByStoreIdOnly"; +const std::string KvDBProperties::SECURITY_LABEL = "securityLabel"; +const std::string KvDBProperties::SECURITY_FLAG = "securityFlag"; +const std::string KvDBProperties::CONFLICT_RESOLVE_POLICY = "conflictResolvePolicy"; +const std::string KvDBProperties::CHECK_INTEGRITY = "checkIntegrity"; +const std::string KvDBProperties::RM_CORRUPTED_DB = "rmCorruptedDb"; +const std::string KvDBProperties::COMPRESS_ON_SYNC = "needCompressOnSync"; +const std::string KvDBProperties::COMPRESSION_RATE = "compressionRate"; + +KvDBProperties::KvDBProperties() + : cipherType_(CipherType::AES_256_GCM) +{} + +KvDBProperties::~KvDBProperties() {} + +std::string KvDBProperties::GetStoreSubDirectory(int type) +{ + switch (type) { + case LOCAL_TYPE: + return DBConstant::LOCAL_SUB_DIR; + case MULTI_VER_TYPE: + return DBConstant::MULTI_SUB_DIR; + case SINGLE_VER_TYPE: + return DBConstant::SINGLE_SUB_DIR; + default: + return "unknown"; + } +} + +void KvDBProperties::GetPassword(CipherType &type, CipherPassword &password) const +{ + type = cipherType_; + password = password_; +} + +void KvDBProperties::SetPassword(CipherType type, const CipherPassword &password) +{ + cipherType_ = type; + password_ = password; +} + +bool KvDBProperties::IsSchemaExist() const +{ + return schema_.IsSchemaValid(); +} + +void KvDBProperties::SetSchema(const SchemaObject &schema) +{ + schema_ = schema; +} + +SchemaObject KvDBProperties::GetSchema() const +{ + return schema_; +} + +int KvDBProperties::GetSecLabel() const +{ + return GetIntProp(KvDBProperties::SECURITY_LABEL, 0); +} + +int KvDBProperties::GetSecFlag() const +{ + return GetIntProp(KvDBProperties::SECURITY_FLAG, 0); +} + +const SchemaObject &KvDBProperties::GetSchemaConstRef() const +{ + return schema_; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/kvdb_utils.cpp b/mock/distributeddb/storage/src/kvdb_utils.cpp new file mode 100644 index 00000000..0cedc59e --- /dev/null +++ b/mock/distributeddb/storage/src/kvdb_utils.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kvdb_utils.h" + +#include "platform_specific.h" +#include "log_print.h" +#include "db_constant.h" +#include "db_errno.h" +#include "db_common.h" +#include "sqlite_utils.h" + +namespace DistributedDB { +void KvDBUtils::GetStoreDirectory(std::string &directory, const std::string &identifierName) +{ + if (!directory.empty() && directory.back() != '/') { + directory += "/"; + } + directory += identifierName; + return; +} + +int KvDBUtils::RemoveKvDB(const std::string &dirAll, const std::string &dirStoreOnly, const std::string &dbName) +{ + int errCodeAll = KvDBUtils::RemoveKvDB(dirAll, dbName); + if (errCodeAll != -E_NOT_FOUND && errCodeAll != E_OK) { + return errCodeAll; + } + int errCodeOnlyStore = KvDBUtils::RemoveKvDB(dirStoreOnly, dbName); + if (errCodeOnlyStore != -E_NOT_FOUND && errCodeOnlyStore != E_OK) { + return errCodeOnlyStore; + } + if ((errCodeAll == -E_NOT_FOUND) && (errCodeOnlyStore == -E_NOT_FOUND)) { + return -E_NOT_FOUND; + } + return E_OK; +} + +int KvDBUtils::RemoveKvDB(const std::string &dir, const std::string &dbName) +{ + std::string dbFileName = dir; + GetStoreDirectory(dbFileName, dbName); + dbFileName += DBConstant::SQLITE_DB_EXTENSION; + int errCode = E_OK; + if (OS::CheckPathExistence(dbFileName)) { + errCode = DBCommon::RemoveAllFilesOfDirectory(dir, true); + if (errCode != E_OK) { + LOGE("Failed to delete the db file! errno[%d], errCode[%d]", errno, errCode); + return -E_REMOVE_FILE; + } + } else { + errCode = -E_NOT_FOUND; + LOGD("Db file not existed! errCode[%d]", errCode); + } + return errCode; +} + +int KvDBUtils::GetKvDbSize(const std::string &dirAll, const std::string &dirStoreOnly, + const std::string &dbName, uint64_t &size) +{ + int errCodeAll = SQLiteUtils::GetDbSize(dirAll, dbName, size); + if (errCodeAll != -E_NOT_FOUND && errCodeAll != E_OK) { + return errCodeAll; + } + + int errCodeOnlyStore = SQLiteUtils::GetDbSize(dirStoreOnly, dbName, size); + if (errCodeOnlyStore != -E_NOT_FOUND && errCodeOnlyStore != E_OK) { + return errCodeOnlyStore; + } + if ((errCodeAll == -E_NOT_FOUND) && (errCodeOnlyStore == -E_NOT_FOUND)) { + return -E_NOT_FOUND; + } + return E_OK; +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/storage/src/kvdb_utils.h b/mock/distributeddb/storage/src/kvdb_utils.h new file mode 100644 index 00000000..b55ac8da --- /dev/null +++ b/mock/distributeddb/storage/src/kvdb_utils.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KVDB_UTILLS_H +#define KVDB_UTILLS_H +#include + +#include "store_types.h" +#include "macro_utils.h" + +namespace DistributedDB { +class KvDBUtils final { +public: + DISABLE_COPY_ASSIGN_MOVE(KvDBUtils); + ~KvDBUtils() {} + + static void GetStoreDirectory(std::string &directory, const std::string &identifierName); + + static int GetKvDbSize(const std::string &dirAll, const std::string &dirStoreOnly, + const std::string &dbName, uint64_t &size); + + static int RemoveKvDB(const std::string &dir, const std::string &dbName); + static int RemoveKvDB(const std::string &dirAll, const std::string &dirStoreOnly, const std::string &dbName); +}; +} // namespace DistributedDB +#endif // LOCAL_KVDB_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/kvdb_windowed_result_set.cpp b/mock/distributeddb/storage/src/kvdb_windowed_result_set.cpp new file mode 100644 index 00000000..ca269bf6 --- /dev/null +++ b/mock/distributeddb/storage/src/kvdb_windowed_result_set.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kvdb_windowed_result_set.h" + +#include "db_errno.h" + +namespace DistributedDB { +KvDBWindowedResultSet::KvDBWindowedResultSet() + : window_(nullptr) +{ +} + +KvDBWindowedResultSet::~KvDBWindowedResultSet() +{ + window_ = nullptr; +} + +int KvDBWindowedResultSet::Open(bool isMemDb) +{ + return -E_NOT_SUPPORT; +} + +int KvDBWindowedResultSet::GetCount() const +{ + return 0; +} + +int KvDBWindowedResultSet::GetPosition() const +{ + return -1; // return invalid position +} + +int KvDBWindowedResultSet::MoveTo(int position) const +{ + return -E_NOT_SUPPORT; +} + +int KvDBWindowedResultSet::GetEntry(Entry &entry) const +{ + return -E_NOT_SUPPORT; +} + +void KvDBWindowedResultSet::Close() +{ +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/kvdb_windowed_result_set.h b/mock/distributeddb/storage/src/kvdb_windowed_result_set.h new file mode 100644 index 00000000..2c5fe3b7 --- /dev/null +++ b/mock/distributeddb/storage/src/kvdb_windowed_result_set.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_DB_WINDOWED_RESULT_SET_H +#define KV_DB_WINDOWED_RESULT_SET_H + +#include "ikvdb_result_set.h" +#include "result_entries_window.h" + +namespace DistributedDB { +class KvDBWindowedResultSet : public IKvDBResultSet { +public: + KvDBWindowedResultSet(); + ~KvDBWindowedResultSet() override; + DISABLE_COPY_ASSIGN_MOVE(KvDBWindowedResultSet); + + // Initialize logic + int Open(bool isMemDb) override; + + // Get total entries count. + // >= 0: count, < 0: errCode. + int GetCount() const override; + + // Get current read position. + // >= 0: position, < 0: errCode + int GetPosition() const override; + + // Move the read position to an absolute position value. + int MoveTo(int position) const override; + + // Get the entry of current position. + int GetEntry(Entry &entry) const override; + + // Finalize logic + void Close() override; + +private: + ResultEntriesWindow *window_; +}; +} // namespace DistributedDB + +#endif // KV_DB_WINDOWED_RESULT_SET_H diff --git a/mock/distributeddb/storage/src/local_kvdb.h b/mock/distributeddb/storage/src/local_kvdb.h new file mode 100644 index 00000000..22f8fa5a --- /dev/null +++ b/mock/distributeddb/storage/src/local_kvdb.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 LOCAL_KVDB_H +#define LOCAL_KVDB_H + +#include "generic_kvdb.h" + +namespace DistributedDB { +class LocalKvDB : public GenericKvDB { +public: + LocalKvDB() = default; + ~LocalKvDB() override {} + + DISABLE_COPY_ASSIGN_MOVE(LocalKvDB); +}; +} // namespace DistributedDB + +#endif // LOCAL_KVDB_H diff --git a/mock/distributeddb/storage/src/multiver/generic_multi_ver_kv_entry.cpp b/mock/distributeddb/storage/src/multiver/generic_multi_ver_kv_entry.cpp new file mode 100644 index 00000000..8c859c66 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/generic_multi_ver_kv_entry.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "generic_multi_ver_kv_entry.h" + +#include "multi_ver_value_object.h" +#include "parcel.h" + +namespace DistributedDB { +GenericMultiVerKvEntry::GenericMultiVerKvEntry() + : operFlag_(0), + timestamp_(0), + oriTimestamp_(0) +{} + +GenericMultiVerKvEntry::~GenericMultiVerKvEntry() +{} + +int GenericMultiVerKvEntry::GetSerialData(std::vector &data) const +{ + std::vector valueObjectSerial; + int errCode = valueObject_.GetSerialData(valueObjectSerial); + if (errCode != E_OK) { + return errCode; + } + + size_t totalLength = Parcel::GetVectorCharLen(key_) + Parcel::GetVectorCharLen(valueObjectSerial) + + Parcel::GetUInt64Len() * 3; // 3 means number of 3 uint64_t parameters: operFlag, timestamp and oriTimestamp. + + data.resize(totalLength); + Parcel parcel(data.data(), data.size()); + + errCode = parcel.WriteVectorChar(key_); + if (errCode != E_OK) { + return errCode; + } + + errCode = parcel.WriteVectorChar(valueObjectSerial); + if (errCode != E_OK) { + return errCode; + } + + errCode = parcel.WriteUInt64(operFlag_); + if (errCode != E_OK) { + return errCode; + } + + errCode = parcel.WriteUInt64(timestamp_); + if (errCode != E_OK) { + return errCode; + } + + errCode = parcel.WriteUInt64(oriTimestamp_); + if (errCode != E_OK) { + return errCode; + } + + return E_OK; +} + +int GenericMultiVerKvEntry::GetValueHash(std::vector &valueHashes) const +{ + return valueObject_.GetValueHash(valueHashes); +} + +int GenericMultiVerKvEntry::DeSerialData(const std::vector &data) +{ + Parcel parcel(const_cast(data.data()), data.size()); + parcel.ReadVectorChar(key_); + std::vector objectData; + parcel.ReadVectorChar(objectData); + parcel.ReadUInt64(operFlag_); + parcel.ReadUInt64(timestamp_); + parcel.ReadUInt64(oriTimestamp_); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + return valueObject_.DeSerialData(objectData); +} + +int GenericMultiVerKvEntry::SetKey(const Key &key) +{ + key_ = key; + return E_OK; +} + +int GenericMultiVerKvEntry::GetKey(Key &key) const +{ + key = key_; + return E_OK; +} + +int GenericMultiVerKvEntry::SetValue(const Value &value) +{ + return valueObject_.DeSerialData(value); +} + +int GenericMultiVerKvEntry::GetValue(Value &value)const +{ + return valueObject_.GetSerialData(value); +} + +void GenericMultiVerKvEntry::SetOperFlag(uint64_t flag) +{ + operFlag_ = flag; +} + +void GenericMultiVerKvEntry::GetOperFlag(uint64_t &flag) const +{ + flag = operFlag_; +} + +uint32_t GenericMultiVerKvEntry::GetDataLength() const +{ + return valueObject_.GetDataLength(); +} + +void GenericMultiVerKvEntry::SetDataLength(uint32_t length) +{ + valueObject_.SetDataLength(length); +} + +void GenericMultiVerKvEntry::SetTimestamp(uint64_t timestamp) +{ + timestamp_ = timestamp; +} + +void GenericMultiVerKvEntry::GetTimestamp(uint64_t ×tamp) const +{ + timestamp = timestamp_; +} + +void GenericMultiVerKvEntry::SetOriTimestamp(uint64_t oriTimestamp) +{ + oriTimestamp_ = oriTimestamp; +} + +void GenericMultiVerKvEntry::GetOriTimestamp(uint64_t &oriTimestamp) const +{ + oriTimestamp = oriTimestamp_; +} +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/storage/src/multiver/generic_multi_ver_kv_entry.h b/mock/distributeddb/storage/src/multiver/generic_multi_ver_kv_entry.h new file mode 100644 index 00000000..9fc13a46 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/generic_multi_ver_kv_entry.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 GENERIC_MULTI_VER_KVENTRY_H +#define GENERIC_MULTI_VER_KVENTRY_H + +#ifndef OMIT_MULTI_VER +#include + +#include "macro_utils.h" +#include "multi_ver_kv_entry.h" +#include "multi_ver_value_object.h" + +namespace DistributedDB { +class GenericMultiVerKvEntry : public MultiVerKvEntry { +public: + GenericMultiVerKvEntry(); + ~GenericMultiVerKvEntry() override; + DISABLE_COPY_ASSIGN_MOVE(GenericMultiVerKvEntry); + + int GetSerialData(std::vector &data) const override; + + int GetValueHash(std::vector &valueHashes) const override; + + void GetTimestamp(uint64_t ×tamp) const override; + void SetTimestamp(uint64_t timestamp) override; + + void GetOriTimestamp(uint64_t &oriTimestamp) const; + void SetOriTimestamp(uint64_t oriTimestamp); + + int DeSerialData(const std::vector &data); + + int SetKey(const Key &key); + int GetKey(Key &key) const; + + int SetValue(const Value &value); + int GetValue(Value &value) const; + + void SetOperFlag(uint64_t flag); + void GetOperFlag(uint64_t &flag) const; + + void SetDataLength(uint32_t length); + uint32_t GetDataLength() const; + +private: + std::vector key_; + MultiVerValueObject valueObject_; + uint64_t operFlag_; + uint64_t timestamp_; + uint64_t oriTimestamp_; +}; +} // namespace DistributedDB + +#endif // GENERIC_MULTI_VER_KVENTRY_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/ikvdb_multi_ver_data_storage.h b/mock/distributeddb/storage/src/multiver/ikvdb_multi_ver_data_storage.h new file mode 100644 index 00000000..49e5f598 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/ikvdb_multi_ver_data_storage.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_KV_DB_MULTI_VER_DATA_STORAGE_H +#define I_KV_DB_MULTI_VER_DATA_STORAGE_H + +#ifndef OMIT_MULTI_VER +#include +#include + +#include "db_types.h" +#include "ikvdb_multi_ver_transaction.h" +#include "multi_ver_def.h" +#include "multi_ver_kv_entry.h" + +namespace DistributedDB { +enum class KvDataType { + KV_DATA_LOCAL, + KV_DATA_SYNC_P2P, +}; + +struct UpdateVerTimestamp { + uint64_t timestamp = 0ull; + bool isNeedUpdate = false; +}; + +class IKvDBMultiVerDataStorage { +public: + struct Property final { + std::string path; + std::string identifierName; + bool isNeedCreate = true; + CipherType cipherType = CipherType::AES_256_GCM; + CipherPassword passwd; + }; + + virtual ~IKvDBMultiVerDataStorage() {}; + virtual int Open(const Property &property) = 0; + virtual int StartWrite(KvDataType dataType, IKvDBMultiVerTransaction *&transaction) = 0; + virtual int CommitWritePhaseOne(IKvDBMultiVerTransaction *transaction, + const UpdateVerTimestamp &multiVerTimestamp) = 0; + virtual int RollbackWritePhaseOne(IKvDBMultiVerTransaction *transaction, const Version &versionInfo) = 0; + virtual int RollbackWrite(IKvDBMultiVerTransaction *transaction) = 0; + virtual void CommitWritePhaseTwo(IKvDBMultiVerTransaction *transaction) = 0; + virtual IKvDBMultiVerTransaction *StartRead(KvDataType dataType, const Version &versionInfo, int &errCode) = 0; + virtual void ReleaseTransaction(IKvDBMultiVerTransaction *transaction) = 0; + virtual void Close() = 0; + virtual int CheckVersion(const Property &property, bool &isDbExist) const = 0; + virtual int GetVersion(const Property &property, int &version, bool &isDbExisted) const = 0; + virtual int BackupCurrentDatabase(const Property &property, const std::string &dir) = 0; + virtual int ImportDatabase(const Property &property, const std::string &dir, const CipherPassword &passwd) = 0; +}; +} // namespace DistributedDB + +#endif // I_KV_DB_MULTI_VER_DATA_STORAGE_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/ikvdb_multi_ver_transaction.h b/mock/distributeddb/storage/src/multiver/ikvdb_multi_ver_transaction.h new file mode 100644 index 00000000..3cb433d5 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/ikvdb_multi_ver_transaction.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_KV_DB_MULTI_VER_TRANSACTION_H +#define I_KV_DB_MULTI_VER_TRANSACTION_H + +#ifndef OMIT_MULTI_VER +#include "db_types.h" +#include "multi_ver_def.h" +#include "multi_ver_kv_entry.h" + +namespace DistributedDB { +class IKvDBMultiVerTransaction { +public: + virtual ~IKvDBMultiVerTransaction() {}; + virtual int Put(const Key &key, const Value &value) = 0; + virtual int Delete(const Key &key) = 0; + virtual int Clear() = 0; + virtual int Get(const Key &key, Value &value) const = 0; + virtual int GetEntries(const Key &keyPrefix, std::vector &entries) const = 0; + virtual int PutBatch(const std::vector &entries, bool isLocal, + std::vector &values) = 0; + virtual int GetEntriesByVersion(const Version &versionInfo, std::vector &entries) const = 0; + virtual int GetDiffEntries(const Version &begin, const Version &end, MultiVerDiffData &data) const = 0; + virtual int GetMaxVersion(MultiVerDataType type, Version &maxVersion) const = 0; + virtual int ClearEntriesByVersion(const Version &versionInfo) = 0; + virtual int StartTransaction() = 0; + virtual int RollBackTransaction() = 0; + virtual int CommitTransaction() = 0; + virtual int UpdateTimestampByVersion(const Version &version, Timestamp stamp) const = 0; + virtual bool IsDataChanged() const = 0; // only for write transaction. + virtual bool IsRecordCleared(const Timestamp timestamp) const = 0; + virtual Timestamp GetCurrentMaxTimestamp() const = 0; + virtual void SetVersion(const Version &versionInfo) = 0; + virtual Version GetVersion() const = 0; + virtual int GetEntriesByVersion(Version version, std::list &data) const = 0; + virtual int GetOverwrittenClearTypeEntries(Version clearVersion, + std::list &data) const = 0; + virtual int GetOverwrittenNonClearTypeEntries(Version version, const Key &hashKey, + std::list &data) const = 0; + virtual int DeleteEntriesByHashKey(Version version, const Key &hashKey) = 0; + virtual int GetValueForTrimSlice(const Key &hashKey, const Version vision, Value &value) const = 0; +}; +} // namespace DistributedDB + +#endif // I_KV_DB_MULTI_VER_TRANSACTION_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_commit.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_commit.cpp new file mode 100644 index 00000000..56e4c35e --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_commit.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "log_print.h" +#include "multi_ver_commit.h" + +namespace DistributedDB { +MultiVerCommit::MultiVerCommit() + : versionInfo_(0), + timestamp_(0), + localFlag_(true) +{} + +MultiVerCommit::~MultiVerCommit() +{} + +Version MultiVerCommit::GetCommitVersion() const +{ + return versionInfo_; +} + +void MultiVerCommit::SetCommitVersion(const Version &versionInfo) +{ + versionInfo_ = versionInfo; + return; +} + +CommitID MultiVerCommit::GetCommitId() const +{ + return commitID_; +} + +void MultiVerCommit::SetCommitId(const CommitID &id) +{ + commitID_ = id; + return; +} + +CommitID MultiVerCommit::GetLeftParentId() const +{ + return leftParentID_; +} + +void MultiVerCommit::SetLeftParentId(const CommitID &id) +{ + leftParentID_ = id; + return; +} + +CommitID MultiVerCommit::GetRightParentId() const +{ + return rightParentID_; +} + +void MultiVerCommit::SetRightParentId(const CommitID &id) +{ + rightParentID_ = id; + return; +} + +Timestamp MultiVerCommit::GetTimestamp() const +{ + return timestamp_; +} + +void MultiVerCommit::SetTimestamp(Timestamp timestamp) +{ + timestamp_ = timestamp; + return; +} + +bool MultiVerCommit::GetLocalFlag() const +{ + return localFlag_; +} + +void MultiVerCommit::SetLocalFlag(bool localFlag) +{ + localFlag_ = localFlag; + return; +} + +DeviceID MultiVerCommit::GetDeviceInfo() const +{ + return deviceInfo_; +} + +void MultiVerCommit::SetDeviceInfo(const DeviceID &deviceInfo) +{ + deviceInfo_ = deviceInfo; + return; +} + +bool MultiVerCommit::CheckCommit() const +{ + if (commitID_.size() == 0 || commitID_.size() > MAX_COMMIT_ID_LENGTH || + leftParentID_.size() > MAX_COMMIT_ID_LENGTH || rightParentID_.size() > MAX_COMMIT_ID_LENGTH || + deviceInfo_.size() > MAX_COMMIT_DEV_LENGTH) { + LOGE("Check commit failed! Error length of commit ID."); + return false; + } + // commitId should not equal to the parentId; the left parent should not equal to the right + if (commitID_ == leftParentID_ || commitID_ == rightParentID_ || + (leftParentID_ == rightParentID_ && leftParentID_.size() != 0)) { + LOGE("Check commit failed! Wrong commit ID."); + return false; + } + return true; +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_commit.h b/mock/distributeddb/storage/src/multiver/multi_ver_commit.h new file mode 100644 index 00000000..7ba7c292 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_commit.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_COMMIT_H +#define MULTI_VER_COMMIT_H + +#ifndef OMIT_MULTI_VER +#include "ikvdb_commit.h" + +#include "multi_ver_def.h" +#include "macro_utils.h" + +namespace DistributedDB { +class MultiVerCommit final : public IKvDBCommit { +public: + MultiVerCommit(); + ~MultiVerCommit() override; + + DISABLE_COPY_ASSIGN_MOVE(MultiVerCommit); + + Version GetCommitVersion() const override; + void SetCommitVersion(const Version &versionInfo) override; + + CommitID GetCommitId() const override; + void SetCommitId(const CommitID &id) override; + + CommitID GetLeftParentId() const override; + void SetLeftParentId(const CommitID &id) override; + + CommitID GetRightParentId() const override; + void SetRightParentId(const CommitID &id) override; + + Timestamp GetTimestamp() const override; + void SetTimestamp(Timestamp timestamp) override; + + bool GetLocalFlag() const override; + void SetLocalFlag(bool localFlag) override; + + DeviceID GetDeviceInfo() const override; + void SetDeviceInfo(const DeviceID &deviceInfo) override; + + bool CheckCommit() const; + +private: + static const size_t MAX_VERSION_INFO_LENGTH = 4096; + static const size_t MAX_COMMIT_ID_LENGTH = 128; + static const size_t MAX_COMMIT_DEV_LENGTH = 256; + Version versionInfo_; + CommitID commitID_; + CommitID leftParentID_; + CommitID rightParentID_; + Timestamp timestamp_; + bool localFlag_; + DeviceID deviceInfo_; +}; +} + +#endif +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_kv_entry.h b/mock/distributeddb/storage/src/multiver/multi_ver_kv_entry.h new file mode 100644 index 00000000..f0390500 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_kv_entry.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_KV_ENTRY_H +#define MULTI_VER_KV_ENTRY_H + +#ifndef OMIT_MULTI_VER +#include + +#include "multi_ver_value_object.h" + +namespace DistributedDB { +class MultiVerKvEntry { +public: + virtual ~MultiVerKvEntry() {}; + + virtual int GetSerialData(std::vector &data) const = 0; + + virtual int GetValueHash(std::vector &valueHashes) const = 0; + + virtual void GetTimestamp(uint64_t ×tamp) const = 0; + + virtual void SetTimestamp(uint64_t timestamp) = 0; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_KV_ENTRY_H +#endif diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_kvdata_storage.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_kvdata_storage.cpp new file mode 100644 index 00000000..25857f60 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_kvdata_storage.cpp @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_kvdata_storage.h" + +#include "db_constant.h" +#include "db_errno.h" +#include "log_print.h" +#include "ikvdb_factory.h" +#include "sqlite_local_kvdb.h" +#include "parcel.h" + +namespace DistributedDB { +namespace { + const uint8_t HASH_COUNT_MAGIC = '$'; + const uint32_t EXPECT_ENTRIES_NUM = 2; + struct DatabaseIdentifierCfg { + std::string databaseDir; + std::string identifier; + std::string fileName; + }; +} + +static IKvDB *OpenKvDB(const DatabaseIdentifierCfg &config, CipherType type, const CipherPassword &passwd, int &errCode) +{ + IKvDBFactory *factory = IKvDBFactory::GetCurrent(); + if (factory == nullptr) { + LOGE("Failed to open IKvDB! Get factory failed."); + return nullptr; + } + + IKvDB *kvDB = factory->CreateKvDb(LOCAL_KVDB, errCode); + if (kvDB == nullptr) { + LOGE("Create local kvdb failed:%d", errCode); + return nullptr; + } + + KvDBProperties dbProperties; + dbProperties.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + dbProperties.SetStringProp(KvDBProperties::DATA_DIR, config.databaseDir); + dbProperties.SetStringProp(KvDBProperties::FILE_NAME, config.fileName); + dbProperties.SetStringProp(KvDBProperties::IDENTIFIER_DIR, config.identifier); + dbProperties.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::MULTI_VER_TYPE); + dbProperties.SetPassword(type, passwd); + + errCode = kvDB->Open(dbProperties); + if (errCode != E_OK) { + LOGE("Failed to open IKvDB! err:%d", errCode); + RefObject::KillAndDecObjRef(kvDB); + kvDB = nullptr; + return nullptr; + } + // Need to refactor in the future + int version = ((config.fileName == DBConstant::MULTI_VER_VALUE_STORE) ? + MULTI_VER_VALUESLICE_STORAGE_VERSION_CURRENT : MULTI_VER_METADATA_STORAGE_VERSION_CURRENT); + errCode = static_cast(kvDB)->SetVersion(dbProperties, version); + if (errCode != E_OK) { + LOGE("[KvStorage][OpenDB] SetVersion fail, errCode=%d.", errCode); + RefObject::KillAndDecObjRef(kvDB); + kvDB = nullptr; + return nullptr; + } + + return kvDB; +} + +static int PutData(IKvDBConnection *kvDBConnection, const Key &key, const Value &value) +{ + if (kvDBConnection == nullptr) { + return -E_INVALID_DB; + } + + if (key.empty() || key.size() > DBConstant::MAX_KEY_SIZE || value.size() > DBConstant::MAX_VALUE_SIZE) { + return -E_INVALID_ARGS; + } + + IOption option; + int errCode = kvDBConnection->Put(option, key, value); + if (errCode != E_OK) { + LOGE("put data failed:%d", errCode); + } + + return errCode; +} + +static int DeleteData(IKvDBConnection *kvDBConnection, const Key &key) +{ + if (kvDBConnection == nullptr) { + return -E_INVALID_DB; + } + + if (key.empty() || key.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + + IOption option; + int errCode = kvDBConnection->Delete(option, key); + if (errCode != E_OK) { + LOGE("Delete data failed:%d", errCode); + } + + return errCode; +} + +static int GetData(const IKvDBConnection *kvDBConnection, const Key &key, Value &value) +{ + if (kvDBConnection == nullptr) { + return -E_INVALID_DB; + } + + if (key.empty() || key.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + + IOption option; + int errCode = kvDBConnection->Get(option, key, value); + if (errCode != E_OK && errCode != -E_NOT_FOUND) { + LOGE("Get data failed:%d", errCode); + } + + return errCode; +} + +static int GetEntries(const IKvDBConnection *kvDBConnection, const Key &keyPrefix, std::vector &entries) +{ + if (kvDBConnection == nullptr) { + return -E_INVALID_DB; + } + + if (keyPrefix.empty() || keyPrefix.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + + IOption option; + int errCode = kvDBConnection->GetEntries(option, keyPrefix, entries); + if (errCode != E_OK && errCode != -E_NOT_FOUND) { + LOGE("Get entries failed:%d", errCode); + } + + return errCode; +} + +static int GetSliceCount(std::vector &&entries, uint32_t &count) +{ + std::vector buffer = (entries[0].key.size() > entries[1].key.size()) ? + std::move(entries[0].value) : std::move(entries[1].value); + Parcel parcel(buffer.data(), buffer.size()); + uint32_t size = parcel.ReadUInt32(count); + if (size != sizeof(count) || parcel.IsError()) { + LOGE("Get slice count size:%u", size); + return -E_PARSE_FAIL; + } + return E_OK; +} + +static int PutSliceCount(IKvDBConnection *kvDBConnection, const Key &sliceKey, uint32_t count) +{ + Key countKey(sliceKey); + countKey.push_back(HASH_COUNT_MAGIC); + std::vector buffer(sizeof(uint32_t), 0); + Parcel parcel(buffer.data(), buffer.size()); + int errCode = parcel.WriteUInt32(count); + if (errCode != E_OK) { + return errCode; + } + IOption option; + errCode = kvDBConnection->Put(option, countKey, buffer); + if (errCode != E_OK) { + LOGE("Put slice count failed:%d", errCode); + } + return errCode; +} + +static int PutSlice(IKvDBConnection *kvDBConnection, const Key &key, const Value &value, bool isAddCount) +{ + std::vector entries; + int errCode = GetEntries(kvDBConnection, key, entries); + uint32_t dataCount = 1; + switch (errCode) { + case E_OK: + if (entries.size() != EXPECT_ENTRIES_NUM) { + return -E_INCORRECT_DATA; + } + errCode = GetSliceCount(std::move(entries), dataCount); + if (errCode != E_OK) { + return errCode; + } + dataCount++; + errCode = PutSliceCount(kvDBConnection, key, dataCount); + return errCode; + case -E_NOT_FOUND: + errCode = PutData(kvDBConnection, key, value); + if (errCode != E_OK) { + return errCode; + } + dataCount = isAddCount ? 1 : 0; + errCode = PutSliceCount(kvDBConnection, key, dataCount); + return errCode; + default: + return errCode; + } +} + +static int DeleteSlice(IKvDBConnection *kvDBConnection, const Key &key) +{ + std::vector entries; + int errCode = GetEntries(kvDBConnection, key, entries); + if (errCode != E_OK) { + return errCode; + } + if (entries.size() != EXPECT_ENTRIES_NUM) { + return -E_INCORRECT_DATA; + } + uint32_t dataCount = 0; + Key countKey(key); + errCode = GetSliceCount(std::move(entries), dataCount); + if (errCode != E_OK) { + return errCode; + } + if (dataCount > 1) { + dataCount--; + errCode = PutSliceCount(kvDBConnection, key, dataCount); + return errCode; + } else { + errCode = DeleteData(kvDBConnection, key); + if (errCode != E_OK) { + return errCode; + } + countKey.push_back(HASH_COUNT_MAGIC); + errCode = DeleteData(kvDBConnection, countKey); + return errCode; + } +} + +MultiVerKvDataStorage::MultiVerKvDataStorage() + : kvStorage_(nullptr), + metaStorage_(nullptr), + kvStorageConnection_(nullptr), + metaStorageConnection_(nullptr) +{} + +MultiVerKvDataStorage::~MultiVerKvDataStorage() +{ + Close(); +} + +int MultiVerKvDataStorage::CheckVersion(const Property &property, bool &isDbAllExist) const +{ + int metaDataVer = 0; + int valueSliceVer = 0; + bool isMetaDbExist = false; + bool isSliceDbExist = false; + int errCode = GetVersion(property, metaDataVer, isMetaDbExist, valueSliceVer, isSliceDbExist); + if (errCode != E_OK) { + LOGE("[KvStorage][CheckVer] GetVersion failed, errCode=%d.", errCode); + return errCode; + } + if (isMetaDbExist != isSliceDbExist) { + // In case failure happens during open progress, some dbFile will not exist, we should recover from this + LOGW("[KvStorage][CheckVer] Detect File Lost, isMetaDbExist=%d, isSliceDbExist=%d.", + isMetaDbExist, isSliceDbExist); + } + isDbAllExist = isMetaDbExist && isSliceDbExist; + if (!isMetaDbExist && !isSliceDbExist) { + // If both dbFile not exist, just return. + return E_OK; + } + LOGD("[KvStorage][CheckVer] MetaDbVer=%d, CurMetaVer=%d, SliceDbVer=%d, CurSliceVer=%d.", metaDataVer, + MULTI_VER_METADATA_STORAGE_VERSION_CURRENT, valueSliceVer, MULTI_VER_VALUESLICE_STORAGE_VERSION_CURRENT); + // For the dbFile not exist, version value will be 0, do not affect version check below + if (metaDataVer > MULTI_VER_METADATA_STORAGE_VERSION_CURRENT || + valueSliceVer > MULTI_VER_VALUESLICE_STORAGE_VERSION_CURRENT) { + LOGE("[KvStorage][CheckVer] Version Not Support!"); + return -E_VERSION_NOT_SUPPORT; + } + return E_OK; +} + +int MultiVerKvDataStorage::GetVersion(const Property &property, int &metaVer, bool &isMetaDbExist, + int &sliceVer, bool &isSliceDbExist) const +{ + SQLiteLocalKvDB *localKvdb = new (std::nothrow) SQLiteLocalKvDB(); + if (localKvdb == nullptr) { + return -E_INVALID_DB; + } + + KvDBProperties dbProperties; + dbProperties.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, property.isNeedCreate); + dbProperties.SetStringProp(KvDBProperties::DATA_DIR, property.dataDir); + dbProperties.SetStringProp(KvDBProperties::FILE_NAME, DBConstant::MULTI_VER_VALUE_STORE); + dbProperties.SetStringProp(KvDBProperties::IDENTIFIER_DIR, property.identifierName); + dbProperties.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::MULTI_VER_TYPE); + dbProperties.SetPassword(property.cipherType, property.passwd); + int errCode = localKvdb->GetVersion(dbProperties, sliceVer, isSliceDbExist); + if (errCode != E_OK) { + LOGE("[KvStorage][GetVer] Get valueSlice storage version fail, errCode=%d.", errCode); + RefObject::DecObjRef(localKvdb); + localKvdb = nullptr; + return errCode; + } + + dbProperties.SetStringProp(KvDBProperties::FILE_NAME, DBConstant::MULTI_VER_META_STORE); + errCode = localKvdb->GetVersion(dbProperties, metaVer, isMetaDbExist); + if (errCode != E_OK) { + LOGE("[KvStorage][GetVer] Get metaData storage version fail, errCode=%d.", errCode); + RefObject::DecObjRef(localKvdb); + localKvdb = nullptr; + return errCode; + } + + RefObject::DecObjRef(localKvdb); + localKvdb = nullptr; + return E_OK; +} + +int MultiVerKvDataStorage::Open(const Property &property) +{ + int errCode = E_OK; + if (kvStorage_ == nullptr) { + DatabaseIdentifierCfg config = {property.dataDir, property.identifierName, DBConstant::MULTI_VER_VALUE_STORE}; + kvStorage_ = OpenKvDB(config, property.cipherType, property.passwd, errCode); + if (kvStorage_ == nullptr) { + LOGE("open kv storage failed"); + goto END; + } + } + + if (metaStorage_ == nullptr) { + DatabaseIdentifierCfg config = {property.dataDir, property.identifierName, DBConstant::MULTI_VER_META_STORE}; + metaStorage_ = OpenKvDB(config, property.cipherType, property.passwd, errCode); + if (metaStorage_ == nullptr) { + LOGE("open meta storage failed"); + goto END; + } + } + + kvStorageConnection_ = kvStorage_->GetDBConnection(errCode); + if (errCode != E_OK) { + goto END; + } + + metaStorageConnection_ = metaStorage_->GetDBConnection(errCode); + if (errCode != E_OK) { + goto END; + } + +END: + if (errCode != E_OK) { + Close(); + } + return errCode; +} + +void MultiVerKvDataStorage::Close() +{ + if (kvStorageConnection_ != nullptr) { + kvStorageConnection_->Close(); + kvStorageConnection_ = nullptr; + } + + if (metaStorageConnection_ != nullptr) { + metaStorageConnection_->Close(); + metaStorageConnection_ = nullptr; + } + + if (kvStorage_ != nullptr) { + RefObject::KillAndDecObjRef(kvStorage_); + kvStorage_ = nullptr; + } + + if (metaStorage_ != nullptr) { + RefObject::KillAndDecObjRef(metaStorage_); + metaStorage_ = nullptr; + } +} + +int MultiVerKvDataStorage::PutMetaData(const Key &key, const Value &value) +{ + return PutData(metaStorageConnection_, key, value); +} + +int MultiVerKvDataStorage::GetMetaData(const Key &key, Value &value) const +{ + return GetData(metaStorageConnection_, key, value); +} + +int MultiVerKvDataStorage::RunRekeyLogic(CipherType type, const CipherPassword &passwd) +{ + int errCode = static_cast(kvStorage_)->RunRekeyLogic(type, passwd); + if (errCode != E_OK) { + LOGE("value storage rekey failed:%d", errCode); + return errCode; + } + errCode = static_cast(metaStorage_)->RunRekeyLogic(type, passwd); + if (errCode != E_OK) { + LOGE("meta storage rekey failed:%d", errCode); + return errCode; + } + return E_OK; +} + +int MultiVerKvDataStorage::RunExportLogic(CipherType type, const CipherPassword &passwd, const std::string &dbDir) const +{ + // execute export + std::string valueDbName = dbDir + "/value_storage.db"; + int errCode = static_cast(kvStorage_)->RunExportLogic(type, passwd, valueDbName); + if (errCode != E_OK) { + LOGE("value storage export failed:%d", errCode); + return errCode; + } + + std::string metaDbName = dbDir + "/meta_storage.db"; + errCode = static_cast(metaStorage_)->RunExportLogic(type, passwd, metaDbName); + if (errCode != E_OK) { + LOGE("meta storage export failed:%d", errCode); + return errCode; + } + return E_OK; +} + +int MultiVerKvDataStorage::BackupCurrentDatabase(const Property &property, const std::string &dir) +{ + KvDBProperties dbProperties; + dbProperties.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + dbProperties.SetStringProp(KvDBProperties::DATA_DIR, property.dataDir); + dbProperties.SetStringProp(KvDBProperties::FILE_NAME, DBConstant::MULTI_VER_META_STORE); + dbProperties.SetStringProp(KvDBProperties::IDENTIFIER_DIR, property.identifierName); + dbProperties.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::MULTI_VER_TYPE); + dbProperties.SetPassword(property.cipherType, property.passwd); + int errCode = SQLiteLocalKvDB::BackupCurrentDatabase(dbProperties, dir); + if (errCode != E_OK) { + return errCode; + } + + dbProperties.SetStringProp(KvDBProperties::FILE_NAME, DBConstant::MULTI_VER_VALUE_STORE); + return SQLiteLocalKvDB::BackupCurrentDatabase(dbProperties, dir); +} + +int MultiVerKvDataStorage::ImportDatabase(const Property &property, const std::string &dir, + const CipherPassword &passwd) +{ + KvDBProperties dbProperties; + dbProperties.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + dbProperties.SetStringProp(KvDBProperties::DATA_DIR, property.dataDir); + dbProperties.SetStringProp(KvDBProperties::FILE_NAME, DBConstant::MULTI_VER_META_STORE); + dbProperties.SetStringProp(KvDBProperties::IDENTIFIER_DIR, property.identifierName); + dbProperties.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::MULTI_VER_TYPE); + dbProperties.SetPassword(property.cipherType, property.passwd); + int errCode = SQLiteLocalKvDB::ImportDatabase(dbProperties, dir, passwd); + if (errCode != E_OK) { + return errCode; + } + + dbProperties.SetStringProp(KvDBProperties::FILE_NAME, DBConstant::MULTI_VER_VALUE_STORE); + return SQLiteLocalKvDB::ImportDatabase(dbProperties, dir, passwd); +} + +SliceTransaction *MultiVerKvDataStorage::GetSliceTransaction(bool isWrite, int &errCode) +{ + auto connect = kvStorage_->GetDBConnection(errCode); + if (connect == nullptr) { + return nullptr; + } + auto transaction = new (std::nothrow) SliceTransaction(isWrite, connect); + if (transaction == nullptr) { + errCode = -E_OUT_OF_MEMORY; + connect->Close(); + return nullptr; + } + errCode = E_OK; + return transaction; +} + +void MultiVerKvDataStorage::ReleaseSliceTransaction(SliceTransaction *&transaction) +{ + if (transaction == nullptr) { + return; + } + transaction->Close(); + delete transaction; + transaction = nullptr; + return; +} + +SliceTransaction::SliceTransaction(bool isWrite, IKvDBConnection *connect) + : isWrite_(isWrite), + connect_(connect) +{} + +SliceTransaction::~SliceTransaction() +{} + +int SliceTransaction::Close() +{ + if (connect_ == nullptr) { + return E_OK; + } + return connect_->Close(); +} + +int SliceTransaction::PutData(const Key &key, const Value &value, bool isAddCount) +{ + if (!isWrite_) { + return -E_INVALID_CONNECTION; + } + return PutSlice(connect_, key, value, isAddCount); +} + +int SliceTransaction::GetData(const Key &key, Value &value) const +{ + return ::DistributedDB::GetData(connect_, key, value); +} + +int SliceTransaction::DeleteData(const Key &key) +{ + if (!isWrite_) { + return -E_INVALID_CONNECTION; + } + return DeleteSlice(connect_, key); +} + +int SliceTransaction::StartTransaction() +{ + if (connect_ == nullptr) { + return -E_INVALID_CONNECTION; + } + return connect_->StartTransaction(); +} + +int SliceTransaction::CommitTransaction() +{ + if (connect_ == nullptr) { + return -E_INVALID_CONNECTION; + } + return connect_->Commit(); +} + +int SliceTransaction::RollbackTransaction() +{ + if (connect_ == nullptr) { + return -E_INVALID_CONNECTION; + } + return connect_->RollBack(); +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_kvdata_storage.h b/mock/distributeddb/storage/src/multiver/multi_ver_kvdata_storage.h new file mode 100644 index 00000000..82cae19d --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_kvdata_storage.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_KV_DATA_STORAGE_H +#define MULTI_VER_KV_DATA_STORAGE_H + +#ifndef OMIT_MULTI_VER +#include +#include + +#include "ikvdb.h" +#include "macro_utils.h" + +namespace DistributedDB { +class SliceTransaction { +public: + SliceTransaction(bool isWrite, IKvDBConnection *connect); + ~SliceTransaction(); + int Close(); + int PutData(const Key &key, const Value &value, bool isAddCount); + int GetData(const Key &key, Value &value) const; + int DeleteData(const Key &key); + int StartTransaction(); + int CommitTransaction(); + int RollbackTransaction(); +private: + bool isWrite_; + IKvDBConnection *connect_; +}; + +class MultiVerKvDataStorage { +public: + struct Property final { + std::string dataDir; + std::string identifierName; + bool isNeedCreate = true; + CipherType cipherType = CipherType::AES_256_GCM; + CipherPassword passwd; + }; + + MultiVerKvDataStorage(); + ~MultiVerKvDataStorage(); + + DISABLE_COPY_ASSIGN_MOVE(MultiVerKvDataStorage); + + int Open(const Property &property); + + void Close(); + + int PutMetaData(const Key &key, const Value &value); + + int GetMetaData(const Key &key, Value &value) const; + + SliceTransaction *GetSliceTransaction(bool isWrite, int &errCode); + + void ReleaseSliceTransaction(SliceTransaction *&transaction); + + int RunRekeyLogic(CipherType type, const CipherPassword &passwd); + + int RunExportLogic(CipherType type, const CipherPassword &passwd, const std::string &dbDir) const; + + int CheckVersion(const Property &property, bool &isDbAllExist) const; + + int BackupCurrentDatabase(const Property &property, const std::string &dir); + + int ImportDatabase(const Property &property, const std::string &dir, const CipherPassword &passwd); + +private: + int GetVersion(const Property &property, int &metaVer, bool &isMetaDbExist, + int &sliceVer, bool &isSliceDbExist) const; + + IKvDB *kvStorage_; + IKvDB *metaStorage_; + IKvDBConnection *kvStorageConnection_; + IKvDBConnection *metaStorageConnection_; + mutable std::mutex metaDataMutex_; + mutable std::mutex kvDataMutex_; +}; +} + +#endif // MULTI_VER_KV_DATA_STORAGE_H +#endif diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_natural_store.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store.cpp new file mode 100644 index 00000000..8f0ca69a --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store.cpp @@ -0,0 +1,1195 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_natural_store.h" + +#include +#include + +#include "securec.h" + +#include "db_constant.h" +#include "ikvdb_factory.h" +#include "db_common.h" +#include "endian_convert.h" +#include "log_print.h" +#include "db_errno.h" +#include "multi_ver_storage_engine.h" +#include "multi_ver_natural_store_connection.h" +#include "generic_multi_ver_kv_entry.h" +#include "sqlite_multi_ver_data_storage.h" +#include "multi_ver_natural_store_commit_storage.h" +#include "multi_ver_vacuum_executor_impl.h" +#include "kvdb_utils.h" +#include "sqlite_utils.h" +#include "platform_specific.h" +#include "package_file.h" +#include "multi_ver_database_oper.h" + +namespace DistributedDB { +namespace { + // file block doesn't support the atomic of the upgrade temporarily. + struct VersionFileBlock { + static const uint64_t MAGIC_NUMBER = 0x37F8C35AULL; + uint64_t magic = MAGIC_NUMBER; // magic number. + uint32_t fileVersion = VERSION_FILE_VERSION_CURRENT; // file format version. + uint32_t version = 0U; // version of the database. + uint8_t tag[MULTI_VER_TAG_SIZE] = {0}; // tag of the multi ver branch. + uint8_t reserved[72] = {0}; // reserved data. + uint8_t checkSum[32] = {0}; // check sum + }; + + void TransferHostFileBlockToNet(VersionFileBlock &block) + { + block.magic = HostToNet(block.magic); + block.fileVersion = HostToNet(block.fileVersion); + block.version = HostToNet(block.version); + } + + void TransferNetFileBlockToHost(VersionFileBlock &block) + { + block.magic = NetToHost(block.magic); + block.fileVersion = NetToHost(block.fileVersion); + block.version = NetToHost(block.version); + } + + int CalcFileBlockCheckSum(VersionFileBlock &block) + { + std::vector vect(reinterpret_cast(&block), + reinterpret_cast(&block) + sizeof(block) - sizeof(block.checkSum)); + std::vector hashVect; + int errCode = DBCommon::CalcValueHash(vect, hashVect); + if (errCode != E_OK) { + return errCode; + } + errCode = memcpy_s(block.checkSum, sizeof(block.checkSum), hashVect.data(), hashVect.size()); + if (errCode != EOK) { + return -E_SECUREC_ERROR; + } + return E_OK; + } + + int CheckFileBlock(VersionFileBlock &block) + { + uint64_t readMagic = NetToHost(block.magic); + if (readMagic != block.MAGIC_NUMBER) { + LOGE("Invalid file head"); + return -E_UNEXPECTED_DATA; + } + + std::vector vect(reinterpret_cast(&block), + reinterpret_cast(&block) + sizeof(block) - sizeof(block.checkSum)); + std::vector hashVect; + int errCode = DBCommon::CalcValueHash(vect, hashVect); + if (errCode != E_OK) { + return errCode; + } + if (memcmp(hashVect.data(), block.checkSum, sizeof(block.checkSum)) != 0) { + LOGE("Check block error"); + return -E_UNEXPECTED_DATA; + } + + return E_OK; + } + + int CreateNewVersionFile(const std::string &versionFileDir, uint32_t version, std::vector &tag) + { + VersionFileBlock block; + block.version = version; + RAND_bytes(block.tag, sizeof(block.tag)); + int errCode = memset_s(block.reserved, sizeof(block.reserved), 0, sizeof(block.reserved)); + if (errCode != EOK) { + return -E_SECUREC_ERROR; + } + + TransferHostFileBlockToNet(block); + errCode = CalcFileBlockCheckSum(block); + if (errCode != E_OK) { + return errCode; + } + FILE *versionFile = fopen(versionFileDir.c_str(), "wb+"); + if (versionFile == nullptr) { + LOGE("Open the version file error:%d", errno); + return -E_SYSTEM_API_FAIL; + } + size_t writeSize = fwrite(static_cast(&block), 1, sizeof(VersionFileBlock), versionFile); + if (writeSize != sizeof(VersionFileBlock)) { + LOGE("Write version file head error:%d", errno); + errCode = -E_SYSTEM_API_FAIL; + } else { + errCode = E_OK; + tag.assign(block.tag, block.tag + sizeof(block.tag)); + } + + fclose(versionFile); + versionFile = nullptr; + return errCode; + } + + int ChangeVersionFile(const std::string &versionFileDir, uint32_t version, std::vector &tag, + bool isChangeTag) + { + FILE *versionFile = fopen(versionFileDir.c_str(), "rb+"); + if (versionFile == nullptr) { + LOGE("Open the version file error:%d", errno); + return -E_SYSTEM_API_FAIL; + } + VersionFileBlock block; + size_t operateSize = fread(static_cast(&block), 1, sizeof(VersionFileBlock), versionFile); + if (operateSize != sizeof(VersionFileBlock)) { + fclose(versionFile); + LOGE("Read file error:%d", errno); + return -E_SYSTEM_API_FAIL; + }; + int errCode = CheckFileBlock(block); + if (errCode != E_OK) { + goto END; + } + TransferHostFileBlockToNet(block); + block.version = version; + + if (isChangeTag) { + RAND_bytes(block.tag, sizeof(block.tag)); + tag.assign(block.tag, block.tag + sizeof(block.tag)); + } + + TransferHostFileBlockToNet(block); + errCode = CalcFileBlockCheckSum(block); + if (errCode != E_OK) { + goto END; + } + + fseeko64(versionFile, 0LL, SEEK_SET); + operateSize = fwrite(&block, 1, sizeof(VersionFileBlock), versionFile); + if (operateSize != sizeof(VersionFileBlock)) { + LOGE("write the file error:%d", errno); + errCode = -E_SYSTEM_API_FAIL; + goto END; + } + END: + fclose(versionFile); + versionFile = nullptr; + return errCode; + } + + int GetVersionAndTag(const std::string &versionFileDir, uint32_t &version, std::vector &tag) + { + FILE *versionFile = fopen(versionFileDir.c_str(), "rb+"); + if (versionFile == nullptr) { + LOGE("Open the version file error:%d", errno); + return -E_SYSTEM_API_FAIL; + } + int errCode = E_OK; + VersionFileBlock block; + size_t readSize = fread(static_cast(&block), 1, sizeof(VersionFileBlock), versionFile); + if (readSize != sizeof(VersionFileBlock)) { + LOGE("read the file error:%d", errno); + errCode = -E_SYSTEM_API_FAIL; + goto END; + }; + errCode = CheckFileBlock(block); + if (errCode != E_OK) { + LOGE("Check the file block error"); + goto END; + } + TransferNetFileBlockToHost(block); + version = block.version; + tag.assign(block.tag, block.tag + sizeof(block.tag)); + END: + fclose(versionFile); + versionFile = nullptr; + return errCode; + } +} + +MultiVerVacuum MultiVerNaturalStore::shadowTrimmer_; +MultiVerNaturalStore::MultiVerNaturalStore() + : multiVerData_(nullptr), + commitHistory_(nullptr), + multiVerKvStorage_(nullptr), + multiVerEngine_(nullptr), + trimmerImpl_(nullptr), + maxRecordTimestamp_(0), + maxCommitVersion_(0) +{} + +MultiVerNaturalStore::~MultiVerNaturalStore() +{ + Clear(); + UnRegisterNotificationEventType(NATURAL_STORE_COMMIT_EVENT); +} + +void MultiVerNaturalStore::Clear() +{ + if (trimmerImpl_ != nullptr) { + shadowTrimmer_.Abort(GetStringIdentifier()); + delete trimmerImpl_; + trimmerImpl_ = nullptr; + } + { + std::lock_guard lock(commitHistMutex_); + if (commitHistory_ != nullptr) { + commitHistory_->Close(); + delete commitHistory_; + commitHistory_ = nullptr; + } + } + { + std::lock_guard lock(multiDataMutex_); + if (multiVerData_ != nullptr) { + multiVerData_->Close(); + delete multiVerData_; + multiVerData_ = nullptr; + } + } + + { + std::lock_guard lock(syncerKvMutex_); + if (multiVerKvStorage_ != nullptr) { + multiVerKvStorage_->Close(); + delete multiVerKvStorage_; + multiVerKvStorage_ = nullptr; + } + } + multiVerEngine_ = nullptr; +} + +int MultiVerNaturalStore::InitStorages(const KvDBProperties &kvDBProp, bool isChangeTag) +{ + std::string dataDir = kvDBProp.GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string identifierDir = kvDBProp.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + bool isNeedCreate = kvDBProp.GetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + CipherType cipherType; + CipherPassword passwd; + kvDBProp.GetPassword(cipherType, passwd); + + IKvDBMultiVerDataStorage::Property multiVerProp = {dataDir, identifierDir, isNeedCreate, cipherType, passwd}; + IKvDBCommitStorage::Property commitProp = {dataDir, identifierDir, isNeedCreate, cipherType, passwd}; + MultiVerKvDataStorage::Property multiVerKvProp = {dataDir, identifierDir, isNeedCreate, cipherType, passwd}; + + int errCode = DBCommon::CreateStoreDirectory(dataDir, identifierDir, DBConstant::MULTI_SUB_DIR, isNeedCreate); + if (errCode != E_OK) { + return errCode; + } + + errCode = CheckVersion(kvDBProp); + if (errCode != E_OK) { + LOGE("Upgrade multi ver failed:%d", errCode); + return errCode; + } + + errCode = multiVerData_->Open(multiVerProp); + if (errCode != E_OK) { + LOGE("MultiVer::InitStorages open multiVerData fail! errCode[%d]", errCode); + return errCode; + } + + errCode = commitHistory_->Open(commitProp); + if (errCode != E_OK) { + LOGE("MultiVer::InitStorages open commitHistory fail! errCode[%d]", errCode); + return errCode; + } + + errCode = multiVerKvStorage_->Open(multiVerKvProp); + if (errCode != E_OK) { + LOGE("Open multi ver kv storage failed:%d", errCode); + return errCode; + } + + errCode = RecoverFromException(); + if (errCode != E_OK) { + LOGE("Recover multi version storage failed:%d", errCode); + return errCode; + } + return InitStorageContext(isChangeTag); +} + +int MultiVerNaturalStore::CheckSubStorageVersion(const KvDBProperties &kvDBProp, bool &isSubStorageAllExist) const +{ + std::string dataDir = kvDBProp.GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string identifierDir = kvDBProp.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + bool isNeedCreate = kvDBProp.GetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + CipherType cipherType; + CipherPassword passwd; + kvDBProp.GetPassword(cipherType, passwd); + + IKvDBMultiVerDataStorage::Property multiVerProp = {dataDir, identifierDir, isNeedCreate, cipherType, passwd}; + IKvDBCommitStorage::Property commitProp = {dataDir, identifierDir, isNeedCreate, cipherType, passwd}; + MultiVerKvDataStorage::Property multiVerKvProp = {dataDir, identifierDir, true, cipherType, passwd}; + + bool isDataStorageExist = false; + bool isCommitStorageExist = false; + bool isKvStorageAllExist = false; + int errCode = multiVerData_->CheckVersion(multiVerProp, isDataStorageExist); + if (errCode != E_OK) { + return errCode; + } + errCode = commitHistory_->CheckVersion(commitProp, isCommitStorageExist); + if (errCode != E_OK) { + return errCode; + } + errCode = multiVerKvStorage_->CheckVersion(multiVerKvProp, isKvStorageAllExist); + if (errCode != E_OK) { + return errCode; + } + if ((isDataStorageExist != isCommitStorageExist) || (isCommitStorageExist != isKvStorageAllExist)) { + // In case failure happens during open progress, some dbFile will not exist, we should recover from this + LOGW("[MultiVerStore][CheckSubVer] Detect File Lost, isDataExist=%d, isCommitExist=%d, isKvAllExist=%d.", + isDataStorageExist, isCommitStorageExist, isKvStorageAllExist); + } + isSubStorageAllExist = isDataStorageExist && isCommitStorageExist && isKvStorageAllExist; + return E_OK; +} + +int MultiVerNaturalStore::CreateStorages() +{ + int errCode = E_OK; + IKvDBFactory *factory = IKvDBFactory::GetCurrent(); + if (factory == nullptr) { + return -E_INVALID_DB; + } + multiVerData_ = factory->CreateMultiVerStorage(errCode); + if (multiVerData_ == nullptr) { + return errCode; + } + + commitHistory_ = factory->CreateMultiVerCommitStorage(errCode); + if (commitHistory_ == nullptr) { + return errCode; + } + + multiVerKvStorage_ = new (std::nothrow) MultiVerKvDataStorage; + if (multiVerKvStorage_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + return E_OK; +} + +int MultiVerNaturalStore::ClearTempFile(const KvDBProperties &kvDBProp) +{ + std::unique_ptr operation = std::make_unique(this, multiVerData_, + commitHistory_, multiVerKvStorage_); + (void)operation->ClearExportedTempFiles(kvDBProp); + int errCode = operation->RekeyRecover(kvDBProp); + if (errCode != E_OK) { + LOGE("Recover for open db failed in multi version:%d", errCode); + return errCode; + } + + errCode = operation->ClearImportTempFile(kvDBProp); + if (errCode != E_OK) { + LOGE("Recover import temp file for open db failed in multi version:%d", errCode); + } + return errCode; +} + +// Open the database +int MultiVerNaturalStore::Open(const KvDBProperties &kvDBProp) +{ + StorageEngineAttr poolSize = {0, 1, 0, 16}; // 1 write 16 read at most. + int errCode = CreateStorages(); + if (errCode != E_OK) { + goto ERROR; + } + + MyProp() = kvDBProp; + errCode = ClearTempFile(kvDBProp); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = InitStorages(kvDBProp); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = RegisterNotificationEventType(NATURAL_STORE_COMMIT_EVENT); + if (errCode != E_OK) { + LOGE("RegisterEventType failed!"); + goto ERROR; + } + + multiVerEngine_ = std::make_unique(); + errCode = multiVerEngine_->InitDatabases(this, multiVerData_, commitHistory_, multiVerKvStorage_, poolSize); + if (errCode != E_OK) { + goto ERROR; + } + // Start the trimming; + trimmerImpl_ = new (std::nothrow) MultiVerVacuumExecutorImpl(this); + if (trimmerImpl_ == nullptr) { + errCode = -E_OUT_OF_MEMORY; + goto ERROR; + } + + shadowTrimmer_.Launch(GetStringIdentifier(), trimmerImpl_); + StartSyncer(); + return E_OK; +ERROR: + Clear(); + return errCode; +} + +void MultiVerNaturalStore::Close() +{ + // Abort the trimming; + SyncAbleKvDB::Close(); + Clear(); +} + +GenericKvDBConnection *MultiVerNaturalStore::NewConnection(int &errCode) +{ + auto connection = new (std::nothrow) MultiVerNaturalStoreConnection(this); + if (connection == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + + errCode = E_OK; + return connection; +} + +// Get interface for syncer. +IKvDBSyncInterface *MultiVerNaturalStore::GetSyncInterface() +{ + return this; +} + +// Get interface type of this kvdb. +int MultiVerNaturalStore::GetInterfaceType() const +{ + return SYNC_MVD; +} + +// Get the interface ref-count, in order to access asynchronously. +void MultiVerNaturalStore::IncRefCount() +{ + IncObjRef(this); +} + +// Drop the interface ref-count. +void MultiVerNaturalStore::DecRefCount() +{ + DecObjRef(this); +} + +// Get the identifier of this kvdb. +std::vector MultiVerNaturalStore::GetIdentifier() const +{ + std::string identifier = MyProp().GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + std::vector identifierVect(identifier.begin(), identifier.end()); + return identifierVect; +} + +std::string MultiVerNaturalStore::GetStringIdentifier() const +{ + std::string identifier = MyProp().GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + std::vector idVect(identifier.begin(), identifier.end()); + return VEC_TO_STR(idVect); +} + +// Get the max timestamp of all entries in database. +void MultiVerNaturalStore::GetMaxTimestamp(Timestamp &stamp) const +{ + std::lock_guard lock(maxTimeMutex_); + stamp = maxRecordTimestamp_; +} + +void MultiVerNaturalStore::SetMaxTimestamp(Timestamp stamp) +{ + std::lock_guard lock(maxTimeMutex_); + maxRecordTimestamp_ = (stamp > maxRecordTimestamp_) ? stamp : maxRecordTimestamp_; +} + +// Get meta data associated with the given key. +int MultiVerNaturalStore::GetMetaData(const Key &key, Value &value) const +{ + int errCode = E_OK; + auto handle = GetHandle(false, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->GetMetaData(key, value); + ReleaseHandle(handle); + return errCode; +} + +// Put meta data as a key-value entry. +int MultiVerNaturalStore::PutMetaData(const Key &key, const Value &value) +{ + int errCode = E_OK; + auto handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->PutMetaData(key, value); + ReleaseHandle(handle); + return errCode; +} + +int MultiVerNaturalStore::DeleteMetaData(const std::vector &keys) +{ + return -E_NOT_SUPPORT; +} + +// Get all meta data keys. +int MultiVerNaturalStore::GetAllMetaKeys(std::vector &keys) const +{ + return E_OK; +} + +bool MultiVerNaturalStore::IsCommitExisted(const MultiVerCommitNode &commit) const +{ + int errCode = E_OK; + auto handle = GetHandle(false, errCode); + if (handle == nullptr) { + return false; + } + + bool result = handle->IsCommitExisted(commit, errCode); + ReleaseHandle(handle); + return result; +} + +int MultiVerNaturalStore::GetDeviceLatestCommit(std::map &commitMap) const +{ + int errCode = E_OK; + auto handle = GetHandle(false, errCode); + if (handle == nullptr) { + return -E_BUSY; + } + + errCode = handle->GetDeviceLatestCommit(commitMap); + ReleaseHandle(handle); + return errCode; +} + +int MultiVerNaturalStore::GetCommitTree(const std::map &commitMap, + std::vector &commits) const +{ + int errCode = E_OK; + auto handle = GetHandle(false, errCode); + if (handle == nullptr) { + return -E_BUSY; + } + + errCode = handle->GetCommitTree(commitMap, commits); + ReleaseHandle(handle); + return errCode; +} + +int MultiVerNaturalStore::GetCommitData(const MultiVerCommitNode &commit, std::vector &entries) const +{ + int errCode = E_OK; + auto handle = GetHandle(false, errCode); + if (handle == nullptr) { + return -E_BUSY; + } + + errCode = handle->GetCommitData(commit, entries); + ReleaseHandle(handle); + return errCode; +} + +MultiVerKvEntry *MultiVerNaturalStore::CreateKvEntry(const std::vector &data) +{ + auto kvEntry = new (std::nothrow) GenericMultiVerKvEntry; + if (kvEntry == nullptr) { + return nullptr; + } + + int errCode = kvEntry->DeSerialData(data); + if (errCode != E_OK) { + LOGE("deserialize data into kv entry failed:%d", errCode); + delete kvEntry; + kvEntry = nullptr; + } + return kvEntry; +} + +void MultiVerNaturalStore::ReleaseKvEntry(const MultiVerKvEntry *entry) +{ + if (entry != nullptr) { + delete entry; + entry = nullptr; + } +} + +bool MultiVerNaturalStore::IsValueSliceExisted(const ValueSliceHash &value) const +{ + int errCode = E_OK; + auto handle = GetHandle(false, errCode); + if (handle == nullptr) { + return false; + } + + bool result = handle->IsValueSliceExisted(value, errCode); + ReleaseHandle(handle); + return result; +} + +int MultiVerNaturalStore::GetValueSlice(const ValueSliceHash &hashValue, ValueSlice &sliceValue) const +{ + int errCode = E_OK; + auto handle = GetHandle(false, errCode); + if (handle == nullptr) { + return -E_BUSY; + } + + errCode = handle->GetValueSlice(hashValue, sliceValue); + ReleaseHandle(handle); + return errCode; +} + +int MultiVerNaturalStore::PutValueSlice(const ValueSliceHash &hashValue, const ValueSlice &sliceValue) const +{ + int errCode = E_OK; + auto handle = GetHandle(true, errCode); + if (handle == nullptr) { + return -E_BUSY; + } + + errCode = handle->PutValueSlice(hashValue, sliceValue, false); + ReleaseHandle(handle); + return errCode; +} + +int MultiVerNaturalStore::PutCommitData(const MultiVerCommitNode &commit, const std::vector &entries, + const std::string &deviceName) +{ + int errCode = E_OK; + auto handle = GetHandle(true, errCode); + if (handle == nullptr) { + return -E_BUSY; + } + + errCode = handle->PutCommitData(commit, entries, deviceName); + ReleaseHandle(handle); + return errCode; +} + +int MultiVerNaturalStore::MergeSyncCommit(const MultiVerCommitNode &commit, + const std::vector &commits) +{ + int errCode = E_OK; + auto handle = GetHandle(true, errCode); + if (handle == nullptr) { + return -E_BUSY; + } + + errCode = handle->MergeSyncCommit(commit, commits); + ReleaseHandle(handle); + return errCode; +} + +void MultiVerNaturalStore::NotifyStartSyncOperation() +{ + shadowTrimmer_.Pause(GetStringIdentifier()); +} + +void MultiVerNaturalStore::NotifyFinishSyncOperation() +{ + shadowTrimmer_.Continue(GetStringIdentifier(), true); +} + +int MultiVerNaturalStore::TransferSyncCommitDevInfo(MultiVerCommitNode &commit, const std::string &devId, + bool isSyncedIn) const +{ + std::string hashDevId = DBCommon::TransferHashString(devId); + if (isSyncedIn) { + // The size of the device info must be hash_size + tag_size; + if (commit.deviceInfo.size() == hashDevId.size() + MULTI_VER_TAG_SIZE) { + // If the hash device info is matched with the local, just remove the hash device info. + if (commit.deviceInfo.compare(0, hashDevId.size(), hashDevId) == 0) { + commit.deviceInfo = commit.deviceInfo.substr(hashDevId.size(), MULTI_VER_TAG_SIZE); + } + return E_OK; + } + LOGE("Unexpected dev info for sync in:%zu", commit.deviceInfo.size()); + return -E_UNEXPECTED_DATA; + } else { + // If the device info only contains the tag info, it must be local node. + if (commit.deviceInfo.size() == MULTI_VER_TAG_SIZE) { + commit.deviceInfo.insert(0, hashDevId); + } else if (commit.deviceInfo.size() != hashDevId.size() + MULTI_VER_TAG_SIZE) { + LOGE("Unexpected dev info for sync out:%zu", commit.deviceInfo.size()); + return -E_UNEXPECTED_DATA; + } + return E_OK; + } +} + +int MultiVerNaturalStore::Rekey(const CipherPassword &passwd) +{ + if (multiVerEngine_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = multiVerEngine_->TryToDisable(false, OperatePerm::REKEY_MONOPOLIZE_PERM); + if (errCode != E_OK) { + return errCode; + } + StopSyncer(); + shadowTrimmer_.Pause(GetStringIdentifier()); + errCode = multiVerEngine_->TryToDisable(true, OperatePerm::REKEY_MONOPOLIZE_PERM); + if (errCode != E_OK) { + multiVerEngine_->Enable(OperatePerm::REKEY_MONOPOLIZE_PERM); + shadowTrimmer_.Continue(GetStringIdentifier(), true); + StartSyncer(); + return errCode; + } + + std::unique_ptr operation = std::make_unique(this, multiVerData_, + commitHistory_, multiVerKvStorage_); + errCode = operation->Rekey(passwd); + + multiVerEngine_->Enable(OperatePerm::REKEY_MONOPOLIZE_PERM); + shadowTrimmer_.Continue(GetStringIdentifier(), true); + StartSyncer(); + + return errCode; +} + +int MultiVerNaturalStore::Export(const std::string &filePath, const CipherPassword &passwd) +{ + if (multiVerEngine_ == nullptr) { + return -E_INVALID_DB; + } + std::string localDev; + int errCode = GetLocalIdentity(localDev); + if (errCode != E_OK) { + LOGE("Failed to GetLocalIdentity!"); + } + // Exclusively write resources + auto handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + std::unique_ptr operation = std::make_unique(this, multiVerData_, + commitHistory_, multiVerKvStorage_); + operation->SetLocalDevId(localDev); + errCode = operation->Export(filePath, passwd); + + ReleaseHandle(handle); + + return errCode; +} + +int MultiVerNaturalStore::Import(const std::string &filePath, const CipherPassword &passwd) +{ + if (multiVerEngine_ == nullptr) { + return -E_INVALID_DB; + } + std::string localDev; + int errCode = GetLocalIdentity(localDev); + if (errCode != E_OK) { + LOGE("Failed to get the local identity!"); + localDev.resize(0); + } + errCode = multiVerEngine_->TryToDisable(false, OperatePerm::IMPORT_MONOPOLIZE_PERM); + if (errCode != E_OK) { + return errCode; + } + StopSyncer(); + shadowTrimmer_.Abort(GetStringIdentifier()); + std::unique_ptr operation; + errCode = multiVerEngine_->TryToDisable(true, OperatePerm::IMPORT_MONOPOLIZE_PERM); + if (errCode != E_OK) { + goto END; + } + operation = std::make_unique(this, multiVerData_, commitHistory_, multiVerKvStorage_); + operation->SetLocalDevId(localDev); + errCode = operation->Import(filePath, passwd); +END: + multiVerEngine_->Enable(OperatePerm::IMPORT_MONOPOLIZE_PERM); + shadowTrimmer_.Launch(GetStringIdentifier(), trimmerImpl_); + StartSyncer(); + return errCode; +} + +uint64_t MultiVerNaturalStore::GetCurrentTimestamp() +{ + return GetTimestamp(); +} + +int MultiVerNaturalStore::GetDiffEntries(const CommitID &begin, const CommitID &end, MultiVerDiffData &data) const +{ + // Get one connection. + int errCode = E_OK; + auto handle = GetHandle(false, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->GetDiffEntries(begin, end, data); + ReleaseHandle(handle); + return errCode; +} + +int MultiVerNaturalStore::RecoverFromException() +{ + // Get the latest local version and the head node. + if (multiVerData_ == nullptr || commitHistory_ == nullptr) { + return -E_INVALID_DB; + } + + IKvDBMultiVerTransaction *transaction = nullptr; + int errCode = multiVerData_->StartWrite(KvDataType::KV_DATA_SYNC_P2P, transaction); + if (transaction == nullptr) { + goto END; + } + errCode = transaction->StartTransaction(); + if (errCode != E_OK) { + goto END; + } + + errCode = CompareVerDataAndLog(transaction); + if (errCode != E_OK) { + LOGE("Compare the version data and log failed:%d", errCode); + transaction->RollBackTransaction(); + goto END; + } + errCode = transaction->CommitTransaction(); +END: + if (transaction != nullptr) { + multiVerData_->ReleaseTransaction(transaction); + transaction = nullptr; + } + return errCode; +} + +int MultiVerNaturalStore::CompareVerDataAndLog(IKvDBMultiVerTransaction *transaction) const +{ + // Get the latest local version, we only care the local data. + Version maxLocalVersion = 0; + int errCode = transaction->GetMaxVersion(MultiVerDataType::NATIVE_TYPE, maxLocalVersion); + if (errCode != E_OK) { + return errCode; + } + + CommitID headerId = commitHistory_->GetHeader(errCode); + if (errCode != E_OK) { + return errCode; + } + + if (headerId.empty()) { + if (maxLocalVersion != 0) { + return transaction->ClearEntriesByVersion(maxLocalVersion); + } + return E_OK; + } + + IKvDBCommit *commitHead = commitHistory_->GetCommit(headerId, errCode); + if (commitHead == nullptr) { + return errCode; + } + + // compare the version + if (commitHead->GetCommitVersion() < maxLocalVersion) { + LOGD("Delete entries"); + errCode = transaction->ClearEntriesByVersion(maxLocalVersion); + } else { + errCode = E_OK; + } + + commitHistory_->ReleaseCommit(commitHead); + commitHead = nullptr; + return errCode; +} + +Version MultiVerNaturalStore::GetMaxCommitVersion() const +{ + return maxCommitVersion_; +} + +void MultiVerNaturalStore::SetMaxCommitVersion(const Version &version) +{ + maxCommitVersion_ = (version > maxCommitVersion_) ? version : maxCommitVersion_; +} + +MultiVerStorageExecutor *MultiVerNaturalStore::GetHandle(bool isWrite, int &errCode, + bool isTrimming, OperatePerm perm) const +{ + if (multiVerEngine_ == nullptr) { + errCode = -E_INVALID_DB; + return nullptr; + } + + if (isWrite && !isTrimming) { + // stop the trimming + shadowTrimmer_.Pause(GetStringIdentifier()); + } + StorageExecutor *handle = nullptr; + if (isTrimming) { + handle = multiVerEngine_->FindExecutor(isWrite, OperatePerm::NORMAL_PERM, errCode, 0); + } else { + handle = multiVerEngine_->FindExecutor(isWrite, perm, errCode); + } + + if (handle == nullptr) { + if (isWrite && !isTrimming) { + // restart the trimming + shadowTrimmer_.Continue(GetStringIdentifier(), false); + } + } else { + if (!handle->GetWritable() && isTrimming) { + static_cast(handle)->InitCurrentReadVersion(); + } + } + return static_cast(handle); +} + +void MultiVerNaturalStore::ReleaseHandle(MultiVerStorageExecutor *&handle, bool isTrimming) const +{ + if (multiVerEngine_ == nullptr || handle == nullptr) { + return; + } + bool isCorrupted = handle->GetCorruptedStatus(); + bool isWrite = handle->GetWritable(); + StorageExecutor *databaseHandle = handle; + multiVerEngine_->Recycle(databaseHandle); + handle = nullptr; + if (isCorrupted) { + CorruptNotify(); + } + if (isWrite && !isTrimming) { + // restart the trimming. + LOGI("Release handle and continue vacuum data!"); + shadowTrimmer_.Continue(GetStringIdentifier(), true); + } +} + +int MultiVerNaturalStore::InitStorageContext(bool isChangeTag) +{ + int errCode = InitStorageContextVersion(isChangeTag); + if (errCode != E_OK) { + return errCode; + } + + maxCommitVersion_ = commitHistory_->GetMaxCommitVersion(errCode); + if (errCode != E_OK) { + LOGE("Get the max commit version failed:%d", errCode); + } + return errCode; +} + +int MultiVerNaturalStore::InitStorageContextVersion(bool isChangeTag) +{ + std::string verFilePath; + int errCode = GetVersionFilePath(MyProp(), verFilePath); + if (errCode != E_OK) { + return errCode; + } + + if (!OS::CheckPathExistence(verFilePath)) { + return CreateNewVersionFile(verFilePath, MULTI_VER_STORE_VERSION_CURRENT, branchTag_); + } + if (isChangeTag) { + return ChangeVersionFile(verFilePath, MULTI_VER_STORE_VERSION_CURRENT, branchTag_, isChangeTag); + } + uint32_t version = 0; + return GetVersionAndTag(verFilePath, version, branchTag_); +} + +void MultiVerNaturalStore::GetCurrentTag(std::vector &tag) const +{ + tag = branchTag_; +} + +void MultiVerNaturalStore::AddVersionConstraintToList(Version version) +{ + std::lock_guard lock(versionConstraintMutex_); + versionConstraints_.insert(version); +} + +void MultiVerNaturalStore::RemoveVersionConstraintFromList(Version version) +{ + std::lock_guard lock(versionConstraintMutex_); + auto iter = versionConstraints_.find(version); + if (iter != versionConstraints_.end()) { + versionConstraints_.erase(iter); + // Auto launch the vacuum. + shadowTrimmer_.AutoRelaunchOnce(GetStringIdentifier()); + } +} + +Version MultiVerNaturalStore::GetMaxTrimmableVersion() const +{ + std::lock_guard lock(versionConstraintMutex_); + if (versionConstraints_.empty()) { + return UINT64_MAX; + } + return *(versionConstraints_.begin()); +} + +int MultiVerNaturalStore::TransObserverTypeToRegisterFunctionType(int observerType, RegisterFuncType &type) const +{ + if (observerType == static_cast(NATURAL_STORE_COMMIT_EVENT)) { + type = OBSERVER_MULTI_VERSION_NS_COMMIT_EVENT; + return E_OK; + } + return -E_NOT_SUPPORT; +} + +const KvDBProperties &MultiVerNaturalStore::GetDbProperties() const +{ + return GetMyProperties(); +} + +int MultiVerNaturalStore::RemoveKvDB(const KvDBProperties &properties) +{ + std::string storeOnlyDir; + std::string storeDir; + GenericKvDB::GetStoreDirectory(properties, KvDBProperties::MULTI_VER_TYPE, storeDir, storeOnlyDir); + int errCodeVersion = KvDBUtils::RemoveKvDB(storeDir, storeOnlyDir, DBConstant::MULTI_VER_DATA_STORE); + int errCodeCommit = KvDBUtils::RemoveKvDB(storeDir, storeOnlyDir, DBConstant::MULTI_VER_COMMIT_STORE); + int errCodeValue = KvDBUtils::RemoveKvDB(storeDir, storeOnlyDir, DBConstant::MULTI_VER_VALUE_STORE); + int errCodeMeta = KvDBUtils::RemoveKvDB(storeDir, storeOnlyDir, DBConstant::MULTI_VER_META_STORE); + LOGD("Delete the versionStorage:%d, commitStorage:%d, valueStorage:%d, metaStorage:%d", + errCodeVersion, errCodeCommit, errCodeValue, errCodeMeta); + DBCommon::RemoveAllFilesOfDirectory(storeDir, true); + DBCommon::RemoveAllFilesOfDirectory(storeOnlyDir, true); + if (errCodeVersion == E_OK && errCodeCommit == E_OK) { + return E_OK; + } + if (errCodeVersion == -E_NOT_FOUND && errCodeCommit == -E_NOT_FOUND) { + return -E_NOT_FOUND; + } + if (errCodeVersion == E_OK && errCodeCommit == -E_NOT_FOUND) { + return E_OK; + } + if (errCodeVersion == -E_NOT_FOUND && errCodeCommit == E_OK) { + return E_OK; + } + return errCodeCommit; +} + +int MultiVerNaturalStore::GetKvDBSize(const KvDBProperties &properties, uint64_t &size) const +{ + std::string storeOnlyDir; + std::string storeDir; + GenericKvDB::GetStoreDirectory(properties, KvDBProperties::MULTI_VER_TYPE, storeDir, storeOnlyDir); + + std::vector storageNames = { + DBConstant::MULTI_VER_DATA_STORE, + DBConstant::MULTI_VER_COMMIT_STORE, + DBConstant::MULTI_VER_VALUE_STORE, + DBConstant::MULTI_VER_META_STORE + }; + + // there only calculate db related file size + for (const auto &storageName : storageNames) { + uint64_t dbSize = 0; + int errCode = KvDBUtils::GetKvDbSize(storeDir, storeOnlyDir, storageName, dbSize); + if (errCode == E_OK) { + size += dbSize; + continue; + } + + if (errCode == -E_NOT_FOUND) { + return -E_NOT_FOUND; + } + + size = 0; + return errCode; + } + return E_OK; +} + +KvDBProperties &MultiVerNaturalStore::GetDbPropertyForUpdate() +{ + return MyProp(); +} + +int MultiVerNaturalStore::CheckVersion(const KvDBProperties &kvDBProp) const +{ + LOGD("[MultiVerStore][CheckVer] Current Overall Version: %u.", MULTI_VER_STORE_VERSION_CURRENT); + bool isVerFileExist = false; + int errCode = CheckOverallVersionViaVersionFile(kvDBProp, isVerFileExist); + if (errCode != E_OK) { + return errCode; + } + bool isSubStorageExist = false; + errCode = CheckSubStorageVersion(kvDBProp, isSubStorageExist); + if (errCode != E_OK) { + return errCode; + } + if (isVerFileExist != isSubStorageExist) { + LOGW("[MultiVerStore][CheckVer] Detect File Lost, isVerFileExist=%d, isSubStorageExist=%d.", + isVerFileExist, isSubStorageExist); + } + return E_OK; +} + +int MultiVerNaturalStore::CheckOverallVersionViaVersionFile(const KvDBProperties &kvDBProp, bool &isVerFileExist) const +{ + std::string verFilePath; + int errCode = GetVersionFilePath(kvDBProp, verFilePath); + if (errCode != E_OK) { + return errCode; + } + // Absent of version file may because: 1: Newly created database; 2: An already created database lost version file. + // In both case, we returned E_OK here. After each sub storage be successfully open and upgrade, create verFile. + if (!OS::CheckPathExistence(verFilePath)) { + LOGD("[MultiVerStore][CheckOverVer] No Version File."); + isVerFileExist = false; + return E_OK; + } + isVerFileExist = true; + + uint32_t overallVersion = 0; + std::vector branchTagInVerFile; + errCode = GetVersionAndTag(verFilePath, overallVersion, branchTagInVerFile); + if (errCode != E_OK) { + LOGE("[MultiVerStore][CheckOverVer] GetVersionAndTag fail, errCode=%d.", errCode); + return errCode; + } + LOGD("[MultiVerStore][CheckOverVer] overallVersion=%u, tag=%s.", overallVersion, VEC_TO_STR(branchTagInVerFile)); + if (overallVersion > MULTI_VER_STORE_VERSION_CURRENT) { + LOGE("[MultiVerStore][CheckOverVer] Version Not Support!"); + return -E_VERSION_NOT_SUPPORT; + } + return E_OK; +} + +int MultiVerNaturalStore::GetVersionFilePath(const KvDBProperties &kvDBProp, std::string &outPath) const +{ + std::string verFiledir; + int errCode = GetWorkDir(kvDBProp, verFiledir); + if (errCode != E_OK) { + LOGE("[MultiVerStore][GetVerFilePath] GetWorkDir fail, errCode=%d", errCode); + return errCode; + } + outPath = verFiledir + "/" + DBConstant::MULTI_SUB_DIR + "/version"; + return E_OK; +} + +int MultiVerNaturalStore::DeleteMetaDataByPrefixKey(const Key &keyPrefix) const +{ + (void)keyPrefix; + return -E_NOT_SUPPORT; +} + +void MultiVerNaturalStore::SetDataInterceptor(const PushDataInterceptor &interceptor) +{ + (void)interceptor; + return; +} + +DEFINE_OBJECT_TAG_FACILITIES(MultiVerNaturalStore) +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_natural_store.h b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store.h new file mode 100644 index 00000000..28145b1d --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store.h @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_NATURAL_STORE_H +#define MULTI_VER_NATURAL_STORE_H + +#ifndef OMIT_MULTI_VER +#include "sync_able_kvdb.h" +#include "multi_ver_kvdb_sync_interface.h" +#include "kv_store_changed_data.h" +#include "ikvdb_multi_ver_data_storage.h" +#include "ikvdb_commit_storage.h" +#include "macro_utils.h" +#include "multi_ver_kvdata_storage.h" +#include "multi_ver_storage_executor.h" +#include "multi_ver_storage_engine.h" +#include "multi_ver_vacuum.h" + +namespace DistributedDB { +enum NaturalStoreNotificationEventType { + NATURAL_STORE_COMMIT_EVENT = 0 +}; +class MultiVerVacuumExecutorImpl; +class MultiVerNaturalStore final: public SyncAbleKvDB, public MultiVerKvDBSyncInterface { +public: + MultiVerNaturalStore(); + ~MultiVerNaturalStore() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(MultiVerNaturalStore); + + // Open the database + int Open(const KvDBProperties &kvDBProp) override; + + // Invoked automatically when connection count is zero + void Close() override; + + // Create a connection object. + GenericKvDBConnection *NewConnection(int &errCode) override; + + // Get interface for syncer. + IKvDBSyncInterface *GetSyncInterface() override; + + // Get interface type of this kvdb. + int GetInterfaceType() const override; + + // Get the interface ref-count, in order to access asynchronously. + void IncRefCount() override; + + // Drop the interface ref-count. + void DecRefCount() override; + + // Get the identifier of this kvdb. + std::vector GetIdentifier() const override; + + // Get the max timestamp of all entries in database. + void GetMaxTimestamp(Timestamp &stamp) const override; + + // Get meta data associated with the given key. + int GetMetaData(const Key &key, Value &value) const override; + + // Put meta data as a key-value entry. + int PutMetaData(const Key &key, const Value &value) override; + + // Delete multiple meta data records in a transaction. + int DeleteMetaData(const std::vector &keys) override; + // Delete multiple meta data records with key prefix in a transaction. + int DeleteMetaDataByPrefixKey(const Key &keyPrefix) const override; + + // Get all meta data keys. + int GetAllMetaKeys(std::vector &keys) const override; + + bool IsCommitExisted(const MultiVerCommitNode &commit) const override; + + int GetDeviceLatestCommit(std::map &) const override; + + int GetCommitTree(const std::map &, + std::vector &) const override; + + int GetCommitData(const MultiVerCommitNode &commit, std::vector &entries) const override; + + MultiVerKvEntry *CreateKvEntry(const std::vector &data) override; + + void ReleaseKvEntry(const MultiVerKvEntry *entry) override; + + bool IsValueSliceExisted(const ValueSliceHash &value) const override; + + int GetValueSlice(const ValueSliceHash &hashValue, ValueSlice &sliceValue) const override; + + int PutValueSlice(const ValueSliceHash &hashValue, const ValueSlice &sliceValue) const override; + + int PutCommitData(const MultiVerCommitNode &commit, const std::vector &entries, + const std::string &deviceName) override; + + int MergeSyncCommit(const MultiVerCommitNode &commit, const std::vector &commits) override; + + void NotifyStartSyncOperation() override; + + void NotifyFinishSyncOperation() override; + + int TransferSyncCommitDevInfo(MultiVerCommitNode &commit, const std::string &devId, bool isSyncedIn) const override; + + int Rekey(const CipherPassword &passwd) override; + + int Export(const std::string &filePath, const CipherPassword &passwd) override; + + int Import(const std::string &filePath, const CipherPassword &passwd) override; + + int GetDiffEntries(const CommitID &begin, const CommitID &end, MultiVerDiffData &data) const; + + uint64_t GetCurrentTimestamp(); + + // Set the max timestamp + void SetMaxTimestamp(Timestamp stamp); + + Version GetMaxCommitVersion() const; + + void SetMaxCommitVersion(const Version &version); + + MultiVerStorageExecutor *GetHandle(bool isWrite, int &errCode, + bool isTrimming = false, OperatePerm perm = OperatePerm::NORMAL_PERM) const; + + void ReleaseHandle(MultiVerStorageExecutor *&handle, bool isTrimming = false) const; + + void GetCurrentTag(std::vector &tag) const; + + // Just provide the version constraint for trimmming data(include observer and the snapshot) + void AddVersionConstraintToList(Version version); + + void RemoveVersionConstraintFromList(Version version); + + // Get the max trimmable version, if no need trimming, return 0; if need trimming all return the MAX_UINT64. + Version GetMaxTrimmableVersion() const; + + int TransObserverTypeToRegisterFunctionType(int observerType, RegisterFuncType &type) const override; + + const KvDBProperties &GetDbProperties() const override; + + int RemoveKvDB(const KvDBProperties &properties) override; + + int GetKvDBSize(const KvDBProperties &properties, uint64_t &size) const override; + + KvDBProperties &GetDbPropertyForUpdate(); + + int InitStorages(const KvDBProperties &kvDBProp, bool isChangeTag = false); + + void SetDataInterceptor(const PushDataInterceptor &interceptor) override; + +private: + + int CheckSubStorageVersion(const KvDBProperties &kvDBProp, bool &isSubStorageAllExist) const; + + int CreateStorages(); + + int CreateStoreDirectory(const std::string &directory, const std::string &identifierName); + + void Clear(); + + int RecoverFromException(); + + int CompareVerDataAndLog(IKvDBMultiVerTransaction *transaction) const; + + int ClearTempFile(const KvDBProperties &kvDBProp); + + int InitStorageContext(bool isChangeTag); + + int InitStorageContextVersion(bool isChangeTag); + + std::string GetStringIdentifier() const; + + int CheckVersion(const KvDBProperties &kvDBProp) const; + + int CheckOverallVersionViaVersionFile(const KvDBProperties &kvDBProp, bool &isVerFileExist) const; + + int GetVersionFilePath(const KvDBProperties &kvDBProp, std::string &outPath) const; + + DECLARE_OBJECT_TAG(MultiVerNaturalStore); + + static MultiVerVacuum shadowTrimmer_; + IKvDBMultiVerDataStorage *multiVerData_; + IKvDBCommitStorage *commitHistory_; + MultiVerKvDataStorage *multiVerKvStorage_; + std::unique_ptr multiVerEngine_; + MultiVerVacuumExecutorImpl *trimmerImpl_; + mutable std::mutex commitHistMutex_; + mutable std::mutex multiDataMutex_; + mutable std::mutex syncerKvMutex_; + mutable std::mutex maxTimeMutex_; + mutable std::mutex versionConstraintMutex_; + mutable uint64_t maxRecordTimestamp_; + Version maxCommitVersion_; + std::vector branchTag_; + std::multiset versionConstraints_; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_NATURAL_STORE_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_notify_data.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_notify_data.cpp new file mode 100644 index 00000000..2ece9cb4 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_notify_data.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_natural_store_commit_notify_data.h" +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +MultiVerNaturalStoreCommitNotifyData::MultiVerNaturalStoreCommitNotifyData(MultiVerNaturalStore *db, + const CommitID &startCommitID, const CommitID &endCommitID, Version curVersion) + : db_(db), + startCommitID_(startCommitID), + endCommitID_(endCommitID), + isFilled_(false), + version_(curVersion) +{} + +MultiVerNaturalStoreCommitNotifyData::~MultiVerNaturalStoreCommitNotifyData() +{ + if (db_ != nullptr) { + db_->RemoveVersionConstraintFromList(version_); + } + + db_ = nullptr; +} + +const std::list MultiVerNaturalStoreCommitNotifyData::GetInsertedEntries(int &errCode) const +{ + errCode = FillInnerData(); + if (errCode != E_OK) { + LOGE("Failed to fill inner data in GetInsertedEntries(), err:%d", errCode); + } + return diffData_.inserted; +} + +const std::list MultiVerNaturalStoreCommitNotifyData::GetUpdatedEntries(int &errCode) const +{ + errCode = FillInnerData(); + if (errCode != E_OK) { + LOGE("Failed to fill inner data in GetUpdatedEntries(), err:%d", errCode); + } + return diffData_.updated; +} + +const std::list MultiVerNaturalStoreCommitNotifyData::GetDeletedEntries(int &errCode) const +{ + errCode = FillInnerData(); + if (errCode != E_OK) { + LOGE("Failed to fill inner data in GetDeletedEntries(), err:%d", errCode); + } + return diffData_.deleted; +} + +bool MultiVerNaturalStoreCommitNotifyData::IsCleared() const +{ + int errCode = FillInnerData(); + if (errCode != E_OK) { + LOGE("Failed to fill inner data in IsCleared(), err:%d", errCode); + } + return diffData_.isCleared; +} + +bool MultiVerNaturalStoreCommitNotifyData::IsChangedDataEmpty() const +{ + int errCode = FillInnerData(); + if (errCode != E_OK) { + LOGE("Failed to fill inner data in IsEmpty(), err:%d", errCode); + } + return !diffData_.isCleared && + diffData_.inserted.empty() && + diffData_.updated.empty() && + diffData_.deleted.empty(); +} + +int MultiVerNaturalStoreCommitNotifyData::FillInnerData() const +{ + std::lock_guard lock(fillMutex_); + if (isFilled_) { + return E_OK; + } + if (db_ == nullptr) { + LOGE("Failed to fill inner data, db is nullptr"); + return -E_INVALID_DB; + } + + int errCode = db_->GetDiffEntries(startCommitID_, endCommitID_, diffData_); + if (errCode != E_OK) { + LOGE("Failed to get diff entries when filling inner data, err:%d", errCode); + return errCode; + } + isFilled_ = true; + return E_OK; +} + +DEFINE_OBJECT_TAG_FACILITIES(MultiVerNaturalStoreCommitNotifyData) +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_notify_data.h b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_notify_data.h new file mode 100644 index 00000000..90d6ee80 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_notify_data.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_NATURAL_STORE_COMMIT_NOTIFY_DATA_H +#define MULTI_VER_NATURAL_STORE_COMMIT_NOTIFY_DATA_H + +#ifndef OMIT_MULTI_VER +#include + +#include "kvdb_commit_notify_filterable_data.h" +#include "multi_ver_natural_store.h" + +namespace DistributedDB { +class MultiVerNaturalStoreCommitNotifyData final : public KvDBCommitNotifyFilterAbleData { +public: + MultiVerNaturalStoreCommitNotifyData(MultiVerNaturalStore *db, const CommitID &startCommitID, + const CommitID &endCommitID, Version curVersion); + ~MultiVerNaturalStoreCommitNotifyData(); + DISABLE_COPY_ASSIGN_MOVE(MultiVerNaturalStoreCommitNotifyData); + + const std::list GetInsertedEntries(int &errCode) const override; + + const std::list GetUpdatedEntries(int &errCode) const override; + + const std::list GetDeletedEntries(int &errCode) const override; + + bool IsCleared() const override; + + bool IsChangedDataEmpty() const override; + +private: + int FillInnerData() const; + + DECLARE_OBJECT_TAG(MultiVerNaturalStoreCommitNotifyData); + + mutable MultiVerNaturalStore *db_; + CommitID startCommitID_; + CommitID endCommitID_; + mutable MultiVerDiffData diffData_; + mutable bool isFilled_; + mutable std::mutex fillMutex_; + Version version_; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_NATURAL_STORE_COMMIT_NOTIFY_DATA_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_storage.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_storage.cpp new file mode 100644 index 00000000..02133d50 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_storage.cpp @@ -0,0 +1,914 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_natural_store_commit_storage.h" + +#include + +#include "db_errno.h" +#include "db_constant.h" +#include "log_print.h" +#include "multi_ver_commit.h" +#include "ikvdb_factory.h" +#include "parcel.h" +#include "db_common.h" +#include "sqlite_local_kvdb.h" +#include "kvdb_utils.h" + +namespace DistributedDB { +using std::string; +using std::vector; +using std::list; +using std::map; +using std::make_pair; +using std::stack; + +namespace { + const size_t MAX_COMMIT_ST_LENGTH = 4096; + const Version VERSION_MAX = 0xFFFFFFFFFFFFFFFF; + const string MULTI_VER_COMMIT_DB_NAME = "commit_logs.db"; +} + +const string MultiVerNaturalStoreCommitStorage::HEADER_KEY = "header commit"; + +MultiVerNaturalStoreCommitStorage::MultiVerNaturalStoreCommitStorage() + : commitStorageDatabase_(nullptr), + commitStorageDBConnection_(nullptr) +{} + +MultiVerNaturalStoreCommitStorage::~MultiVerNaturalStoreCommitStorage() +{ + Close(); +} + +int MultiVerNaturalStoreCommitStorage::CheckVersion(const Property &property, bool &isDbExist) const +{ + int dbVer = 0; + int errCode = GetVersion(property, dbVer, isDbExist); + if (errCode != E_OK) { + LOGE("[CommitStorage][CheckVer] GetVersion failed, errCode=%d.", errCode); + return errCode; + } + if (!isDbExist) { + return E_OK; + } + LOGD("[CommitStorage][CheckVer] DbVersion=%d, CurVersion=%d.", dbVer, MULTI_VER_COMMIT_STORAGE_VERSION_CURRENT); + if (dbVer > MULTI_VER_COMMIT_STORAGE_VERSION_CURRENT) { + LOGE("[CommitStorage][CheckVer] Version Not Support!"); + return -E_VERSION_NOT_SUPPORT; + } + return E_OK; +} + +int MultiVerNaturalStoreCommitStorage::GetVersion(const IKvDBCommitStorage::Property &property, + int &version, bool &isDbExisted) +{ + SQLiteLocalKvDB *localKvdb = new (std::nothrow) SQLiteLocalKvDB(); + if (localKvdb == nullptr) { + return -E_INVALID_DB; + } + + KvDBProperties dbProperties; + dbProperties.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, property.isNeedCreate); + dbProperties.SetStringProp(KvDBProperties::DATA_DIR, property.path); + dbProperties.SetStringProp(KvDBProperties::FILE_NAME, DBConstant::MULTI_VER_COMMIT_STORE); + dbProperties.SetStringProp(KvDBProperties::IDENTIFIER_DIR, property.identifierName); + dbProperties.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::MULTI_VER_TYPE); + dbProperties.SetPassword(property.cipherType, property.passwd); + + int errCode = localKvdb->GetVersion(dbProperties, version, isDbExisted); + RefObject::DecObjRef(localKvdb); + localKvdb = nullptr; + return errCode; +} + +int MultiVerNaturalStoreCommitStorage::Open(const IKvDBCommitStorage::Property &property) +{ + if (commitStorageDatabase_ != nullptr && commitStorageDBConnection_ != nullptr) { + return E_OK; + } + IKvDBFactory *factory = IKvDBFactory::GetCurrent(); + if (factory == nullptr) { + LOGE("Failed to open IKvDB! Get factory failed."); + return -E_INVALID_DB; + } + int errCode = E_OK; + commitStorageDatabase_ = factory->CreateCommitStorageDB(errCode); + if (commitStorageDatabase_ == nullptr) { + LOGE("Failed to create commit storage database:%d", errCode); + return -E_INVALID_DB; + } + + KvDBProperties dbProperties; + dbProperties.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, property.isNeedCreate); + dbProperties.SetStringProp(KvDBProperties::DATA_DIR, property.path); + dbProperties.SetStringProp(KvDBProperties::FILE_NAME, DBConstant::MULTI_VER_COMMIT_STORE); + dbProperties.SetStringProp(KvDBProperties::IDENTIFIER_DIR, property.identifierName); + dbProperties.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::MULTI_VER_TYPE); + dbProperties.SetPassword(property.cipherType, property.passwd); + + errCode = commitStorageDatabase_->Open(dbProperties); + if (errCode != E_OK) { + LOGE("Failed to open commit storage database! err:%d", errCode); + RefObject::KillAndDecObjRef(commitStorageDatabase_); + commitStorageDatabase_ = nullptr; + return errCode; + } + commitStorageDBConnection_ = commitStorageDatabase_->GetDBConnection(errCode); + if (commitStorageDBConnection_ == nullptr) { + LOGE("Failed to get connection for commit storage! err:%d", errCode); + RefObject::KillAndDecObjRef(commitStorageDatabase_); + commitStorageDatabase_ = nullptr; + return errCode; + } + // Need to refactor in the future + errCode = static_cast(commitStorageDatabase_)->SetVersion(dbProperties, + MULTI_VER_COMMIT_STORAGE_VERSION_CURRENT); + if (errCode != E_OK) { + LOGE("[CommitStorage][Open] SetVersion fail, errCode=%d.", errCode); + Close(); + return errCode; + } + return E_OK; +} + +void MultiVerNaturalStoreCommitStorage::Close() +{ + if (commitStorageDatabase_ != nullptr && commitStorageDBConnection_ != nullptr) { + commitStorageDBConnection_->Close(); + commitStorageDBConnection_ = nullptr; + } + if (commitStorageDatabase_ != nullptr) { + IKvDB::DecObjRef(commitStorageDatabase_); + commitStorageDatabase_ = nullptr; + } +} + +int MultiVerNaturalStoreCommitStorage::Remove(const IKvDBCommitStorage::Property &property) +{ + if (commitStorageDatabase_ != nullptr && commitStorageDBConnection_ != nullptr) { + commitStorageDBConnection_->Close(); + commitStorageDBConnection_ = nullptr; + RefObject::DecObjRef(commitStorageDatabase_); + commitStorageDatabase_ = nullptr; + } + + std::string dataDir = property.path + ("/" + property.identifierName + "/" + DBConstant::MULTI_SUB_DIR + "/"); + int errCode = KvDBUtils::RemoveKvDB(dataDir, DBConstant::MULTI_VER_COMMIT_STORE); + if (errCode != E_OK) { + LOGE("Failed to remove commit storage database! err:%d", errCode); + return errCode; + } + return E_OK; +} + +IKvDBCommit *MultiVerNaturalStoreCommitStorage::AllocCommit(int &errCode) const +{ + auto commit = new (std::nothrow) MultiVerCommit(); + if (commit != nullptr) { + errCode = E_OK; + } else { + errCode = -E_OUT_OF_MEMORY; + LOGE("Failed to alloc commit! Bad alloc."); + } + return commit; +} + +IKvDBCommit *MultiVerNaturalStoreCommitStorage::GetCommit(const CommitID &commitId, int &errCode) const +{ + if (commitStorageDatabase_ == nullptr || commitStorageDBConnection_ == nullptr) { + LOGE("Failed to get commit! Commit storage do not open."); + errCode = -E_INVALID_DB; + return nullptr; + } + Key key; + TransferCommitIDToKey(commitId, key); + IOption option; + Value value; + errCode = commitStorageDBConnection_->Get(option, key, value); + if (errCode != E_OK) { + if (errCode != -E_NOT_FOUND) { + LOGE("Failed to get the commit:%d", errCode); + } + return nullptr; + } + + IKvDBCommit *commit = AllocCommit(errCode); + if (commit == nullptr) { + return nullptr; + } + + errCode = TransferValueToCommit(value, *commit); + if (errCode != E_OK) { + delete commit; + commit = nullptr; + } + return commit; +} + +int MultiVerNaturalStoreCommitStorage::StartVacuum() +{ + if (commitStorageDBConnection_ == nullptr) { + LOGE("commitStorage Connection not existed!"); + return -E_INVALID_CONNECTION; + } + return commitStorageDBConnection_->StartTransaction(); +} + +int MultiVerNaturalStoreCommitStorage::CancelVacuum() +{ + if (commitStorageDBConnection_ == nullptr) { + LOGE("commitStorage Connection not existed!"); + return -E_INVALID_CONNECTION; + } + return commitStorageDBConnection_->RollBack(); +} + +int MultiVerNaturalStoreCommitStorage::FinishlVacuum() +{ + if (commitStorageDBConnection_ == nullptr) { + LOGE("commitStorage Connection not existed!"); + return -E_INVALID_CONNECTION; + } + return commitStorageDBConnection_->Commit(); +} + +int MultiVerNaturalStoreCommitStorage::GetAllCommitsInTree(std::list &commits) const +{ + std::map commitsTable; + CommitID headerId; + int errCode = GetAllCommits(commitsTable, headerId); + if (errCode != E_OK || commitsTable.empty()) { // error or no commit. + return errCode; + } + + std::stack commitStack; + commitStack.push(headerId); + while (!commitStack.empty()) { + auto currentCommitIter = commitsTable.find(commitStack.top()); + if (currentCommitIter == commitsTable.end()) { + // not found the node in the commit tree. + commits.clear(); + errCode = -E_UNEXPECTED_DATA; + break; + } + + commitStack.pop(); + if (currentCommitIter->second == nullptr) { + // if the node has been released or traveled. + continue; + } + + AddParentsToStack(currentCommitIter->second, commitsTable, commitStack); + MultiVerCommitNode commitNode; + // Get the current commit info + commitNode.commitId = currentCommitIter->first; + commitNode.deviceInfo = currentCommitIter->second->GetDeviceInfo(); + commitNode.isLocal = (currentCommitIter->second->GetLocalFlag() ? + MultiVerCommitNode::LOCAL_FLAG : MultiVerCommitNode::NON_LOCAL_FLAG); + commitNode.leftParent = currentCommitIter->second->GetLeftParentId(); + commitNode.rightParent = currentCommitIter->second->GetRightParentId(); + commitNode.timestamp = currentCommitIter->second->GetTimestamp(); + commitNode.version = currentCommitIter->second->GetCommitVersion(); + commits.push_back(commitNode); + + ReleaseCommit(currentCommitIter->second); + currentCommitIter->second = nullptr; // has been traveled, set to nullptr. + } + + commits.sort([] (const MultiVerCommitNode &thisNode, const MultiVerCommitNode &thatNode) { + return (thisNode.version > thatNode.version); + }); + ReleaseUnusedCommits(commitsTable); + + return errCode; +} + +int MultiVerNaturalStoreCommitStorage::AddCommit(const IKvDBCommit &commitEntry, bool isHeader) +{ + int errCode = CheckAddedCommit(commitEntry); + if (errCode != E_OK) { + return errCode; + } + + Key key; + TransferCommitIDToKey(commitEntry.GetCommitId(), key); + Value value; + errCode = TransferCommitToValue(commitEntry, value); + if (errCode != E_OK) { + return errCode; + } + IOption option; + errCode = commitStorageDBConnection_->StartTransaction(); + if (errCode != E_OK) { + return errCode; + } + + errCode = commitStorageDBConnection_->Put(option, key, value); + if (errCode != E_OK) { + goto END; + } + + if (isHeader) { + errCode = SetHeaderInner(commitEntry.GetCommitId()); + } +END: + if (errCode != E_OK) { + commitStorageDBConnection_->RollBack(); + } else { + errCode = commitStorageDBConnection_->Commit(); + } + + return errCode; +} + +int MultiVerNaturalStoreCommitStorage::RemoveCommit(const CommitID &commitId) +{ + if (commitStorageDatabase_ == nullptr || commitStorageDBConnection_ == nullptr) { + LOGE("Failed to get commit! Commit storage do not open."); + return -E_INVALID_DB; + } + int errCode = commitStorageDBConnection_->StartTransaction(); + if (errCode != E_OK) { + LOGE("Failed to remove commit when start transaction! err:%d", errCode); + return errCode; + } + Key key; + IOption option; + CommitID header = GetHeader(errCode); + if (header == commitId) { + IKvDBCommit *commit = GetCommit(commitId, errCode); + if (commit == nullptr) { + LOGE("Failed to remove commit when get header commit! err:%d", errCode); + goto ERROR; + } + errCode = SetHeader(commit->GetLeftParentId()); + ReleaseCommit(commit); + commit = nullptr; + if (errCode != E_OK) { + LOGE("Failed to remove commit when set header commit! err:%d", errCode); + goto ERROR; + } + } else { + LOGE("Failed to remove commit! The commit is not the header."); + errCode = -E_UNEXPECTED_DATA; + goto ERROR; + } + TransferCommitIDToKey(commitId, key); + errCode = commitStorageDBConnection_->Delete(option, key); + if (errCode != E_OK) { + LOGI("Failed to remove commit when remove commit! err:%d", errCode); + goto ERROR; + } + errCode = commitStorageDBConnection_->Commit(); + if (errCode != E_OK) { + LOGE("Failed to remove commit when commit! err:%d", errCode); + goto ERROR; + } + return E_OK; +ERROR: + (void)commitStorageDBConnection_->RollBack(); + return errCode; +} + +void MultiVerNaturalStoreCommitStorage::ReleaseCommit(const IKvDBCommit *commit) const +{ + if (commit != nullptr) { + delete commit; + commit = nullptr; + } +} + +int MultiVerNaturalStoreCommitStorage::SetHeader(const CommitID &commitId) +{ + if (commitStorageDatabase_ == nullptr || commitStorageDBConnection_ == nullptr) { + LOGE("Failed to get commit! Commit storage do not open."); + return -E_INVALID_DB; + } + + if (commitId.size() != 0) { + int errCode = E_OK; + if (!CommitExist(commitId, errCode)) { + LOGE("Failed to set header! The commit does not exist."); + return errCode; + } + } + + return SetHeaderInner(commitId); +} + +CommitID MultiVerNaturalStoreCommitStorage::GetHeader(int &errCode) const +{ + CommitID headerCommitID; + if (commitStorageDatabase_ == nullptr || commitStorageDBConnection_ == nullptr) { + LOGE("Failed to get commit for uninitialized store"); + errCode = -E_INVALID_DB; + return headerCommitID; + } + Key key; + TransferStringToKey(HEADER_KEY, key); + IOption option; + Value value; + errCode = commitStorageDBConnection_->Get(option, key, value); + if (errCode != E_OK) { + if (errCode == -E_NOT_FOUND) { // not find the header, means no header. + LOGI("Not find the header."); + errCode = E_OK; + } else { + LOGE("Get the commit header failed:%d", errCode); + return headerCommitID; + } + } + TransferValueToCommitID(value, headerCommitID); + return headerCommitID; +} + +bool MultiVerNaturalStoreCommitStorage::CommitExist(const CommitID &commitId, int &errCode) const +{ + IKvDBCommit *commit = GetCommit(commitId, errCode); + if (commit == nullptr) { + return false; + } else { + ReleaseCommit(commit); + commit = nullptr; + return true; + } +} + +void MultiVerNaturalStoreCommitStorage::ReleaseUnusedCommits( + std::map &commits) const +{ + // need release the unmerged commits + for (auto &item : commits) { + if (item.second != nullptr) { + ReleaseCommit(item.second); + item.second = nullptr; + } + } + commits.clear(); +} + +void MultiVerNaturalStoreCommitStorage::ReleaseLatestCommits( + std::map &latestCommits) const +{ + // need release the commits for exception. + for (auto &item : latestCommits) { + if (item.second != nullptr) { + ReleaseCommit(item.second); + item.second = nullptr; + } + } + latestCommits.clear(); +} + +void MultiVerNaturalStoreCommitStorage::ReleaseCommitList(list &commits) const +{ + for (auto &item : commits) { + if (item != nullptr) { + ReleaseCommit(item); + item = nullptr; + } + } + commits.clear(); +} + +int MultiVerNaturalStoreCommitStorage::GetLatestCommits(std::map &latestCommits) const +{ + latestCommits.clear(); + map commits; + CommitID headerId; + int errCode = GetAllCommits(commits, headerId); + if (errCode != E_OK || commits.empty()) { // error or no commit. + return errCode; + } + + std::stack commitStack; + commitStack.push(headerId); + while (!commitStack.empty()) { + CommitID frontId = commitStack.top(); + auto currentCommitIter = commits.find(frontId); + if (currentCommitIter == commits.end()) { + // not found the node in the commit tree. + LOGE("Not found the commit for the latest commits!"); + ReleaseLatestCommits(latestCommits); + errCode = -E_UNEXPECTED_DATA; + break; + } + + commitStack.pop(); + if (currentCommitIter->second == nullptr) { + // if the node has been released or traveled. + continue; + } + + AddParentsToStack(currentCommitIter->second, commits, commitStack); + + // Get the current commit info + DeviceID deviceInfo = currentCommitIter->second->GetDeviceInfo(); + auto latestCommit = latestCommits.find(deviceInfo); + if (latestCommit == latestCommits.end()) { + // not found any node of the device in the commit tree. + latestCommits.insert(make_pair(deviceInfo, currentCommitIter->second)); + } else if (CompareCommit(latestCommit->second, currentCommitIter->second)) { + // if the current commit version is bigger than the stored. + ReleaseCommit(latestCommit->second); + latestCommit->second = currentCommitIter->second; + } else { + ReleaseCommit(currentCommitIter->second); + } + currentCommitIter->second = nullptr; // has been traveled, set to nullptr. + } + + ReleaseUnusedCommits(commits); + return errCode; +} + +void MultiVerNaturalStoreCommitStorage::GetLocalVersionThredForLatestCommits( + const map &allCommits, const map &latestCommits, + map &latestCommitVersions) +{ + for (const auto &latestCommit : latestCommits) { + auto commitIter = allCommits.find(latestCommit.second); + if (commitIter != allCommits.end()) { + // found in the local store, just set the threshold. + latestCommitVersions.insert(make_pair(latestCommit.first, commitIter->second->GetCommitVersion())); + } else { + // not found in the local store, means that newer than local. + latestCommitVersions.insert(make_pair(latestCommit.first, VERSION_MAX)); + } + } +} + +void MultiVerNaturalStoreCommitStorage::AddParentsToStack(const IKvDBCommit *commit, + const std::map &allCommits, std::stack &commitStack) +{ + if (commit == nullptr) { + return; + } + + auto leftParentId = commit->GetLeftParentId(); + auto rightParentId = commit->GetRightParentId(); + if (!rightParentId.empty()) { + auto iter = allCommits.find(rightParentId); + if (iter != allCommits.end() && iter->second != nullptr) { + // if the right parent has not been traveled, just push into the stack. + commitStack.push(rightParentId); + } + } + if (!leftParentId.empty()) { + auto iter = allCommits.find(leftParentId); + if (iter != allCommits.end() && iter->second != nullptr) { + // if the left parent has not been traveled, just push into the stack. + commitStack.push(leftParentId); + } + } +} + +int MultiVerNaturalStoreCommitStorage::GetCommitTree(const map &latestCommits, + list &commits) const +{ + commits.clear(); + CommitID header; + map allCommits; + int errCode = GetAllCommits(allCommits, header); + // error or no commit. + if (errCode != E_OK || allCommits.empty()) { + return errCode; + } + map latestCommitVersions; + GetLocalVersionThredForLatestCommits(allCommits, latestCommits, latestCommitVersions); + std::stack commitStack; + commitStack.push(header); + while (!commitStack.empty()) { + CommitID frontId = commitStack.top(); + auto currentCommitIter = allCommits.find(frontId); + if (currentCommitIter == allCommits.end()) { + // not found the node in the commit tree. + LOGE("Not found the commit in the local tree!"); + ReleaseCommitList(commits); + errCode = -E_UNEXPECTED_DATA; + break; + } + commitStack.pop(); + if (currentCommitIter->second == nullptr) { + // if the commit has been traveled. + continue; + } + AddParentsToStack(currentCommitIter->second, allCommits, commitStack); + // Get the current commit info + DeviceID deviceInfo = currentCommitIter->second->GetDeviceInfo(); + auto latestCommit = latestCommitVersions.find(deviceInfo); + if (latestCommit == latestCommitVersions.end() || + latestCommit->second < currentCommitIter->second->GetCommitVersion()) { + // not found in the latest commits of the other device, + // or the current commit version is bigger than the threshold. + commits.push_back(currentCommitIter->second); + } else { + // means that the commit existed in the other device. + ReleaseCommit(currentCommitIter->second); + } + currentCommitIter->second = nullptr; + } + + ReleaseUnusedCommits(allCommits); + RefreshCommitTree(commits); // for version ascend. + return errCode; +} + +int MultiVerNaturalStoreCommitStorage::RunRekeyLogic(CipherType type, const CipherPassword &passwd) +{ + int errCode = static_cast(commitStorageDatabase_)->RunRekeyLogic(type, passwd); + if (errCode != E_OK) { + LOGE("commit logs rekey failed:%d", errCode); + } + return errCode; +} + +int MultiVerNaturalStoreCommitStorage::RunExportLogic(CipherType type, const CipherPassword &passwd, + const std::string &dbDir) +{ + // execute export + std::string newDbName = dbDir + "/" + MULTI_VER_COMMIT_DB_NAME; + int errCode = static_cast(commitStorageDatabase_)->RunExportLogic(type, passwd, newDbName); + if (errCode != E_OK) { + LOGE("commit logs export failed:%d", errCode); + } + return errCode; +} + +void MultiVerNaturalStoreCommitStorage::TransferCommitIDToKey(const CommitID &commitID, Key &key) +{ + key = commitID; +} + +int MultiVerNaturalStoreCommitStorage::TransferCommitToValue(const IKvDBCommit &commit, Value &value) +{ + // 3 uint64_t members. + uint32_t totalLength = Parcel::GetUInt64Len() * 3 + Parcel::GetVectorCharLen(commit.GetCommitId()) + + Parcel::GetVectorCharLen(commit.GetLeftParentId()) + Parcel::GetVectorCharLen(commit.GetRightParentId()) + + Parcel::GetStringLen(commit.GetDeviceInfo()); + if (totalLength > MAX_COMMIT_ST_LENGTH) { + LOGE("The commit length is over the max threshold"); + return -E_UNEXPECTED_DATA; + } + + value.resize(totalLength); + Parcel parcel(const_cast(value.data()), totalLength); + + int errCode = parcel.WriteUInt64(commit.GetTimestamp()); + if (errCode != E_OK) { + return errCode; + } + + uint64_t localFlag = static_cast((commit.GetLocalFlag() == true) ? 1 : 0); + errCode = parcel.WriteUInt64(localFlag); + if (errCode != E_OK) { + return errCode; + } + + errCode = parcel.WriteUInt64(commit.GetCommitVersion()); + if (errCode != E_OK) { + return errCode; + } + + errCode = parcel.WriteVectorChar(commit.GetCommitId()); + if (errCode != E_OK) { + return errCode; + } + + errCode = parcel.WriteVectorChar(commit.GetLeftParentId()); + if (errCode != E_OK) { + return errCode; + } + + errCode = parcel.WriteVectorChar(commit.GetRightParentId()); + if (errCode != E_OK) { + return errCode; + } + + return parcel.WriteString(commit.GetDeviceInfo()); +} + +int MultiVerNaturalStoreCommitStorage::TransferValueToCommit(const Value &value, IKvDBCommit &commit) +{ + size_t valueLength = value.size(); + if (valueLength == 0 || valueLength >= MAX_COMMIT_ST_LENGTH) { + LOGE("Failed to transfer value to commit struct! invalid value length:%zu.", valueLength); + return -E_UNEXPECTED_DATA; + } + + Timestamp timestamp = 0; + uint64_t localFlag = 1; + Version versionInfo; + + CommitID commitID; + CommitID leftParentID; + CommitID rightParentID; + DeviceID deviceInfo; + + Parcel parcel(const_cast(value.data()), valueLength); + parcel.ReadUInt64(timestamp); + parcel.ReadUInt64(localFlag); + parcel.ReadUInt64(versionInfo); + parcel.ReadVectorChar(commitID); + parcel.ReadVectorChar(leftParentID); + parcel.ReadVectorChar(rightParentID); + parcel.ReadString(deviceInfo); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + + // set commit value + commit.SetCommitVersion(versionInfo); + commit.SetCommitId(commitID); + commit.SetLeftParentId(leftParentID); + commit.SetRightParentId(rightParentID); + commit.SetTimestamp(timestamp); + commit.SetLocalFlag((localFlag == 1) ? true : false); + commit.SetDeviceInfo(deviceInfo); + return E_OK; +} + +void MultiVerNaturalStoreCommitStorage::TransferStringToKey(const string &str, Key &key) +{ + key.assign(str.begin(), str.end()); +} + +void MultiVerNaturalStoreCommitStorage::TransferCommitIDToValue(const CommitID &commitID, Value &value) +{ + value = commitID; +} + +void MultiVerNaturalStoreCommitStorage::TransferValueToCommitID(const Value &value, CommitID &commitID) +{ + commitID = value; +} + +bool MultiVerNaturalStoreCommitStorage::CompareCommit(const IKvDBCommit *first, + const IKvDBCommit *second) +{ + if (first == nullptr || second == nullptr) { + return false; + } + return first->GetCommitVersion() < second->GetCommitVersion(); +} + +int MultiVerNaturalStoreCommitStorage::GetAllCommits(map &commits, + CommitID &headerId) const +{ + if (commitStorageDatabase_ == nullptr || commitStorageDBConnection_ == nullptr) { + LOGE("Failed to get all commits for uninitialized store"); + return -E_INVALID_DB; + } + IOption option; + Key keyPrefix; + vector entries; + int errCode = commitStorageDBConnection_->GetEntries(option, keyPrefix, entries); + if (errCode != E_OK) { + if (errCode == -E_NOT_FOUND) { + errCode = E_OK; + } else { + LOGE("Failed to get commit entries from DB:%d", errCode); + } + + return errCode; + } + + Key header; + TransferStringToKey(HEADER_KEY, header); + + for (const auto &entry : entries) { + if (entry.key == header) { + headerId = entry.value; // get the header. + continue; + } + IKvDBCommit *commit = new (std::nothrow) MultiVerCommit(); + if (commit == nullptr) { + ReleaseUnusedCommits(commits); + LOGE("Failed to alloc commit! Bad alloc."); + return -E_OUT_OF_MEMORY; + } + errCode = TransferValueToCommit(entry.value, *commit); + if (errCode != E_OK) { + delete commit; + commit = nullptr; + ReleaseUnusedCommits(commits); + return errCode; + } + commits.insert(make_pair(commit->GetCommitId(), commit)); + } + return E_OK; +} + +int MultiVerNaturalStoreCommitStorage::SetHeaderInner(const CommitID &commitId) +{ + Key key; + Value value; + TransferStringToKey(HEADER_KEY, key); + TransferCommitIDToValue(commitId, value); + IOption option; + int errCode = commitStorageDBConnection_->Put(option, key, value); + if (errCode != E_OK) { + LOGE("Failed to set header! err:%d", errCode); + } + return errCode; +} + +int MultiVerNaturalStoreCommitStorage::CheckAddedCommit(const IKvDBCommit &commitEntry) const +{ + if (commitStorageDatabase_ == nullptr || commitStorageDBConnection_ == nullptr) { + LOGE("Failed to get commit! Commit storage do not open."); + return -E_INVALID_DB; + } + // Parameter check + if (!((static_cast(commitEntry)).CheckCommit())) { + LOGE("Failed to add commit! Commit is invalid."); + return -E_UNEXPECTED_DATA; + } + int errCode = E_OK; + if (commitEntry.GetLeftParentId().size() != 0) { + if (!CommitExist(commitEntry.GetLeftParentId(), errCode)) { + LOGE("Failed to add commit! The left parent commit does not exist."); + return errCode; + } + } + if (commitEntry.GetRightParentId().size() != 0) { + if (!CommitExist(commitEntry.GetRightParentId(), errCode)) { + LOGE("Failed to add commit! The right parent commit does not exist."); + return errCode; + } + } + + return E_OK; +} + +void MultiVerNaturalStoreCommitStorage::RefreshCommitTree(std::list &commits) +{ + if (commits.empty()) { + return; + } + commits.sort(CompareCommit); +} + +Version MultiVerNaturalStoreCommitStorage::GetMaxCommitVersion(int &errCode) const +{ + std::map commits; + CommitID headerId; + errCode = GetAllCommits(commits, headerId); + if (errCode != E_OK || commits.empty()) { // means no commit or error. + return 0; + } + + Version maxVersion = 0; + for (const auto &item : commits) { + if (item.second != nullptr) { + Version itemVersion = item.second->GetCommitVersion(); + maxVersion = (maxVersion < itemVersion) ? itemVersion : maxVersion; + } + } + ReleaseUnusedCommits(commits); + errCode = E_OK; + return maxVersion; +} + +int MultiVerNaturalStoreCommitStorage::BackupCurrentDatabase(const Property &property, const std::string &dir) +{ + KvDBProperties dbProperties; + dbProperties.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + dbProperties.SetStringProp(KvDBProperties::DATA_DIR, property.path); + dbProperties.SetStringProp(KvDBProperties::FILE_NAME, DBConstant::MULTI_VER_COMMIT_STORE); + dbProperties.SetStringProp(KvDBProperties::IDENTIFIER_DIR, property.identifierName); + dbProperties.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::MULTI_VER_TYPE); + dbProperties.SetPassword(property.cipherType, property.passwd); + int errCode = SQLiteLocalKvDB::BackupCurrentDatabase(dbProperties, dir); + return errCode; +} + +int MultiVerNaturalStoreCommitStorage::ImportDatabase(const Property &property, const std::string &dir, + const CipherPassword &passwd) +{ + KvDBProperties dbProperties; + dbProperties.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + dbProperties.SetStringProp(KvDBProperties::DATA_DIR, property.path); + dbProperties.SetStringProp(KvDBProperties::FILE_NAME, DBConstant::MULTI_VER_COMMIT_STORE); + dbProperties.SetStringProp(KvDBProperties::IDENTIFIER_DIR, property.identifierName); + dbProperties.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::MULTI_VER_TYPE); + dbProperties.SetPassword(property.cipherType, property.passwd); + int errCode = SQLiteLocalKvDB::ImportDatabase(dbProperties, dir, passwd); + return errCode; +} +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_storage.h b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_storage.h new file mode 100644 index 00000000..3a310de4 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_commit_storage.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_NATURAL_STORE_COMMIT_STORAGE_H +#define MULTI_VER_NATURAL_STORE_COMMIT_STORAGE_H + +#ifndef OMIT_MULTI_VER +#include +#include + +#include "db_types.h" +#include "ikvdb.h" +#include "ikvdb_commit_storage.h" +#include "macro_utils.h" + +namespace DistributedDB { +class MultiVerNaturalStoreCommitStorage final : public IKvDBCommitStorage { +public: + MultiVerNaturalStoreCommitStorage(); + ~MultiVerNaturalStoreCommitStorage() override; + + DISABLE_COPY_ASSIGN_MOVE(MultiVerNaturalStoreCommitStorage); + + int CheckVersion(const Property &property, bool &isDbExist) const override; + + int Open(const IKvDBCommitStorage::Property &property) override; + + void Close() override; + + int Remove(const IKvDBCommitStorage::Property &property) override; + + IKvDBCommit *AllocCommit(int &errCode) const override; + + IKvDBCommit *GetCommit(const CommitID &commitId, int &errCode) const override; + + int AddCommit(const IKvDBCommit &commitEntry, bool isHeader) override; + int RemoveCommit(const CommitID &commitId) override; + + void ReleaseCommit(const IKvDBCommit *commit) const override; + + int SetHeader(const CommitID &commitId) override; + CommitID GetHeader(int &errCode) const override; + + bool CommitExist(const CommitID &commitId, int &errCode) const override; + + int GetLatestCommits(std::map &latestCommits) const override; + + Version GetMaxCommitVersion(int &errCode) const override; + + int GetCommitTree(const std::map &latestCommits, + std::list &commits) const override; + + int RunRekeyLogic(CipherType type, const CipherPassword &passwd); + + int RunExportLogic(CipherType type, const CipherPassword &passwd, const std::string &dbDir); + + int BackupCurrentDatabase(const Property &property, const std::string &dir) override; + + int ImportDatabase(const Property &property, const std::string &dir, const CipherPassword &passwd) override; + + int StartVacuum() override; + + int CancelVacuum() override; + + int FinishlVacuum() override; + + int GetAllCommitsInTree(std::list &commits) const override; + +private: + static void TransferCommitIDToKey(const CommitID &commitID, Key &key); + + static int TransferCommitToValue(const IKvDBCommit &commit, Value &value); + static int TransferValueToCommit(const Value &value, IKvDBCommit &commit); + + static void TransferStringToKey(const std::string &str, Key &key); + + static void TransferCommitIDToValue(const CommitID &commitID, Value &value); + static void TransferValueToCommitID(const Value &value, CommitID &commitID); + + static bool CompareCommit(const IKvDBCommit *first, const IKvDBCommit *second); + + static void GetLocalVersionThredForLatestCommits(const std::map &allCommits, + const std::map &latestCommits, std::map &latestCommitVersions); + + static void AddParentsToStack(const IKvDBCommit *commit, + const std::map &allCommits, std::stack &commitStack); + + static void RefreshCommitTree(std::list &commits); + + int GetAllCommits(std::map &commits, CommitID &header) const; + + int SetHeaderInner(const CommitID &commitId); + + int CheckAddedCommit(const IKvDBCommit &commitEntry) const; + + // Release the latest commits for exception. + void ReleaseLatestCommits(std::map &latestCommits) const; + + // Release the untraveled commits + void ReleaseUnusedCommits(std::map &commits) const; + + void ReleaseCommitList(std::list &commits) const; + + static int GetVersion(const IKvDBCommitStorage::Property &property, int &version, bool &isDbExisted); + +private: + static const std::string HEADER_KEY; + std::string branchTag_; + IKvDB *commitStorageDatabase_; + IKvDBConnection *commitStorageDBConnection_; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_NATURAL_STORE_COMMIT_STORAGE_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_connection.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_connection.cpp new file mode 100644 index 00000000..6ba32ddb --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_connection.cpp @@ -0,0 +1,524 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_natural_store_connection.h" + +#include +#include +#include + +#include "log_print.h" +#include "db_errno.h" +#include "db_constant.h" +#include "multi_ver_natural_store_snapshot.h" +#include "multi_ver_natural_store.h" + +namespace DistributedDB { +MultiVerNaturalStoreConnection::MultiVerNaturalStoreConnection(MultiVerNaturalStore *kvDB) + : SyncAbleKvDBConnection(kvDB), + writeHandle_(nullptr) +{} + +MultiVerNaturalStoreConnection::~MultiVerNaturalStoreConnection() +{ + writeHandle_ = nullptr; +} + +// Get the value from the database +int MultiVerNaturalStoreConnection::Get(const IOption &option, const Key &key, Value &value) const +{ + int errCode = CheckDataStatus(key, {}, false); + if (errCode != E_OK) { + return errCode; + } + { + // Only for the read in the write transaction + std::lock_guard lock(writeMutex_); + if (writeHandle_ != nullptr) { + return writeHandle_->Get(key, value); + } + } + + auto handle = GetHandle(false, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->InitCurrentReadVersion(); + if (errCode == E_OK) { + errCode = handle->Get(key, value); + } + + GetDB()->ReleaseHandle(handle); + return errCode; +} + +// Put the value to the database +int MultiVerNaturalStoreConnection::Put(const IOption &option, const Key &key, const Value &value) +{ + bool isAuto = false; + int errCode = CheckDataStatus(key, value, false); + if (errCode != E_OK) { + return errCode; + } + + std::lock_guard lock(writeMutex_); + errCode = StartTransactionInner(isAuto); + if (errCode != E_OK) { + LOGE("Start transaction failed:%d", errCode); + return errCode; + } + + errCode = writeHandle_->Put(key, value); + if (errCode != E_OK) { + LOGE("Put value err:%d", errCode); + if (isAuto) { + (void)(RollBackTransactionInner()); + } + return errCode; + } + + if (isAuto) { + errCode = CommitTransactionInner(); + } + + return errCode; +} + +// Delete the value from the database +int MultiVerNaturalStoreConnection::Delete(const IOption &option, const Key &key) +{ + int errCode = CheckDataStatus(key, {}, true); + if (errCode != E_OK) { + return errCode; + } + bool isAuto = false; + std::lock_guard lock(writeMutex_); + errCode = StartTransactionInner(isAuto); + if (errCode != E_OK) { + LOGE("start transaction to delete failed:%d", errCode); + return errCode; + } + + errCode = writeHandle_->Delete(key); + if (errCode != E_OK) { + if (isAuto) { + int rollbackErrCode = RollBackTransactionInner(); + LOGE("Connection Delete fail, rollback(state:%d) transaction!", rollbackErrCode); + } + return errCode; + } + + if (isAuto) { + errCode = CommitTransactionInner(); + } + return errCode; +} + +// Clear all the data from the database +int MultiVerNaturalStoreConnection::Clear(const IOption &option) +{ + bool isAuto = false; + std::lock_guard lock(writeMutex_); + int errCode = StartTransactionInner(isAuto); + if (errCode != E_OK) { + LOGE("start transaction to clear failed:%d", errCode); + return errCode; + } + + errCode = writeHandle_->Clear(); + if (errCode != E_OK) { + if (isAuto) { + int rollbackErrCode = RollBackTransactionInner(); + LOGD("Connection Clear, rollback(state:%d) transaction!", rollbackErrCode); + } + return errCode; + } + + if (isAuto) { + errCode = CommitTransactionInner(); + } + return errCode; +} + +// Get all the data from the database +int MultiVerNaturalStoreConnection::GetEntries(const IOption &option, + const Key &keyPrefix, std::vector &entries) const +{ + { + std::lock_guard lock(writeMutex_); + if (writeHandle_ != nullptr) { + return writeHandle_->GetEntries(keyPrefix, entries); + } + } + + int errCode = E_OK; + auto handle = GetHandle(false, errCode); + if (handle == nullptr) { + return errCode; + } + errCode = handle->GetEntries(keyPrefix, entries); + GetDB()->ReleaseHandle(handle); + return errCode; +} + +// Put the batch values to the database. +int MultiVerNaturalStoreConnection::PutBatch(const IOption &option, const std::vector &entries) +{ + bool isAuto = false; + if (entries.empty() || entries.size() > DBConstant::MAX_BATCH_SIZE) { + return -E_INVALID_ARGS; + } + for (const auto &item : entries) { + if (CheckDataStatus(item.key, item.value, false) != E_OK) { + return -E_INVALID_ARGS; + } + } + + std::lock_guard lock(writeMutex_); + + // if the transaction is not started auto + int errCode = StartTransactionInner(isAuto); + if (errCode != E_OK) { + LOGE("start transaction failed:%d", errCode); + return errCode; + } + + for (const auto &item : entries) { + errCode = writeHandle_->Put(item.key, item.value); + if (errCode != E_OK) { + if (isAuto) { + (void)(RollBackTransactionInner()); + } + return errCode; + } + } + + if (isAuto) { + errCode = CommitTransactionInner(); + } + return errCode; +} + +// Delete the batch values from the database. +int MultiVerNaturalStoreConnection::DeleteBatch(const IOption &option, const std::vector &keys) +{ + if (keys.empty() || keys.size() > DBConstant::MAX_BATCH_SIZE) { + LOGE("[MultiVer]DeleteBatch size[%zu]!", keys.size()); + return -E_INVALID_ARGS; + } + if (!CheckDeletedKeys(keys)) { + return -E_INVALID_ARGS; + } + bool isAuto = false; + std::lock_guard lock(writeMutex_); + // if the transaction is not started auto + int errCode = StartTransactionInner(isAuto); + if (errCode != E_OK) { + LOGE("Start transaction failed:%d", errCode); + return errCode; + } + + // delete automatic + bool needCommit = false; + for (const auto &item : keys) { + errCode = writeHandle_->Delete(item); + if (errCode == E_OK) { + needCommit = true; + } else if (errCode != -E_NOT_FOUND) { + if (isAuto) { + (void)(RollBackTransactionInner()); + } + LOGE("Delete failed:%d", errCode); + return errCode; + } + } + + if (isAuto) { + if (needCommit) { + errCode = CommitTransactionInner(); + } else { + (void)(RollBackTransactionInner()); + errCode = -E_NOT_FOUND; + } + } else { + errCode = needCommit ? E_OK : -E_NOT_FOUND; + } + return errCode; +} + +int MultiVerNaturalStoreConnection::GetSnapshot(IKvDBSnapshot *&snapshot) const +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = E_OK; + auto handle = GetHandle(false, errCode); + if (handle == nullptr) { + LOGE("Get the handle for snapshot failed:%d", errCode); + return errCode; + } + + errCode = handle->InitCurrentReadVersion(); + if (errCode != E_OK) { + LOGE("Init the handle version for snapshot failed:%d", errCode); + GetDB()->ReleaseHandle(handle); + return errCode; + } + + snapshot = new (std::nothrow) MultiVerNaturalStoreSnapshot(handle); + if (snapshot == nullptr) { + GetDB()->ReleaseHandle(handle); + return -E_OUT_OF_MEMORY; + } + + std::lock_guard lock(snapshotMutex_); + snapshots_.insert(snapshot); + GetDB()->AddVersionConstraintToList(handle->GetCurrentReadVersion()); + return E_OK; +} + +// Release the created snapshot +void MultiVerNaturalStoreConnection::ReleaseSnapshot(IKvDBSnapshot *&snapshot) +{ + if (snapshot == nullptr) { + return; + } + + std::lock_guard lock(snapshotMutex_); + static_cast(snapshot)->Close(); + snapshots_.erase(snapshot); + delete snapshot; + snapshot = nullptr; + return; +} + +// Start the transaction +int MultiVerNaturalStoreConnection::StartTransaction() +{ + // Get the state of the transaction. + std::lock_guard lock(writeMutex_); + if (writeHandle_ != nullptr) { + LOGE("Transaction is already running"); + return -E_TRANSACT_STATE; + } + bool isAuto = false; + return StartTransactionInner(isAuto); +} + +// Commit the transaction +int MultiVerNaturalStoreConnection::Commit() +{ + std::lock_guard lock(writeMutex_); + return CommitTransactionInner(); +} + +// Roll back the transaction +int MultiVerNaturalStoreConnection::RollBack() +{ + std::lock_guard lock(writeMutex_); + return RollBackTransactionInner(); +} + +bool MultiVerNaturalStoreConnection::IsTransactionStarted() const +{ + std::lock_guard lock(writeMutex_); + if (writeHandle_ != nullptr) { + return true; + } + return false; +} + +// Close and delete the connection. +int MultiVerNaturalStoreConnection::PreClose() +{ + std::lock_guard snapshotLock(snapshotMutex_); + if (snapshots_.size() > 0) { + LOGE("the connection have unreleased snapshot, should not close."); + return -E_BUSY; + } + + std::lock_guard writeLock(writeMutex_); + if (writeHandle_ != nullptr) { + LOGE("the connection have transaction, should not close."); + (void)(RollBackTransactionInner()); + } + return E_OK; +} + +// Commit Transaction for local change data +int MultiVerNaturalStoreConnection::CommitTransactionInner() +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + // Get the state of the transaction. + if (writeHandle_ == nullptr) { + LOGE("Transaction has not been started."); + return -E_TRANSACT_STATE; + } + + int errCode = writeHandle_->CommitTransaction(); + GetDB()->ReleaseHandle(writeHandle_); + + return errCode; +} + +// If the transaction is started automatically, should roll back automatically +int MultiVerNaturalStoreConnection::RollBackTransactionInner() +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + if (writeHandle_ == nullptr) { + return -E_TRANSACT_STATE; + } + + int errCode = writeHandle_->RollBackTransaction(); + GetDB()->ReleaseHandle(writeHandle_); + + return errCode; +} + +int MultiVerNaturalStoreConnection::StartTransactionInner(bool &isAuto) +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + isAuto = false; + if (writeHandle_ != nullptr) { + return E_OK; + } + + int errCode = E_OK; + auto handle = GetHandle(true, errCode); + if (handle == nullptr) { + LOGE("Get write handle for transaction failed:%d", errCode); + return errCode; + } + errCode = handle->StartTransaction(); + if (errCode != E_OK) { + LOGE("Start transaction failed:%d", errCode); + GetDB()->ReleaseHandle(handle); + return errCode; + } + + writeHandle_ = handle; + isAuto = true; + + return E_OK; +} + +int MultiVerNaturalStoreConnection::TranslateObserverModeToEventTypes(unsigned mode, + std::list &eventTypes) const +{ + if (mode != NATURAL_STORE_COMMIT_EVENT) { + return -E_NOT_SUPPORT; + } else { + eventTypes.push_back(NATURAL_STORE_COMMIT_EVENT); + return E_OK; + } +} + +int MultiVerNaturalStoreConnection::Rekey(const CipherPassword &passwd) +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + std::lock_guard lock(rekeyMutex_); + // Check the condition, have no more than one connection. + int errCode = kvDB_->TryToDisableConnection(OperatePerm::REKEY_MONOPOLIZE_PERM); + if (errCode != E_OK) { + return errCode; + } + + // Check the observer condition. + errCode = GenericKvDBConnection::PreCheckExclusiveStatus(); + if (errCode != E_OK) { + kvDB_->ReEnableConnection(OperatePerm::REKEY_MONOPOLIZE_PERM); + return errCode; + } + + // No need the check other + errCode = kvDB_->Rekey(passwd); + GenericKvDBConnection::ResetExclusiveStatus(); + kvDB_->ReEnableConnection(OperatePerm::REKEY_MONOPOLIZE_PERM); + return errCode; +} + +int MultiVerNaturalStoreConnection::Export(const std::string &filePath, const CipherPassword &passwd) +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + return kvDB_->Export(filePath, passwd); +} + +int MultiVerNaturalStoreConnection::Import(const std::string &filePath, const CipherPassword &passwd) +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + std::lock_guard lock(importMutex_); + int errCode = kvDB_->TryToDisableConnection(OperatePerm::IMPORT_MONOPOLIZE_PERM); + if (errCode != E_OK) { + return errCode; + } + + // Check the observer condition. + errCode = GenericKvDBConnection::PreCheckExclusiveStatus(); + if (errCode != E_OK) { + kvDB_->ReEnableConnection(OperatePerm::IMPORT_MONOPOLIZE_PERM); + return errCode; + } + errCode = kvDB_->Import(filePath, passwd); + GenericKvDBConnection::ResetExclusiveStatus(); + kvDB_->ReEnableConnection(OperatePerm::IMPORT_MONOPOLIZE_PERM); + return errCode; +} + +bool MultiVerNaturalStoreConnection::CheckDeletedKeys(const std::vector &keys) +{ + for (const auto &item : keys) { + if (item.empty() || item.size() > DBConstant::MAX_KEY_SIZE) { + return false; + } + } + return true; +} + +int MultiVerNaturalStoreConnection::CheckDataStatus(const Key &key, const Value &value, bool isDeleted) const +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + return static_cast(kvDB_)->CheckDataStatus(key, value, isDeleted); +} + +MultiVerStorageExecutor *MultiVerNaturalStoreConnection::GetHandle(bool isWrite, int &errCode) const +{ + MultiVerNaturalStore *multiVerNatureStore = GetDB(); + if (multiVerNatureStore == nullptr) { + errCode = -E_INVALID_DB; + return nullptr; + } + + return multiVerNatureStore->GetHandle(isWrite, errCode); +} + +DEFINE_OBJECT_TAG_FACILITIES(MultiVerNaturalStoreConnection) +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_connection.h b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_connection.h new file mode 100644 index 00000000..86456481 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_connection.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_NATURAL_STORE_CONNECTION_H +#define MULTI_VER_NATURAL_STORE_CONNECTION_H + +#ifndef OMIT_MULTI_VER +#include +#include + +#include "macro_utils.h" +#include "sync_able_kvdb_connection.h" +#include "multi_ver_def.h" +#include "multi_ver_kv_entry.h" +#include "multi_ver_storage_executor.h" + +namespace DistributedDB { +class MultiVerNaturalStore; + +enum class TransactState { + TRANSACT_IDLE, + TRANSACT_IN_PROGRESS, +}; + +class MultiVerNaturalStoreConnection : public SyncAbleKvDBConnection { +public: + explicit MultiVerNaturalStoreConnection(MultiVerNaturalStore *kvDB); + ~MultiVerNaturalStoreConnection() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(MultiVerNaturalStoreConnection); + + // Get the value from the database + int Get(const IOption &option, const Key &key, Value &value) const override; + + // Put the value to the database + int Put(const IOption &option, const Key &key, const Value &value) override; + + // Delete the value from the database + int Delete(const IOption &option, const Key &key) override; + + // Clear all the data from the database + int Clear(const IOption &option) override; + + // Get all the data from the database + int GetEntries(const IOption &option, const Key &keyPrefix, std::vector &entries) const override; + + // Put the batch values to the database. + int PutBatch(const IOption &option, const std::vector &entries) override; + + // Put the synced data by commit. + int PutCommitData(const MultiVerCommitNode &commit, const std::vector &entries); + + // Delete the batch values from the database. + int DeleteBatch(const IOption &option, const std::vector &keys) override; + + // Get the snapshot + int GetSnapshot(IKvDBSnapshot *&snapshot) const override; + + // Release the created snapshot + void ReleaseSnapshot(IKvDBSnapshot *&snapshot) override; + + // Start the transaction + int StartTransaction() override; + + // Commit the transaction + int Commit() override; + + // Roll back the transaction + int RollBack() override; + + // Check if the transaction already started manually + bool IsTransactionStarted() const override; + + // Called when close and delete the connection. + int PreClose() override; + + // Parse event types(from observer mode). + int TranslateObserverModeToEventTypes(unsigned mode, std::list &eventTypes) const override; + + int Rekey(const CipherPassword &passwd) override; + + int Export(const std::string &filePath, const CipherPassword &passwd) override; + + int Import(const std::string &filePath, const CipherPassword &passwd) override; + +private: + static bool CheckDeletedKeys(const std::vector &keys); + + int CheckDataStatus(const Key &key, const Value &value, bool isDeleted) const; + + int StartTransactionInner(bool &isAuto); + + int CommitTransactionInner(); + + int RollBackTransactionInner(); + + int CheckTransactionState(); + + MultiVerStorageExecutor *GetHandle(bool isWrite, int &errCode) const; + + DECLARE_OBJECT_TAG(MultiVerNaturalStoreConnection); + + MultiVerStorageExecutor *writeHandle_; + mutable std::set snapshots_; + mutable std::mutex snapshotMutex_; + mutable std::mutex writeMutex_; + std::mutex rekeyMutex_; + std::mutex importMutex_; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_NATURAL_STORE_CONNECTION_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_snapshot.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_snapshot.cpp new file mode 100644 index 00000000..5b11e3af --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_snapshot.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_natural_store_snapshot.h" + +#include "db_constant.h" +#include "db_errno.h" +#include "log_print.h" +#include "multi_ver_storage_executor.h" + +namespace DistributedDB { +MultiVerNaturalStoreSnapshot::MultiVerNaturalStoreSnapshot(StorageExecutor *handle) + : databaseHandle_(handle) +{} + +MultiVerNaturalStoreSnapshot::~MultiVerNaturalStoreSnapshot() +{ + databaseHandle_ = nullptr; +} + +int MultiVerNaturalStoreSnapshot::Get(const Key &key, Value &value) const +{ + if (databaseHandle_ == nullptr) { + return -E_INVALID_DB; + } + if (key.empty() || key.size() > DBConstant::MAX_KEY_SIZE) { + LOGE("[MultiSnapshot] Invalid key[%zu]", key.size()); + return -E_INVALID_ARGS; + } + return static_cast(databaseHandle_)->Get(key, value); +} + +int MultiVerNaturalStoreSnapshot::GetEntries(const Key &keyPrefix, std::vector &entries) const +{ + if (databaseHandle_ == nullptr) { + return -E_INVALID_DB; + } + if (keyPrefix.size() > DBConstant::MAX_KEY_SIZE) { + LOGE("[MultiSnapshot] Invalid prefix[%zu]", keyPrefix.size()); + return -E_INVALID_ARGS; + } + return static_cast(databaseHandle_)->GetEntries(keyPrefix, entries); +} + +void MultiVerNaturalStoreSnapshot::Close() +{ + if (databaseHandle_ != nullptr) { + static_cast(databaseHandle_)->Close(); + databaseHandle_ = nullptr; + } +} +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_snapshot.h b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_snapshot.h new file mode 100644 index 00000000..86ae7e9b --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_snapshot.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_NATURAL_STORE_SNAPSHOT_H +#define MULTI_VER_NATURAL_STORE_SNAPSHOT_H + +#ifndef OMIT_MULTI_VER +#include "ikvdb_snapshot.h" +#include "storage_executor.h" + +namespace DistributedDB { +class MultiVerNaturalStoreSnapshot : public IKvDBSnapshot { +public: + explicit MultiVerNaturalStoreSnapshot(StorageExecutor *handle); + ~MultiVerNaturalStoreSnapshot() override; + + DISABLE_COPY_ASSIGN_MOVE(MultiVerNaturalStoreSnapshot); + + // Get the value according the key in the snapshot + int Get(const Key &key, Value &value) const override; + + // Get the data according the prefix key in the snapshot + int GetEntries(const Key &keyPrefix, std::vector &entries) const override; + + void Close(); + +private: + StorageExecutor *databaseHandle_; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_NATURAL_STORE_SNAPSHOT_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_transfer_data.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_transfer_data.cpp new file mode 100644 index 00000000..acc4e771 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_transfer_data.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_natural_store_transfer_data.h" + +#include "db_constant.h" +#include "log_print.h" +#include "db_errno.h" + +namespace DistributedDB { +int MultiVerNaturalStoreTransferData::SegmentAndTransferValueToHash(const Value &oriValue, + std::vector &partValues) const +{ + if (oriValue.size() <= sliceLengthThreshold_) { + return -E_UNEXPECTED_DATA; + } + + const uint32_t sizeByte = blockSizeByte_; + if (sizeByte == 0) { + return -E_UNEXPECTED_DATA; + } + + const size_t partNum = oriValue.size() / sizeByte; + + for (size_t i = 0; i < partNum; i++) { + Value tempValue(sizeByte); + // When the hash value is combined, the overlapped part is removed. So not need -1 at tail + std::copy(oriValue.begin() + i * sizeByte, oriValue.begin() + sizeByte * (i + 1), tempValue.begin()); + partValues.push_back(std::move(tempValue)); + } + Value tailValue(oriValue.size() - partNum * sizeByte); + std::copy(oriValue.begin() + partNum * sizeByte, oriValue.end(), tailValue.begin()); + if (!tailValue.empty()) { + partValues.push_back(tailValue); + } + + return E_OK; +} +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_transfer_data.h b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_transfer_data.h new file mode 100644 index 00000000..5fa07f71 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_natural_store_transfer_data.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_NATURAL_STORE_TRANSFER_DATA_H +#define MULTI_VER_NATURAL_STORE_TRANSFER_DATA_H + +#ifndef OMIT_MULTI_VER +#include "db_types.h" +#include "macro_utils.h" + +namespace DistributedDB { +class MultiVerNaturalStoreTransferData { +public: + MultiVerNaturalStoreTransferData() {}; + ~MultiVerNaturalStoreTransferData() {}; + + DISABLE_COPY_ASSIGN_MOVE(MultiVerNaturalStoreTransferData); + + int SegmentAndTransferValueToHash(const Value &oriValue, std::vector &partValues) const; + +private: + size_t sliceLengthThreshold_ = 4194304; // 4MB + size_t blockSizeByte_ = 4194304; // 4MB +}; +} // namespace DistributedDB + +#endif // MULTI_VER_NATURAL_STORE_CONNECTION_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_storage_engine.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_storage_engine.cpp new file mode 100644 index 00000000..0ec1015d --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_storage_engine.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_storage_engine.h" + +#include "multi_ver_storage_executor.h" + +#include "db_errno.h" + +namespace DistributedDB { +MultiVerStorageEngine::MultiVerStorageEngine() + : kvDB_(nullptr), + dataStorage_(nullptr), + commitStorage_(nullptr), + kvDataStorage_(nullptr) +{} + +MultiVerStorageEngine::~MultiVerStorageEngine() +{ + kvDB_ = nullptr; + dataStorage_ = nullptr; + commitStorage_ = nullptr; + kvDataStorage_ = nullptr; +} + +int MultiVerStorageEngine::InitDatabases(IKvDB *kvDB, IKvDBMultiVerDataStorage *dataStorage, + IKvDBCommitStorage *commitStorage, MultiVerKvDataStorage *kvDataStorage, const StorageEngineAttr &poolSize) +{ + if (StorageEngine::CheckEngineAttr(poolSize)) { + return -E_INVALID_ARGS; + } + if (kvDB == nullptr || dataStorage == nullptr || + commitStorage == nullptr || kvDataStorage == nullptr) { + return -E_INVALID_DB; + } + engineAttr_ = poolSize; + kvDB_ = kvDB; + dataStorage_ = dataStorage; + commitStorage_ = commitStorage; + kvDataStorage_ = kvDataStorage; + return Init(); +} + +int MultiVerStorageEngine::CreateNewExecutor(bool isWrite, StorageExecutor *&handle) +{ + handle = new (std::nothrow) MultiVerStorageExecutor(kvDB_, dataStorage_, commitStorage_, kvDataStorage_, isWrite); + if (handle == nullptr) { + return -E_OUT_OF_MEMORY; + } + return E_OK; +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_storage_engine.h b/mock/distributeddb/storage/src/multiver/multi_ver_storage_engine.h new file mode 100644 index 00000000..93335711 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_storage_engine.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_STORAGE_ENGINE_H +#define MULTI_STORAGE_ENGINE_H + +#ifndef OMIT_MULTI_VER +#include "storage_engine.h" +#include "macro_utils.h" +#include "ikvdb.h" +#include "ikvdb_multi_ver_data_storage.h" +#include "ikvdb_commit_storage.h" +#include "multi_ver_kvdata_storage.h" + +namespace DistributedDB { +class MultiVerStorageEngine : public StorageEngine { +public: + MultiVerStorageEngine(); + ~MultiVerStorageEngine() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(MultiVerStorageEngine); + + int InitDatabases(IKvDB *kvDB, IKvDBMultiVerDataStorage *dataStorage, + IKvDBCommitStorage *commitStorage, MultiVerKvDataStorage *kvDataStorage, const StorageEngineAttr &poolSize); + +protected: + int CreateNewExecutor(bool isWrite, StorageExecutor *&handle) override; + +private: + IKvDB *kvDB_; + IKvDBMultiVerDataStorage *dataStorage_; + IKvDBCommitStorage *commitStorage_; + MultiVerKvDataStorage *kvDataStorage_; +}; +} // namespace DistributedDB +#endif // MULTI_STORAGE_ENGINE_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_storage_executor.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_storage_executor.cpp new file mode 100644 index 00000000..14b82f68 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_storage_executor.cpp @@ -0,0 +1,1503 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_storage_executor.h" + +#include + +#include "db_common.h" +#include "db_errno.h" +#include "log_print.h" +#include "multi_ver_natural_store.h" +#include "multi_ver_natural_store_commit_notify_data.h" +#include "multi_ver_natural_store_transfer_data.h" +#include "value_hash_calc.h" + +namespace DistributedDB { +MultiVerStorageExecutor::MultiVerStorageExecutor(IKvDB *kvDB, IKvDBMultiVerDataStorage *dataStorage, + IKvDBCommitStorage *commitStorage, MultiVerKvDataStorage *kvDataStorage, bool writable) + : StorageExecutor(writable), + kvDB_(kvDB), + dataStorage_(dataStorage), + commitStorage_(commitStorage), + kvDataStorage_(kvDataStorage), + transaction_(nullptr), + sliceTransaction_(nullptr) +{} + +MultiVerStorageExecutor::~MultiVerStorageExecutor() +{ + kvDB_ = nullptr; + dataStorage_ = nullptr; + commitStorage_ = nullptr; + kvDataStorage_ = nullptr; + transaction_ = nullptr; +} + +int MultiVerStorageExecutor::Reset() +{ + return E_OK; +} + +int MultiVerStorageExecutor::PutMetaData(const Key &key, const Value &value) +{ + if (kvDataStorage_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = kvDataStorage_->PutMetaData(key, value); + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::GetMetaData(const Key &key, Value &value) const +{ + if (kvDataStorage_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = kvDataStorage_->GetMetaData(key, value); + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::GetDeviceLatestCommit(std::map &commitMap) const +{ + if (commitStorage_ == nullptr) { + LOGE("The commit history module is null."); + return -E_INVALID_DB; + } + std::map latestCommits; + int errCode = commitStorage_->GetLatestCommits(latestCommits); + if (errCode != E_OK) { + LOGE("Get latest commits failed:%d", errCode); + return CheckCorruptedStatus(errCode); + } + for (auto &latestCommit : latestCommits) { + uint64_t localFlag = (latestCommit.second->GetLocalFlag() ? + MultiVerCommitNode::LOCAL_FLAG : MultiVerCommitNode::NON_LOCAL_FLAG); + MultiVerCommitNode commit = { + latestCommit.second->GetCommitId(), // commitId + latestCommit.second->GetLeftParentId(), // leftParent + latestCommit.second->GetRightParentId(), // rightParent + latestCommit.second->GetTimestamp(), // timestamp + latestCommit.second->GetCommitVersion(), // version + localFlag, // isLocal + latestCommit.second->GetDeviceInfo() // deviceInfo + }; + + commitStorage_->ReleaseCommit(latestCommit.second); + latestCommit.second = nullptr; + commitMap.insert(std::make_pair(latestCommit.first, std::move(commit))); + } + latestCommits.clear(); + return E_OK; +} + +int MultiVerStorageExecutor::GetCommitTree(const std::map &commitMap, + std::vector &commits) const +{ + if (commitStorage_ == nullptr) { + LOGE("The commit history module is null."); + return -E_INVALID_DB; + } + std::map latestCommits; + for (auto &latestCommit : commitMap) { + latestCommits.insert(std::make_pair(latestCommit.first, latestCommit.second.commitId)); + } + std::list commitTree; + int errCode = commitStorage_->GetCommitTree(latestCommits, commitTree); + if (errCode != E_OK) { + LOGE("Get commit tree failed:%d", errCode); + return CheckCorruptedStatus(errCode); + } + LOGD("Get commit tree size:%zu", commitTree.size()); + for (auto &commitNode : commitTree) { + if (commitNode == nullptr) { + continue; + } + uint64_t localFlag = (commitNode->GetLocalFlag() ? + MultiVerCommitNode::LOCAL_FLAG : MultiVerCommitNode::NON_LOCAL_FLAG); + MultiVerCommitNode commit = { + commitNode->GetCommitId(), // commitId + commitNode->GetLeftParentId(), // leftParent + commitNode->GetRightParentId(), // rightParent + commitNode->GetTimestamp(), // timestamp + commitNode->GetCommitVersion(), // version + localFlag, // isLocal + commitNode->GetDeviceInfo() // deviceInfo + }; + + commitStorage_->ReleaseCommit(commitNode); + commitNode = nullptr; + commits.push_back(std::move(commit)); + } + commitTree.clear(); + return E_OK; +} + +int MultiVerStorageExecutor::GetCommitData(const MultiVerCommitNode &commit, + std::vector &entries) const +{ + if ((commitStorage_ == nullptr) || (dataStorage_ == nullptr)) { + return -E_INVALID_DB; + } + // call the putting value method. + CommitID commitId = commit.commitId; + int errCode = E_OK; + Version version; + IKvDBCommit *commitNode = commitStorage_->GetCommit(commitId, errCode); + if (commitNode == nullptr) { + LOGE("Failed to get the commit:%d", errCode); + return CheckCorruptedStatus(errCode); + } + + // Get the commit and the version. + std::string devInfo = commitNode->GetDeviceInfo(); + version = commitNode->GetCommitVersion(); + commitStorage_->ReleaseCommit(commitNode); + commitNode = nullptr; + if (devInfo.size() != MULTI_VER_TAG_SIZE) { + LOGD("skip the foreign data"); + entries.clear(); + entries.shrink_to_fit(); + return E_OK; + } + + IKvDBMultiVerTransaction *transaction = dataStorage_->StartRead(KvDataType::KV_DATA_SYNC_P2P, version, errCode); + if (transaction == nullptr) { + LOGE("Failed to get the transaction:%d", errCode); + goto END; + } + + errCode = transaction->GetEntriesByVersion(version, entries); + if (errCode != E_OK) { + LOGE("Get entries by version failed:%d", errCode); + } +END: + if (transaction != nullptr) { + dataStorage_->ReleaseTransaction(transaction); + transaction = nullptr; + } + return CheckCorruptedStatus(errCode); +} + +bool MultiVerStorageExecutor::IsCommitExisted(const MultiVerCommitNode &commit, int &errCode) const +{ + if ((commitStorage_ == nullptr) || (dataStorage_ == nullptr)) { + LOGE("The commit history module or data storage is null."); + return false; + } + auto readCommit = commitStorage_->GetCommit(commit.commitId, errCode); + if (readCommit == nullptr) { + return false; + } + commitStorage_->ReleaseCommit(readCommit); + + bool result = false; + std::vector entries; + auto transaction = dataStorage_->StartRead(KvDataType::KV_DATA_SYNC_P2P, commit.version, errCode); + if (transaction == nullptr) { + LOGE("Failed to get the transaction:%d", errCode); + goto END; + } + + errCode = transaction->GetEntriesByVersion(commit.version, entries); + if (errCode != E_OK) { + LOGE("Get entries by version failed:%d", errCode); + goto END; + } + if (!entries.empty()) { + result = true; + } +END: + if (errCode != E_OK) { + result = false; + } + if (transaction != nullptr) { + dataStorage_->ReleaseTransaction(transaction); + } + + ReleaseMultiVerKvEntries(entries); + errCode = CheckCorruptedStatus(errCode); + return result; +} + +bool MultiVerStorageExecutor::IsValueSliceExisted(const ValueSliceHash &value, int &errCode) const +{ + if (kvDataStorage_ == nullptr) { + errCode = -E_INVALID_DB; + return false; + } + auto sliceTransaction = kvDataStorage_->GetSliceTransaction(false, errCode); + if (sliceTransaction == nullptr) { + (void)(CheckCorruptedStatus(errCode)); + return false; + } + Value valueReal; + errCode = sliceTransaction->GetData(value, valueReal); + kvDataStorage_->ReleaseSliceTransaction(sliceTransaction); + if (errCode == E_OK) { + return true; + } + (void)(CheckCorruptedStatus(errCode)); + return false; +} + +int MultiVerStorageExecutor::GetValueSlice(const ValueSliceHash &hashValue, ValueSlice &sliceValue) const +{ + return GetValueSliceInner(nullptr, hashValue, sliceValue); +} + +int MultiVerStorageExecutor::PutValueSlice(const ValueSliceHash &hashValue, const ValueSlice &sliceValue, + bool isAddCount) +{ + return PutValueSliceInner(nullptr, hashValue, sliceValue, isAddCount); +} + +int MultiVerStorageExecutor::GetValueSliceInner(const SliceTransaction *sliceTransaction, + const ValueSliceHash &hashValue, ValueSlice &sliceValue) const +{ + int errCode; + if (sliceTransaction != nullptr) { + errCode = sliceTransaction->GetData(hashValue, sliceValue); + return CheckCorruptedStatus(errCode); + } + if (kvDataStorage_ == nullptr) { + return -E_INVALID_DB; + } + + auto sliceTransact = kvDataStorage_->GetSliceTransaction(false, errCode); + if (sliceTransact == nullptr) { + return CheckCorruptedStatus(errCode); + } + + errCode = sliceTransact->GetData(hashValue, sliceValue); + kvDataStorage_->ReleaseSliceTransaction(sliceTransact); + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::PutValueSliceInner(SliceTransaction *sliceTransaction, const ValueSliceHash &hashValue, + const ValueSlice &sliceValue, bool isAddCount) +{ + int errCode; + if (sliceTransaction != nullptr) { + errCode = sliceTransaction->PutData(hashValue, sliceValue, isAddCount); + return CheckCorruptedStatus(errCode); + } + + if (kvDataStorage_ == nullptr) { + return -E_INVALID_DB; + } + + auto transaction = kvDataStorage_->GetSliceTransaction(true, errCode); + if (transaction == nullptr) { + return CheckCorruptedStatus(errCode); + } + + errCode = transaction->PutData(hashValue, sliceValue, isAddCount); + kvDataStorage_->ReleaseSliceTransaction(transaction); + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::DeleteValueSliceInner(SliceTransaction *sliceTransaction, + const ValueSliceHash &hashValue) +{ + int errCode; + if (sliceTransaction != nullptr) { + errCode = sliceTransaction->DeleteData(hashValue); + return CheckCorruptedStatus(errCode); + } + + if (kvDataStorage_ == nullptr) { + return -E_INVALID_DB; + } + + auto transaction = kvDataStorage_->GetSliceTransaction(true, errCode); + if (transaction == nullptr) { + return CheckCorruptedStatus(errCode); + } + + errCode = transaction->DeleteData(hashValue); + kvDataStorage_->ReleaseSliceTransaction(transaction); + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::StartSliceTransaction() +{ + if (kvDataStorage_ == nullptr) { + return -E_INVALID_DB; + } + if (sliceTransaction_ != nullptr) { + return -E_UNEXPECTED_DATA; + } + int errCode; + sliceTransaction_ = kvDataStorage_->GetSliceTransaction(true, errCode); + if (sliceTransaction_ == nullptr) { + return errCode; + } + errCode = sliceTransaction_->StartTransaction(); + if (errCode != E_OK) { + kvDataStorage_->ReleaseSliceTransaction(sliceTransaction_); + } + return errCode; +} + +int MultiVerStorageExecutor::CommitSliceTransaction() +{ + if (sliceTransaction_ == nullptr) { + return -E_UNEXPECTED_DATA; + } + int errCode = sliceTransaction_->CommitTransaction(); + if (errCode != E_OK) { + LOGE("Commit slice transaction failed:%d", errCode); + } + if (kvDataStorage_ == nullptr) { + return -E_INVALID_DB; + } + kvDataStorage_->ReleaseSliceTransaction(sliceTransaction_); + sliceTransaction_ = nullptr; + return errCode; +} + +int MultiVerStorageExecutor::RollbackSliceTransaction() +{ + if (sliceTransaction_ == nullptr) { + return -E_UNEXPECTED_DATA; + } + int errCode = sliceTransaction_->RollbackTransaction(); + if (errCode != E_OK) { + LOGE("Commit slice transaction failed:%d", errCode); + } + if (kvDataStorage_ == nullptr) { + return -E_INVALID_DB; + } + kvDataStorage_->ReleaseSliceTransaction(sliceTransaction_); + sliceTransaction_ = nullptr; + return errCode; +} + +int MultiVerStorageExecutor::ReInitTransactionVersion(const MultiVerCommitNode &commit) +{ + if (commitStorage_ == nullptr) { + LOGE("The commit history module is null when reinit transaction version."); + return -E_INVALID_DB; + } + int errCode = StartTransaction(); + if (errCode != E_OK) { + LOGE("Start transaction failed:%d", errCode); + return errCode; + } + auto readCommit = commitStorage_->GetCommit(commit.commitId, errCode); + if (readCommit == nullptr) { + if (errCode != -E_NOT_FOUND) { + RollBackTransaction(); + LOGE("Get the commit error:%d", errCode); + return errCode; + } else { + errCode = E_OK; + } + } else { + LOGD("Reput the version:%" PRIu64, readCommit->GetCommitVersion()); + transaction_->SetVersion(readCommit->GetCommitVersion()); + commitStorage_->ReleaseCommit(readCommit); + } + + if (errCode != E_OK) { + RollBackTransaction(); + } + return errCode; +} + +int MultiVerStorageExecutor::AddSliceDataCount(const std::vector &values) +{ + for (const auto &item : values) { + MultiVerValueObject valueObject; + int errCode = valueObject.DeSerialData(item); + if (errCode != E_OK) { + return errCode; + } + if (!valueObject.IsHash()) { + continue; + } + std::vector valueHashList; + valueObject.GetValueHash(valueHashList); + for (auto &iter : valueHashList) { + Value filledData; + errCode = PutValueSliceInner(sliceTransaction_, iter, filledData, true); + if (errCode != E_OK) { + LOGE("Add the slice value count failed:%d", errCode); + return errCode; + } + } + } + return E_OK; +} +int MultiVerStorageExecutor::PutCommitData(const MultiVerCommitNode &commit, + const std::vector &entries, const std::string &deviceName) +{ + // Update the version while the commit has been put. + int errCode = ReInitTransactionVersion(commit); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + errCode = StartSliceTransaction(); + if (errCode != E_OK) { + RollBackTransaction(); + return CheckCorruptedStatus(errCode); + } + + if (transaction_ == nullptr) { + return -E_INVALID_DB; + } + + std::vector values; + errCode = transaction_->PutBatch(entries, false, values); + if (errCode != E_OK) { + LOGE("Put batch synced data failed:%d", errCode); + goto END; + } + errCode = AddSliceDataCount(values); + if (errCode != E_OK) { + goto END; + } + errCode = CommitSliceTransaction(); + if (errCode != E_OK) { + RollBackTransaction(); + } else { + errCode = CommitTransaction(commit, false); + } + return CheckCorruptedStatus(errCode); +END: + if (errCode != E_OK) { + (void)(RollbackSliceTransaction()); + RollBackTransaction(); + } + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::MergeSyncCommit(const MultiVerCommitNode &commit, + const std::vector &commits) +{ + if (commits.empty()) { + return E_OK; + } + // if all the nodes have two parents, no need to merge. + bool isAllMerged = true; + for (const auto &item : commits) { + if (item.rightParent.empty()) { + isAllMerged = false; + } + } + + if (isAllMerged) { + LOGI("all nodes have been merged"); + return E_OK; + } + + int errCode = MergeCommits(commits); + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::MergeOneCommit(const MultiVerCommitNode &commit) +{ + std::vector entries; + int errCode = GetResolvedConflictEntries(commit, entries); + if (errCode != E_OK) { + return errCode; + } + + if (transaction_ == nullptr) { + return -E_INVALID_DB; + } + + std::vector values; + errCode = transaction_->PutBatch(entries, true, values); + if (errCode != E_OK) { + goto END; + } + + errCode = AddSliceDataCount(values); +END: + ReleaseMultiVerKvEntries(entries); + return errCode; +} + +int MultiVerStorageExecutor::MergeCommits(const std::vector &commits) +{ + const MultiVerCommitNode &rootCommitNode = commits.back(); + std::string rootNodeDeviceInfo = rootCommitNode.deviceInfo; + if (rootNodeDeviceInfo.size() != SHA256_DIGEST_LENGTH + MULTI_VER_TAG_SIZE) { + return -E_UNEXPECTED_DATA; + } + int errCode = StartTransaction(); + if (errCode != E_OK) { + return errCode; + } + errCode = StartSliceTransaction(); + if (errCode != E_OK) { + RollBackTransaction(); + return errCode; + } + for (const auto &item : commits) { + // only need to merge the node data which is from the same device + if (item.deviceInfo.size() != SHA256_DIGEST_LENGTH + MULTI_VER_TAG_SIZE && + item.deviceInfo.size() != MULTI_VER_TAG_SIZE) { + errCode = -E_UNEXPECTED_DATA; + break; + } + if (item.deviceInfo.size() == MULTI_VER_TAG_SIZE || + item.deviceInfo.compare(0, SHA256_DIGEST_LENGTH, rootNodeDeviceInfo, 0, SHA256_DIGEST_LENGTH) != 0) { + LOGD("Skip the version:%" PRIu64, item.version); + continue; + } + errCode = MergeOneCommit(item); + if (errCode != E_OK) { + break; + } + } + + if (errCode != E_OK) { + (void)(RollbackSliceTransaction()); + errCode = RollBackTransaction(); + } else { + errCode = CommitSliceTransaction(); + if (errCode == E_OK) { + errCode = CommitTransaction(rootCommitNode, true); + } else { + LOGE("Commit the slice transaction error, rollback the data transaction"); + RollBackTransaction(); + } + } + return errCode; +} + +int MultiVerStorageExecutor::GetDiffEntries(const CommitID &begin, const CommitID &end, MultiVerDiffData &data) const +{ + if ((commitStorage_ == nullptr) || (dataStorage_ == nullptr)) { + return -E_INVALID_DB; + } + + int errCode = E_OK; + Version verBegin; + if (begin.empty()) { + verBegin = 0; + } else { + IKvDBCommit *commitBegin = commitStorage_->GetCommit(begin, errCode); + if (commitBegin == nullptr) { + verBegin = 0; + } else { + verBegin = commitBegin->GetCommitVersion(); + } + commitStorage_->ReleaseCommit(commitBegin); + commitBegin = nullptr; + } + + IKvDBCommit *commitEnd = commitStorage_->GetCommit(end, errCode); + if (commitEnd == nullptr) { + return CheckCorruptedStatus(errCode); + } + + Version verEnd = commitEnd->GetCommitVersion(); + commitStorage_->ReleaseCommit(commitEnd); + commitEnd = nullptr; + + IKvDBMultiVerTransaction *transaction = + dataStorage_->StartRead(KvDataType::KV_DATA_SYNC_P2P, verBegin, errCode); + if (transaction == nullptr) { + LOGE("Get diff data start read failed:%d", errCode); + return CheckCorruptedStatus(errCode); + } + + errCode = transaction->GetDiffEntries(verBegin, verEnd, data); + if (errCode != E_OK) { + LOGE("get diff entries failed:%d", errCode); + goto END; + } + + errCode = TransferDiffEntries(data); +END: + dataStorage_->ReleaseTransaction(transaction); + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::Get(const Key &key, Value &value) const +{ + if (dataStorage_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = E_OK; + auto transaction = dataStorage_->StartRead(KvDataType::KV_DATA_SYNC_P2P, readVersion_, errCode); + if (transaction == nullptr) { + LOGE("Get read transaction failed:%d", errCode); + return CheckCorruptedStatus(errCode); + } + + Value rawValue; + errCode = transaction->Get(key, rawValue); + + dataStorage_->ReleaseTransaction(transaction); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + + return TransferToUserValue(rawValue, value); +} + +int MultiVerStorageExecutor::GetEntries(const Key &keyPrefix, std::vector &entries) const +{ + if (dataStorage_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = E_OK; + auto transaction = dataStorage_->StartRead(KvDataType::KV_DATA_SYNC_P2P, readVersion_, errCode); + if (transaction == nullptr) { + LOGE("Get read transaction failed:%d", errCode); + return CheckCorruptedStatus(errCode); + } + + errCode = transaction->GetEntries(keyPrefix, entries); + + dataStorage_->ReleaseTransaction(transaction); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + + for (auto &item : entries) { + Value userValue; + errCode = TransferToUserValue(item.value, userValue); + if (errCode != E_OK) { + entries.clear(); + entries.shrink_to_fit(); + break; + } + std::swap(userValue, item.value); + } + + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::Put(const Key &key, const Value &value) +{ + if (transaction_ == nullptr) { + return -E_INVALID_DB; + } + Value savedValue; + int errCode = TransferToSavedValue(value, savedValue); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + errCode = transaction_->Put(key, savedValue); + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::Delete(const Key &key) +{ + if (transaction_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = transaction_->Delete(key); + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::Clear() +{ + if (transaction_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = transaction_->Clear(); + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::StartAllDbTransaction() +{ + if (dataStorage_ == nullptr || commitStorage_ == nullptr) { + return -E_INVALID_DB; + } + + IKvDBMultiVerTransaction *transaction = nullptr; + int errCode = dataStorage_->StartWrite(KvDataType::KV_DATA_SYNC_P2P, transaction); + if (transaction == nullptr) { + LOGE("start write transaction failed:%d", errCode); + return CheckCorruptedStatus(errCode); + } + + // start data storage transaction + Version maxVersion = static_cast(kvDB_)->GetMaxCommitVersion(); + transaction->SetVersion(maxVersion); + + errCode = transaction->StartTransaction(); + if (errCode != E_OK) { + LOGE("Start dataStorage transaction failed:%d", errCode); + goto END; + } + + // start commit history transaction + errCode = commitStorage_->StartVacuum(); + if (errCode != E_OK) { + transaction->RollBackTransaction(); + LOGE("Start commitStorage transaction failed:%d", errCode); + goto END; + } + + // start slice data transaction + errCode = StartSliceTransaction(); + if (errCode != E_OK) { + transaction->RollBackTransaction(); + commitStorage_->CancelVacuum(); + LOGE("Start kvDataStorage transaction failed:%d", errCode); + goto END; + } + transaction_ = transaction; +END: + if (errCode != E_OK) { + dataStorage_->ReleaseTransaction(transaction); + transaction = nullptr; + transaction_ = nullptr; + return CheckCorruptedStatus(errCode); + } + + return errCode; +} + +int MultiVerStorageExecutor::StartTransaction(MultiTransactionType type) +{ + if (type == MultiTransactionType::ALL_DATA) { + return StartAllDbTransaction(); + } + + if (dataStorage_ == nullptr) { + return -E_INVALID_DB; + } + IKvDBMultiVerTransaction *transaction = nullptr; + int errCode = dataStorage_->StartWrite(KvDataType::KV_DATA_SYNC_P2P, transaction); + if (transaction == nullptr) { + LOGE("start write transaction failed:%d", errCode); + return CheckCorruptedStatus(errCode); + } + + // Get the current max version, and the current version is max version + 1. + Version maxVersion = static_cast(kvDB_)->GetMaxCommitVersion(); + transaction->SetVersion(++maxVersion); + errCode = transaction->StartTransaction(); + if (errCode != E_OK) { + dataStorage_->ReleaseTransaction(transaction); + transaction = nullptr; + LOGE("Start transaction failed:%d", errCode); + return CheckCorruptedStatus(errCode); + } + transaction_ = transaction; + return E_OK; +} + +int MultiVerStorageExecutor::CommitAllDbTransaction() +{ + if (dataStorage_ == nullptr || commitStorage_ == nullptr || transaction_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = transaction_->CommitTransaction(); + if (errCode != E_OK) { + (void)(RollbackSliceTransaction()); + commitStorage_->CancelVacuum(); + LOGE("commit phase one failed:%d", errCode); + goto END; + } + + // start slice data transaction + errCode = CommitSliceTransaction(); + if (errCode != E_OK) { + commitStorage_->CancelVacuum(); + LOGE("Finish kvDataStorage transaction failed:%d", errCode); + goto END; + } + + // start commit history transaction + errCode = commitStorage_->FinishlVacuum(); + if (errCode != E_OK) { + LOGE("Finish commitStorage transaction failed:%d", errCode); + goto END; + } + +END: + dataStorage_->ReleaseTransaction(transaction_); + transaction_ = nullptr; + + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::CommitTransaction(MultiTransactionType type) +{ + if (type == MultiTransactionType::ALL_DATA) { + return CommitAllDbTransaction(); + } + + if ((dataStorage_ == nullptr) || (transaction_ == nullptr)) { + return -E_INVALID_DB; + } + UpdateVerTimestamp multiVerTimestamp = {static_cast(kvDB_)->GetCurrentTimestamp(), true}; + Version commitVersion; + CommitID commitId; + int errCode = E_OK; + bool isDataChanged = transaction_->IsDataChanged(); + if (!isDataChanged) { + transaction_->RollBackTransaction(); + goto END; + } + + errCode = dataStorage_->CommitWritePhaseOne(transaction_, multiVerTimestamp); + if (errCode != E_OK) { + LOGE("commit phase one failed:%d", errCode); + goto END; + } + + commitVersion = transaction_->GetVersion(); + errCode = FillAndCommitLogEntry(commitVersion, commitId, multiVerTimestamp.timestamp); + if (errCode != E_OK) { + LOGE("rollback commit phase one failed:%d", errCode); + dataStorage_->RollbackWritePhaseOne(transaction_, commitVersion); + goto END; + } + LOGD("local commit version:%" PRIu64, commitVersion); + static_cast(kvDB_)->SetMaxTimestamp(multiVerTimestamp.timestamp); + dataStorage_->CommitWritePhaseTwo(transaction_); + static_cast(kvDB_)->SetMaxCommitVersion(commitVersion); +END: + dataStorage_->ReleaseTransaction(transaction_); + transaction_ = nullptr; + if (errCode == E_OK && isDataChanged) { + CommitNotifiedData(commitId); + } + + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::RollBackAllDbTransaction() +{ + if ((dataStorage_ == nullptr) || (commitStorage_ == nullptr)) { + return -E_INVALID_DB; + } + int errCode = dataStorage_->RollbackWrite(transaction_); + if (errCode != E_OK) { + LOGE("Data storage rollback fail!"); + (void)(commitStorage_->CancelVacuum()); + (void)(RollbackSliceTransaction()); + goto END; + } + + errCode = commitStorage_->CancelVacuum(); + if (errCode != E_OK) { + LOGE("Commit storage rollback fail!"); + (void)(RollbackSliceTransaction()); + goto END; + } + + errCode = RollbackSliceTransaction(); + if (errCode != E_OK) { + LOGE("Value slice rollback fail!"); + } + +END: + dataStorage_->ReleaseTransaction(transaction_); + transaction_ = nullptr; + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::RollBackTransaction(MultiTransactionType type) +{ + if (dataStorage_ == nullptr || transaction_ == nullptr) { + LOGE("invalid transaction for rollback"); + return -E_INVALID_DB; + } + + if (type == MultiTransactionType::ALL_DATA) { + return RollBackAllDbTransaction(); + } + + int errCode = dataStorage_->RollbackWrite(transaction_); + dataStorage_->ReleaseTransaction(transaction_); + transaction_ = nullptr; + return CheckCorruptedStatus(errCode); +} + +void MultiVerStorageExecutor::Close() +{ + MultiVerStorageExecutor *handle = this; + + MultiVerNaturalStore *multiVerNatureStore = static_cast(kvDB_); + if (multiVerNatureStore == nullptr) { + return; + } + + if (readVersion_ != 0) { + multiVerNatureStore->RemoveVersionConstraintFromList(readVersion_); + readVersion_ = 0; + } + multiVerNatureStore->ReleaseHandle(handle); +} + +int MultiVerStorageExecutor::InitCurrentReadVersion() +{ + if (commitStorage_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = E_OK; + CommitID commitId = commitStorage_->GetHeader(errCode); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + + Version version = 0; + // if no head, just use the initial version. + if (!commitId.empty()) { + IKvDBCommit *commit = commitStorage_->GetCommit(commitId, errCode); + if (commit == nullptr) { + LOGE("get the header commit failed:%d", errCode); + return CheckCorruptedStatus(errCode); + } + + version = commit->GetCommitVersion(); + commitStorage_->ReleaseCommit(commit); + commit = nullptr; + } + readVersion_ = version; + return E_OK; +} + +int MultiVerStorageExecutor::TransferDiffEntries(MultiVerDiffData &data) const +{ + int errCode; + Value valueTmp; + for (auto &insertedItem : data.inserted) { + errCode = TransferToUserValue(insertedItem.value, valueTmp); + if (errCode != E_OK) { + return errCode; + } + std::swap(insertedItem.value, valueTmp); + } + + for (auto &updatedItem : data.updated) { + errCode = TransferToUserValue(updatedItem.value, valueTmp); + if (errCode != E_OK) { + return errCode; + } + std::swap(updatedItem.value, valueTmp); + } + + for (auto &deletedItem : data.deleted) { + errCode = TransferToUserValue(deletedItem.value, valueTmp); + if (errCode != E_OK) { + return errCode; + } + std::swap(deletedItem.value, valueTmp); + } + + return E_OK; +} + +int MultiVerStorageExecutor::TransferToUserValue(const Value &savedValue, Value &value) const +{ + MultiVerValueObject valueObject; + int errCode = valueObject.DeSerialData(savedValue); + if (errCode != E_OK) { + LOGE("Deserialize the multi ver saved value failed:%d", errCode); + return errCode; + } + if (!valueObject.IsHash()) { + return valueObject.GetValue(value); + } + + std::vector sliceHashVect; + errCode = valueObject.GetValueHash(sliceHashVect); + if (errCode != E_OK) { + return errCode; + } + value.clear(); + value.shrink_to_fit(); + for (const auto &item : sliceHashVect) { + Value itemValue; + errCode = GetValueSlice(item, itemValue); + if (errCode != E_OK) { + LOGE("Get hash entry error:%d", errCode); + break; + } + value.insert(value.end(), itemValue.begin(), itemValue.end()); + } + + return errCode; +} + +int MultiVerStorageExecutor::TransferToValueObject(const Value &value, MultiVerValueObject &valueObject) +{ + MultiVerNaturalStoreTransferData splitData; + std::vector partValues; + // Segment data into blocks by fixed size + // You can set Threshold and blocksize by SetSliceLengthThreshold, SetBlockSizeByte; + int errCode = splitData.SegmentAndTransferValueToHash(value, partValues); + if (errCode == E_OK) { + valueObject.SetFlag(MultiVerValueObject::HASH_FLAG); + + // Tansfer blocks data to hash value list + std::vector hashValues; + ValueSliceHash hashValue; + for (const auto &partValue : partValues) { + if (DBCommon::CalcValueHash(partValue, hashValue) != E_OK) { + return -E_INTERNAL_ERROR; + } + // Put hash value into table + errCode = PutValueSlice(hashValue, partValue, true); + if (errCode != E_OK) { + return errCode; + } + hashValues.push_back(std::move(hashValue)); + } + + valueObject.SetValueHash(hashValues); + } else { + valueObject.SetFlag(0); + valueObject.SetValue(value); + } + valueObject.SetDataLength(value.size()); + return E_OK; +} + +int MultiVerStorageExecutor::TransferToSavedValue(const Value &value, Value &savedValue) +{ + MultiVerValueObject valueObject; + int errCode = TransferToValueObject(value, valueObject); + if (errCode != E_OK) { + LOGE("Failed to get the serialize data of value object:%d", errCode); + return errCode; + } + + errCode = valueObject.GetSerialData(savedValue); + if (errCode != E_OK) { + LOGE("failed to get the serialize data of savedValue:%d", errCode); + return errCode; + } + + return E_OK; +} + +int MultiVerStorageExecutor::GetResolvedConflictEntries(const MultiVerCommitNode &commitItem, + std::vector &entries) const +{ + if (commitStorage_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = E_OK; + auto commit = commitStorage_->GetCommit(commitItem.commitId, errCode); + if (commit == nullptr) { + LOGE("failed to get the commit in merge:%d", errCode); + return errCode; + } + entries.clear(); + entries.shrink_to_fit(); + Version version = commit->GetCommitVersion(); + LOGD("Version is %" PRIu64, version); + if (transaction_ != nullptr) { + errCode = transaction_->GetEntriesByVersion(version, entries); + if (errCode != E_OK) { + LOGE("failed to get the entries by version:%d", errCode); + } + } + commitStorage_->ReleaseCommit(commit); + return errCode; +} + +void MultiVerStorageExecutor::CommitNotifiedData(const CommitID &commitId) +{ + CommitID startId; + Version currentVersion; + int errCode = GetParentCommitId(commitId, startId, currentVersion); + if (errCode != E_OK || currentVersion == 0) { // make sure that the version - 1 is valid. + LOGE("Notify: get the parent commit failed:%d", errCode); + return; + } + MultiVerNaturalStoreCommitNotifyData *committedData = + new (std::nothrow) MultiVerNaturalStoreCommitNotifyData( + static_cast(kvDB_), startId, commitId, currentVersion - 1); + if (committedData != nullptr) { + static_cast(kvDB_)->AddVersionConstraintToList(currentVersion - 1); + static_cast(kvDB_)->CommitNotify(NATURAL_STORE_COMMIT_EVENT, committedData); + committedData->DecObjRef(committedData); + committedData = nullptr; + } else { + LOGE("Failed to do commit notify because of OOM."); + } +} + +int MultiVerStorageExecutor::GetParentCommitId(const CommitID &commitId, CommitID &parentId, Version &curVersion) const +{ + if (commitStorage_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = E_OK; + IKvDBCommit *commit = commitStorage_->GetCommit(commitId, errCode); + if (commit == nullptr) { + LOGE("Get commit failed while getting the parent id:%d", errCode); + return CheckCorruptedStatus(errCode); + } + + parentId = commit->GetLeftParentId(); + curVersion = commit->GetCommitVersion(); + commitStorage_->ReleaseCommit(commit); + commit = nullptr; + return E_OK; +} + +int MultiVerStorageExecutor::AllocNewCommitId(CommitID &commitId) const +{ + // Only for allocate for temporary. + commitId.resize(COMMIT_ID_LENGTH); + RAND_bytes(commitId.data(), COMMIT_ID_LENGTH); + return E_OK; +} + +int MultiVerStorageExecutor::FillAndCommitLogEntry(const Version &versionInfo, CommitID &commitId, + uint64_t timestamp) const +{ + if (kvDB_ == nullptr || commitStorage_ == nullptr) { + return -E_INVALID_DB; + } + // Get the commit id. + int errCode = E_OK; + IKvDBCommit *commit = commitStorage_->AllocCommit(errCode); + if (commit == nullptr) { + LOGE("Failed to alloc the commit locally:%d", errCode); + return errCode; + } + + (void)(AllocNewCommitId(commitId)); + std::vector vectTag; + static_cast(kvDB_)->GetCurrentTag(vectTag); + std::string strTag(vectTag.begin(), vectTag.end()); + + // Get the commit struct. + CommitID header = commitStorage_->GetHeader(errCode); + if (errCode != E_OK) { + goto END; + } + + commit->SetLeftParentId(header); + commit->SetCommitId(commitId); + commit->SetCommitVersion(versionInfo); + commit->SetLocalFlag(true); + commit->SetTimestamp(timestamp); + commit->SetDeviceInfo(strTag); + + // write the commit history. + errCode = commitStorage_->AddCommit(*commit, true); + if (errCode != E_OK) { + LOGE("Add commit history failed:%d", errCode); + } + +END: + if (commit != nullptr) { + commitStorage_->ReleaseCommit(commit); + commit = nullptr; + } + return errCode; +} + +int MultiVerStorageExecutor::FillCommitByForeign(IKvDBCommit *commit, + const MultiVerCommitNode &multiVerCommit, const Version &versionInfo, const CommitID &commitId, bool isMerge) const +{ + if (isMerge) { + if (commitStorage_ == nullptr || kvDB_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = E_OK; + CommitID header = commitStorage_->GetHeader(errCode); + if (errCode != E_OK) { + return errCode; + } + std::vector vectTag; + static_cast(kvDB_)->GetCurrentTag(vectTag); + std::string strTag(vectTag.begin(), vectTag.end()); + + commit->SetCommitId(commitId); + commit->SetLeftParentId(header); + commit->SetRightParentId(multiVerCommit.commitId); + commit->SetLocalFlag(true); + Timestamp timestamp = static_cast(kvDB_)->GetCurrentTimestamp(); + commit->SetTimestamp(timestamp); + commit->SetDeviceInfo(strTag); + } else { + commit->SetCommitId(multiVerCommit.commitId); + commit->SetLeftParentId(multiVerCommit.leftParent); + commit->SetRightParentId(multiVerCommit.rightParent); + commit->SetTimestamp(multiVerCommit.timestamp); + commit->SetLocalFlag(false); + commit->SetDeviceInfo(multiVerCommit.deviceInfo); + } + + commit->SetCommitVersion(versionInfo); + return E_OK; +} + +int MultiVerStorageExecutor::FillAndCommitLogEntry(const Version &versionInfo, + const MultiVerCommitNode &multiVerCommit, CommitID &commitId, bool isMerge, Timestamp ×tamp) const +{ + if (commitStorage_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = E_OK; + IKvDBCommit *commit = commitStorage_->AllocCommit(errCode); + if (commit == nullptr) { + return errCode; + } + + if (isMerge) { + (void)(AllocNewCommitId(commitId)); + } + + errCode = FillCommitByForeign(commit, multiVerCommit, versionInfo, commitId, isMerge); + if (errCode != E_OK) { + LOGE("Failed to fill the sync commit:%d", errCode); + goto END; + } + + timestamp = isMerge ? static_cast(kvDB_)->GetCurrentTimestamp() : multiVerCommit.timestamp; + commit->SetTimestamp(timestamp); + + // write the commit history. + errCode = commitStorage_->AddCommit(*commit, isMerge); + if (errCode != E_OK) { + LOGE("Add commit history failed:%d", errCode); + } +END: + if (commit != nullptr) { + commitStorage_->ReleaseCommit(commit); + commit = nullptr; + } + + return errCode; +} + +int MultiVerStorageExecutor::CommitTransaction(const MultiVerCommitNode &multiVerCommit, bool isMerge) +{ + if ((transaction_ == nullptr) || (dataStorage_ == nullptr)) { + LOGE("invalid transaction for commit"); + return -E_INVALID_DB; + } + + Version commitVersion; + CommitID commitId; + UpdateVerTimestamp multiVerTimestamp = {0ull, false}; + bool isDataChanged = transaction_->IsDataChanged(); + + int errCode = dataStorage_->CommitWritePhaseOne(transaction_, multiVerTimestamp); + if (errCode != E_OK) { + LOGE("commit phase one failed:%d", errCode); + goto END; + } + + commitVersion = transaction_->GetVersion(); + errCode = FillAndCommitLogEntry(commitVersion, multiVerCommit, commitId, isMerge, multiVerTimestamp.timestamp); + if (errCode != E_OK) { + LOGE("rollback commit phase one failed:%d", errCode); + dataStorage_->RollbackWritePhaseOne(transaction_, commitVersion); + goto END; + } + + dataStorage_->CommitWritePhaseTwo(transaction_); + static_cast(kvDB_)->SetMaxTimestamp(multiVerTimestamp.timestamp); + static_cast(kvDB_)->SetMaxCommitVersion(commitVersion); + LOGD("sync commit version:%" PRIu64, commitVersion); +END: + dataStorage_->ReleaseTransaction(transaction_); + transaction_ = nullptr; + + if (errCode == E_OK && isMerge && isDataChanged) { + CommitNotifiedData(commitId); + } + + return CheckCorruptedStatus(errCode); +} + +void MultiVerStorageExecutor::ReleaseMultiVerKvEntries(std::vector &entries) +{ + for (auto &item : entries) { + if (item != nullptr) { + delete item; + item = nullptr; + } + } + entries.clear(); + entries.shrink_to_fit(); +} + +Version MultiVerStorageExecutor::GetCurrentReadVersion() const +{ + return readVersion_; +} + +int MultiVerStorageExecutor::GetAllCommitsInTree(std::list &commits) const +{ + if (commitStorage_ == nullptr) { + return -E_INVALID_DB; + } + + return commitStorage_->GetAllCommitsInTree(commits); +} + +int MultiVerStorageExecutor::GetEntriesByVersion(Version version, std::list &data) const +{ + if (dataStorage_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = E_OK; + IKvDBMultiVerTransaction *transaction = nullptr; + if (transaction_ == nullptr) { + transaction = dataStorage_->StartRead(KvDataType::KV_DATA_SYNC_P2P, version, errCode); + if (transaction == nullptr) { + LOGE("Failed to get the transaction:%d", errCode); + goto END; + } + } else { + transaction = transaction_; + } + + // Note that the transaction fails and the parameters are empty. + errCode = transaction->GetEntriesByVersion(version, data); +END: + if (transaction != transaction_) { + dataStorage_->ReleaseTransaction(transaction); + transaction = nullptr; + } + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::GetOverwrittenClearTypeEntries(Version clearVersion, + std::list &data) const +{ + if (dataStorage_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = E_OK; + IKvDBMultiVerTransaction *transaction = nullptr; + if (transaction_ == nullptr) { + transaction = dataStorage_->StartRead(KvDataType::KV_DATA_SYNC_P2P, clearVersion, errCode); + if (transaction == nullptr) { + LOGE("Failed to get the transaction:%d", errCode); + goto END; + } + } else { + transaction = transaction_; + } + + errCode = transaction->GetOverwrittenClearTypeEntries(clearVersion, data); +END: + if (transaction != transaction_) { + dataStorage_->ReleaseTransaction(transaction); + transaction = nullptr; + } + + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::GetOverwrittenNonClearTypeEntries(Version version, const Key &hashKey, + std::list &data) const +{ + if (dataStorage_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = E_OK; + IKvDBMultiVerTransaction *transaction = nullptr; + if (transaction_ == nullptr) { + transaction = dataStorage_->StartRead(KvDataType::KV_DATA_SYNC_P2P, version, errCode); + if (transaction == nullptr) { + LOGE("Failed to get the transaction:%d", errCode); + goto END; + } + } else { + transaction = transaction_; + } + + errCode = transaction->GetOverwrittenNonClearTypeEntries(version, hashKey, data); +END: + if (transaction != transaction_) { + dataStorage_->ReleaseTransaction(transaction); + transaction = nullptr; + } + + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::DeleteEntriesByHashKey(Version version, const Key &hashKey) +{ + if (transaction_ == nullptr) { + LOGI("You need start transaction before this operation!"); + return -E_NOT_PERMIT; + } + + Value savedValue; + int errCode = transaction_->GetValueForTrimSlice(hashKey, version, savedValue); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + + errCode = transaction_->DeleteEntriesByHashKey(version, hashKey); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + + MultiVerValueObject valueObject; + errCode = valueObject.DeSerialData(savedValue); + // savedValue empty is del or clear record + if (!valueObject.IsHash() || savedValue.empty()) { + return E_OK; + } + if (errCode != E_OK) { + return errCode; + } + + std::vector sliceHashVect; + errCode = valueObject.GetValueHash(sliceHashVect); + if (errCode != E_OK) { + return errCode; + } + + for (const auto &item : sliceHashVect) { + errCode = DeleteValueSliceInner(sliceTransaction_, item); + if (errCode != E_OK) { + LOGI("Value slice delete fail!"); + break; + } + } + + return CheckCorruptedStatus(errCode); +} + +int MultiVerStorageExecutor::UpdateTrimedFlag(Version version, const Key &hashKey) +{ + (void)version; + (void)hashKey; + return E_OK; +} + +int MultiVerStorageExecutor::UpdateTrimedFlag(const CommitID &commit) +{ + (void)commit; + return E_OK; +} +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_storage_executor.h b/mock/distributeddb/storage/src/multiver/multi_ver_storage_executor.h new file mode 100644 index 00000000..4b3f99c6 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_storage_executor.h @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_STORAGE_EXECUTOR_H +#define MULTI_VER_STORAGE_EXECUTOR_H + +#ifndef OMIT_MULTI_VER +#include "storage_executor.h" +#include "ikvdb.h" +#include "ikvdb_commit_storage.h" +#include "ikvdb_multi_ver_data_storage.h" +#include "macro_utils.h" +#include "multi_ver_kvdata_storage.h" + +namespace DistributedDB { +enum class MultiTransactionType { + NORMAL_DATA, + ALL_DATA, +}; + +class MultiVerStorageExecutor : public StorageExecutor { +public: + MultiVerStorageExecutor(IKvDB *kvDB, IKvDBMultiVerDataStorage *dataStorage, IKvDBCommitStorage *commitStorage, + MultiVerKvDataStorage *kvDataStorage, bool writable); + ~MultiVerStorageExecutor() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(MultiVerStorageExecutor); + + int Reset() override; + + int Put(const Key &key, const Value &value); + + int Get(const Key &key, Value &value) const; + + int GetEntries(const Key &keyPrefix, std::vector &entries) const; + + int Delete(const Key &key); + + int Clear(); + + int PutMetaData(const Key &key, const Value &value); + + int GetMetaData(const Key &key, Value &value) const; + + int GetDeviceLatestCommit(std::map &commitMap) const; + + int GetCommitTree(const std::map &commitMap, + std::vector &commits) const; + + bool IsCommitExisted(const MultiVerCommitNode &commit, int &errCode) const; + + int GetCommitData(const MultiVerCommitNode &commit, std::vector &entries) const; + + bool IsValueSliceExisted(const ValueSliceHash &value, int &errCode) const; + + int GetValueSlice(const ValueSliceHash &hashValue, ValueSlice &sliceValue) const; + + int PutValueSlice(const ValueSliceHash &hashValue, const ValueSlice &sliceValue, bool isAddCount); + + int PutCommitData(const MultiVerCommitNode &commit, const std::vector &entries, + const std::string &deviceName); + + int MergeSyncCommit(const MultiVerCommitNode &commit, const std::vector &commits); + + int GetDiffEntries(const CommitID &begin, const CommitID &end, MultiVerDiffData &data) const; + + int StartTransaction(MultiTransactionType type = MultiTransactionType::NORMAL_DATA); + + int CommitTransaction(MultiTransactionType type = MultiTransactionType::NORMAL_DATA); + + int RollBackTransaction(MultiTransactionType type = MultiTransactionType::NORMAL_DATA); + + int InitCurrentReadVersion(); + + void Close(); + + Version GetCurrentReadVersion() const; + + // Get all the commits with the view of one commit. + int GetAllCommitsInTree(std::list &commits) const; + + // Get all the hash key of one version. + int GetEntriesByVersion(Version version, std::list &data) const; + + // Get all the overwritten record whose version is less than the specified version and tag is less the cleard data. + int GetOverwrittenClearTypeEntries(Version clearVersion, std::list &data) const; + + // Get all the overwritten non-cleared record whose version is less than the specified version. + int GetOverwrittenNonClearTypeEntries(Version version, const Key &hashKey, + std::list &data) const; + + // Delete the data whose hash key is equal to the hashKey and version is less than the specified. + int DeleteEntriesByHashKey(Version version, const Key &hashKey); + + // Update the trimmed flag for the hash key with the specified version. + int UpdateTrimedFlag(Version version, const Key &hashKey); + + // Update the trimmed flag for the commit. + int UpdateTrimedFlag(const CommitID &commit); + +private: + static void ReleaseMultiVerKvEntries(std::vector &entries); + + int GetSliceCount(std::vector &&entries, uint32_t &count) const; + + int PutSliceCount(const Key &sliceKey, uint32_t count) const; + + int CommitTransaction(const MultiVerCommitNode &multiVerCommit, bool isMerge); + + int GetResolvedConflictEntries(const MultiVerCommitNode &commitItem, std::vector &entries) const; + + int TransferDiffEntries(MultiVerDiffData &data) const; + + int TransferToUserValue(const Value &savedValue, Value &value) const; + + int TransferToSavedValue(const Value &value, Value &savedValue); + + void CommitNotifiedData(const CommitID &commitId); + + int GetParentCommitId(const CommitID &commitId, CommitID &parentId, Version &curVersion) const; + + int AllocNewCommitId(CommitID &commitId) const; + + int FillAndCommitLogEntry(const Version &versionInfo, CommitID &commitId, uint64_t timestamp) const; + + int FillCommitByForeign(IKvDBCommit *commit, const MultiVerCommitNode &multiVerCommit, + const Version &versionInfo, const CommitID &commitId, bool isMerge) const; + + int FillAndCommitLogEntry(const Version &versionInfo, const MultiVerCommitNode &multiVerCommit, + CommitID &commitId, bool isMerge, Timestamp ×tamp) const; + + int MergeOneCommit(const MultiVerCommitNode &commit); + + int MergeCommits(const std::vector &commits); + + int CommitSyncCommits(); + + int StartAllDbTransaction(); + + int TransferToValueObject(const Value &value, MultiVerValueObject &valueObject); + + int RollBackAllDbTransaction(); + + int CommitAllDbTransaction(); + + int ReInitTransactionVersion(const MultiVerCommitNode &commit); + + int StartSliceTransaction(); + + int CommitSliceTransaction(); + + int RollbackSliceTransaction(); + + int GetValueSliceInner(const SliceTransaction *sliceTransaction, const ValueSliceHash &hashValue, + ValueSlice &sliceValue) const; + + int PutValueSliceInner(SliceTransaction *sliceTransaction, const ValueSliceHash &hashValue, + const ValueSlice &sliceValue, bool isAddCount); + + int DeleteValueSliceInner(SliceTransaction *sliceTransaction, const ValueSliceHash &hashValue); + + int AddSliceDataCount(const std::vector &values); + + static const int COMMIT_ID_LENGTH = 20; + IKvDB *kvDB_; + IKvDBMultiVerDataStorage *dataStorage_; + IKvDBCommitStorage *commitStorage_; + MultiVerKvDataStorage *kvDataStorage_; + IKvDBMultiVerTransaction *transaction_; + SliceTransaction *sliceTransaction_; + Version readVersion_ = 0; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_STORAGE_EXECUTOR_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_vacuum.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_vacuum.cpp new file mode 100644 index 00000000..9014b470 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_vacuum.cpp @@ -0,0 +1,674 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_vacuum.h" + +#include +#include +#include + +#include "db_errno.h" +#include "db_common.h" +#include "log_print.h" +#include "macro_utils.h" +#include "runtime_context.h" + +namespace DistributedDB { +std::atomic MultiVerVacuum::enabled_{true}; + +void MultiVerVacuum::Enable(bool isEnable) +{ + enabled_ = isEnable; +} + +int MultiVerVacuum::Launch(const std::string &dbIdentifier, MultiVerVacuumExecutor *dbHandle) +{ + if (!enabled_) { + LOGW("[Vacuum][Launch] Functionality Not Enabled!"); + return E_OK; + } + if (dbIdentifier.empty() || dbHandle == nullptr) { + return -E_INVALID_ARGS; + } + + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + if (dbMapVacuumTask_.count(dbIdentifier) == 0) { + dbMapVacuumTask_[dbIdentifier].runWaitOrder = incRunWaitOrder_++; + dbMapVacuumTask_[dbIdentifier].databaseHandle = dbHandle; + } else if (dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::ABORT_DONE || + dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::FINISH) { + // Reset vacuum task + dbMapVacuumTask_[dbIdentifier].status = VacuumTaskStatus::RUN_WAIT; + dbMapVacuumTask_[dbIdentifier].launchErrorHappen = false; + dbMapVacuumTask_[dbIdentifier].autoRelaunchOnce = false; + dbMapVacuumTask_[dbIdentifier].immediatelyRelaunchable = true; + dbMapVacuumTask_[dbIdentifier].runWaitOrder = incRunWaitOrder_++; + dbMapVacuumTask_[dbIdentifier].pauseNeedCount = 0; + dbMapVacuumTask_[dbIdentifier].databaseHandle = dbHandle; + } else { + dbMapVacuumTask_[dbIdentifier].launchErrorHappen = true; + LOGE("[Vacuum][Launch] Unexpected pre-status=%d!", static_cast(dbMapVacuumTask_[dbIdentifier].status)); + return -E_NOT_PERMIT; + } + ActivateBackgroundVacuumTaskExecution(); + return E_OK; +} + +int MultiVerVacuum::Pause(const std::string &dbIdentifier) +{ + if (!enabled_) { + return E_OK; + } + if (dbIdentifier.empty()) { + return -E_INVALID_ARGS; + } + + std::unique_lock vacuumTaskLockGuard(vacuumTaskMutex_); + if (dbMapVacuumTask_.count(dbIdentifier) == 0) { + return -E_NOT_FOUND; + } else if (dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::RUN_WAIT || + dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::PAUSE_DONE) { + dbMapVacuumTask_[dbIdentifier].status = VacuumTaskStatus::PAUSE_DONE; + dbMapVacuumTask_[dbIdentifier].immediatelyRelaunchable = false; + IncPauseNeedCount(dbMapVacuumTask_[dbIdentifier]); + } else if (dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::RUN_NING || + dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::PAUSE_WAIT) { + dbMapVacuumTask_[dbIdentifier].status = VacuumTaskStatus::PAUSE_WAIT; + dbMapVacuumTask_[dbIdentifier].immediatelyRelaunchable = false; + IncPauseNeedCount(dbMapVacuumTask_[dbIdentifier]); + vacuumTaskCv_.wait(vacuumTaskLockGuard, [this, &dbIdentifier] { + // In concurrency scenario that executor is about to finish this task, the final status may be FINISH. + // Even more, in case Abort be called immediately after task finished, the final status may be ABORT_DONE. + return dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::PAUSE_DONE || + dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::ABORT_DONE || + dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::FINISH; + }); + } else if (dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::FINISH) { + dbMapVacuumTask_[dbIdentifier].immediatelyRelaunchable = false; + IncPauseNeedCount(dbMapVacuumTask_[dbIdentifier]); + } else { + LOGE("[Vacuum][Pause] Unexpected pre-status=%d!", static_cast(dbMapVacuumTask_[dbIdentifier].status)); + return -E_NOT_PERMIT; + } + return E_OK; +} + +int MultiVerVacuum::Continue(const std::string &dbIdentifier, bool autoRelaunchOnce) +{ + if (!enabled_) { + return E_OK; + } + if (dbIdentifier.empty()) { + return -E_INVALID_ARGS; + } + + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + if (dbMapVacuumTask_.count(dbIdentifier) == 0) { + return -E_NOT_FOUND; + } else if (dbMapVacuumTask_[dbIdentifier].launchErrorHappen) { + LOGE("[Vacuum][Continue] LaunchErrorHappen detected, pre-status=%d!", + static_cast(dbMapVacuumTask_[dbIdentifier].status)); + return -E_NOT_PERMIT; + } else if (dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::PAUSE_DONE) { + DecPauseNeedCount(dbMapVacuumTask_[dbIdentifier]); + bool relaunchFlag = (dbMapVacuumTask_[dbIdentifier].autoRelaunchOnce || autoRelaunchOnce); + dbMapVacuumTask_[dbIdentifier].autoRelaunchOnce = relaunchFlag; + // Truly continue this task only when all pause had been counteracted + if (IsPauseNotNeed(dbMapVacuumTask_[dbIdentifier])) { + dbMapVacuumTask_[dbIdentifier].status = VacuumTaskStatus::RUN_WAIT; + dbMapVacuumTask_[dbIdentifier].runWaitOrder = incRunWaitOrder_++; + dbMapVacuumTask_[dbIdentifier].immediatelyRelaunchable = true; + ActivateBackgroundVacuumTaskExecution(); + } + } else if (dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::FINISH) { + // Update relaunch flag first + DecPauseNeedCount(dbMapVacuumTask_[dbIdentifier]); + bool relaunchFlag = (dbMapVacuumTask_[dbIdentifier].autoRelaunchOnce || autoRelaunchOnce); + dbMapVacuumTask_[dbIdentifier].autoRelaunchOnce = relaunchFlag; + // All pause had been counteracted, so this task is immediatelyRelaunchable, but not necessarily relaunch now. + if (IsPauseNotNeed(dbMapVacuumTask_[dbIdentifier])) { + dbMapVacuumTask_[dbIdentifier].immediatelyRelaunchable = true; + // Do autoRelaunch if need + if (dbMapVacuumTask_[dbIdentifier].autoRelaunchOnce) { + dbMapVacuumTask_[dbIdentifier].status = VacuumTaskStatus::RUN_WAIT; + dbMapVacuumTask_[dbIdentifier].runWaitOrder = incRunWaitOrder_++; + dbMapVacuumTask_[dbIdentifier].autoRelaunchOnce = false; + ActivateBackgroundVacuumTaskExecution(); + } + } + } else { + LOGE("[Vacuum][Continue] Unexpected pre-status=%d!", static_cast(dbMapVacuumTask_[dbIdentifier].status)); + return -E_NOT_PERMIT; + } + return E_OK; +} + +int MultiVerVacuum::Abort(const std::string &dbIdentifier) +{ + if (!enabled_) { + return E_OK; + } + if (dbIdentifier.empty()) { + return -E_INVALID_ARGS; + } + + // The pauseNeedCount must be zero in RUN_WAIT and RUN_NING case, but not always zero in FINISH case. + // If pause is called more than continue, status may be PAUSE_WAIT, PAUSE_DONE, which is not expected. + // The pauseNeedCount, runWaitOrder and autoRelaunchOnce will be reset when launch(Not Auto) if abort normally + std::unique_lock vacuumTaskLockGuard(vacuumTaskMutex_); + if (dbMapVacuumTask_.count(dbIdentifier) == 0) { + return -E_NOT_FOUND; + } else if (dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::RUN_WAIT || + dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::PAUSE_DONE || + dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::FINISH) { + dbMapVacuumTask_[dbIdentifier].status = VacuumTaskStatus::ABORT_DONE; + dbMapVacuumTask_[dbIdentifier].launchErrorHappen = false; + dbMapVacuumTask_[dbIdentifier].immediatelyRelaunchable = false; + // In this place, the background will not access information of this vacuum task + dbMapVacuumTask_[dbIdentifier].databaseHandle = nullptr; + ResetNodeAndRecordContextInfo(dbMapVacuumTask_[dbIdentifier]); + } else if (dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::RUN_NING || + dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::PAUSE_WAIT) { + dbMapVacuumTask_[dbIdentifier].status = VacuumTaskStatus::ABORT_WAIT; + dbMapVacuumTask_[dbIdentifier].immediatelyRelaunchable = false; + vacuumTaskCv_.wait(vacuumTaskLockGuard, [this, &dbIdentifier] { + // In concurrency scenario that executor is about to finish this task, the final status may be FINISH + return dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::ABORT_DONE || + dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::FINISH; + }); + // Resource is cleaned by background task, still set ABORT_DONE and reset launchErrorHappen and databaseHandle. + dbMapVacuumTask_[dbIdentifier].status = VacuumTaskStatus::ABORT_DONE; + dbMapVacuumTask_[dbIdentifier].launchErrorHappen = false; + dbMapVacuumTask_[dbIdentifier].databaseHandle = nullptr; + } else { + LOGE("[Vacuum][Abort] Unexpected pre-status=%d!", static_cast(dbMapVacuumTask_[dbIdentifier].status)); + return -E_NOT_PERMIT; + } + return E_OK; +} + +int MultiVerVacuum::AutoRelaunchOnce(const std::string &dbIdentifier) +{ + if (!enabled_) { + return E_OK; + } + if (dbIdentifier.empty()) { + return -E_INVALID_ARGS; + } + + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + if (dbMapVacuumTask_.count(dbIdentifier) == 0) { + return -E_NOT_FOUND; + } else if (dbMapVacuumTask_[dbIdentifier].launchErrorHappen) { + LOGE("[Vacuum][AutoRelaunch] LaunchErrorHappen detected, pre-status=%d!", + static_cast(dbMapVacuumTask_[dbIdentifier].status)); + return -E_NOT_PERMIT; + } else if (dbMapVacuumTask_[dbIdentifier].status == VacuumTaskStatus::FINISH && + dbMapVacuumTask_[dbIdentifier].immediatelyRelaunchable) { + // Relaunch this task immediately + dbMapVacuumTask_[dbIdentifier].status = VacuumTaskStatus::RUN_WAIT; + dbMapVacuumTask_[dbIdentifier].autoRelaunchOnce = false; + dbMapVacuumTask_[dbIdentifier].runWaitOrder = incRunWaitOrder_++; + } else { + // Set flag true in order to Relaunch this task once when it finish + dbMapVacuumTask_[dbIdentifier].autoRelaunchOnce = true; + } + ActivateBackgroundVacuumTaskExecution(); + return E_OK; +} + +int MultiVerVacuum::QueryStatus(const std::string &dbIdentifier, VacuumTaskStatus &outStatus) const +{ + if (dbIdentifier.empty()) { + return -E_INVALID_ARGS; + } + + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + if (dbMapVacuumTask_.count(dbIdentifier) == 0) { + return -E_NOT_FOUND; + } + + outStatus = dbMapVacuumTask_.at(dbIdentifier).status; + return E_OK; +} + +MultiVerVacuum::~MultiVerVacuum() +{ + // Mainly for stop the background task, resources automatically clean by this deconstruction + std::unique_lock vacuumTaskLockGuard(vacuumTaskMutex_); + for (auto &each : dbMapVacuumTask_) { + if (each.second.status == VacuumTaskStatus::RUN_WAIT || each.second.status == VacuumTaskStatus::PAUSE_DONE) { + // For RUN_WAIT and PAUSE_DONE, change to ABORT_DONE + each.second.status = VacuumTaskStatus::ABORT_DONE; + } else if (each.second.status == VacuumTaskStatus::RUN_NING || + each.second.status == VacuumTaskStatus::PAUSE_WAIT) { + // For RUN_NING and PAUSE_WAIT, change to ABORT_WAIT + each.second.status = VacuumTaskStatus::ABORT_WAIT; + } + // For ABORT_WAIT, ABORT_DONE and FINISH, remain as it is. + } + // Wait for background task to quit + vacuumTaskCv_.wait(vacuumTaskLockGuard, [this] { + return !isBackgroundVacuumTaskInExecution_; + }); +} + +void MultiVerVacuum::VacuumTaskExecutor() +{ + // Endless loop until nothing to do + while (true) { + std::string nextDatabase; + { + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + int errCode = SearchVacuumTaskToExecute(nextDatabase); + if (errCode != E_OK) { + LOGI("[Vacuum][Executor] No available task to execute, about to quit."); + isBackgroundVacuumTaskInExecution_ = false; + // Awake the deconstruction that background thread is about to quit + vacuumTaskCv_.notify_all(); + return; + } + } + // No thread will remove entry from dbMapVacuumTask_, so here is concurrency safe. + LOGI("[Vacuum][Executor] Execute vacuum task for database=%s.", nextDatabase.c_str()); + ExecuteSpecificVacuumTask(dbMapVacuumTask_[nextDatabase]); + // Awake foreground thread at this task switch point + vacuumTaskCv_.notify_all(); + } +} + +void MultiVerVacuum::ExecuteSpecificVacuumTask(VacuumTaskContext &inTask) +{ + // No other thread will access handle, node and record field of a RUN_NING, PAUSE_WAIT, ABORT_WAIT status task + // So it is concurrency safe to access or change these field without protection of lockguard + if (inTask.leftBranchCommits.empty() && inTask.rightBranchCommits.empty()) { + // Newly launched task + int errCode = inTask.databaseHandle->GetVacuumAbleCommits(inTask.leftBranchCommits, inTask.rightBranchCommits); + if (errCode != E_OK) { + LOGE("[Vacuum][Execute] GetVacuumAbleCommits fail, errCode=%d.", errCode); + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + FinishVaccumTask(inTask); + return; + } + } + + // Vacuum left branch first, since record of left branch will be synced out, more urgently + while (!inTask.leftBranchCommits.empty()) { + int errCode = DealWithLeftBranchCommit(inTask); + if (errCode != E_OK) { + return; + } + } + LOGD("[Vacuum][Execute] All vacuum able commits of left branch have been dealt with for this database!"); + + // Vacuum right branch later, since record of right branch will not be synced out, not so urgent + while (!inTask.rightBranchCommits.empty()) { + int errCode = DealWithRightBranchCommit(inTask); + if (errCode != E_OK) { + return; + } + } + LOGD("[Vacuum][Execute] All vacuum able commits of right branch have been dealt with for this database!"); + + // Commit changes before finish this task, if fail, just finish it(commit fail auto rollback) + int errCode = CommitTransactionIfNeed(inTask); + if (errCode != E_OK) { + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + FinishVaccumTask(inTask); + return; + } + + // Every commit of this task has been treated, consider finish or relaunch the task + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + if (inTask.status == VacuumTaskStatus::RUN_NING && inTask.autoRelaunchOnce) { + RelaunchVacuumTask(inTask); + } else { + // If in PAUSE_WAIT or ABORT_WAIT status, shall not relaunch it, just finish it to make sure it be unactive + // The autoRelaunchOnce will be set false, if need relaunch, the continue operation will set it true again + FinishVaccumTask(inTask); + } +} + +int MultiVerVacuum::DealWithLeftBranchCommit(VacuumTaskContext &inTask) +{ + return DoDealCommitOfLeftOrRight(inTask, inTask.leftBranchCommits, true); +} + +int MultiVerVacuum::DealWithLeftBranchVacuumNeedRecord(VacuumTaskContext &inTask) +{ + int errCode = DoCommitAndQuitIfWaitStatusObserved(inTask); + if (errCode != E_OK) { + return errCode; + } + // No other thread will access handle, node and record field of a RUN_NING, PAUSE_WAIT, ABORT_WAIT status task + // So it is concurrency safe to access or change these field without protection of lockguard + const MultiVerRecordInfo &record = inTask.vacuumNeedRecords.front(); + LOGD("[Vacuum][DealLeftRecord] Type=%" PRIu32 ", Version=%" PRIu64 ", HashKey=%s.", + static_cast(record.type), record.version, VEC_TO_STR(record.hashKey)); + if (inTask.shadowRecords.empty()) { + if (record.type == RecordType::CLEAR) { + errCode = inTask.databaseHandle->GetShadowRecordsOfClearTypeRecord(record.version, record.hashKey, + inTask.shadowRecords); + } else { + errCode = inTask.databaseHandle->GetShadowRecordsOfNonClearTypeRecord(record.version, record.hashKey, + inTask.shadowRecords); + } + if (errCode != E_OK) { + LOGE("[Vacuum][DealLeftRecord] GetShadowRecords fail, Type=%d, Version=%llu, HashKey=%s, errCode=%d.", + static_cast(record.type), ULL(record.version), VEC_TO_STR(record.hashKey), errCode); + DoRollBackAndFinish(inTask); + return errCode; + } + } + + while (!inTask.shadowRecords.empty()) { + errCode = DealWithLeftBranchShadowRecord(inTask); + if (errCode != E_OK) { + return errCode; + } + } + + // Every shadowRecords of this vacuumNeedRecord has been treated, mark this vacuumNeedRecord as vacuum done + errCode = StartTransactionIfNotYet(inTask); + if (errCode != E_OK) { + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + FinishVaccumTask(inTask); + return errCode; + } + errCode = inTask.databaseHandle->MarkRecordAsVacuumDone(record.version, record.hashKey); + if (errCode != E_OK) { + LOGE("[Vacuum][DealLeftRecord] MarkRecordAsVacuumDone fail, Type=%d, Version=%llu, HashKey=%s, errCode=%d.", + static_cast(record.type), ULL(record.version), VEC_TO_STR(record.hashKey), errCode); + DoRollBackAndFinish(inTask); + return errCode; + } + // Pop out this vacuumNeedRecord + inTask.vacuumNeedRecords.pop_front(); + return E_OK; +} + +int MultiVerVacuum::DealWithLeftBranchShadowRecord(VacuumTaskContext &inTask) +{ + return DoDeleteRecordOfLeftShadowOrRightVacuumNeed(inTask, inTask.shadowRecords); +} + +int MultiVerVacuum::DealWithRightBranchCommit(VacuumTaskContext &inTask) +{ + return DoDealCommitOfLeftOrRight(inTask, inTask.rightBranchCommits, false); +} + +int MultiVerVacuum::DealWithRightBranchVacuumNeedRecord(VacuumTaskContext &inTask) +{ + return DoDeleteRecordOfLeftShadowOrRightVacuumNeed(inTask, inTask.vacuumNeedRecords); +} + +int MultiVerVacuum::DoDealCommitOfLeftOrRight(VacuumTaskContext &inTask, std::list &commitList, + bool isLeft) +{ + int errCode = DoCommitAndQuitIfWaitStatusObserved(inTask); + if (errCode != E_OK) { + return errCode; + } + // No other thread will access handle, node and record field of a RUN_NING, PAUSE_WAIT, ABORT_WAIT status task + // So it is concurrency safe to access or change these field without protection of lockguard + const MultiVerCommitInfo &commit = commitList.front(); + LOGD("[Vacuum][DoDealCommit] Version=%llu, CommitId=%s, isLeft=%d.", ULL(commit.version), + VEC_TO_STR(commit.commitId), isLeft); + if (inTask.vacuumNeedRecords.empty()) { + errCode = inTask.databaseHandle->GetVacuumNeedRecordsByVersion(commit.version, inTask.vacuumNeedRecords); + if (errCode != E_OK) { + LOGE("[Vacuum][DoDealCommit] GetVacuumNeedRecordsByVersion fail, Version=%llu, CommitId=%s, isLeft=%d, " + "errCode=%d.", ULL(commit.version), VEC_TO_STR(commit.commitId), isLeft, errCode); + DoRollBackAndFinish(inTask); + return errCode; + } + } + + while (!inTask.vacuumNeedRecords.empty()) { + if (isLeft) { + errCode = DealWithLeftBranchVacuumNeedRecord(inTask); + } else { + errCode = DealWithRightBranchVacuumNeedRecord(inTask); + } + if (errCode != E_OK) { + return errCode; + } + } + + // Every vacuumNeedRecords of this commit has been treated, mark this commit as vacuum done + errCode = StartTransactionIfNotYet(inTask); + if (errCode != E_OK) { + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + FinishVaccumTask(inTask); + return errCode; + } + errCode = inTask.databaseHandle->MarkCommitAsVacuumDone(commit.commitId); + if (errCode != E_OK) { + LOGE("[Vacuum][DoDealCommit] MarkCommitAsVacuumDone fail, Version=%llu, CommitId=%s, isLeft=%d, errCode=%d.", + ULL(commit.version), VEC_TO_STR(commit.commitId), isLeft, errCode); + DoRollBackAndFinish(inTask); + return errCode; + } + // Pop out this commit + commitList.pop_front(); + return E_OK; +} + +int MultiVerVacuum::DoDeleteRecordOfLeftShadowOrRightVacuumNeed(VacuumTaskContext &inTask, + std::list &recordList) +{ + int errCode = DoCommitAndQuitIfWaitStatusObserved(inTask); + if (errCode != E_OK) { + return errCode; + } + // No other thread will access handle, node and record field of a RUN_NING, PAUSE_WAIT, ABORT_WAIT status task + // So it is concurrency safe to access or change these field without protection of lockguard + const MultiVerRecordInfo &record = recordList.front(); + LOGD("[Vacuum][DoDeleteRecord] Type=%u, Version=%llu, HashKey=%s.", static_cast(record.type), + ULL(record.version), VEC_TO_STR(record.hashKey)); + errCode = StartTransactionIfNotYet(inTask); + if (errCode != E_OK) { + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + FinishVaccumTask(inTask); + return errCode; + } + errCode = inTask.databaseHandle->DeleteRecordTotally(record.version, record.hashKey); + if (errCode != E_OK) { + LOGE("[Vacuum][DoDeleteRecord] DeleteRecordTotally fail, Type=%u, Version=%llu, HashKey=%s, errCode=%d.", + static_cast(record.type), ULL(record.version), VEC_TO_STR(record.hashKey), errCode); + DoRollBackAndFinish(inTask); + return errCode; + } + // Pop out this shadowRecord or vacuumNeedRecord + recordList.pop_front(); + return E_OK; +} + +void MultiVerVacuum::DoRollBackAndFinish(VacuumTaskContext &inTask) +{ + RollBackTransactionIfNeed(inTask); + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + FinishVaccumTask(inTask); +} + +int MultiVerVacuum::DoCommitAndQuitIfWaitStatusObserved(VacuumTaskContext &inTask) +{ + bool waitStatusObserved = false; + { + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + if (inTask.status == VacuumTaskStatus::PAUSE_WAIT || inTask.status == VacuumTaskStatus::ABORT_WAIT) { + waitStatusObserved = true; + } + } + // Only this TaskThread will change a PAUSE_WAIT or ABORT_WAIT status to other status + // So here during the gap of miss-lockguard-protection, the status of this inTask will not change + if (waitStatusObserved) { + // CommitTransactionIfNeed may be an time cost operation, should not be called within the range of lockguard + int errCode = CommitTransactionIfNeed(inTask); + // Change status operation should be protected within the lockguard + std::lock_guard vacuumTaskLockGuard(vacuumTaskMutex_); + if (errCode != E_OK) { + // If commit fail, just finish this task(commit fail auto rollback) + FinishVaccumTask(inTask); + return errCode; + } + if (inTask.status == VacuumTaskStatus::ABORT_WAIT) { + AbortVacuumTask(inTask); + return -E_TASK_BREAK_OFF; + } + // Nor commit fail, nor Abort_wait case, here is Pause_wait Case, just set status to Pause_done + inTask.status = VacuumTaskStatus::PAUSE_DONE; + return -E_TASK_BREAK_OFF; + } + return E_OK; +} + +int MultiVerVacuum::StartTransactionIfNotYet(VacuumTaskContext &inTask) +{ + if (!inTask.isTransactionStarted) { + int errCode = inTask.databaseHandle->StartTransactionForVacuum(); + if (errCode != E_OK) { + LOGE("[Vacuum][StartTransact] StartTransactionForVacuum fail, errCode=%d.", errCode); + return errCode; + } + inTask.isTransactionStarted = true; + } + return E_OK; +} + +int MultiVerVacuum::CommitTransactionIfNeed(VacuumTaskContext &inTask) +{ + if (inTask.isTransactionStarted) { + // Whether CommitTransactionForVacuum fail or not, the transaction is ended. + inTask.isTransactionStarted = false; + int errCode = inTask.databaseHandle->CommitTransactionForVacuum(); + if (errCode != E_OK) { + LOGE("[Vacuum][CommitTransact] CommitTransactionForVacuum fail, errCode=%d.", errCode); + return errCode; + } + } + return E_OK; +} + +void MultiVerVacuum::RollBackTransactionIfNeed(VacuumTaskContext &inTask) +{ + if (inTask.isTransactionStarted) { + // Whether RollBackTransactionForVacuum fail or not, the transaction is ended. + inTask.isTransactionStarted = false; + int errCode = inTask.databaseHandle->RollBackTransactionForVacuum(); + if (errCode != E_OK) { + LOGE("[Vacuum][RollBackTransact] RollBackTransactionForVacuum fail, errCode=%d.", errCode); + } + } +} + +void MultiVerVacuum::FinishVaccumTask(VacuumTaskContext &inTask) +{ + inTask.status = VacuumTaskStatus::FINISH; + // It is OK to reset the autoRelaunchOnce. Since this is called when this task is RUN_NING status, all pause to + // this task will block and wait, and all continue to this task happens after we reset the autoRelaunchOnce + inTask.autoRelaunchOnce = false; + // Do not reset the databaseHandle while finish a task, because it will be reused after autoRelaunch + ResetNodeAndRecordContextInfo(inTask); +} + +void MultiVerVacuum::RelaunchVacuumTask(VacuumTaskContext &inTask) +{ + inTask.status = VacuumTaskStatus::RUN_WAIT; + inTask.runWaitOrder = incRunWaitOrder_++; // Queue at the back + inTask.autoRelaunchOnce = false; + // Obviously can not reset the databaseHandle while relaunch a task + ResetNodeAndRecordContextInfo(inTask); +} + +void MultiVerVacuum::AbortVacuumTask(VacuumTaskContext &inTask) +{ + inTask.status = VacuumTaskStatus::ABORT_DONE; + inTask.autoRelaunchOnce = false; + inTask.databaseHandle = nullptr; // reset handle in abort case + ResetNodeAndRecordContextInfo(inTask); +} + +void MultiVerVacuum::ResetNodeAndRecordContextInfo(VacuumTaskContext &inTask) +{ + inTask.leftBranchCommits.clear(); + inTask.rightBranchCommits.clear(); + inTask.vacuumNeedRecords.clear(); + inTask.shadowRecords.clear(); + inTask.isTransactionStarted = false; +} + +int MultiVerVacuum::SearchVacuumTaskToExecute(std::string &outDbIdentifier) +{ + // Find a vacuum task with the smallest runWaitOrder among tasks that is in RUN_WAIT Status(Except In Error). + uint64_t minRunWaitOrder = UINT64_MAX; + for (auto &eachTask : dbMapVacuumTask_) { + LOGD("[Vacuum][Search] db=%s, status=%d, error=%d, relaunch=%d, immediate=%d, runWait=%llu, pauseCount=%llu.", + eachTask.first.c_str(), static_cast(eachTask.second.status), eachTask.second.launchErrorHappen, + eachTask.second.autoRelaunchOnce, eachTask.second.immediatelyRelaunchable, + ULL(eachTask.second.runWaitOrder), ULL(eachTask.second.pauseNeedCount)); + if (eachTask.second.status == VacuumTaskStatus::RUN_WAIT && !eachTask.second.launchErrorHappen) { + if (eachTask.second.runWaitOrder < minRunWaitOrder) { + minRunWaitOrder = eachTask.second.runWaitOrder; + outDbIdentifier = eachTask.first; + } + } + } + if (!outDbIdentifier.empty()) { + dbMapVacuumTask_[outDbIdentifier].status = VacuumTaskStatus::RUN_NING; + return E_OK; + } else { + return -E_NOT_FOUND; + } +} + +void MultiVerVacuum::ActivateBackgroundVacuumTaskExecution() +{ + if (!isBackgroundVacuumTaskInExecution_) { + TaskAction backgroundTask = [this]() { + LOGI("[Vacuum][Activate] Begin Background Execution."); + VacuumTaskExecutor(); + LOGI("[Vacuum][Activate] End Background Execution."); + }; + int errCode = RuntimeContext::GetInstance()->ScheduleTask(backgroundTask); + if (errCode != E_OK) { + LOGE("[Vacuum][Activate] ScheduleTask failed, errCode = %d.", errCode); + return; + } + isBackgroundVacuumTaskInExecution_ = true; + } +} + +void MultiVerVacuum::IncPauseNeedCount(VacuumTaskContext &inTask) +{ + inTask.pauseNeedCount++; +} + +void MultiVerVacuum::DecPauseNeedCount(VacuumTaskContext &inTask) +{ + if (inTask.pauseNeedCount == 0) { + LOGE("[Vacuum][DecPause] PauseNeedCount Zero Before Decrease."); + return; + } + inTask.pauseNeedCount--; +} + +bool MultiVerVacuum::IsPauseNotNeed(VacuumTaskContext &inTask) +{ + return inTask.pauseNeedCount == 0; +} +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_vacuum_executor_impl.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_vacuum_executor_impl.cpp new file mode 100644 index 00000000..c2047fe5 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_vacuum_executor_impl.cpp @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_vacuum_executor_impl.h" +#include "db_errno.h" +#include "log_print.h" +#include "multi_ver_storage_executor.h" + +namespace DistributedDB { +namespace { + const uint64_t DEL_FLAG = 0x02; // Del type record flag in OperFlag + const uint64_t CLEAR_FLAG = 0x03; // Clear type record flag in OperFlag + const uint64_t MASK_FLAG = 0x07; // mask. +} + +MultiVerVacuumExecutorImpl::MultiVerVacuumExecutorImpl(MultiVerNaturalStore *multiKvDB) + : multiKvDB_(multiKvDB), writeHandle_(nullptr) +{ +} + +MultiVerVacuumExecutorImpl::~MultiVerVacuumExecutorImpl() +{ + // In abnormal case that transaction not commit or rollback + if (multiKvDB_ != nullptr && writeHandle_ != nullptr) { + multiKvDB_->ReleaseHandle(writeHandle_, true); + } +} + +// Call this always beyond transaction +int MultiVerVacuumExecutorImpl::GetVacuumAbleCommits(std::list &leftBranchCommits, + std::list &rightBranchCommits) const +{ + if (multiKvDB_ == nullptr) { + return -E_INVALID_DB; + } + if (writeHandle_ != nullptr) { + LOGE("[VacuumExec][GetCommit] Mis-Called Within Transaction"); + return -E_NOT_PERMIT; + } + + // It will return at least zero, it's ok. return at most UINT64_MAX to means that all left commit are vacuumable. + uint64_t maxVersionOfVacuumAbleLeftCommit = multiKvDB_->GetMaxTrimmableVersion(); + + int errCode = E_OK; + MultiVerStorageExecutor *readHandle = multiKvDB_->GetHandle(false, errCode, true); + if (errCode != E_OK || readHandle == nullptr) { + LOGE("[VacuumExec][GetCommit] GetHandle fail, errCode=%d", errCode); + return errCode; + } + + std::list commitsInTree; + errCode = readHandle->GetAllCommitsInTree(commitsInTree); + if (errCode != E_OK) { + LOGE("[VacuumExec][GetCommit] GetAllCommitsInTree fail, errCode=%d", errCode); + multiKvDB_->ReleaseHandle(readHandle, true); + return errCode; + } + + // As discussed and agreed, the commit in commitsInTree had already be sorted in descending order by version + for (auto &eachCommit : commitsInTree) { + if (eachCommit.isLocal) { + if (eachCommit.version > maxVersionOfVacuumAbleLeftCommit) { + continue; + } + leftBranchCommits.emplace_back(MultiVerCommitInfo{eachCommit.version, eachCommit.commitId}); + } else { + rightBranchCommits.emplace_back(MultiVerCommitInfo{eachCommit.version, eachCommit.commitId}); + } + } + + multiKvDB_->ReleaseHandle(readHandle, true); + return E_OK; +} + +// Call this within or beyond transaction +int MultiVerVacuumExecutorImpl::GetVacuumNeedRecordsByVersion(uint64_t version, + std::list &vacuumNeedRecords) +{ + if (multiKvDB_ == nullptr) { + return -E_INVALID_DB; + } + MultiVerStorageExecutor *handle = GetCorrectHandleForUse(); + if (handle == nullptr) { + return -E_NO_RESOURCE_FOR_USE; + } + + std::list recordsInCommit; + int errCode = handle->GetEntriesByVersion(version, recordsInCommit); + if (errCode != E_OK) { + LOGE("[VacuumExec][GetVacuumNeed] GetEntriesByVersion fail, errCode=%d", errCode); + ReleaseHandleIfNeed(handle); + return errCode; + } + + for (auto &eachRecord : recordsInCommit) { + vacuumNeedRecords.emplace_back(MultiVerRecordInfo{GetRecordType(eachRecord), eachRecord.version, + eachRecord.key}); + } + + ReleaseHandleIfNeed(handle); + return E_OK; +} + +// Call this within or beyond transaction +int MultiVerVacuumExecutorImpl::GetShadowRecordsOfClearTypeRecord(uint64_t version, + const std::vector &hashKey, std::list &shadowRecords) +{ + if (multiKvDB_ == nullptr) { + return -E_INVALID_DB; + } + MultiVerStorageExecutor *handle = GetCorrectHandleForUse(); + if (handle == nullptr) { + return -E_NO_RESOURCE_FOR_USE; + } + + std::list clearShadowRecords; + int errCode = handle->GetOverwrittenClearTypeEntries(version, clearShadowRecords); + if (errCode != E_OK) { + LOGE("[VacuumExec][GetShadowClear] GetOverwrittenClearTypeEntries:%zu fail, err=%d", hashKey.size(), errCode); + ReleaseHandleIfNeed(handle); + return errCode; + } + + for (auto &eachRecord : clearShadowRecords) { + shadowRecords.emplace_back(MultiVerRecordInfo{GetRecordType(eachRecord), eachRecord.version, eachRecord.key}); + } + + ReleaseHandleIfNeed(handle); + return E_OK; +} + +// Call this within or beyond transaction +int MultiVerVacuumExecutorImpl::GetShadowRecordsOfNonClearTypeRecord(uint64_t version, + const std::vector &hashKey, std::list &shadowRecords) +{ + if (multiKvDB_ == nullptr) { + return -E_INVALID_DB; + } + MultiVerStorageExecutor *handle = GetCorrectHandleForUse(); + if (handle == nullptr) { + return -E_NO_RESOURCE_FOR_USE; + } + + std::list nonClearShadowRecords; + int errCode = handle->GetOverwrittenNonClearTypeEntries(version, hashKey, nonClearShadowRecords); + if (errCode != E_OK) { + LOGE("[VacuumExec][GetShadowNonClear] GetOverwrittenNonClearTypeEntries fail, errCode=%d", errCode); + ReleaseHandleIfNeed(handle); + return errCode; + } + + for (auto &eachRecord : nonClearShadowRecords) { + shadowRecords.emplace_back(MultiVerRecordInfo{GetRecordType(eachRecord), eachRecord.version, eachRecord.key}); + } + + ReleaseHandleIfNeed(handle); + return E_OK; +} + +// Call this before change the database +int MultiVerVacuumExecutorImpl::StartTransactionForVacuum() +{ + if (multiKvDB_ == nullptr) { + return -E_INVALID_DB; + } + if (writeHandle_ != nullptr) { + LOGE("[VacuumExec][Start] Transaction Already Started."); + return -E_NOT_PERMIT; + } + + int errCode = E_OK; + writeHandle_ = multiKvDB_->GetHandle(true, errCode, true); + if (errCode != E_OK || writeHandle_ == nullptr) { + LOGE("[VacuumExec][Start] GetHandle fail, errCode=%d", errCode); + return errCode; + } + + errCode = writeHandle_->StartTransaction(MultiTransactionType::ALL_DATA); + if (errCode != E_OK) { + LOGE("[VacuumExec][Start] StartTransaction fail, errCode=%d", errCode); + multiKvDB_->ReleaseHandle(writeHandle_, true); + writeHandle_ = nullptr; + return errCode; + } + return E_OK; +} + +// Call this if nothing error happened, if this itself failed, do not need to call rollback +int MultiVerVacuumExecutorImpl::CommitTransactionForVacuum() +{ + if (multiKvDB_ == nullptr) { + return -E_INVALID_DB; + } + if (writeHandle_ == nullptr) { + LOGE("[VacuumExec][Commit] Transaction Had Not Been Started."); + return -E_NOT_PERMIT; + } + + int errCode = writeHandle_->CommitTransaction(MultiTransactionType::ALL_DATA); + if (errCode != E_OK) { + // Commit fail do not need to call rollback which is automatically + LOGE("[VacuumExec][Commit] CommitTransaction fail, errCode=%d", errCode); + } + multiKvDB_->ReleaseHandle(writeHandle_, true); + writeHandle_ = nullptr; + return errCode; +} + +// Call this if anything wrong happened after start transaction except commit fail +int MultiVerVacuumExecutorImpl::RollBackTransactionForVacuum() +{ + if (multiKvDB_ == nullptr) { + return -E_INVALID_DB; + } + if (writeHandle_ == nullptr) { + LOGE("[VacuumExec][RollBack] Transaction Had Not Been Started."); + return -E_NOT_PERMIT; + } + + int errCode = writeHandle_->RollBackTransaction(MultiTransactionType::ALL_DATA); + if (errCode != E_OK) { + LOGE("[VacuumExec][RollBack] RollBackTransaction fail, errCode=%d", errCode); + } + multiKvDB_->ReleaseHandle(writeHandle_, true); + writeHandle_ = nullptr; + return errCode; +} + +// Call this always within transaction +int MultiVerVacuumExecutorImpl::DeleteRecordTotally(uint64_t version, const std::vector &hashKey) +{ + if (multiKvDB_ == nullptr) { + return -E_INVALID_DB; + } + if (writeHandle_ == nullptr) { + LOGE("[VacuumExec][Delete] Transaction Had Not Been Started."); + return -E_NOT_PERMIT; + } + + int errCode = writeHandle_->DeleteEntriesByHashKey(version, hashKey); + if (errCode != E_OK) { + LOGE("[VacuumExec][Delete] DeleteEntriesByHashKey fail, errCode=%d", errCode); + } + return errCode; +} + +// Call this always within transaction +int MultiVerVacuumExecutorImpl::MarkRecordAsVacuumDone(uint64_t version, const std::vector &hashKey) +{ + if (multiKvDB_ == nullptr) { + return -E_INVALID_DB; + } + if (writeHandle_ == nullptr) { + LOGE("[VacuumExec][MarkRecord] Transaction Had Not Been Started."); + return -E_NOT_PERMIT; + } + + int errCode = writeHandle_->UpdateTrimedFlag(version, hashKey); + if (errCode != E_OK) { + LOGE("[VacuumExec][MarkRecord] UpdateTrimedFlag fail, errCode=%d", errCode); + } + return errCode; +} + +// Call this always within transaction +int MultiVerVacuumExecutorImpl::MarkCommitAsVacuumDone(const std::vector &commitId) +{ + if (multiKvDB_ == nullptr) { + return -E_INVALID_DB; + } + if (writeHandle_ == nullptr) { + LOGE("[VacuumExec][MarkCommit] Transaction Had Not Been Started."); + return -E_NOT_PERMIT; + } + + int errCode = writeHandle_->UpdateTrimedFlag(commitId); + if (errCode != E_OK) { + LOGE("[VacuumExec][MarkCommit] UpdateTrimedFlag fail, errCode=%d", errCode); + } + return errCode; +} + +MultiVerStorageExecutor *MultiVerVacuumExecutorImpl::GetCorrectHandleForUse() const +{ + if (writeHandle_ != nullptr) { + return writeHandle_; + } + int errCode = E_OK; + MultiVerStorageExecutor *handle = multiKvDB_->GetHandle(false, errCode, true); + if (errCode != E_OK || handle == nullptr) { + LOGE("[VacuumExec][GetHandle] GetHandle fail, errCode=%d", errCode); + return nullptr; + } + return handle; +} + +void MultiVerVacuumExecutorImpl::ReleaseHandleIfNeed(MultiVerStorageExecutor *inHandle) +{ + if (inHandle != writeHandle_) { + multiKvDB_->ReleaseHandle(inHandle, true); + } +} + +RecordType MultiVerVacuumExecutorImpl::GetRecordType(const MultiVerTrimedVersionData &inRecord) const +{ + if ((inRecord.operFlag & MASK_FLAG) == CLEAR_FLAG) { + return RecordType::CLEAR; + } else if ((inRecord.operFlag & MASK_FLAG) == DEL_FLAG) { + return RecordType::DELETE; + } else { + return RecordType::VALID; + } +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_vacuum_executor_impl.h b/mock/distributeddb/storage/src/multiver/multi_ver_vacuum_executor_impl.h new file mode 100644 index 00000000..ae955ec8 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_vacuum_executor_impl.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_VACUUM_EXECUTOR_IMPL_H +#define MULTI_VER_VACUUM_EXECUTOR_IMPL_H + +#ifndef OMIT_MULTI_VER +#include "multi_ver_vacuum_executor.h" +#include "multi_ver_natural_store.h" + +namespace DistributedDB { +// All functions will not be concurrently called +class MultiVerVacuumExecutorImpl final : public MultiVerVacuumExecutor { +public: + explicit MultiVerVacuumExecutorImpl(MultiVerNaturalStore *multiKvDB); + ~MultiVerVacuumExecutorImpl() override; + + // Call this always beyond transaction + int GetVacuumAbleCommits(std::list &leftBranchCommits, + std::list &rightBranchCommits) const override; + + // Call this within or beyond transaction + int GetVacuumNeedRecordsByVersion(uint64_t version, std::list &vacuumNeedRecords) override; + + // Call this within or beyond transaction + int GetShadowRecordsOfClearTypeRecord(uint64_t version, const std::vector &hashKey, + std::list &shadowRecords) override; + + // Call this within or beyond transaction + int GetShadowRecordsOfNonClearTypeRecord(uint64_t version, const std::vector &hashKey, + std::list &shadowRecords) override; + + // Call this before change the database + int StartTransactionForVacuum() override; + + // Call this if nothing error happened, if this itself failed, do not need to call rollback + int CommitTransactionForVacuum() override; + + // Call this if anything wrong happened after start transaction except commit fail + int RollBackTransactionForVacuum() override; + + // Call this always within transaction + int DeleteRecordTotally(uint64_t version, const std::vector &hashKey) override; + + // Call this always within transaction + int MarkRecordAsVacuumDone(uint64_t version, const std::vector &hashKey) override; + + // Call this always within transaction + int MarkCommitAsVacuumDone(const std::vector &commitId) override; +private: + MultiVerStorageExecutor *GetCorrectHandleForUse() const; + void ReleaseHandleIfNeed(MultiVerStorageExecutor *inHandle); + + RecordType GetRecordType(const MultiVerTrimedVersionData &inRecord) const; + + MultiVerNaturalStore *multiKvDB_; + MultiVerStorageExecutor *writeHandle_; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_VACUUM_EXECUTOR_IMPL_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_value_object.cpp b/mock/distributeddb/storage/src/multiver/multi_ver_value_object.cpp new file mode 100644 index 00000000..796f4166 --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_value_object.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_value_object.h" +#include "parcel.h" + +namespace DistributedDB { +namespace { + const size_t SLICE_HASH_VALUE_SIZE = 32; +} + +int MultiVerValueObject::GetValueHash(std::vector &valueHashes) const +{ + if (!IsHash()) { + return E_OK; + } + + for (size_t i = 0; i < valueHashVector_.size() / SLICE_HASH_VALUE_SIZE; i++) { + ValueSliceHash sliceHash; + sliceHash.assign(valueHashVector_.begin() + i * SLICE_HASH_VALUE_SIZE, + valueHashVector_.begin() + (i + 1) * SLICE_HASH_VALUE_SIZE); + valueHashes.push_back(std::move(sliceHash)); + } + return E_OK; +} + +int MultiVerValueObject::SetValueHash(const std::vector &valueHashes) +{ + valueHashVector_.clear(); + valueHashVector_.shrink_to_fit(); + for (const auto &item : valueHashes) { + valueHashVector_.insert(valueHashVector_.end(), item.begin(), item.end()); + } + head_.flag = HASH_FLAG; + return E_OK; +} + +int MultiVerValueObject::GetSerialData(std::vector &data) const +{ + const uint32_t serialIntNum = 4; // 4 int + size_t totalLength = Parcel::GetIntLen() * serialIntNum + Parcel::GetVectorCharLen(valueHashVector_); + data.resize(totalLength); + Parcel parcel(data.data(), data.size()); + + int errCode = parcel.WriteInt(head_.flag); + if (errCode != E_OK) { + return errCode; + } + + errCode = parcel.WriteInt(head_.reserved); + if (errCode != E_OK) { + return errCode; + } + + errCode = parcel.WriteInt(head_.hashNum); + if (errCode != E_OK) { + return errCode; + } + + errCode = parcel.WriteInt(head_.length); + if (errCode != E_OK) { + return errCode; + } + + errCode = parcel.WriteVectorChar(valueHashVector_); + if (errCode != E_OK) { + return errCode; + } + + return E_OK; +} + +int MultiVerValueObject::DeSerialData(const std::vector &data) +{ + Parcel parcel(const_cast(data.data()), data.size()); + int32_t readValue = 0; + parcel.ReadInt(readValue); + head_.flag = static_cast(readValue); + parcel.ReadInt(readValue); + head_.reserved = static_cast(readValue); + parcel.ReadInt(readValue); + head_.hashNum = static_cast(readValue); + parcel.ReadInt(readValue); + head_.length = static_cast(readValue); + parcel.ReadVectorChar(valueHashVector_); + if (parcel.IsError()) { + LOGE("Deserial the multi ver value object error"); + return -E_PARSE_FAIL; + } + if (((head_.flag & HASH_FLAG) == HASH_FLAG) && ((valueHashVector_.size() % SLICE_HASH_VALUE_SIZE) != 0)) { + LOGE("Value hash list total size is unexpected:%zu", valueHashVector_.size()); + return -E_PARSE_FAIL; + } + return E_OK; +} + +uint32_t MultiVerValueObject::GetDataLength() const +{ + return head_.length; +} + +void MultiVerValueObject::SetDataLength(uint32_t length) +{ + head_.length = length; +} + +int MultiVerValueObject::GetValue(Value &value) const +{ + if ((head_.flag & HASH_FLAG) == HASH_FLAG) { + return -E_NOT_SUPPORT; + } + value.assign(valueHashVector_.begin(), valueHashVector_.end()); + return E_OK; +} + +int MultiVerValueObject::SetValue(const Value &value) +{ + head_.flag = 0; + valueHashVector_.clear(); + valueHashVector_.shrink_to_fit(); + valueHashVector_.assign(value.begin(), value.end()); + return E_OK; +} + +bool MultiVerValueObject::IsHash() const +{ + return (head_.flag & HASH_FLAG) == HASH_FLAG; +} + +void MultiVerValueObject::SetFlag(uint8_t flag) +{ + head_.flag = flag; +} +} +#endif diff --git a/mock/distributeddb/storage/src/multiver/multi_ver_value_object.h b/mock/distributeddb/storage/src/multiver/multi_ver_value_object.h new file mode 100644 index 00000000..ca62af3e --- /dev/null +++ b/mock/distributeddb/storage/src/multiver/multi_ver_value_object.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_VALUE_OBJECT_H +#define MULTI_VER_VALUE_OBJECT_H + +#ifndef OMIT_MULTI_VER +#include + +#include "db_types.h" + +namespace DistributedDB { +using ValueSliceHash = std::vector; +using ValueSlice = std::vector; + +struct ValueObjectHead { + uint8_t flag = 0; + uint8_t reserved = 0; + uint16_t hashNum = 1; + uint32_t length = 0; +}; + +class MultiVerValueObject { +public: + static const uint8_t HASH_FLAG = 0x01; + + MultiVerValueObject() {} + ~MultiVerValueObject() {} + + int GetValueHash(std::vector &valueHashes) const; + int SetValueHash(const std::vector &valueHashes); + + int GetSerialData(std::vector &data) const; + int DeSerialData(const std::vector &data); + + uint32_t GetDataLength() const; + void SetDataLength(uint32_t); + + int GetValue(Value &value) const; + int SetValue(const Value &value); + + bool IsHash() const; + + // 1:value has been Hash. 0:Origin value + void SetFlag(uint8_t flag); + +private: + ValueObjectHead head_; + std::vector valueHashVector_; +}; +} + +#endif // MULTI_VER_VALUE_OBJECT_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/operation/database_oper.cpp b/mock/distributeddb/storage/src/operation/database_oper.cpp new file mode 100644 index 00000000..5a12f0a1 --- /dev/null +++ b/mock/distributeddb/storage/src/operation/database_oper.cpp @@ -0,0 +1,580 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "database_oper.h" + +#include "db_errno.h" +#include "db_constant.h" +#include "db_common.h" +#include "log_print.h" +#include "platform_specific.h" +#include "package_file.h" +#include "res_finalizer.h" +#include "runtime_context.h" + +namespace DistributedDB { +void DatabaseOper::SetLocalDevId(const std::string &deviceId) +{ + deviceId_ = deviceId; +} + +int DatabaseOper::ExecuteRekey(const CipherPassword &passwd, const KvDBProperties &property) +{ + int errCode = E_OK; + if (!RekeyPreHandle(passwd, errCode)) { + LOGI("Finish rekey when RekeyPre Handle, errCode = [%d]", errCode); + return errCode; + } + + std::string ctrlFileName; + std::string newFileName; + errCode = CreateStatusCtrlFile(property, ctrlFileName, newFileName); + if (errCode != E_OK) { + return errCode; + } + + LOGI("Backup the current file while rekey."); + errCode = BackupDb(passwd); + if (errCode != E_OK) { + LOGE("ExecuteRekey backup db failed! errCode = [%d]", errCode); + (void)RekeyRecover(property); + return errCode; + } + + errCode = RenameStatusCtrlFile(ctrlFileName, newFileName); + if (errCode != E_OK) { + (void)RekeyRecover(property); + LOGE("ExecuteRekey rename status ctrl failed! errCode = [%d]", errCode); + return errCode; + } + + errCode = CloseStorages(); + if (errCode != E_OK) { + return errCode; + } + + errCode = RekeyPostHandle(passwd); + if (errCode == -E_EKEYREVOKED) { + errCode = -E_FORBID_CACHEDB; + LOGI("Can not reopen database after rekey for the access controlled. errCode = [%d]", errCode); + } + return errCode; +} + +int DatabaseOper::GetCtrlFilePrefix(const KvDBProperties &property, std::string &filePrefix) const +{ + std::string baseDir; + int errCode = GetWorkDir(property, baseDir); + if (errCode != E_OK) { + return errCode; + } + + int dbType = property.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + std::string dbSubDir = KvDBProperties::GetStoreSubDirectory(dbType); + filePrefix = baseDir + "/" + dbSubDir; + return E_OK; +} + +int DatabaseOper::RekeyRecover(const KvDBProperties &property) +{ + std::string workDir; + int errCode = GetWorkDir(property, workDir); + if (errCode != E_OK) { + return errCode; + } + + int dbType = property.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + std::string dbSubDir = KvDBProperties::GetStoreSubDirectory(dbType); + + std::string preCtrlFileName = workDir + "/" + dbSubDir + DBConstant::REKEY_FILENAME_POSTFIX_PRE; + bool isPreCtrlFileExist = OS::CheckPathExistence(preCtrlFileName); + + std::string endCtrlFileName = workDir + "/" + dbSubDir + DBConstant::REKEY_FILENAME_POSTFIX_OK; + bool isEndCtrlFileExist = OS::CheckPathExistence(endCtrlFileName); + + std::string currentDir = workDir + "/" + dbSubDir; + bool isPrimeDbDirExist = OS::CheckPathExistence(currentDir); + + std::string backupDir = workDir + "/" + dbSubDir + DBConstant::PATH_BACKUP_POSTFIX; + bool isBackupDbDirExist = OS::CheckPathExistence(backupDir); + + // remove the backup directory and ctrl file if Rekey not finish + // name of ctrl file is pre + if (isPreCtrlFileExist) { + LOGI("Rekey recovery:Remove the backup files"); + return RecoverPrehandle(dbType, backupDir, preCtrlFileName); + } + // no ctrl file means nothing need to do + if (!isEndCtrlFileExist) { + return E_OK; + } + + // name of ctrl file is ok + if (isBackupDbDirExist) { + if (isPrimeDbDirExist) { + // scenario 1: both prime and bak dir exist + // rm prime dir -> rename backup dir to prime dir -> rm ctrl file + LOGI("Rekey recovery:Remove the current files"); + if (DBCommon::RemoveAllFilesOfDirectory(currentDir, true) != E_OK) { + LOGE("Remove the prime dir failed: %d", errno); + return -E_REMOVE_FILE; + } + } + + // scenario 2: only bak dir exist + // rename backup dir to prime dir -> rm ctrl file + if (rename(backupDir.c_str(), currentDir.c_str()) != E_OK) { + LOGE("Rename the bak dir to prime dir failed:%d.", errno); + return -E_SYSTEM_API_FAIL; + } + } + // scenario 3: only prime dir exist + // scenario 4: both prime and bak dir not exist + // remove ctrl file + if (RemoveFile(endCtrlFileName) != E_OK) { + LOGE("Remove the end ctrl file failed: %d", errno); + return -E_REMOVE_FILE; + } + return E_OK; +} + +int DatabaseOper::CheckSecurityOption(const std::string &filePath, const KvDBProperties &property) const +{ + SecurityOption secOption; + int errCode = RuntimeContext::GetInstance()->GetSecurityOption(filePath, secOption); + if (errCode != E_OK && errCode != -E_NOT_SUPPORT) { + LOGE("Get import package security option fail! errCode = [%d]", errCode); + return errCode; + } + + SecurityOption dbSecOpt; + dbSecOpt.securityFlag = property.GetSecFlag(); + dbSecOpt.securityLabel = property.GetSecLabel(); + + if (dbSecOpt == secOption || secOption.securityLabel == SecurityLabel::NOT_SET) { + return E_OK; + } + LOGE("Import package secOpt %d %d vs database %d %d", + secOption.securityFlag, secOption.securityLabel, dbSecOpt.securityFlag, dbSecOpt.securityLabel); + return -E_SECURITY_OPTION_CHECK_ERROR; +} + +int DatabaseOper::ExecuteImport(const std::string &filePath, const CipherPassword &passwd, + const KvDBProperties &property) const +{ + ImportFileInfo importInfo; + InitImportFileInfo(importInfo, property); + + int errCode = CheckSecurityOption(filePath, property); + if (errCode != E_OK) { + return errCode; + } + + // 1. unpack and check the file. + LOGI("Unpack the imported file"); + errCode = UnpackAndCheckImportedFile(filePath, importInfo, property); + if (errCode != E_OK) { + return errCode; + } + + // Using RAII define tempState clean when object finalize execute + ResFinalizer tempStateClean([&errCode, &property, this]() { + int innerCode = this->ClearImportTempFile(property); + if (innerCode != E_OK) { + LOGE("Failed to clean the intermediate import files, errCode = [%d]", innerCode); + } + // Finish. reinitialize the database. + if (errCode != E_OK) { + innerCode = this->ImportPostHandle(); + LOGE("Reinit the database after import, errCode = [%d]", innerCode); + } + }); + + // 2. backup the current database. + LOGI("Backup the current database while import."); + errCode = BackupCurrentDatabase(importInfo); + if (errCode != E_OK) { + LOGE("Failed to backup current databases, errCode = [%d]", errCode); + return errCode; + } + + // 3. export the unpacked file to the current database. + LOGI("Import the unpacked database."); + errCode = ImportUnpackedDatabase(importInfo, passwd); + if (errCode != E_OK) { + LOGE("Failed to import from the unpacked databases, errCode = [%d]", errCode); + } + DBCommon::RemoveAllFilesOfDirectory(importInfo.unpackedDir); + return errCode; +} + +int DatabaseOper::CreateBackupDirForExport(const KvDBProperties &property, std::string ¤tDir, + std::string &backupDir) const +{ + std::string baseDir; + int errCode = GetWorkDir(property, baseDir); + if (errCode != E_OK) { + LOGE("Get work dir failed:%d.", errCode); + return errCode; + } + + int databaseType = property.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + std::string subDir = KvDBProperties::GetStoreSubDirectory(databaseType); + currentDir = baseDir + "/" + subDir; + + backupDir = baseDir + "/" + subDir + DBConstant::PATH_POSTFIX_EXPORT_BACKUP + "/"; + errCode = DBCommon::CreateDirectory(backupDir); + if (errCode != E_OK) { + return errCode; + } + std::vector dbDir {DBConstant::MAINDB_DIR, DBConstant::METADB_DIR, DBConstant::CACHEDB_DIR}; + for (const auto &item : dbDir) { + if (DBCommon::CreateDirectory(backupDir + "/" + item) != E_OK) { + return -E_SYSTEM_API_FAIL; + } + } + return errCode; +} + +int DatabaseOper::ExecuteExport(const std::string &filePath, const CipherPassword &passwd, + const KvDBProperties &property) const +{ + if (deviceId_.empty()) { + return -E_NOT_INIT; + } + + std::string currentDir; + std::string backupDir; + int errCode = CreateBackupDirForExport(property, currentDir, backupDir); + if (errCode != E_OK) { + return errCode; + } + + errCode = ExportAllDatabases(currentDir, passwd, backupDir); + if (errCode != E_OK) { + LOGE("Export databases fail!:%d.", errCode); + (void)ClearExportedTempFiles(property); + return errCode; + } + + errCode = PackExportedDatabase(backupDir, filePath, property); + if (errCode != E_OK) { + OS::RemoveFile(filePath); // Pack file failed, need rollback delete Intermediate state package file + LOGE("[DatabaseOper][ExecuteExport] Pack files fail! errCode = [%d], errno = [%d].", errCode, errno); + (void)ClearExportedTempFiles(property); + return errCode; + } + + SecurityOption secOption {property.GetSecLabel(), property.GetSecFlag()}; + // RuntimeContext can make sure GetInstance not nullptr + errCode = RuntimeContext::GetInstance()->SetSecurityOption(filePath, secOption); + if (errCode != E_OK) { + if (errCode == -E_NOT_SUPPORT) { + (void)ClearExportedTempFiles(property); + return E_OK; + } + OS::RemoveFile(filePath); + LOGE("[DatabaseOper][ExecuteExport] Set security option fail! errCode = [%d].", errCode); + } + + (void)ClearExportedTempFiles(property); + return errCode; +} + +// private begin +int DatabaseOper::CreateStatusCtrlFile(const KvDBProperties &property, std::string &orgCtrlFile, + std::string &newCtrlFile) +{ + std::string filePrefix; + int errCode = GetCtrlFilePrefix(property, filePrefix); + if (errCode != E_OK) { + return errCode; + } + + // create control file + newCtrlFile = filePrefix + DBConstant::REKEY_FILENAME_POSTFIX_OK; + orgCtrlFile = filePrefix + DBConstant::REKEY_FILENAME_POSTFIX_PRE; + return OS::CreateFileByFileName(orgCtrlFile); +} + +int DatabaseOper::RenameStatusCtrlFile(const std::string &orgCtrlFile, const std::string &newCtrlFile) +{ + int errCode = rename(orgCtrlFile.c_str(), newCtrlFile.c_str()); + if (errCode != E_OK) { + LOGE("change ctrl file name to ok failed: %d.", errCode); + return -E_SYSTEM_API_FAIL; + } + return E_OK; +} + +int DatabaseOper::RecoverPrehandle(int dbType, const std::string &dir, const std::string &fileName) +{ + if (DBCommon::RemoveAllFilesOfDirectory(dir, true) != E_OK) { + LOGE("Remove the backup dir failed:%d", errno); + return -E_REMOVE_FILE; + } + if (RemoveFile(fileName) != E_OK) { + LOGE("Remove the pre ctrl file failed:%d", errno); + return -E_REMOVE_FILE; + } + return E_OK; +} + +int DatabaseOper::RemoveDbDir(const std::string &dir, int dbType, bool isNeedDelDir) +{ + if (!OS::CheckPathExistence(dir)) { + return E_OK; + } + + if (dbType == DBConstant::DB_TYPE_LOCAL) { + std::vector dbNameList = { + DBConstant::LOCAL_DATABASE_NAME + }; + return RemoveDbFiles(dir, dbNameList, isNeedDelDir); + } + if (dbType == DBConstant::DB_TYPE_SINGLE_VER) { + std::vector dbNameList = { + DBConstant::SINGLE_VER_DATA_STORE + }; + return RemoveDbFiles(dir, dbNameList, isNeedDelDir); + } + if (dbType == DBConstant::DB_TYPE_MULTI_VER) { + std::vector dbNameList = { + DBConstant::MULTI_VER_DATA_STORE, DBConstant::MULTI_VER_COMMIT_STORE, + DBConstant::MULTI_VER_VALUE_STORE, DBConstant::MULTI_VER_META_STORE + }; + return RemoveDbFiles(dir, dbNameList, isNeedDelDir); + } + return -E_NOT_SUPPORT; +} + +int DatabaseOper::RemoveFile(const std::string &fileName) +{ + if (!OS::CheckPathExistence(fileName)) { + return E_OK; + } + + if (OS::RemoveFile(fileName.c_str()) != E_OK) { + LOGE("Remove file failed:%d", errno); + return -E_REMOVE_FILE; + } + return E_OK; +} + +int DatabaseOper::GetWorkDir(const KvDBProperties &property, std::string &workDir) +{ + std::string dataDir = property.GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string identifierDir = property.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + if (dataDir.empty()) { + return -E_INVALID_ARGS; + } + + workDir = dataDir + "/" + identifierDir; + return E_OK; +} + +// Only for remove the backup directory while rekey. +int DatabaseOper::RemoveDbFiles(const std::string &dir, const std::vector &dbNameList, bool isNeedDelDir) +{ + for (const auto &iter : dbNameList) { + // remove + std::string dbFile = dir + "/" + iter + ".db"; + if (RemoveFile(dbFile) != E_OK) { + LOGE("Remove the db file failed:%d", errno); + return -E_REMOVE_FILE; + } + + dbFile = dir + "/" + iter + ".db-wal"; + if (RemoveFile(dbFile) != E_OK) { + LOGE("Remove the wal file failed:%d", errno); + return -E_REMOVE_FILE; + } + + dbFile = dir + "/" + iter + ".db-shm"; + if (RemoveFile(dbFile) != E_OK) { + LOGE("Remove the shm file failed:%d", errno); + return -E_REMOVE_FILE; + } + } + if (isNeedDelDir && OS::RemoveDBDirectory(dir) != E_OK) { + LOGE("Remove directory:%d", errno); + return -E_REMOVE_FILE; + } + return E_OK; +} + +void DatabaseOper::InitImportFileInfo(ImportFileInfo &info, const KvDBProperties &property) +{ + std::string dataDir = property.GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string identifierDir = property.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + int databaseType = property.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + std::string subDir = KvDBProperties::GetStoreSubDirectory(databaseType); + + std::string baseDir = dataDir + "/" + identifierDir + "/" + subDir; + info.backupDir = baseDir + DBConstant::PATH_POSTFIX_IMPORT_BACKUP + "/"; + info.unpackedDir = baseDir + DBConstant::PATH_POSTFIX_UNPACKED + "/"; + info.currentDir = baseDir + "/"; + info.curValidFile = baseDir + DBConstant::PATH_POSTFIX_IMPORT_ORIGIN; // origin directory is valid. + info.backValidFile = baseDir + DBConstant::PATH_POSTFIX_IMPORT_DUP; // the back directory is valid. +} + +int DatabaseOper::UnpackAndCheckImportedFile(const std::string &srcFile, const ImportFileInfo &info, + const KvDBProperties &property) const +{ + int errCode = DBCommon::CreateDirectory(info.unpackedDir); + if (errCode != E_OK) { + return errCode; + } + + FileInfo fileInfo; + errCode = PackageFile::UnpackFile(srcFile, info.unpackedDir, fileInfo); + if (errCode != E_OK) { + DBCommon::RemoveAllFilesOfDirectory(info.unpackedDir); + LOGE("Failed to unpack the imported file:%d", errCode); + return errCode; + } + int dbType = property.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + if (fileInfo.dbType != static_cast(dbType) || fileInfo.deviceID != deviceId_) { + DBCommon::RemoveAllFilesOfDirectory(info.unpackedDir); + LOGE("Check db type [%u] vs [%u] or devicesId fail!", fileInfo.dbType, static_cast(dbType)); + return -E_INVALID_FILE; + } + return E_OK; +} + +int DatabaseOper::RecoverImportedBackFiles(const std::string &dir, const std::string &fileName, int dbType) const +{ + std::string backupDir = dir + DBConstant::PATH_POSTFIX_IMPORT_BACKUP; + // if backup directory is not existed + if (!OS::CheckPathExistence(backupDir)) { + goto END; + } + + if (DBCommon::RemoveAllFilesOfDirectory(dir, true) != E_OK) { + LOGE("Remove the current db dir failed"); + return -E_REMOVE_FILE; + } + + if (rename(backupDir.c_str(), dir.c_str()) != E_OK) { + LOGE("Rename the backfile error:%d", errno); + return -E_SYSTEM_API_FAIL; + } + +END: + if (RemoveFile(fileName) != E_OK) { + LOGE("Remove the pre ctrl file failed:%d", errno); + return -E_REMOVE_FILE; + } + return E_OK; +} + +int DatabaseOper::RemoveImportedBackFiles(const std::string &backupDir, const std::string &ctrlFileName, int dbType) + const +{ + if (DBCommon::RemoveAllFilesOfDirectory(backupDir, true) != E_OK) { + LOGE("Remove the backup dir failed"); + return -E_REMOVE_FILE; + } + + if (RemoveFile(ctrlFileName) != E_OK) { + LOGE("Remove the pre ctrl file failed"); + return -E_REMOVE_FILE; + } + return E_OK; +} + +int DatabaseOper::ClearImportTempFile(const KvDBProperties &property) const +{ + // get work directory + std::string workDir; + int errCode = GetWorkDir(property, workDir); + if (errCode != E_OK) { + return errCode; + } + + int dbType = property.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + std::string dbSubDir = KvDBProperties::GetStoreSubDirectory(dbType); + + std::string oriKeepFile = workDir + "/" + dbSubDir + DBConstant::PATH_POSTFIX_IMPORT_ORIGIN; + bool isOriKeepFileExist = OS::CheckPathExistence(oriKeepFile); + + std::string backKeepFile = workDir + "/" + dbSubDir + DBConstant::PATH_POSTFIX_IMPORT_DUP; + bool isBakKeepFileExist = OS::CheckPathExistence(backKeepFile); + + std::string currentDir = workDir + "/" + dbSubDir; + std::string backupDir = workDir + "/" + dbSubDir + DBConstant::PATH_POSTFIX_IMPORT_BACKUP; + bool isBackupDbDirExist = OS::CheckPathExistence(backupDir); + std::string exportBackupDir = workDir + "/" + dbSubDir + DBConstant::PATH_POSTFIX_UNPACKED; + DBCommon::RemoveAllFilesOfDirectory(exportBackupDir); + + if (isOriKeepFileExist && isBakKeepFileExist) { + LOGE("Origin and backup file shouldn't exist concurrently"); + } + + // Clear the backup dir and the ctrl file + if (isOriKeepFileExist) { + return RemoveImportedBackFiles(backupDir, oriKeepFile, dbType); + } + + // remove the main directory and restore the backup files. + if (isBakKeepFileExist) { + return RecoverImportedBackFiles(currentDir, backKeepFile, dbType); + } + + if (isBackupDbDirExist) { + // Import success, clean backupdir + if (DBCommon::RemoveAllFilesOfDirectory(backupDir, true) != E_OK) { + LOGE("Remove the backup dir failed"); + return -E_REMOVE_FILE; + } + } + + return E_OK; +} + +int DatabaseOper::ClearExportedTempFiles(const KvDBProperties &property) const +{ + std::string workDir; + int errCode = GetWorkDir(property, workDir); + if (errCode != E_OK) { + return errCode; + } + + int dbType = property.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + std::string dbSubDir = KvDBProperties::GetStoreSubDirectory(dbType); + std::string backupDir = workDir + "/" + dbSubDir + DBConstant::PATH_POSTFIX_EXPORT_BACKUP; + errCode = DBCommon::RemoveAllFilesOfDirectory(backupDir); + if (errCode != E_OK) { + LOGE("Remove the exported backup dir failed"); + return -E_REMOVE_FILE; + } + + return errCode; +} + +int DatabaseOper::PackExportedDatabase(const std::string &fileDir, const std::string &packedFile, + const KvDBProperties &property) const +{ + LOGI("Pack the exported database."); + int databaseType = property.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + FileInfo fileInfo = {static_cast(databaseType), deviceId_}; + int errCode = PackageFile::PackageFiles(fileDir, packedFile, fileInfo); + if (errCode != E_OK) { + LOGE("Pack the database error:%d", errCode); + } + + return errCode; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/operation/database_oper.h b/mock/distributeddb/storage/src/operation/database_oper.h new file mode 100644 index 00000000..5e933b0a --- /dev/null +++ b/mock/distributeddb/storage/src/operation/database_oper.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DATABASE_OPER_H +#define DATABASE_OPER_H + +#include + +#include "kvdb_properties.h" +#include "generic_kvdb.h" + +namespace DistributedDB { +class DatabaseOper { +public: + virtual ~DatabaseOper() {}; + + virtual int Rekey(const CipherPassword &passwd) = 0; + + virtual int Import(const std::string &filePath, const CipherPassword &passwd) = 0; + + virtual int Export(const std::string &filePath, const CipherPassword &passwd) const = 0; + + void SetLocalDevId(const std::string &deviceId); + + int RekeyRecover(const KvDBProperties &property); + + int ClearImportTempFile(const KvDBProperties &property) const; + + int ClearExportedTempFiles(const KvDBProperties &property) const; + +protected: + int ExecuteRekey(const CipherPassword &passwd, const KvDBProperties &property); + + virtual bool RekeyPreHandle(const CipherPassword &passwd, int &errCode) = 0; + + virtual int BackupDb(const CipherPassword &passwd) const = 0; + + virtual int CloseStorages() = 0; + + virtual int RekeyPostHandle(const CipherPassword &passwd) = 0; + + int GetCtrlFilePrefix(const KvDBProperties &property, std::string &filePrefix) const; + + virtual int ExportAllDatabases(const std::string ¤tDir, const CipherPassword &passwd, + const std::string &dbDir) const = 0; + + static int RemoveFile(const std::string &fileName); + + // import begin + int ExecuteImport(const std::string &filePath, const CipherPassword &passwd, const KvDBProperties &property) const; + + virtual int BackupCurrentDatabase(const ImportFileInfo &info) const = 0; + + virtual int ImportUnpackedDatabase(const ImportFileInfo &info, const CipherPassword &srcPasswd) const = 0; + + virtual int ImportPostHandle() const = 0; + + // export begin + int ExecuteExport(const std::string &filePath, const CipherPassword &passwd, const KvDBProperties &property) const; + +private: + int CreateStatusCtrlFile(const KvDBProperties &property, std::string &orgCtrlFile, std::string &newCtrlFile); + + static int RenameStatusCtrlFile(const std::string &orgCtrlFile, const std::string &newCtrlFile); + + int RecoverPrehandle(int dbType, const std::string &dir, const std::string &fileName); + + int RemoveDbDir(const std::string &dir, int dbType, bool isNeedDelDir = true); + + static int GetWorkDir(const KvDBProperties &property, std::string &workDir); + + int RemoveDbFiles(const std::string &dir, const std::vector &dbNameList, bool isNeedDelDir = true); + + static void InitImportFileInfo(ImportFileInfo &info, const KvDBProperties &property); + + int UnpackAndCheckImportedFile(const std::string &srcFile, const ImportFileInfo &info, + const KvDBProperties &property) const; + + int RecoverImportedBackFiles(const std::string &dir, const std::string &fileName, int dbType) const; + + int RemoveImportedBackFiles(const std::string &backupDir, const std::string &ctrlFileName, int dbType) const; + + int PackExportedDatabase(const std::string &fileDir, const std::string &packedFile, + const KvDBProperties &property) const; + + int CheckSecurityOption(const std::string &filePath, const KvDBProperties &property) const; + + int CreateBackupDirForExport(const KvDBProperties &property, std::string ¤tDir, std::string &backupDir) const; + + std::string deviceId_; +}; +} // namespace DistributedDB + +#endif // DATABASE_OPER_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/operation/local_database_oper.cpp b/mock/distributeddb/storage/src/operation/local_database_oper.cpp new file mode 100644 index 00000000..ceede762 --- /dev/null +++ b/mock/distributeddb/storage/src/operation/local_database_oper.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "local_database_oper.h" + +#include "log_print.h" +#include "platform_specific.h" +#include "db_errno.h" +#include "db_constant.h" +#include "db_common.h" + +namespace DistributedDB { +LocalDatabaseOper::LocalDatabaseOper(SQLiteLocalKvDB *localKvDb, SQLiteStorageEngine *storageEngine) + : localKvDb_(localKvDb), + storageEngine_(storageEngine) +{} + +int LocalDatabaseOper::Rekey(const CipherPassword &passwd) +{ + if (localKvDb_ == nullptr || storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + + return ExecuteRekey(passwd, localKvDb_->GetDbProperties()); +} + +int LocalDatabaseOper::Import(const std::string &filePath, const CipherPassword &passwd) +{ + if (localKvDb_ == nullptr || storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + + return ExecuteImport(filePath, passwd, localKvDb_->GetDbProperties()); +} + +int LocalDatabaseOper::Export(const std::string &filePath, const CipherPassword &passwd) const +{ + return ExecuteExport(filePath, passwd, localKvDb_->GetDbProperties()); +} + +bool LocalDatabaseOper::RekeyPreHandle(const CipherPassword &passwd, int &errCode) +{ + CipherType cipherType; + CipherPassword cachePasswd; + localKvDb_->GetDbProperties().GetPassword(cipherType, cachePasswd); + + if (cachePasswd.GetSize() == 0 && passwd.GetSize() == 0) { + errCode = E_OK; + return false; + } + + // need invoke sqlite3 rekey + if (cachePasswd.GetSize() > 0 && passwd.GetSize() > 0) { + errCode = localKvDb_->RunRekeyLogic(cipherType, passwd); + return false; + } + + return true; +} + +int LocalDatabaseOper::BackupDb(const CipherPassword &passwd) const +{ + std::string filePrefix; + int errCode = GetCtrlFilePrefix(localKvDb_->GetDbProperties(), filePrefix); + if (errCode != E_OK) { + return errCode; + } + + // create backup dir + std::string backupDir = filePrefix + DBConstant::PATH_BACKUP_POSTFIX; + errCode = DBCommon::CreateDirectory(backupDir); + if (errCode != E_OK) { + LOGE("create backup dir failed:%d.", errCode); + return errCode; + } + + // export db to backup + CipherType cipherType; + CipherPassword oldPasswd; + localKvDb_->GetDbProperties().GetPassword(cipherType, oldPasswd); + std::string backupDbName = backupDir + "/" + DBConstant::LOCAL_DATABASE_NAME + DBConstant::SQLITE_DB_EXTENSION; + return localKvDb_->RunExportLogic(cipherType, passwd, backupDbName); +} + +int LocalDatabaseOper::CloseStorages() +{ + // close old db + storageEngine_->Release(); + int errCode = RekeyRecover(localKvDb_->GetDbProperties()); + if (errCode != E_OK) { + LOGE("Recover failed after rekey ok:%d.", errCode); + int innerCode = localKvDb_->InitDatabaseContext(localKvDb_->GetDbProperties()); + if (innerCode != E_OK) { + LOGE("ReInit the handlePool failed:%d", innerCode); + } + } + return errCode; +} + +int LocalDatabaseOper::RekeyPostHandle(const CipherPassword &passwd) +{ + CipherType cipherType; + CipherPassword oldPasswd; + localKvDb_->GetDbPropertyForUpdate().GetPassword(cipherType, oldPasswd); + localKvDb_->GetDbPropertyForUpdate().SetPassword(cipherType, passwd); + return localKvDb_->InitDatabaseContext(localKvDb_->GetDbProperties()); +} + +int LocalDatabaseOper::ExportAllDatabases(const std::string ¤tDir, const CipherPassword &passwd, + const std::string &dbDir) const +{ + std::string backupDbName = dbDir + DBConstant::LOCAL_DATABASE_NAME + DBConstant::SQLITE_DB_EXTENSION; + std::string currentDb = currentDir + "/" + DBConstant::LOCAL_DATABASE_NAME + DBConstant::SQLITE_DB_EXTENSION; + + CipherType cipherType; + CipherPassword currPasswd; + localKvDb_->GetDbProperties().GetPassword(cipherType, currPasswd); + int errCode = SQLiteUtils::ExportDatabase(currentDb, cipherType, currPasswd, backupDbName, passwd); + if (errCode != E_OK) { + LOGE("Export the database failed:%d", errCode); + } + return errCode; +} + +int LocalDatabaseOper::BackupCurrentDatabase(const ImportFileInfo &info) const +{ + storageEngine_->Release(); + // create the pre flag file. + int errCode = OS::CreateFileByFileName(info.curValidFile); + if (errCode != E_OK) { + LOGE("create ctrl file failed:%d.", errCode); + return errCode; + } + + // create backup dir + errCode = DBCommon::CreateDirectory(info.backupDir); + if (errCode != E_OK) { + LOGE("Create backup dir failed:%d.", errCode); + (void)RemoveFile(info.curValidFile); + return errCode; + } + + std::string currentFile = info.currentDir + DBConstant::LOCAL_DATABASE_NAME + + DBConstant::SQLITE_DB_EXTENSION; + std::string backupFile = info.backupDir + DBConstant::LOCAL_DATABASE_NAME + + DBConstant::SQLITE_DB_EXTENSION; + errCode = DBCommon::CopyFile(currentFile, backupFile); + if (errCode != E_OK) { + LOGE("Backup the current database error:%d", errCode); + return errCode; + } + int innerCode = rename(info.curValidFile.c_str(), info.backValidFile.c_str()); + if (innerCode != 0) { + LOGE("Failed to rename the file after the backup:%d", errno); + errCode = -E_SYSTEM_API_FAIL; + } + return errCode; +} + +int LocalDatabaseOper::ImportUnpackedDatabase(const ImportFileInfo &info, const CipherPassword &srcPasswd) const +{ + // create backup dir + int errCode = DBCommon::RemoveAllFilesOfDirectory(info.currentDir, false); + if (errCode != E_OK) { + return errCode; + } + + std::string unpackedFile = info.unpackedDir + DBConstant::LOCAL_DATABASE_NAME + DBConstant::SQLITE_DB_EXTENSION; + std::string currentFile = info.currentDir + DBConstant::LOCAL_DATABASE_NAME + DBConstant::SQLITE_DB_EXTENSION; + CipherType cipherType; + CipherPassword passwd; + localKvDb_->GetDbProperties().GetPassword(cipherType, passwd); + errCode = SQLiteUtils::ExportDatabase(unpackedFile, cipherType, srcPasswd, currentFile, passwd); + DBCommon::RemoveAllFilesOfDirectory(info.unpackedDir); + if (errCode != E_OK) { + LOGE("export the unpacked database to current error:%d", errCode); + errCode = -E_INVALID_FILE; + return errCode; + } + + // reinitialize the database, and delete the backup database. + errCode = localKvDb_->InitDatabaseContext(localKvDb_->GetDbProperties()); + if (errCode != E_OK) { + LOGE("InitDatabaseContext error:%d", errCode); + return errCode; + } + + // rename the flag file. + int innerCode = rename(info.backValidFile.c_str(), info.curValidFile.c_str()); + if (innerCode != E_OK) { + LOGE("Failed to rename after the import operation:%d", errno); + errCode = -E_SYSTEM_API_FAIL; + } + + return errCode; +} + +int LocalDatabaseOper::ImportPostHandle() const +{ + return localKvDb_->InitDatabaseContext(localKvDb_->GetDbProperties()); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/operation/local_database_oper.h b/mock/distributeddb/storage/src/operation/local_database_oper.h new file mode 100644 index 00000000..dc0359ed --- /dev/null +++ b/mock/distributeddb/storage/src/operation/local_database_oper.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 LOCAL_DATABASE_OPER_H +#define LOCAL_DATABASE_OPER_H + +#include "database_oper.h" +#include "sqlite_local_kvdb.h" + +namespace DistributedDB { +class LocalDatabaseOper : public DatabaseOper { +public: + LocalDatabaseOper(SQLiteLocalKvDB *localKvDb, SQLiteStorageEngine *storageEngine); + ~LocalDatabaseOper() override {}; + + int Rekey(const CipherPassword &passwd) override; + + int Import(const std::string &filePath, const CipherPassword &passwd) override; + + int Export(const std::string &filePath, const CipherPassword &passwd) const override; + +protected: + bool RekeyPreHandle(const CipherPassword &passwd, int &errCode) override; + + int BackupDb(const CipherPassword &passwd) const override; + + int CloseStorages() override; + + int RekeyPostHandle(const CipherPassword &passwd) override; + + int ExportAllDatabases(const std::string ¤tDir, const CipherPassword &passwd, + const std::string &dbDir) const override; + + int BackupCurrentDatabase(const ImportFileInfo &info) const override; + + int ImportUnpackedDatabase(const ImportFileInfo &info, const CipherPassword &srcPasswd) const override; + + int ImportPostHandle() const override; + +private: + SQLiteLocalKvDB *localKvDb_; + SQLiteStorageEngine *storageEngine_; +}; +} // namespace DistributedDB +#endif // LOCAL_DATABASE_OPER_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/operation/multi_ver_database_oper.cpp b/mock/distributeddb/storage/src/operation/multi_ver_database_oper.cpp new file mode 100644 index 00000000..76a33727 --- /dev/null +++ b/mock/distributeddb/storage/src/operation/multi_ver_database_oper.cpp @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_database_oper.h" + +#include "db_errno.h" +#include "log_print.h" +#include "db_constant.h" +#include "db_common.h" +#include "platform_specific.h" +#include "sqlite_multi_ver_data_storage.h" +#include "multi_ver_natural_store_commit_storage.h" + +namespace DistributedDB { +MultiVerDatabaseOper::MultiVerDatabaseOper(MultiVerNaturalStore *multiVerNaturalStore, + IKvDBMultiVerDataStorage *multiVerData, IKvDBCommitStorage *commitHistory, MultiVerKvDataStorage *multiVerKvStorage) + : multiVerNaturalStore_(multiVerNaturalStore), + multiVerData_(multiVerData), + commitHistory_(commitHistory), + multiVerKvStorage_(multiVerKvStorage) +{} + +int MultiVerDatabaseOper::Rekey(const CipherPassword &passwd) +{ + if (multiVerNaturalStore_ == nullptr || multiVerData_ == nullptr || commitHistory_ == nullptr || + multiVerKvStorage_ == nullptr) { + return -E_INVALID_DB; + } + + return ExecuteRekey(passwd, multiVerNaturalStore_->GetDbProperties()); +} + +int MultiVerDatabaseOper::Import(const std::string &filePath, const CipherPassword &passwd) +{ + if (multiVerNaturalStore_ == nullptr || multiVerData_ == nullptr || commitHistory_ == nullptr || + multiVerKvStorage_ == nullptr) { + return -E_INVALID_DB; + } + + return ExecuteImport(filePath, passwd, multiVerNaturalStore_->GetDbProperties()); +} + +int MultiVerDatabaseOper::Export(const std::string &filePath, const CipherPassword &passwd) const +{ + if (multiVerNaturalStore_ == nullptr || multiVerData_ == nullptr || commitHistory_ == nullptr || + multiVerKvStorage_ == nullptr) { + return -E_INVALID_DB; + } + return ExecuteExport(filePath, passwd, multiVerNaturalStore_->GetDbProperties()); +} + +bool MultiVerDatabaseOper::RekeyPreHandle(const CipherPassword &passwd, int &errCode) +{ + CipherType cipherType; + CipherPassword cachePasswd; + multiVerNaturalStore_->GetDbProperties().GetPassword(cipherType, cachePasswd); + + if (cachePasswd.GetSize() == 0 && passwd.GetSize() == 0) { + errCode = E_OK; + return false; + } + + return true; +} + +int MultiVerDatabaseOper::BackupDb(const CipherPassword &passwd) const +{ + std::string filePrefix; + int errCode = GetCtrlFilePrefix(multiVerNaturalStore_->GetDbProperties(), filePrefix); + if (errCode != E_OK) { + return errCode; + } + + // create backup dir + std::string currentDir = filePrefix; + std::string backupDir = filePrefix + DBConstant::PATH_BACKUP_POSTFIX; + + // export db to backup + return ExportAllDatabases(currentDir, passwd, backupDir); +} + +int MultiVerDatabaseOper::CloseStorages() +{ + if (commitHistory_ != nullptr) { + commitHistory_->Close(); + } + if (multiVerData_ != nullptr) { + multiVerData_->Close(); + } + if (multiVerKvStorage_ != nullptr) { + multiVerKvStorage_->Close(); + } + + // rm old dir -> rename backup dir to prime dir -> rm ctrl file + int errCode = RekeyRecover(multiVerNaturalStore_->GetDbProperties()); + if (errCode != E_OK) { + LOGE("Recover failed after run all export ok: %d.", errCode); + } + return errCode; +} + +int MultiVerDatabaseOper::RekeyPostHandle(const CipherPassword &passwd) +{ + CipherType cipherType; + CipherPassword oldPasswd; + multiVerNaturalStore_->GetDbPropertyForUpdate().GetPassword(cipherType, oldPasswd); + multiVerNaturalStore_->GetDbPropertyForUpdate().SetPassword(cipherType, passwd); + + int errCode = multiVerNaturalStore_->InitStorages(multiVerNaturalStore_->GetDbProperties()); + if (errCode != E_OK) { + return errCode; + } + return RekeyRecover(multiVerNaturalStore_->GetDbProperties()); +} + +int MultiVerDatabaseOper::ExportAllDatabases(const std::string ¤tDir, const CipherPassword &passwd, + const std::string &dbDir) const +{ + int errCode = DBCommon::CreateDirectory(dbDir); + if (errCode != E_OK) { + return errCode; + } + CipherType cipherType; + CipherPassword oldPasswd; + multiVerNaturalStore_->GetDbPropertyForUpdate().GetPassword(cipherType, oldPasswd); + errCode = static_cast(multiVerData_)->RunExportLogic(cipherType, passwd, dbDir); + if (errCode != E_OK) { + return errCode; + } + errCode = static_cast(commitHistory_)->RunExportLogic(cipherType, + passwd, dbDir); + if (errCode != E_OK) { + return errCode; + } + errCode = multiVerKvStorage_->RunExportLogic(cipherType, passwd, dbDir); + if (errCode != E_OK) { + return errCode; + } + + std::string versionFile = currentDir + "/version"; + if (OS::CheckPathExistence(versionFile)) { + std::string targetVerFile = dbDir + "/version"; + DBCommon::CopyFile(versionFile, targetVerFile); + } + + return E_OK; +} + +int MultiVerDatabaseOper::BackupCurrentDatabase(const ImportFileInfo &info) const +{ + if (multiVerKvStorage_ == nullptr || commitHistory_ == nullptr || multiVerData_ == nullptr) { + return -E_INVALID_DB; + } + commitHistory_->Close(); + multiVerData_->Close(); + multiVerKvStorage_->Close(); + + // Create the file which imply that the current database files is valid. + int errCode = OS::CreateFileByFileName(info.curValidFile); + if (errCode != E_OK) { + LOGE("Create current valid file failed:%d.", errCode); + return errCode; + } + + std::string dataDir = multiVerNaturalStore_->GetDbProperties().GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string id = multiVerNaturalStore_->GetDbProperties().GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + bool isNeedCreate = multiVerNaturalStore_->GetDbProperties().GetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + + CipherType cipherType; + CipherPassword passwd; + multiVerNaturalStore_->GetDbProperties().GetPassword(cipherType, passwd); + + IKvDBMultiVerDataStorage::Property multiVerProp = {dataDir, id, isNeedCreate, cipherType, passwd}; + IKvDBCommitStorage::Property commitProp = {dataDir, id, isNeedCreate, cipherType, passwd}; + MultiVerKvDataStorage::Property multiVerKvProp = {dataDir, id, isNeedCreate, cipherType, passwd}; + + errCode = DBCommon::CreateDirectory(info.backupDir); + if (errCode != E_OK) { + LOGE("Create backup dir failed"); + RemoveFile(info.curValidFile); + return errCode; + } + + errCode = multiVerData_->BackupCurrentDatabase(multiVerProp, info.backupDir); + if (errCode != E_OK) { + return errCode; + } + + errCode = commitHistory_->BackupCurrentDatabase(commitProp, info.backupDir); + if (errCode != E_OK) { + return errCode; + } + + errCode = multiVerKvStorage_->BackupCurrentDatabase(multiVerKvProp, info.backupDir); + if (errCode != E_OK) { + return errCode; + } + + (void)DBCommon::CopyFile(info.currentDir + "/version", info.backupDir + "/version"); + int innerCode = rename(info.curValidFile.c_str(), info.backValidFile.c_str()); + if (innerCode != 0) { + LOGE("Failed to rename the file after the backup:%d", errno); + return -E_SYSTEM_API_FAIL; + } + return E_OK; +} + +int MultiVerDatabaseOper::ImportUnpackedDatabase(const ImportFileInfo &info, const CipherPassword &srcPasswd) const +{ + // create backup dir + int errCode = DBCommon::RemoveAllFilesOfDirectory(info.currentDir, false); + if (errCode != E_OK) { + return errCode; + } + + errCode = ImportDatabase(info.unpackedDir, srcPasswd); + DBCommon::CopyFile(info.unpackedDir + "/version", info.currentDir + "/version"); + (void)DBCommon::RemoveAllFilesOfDirectory(info.unpackedDir); + if (errCode != E_OK) { + LOGE("export the unpacked database to current error:%d", errCode); + errCode = -E_INVALID_FILE; + return errCode; + } + + // reinitialize the database, and delete the backup database. + errCode = multiVerNaturalStore_->InitStorages(multiVerNaturalStore_->GetDbProperties(), true); + if (errCode != E_OK) { + LOGE("InitStorages error:%d", errCode); + return errCode; + } + + // rename the flag file. + int innerCode = rename(info.backValidFile.c_str(), info.curValidFile.c_str()); + if (innerCode != E_OK) { + LOGE("Failed to rename after the import operation:%d", errno); + errCode = -E_SYSTEM_API_FAIL; + } + + return errCode; +} + +int MultiVerDatabaseOper::ImportPostHandle() const +{ + return multiVerNaturalStore_->InitStorages(multiVerNaturalStore_->GetDbProperties()); +} + +// private +int MultiVerDatabaseOper::ImportDatabase(const std::string &dir, const CipherPassword &passwd) const +{ + if (multiVerKvStorage_ == nullptr || commitHistory_ == nullptr || multiVerData_ == nullptr) { + return -E_INVALID_DB; + } + + std::string dataDir = multiVerNaturalStore_->GetDbProperties().GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string id = multiVerNaturalStore_->GetDbProperties().GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + + CipherType cipherType; + CipherPassword currPasswd; + multiVerNaturalStore_->GetDbProperties().GetPassword(cipherType, currPasswd); + + IKvDBMultiVerDataStorage::Property multiVerProp = {dataDir, id, true, cipherType, currPasswd}; + IKvDBCommitStorage::Property commitProp = {dataDir, id, true, cipherType, currPasswd}; + MultiVerKvDataStorage::Property multiVerKvProp = {dataDir, id, true, cipherType, currPasswd}; + int errCode = multiVerData_->ImportDatabase(multiVerProp, dir, passwd); + if (errCode != E_OK) { + return errCode; + } + + errCode = commitHistory_->ImportDatabase(commitProp, dir, passwd); + if (errCode != E_OK) { + return errCode; + } + return multiVerKvStorage_->ImportDatabase(multiVerKvProp, dir, passwd); +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/operation/multi_ver_database_oper.h b/mock/distributeddb/storage/src/operation/multi_ver_database_oper.h new file mode 100644 index 00000000..eb2fa310 --- /dev/null +++ b/mock/distributeddb/storage/src/operation/multi_ver_database_oper.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_DATABASE_OPER_H +#define MULTI_VER_DATABASE_OPER_H + +#ifndef OMIT_MULTI_VER +#include "database_oper.h" +#include "multi_ver_natural_store.h" + +namespace DistributedDB { +class MultiVerDatabaseOper : public DatabaseOper { +public: + MultiVerDatabaseOper(MultiVerNaturalStore *multiVerNaturalStore, IKvDBMultiVerDataStorage *multiVerData, + IKvDBCommitStorage *commitHistory, MultiVerKvDataStorage *multiVerKvStorage); + ~MultiVerDatabaseOper() override {}; + + int Rekey(const CipherPassword &passwd) override; + + int Import(const std::string &filePath, const CipherPassword &passwd) override; + + int Export(const std::string &filePath, const CipherPassword &passwd) const override; + +protected: + bool RekeyPreHandle(const CipherPassword &passwd, int &errCode) override; + + int BackupDb(const CipherPassword &passwd) const override; + + int CloseStorages() override; + + int RekeyPostHandle(const CipherPassword &passwd) override; + + int ExportAllDatabases(const std::string ¤tDir, const CipherPassword &passwd, + const std::string &dbDir) const override; + + int BackupCurrentDatabase(const ImportFileInfo &info) const override; + + int ImportUnpackedDatabase(const ImportFileInfo &info, const CipherPassword &srcPasswd) const override; + + int ImportPostHandle() const override; + +private: + int ImportDatabase(const std::string &dir, const CipherPassword &passwd) const; + + MultiVerNaturalStore *multiVerNaturalStore_; + IKvDBMultiVerDataStorage *multiVerData_; + IKvDBCommitStorage *commitHistory_; + MultiVerKvDataStorage *multiVerKvStorage_; +}; +} // namespace DistributedDB +#endif // MULTI_VER_DATABASE_OPER_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/operation/single_ver_database_oper.cpp b/mock/distributeddb/storage/src/operation/single_ver_database_oper.cpp new file mode 100644 index 00000000..6863a736 --- /dev/null +++ b/mock/distributeddb/storage/src/operation/single_ver_database_oper.cpp @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_database_oper.h" + +#include "db_errno.h" +#include "log_print.h" +#include "db_constant.h" +#include "db_common.h" +#include "platform_specific.h" + +namespace DistributedDB { +SingleVerDatabaseOper::SingleVerDatabaseOper(SQLiteSingleVerNaturalStore *naturalStore, + SQLiteStorageEngine *storageEngine) + : singleVerNaturalStore_(naturalStore), + storageEngine_(storageEngine) +{} + +int SingleVerDatabaseOper::SetSecOpt(const std::string &path, bool isDir) const +{ + std::string currentMetaPath = path + "/" + DBConstant::METADB_DIR; + std::string currentMainPath = path + "/" + DBConstant::MAINDB_DIR; + if (!isDir) { + currentMetaPath = currentMetaPath + "/" + DBConstant::SINGLE_VER_META_STORE + DBConstant::SQLITE_DB_EXTENSION; + currentMainPath = currentMainPath + "/" + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + } + SecurityOption option; + int mainSecLabel = singleVerNaturalStore_->GetDbProperties().GetSecLabel(); + option.securityLabel = ((mainSecLabel >= SecurityLabel::S2) ? SecurityLabel::S2 : mainSecLabel); + int errCode = RuntimeContext::GetInstance()->SetSecurityOption(currentMetaPath, option); + if (errCode != E_OK && errCode != -E_NOT_SUPPORT) { + return errCode; + } + + option.securityLabel = singleVerNaturalStore_->GetDbProperties().GetSecLabel(); + option.securityFlag = singleVerNaturalStore_->GetDbProperties().GetSecFlag(); + errCode = RuntimeContext::GetInstance()->SetSecurityOption(currentMainPath, option); + if (errCode != E_OK && errCode != -E_NOT_SUPPORT) { + return errCode; + } + return E_OK; +} + +int SingleVerDatabaseOper::Rekey(const CipherPassword &passwd) +{ + if (singleVerNaturalStore_ == nullptr || storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + + return ExecuteRekey(passwd, singleVerNaturalStore_->GetDbProperties()); +} + +int SingleVerDatabaseOper::Import(const std::string &filePath, const CipherPassword &passwd) +{ + if (singleVerNaturalStore_ == nullptr || storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + + return ExecuteImport(filePath, passwd, singleVerNaturalStore_->GetDbProperties()); +} + +int SingleVerDatabaseOper::Export(const std::string &filePath, const CipherPassword &passwd) const +{ + if (singleVerNaturalStore_ == nullptr || storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + + return ExecuteExport(filePath, passwd, singleVerNaturalStore_->GetDbProperties()); +} + +bool SingleVerDatabaseOper::RekeyPreHandle(const CipherPassword &passwd, int &errCode) +{ + if (singleVerNaturalStore_->GetDbProperties().GetBoolProp(KvDBProperties::MEMORY_MODE, false)) { + errCode = -E_NOT_SUPPORT; + return false; + } + + CipherType cipherType; + CipherPassword cachePasswd; + singleVerNaturalStore_->GetDbProperties().GetPassword(cipherType, cachePasswd); + + if (cachePasswd.GetSize() == 0 && passwd.GetSize() == 0) { + errCode = E_OK; + return false; + } + + // need invoke sqlite3 rekey + if (cachePasswd.GetSize() > 0 && passwd.GetSize() > 0) { + errCode = RunRekeyLogic(cipherType, passwd); + return false; + } + + return true; +} + +int SingleVerDatabaseOper::BackupDb(const CipherPassword &passwd) const +{ + std::string filePrefix; + int errCode = GetCtrlFilePrefix(singleVerNaturalStore_->GetDbProperties(), filePrefix); + if (errCode != E_OK) { + return errCode; + } + + // create backup dir + std::string backupDir = filePrefix + DBConstant::PATH_BACKUP_POSTFIX; + errCode = DBCommon::CreateDirectory(backupDir); + if (errCode != E_OK) { + LOGE("create backup dir failed:%d.", errCode); + return errCode; + } + + std::vector dbDir {DBConstant::MAINDB_DIR, DBConstant::METADB_DIR, DBConstant::CACHEDB_DIR}; + for (const auto &item : dbDir) { + if (DBCommon::CreateDirectory(backupDir + "/" + item) != E_OK) { + return -E_SYSTEM_API_FAIL; + } + } + + errCode = SetSecOpt(backupDir, true); + if (errCode != E_OK) { + LOGE("Set backup dir secOption failed, errCode = [%d]", errCode); + return errCode; + } + + // export db to backup + errCode = RunExportLogic(passwd, filePrefix); + if (errCode != E_OK) { + return errCode; + } + + return SetSecOpt(backupDir, false); // set file SecOpt +} + +int SingleVerDatabaseOper::CloseStorages() +{ + // close old db + storageEngine_->Release(); + int errCode = RekeyRecover(singleVerNaturalStore_->GetDbProperties()); + if (errCode != E_OK) { + LOGE("Recover failed after rekey ok:%d.", errCode); + int innerCode = InitStorageEngine(); + if (innerCode != E_OK) { + LOGE("ReInit the handlePool failed:%d", innerCode); + } + } + return errCode; +} + +int SingleVerDatabaseOper::RekeyPostHandle(const CipherPassword &passwd) +{ + CipherType cipherType; + CipherPassword oldPasswd; + singleVerNaturalStore_->GetDbPropertyForUpdate().GetPassword(cipherType, oldPasswd); + singleVerNaturalStore_->GetDbPropertyForUpdate().SetPassword(cipherType, passwd); + singleVerNaturalStore_->GetDbPropertyForUpdate().SetBoolProp( + KvDBProperties::ENCRYPTED_MODE, (passwd.GetSize() == 0) ? false : true); + + return InitStorageEngine(); +} + +int SingleVerDatabaseOper::ExportMainDB(const std::string ¤tDir, const CipherPassword &passwd, + const std::string &dbDir) const +{ + std::string backupDbName = dbDir + DBConstant::MAINDB_DIR + "/" + DBConstant::SINGLE_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION; + std::string currentDb = currentDir + "/" + DBConstant::MAINDB_DIR + "/" + DBConstant::SINGLE_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION; + + CipherType cipherType; + CipherPassword currPasswd; + singleVerNaturalStore_->GetDbProperties().GetPassword(cipherType, currPasswd); + LOGI("Begin the sqlite main database export!"); + int errCode = SQLiteUtils::ExportDatabase(currentDb, cipherType, currPasswd, backupDbName, passwd); + if (errCode != E_OK) { + LOGE("Export the database failed:%d", errCode); + } + + return errCode; +} + +int SingleVerDatabaseOper::ExportMetaDB(const std::string ¤tDir, const CipherPassword &passwd, + const std::string &dbDir) const +{ + std::string backupDbName = dbDir + DBConstant::METADB_DIR + "/" + DBConstant::SINGLE_VER_META_STORE + + DBConstant::SQLITE_DB_EXTENSION; + std::string currentDb = currentDir + "/" + DBConstant::METADB_DIR + "/" + DBConstant::SINGLE_VER_META_STORE + + DBConstant::SQLITE_DB_EXTENSION; + if (!OS::CheckPathExistence(currentDb)) { // Is S2 label, can access + LOGD("No metaDB, no need Export metaDB."); + return E_OK; + } + + // Set metaDB db passwd same as mainDB temp, may be not need + LOGI("Begin the sqlite meta database export."); + int errCode = SQLiteUtils::ExportDatabase(currentDb, CipherType::DEFAULT, CipherPassword(), + backupDbName, CipherPassword()); + if (errCode != E_OK) { + LOGE("Export the database failed:%d", errCode); + } + + return errCode; +} + +int SingleVerDatabaseOper::ExportAllDatabases(const std::string ¤tDir, const CipherPassword &passwd, + const std::string &dbDir) const +{ + int errCode = ExportMainDB(currentDir, passwd, dbDir); + if (errCode != E_OK) { + LOGE("Export MainDB fail, errCode = [%d]", errCode); + return errCode; + } + + errCode = ExportMetaDB(currentDir, passwd, dbDir); + if (errCode != E_OK) { + LOGE("Export MetaDB fail, errCode = [%d]", errCode); + return errCode; + } + return errCode; +} + +int SingleVerDatabaseOper::BackupDatabase(const ImportFileInfo &info) const +{ + std::string currentMainFile = info.currentDir + DBConstant::MAINDB_DIR + "/" + DBConstant::SINGLE_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION; + std::string backupMainFile = info.backupDir + DBConstant::MAINDB_DIR + "/" + DBConstant::SINGLE_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION; + int errCode = DBCommon::CopyFile(currentMainFile, backupMainFile); + if (errCode != E_OK) { + LOGE("Backup the current database error:%d", errCode); + return errCode; + } + + std::string currentMetaFile = info.currentDir + DBConstant::METADB_DIR + "/" + DBConstant::SINGLE_VER_META_STORE + + DBConstant::SQLITE_DB_EXTENSION; + if (OS::CheckPathExistence(currentMetaFile)) { + std::string backupMetaFile = info.backupDir + DBConstant::METADB_DIR + "/" + DBConstant::SINGLE_VER_META_STORE + + DBConstant::SQLITE_DB_EXTENSION; + errCode = DBCommon::CopyFile(currentMetaFile, backupMetaFile); + if (errCode != E_OK) { + LOGE("Backup the current database error:%d", errCode); + return errCode; + } + } + return E_OK; +} + +int SingleVerDatabaseOper::BackupCurrentDatabase(const ImportFileInfo &info) const +{ + storageEngine_->Release(); + // create the pre flag file. + int errCode = OS::CreateFileByFileName(info.curValidFile); + if (errCode != E_OK) { + LOGE("create ctrl file failed:%d.", errCode); + return errCode; + } + + // create backup dir + errCode = DBCommon::CreateDirectory(info.backupDir); + if (errCode != E_OK) { + LOGE("Create backup dir failed:%d.", errCode); + return errCode; + } + + std::vector dbDir {DBConstant::MAINDB_DIR, DBConstant::METADB_DIR, DBConstant::CACHEDB_DIR}; + for (const auto &item : dbDir) { + if (DBCommon::CreateDirectory(info.backupDir + "/" + item) != E_OK) { + return -E_SYSTEM_API_FAIL; + } + } + + errCode = SetSecOpt(info.backupDir, true); + if (errCode != E_OK) { + LOGE("[singleVer][BackupCurrentDatabase]Set secOpt to dir fail, errCode = [%d]", errCode); + return errCode; + } + + errCode = BackupDatabase(info); + if (errCode != E_OK) { + LOGE("[SingleVerDatabaseOper][BackupCurrentDatabase] backup current database fail, errCode = [%d]", errCode); + return errCode; + } + + // Protect the loss of label information when the abnormal scene is restored + errCode = SetSecOpt(info.backupDir, false); + if (errCode != E_OK) { + LOGE("[singleVer][BackupCurrentDatabase]Set secOpt to file fail, errCode = [%d]", errCode); + return errCode; + } + + // rename + int innerCode = rename(info.curValidFile.c_str(), info.backValidFile.c_str()); + if (innerCode != 0) { + LOGE("Failed to rename the file after the backup:%d", errno); + errCode = -E_SYSTEM_API_FAIL; + } + return errCode; +} + +int SingleVerDatabaseOper::ClearCurrentDatabase(const ImportFileInfo &info) const +{ + int errCode = DBCommon::RemoveAllFilesOfDirectory(info.currentDir, false); + if (errCode != E_OK) { + return errCode; + } + + std::vector dbExtensionVec { DBConstant::MAINDB_DIR, DBConstant::METADB_DIR, DBConstant::CACHEDB_DIR }; + for (const auto &item : dbExtensionVec) { + if (DBCommon::CreateDirectory(info.currentDir + "/" + item) != E_OK) { + return -E_SYSTEM_API_FAIL; + } + } + return errCode; +} + +int SingleVerDatabaseOper::ImportUnpackedMainDatabase(const ImportFileInfo &info, + const CipherPassword &srcPasswd) const +{ + std::string unpackedMainFile = info.unpackedDir + DBConstant::MAINDB_DIR + "/" + DBConstant::SINGLE_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION; + std::string currentMainFile = info.currentDir + DBConstant::MAINDB_DIR + "/" + + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + CipherType cipherType; + CipherPassword passwd; + singleVerNaturalStore_->GetDbProperties().GetPassword(cipherType, passwd); + + std::string unpackedOldMainFile = info.unpackedDir + "/" + DBConstant::SINGLE_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION; + bool isMainDbExisted = OS::CheckPathExistence(unpackedMainFile); + bool isOldMainDbExisted = OS::CheckPathExistence(unpackedOldMainFile); // version < 3, mainDb in singer_ver/ + if (isMainDbExisted && isOldMainDbExisted) { + LOGE("Unpacked dir existed two diff version mainDb!"); + return -E_INVALID_FILE; + } + + int errCode = E_OK; + if (isMainDbExisted) { + errCode = SQLiteUtils::ExportDatabase(unpackedMainFile, cipherType, srcPasswd, currentMainFile, passwd); + if (errCode != E_OK) { + LOGE("Export the unpacked main database to current error:%d", errCode); + return -E_INVALID_FILE; + } + } + + if (isOldMainDbExisted) { + errCode = SQLiteUtils::ExportDatabase(unpackedOldMainFile, cipherType, srcPasswd, currentMainFile, passwd); + if (errCode != E_OK) { + LOGE("Export the unpacked old version(<3) main database to current error:%d", errCode); + return -E_INVALID_FILE; + } + } + return errCode; +} + +int SingleVerDatabaseOper::ImportUnpackedMetaDatabase(const ImportFileInfo &info) const +{ + LOGI("MetaDB existed, need import, no need upgrade!"); + std::string unpackedMetaFile = info.unpackedDir + DBConstant::METADB_DIR + "/" + + DBConstant::SINGLE_VER_META_STORE + DBConstant::SQLITE_DB_EXTENSION; + std::string currentMetaFile = info.currentDir + DBConstant::METADB_DIR + "/" + + DBConstant::SINGLE_VER_META_STORE + DBConstant::SQLITE_DB_EXTENSION; + int errCode = SQLiteUtils::ExportDatabase(unpackedMetaFile, CipherType::DEFAULT, CipherPassword(), + currentMetaFile, CipherPassword()); + if (errCode != E_OK) { + LOGE("export the unpacked meta database to current error:%d", errCode); + errCode = -E_INVALID_FILE; + } + return errCode; +} + +int SingleVerDatabaseOper::ImportUnpackedDatabase(const ImportFileInfo &info, const CipherPassword &srcPasswd) const +{ + std::string unpackedMetaFile = info.unpackedDir + DBConstant::METADB_DIR + "/" + + DBConstant::SINGLE_VER_META_STORE + DBConstant::SQLITE_DB_EXTENSION; + bool metaDbExisted = OS::CheckPathExistence(unpackedMetaFile); + int errCode = ClearCurrentDatabase(info); + if (errCode != E_OK) { + return errCode; + } + + errCode = ImportUnpackedMainDatabase(info, srcPasswd); + if (errCode != E_OK) { + LOGE("import unpacked mainDb fail, errCode = [%d]", errCode); + return errCode; + } + + if (metaDbExisted) { // Is S2 label, no need deal + errCode = ImportUnpackedMetaDatabase(info); + if (errCode != E_OK) { + LOGE("import unpacked metaDb fail, errCode = [%d]", errCode); + return errCode; + } + } + + (void)SetSecOpt(info.currentDir, false); // not care err, Make sure to set the label + + // reinitialize the database, and delete the backup database. + errCode = singleVerNaturalStore_->InitDatabaseContext(singleVerNaturalStore_->GetDbProperties(), true); + if (errCode != E_OK) { + LOGE("InitDatabaseContext error:%d", errCode); + return errCode; + } + + // rename the flag file. + int innerCode = rename(info.backValidFile.c_str(), info.curValidFile.c_str()); + if (innerCode != E_OK) { + LOGE("Failed to rename after the import operation:%d", errno); + errCode = -E_SYSTEM_API_FAIL; + } + return errCode; +} + +int SingleVerDatabaseOper::ImportPostHandle() const +{ + return singleVerNaturalStore_->InitDatabaseContext(singleVerNaturalStore_->GetDbProperties(), true); +} + +// private begin +int SingleVerDatabaseOper::RunExportLogic(const CipherPassword &passwd, const std::string &filePrefix) const +{ + std::string currentMainDb = filePrefix + "/" + DBConstant::MAINDB_DIR + "/" + + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + CipherType cipherType; + CipherPassword currPasswd; + singleVerNaturalStore_->GetDbProperties().GetPassword(cipherType, currPasswd); + + // get backup db name + std::string backupMainDbName = filePrefix + DBConstant::PATH_BACKUP_POSTFIX + "/" + DBConstant::MAINDB_DIR + "/" + + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + + int errCode = SQLiteUtils::ExportDatabase(currentMainDb, cipherType, currPasswd, backupMainDbName, passwd); + if (errCode != E_OK) { + LOGE("single ver database export mainDb fail, errCode = [%d]", errCode); + return errCode; + } + + std::string currentMetaDb = filePrefix + "/" + DBConstant::METADB_DIR + "/" + + DBConstant::SINGLE_VER_META_STORE + DBConstant::SQLITE_DB_EXTENSION; + if (!OS::CheckPathExistence(currentMetaDb)) { + LOGD("No metaDB, no need Export metaDB."); + return E_OK; + } + + LOGI("Begin export metaDB to back up!"); + std::string backupMetaDbName = filePrefix + DBConstant::PATH_BACKUP_POSTFIX + "/" + DBConstant::METADB_DIR + "/" + + DBConstant::SINGLE_VER_META_STORE + DBConstant::SQLITE_DB_EXTENSION; + // Set metaDB db passwd same as mainDB temp, may be not need + errCode = SQLiteUtils::ExportDatabase(currentMetaDb, CipherType::DEFAULT, CipherPassword(), + backupMetaDbName, CipherPassword()); + if (errCode != E_OK) { + LOGE("single ver database export metaDb fail, errCode = [%d]", errCode); + return errCode; + } + return errCode; +} + +int SingleVerDatabaseOper::InitStorageEngine() +{ + OpenDbProperties option; + InitDataBaseOption(option); + bool isMemoryMode = singleVerNaturalStore_->GetDbProperties().GetBoolProp(KvDBProperties::MEMORY_MODE, false); + // Use 1 read handle to check passwd + StorageEngineAttr poolSize = {0, 1, 1, 16}; // at most 1 write 16 read. + if (isMemoryMode) { + poolSize.minWriteNum = 1; // keep at least one connection. + } + + std::string identify = singleVerNaturalStore_->GetDbProperties().GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + int errCode = storageEngine_->InitSQLiteStorageEngine(poolSize, option, identify); + if (errCode != E_OK) { + LOGE("[SingleVerOper]Init the sqlite storage engine failed:%d", errCode); + } + return errCode; +} + +void SingleVerDatabaseOper::InitDataBaseOption(OpenDbProperties &option) const +{ + const KvDBProperties properties = singleVerNaturalStore_->GetDbProperties(); + const std::string dataDir = properties.GetStringProp(KvDBProperties::DATA_DIR, ""); + const std::string identifierDir = properties.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + std::string uri = dataDir + "/" + identifierDir + "/" + DBConstant::SINGLE_SUB_DIR + "/" + + DBConstant::MAINDB_DIR + "/" + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + bool isMemoryDb = properties.GetBoolProp(KvDBProperties::MEMORY_MODE, false); + if (isMemoryDb) { + uri = identifierDir + DBConstant::SQLITE_MEMDB_IDENTIFY; + LOGD("Begin create memory natural store database"); + } + + std::vector createTableSqls; + CipherType cipherType; + CipherPassword passwd; + properties.GetPassword(cipherType, passwd); + bool isCreate = properties.GetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + + SecurityOption securityOpt; + securityOpt.securityLabel = properties.GetSecLabel(); + securityOpt.securityFlag = properties.GetSecFlag(); + + option = {uri, isCreate, isMemoryDb, createTableSqls, cipherType, passwd}; + std::string dirPath = dataDir + "/" + identifierDir + "/" + DBConstant::SINGLE_SUB_DIR; + option.subdir = dirPath; + option.securityOpt = securityOpt; + option.conflictReslovePolicy = properties.GetIntProp(KvDBProperties::CONFLICT_RESOLVE_POLICY, 0); +} + +int SingleVerDatabaseOper::RunRekeyLogic(CipherType type, const CipherPassword &passwd) +{ + OpenDbProperties option; + InitDataBaseOption(option); + option.createIfNecessary = true; + option.cipherType = type; + sqlite3 *db = nullptr; + + // open one temporary connection. + int errCode = SQLiteUtils::OpenDatabase(option, db); + if (errCode != E_OK) { + LOGE("[RunRekeyLogic] Open database new connect fail!, errCode = [%d]", errCode); + goto END; + } + + errCode = SQLiteUtils::Rekey(db, passwd); + if (errCode != E_OK) { + LOGE("[RunRekeyLogic] Rekey fail!, errCode = [%d]", errCode); + goto END; + } + + // Release all the connections, update the passwd and re-initialize the storage engine. + storageEngine_->Release(); + singleVerNaturalStore_->GetDbPropertyForUpdate().SetPassword(type, passwd); + errCode = InitStorageEngine(); + if (errCode != E_OK) { + LOGE("Init storage engine while rekey open failed:%d", errCode); + } + + // Rekey while locked before init storage engine, it can not open file, but rekey successfully + if (storageEngine_->GetEngineState() != EngineState::MAINDB && errCode == -E_EKEYREVOKED) { + LOGI("Rekey successfully, locked state init state successfully, need ignore open file failed!"); + errCode = -E_FORBID_CACHEDB; + } + +END: + if (db != nullptr) { + (void)sqlite3_close_v2(db); + db = nullptr; + } + return errCode; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/operation/single_ver_database_oper.h b/mock/distributeddb/storage/src/operation/single_ver_database_oper.h new file mode 100644 index 00000000..b0ea7870 --- /dev/null +++ b/mock/distributeddb/storage/src/operation/single_ver_database_oper.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_DATABASE_OPER_H +#define SINGLE_VER_DATABASE_OPER_H + +#include "database_oper.h" +#include "sqlite_single_ver_natural_store.h" + +namespace DistributedDB { +class SingleVerDatabaseOper : public DatabaseOper { +public: + SingleVerDatabaseOper(SQLiteSingleVerNaturalStore *naturalStore, SQLiteStorageEngine *storageEngine); + ~SingleVerDatabaseOper() override {}; + + int Rekey(const CipherPassword &passwd) override; + + int Import(const std::string &filePath, const CipherPassword &passwd) override; + + int Export(const std::string &filePath, const CipherPassword &passwd) const override; + +protected: + bool RekeyPreHandle(const CipherPassword &passwd, int &errCode) override; + + int BackupDb(const CipherPassword &passwd) const override; + + int CloseStorages() override; + + int RekeyPostHandle(const CipherPassword &passwd) override; + + int ExportAllDatabases(const std::string ¤tDir, const CipherPassword &passwd, + const std::string &dbDir) const override; + + int BackupCurrentDatabase(const ImportFileInfo &info) const override; + + int ImportUnpackedDatabase(const ImportFileInfo &info, const CipherPassword &srcPasswd) const override; + + int ImportPostHandle() const override; + +private: + int InitStorageEngine(); + + void InitDataBaseOption(OpenDbProperties &option) const; + + int RunExportLogic(const CipherPassword &passwd, const std::string &filePrefix) const; + + int RunRekeyLogic(CipherType type, const CipherPassword &passwd); + + int ExportMainDB(const std::string ¤tDir, const CipherPassword &passwd, const std::string &dbDir) const; + + int ExportMetaDB(const std::string ¤tDir, const CipherPassword &passwd, const std::string &dbDir) const; + + int ClearCurrentDatabase(const ImportFileInfo &info) const; + + int ImportUnpackedMainDatabase(const ImportFileInfo &info, const CipherPassword &srcPasswd) const; + + int ImportUnpackedMetaDatabase(const ImportFileInfo &info) const; + + int SetSecOpt(const std::string &path, bool isDir = true) const; + + int BackupDatabase(const ImportFileInfo &info) const; + + SQLiteSingleVerNaturalStore *singleVerNaturalStore_; + SQLiteStorageEngine *storageEngine_; +}; +} // namespace DistributedDB +#endif // SINGLE_VER_DATABASE_OPER_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/package_file.cpp b/mock/distributeddb/storage/src/package_file.cpp new file mode 100644 index 00000000..85eee274 --- /dev/null +++ b/mock/distributeddb/storage/src/package_file.cpp @@ -0,0 +1,571 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "package_file.h" + +#include + +#include "db_errno.h" +#include "value_hash_calc.h" +#include "parcel.h" +#include "platform_specific.h" + +namespace DistributedDB { +using std::string; +using std::vector; +using std::list; +using std::ifstream; +using std::ofstream; +using std::ios; +using std::ios_base; + +namespace { + constexpr uint32_t MAX_FILE_NAME_LEN = 256; + constexpr uint32_t CHECKSUM_LEN = SHA256_DIGEST_LENGTH; + constexpr uint32_t CHECKSUM_BLOCK_SIZE = 64; + constexpr uint32_t DEVICE_ID_LEN = SHA256_DIGEST_LENGTH; + constexpr uint32_t MAGIC_LEN = 16; + constexpr uint32_t CURRENT_VERSION = 0; + constexpr uint64_t BUFFER_LEN = 4096; + const string MAGIC = "HW package file"; + const string FILE_SEPARATOR = "/"; + const string INVALID_FILE_WORDS = ".."; + + const uint32_t FILE_HEADER_LEN = MAGIC_LEN + CHECKSUM_LEN + DEVICE_ID_LEN + Parcel::GetUInt32Len() * 3; + const uint32_t FILE_CONTEXT_LEN = MAX_FILE_NAME_LEN + Parcel::GetUInt32Len() * 2 + Parcel::GetUInt64Len() * 2; +} + +struct FileContext { + char fileName[MAX_FILE_NAME_LEN] = {0}; + uint32_t fileType = 0; + uint32_t parentID = 0; + uint64_t fileLen = 0; + uint64_t offset = 0; +}; + +static void Clear(ofstream &target, string targetFile) +{ + if (target.is_open()) { + target.close(); + } + if (OS::RemoveFile(targetFile.c_str()) != E_OK) { + LOGE("Remove file failed."); + } + return; +} + +static int GetChecksum(const string &file, vector &result) +{ + ifstream fileHandle(file, ios::in | ios::binary); + if (!fileHandle.good()) { + LOGE("[GetChecksum]Error fileHandle!"); + return -E_INVALID_PATH; + } + ValueHashCalc calc; + int errCode = calc.Initialize(); + if (errCode != E_OK) { + LOGE("[GetChecksum]Calc Initialize fail!"); + return errCode; + } + fileHandle.seekg(static_cast(MAGIC_LEN + Parcel::GetUInt32Len() + CHECKSUM_LEN), ios_base::beg); + vector buffer(CHECKSUM_BLOCK_SIZE, 0); + bool readEnd = false; + while (!readEnd) { + fileHandle.read(reinterpret_cast(buffer.data()), buffer.size()); + if (fileHandle.eof()) { + readEnd = true; + } else if (!fileHandle.good()) { + LOGE("[GetChecksum]fileHandle error!"); + return -E_INVALID_PATH; + } + errCode = calc.Update(buffer); + if (errCode != E_OK) { + LOGE("[GetChecksum]Calc Update fail!"); + return errCode; + } + buffer.assign(CHECKSUM_BLOCK_SIZE, 0); + } + vector resultBuf; + errCode = calc.GetResult(resultBuf); + if (errCode != E_OK) { + LOGE("[GetChecksum]Calc GetResult fail!"); + return errCode; + } + result.assign(resultBuf.begin(), resultBuf.end()); + return E_OK; +} + +static int GetFileContexts(const string &sourcePath, list &fileContexts) +{ + list files; + int errCode = OS::GetFileAttrFromPath(sourcePath, files, false); + if (errCode != E_OK) { + LOGE("[GetFileContexts] get file attr from path fail, errCode = [%d]", errCode); + return errCode; + } + FileContext fileContext; + int countLimit = 0; + for (auto file = files.begin(); file != files.end(); file++, countLimit++) { + if (countLimit > 20) { // Limit number of files 20 for security + LOGE("Too deep access for get file context!"); + return -E_INVALID_PATH; + } + + if (file->fileType != OS::FILE && file->fileType != OS::PATH) { + continue; + } + + errCode = memset_s(fileContext.fileName, MAX_FILE_NAME_LEN, 0, MAX_FILE_NAME_LEN); + if (errCode != EOK) { + return -E_SECUREC_ERROR; + } + + if (file->fileName.size() >= MAX_FILE_NAME_LEN) { + LOGE("file name is too long!"); + return -E_INVALID_FILE; + } + + errCode = memcpy_s(fileContext.fileName, MAX_FILE_NAME_LEN, file->fileName.c_str(), file->fileName.size()); + if (errCode != EOK) { + return -E_SECUREC_ERROR; + } + + fileContext.fileLen = file->fileLen; + fileContext.fileType = file->fileType; + fileContexts.push_back(fileContext); + } + LOGD("Get file contexts, fileContexts size is [%zu]", fileContexts.size()); + return E_OK; +} + +static int FileContentCopy(ifstream &sourceFile, ofstream &targetFile, uint64_t fileLen) +{ + uint64_t leftLen = fileLen; + vector buffer(BUFFER_LEN, 0); + while (leftLen > 0) { + uint64_t readLen = (leftLen > BUFFER_LEN) ? BUFFER_LEN : leftLen; + sourceFile.read(buffer.data(), readLen); + if (!sourceFile.good()) { + LOGE("[FileContentCopy] SourceFile error! sys[%d]", errno); + return -E_INVALID_PATH; + } + targetFile.write(buffer.data(), readLen); + if (!targetFile.good()) { + LOGE("[FileContentCopy] TargetFile error! sys[%d]", errno); + return -E_INVALID_PATH; + } + leftLen -= readLen; + } + return E_OK; +} + +static int PackFileHeader(ofstream &targetFile, const FileInfo &fileInfo, uint32_t fileNum) +{ + if (fileInfo.deviceID.size() != DEVICE_ID_LEN) { + return -E_INVALID_ARGS; + } + vector buffer(FILE_HEADER_LEN, 0); + vector checksum(CHECKSUM_LEN, 0); + Parcel parcel(buffer.data(), FILE_HEADER_LEN); + + int errCode = parcel.WriteBlob(MAGIC.c_str(), MAGIC_LEN); + if (errCode != E_OK) { + return errCode; + } + // before current version package version is always 0 + errCode = parcel.WriteUInt32(CURRENT_VERSION); + if (errCode != E_OK) { + return errCode; + } + errCode = parcel.WriteBlob(checksum.data(), CHECKSUM_LEN); + if (errCode != E_OK) { + return errCode; + } + errCode = parcel.WriteBlob(fileInfo.deviceID.c_str(), DEVICE_ID_LEN); + if (errCode != E_OK) { + return errCode; + } + errCode = parcel.WriteUInt32(fileInfo.dbType); + if (errCode != E_OK) { + return errCode; + } + errCode = parcel.WriteUInt32(fileNum); + if (errCode != E_OK) { + return errCode; + } + targetFile.write(reinterpret_cast(buffer.data()), buffer.size()); + if (!targetFile.good()) { + return -E_INVALID_PATH; + } + return E_OK; +} + +static int CheckMagicHeader(Parcel &fileHeaderParcel) +{ + vector buffer(MAGIC_LEN, 0); + (void)fileHeaderParcel.ReadBlob(buffer.data(), MAGIC_LEN); + if (fileHeaderParcel.IsError()) { + LOGE("[CheckMagicHeader]fileHeaderParcel error!"); + return -E_PARSE_FAIL; + } + if (memcmp(MAGIC.c_str(), buffer.data(), MAGIC_LEN) != 0) { + return -E_INVALID_FILE; + } + return E_OK; +} + +static int UnpackFileHeader(ifstream &sourceFile, const string &sourceFileName, FileInfo &fileInfo, uint32_t &fileNum) +{ + vector fileHeader(FILE_HEADER_LEN, 0); + sourceFile.read(reinterpret_cast(fileHeader.data()), FILE_HEADER_LEN); + if (!sourceFile.good()) { + LOGE("UnpackFileHeader sourceFile error!"); + return -E_INVALID_FILE; + } + Parcel parcel(fileHeader.data(), FILE_HEADER_LEN); + int errCode = CheckMagicHeader(parcel); + if (errCode != E_OK) { + return errCode; + } + uint32_t version; + vector buffer(CHECKSUM_LEN, 0); + (void)parcel.ReadUInt32(version); + (void)parcel.ReadBlob(buffer.data(), CHECKSUM_LEN); + if (parcel.IsError()) { + LOGE("UnpackFileHeader parcel version error!"); + return -E_PARSE_FAIL; + } + vector checksum(CHECKSUM_LEN, 0); + errCode = GetChecksum(sourceFileName, checksum); + if (errCode != E_OK) { + LOGE("Get checksum failed."); + return errCode; + } + if (buffer != checksum) { + LOGE("Checksum check failed."); + return -E_INVALID_FILE; + } + buffer.resize(DEVICE_ID_LEN); + (void)parcel.ReadBlob(buffer.data(), DEVICE_ID_LEN); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + fileInfo.deviceID.resize(DEVICE_ID_LEN); + fileInfo.deviceID.assign(buffer.begin(), buffer.end()); + (void)parcel.ReadUInt32(fileInfo.dbType); + (void)parcel.ReadUInt32(fileNum); + if (parcel.IsError()) { + LOGE("UnpackFileHeader parcel dbType error!"); + return -E_PARSE_FAIL; + } + return E_OK; +} + +static int PackFileContext(ofstream &targetFile, const FileContext &fileContext) +{ + vector buffer(FILE_CONTEXT_LEN, 0); + Parcel parcel(buffer.data(), FILE_CONTEXT_LEN); + int errCode = parcel.WriteBlob(fileContext.fileName, MAX_FILE_NAME_LEN); + if (errCode != E_OK) { + LOGE("PackFileContext fileContext fileName error!"); + return errCode; + } + errCode = parcel.WriteUInt32(fileContext.fileType); + if (errCode != E_OK) { + return errCode; + } + errCode = parcel.WriteUInt32(0); + if (errCode != E_OK) { + return errCode; + } + errCode = parcel.WriteUInt64(fileContext.fileLen); + if (errCode != E_OK) { + return errCode; + } + errCode = parcel.WriteUInt64(fileContext.offset); + if (errCode != E_OK) { + return errCode; + } + targetFile.write(reinterpret_cast(buffer.data()), buffer.size()); + if (!targetFile.good()) { + return -E_INVALID_PATH; + } + return E_OK; +} + +static int UnpackFileContext(ifstream &sourceFile, FileContext &fileContext) +{ + vector buffer(FILE_CONTEXT_LEN, 0); + sourceFile.read(reinterpret_cast(buffer.data()), buffer.size()); + if (!sourceFile.good()) { + return -E_INVALID_PATH; + } + Parcel parcel(buffer.data(), FILE_CONTEXT_LEN); + (void)parcel.ReadBlob(fileContext.fileName, MAX_FILE_NAME_LEN); + (void)parcel.ReadUInt32(fileContext.fileType); + (void)parcel.ReadUInt32(fileContext.parentID); + (void)parcel.ReadUInt64(fileContext.fileLen); + (void)parcel.ReadUInt64(fileContext.offset); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + return E_OK; +} + +static int PackFileContent(ofstream &targetFile, const string &sourcePath, const FileContext &fileContext) +{ + if (fileContext.fileType != OS::FILE) { + return E_OK; + } + string fileName = sourcePath + fileContext.fileName; + ifstream file(fileName, ios::in | ios::binary); + if (!file.good()) { + LOGE("[PackFileContent] File error! sys[%d]", errno); + return -E_INVALID_PATH; + } + file.seekg(0, ios_base::end); + if (!file.good()) { + LOGE("[PackFileContent]file error after seekg! sys[%d]", errno); + return -E_INVALID_PATH; + } + if (file.tellg() < 0) { + LOGE("[PackFileContent]file error after tellg! sys[%d]", errno); + return -E_INVALID_PATH; + } + uint64_t fileLen = static_cast(file.tellg()); + file.seekg(0, ios_base::beg); + if (!file.good()) { + LOGE("[PackFileContent]file error after seekg fileLen! sys[%d]", errno); + return -E_INVALID_PATH; + } + + return FileContentCopy(file, targetFile, fileLen); +} + +static int UnpackFileContent(ifstream &sourceFile, const string &targetPath, const FileContext &fileContext) +{ + if (fileContext.fileType != OS::FILE) { + return E_OK; + } + + string fileName = fileContext.fileName; + fileName = targetPath + FILE_SEPARATOR + fileName; + + // check if fileName contains the words ".." + std::string::size_type pos = fileName.find(INVALID_FILE_WORDS); + if (pos != std::string::npos) { + LOGE("[UnpackFileContent]fileName contains the words double dot!!!"); + return -E_INVALID_PATH; + } + + ofstream file(fileName, ios::out | ios::binary); + if (!file.good()) { + file.close(); + LOGE("[UnpackFileContent]Get checksum failed."); + return -E_INVALID_PATH; + } + int errCode = FileContentCopy(sourceFile, file, fileContext.fileLen); + file.close(); + return errCode; +} + +static int WriteChecksum(const string &targetFile) +{ + vector checksum(CHECKSUM_LEN, 0); + int errCode = GetChecksum(targetFile, checksum); + if (errCode != E_OK) { + LOGE("Get checksum failed."); + return errCode; + } + ofstream targetHandle(targetFile, ios::in | ios::out | ios::binary); + if (!targetHandle.good()) { + Clear(targetHandle, targetFile); + LOGE("[WriteChecksum]targetHandle error, sys err [%d]", errno); + return -E_INVALID_PATH; + } + targetHandle.seekp(static_cast(MAGIC_LEN + Parcel::GetUInt32Len()), ios_base::beg); + if (!targetHandle.good()) { + Clear(targetHandle, targetFile); + LOGE("[WriteChecksum]targetHandle error after seekp, sys err [%d]", errno); + return -E_INVALID_PATH; + } + targetHandle.write(checksum.data(), checksum.size()); + if (!targetHandle.good()) { + Clear(targetHandle, targetFile); + LOGE("[WriteChecksum]targetHandle error after write, sys err [%d]", errno); + return -E_INVALID_PATH; + } + targetHandle.close(); + return E_OK; +} + +static int CopyFilePermissions(const string &sourceFile, const string &targetFile) +{ + uint32_t permissions; + int errCode = OS::GetFilePermissions(sourceFile, permissions); + if (errCode != E_OK) { + LOGE("Get file permissions failed."); + return errCode; + } + errCode = OS::SetFilePermissions(targetFile, permissions); + if (errCode != E_OK) { + LOGE("Set file permissions failed."); + } + return errCode; +} + +int PackageFile::PackageFiles(const string &sourcePath, const string &targetFile, + const FileInfo &fileInfo) +{ + int errCode = ExePackage(sourcePath, targetFile, fileInfo); + if (errno == EKEYREVOKED) { + errCode = -E_EKEYREVOKED; + LOGE("[PackageFile][PackageFiles] Forbid access files errCode [%d].", errCode); + } + return errCode; +} + +int PackageFile::GetPackageVersion(const std::string &sourceFile, uint32_t &version) +{ + int errCode = E_OK; + vector fileHeader(FILE_HEADER_LEN, 0); + Parcel parcel(fileHeader.data(), FILE_HEADER_LEN); + + ifstream sourceHandle(sourceFile, ios::in | ios::binary); + if (!sourceHandle.good()) { + LOGE("sourceHandle error, sys err [%d]", errno); + errCode = -E_INVALID_PATH; + goto END; + } + + sourceHandle.read(reinterpret_cast(fileHeader.data()), FILE_HEADER_LEN); + if (!sourceHandle.good()) { + LOGE("GetPackageVersion read sourceFile handle error!"); + errCode = -E_INVALID_PATH; + goto END; + } + + errCode = CheckMagicHeader(parcel); + if (errCode != E_OK) { + errCode = -E_INVALID_PATH; + goto END; + } + + (void)parcel.ReadUInt32(version); +END: + if (errno == EKEYREVOKED) { + errCode = -E_EKEYREVOKED; + LOGE("[PackageFile][PackageFiles] Forbid access files by secLabel, errCode [%d].", errCode); + } + return errCode; +} + +int PackageFile::ExePackage(const string &sourcePath, const string &targetFile, + const FileInfo &fileInfo) +{ + list fileContexts; + int errCode = GetFileContexts(sourcePath, fileContexts); + if (errCode != E_OK) { + return errCode; + } + if (fileContexts.empty()) { + return -E_EMPTY_PATH; + } + ofstream targetHandle(targetFile, ios::out | ios::binary); + if (!targetHandle.good()) { + Clear(targetHandle, targetFile); + LOGE("[PackageFiles]targetHandle error, sys err [%d], [%zu]", errno, fileContexts.size()); + return -E_INVALID_PATH; + } + + errCode = CopyFilePermissions(sourcePath + FILE_SEPARATOR + string(fileContexts.front().fileName), targetFile); + if (errCode != E_OK) { + LOGE("Copy file fail when execute pack files! errCode = [%d]", errCode); + Clear(targetHandle, targetFile); + return errCode; + } + + errCode = PackFileHeader(targetHandle, fileInfo, static_cast(fileContexts.size())); + if (errCode != E_OK) { + Clear(targetHandle, targetFile); + LOGE("[PackageFiles]Pack file header err[%d]!!!", errCode); + return errCode; + } + // FILE_HEADER_LEN is 92, FILE_CONTEXT_LEN is 280, fileContexts.size() < UINT_MAX, the offset will never overflow. + uint64_t offset = FILE_HEADER_LEN + FILE_CONTEXT_LEN * static_cast(fileContexts.size()); + for (auto &file : fileContexts) { + file.offset = offset; + errCode = PackFileContext(targetHandle, file); + if (errCode != E_OK) { + Clear(targetHandle, targetFile); + LOGE("[PackageFiles]Pack file context err[%d]!!!", errCode); + return errCode; + } + offset += file.fileLen; + } + for (const auto &file : fileContexts) { + // If file type is path no need pack content in PackFileContent + errCode = PackFileContent(targetHandle, sourcePath, file); + if (errCode != E_OK) { + Clear(targetHandle, targetFile); + return errCode; + } + } + targetHandle.close(); + return WriteChecksum(targetFile); +} + +int PackageFile::UnpackFile(const string &sourceFile, const string &targetPath, FileInfo &fileInfo) +{ + ifstream sourceHandle(sourceFile, ios::in | ios::binary); + if (!sourceHandle.good()) { + LOGE("sourceHandle error, sys err [%d]", errno); + return -E_INVALID_PATH; + } + uint32_t fileNum; + int errCode = UnpackFileHeader(sourceHandle, sourceFile, fileInfo, fileNum); + if (errCode != E_OK) { + return errCode; + } + FileContext fileContext; + list fileContexts; + sourceHandle.seekg(static_cast(FILE_HEADER_LEN), ios_base::beg); + if (!sourceHandle.good()) { + return -E_INVALID_PATH; + } + for (uint32_t fileCount = 0; fileCount < fileNum; fileCount++) { + errCode = UnpackFileContext(sourceHandle, fileContext); + if (errCode != E_OK) { + return errCode; + } + fileContexts.push_back(fileContext); + } + + for (const auto &file : fileContexts) { + if (file.fileType == OS::PATH) { + std::string dirPath = targetPath + "/" + std::string(file.fileName); + if (!OS::CheckPathExistence(dirPath) && OS::MakeDBDirectory(dirPath) != E_OK) { + return -E_SYSTEM_API_FAIL; + } + continue; + } + errCode = UnpackFileContent(sourceHandle, targetPath, file); + if (errCode != E_OK) { + return errCode; + } + } + return E_OK; +} +} diff --git a/mock/distributeddb/storage/src/package_file.h b/mock/distributeddb/storage/src/package_file.h new file mode 100644 index 00000000..c404091e --- /dev/null +++ b/mock/distributeddb/storage/src/package_file.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 PACKAGE_FILE_H +#define PACKAGE_FILE_H + +#include +namespace DistributedDB { +struct FileInfo { + uint32_t dbType; + std::string deviceID; +}; + +class PackageFile { +public: + PackageFile() {} + ~PackageFile() {} + static int PackageFiles(const std::string &sourcePath, const std::string &targetFile, const FileInfo &fileInfo); + static int UnpackFile(const std::string &sourceFile, const std::string &targetPath, FileInfo &fileInfo); + static int GetPackageVersion(const std::string &sourceFile, uint32_t &version); +private: + static int ExePackage(const std::string &sourcePath, const std::string &targetFile, const FileInfo &fileInfo); +}; +} + +#endif // PACKAGE_FILE_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/relational_store_connection.cpp b/mock/distributeddb/storage/src/relational_store_connection.cpp new file mode 100644 index 00000000..76b7fffc --- /dev/null +++ b/mock/distributeddb/storage/src/relational_store_connection.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "relational_store_connection.h" +#include "db_errno.h" +#include "sqlite_single_ver_relational_storage_executor.h" + +namespace DistributedDB { +RelationalStoreConnection::RelationalStoreConnection() : isExclusive_(false) +{} + +RelationalStoreConnection::RelationalStoreConnection(IRelationalStore *store) + : store_(store), isExclusive_(false) +{} + +int RelationalStoreConnection::Pragma(int cmd, void *parameter) +{ + (void) cmd; + (void) parameter; + return E_OK; +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/relational_store_instance.cpp b/mock/distributeddb/storage/src/relational_store_instance.cpp new file mode 100644 index 00000000..ca1c40b1 --- /dev/null +++ b/mock/distributeddb/storage/src/relational_store_instance.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "relational_store_instance.h" + +#include +#include + +#include "db_common.h" +#include "db_errno.h" +#include "sqlite_relational_store.h" +#include "log_print.h" + +namespace DistributedDB { +RelationalStoreInstance *RelationalStoreInstance::instance_ = nullptr; +std::mutex RelationalStoreInstance::instanceLock_; + +static std::mutex storeLock_; +static std::map dbs_; + +RelationalStoreInstance::RelationalStoreInstance() +{} + +RelationalStoreInstance *RelationalStoreInstance::GetInstance() +{ + std::lock_guard lockGuard(instanceLock_); + if (instance_ == nullptr) { + instance_ = new (std::nothrow) RelationalStoreInstance(); + if (instance_ == nullptr) { + LOGE("failed to new RelationalStoreManager!"); + return nullptr; + } + } + return instance_; +} + +int RelationalStoreInstance::ReleaseDataBaseConnection(RelationalStoreConnection *connection) +{ + if (connection == nullptr) { + return -E_INVALID_DB; + } + auto manager = RelationalStoreInstance::GetInstance(); + if (manager == nullptr) { + return -E_OUT_OF_MEMORY; + } + std::string identifier = connection->GetIdentifier(); + manager->EnterDBOpenCloseProcess(identifier); + int errCode = connection->Close(); + manager->ExitDBOpenCloseProcess(identifier); + + if (errCode != E_OK) { + LOGE("Release db connection failed. %d", errCode); + } + return errCode; +} + +int RelationalStoreInstance::CheckDatabaseFileStatus(const std::string &id) +{ + std::lock_guard lockGuard(storeLock_); + if (dbs_.count(id) != 0 && dbs_[id] != nullptr) { + return -E_BUSY; + } + return E_OK; +} + +static IRelationalStore *GetFromCache(const RelationalDBProperties &properties, int &errCode) +{ + errCode = E_OK; + std::string identifier = properties.GetStringProp(RelationalDBProperties::IDENTIFIER_DATA, ""); + std::lock_guard lockGuard(storeLock_); + auto iter = dbs_.find(identifier); + if (iter == dbs_.end()) { + errCode = -E_NOT_FOUND; + return nullptr; + } + + auto *db = iter->second; + if (db == nullptr) { + LOGE("Store cache is nullptr, there may be a logic error"); + errCode = -E_INTERNAL_ERROR; + return nullptr; + } + db->IncObjRef(db); + return db; +} + +// Save to IKvDB to the global map +void RelationalStoreInstance::RemoveKvDBFromCache(const RelationalDBProperties &properties) +{ + std::string identifier = properties.GetStringProp(RelationalDBProperties::IDENTIFIER_DATA, ""); + std::lock_guard lockGuard(storeLock_); + dbs_.erase(identifier); +} + +void RelationalStoreInstance::SaveRelationalDBToCache(IRelationalStore *store, const RelationalDBProperties &properties) +{ + std::string identifier = properties.GetStringProp(RelationalDBProperties::IDENTIFIER_DATA, ""); + std::lock_guard lockGuard(storeLock_); + if (dbs_.count(identifier) == 0) { + dbs_.insert(std::pair(identifier, store)); + } +} + +IRelationalStore *RelationalStoreInstance::OpenDatabase(const RelationalDBProperties &properties, int &errCode) +{ + auto db = new (std::nothrow) SQLiteRelationalStore(); + if (db == nullptr) { + errCode = -E_OUT_OF_MEMORY; + LOGE("Failed to get relational store! err:%d", errCode); + return nullptr; + } + + db->OnClose([this, properties]() { + LOGI("Remove from the cache"); + this->RemoveKvDBFromCache(properties); + }); + + errCode = db->Open(properties); + if (errCode != E_OK) { + LOGE("Failed to open db! err:%d", errCode); + RefObject::KillAndDecObjRef(db); + return nullptr; + } + db->WakeUpSyncer(); + + SaveRelationalDBToCache(db, properties); + return db; +} + +IRelationalStore *RelationalStoreInstance::GetDataBase(const RelationalDBProperties &properties, int &errCode) +{ + auto *db = GetFromCache(properties, errCode); + if (db != nullptr) { + LOGD("Get db from cache."); + return db; + } + + // file lock + RelationalStoreInstance *manager = RelationalStoreInstance::GetInstance(); + if (manager == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + + db = manager->OpenDatabase(properties, errCode); + if (errCode != E_OK) { + LOGE("Create data base failed, errCode = [%d]", errCode); + } + return db; +} + +RelationalStoreConnection *RelationalStoreInstance::GetDatabaseConnection(const RelationalDBProperties &properties, + int &errCode) +{ + std::string identifier = properties.GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + LOGD("Begin to get [%s] database connection.", STR_MASK(DBCommon::TransferStringToHex(identifier))); + RelationalStoreInstance *manager = RelationalStoreInstance::GetInstance(); + if (manager == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + manager->EnterDBOpenCloseProcess(properties.GetStringProp(DBProperties::IDENTIFIER_DATA, "")); + RelationalStoreConnection *connection = nullptr; + std::string canonicalDir; + IRelationalStore *db = GetDataBase(properties, errCode); + if (db == nullptr) { + LOGE("Failed to open the db:%d", errCode); + goto END; + } + + canonicalDir = properties.GetStringProp(KvDBProperties::DATA_DIR, ""); + if (canonicalDir.empty() || canonicalDir != db->GetStorePath()) { + LOGE("Failed to check store path, the input path does not match with cached store."); + errCode = -E_INVALID_ARGS; + goto END; + } + + connection = db->GetDBConnection(errCode); + if (connection == nullptr) { // not kill db, Other operations like import may be used concurrently + LOGE("Failed to get the db connect for delegate:%d", errCode); + } + +END: + RefObject::DecObjRef(db); // restore the reference increased by the cache. + manager->ExitDBOpenCloseProcess(properties.GetStringProp(DBProperties::IDENTIFIER_DATA, "")); + return connection; +} + +void RelationalStoreInstance::EnterDBOpenCloseProcess(const std::string &identifier) +{ + std::unique_lock lock(relationalDBOpenMutex_); + relationalDBOpenCondition_.wait(lock, [this, &identifier]() { + return this->relationalDBOpenSet_.count(identifier) == 0; + }); + (void)relationalDBOpenSet_.insert(identifier); +} + +void RelationalStoreInstance::ExitDBOpenCloseProcess(const std::string &identifier) +{ + std::unique_lock lock(relationalDBOpenMutex_); + (void)relationalDBOpenSet_.erase(identifier); + relationalDBOpenCondition_.notify_all(); +} + +void RelationalStoreInstance::Dump(int fd) +{ + std::lock_guard autoLock(storeLock_); + for (const auto &entry : dbs_) { + RefObject::IncObjRef(entry.second); + entry.second->Dump(fd); + RefObject::DecObjRef(entry.second); + } +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/relational_sync_able_storage.cpp b/mock/distributeddb/storage/src/relational_sync_able_storage.cpp new file mode 100644 index 00000000..dbaf8f9b --- /dev/null +++ b/mock/distributeddb/storage/src/relational_sync_able_storage.cpp @@ -0,0 +1,618 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "relational_sync_able_storage.h" + +#include "data_compression.h" +#include "db_common.h" +#include "db_dfx_adapter.h" +#include "generic_single_ver_kv_entry.h" +#include "platform_specific.h" +#include "runtime_context.h" + +namespace DistributedDB { +#define CHECK_STORAGE_ENGINE do { \ + if (storageEngine_ == nullptr) { \ + return -E_INVALID_DB; \ + } \ +} while (0) + +RelationalSyncAbleStorage::RelationalSyncAbleStorage(StorageEngine *engine) + : storageEngine_(static_cast(engine)) +{} + +RelationalSyncAbleStorage::~RelationalSyncAbleStorage() +{} + +// Get interface type of this relational db. +int RelationalSyncAbleStorage::GetInterfaceType() const +{ + return SYNC_RELATION; +} + +// Get the interface ref-count, in order to access asynchronously. +void RelationalSyncAbleStorage::IncRefCount() +{ + LOGD("RelationalSyncAbleStorage ref +1"); + IncObjRef(this); +} + +// Drop the interface ref-count. +void RelationalSyncAbleStorage::DecRefCount() +{ + LOGD("RelationalSyncAbleStorage ref -1"); + DecObjRef(this); +} + +// Get the identifier of this rdb. +std::vector RelationalSyncAbleStorage::GetIdentifier() const +{ + std::string identifier = storageEngine_->GetIdentifier(); + return std::vector(identifier.begin(), identifier.end()); +} + +// Get the max timestamp of all entries in database. +void RelationalSyncAbleStorage::GetMaxTimestamp(Timestamp ×tamp) const +{ + int errCode = E_OK; + auto handle = GetHandle(false, errCode, OperatePerm::NORMAL_PERM); + if (handle == nullptr) { + return; + } + timestamp = 0; + errCode = handle->GetMaxTimestamp(storageEngine_->GetSchemaRef().GetTableNames(), timestamp); + if (errCode != E_OK) { + LOGE("GetMaxTimestamp failed, errCode:%d", errCode); + } + ReleaseHandle(handle); + return; +} + +int RelationalSyncAbleStorage::GetMaxTimestamp(const std::string &tableName, Timestamp ×tamp) const +{ + int errCode = E_OK; + auto handle = GetHandle(false, errCode, OperatePerm::NORMAL_PERM); + if (handle == nullptr) { + return errCode; + } + timestamp = 0; + errCode = handle->GetMaxTimestamp({ tableName }, timestamp); + if (errCode != E_OK) { + LOGE("GetMaxTimestamp failed, errCode:%d", errCode); + } + ReleaseHandle(handle); + return errCode; +} + +SQLiteSingleVerRelationalStorageExecutor *RelationalSyncAbleStorage::GetHandle(bool isWrite, int &errCode, + OperatePerm perm) const +{ + if (storageEngine_ == nullptr) { + errCode = -E_INVALID_DB; + return nullptr; + } + return static_cast(storageEngine_->FindExecutor(isWrite, perm, + errCode)); +} + +void RelationalSyncAbleStorage::ReleaseHandle(SQLiteSingleVerRelationalStorageExecutor *&handle) const +{ + if (storageEngine_ == nullptr) { + return; + } + StorageExecutor *databaseHandle = handle; + storageEngine_->Recycle(databaseHandle); + std::function listener = nullptr; + { + std::lock_guard autoLock(heartBeatMutex_); + listener = heartBeatListener_; + } + if (listener) { + listener(); + } +} + +// Get meta data associated with the given key. +int RelationalSyncAbleStorage::GetMetaData(const Key &key, Value &value) const +{ + CHECK_STORAGE_ENGINE; + if (key.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + + int errCode = E_OK; + auto handle = GetHandle(true, errCode, OperatePerm::NORMAL_PERM); + if (handle == nullptr) { + return errCode; + } + errCode = handle->GetKvData(key, value); + ReleaseHandle(handle); + return errCode; +} + +// Put meta data as a key-value entry. +int RelationalSyncAbleStorage::PutMetaData(const Key &key, const Value &value) +{ + CHECK_STORAGE_ENGINE; + int errCode = E_OK; + auto *handle = GetHandle(true, errCode, OperatePerm::NORMAL_PERM); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->PutKvData(key, value); // meta doesn't need time. + if (errCode != E_OK) { + LOGE("Put kv data err:%d", errCode); + } + ReleaseHandle(handle); + return errCode; +} + +// Delete multiple meta data records in a transaction. +int RelationalSyncAbleStorage::DeleteMetaData(const std::vector &keys) +{ + for (const auto &key : keys) { + if (key.empty() || key.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + } + int errCode = E_OK; + auto handle = GetHandle(true, errCode, OperatePerm::NORMAL_PERM); + if (handle == nullptr) { + return errCode; + } + + handle->StartTransaction(TransactType::IMMEDIATE); + errCode = handle->DeleteMetaData(keys); + if (errCode != E_OK) { + handle->Rollback(); + LOGE("[SinStore] DeleteMetaData failed, errCode = %d", errCode); + } else { + handle->Commit(); + } + ReleaseHandle(handle); + return errCode; +} + +// Delete multiple meta data records with key prefix in a transaction. +int RelationalSyncAbleStorage::DeleteMetaDataByPrefixKey(const Key &keyPrefix) const +{ + if (keyPrefix.empty() || keyPrefix.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + + int errCode = E_OK; + auto handle = GetHandle(true, errCode, OperatePerm::NORMAL_PERM); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->DeleteMetaDataByPrefixKey(keyPrefix); + if (errCode != E_OK) { + LOGE("[SinStore] DeleteMetaData by prefix key failed, errCode = %d", errCode); + } + ReleaseHandle(handle); + return errCode; +} + +// Get all meta data keys. +int RelationalSyncAbleStorage::GetAllMetaKeys(std::vector &keys) const +{ + CHECK_STORAGE_ENGINE; + int errCode = E_OK; + auto *handle = GetHandle(true, errCode, OperatePerm::NORMAL_PERM); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->GetAllMetaKeys(keys); + ReleaseHandle(handle); + return errCode; +} + +const KvDBProperties &RelationalSyncAbleStorage::GetDbProperties() const +{ + return properties_; +} + +static int GetKvEntriesByDataItems(std::vector &entries, std::vector &dataItems) +{ + int errCode = E_OK; + for (auto &item : dataItems) { + auto entry = new (std::nothrow) GenericSingleVerKvEntry(); + if (entry == nullptr) { + errCode = -E_OUT_OF_MEMORY; + LOGE("GetKvEntries failed, errCode:%d", errCode); + SingleVerKvEntry::Release(entries); + break; + } + entry->SetEntryData(std::move(item)); + entries.push_back(entry); + } + return errCode; +} + +static size_t GetDataItemSerialSize(const DataItem &item, size_t appendLen) +{ + // timestamp and local flag: 3 * uint64_t, version(uint32_t), key, value, origin dev and the padding size. + // the size would not be very large. + static const size_t maxOrigDevLength = 40; + size_t devLength = std::max(maxOrigDevLength, item.origDev.size()); + size_t dataSize = (Parcel::GetUInt64Len() * 3 + Parcel::GetUInt32Len() + Parcel::GetVectorCharLen(item.key) + + Parcel::GetVectorCharLen(item.value) + devLength + appendLen); + return dataSize; +} + +static bool CanHoldDeletedData(const std::vector &dataItems, const DataSizeSpecInfo &dataSizeInfo, + size_t appendLen) +{ + bool reachThreshold = (dataItems.size() >= dataSizeInfo.packetSize); + for (size_t i = 0, blockSize = 0; !reachThreshold && i < dataItems.size(); i++) { + blockSize += GetDataItemSerialSize(dataItems[i], appendLen); + reachThreshold = (blockSize >= dataSizeInfo.blockSize * DBConstant::QUERY_SYNC_THRESHOLD); + } + return !reachThreshold; +} + +static void ProcessContinueTokenForQuerySync(const std::vector &dataItems, int &errCode, + SQLiteSingleVerRelationalContinueToken *&token) +{ + if (errCode != -E_UNFINISHED) { // Error happened or get data finished. Token should be cleared. + delete token; + token = nullptr; + return; + } + + if (dataItems.empty()) { + errCode = -E_INTERNAL_ERROR; + LOGE("Get data unfinished but data items is empty."); + delete token; + token = nullptr; + return; + } + token->SetNextBeginTime(dataItems.back()); +} + +/** + * Caller must ensure that parameter token is valid. + * If error happened, token will be deleted here. + */ +int RelationalSyncAbleStorage::GetSyncDataForQuerySync(std::vector &dataItems, + SQLiteSingleVerRelationalContinueToken *&token, const DataSizeSpecInfo &dataSizeInfo) const +{ + if (storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = E_OK; + auto handle = static_cast(storageEngine_->FindExecutor(false, + OperatePerm::NORMAL_PERM, errCode)); + if (handle == nullptr) { + goto ERROR; + } + + do { + errCode = handle->GetSyncDataByQuery(dataItems, + Parcel::GetAppendedLen(), + dataSizeInfo, + std::bind(&SQLiteSingleVerRelationalContinueToken::GetStatement, *token, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), + storageEngine_->GetSchemaRef().GetTable(token->GetQuery().GetTableName())); + if (errCode == -E_FINISHED) { + token->FinishGetData(); + errCode = token->IsGetAllDataFinished() ? E_OK : -E_UNFINISHED; + } + } while (errCode == -E_UNFINISHED && CanHoldDeletedData(dataItems, dataSizeInfo, Parcel::GetAppendedLen())); + +ERROR: + if (errCode != -E_UNFINISHED && errCode != E_OK) { // Error happened. + dataItems.clear(); + } + ProcessContinueTokenForQuerySync(dataItems, errCode, token); + ReleaseHandle(handle); + return errCode; +} + +// use kv struct data to sync +// Get the data which would be synced with query condition +int RelationalSyncAbleStorage::GetSyncData(QueryObject &query, const SyncTimeRange &timeRange, + const DataSizeSpecInfo &dataSizeInfo, ContinueToken &continueStmtToken, + std::vector &entries) const +{ + if (!timeRange.IsValid()) { + return -E_INVALID_ARGS; + } + query.SetSchema(storageEngine_->GetSchemaRef()); + auto token = new (std::nothrow) SQLiteSingleVerRelationalContinueToken(timeRange, query); + if (token == nullptr) { + LOGE("[SingleVerNStore] Allocate continue token failed."); + return -E_OUT_OF_MEMORY; + } + + continueStmtToken = static_cast(token); + return GetSyncDataNext(entries, continueStmtToken, dataSizeInfo); +} + +int RelationalSyncAbleStorage::GetSyncDataNext(std::vector &entries, + ContinueToken &continueStmtToken, const DataSizeSpecInfo &dataSizeInfo) const +{ + auto token = static_cast(continueStmtToken); + if (!token->CheckValid()) { + return -E_INVALID_ARGS; + } + const auto fieldInfos = storageEngine_->GetSchemaRef().GetTable(token->GetQuery().GetTableName()).GetFieldInfos(); + std::vector fieldNames; + for (const auto &fieldInfo : fieldInfos) { + fieldNames.push_back(fieldInfo.GetFieldName()); + } + token->SetFieldNames(fieldNames); + + std::vector dataItems; + int errCode = GetSyncDataForQuerySync(dataItems, token, dataSizeInfo); + if (errCode != E_OK && errCode != -E_UNFINISHED) { // The code need be sent to outside except new error happened. + continueStmtToken = static_cast(token); + return errCode; + } + + int innerCode = GetKvEntriesByDataItems(entries, dataItems); + if (innerCode != E_OK) { + errCode = innerCode; + delete token; + token = nullptr; + } + continueStmtToken = static_cast(token); + return errCode; +} + +int RelationalSyncAbleStorage::PutSyncDataWithQuery(const QueryObject &object, + const std::vector &entries, const DeviceID &deviceName) +{ + std::vector dataItems; + for (auto itemEntry : entries) { + GenericSingleVerKvEntry *entry = static_cast(itemEntry); + if (entry != nullptr) { + DataItem item; + item.origDev = entry->GetOrigDevice(); + item.flag = entry->GetFlag(); + item.timestamp = entry->GetTimestamp(); + item.writeTimestamp = entry->GetWriteTimestamp(); + entry->GetKey(item.key); + entry->GetValue(item.value); + entry->GetHashKey(item.hashKey); + dataItems.push_back(item); + } + } + + return PutSyncData(object, dataItems, deviceName); +} + +int RelationalSyncAbleStorage::SaveSyncDataItems(const QueryObject &object, std::vector &dataItems, + const std::string &deviceName) +{ + int errCode = E_OK; + LOGD("[RelationalSyncAbleStorage::SaveSyncDataItems] Get write handle."); + auto *handle = GetHandle(true, errCode, OperatePerm::NORMAL_PERM); + if (handle == nullptr) { + return errCode; + } + QueryObject query = object; + query.SetSchema(storageEngine_->GetSchemaRef()); + DBDfxAdapter::StartTraceSQL(); + errCode = handle->SaveSyncItems(query, dataItems, deviceName, + storageEngine_->GetSchemaRef().GetTable(object.GetTableName())); + DBDfxAdapter::FinishTraceSQL(); + if (errCode == E_OK) { + // dataItems size > 0 now because already check before + // all dataItems will write into db now, so need to observer notify here + // if some dataItems will not write into db in the future, observer notify here need change + TriggerObserverAction(deviceName); + } + + ReleaseHandle(handle); + return errCode; +} + +int RelationalSyncAbleStorage::PutSyncData(const QueryObject &query, std::vector &dataItems, + const std::string &deviceName) +{ + if (deviceName.length() > DBConstant::MAX_DEV_LENGTH) { + LOGW("Device length is invalid for sync put"); + return -E_INVALID_ARGS; + } + + int errCode = SaveSyncDataItems(query, dataItems, deviceName); // Currently true to check value content + if (errCode != E_OK) { + LOGE("[Relational] PutSyncData errCode:%d", errCode); + } + return errCode; +} + +int RelationalSyncAbleStorage::RemoveDeviceData(const std::string &deviceName, bool isNeedNotify) +{ + (void) deviceName; + (void) isNeedNotify; + return -E_NOT_SUPPORT; +} + +RelationalSchemaObject RelationalSyncAbleStorage::GetSchemaInfo() const +{ + return storageEngine_->GetSchemaRef(); +} + +int RelationalSyncAbleStorage::GetSecurityOption(SecurityOption &option) const +{ + return -E_NOT_SUPPORT; +} + +void RelationalSyncAbleStorage::NotifyRemotePushFinished(const std::string &deviceId) const +{ + return; +} + +// Get the timestamp when database created or imported +int RelationalSyncAbleStorage::GetDatabaseCreateTimestamp(Timestamp &outTime) const +{ + return OS::GetCurrentSysTimeInMicrosecond(outTime); +} + +// Get batch meta data associated with the given key. +int RelationalSyncAbleStorage::GetBatchMetaData(const std::vector &keys, std::vector &entries) const +{ + return -E_NOT_SUPPORT; +} + +// Put batch meta data as a key-value entry vector +int RelationalSyncAbleStorage::PutBatchMetaData(std::vector &entries) +{ + return -E_NOT_SUPPORT; +} + +std::vector RelationalSyncAbleStorage::GetTablesQuery() +{ + return {}; +} + +int RelationalSyncAbleStorage::LocalDataChanged(int notifyEvent, std::vector &queryObj) +{ + (void) queryObj; + return -E_NOT_SUPPORT; +} + +int RelationalSyncAbleStorage::CreateDistributedDeviceTable(const std::string &device, + const RelationalSyncStrategy &syncStrategy) +{ + int errCode = E_OK; + auto *handle = GetHandle(true, errCode, OperatePerm::NORMAL_PERM); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + LOGE("Start transaction failed:%d", errCode); + ReleaseHandle(handle); + return errCode; + } + + for (const auto &[table, strategy] : syncStrategy) { + if (!strategy.permitSync) { + continue; + } + + errCode = handle->CreateDistributedDeviceTable(device, storageEngine_->GetSchemaRef().GetTable(table)); + if (errCode != E_OK) { + LOGE("Create distributed device table failed. %d", errCode); + break; + } + } + + if (errCode == E_OK) { + errCode = handle->Commit(); + } else { + (void)handle->Rollback(); + } + + ReleaseHandle(handle); + return errCode; +} + +int RelationalSyncAbleStorage::RegisterSchemaChangedCallback(const std::function &callback) +{ + std::lock_guard lock(onSchemaChangedMutex_); + onSchemaChanged_ = callback; + return E_OK; +} + +void RelationalSyncAbleStorage::NotifySchemaChanged() +{ + std::lock_guard lock(onSchemaChangedMutex_); + if (onSchemaChanged_) { + LOGD("Notify relational schema was changed"); + onSchemaChanged_(); + } +} +int RelationalSyncAbleStorage::GetCompressionAlgo(std::set &algorithmSet) const +{ + algorithmSet.clear(); + DataCompression::GetCompressionAlgo(algorithmSet); + return E_OK; +} + +void RelationalSyncAbleStorage::RegisterObserverAction(const RelationalObserverAction &action) +{ + std::lock_guard lock(dataChangeDeviceMutex_); + dataChangeDeviceCallback_ = action; +} + +void RelationalSyncAbleStorage::TriggerObserverAction(const std::string &deviceName) +{ + { + std::lock_guard lock(dataChangeDeviceMutex_); + if (!dataChangeDeviceCallback_) { + return; + } + } + IncObjRef(this); + int taskErrCode = RuntimeContext::GetInstance()->ScheduleTask([this, deviceName] { + std::lock_guard lock(dataChangeDeviceMutex_); + if (dataChangeDeviceCallback_) { + dataChangeDeviceCallback_(deviceName); + } + DecObjRef(this); + }); + if (taskErrCode != E_OK) { + LOGE("TriggerObserverAction scheduletask retCode=%d", taskErrCode); + DecObjRef(this); + } +} + +void RelationalSyncAbleStorage::RegisterHeartBeatListener(const std::function &listener) +{ + std::lock_guard autoLock(heartBeatMutex_); + heartBeatListener_ = listener; +} + +int RelationalSyncAbleStorage::CheckAndInitQueryCondition(QueryObject &query) const +{ + RelationalSchemaObject schema = storageEngine_->GetSchemaRef(); + TableInfo table = schema.GetTable(query.GetTableName()); + if (table.GetTableName() != query.GetTableName()) { + LOGE("Query table is not a distributed table."); + return -E_DISTRIBUTED_SCHEMA_NOT_FOUND; + } + query.SetSchema(schema); + + int errCode = E_OK; + auto *handle = GetHandle(false, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->CheckQueryObjectLegal(table, query); + if (errCode != E_OK) { + LOGE("Check relational query condition failed. %d", errCode); + } + + ReleaseHandle(handle); + return errCode; +} + +bool RelationalSyncAbleStorage::CheckCompatible(const std::string &schema, uint8_t type) const +{ + // return true if is relational schema. + return !schema.empty() && ReadSchemaType(type) == SchemaType::RELATIVE; +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/relationaldb_properties.cpp b/mock/distributeddb/storage/src/relationaldb_properties.cpp new file mode 100644 index 00000000..52d34820 --- /dev/null +++ b/mock/distributeddb/storage/src/relationaldb_properties.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "relationaldb_properties.h" + +namespace DistributedDB { +RelationalDBProperties::RelationalDBProperties() +{} + +RelationalDBProperties::~RelationalDBProperties() +{} + +bool RelationalDBProperties::IsSchemaExist() const +{ + return schema_.IsSchemaValid(); +} + +void RelationalDBProperties::SetSchema(const RelationalSchemaObject &schema) +{ + schema_ = schema; +} + +RelationalSchemaObject RelationalDBProperties::GetSchema() const +{ + return schema_; +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/result_entries_window.cpp b/mock/distributeddb/storage/src/result_entries_window.cpp new file mode 100644 index 00000000..775ef833 --- /dev/null +++ b/mock/distributeddb/storage/src/result_entries_window.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "result_entries_window.h" + +#include "db_constant.h" +#include "db_errno.h" + +using std::vector; +using std::move; + +namespace DistributedDB { +ResultEntriesWindow::ResultEntriesWindow() + : rawCursor_(nullptr), + windowSize_(0), + totalCount_(0), + begin_(0), + currentPosition_(0) {} + +ResultEntriesWindow::~ResultEntriesWindow() +{ + if (rawCursor_ != nullptr) { + (void)(rawCursor_->Close()); + rawCursor_ = nullptr; + } +} + +int ResultEntriesWindow::Init(IKvDBRawCursor *rawCursor, int64_t windowSize, double scale) +{ + if (rawCursor == nullptr || + (windowSize <= 0 || windowSize > MAX_WINDOW_SIZE) || + (scale <= std::numeric_limits::epsilon() || scale > 1)) { + return -E_INVALID_ARGS; + } + int errCode = rawCursor->Open(); + if (errCode != E_OK) { + return errCode; + } + + rawCursor_ = rawCursor; + windowSize_ = static_cast(static_cast(windowSize) * scale); + totalCount_ = rawCursor_->GetCount(); + return E_OK; +} + +int ResultEntriesWindow::GetTotalCount() const +{ + return totalCount_; +} + +int ResultEntriesWindow::GetCurrentPosition() const +{ + return currentPosition_; +} + +bool ResultEntriesWindow::MoveToPosition(int position) +{ + if ((rawCursor_ == nullptr && buffer_.size() == 0) || (position < 0 || position >= totalCount_)) { + return false; + } + if (buffer_.size() == 0) { + if (SetCursor(0, position) != E_OK) { + return false; + } + return true; + } + int last = begin_ + buffer_.size() - 1; + if (position > last) { + buffer_.clear(); + buffer_.shrink_to_fit(); + if (SetCursor(last + 1, position) != E_OK) { + return false; + } + return true; + } else if (position < begin_) { + if (rawCursor_ == nullptr) { + return false; + } + buffer_.clear(); + buffer_.shrink_to_fit(); + if (rawCursor_->Reload() != E_OK) { + ResetWindow(); + return false; + } + if (SetCursor(0, position) != E_OK) { + return false; + } + return true; + } else { + currentPosition_ = position; + } + return true; +} + +int ResultEntriesWindow::GetEntry(Entry &entry) const +{ + if (rawCursor_ == nullptr && buffer_.size() == 0) { + return -E_NOT_INIT; + } + if (totalCount_ == 0) { + return -E_NOT_FOUND; + } + if (buffer_.size() == 0) { + int errCode = LoadData(0, currentPosition_); + if (errCode != E_OK) { + return errCode; + } + } + entry = buffer_[currentPosition_ - begin_]; + return E_OK; +} + +void ResultEntriesWindow::ResetWindow() +{ + buffer_.clear(); + buffer_.shrink_to_fit(); + if (rawCursor_ != nullptr) { + (void)(rawCursor_->Reload()); + } + begin_ = 0; + currentPosition_ = 0; + return; +} + +int ResultEntriesWindow::SetCursor(int begin, int target) +{ + int errCode = LoadData(begin, target); + if (errCode == E_OK) { + begin_ = target; + currentPosition_ = target; + } else { + ResetWindow(); + } + return errCode; +} + +int ResultEntriesWindow::LoadData(int begin, int target) const +{ + if (rawCursor_ == nullptr) { + return -E_NOT_INIT; + } + if (target < begin || target >= totalCount_) { + return -E_INVALID_ARGS; + } + int cursor = begin; + int errCode = E_OK; + for (; cursor < target; cursor++) { + Entry next; + errCode = rawCursor_->GetNext(next, false); + if (errCode != E_OK) { + return errCode; + } + } + int64_t bufferSize = 0; + for (; cursor < totalCount_ && bufferSize < windowSize_; cursor++) { + Entry next; + errCode = rawCursor_->GetNext(next, true); + if (errCode != E_OK) { + return errCode; + } + // filter the abnormal data. + if (next.key.size() > DBConstant::MAX_KEY_SIZE || next.value.size() > DBConstant::MAX_VALUE_SIZE) { + continue; + } + bufferSize += next.key.size() + next.value.size(); + buffer_.push_back(move(next)); + } + if (buffer_.size() == static_cast(totalCount_)) { + (void)(rawCursor_->Close()); + rawCursor_ = nullptr; + } + return E_OK; +} +} diff --git a/mock/distributeddb/storage/src/result_entries_window.h b/mock/distributeddb/storage/src/result_entries_window.h new file mode 100644 index 00000000..7b0fe270 --- /dev/null +++ b/mock/distributeddb/storage/src/result_entries_window.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RESULT_ENTRIES_WINDOW_H +#define RESULT_ENTRIES_WINDOW_H + +#include "macro_utils.h" +#include "ikvdb_raw_cursor.h" + +namespace DistributedDB { +class ResultEntriesWindow { +public: + ResultEntriesWindow(); + ~ResultEntriesWindow(); + int Init(IKvDBRawCursor *rawCursor, int64_t windowSize, double scale); + DISABLE_COPY_ASSIGN_MOVE(ResultEntriesWindow); + int GetTotalCount() const; + int GetCurrentPosition() const; + bool MoveToPosition(int position); + int GetEntry(Entry &entry) const; + +private: + void ResetWindow(); + int SetCursor(int begin, int target); + int LoadData(int begin, int target) const; + +private: + static const int64_t MAX_WINDOW_SIZE = 0xFFFFFFFFL; // 4G - 1 + mutable IKvDBRawCursor *rawCursor_; + int64_t windowSize_; + int totalCount_; + mutable std::vector buffer_; + int begin_; + int currentPosition_; +}; +} // namespace DistributedDB + +#endif // RESULT_ENTRIES_WINDOW_H diff --git a/mock/distributeddb/storage/src/single_ver_natural_store_commit_notify_data.cpp b/mock/distributeddb/storage/src/single_ver_natural_store_commit_notify_data.cpp new file mode 100644 index 00000000..56d61937 --- /dev/null +++ b/mock/distributeddb/storage/src/single_ver_natural_store_commit_notify_data.cpp @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_natural_store_commit_notify_data.h" +#include "db_errno.h" +#include "log_print.h" +#include "db_common.h" + +namespace DistributedDB { +SingleVerNaturalStoreCommitNotifyData::SingleVerNaturalStoreCommitNotifyData() : conflictedFlag_(0) {} + +const std::list SingleVerNaturalStoreCommitNotifyData::GetInsertedEntries(int &errCode) const +{ + return FilterEntriesByKey(insertedEntries_, keyFilter_, errCode); +} + +const std::list SingleVerNaturalStoreCommitNotifyData::GetUpdatedEntries(int &errCode) const +{ + return FilterEntriesByKey(updatedEntries_, keyFilter_, errCode); +} + +const std::list SingleVerNaturalStoreCommitNotifyData::GetDeletedEntries(int &errCode) const +{ + return FilterEntriesByKey(deletedEntries_, keyFilter_, errCode); +} + +const std::list SingleVerNaturalStoreCommitNotifyData::GetCommitConflicts(int &errCode) const +{ + errCode = E_OK; + return conflictedEntries_; +} + +void SingleVerNaturalStoreCommitNotifyData::SetFilterKey(const Key &key) +{ + keyFilter_ = key; + return; +} + +bool SingleVerNaturalStoreCommitNotifyData::IsChangedDataEmpty() const +{ + int errCode; + return (!IsCleared() && GetInsertedEntries(errCode).empty() && GetUpdatedEntries(errCode).empty() && + GetDeletedEntries(errCode).empty()); +} + +bool SingleVerNaturalStoreCommitNotifyData::IsConflictedDataEmpty() const +{ + return conflictedEntries_.empty(); +} + +int SingleVerNaturalStoreCommitNotifyData::InsertCommittedData(const Entry &entry, DataType dataType, bool needMerge) +{ + if (!needMerge) { + return InsertEntry(dataType, entry); + } + + Key hashKey; + DBCommon::CalcValueHash(entry.key, hashKey); + // conclude the operation type + if (!IsKeyPropSet(hashKey)) { + return E_OK; + } + DataType type = DataType::NONE; + if (keyPropRecord_[hashKey].existStatus == ExistStatus::EXIST) { + if (dataType == DataType::INSERT || dataType == DataType::UPDATE) { + type = DataType::UPDATE; + } else if (dataType == DataType::DELETE) { + type = DataType::DELETE; + } + } else { + if (dataType == DataType::INSERT || dataType == DataType::UPDATE) { + type = DataType::INSERT; + } else if (dataType == DataType::DELETE) { + type = DataType::NONE; + } + } + + // clear the old data + DeleteEntryByKey(entry.key, keyPropRecord_[hashKey].latestType); + + // update the latest operation type value + keyPropRecord_[hashKey].latestType = type; + + return InsertEntry(type, entry); +} + +int SingleVerNaturalStoreCommitNotifyData::InsertEntry(DataType dataType, const Entry &entry) +{ + if (dataType == DataType::INSERT) { + insertedEntries_.push_back(entry); + } else if (dataType == DataType::UPDATE) { + updatedEntries_.push_back(entry); + } else if (dataType == DataType::DELETE) { + deletedEntries_.push_back(entry); + } + return E_OK; +} + +int SingleVerNaturalStoreCommitNotifyData::InsertConflictedItem(const DataItemInfo &itemInfo, bool isOriginal) +{ + Key hashKey; + DBCommon::CalcValueHash(itemInfo.dataItem.key, hashKey); + if (!IsKeyPropSet(hashKey)) { + LOGE("key property not set."); + return E_OK; + } + // key not exist in db + if (keyPropRecord_[hashKey].existStatus == ExistStatus::NONE) { + return E_OK; + } + + auto iter = orgDataItem_.find(itemInfo.dataItem.key); + if (iter == orgDataItem_.end()) { + if (isOriginal) { + orgDataItem_[itemInfo.dataItem.key] = itemInfo; + } + return E_OK; + } + if (!isOriginal) { + PutIntoConflictData(iter->second, itemInfo); + } + + return E_OK; +} + +const std::list SingleVerNaturalStoreCommitNotifyData::FilterEntriesByKey( + const std::list &entries, const Key &filterKey, int &errCode) +{ + errCode = E_OK; + if (filterKey.size() == 0) { + return entries; + } + std::list filterEntries; + for (const auto &entry : entries) { + if (entry.key == filterKey) { + filterEntries.push_back(entry); + } + } + return filterEntries; +} + +void SingleVerNaturalStoreCommitNotifyData::DeleteEntry(const Key &key, std::list &entries) const +{ + if (entries.empty()) { + return; + } + entries.remove_if([&key](const Entry &entry) { + return entry.key == key; + }); +} + +void SingleVerNaturalStoreCommitNotifyData::DeleteEntryByKey(const Key &key, DataType type) +{ + if (type == DataType::INSERT) { + DeleteEntry(key, insertedEntries_); + } + + if (type == DataType::UPDATE) { + DeleteEntry(key, updatedEntries_); + } + + if (type == DataType::DELETE) { + DeleteEntry(key, deletedEntries_); + } +} + +void SingleVerNaturalStoreCommitNotifyData::InitKeyPropRecord(const Key &key, ExistStatus status) +{ + // check if key status set before, we can only set key status at the first time + if (IsKeyPropSet(key)) { + return; + } + + keyPropRecord_[key].existStatus = status; +} + +void SingleVerNaturalStoreCommitNotifyData::SetConflictedNotifiedFlag(int conflictedFlag) +{ + conflictedFlag_ = conflictedFlag; +} + +int SingleVerNaturalStoreCommitNotifyData::GetConflictedNotifiedFlag() const +{ + return conflictedFlag_; +} + +bool SingleVerNaturalStoreCommitNotifyData::IsConflictedNotifyMatched(const DataItem &itemPut, + const DataItem &itemGet) const +{ + int dataConflictedType = 0; + // Local put + if ((itemPut.flag & DataItem::LOCAL_FLAG) != 0) { + dataConflictedType = SINGLE_VER_CONFLICT_NATIVE_ALL; + } else { + // Compare the origin device of the get and put item. + if (itemPut.origDev != itemGet.origDev) { + dataConflictedType = SINGLE_VER_CONFLICT_FOREIGN_KEY_ORIG; + } else { + dataConflictedType = SINGLE_VER_CONFLICT_FOREIGN_KEY_ONLY; + } + } + + int conflictedFlag = GetConflictedNotifiedFlag(); + LOGD("flag bind kvdb is %d, current data conflicted flag is %d", conflictedFlag, dataConflictedType); + return (static_cast(conflictedFlag) & static_cast(dataConflictedType)) != 0; +} + +void SingleVerNaturalStoreCommitNotifyData::PutIntoConflictData(const DataItemInfo &orgItemInfo, + const DataItemInfo &newItemInfo) +{ + if (orgItemInfo.dataItem.value == newItemInfo.dataItem.value && + orgItemInfo.dataItem.origDev == newItemInfo.dataItem.origDev && + orgItemInfo.dataItem.flag == newItemInfo.dataItem.flag && + orgItemInfo.deviceName == newItemInfo.deviceName) { + LOGW("same data no need to put."); + return; + } + + KvDBConflictEntry conflictData; + // Local put + if (newItemInfo.isLocal) { + conflictData.type = SingleVerNaturalStoreCommitNotifyData::SINGLE_VER_CONFLICT_NATIVE_ALL; + } else { + // Compare the origin device of the get and put item. + conflictData.type = ((newItemInfo.dataItem.origDev != orgItemInfo.dataItem.origDev) ? + SingleVerNaturalStoreCommitNotifyData::SINGLE_VER_CONFLICT_FOREIGN_KEY_ORIG : + SingleVerNaturalStoreCommitNotifyData::SINGLE_VER_CONFLICT_FOREIGN_KEY_ONLY); + } + + bool isDeleted = ((orgItemInfo.dataItem.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG); + conflictData.oldData = {orgItemInfo.dataItem.value, isDeleted, true}; + + isDeleted = ((newItemInfo.dataItem.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG); + conflictData.newData = {newItemInfo.dataItem.value, isDeleted, newItemInfo.isLocal}; + + // If the new item is deleted, just using the key of the old data item. + // If the items are all deleted, this function should not be executed. + conflictData.key = isDeleted ? orgItemInfo.dataItem.key : newItemInfo.dataItem.key; + if (newItemInfo.dataItem.writeTimestamp <= orgItemInfo.dataItem.writeTimestamp) { + std::swap(conflictData.newData, conflictData.oldData); + } + + DeleteConflictEntry(conflictData.key); + conflictedEntries_.push_back(std::move(conflictData)); +} + +void SingleVerNaturalStoreCommitNotifyData::DeleteConflictEntry(const Key &key) +{ + if (conflictedEntries_.empty()) { + return; + } + auto iter = conflictedEntries_.begin(); + for (; iter != conflictedEntries_.end(); ++iter) { + if (iter->key == key) { + conflictedEntries_.erase(iter); + return; + } + } +} + +bool SingleVerNaturalStoreCommitNotifyData::IsKeyPropSet(const Key &key) const +{ + // check if key status set before + return (keyPropRecord_.find(key) != keyPropRecord_.end()); +} + +DEFINE_OBJECT_TAG_FACILITIES(SingleVerNaturalStoreCommitNotifyData) +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/single_ver_natural_store_commit_notify_data.h b/mock/distributeddb/storage/src/single_ver_natural_store_commit_notify_data.h new file mode 100644 index 00000000..be54d608 --- /dev/null +++ b/mock/distributeddb/storage/src/single_ver_natural_store_commit_notify_data.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_NATURAL_STORE_COMMIT_NOTIFY_DATA_H +#define SINGLE_VER_NATURAL_STORE_COMMIT_NOTIFY_DATA_H + +#include "kvdb_commit_notify_filterable_data.h" + +namespace DistributedDB { +enum class DataType { + NONE, + INSERT, + UPDATE, + DELETE, +}; + +enum class ExistStatus { + NONE, // key never exist in db + DELETED, // key deleted but exist before + EXIST, // key exist +}; +constexpr size_t MAX_TOTAL_NOTIFY_ITEM_SIZE = 1048576; // 1MB +constexpr size_t MAX_TOTAL_NOTIFY_DATA_SIZE = 4195328; // 4MB + 1KB + +struct DataItemInfo { + DataItem dataItem; + bool isLocal = false; + std::vector deviceName; +}; + +class SingleVerNaturalStoreCommitNotifyData final : public KvDBCommitNotifyFilterAbleData { +public: + SingleVerNaturalStoreCommitNotifyData(); + ~SingleVerNaturalStoreCommitNotifyData() {} + DISABLE_COPY_ASSIGN_MOVE(SingleVerNaturalStoreCommitNotifyData); + + const std::list GetInsertedEntries(int &errCode) const override; + + const std::list GetUpdatedEntries(int &errCode) const override; + + const std::list GetDeletedEntries(int &errCode) const override; + + const std::list GetCommitConflicts(int &errCode) const override; + + void SetFilterKey(const Key &key) override; + + bool IsChangedDataEmpty() const override; + + bool IsConflictedDataEmpty() const override; + + int InsertCommittedData(const Entry &entry, DataType dataType, bool needMerge = false); + + int InsertConflictedItem(const DataItemInfo &itemInfo, bool isOriginal = false); + + void InitKeyPropRecord(const Key &key, ExistStatus status); + + void SetConflictedNotifiedFlag(int conflictedFlag); + + int GetConflictedNotifiedFlag() const; + + bool IsConflictedNotifyMatched(const DataItem &itemPut, const DataItem &itemGet) const; + +private: + + struct ItemProp { + ExistStatus existStatus = ExistStatus::NONE; // indicator if the key exist in db before this transaction + DataType latestType = DataType::NONE; // indicator the latest operation type for this key + }; + + int InsertEntry(DataType dataType, const Entry &entry); + + static const std::list FilterEntriesByKey(const std::list &entries, + const Key &filterKey, int &errCode); + + void DeleteEntry(const Key &key, std::list &entries) const; + + void DeleteEntryByKey(const Key &key, DataType type); + + void PutIntoConflictData(const DataItemInfo &orgItemInfo, const DataItemInfo &newItemInfo); + + void DeleteConflictEntry(const Key &key); + + bool IsKeyPropSet(const Key &key) const; + + DECLARE_OBJECT_TAG(SingleVerNaturalStoreCommitNotifyData); + + static const int SINGLE_VER_CONFLICT_FOREIGN_KEY_ONLY = 0x01; // sync conflict for same origin dev + static const int SINGLE_VER_CONFLICT_FOREIGN_KEY_ORIG = 0x02; // sync conflict for different origin dev + static const int SINGLE_VER_CONFLICT_NATIVE_ALL = 0x0c; // native conflict. + + std::list insertedEntries_; + std::list updatedEntries_; + std::list deletedEntries_; + std::list conflictedEntries_; + Key keyFilter_; + std::map keyPropRecord_; // hash key mapping to item property + std::map orgDataItem_; + int conflictedFlag_; // the conflict notifier type composition, 0 means no conflict notifier. +}; +} // namespace DistributedDB + +#endif // SINGLE_VER_NATURAL_STORE_COMMIT_NOTIFY_DATA_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/query_object.cpp b/mock/distributeddb/storage/src/sqlite/query_object.cpp new file mode 100644 index 00000000..4f474755 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/query_object.cpp @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "query_object.h" +#include "db_errno.h" +#include "get_query_info.h" +#include "log_print.h" + +namespace DistributedDB { +namespace { +const int INVALID_LIMIT = INT_MAX; +const int LIMIT_FIELD_VALUE_SIZE = 2; +} + +QueryObject::QueryObject() + : isValid_(true), + initialized_(false), + limit_(INVALID_LIMIT), + offset_(0), + hasOrderBy_(false), + hasLimit_(false), + hasPrefixKey_(false), + hasInKeys_(false), + orderByCounts_(0) +{ +} + +void QueryObject::GetAttrFromQueryObjNodes() +{ + for (const auto &iter : queryObjNodes_) { + SymbolType symbolType = SqliteQueryHelper::GetSymbolType(iter.operFlag); + if (iter.operFlag == QueryObjType::LIMIT) { + hasLimit_ = true; + if (iter.fieldValue.size() == LIMIT_FIELD_VALUE_SIZE) { + limit_ = iter.fieldValue[0].integerValue; + offset_ = iter.fieldValue[1].integerValue; + } + } else if (iter.operFlag == QueryObjType::ORDERBY) { + hasOrderBy_ = true; + } else if (symbolType == PREFIXKEY_SYMBOL) { + hasPrefixKey_ = true; + } else if (symbolType == IN_KEYS_SYMBOL) { + hasInKeys_ = true; + } + } +} + +QueryObject::QueryObject(const Query &query) + : initialized_(false), + limit_(INVALID_LIMIT), + offset_(0), + hasOrderBy_(false), + hasLimit_(false), + hasPrefixKey_(false), + hasInKeys_(false), + orderByCounts_(0) +{ + QueryExpression queryExpressions = GetQueryInfo::GetQueryExpression(query); + queryObjNodes_ = queryExpressions.GetQueryExpression(); + GetAttrFromQueryObjNodes(); + isValid_ = queryExpressions.GetErrFlag(); + prefixKey_ = queryExpressions.GetPreFixKey(); + suggestIndex_ = queryExpressions.GetSuggestIndex(); + tableName_ = queryExpressions.GetTableName(); + isTableNameSpecified_ = queryExpressions.IsTableNameSpecified(); + keys_ = queryExpressions.GetKeys(); +} + +QueryObject::QueryObject(const std::list &queryObjNodes, const std::vector &prefixKey, + const std::set &keys) + : queryObjNodes_(queryObjNodes), + prefixKey_(prefixKey), + keys_(keys), + isValid_(true), + initialized_(false), + limit_(INVALID_LIMIT), + offset_(0), + hasOrderBy_(false), + hasLimit_(false), + hasPrefixKey_(false), + hasInKeys_(false), + orderByCounts_(0) +{ + GetAttrFromQueryObjNodes(); +} + +QueryObject::~QueryObject() +{} + +int QueryObject::Init() +{ + if (initialized_) { + return E_OK; + } + + int errCode = Parse(); + if (errCode != E_OK) { + LOGE("Parse query object err[%d]!", errCode); + return errCode; + } + + initialized_ = true; + return errCode; +} + +SqliteQueryHelper QueryObject::GetQueryHelper(int &errCode) +{ + errCode = Init(); + if (errCode != E_OK) { + return SqliteQueryHelper(QueryObjInfo{}); + } + QueryObjInfo info {schema_, queryObjNodes_, prefixKey_, suggestIndex_, keys_, + orderByCounts_, isValid_, hasOrderBy_, hasLimit_, hasPrefixKey_, tableName_, isTableNameSpecified_}; + return SqliteQueryHelper {info}; // compiler RVO by default, and RVO is generally required after C++17 +} + +bool QueryObject::IsValid() +{ + if (!initialized_) { + (void)Init(); + } + return isValid_; +} + +bool QueryObject::HasLimit() const +{ + return hasLimit_; +} + +void QueryObject::GetLimitVal(int &limit, int &offset) const +{ + limit = limit_; + offset = offset_; +} + +void QueryObject::SetSchema(const SchemaObject &schema) +{ + schema_ = schema; +} + +bool QueryObject::IsCountValid() const +{ + if (hasLimit_ || hasOrderBy_) { + LOGI("It is invalid for limit and orderby!"); + return false; + } + return true; +} + +const std::vector &QueryObject::GetPrefixKey() const +{ + return prefixKey_; +} + +void QueryObject::ClearNodesFlag() +{ + limit_ = INVALID_LIMIT; + offset_ = 0; + isValid_ = true; + hasOrderBy_ = false; + hasLimit_ = false; + hasPrefixKey_ = false; + hasInKeys_ = false; + orderByCounts_ = 0; +} + +int QueryObject::Parse() +{ + if (!isValid_) { + LOGE("Invalid query object!"); + return -E_INVALID_QUERY_FORMAT; + } + int errCode = ParseQueryObjNodes(); + if (errCode != E_OK) { + LOGE("Check query object illegal!"); + isValid_ = false; + } + return errCode; +} + +int QueryObject::ParseQueryObjNodes() +{ + ClearNodesFlag(); + + auto iter = queryObjNodes_.begin(); + int errCode = E_OK; + while (iter != queryObjNodes_.end()) { + errCode = ParseNode(iter); + if (errCode != E_OK) { + return errCode; + } + iter++; + } + return errCode; +} + +int QueryObject::ParseNode(const std::list::iterator &iter) +{ + // The object is newly instantiated in the connection, and there is no reentrancy problem. + if (!iter->IsValid()) { + return -E_INVALID_QUERY_FORMAT; + } + + switch (SqliteQueryHelper::GetSymbolType(iter->operFlag)) { + case COMPARE_SYMBOL: + case RELATIONAL_SYMBOL: + case RANGE_SYMBOL: + return CheckEqualFormat(iter); + case LINK_SYMBOL: + return CheckLinkerFormat(iter); + case PREFIXKEY_SYMBOL: { + if (hasPrefixKey_) { + LOGE("Only filter by prefix key once!!"); + return -E_INVALID_QUERY_FORMAT; + } + hasPrefixKey_ = true; + if (prefixKey_.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + return E_OK; + } + case SUGGEST_INDEX_SYMBOL: + return CheckSuggestIndexFormat(iter); + case IN_KEYS_SYMBOL: { + if (hasInKeys_) { + LOGE("Only filter by keys in once!!"); + return -E_INVALID_QUERY_FORMAT; + } + int errCode = CheckInKeys(); + if (errCode != E_OK) { + return errCode; + } + hasInKeys_ = true; + return E_OK; + } + default: + return ParseNodeByOperFlag(iter); + } + return E_OK; +} + +int QueryObject::ParseNodeByOperFlag(const std::list::iterator &iter) +{ + switch (iter->operFlag) { + case QueryObjType::LIMIT: + hasLimit_ = true; + if (iter->fieldValue.size() == LIMIT_FIELD_VALUE_SIZE) { + limit_ = iter->fieldValue[0].integerValue; + offset_ = iter->fieldValue[1].integerValue; + } + return CheckLimitFormat(iter); + case QueryObjType::ORDERBY: + return CheckOrderByFormat(iter); + default: + return E_OK; + } + return E_OK; +} + +int QueryObject::CheckLinkerBefore(const std::list::iterator &iter) const +{ + auto preIter = std::prev(iter, 1); + SymbolType symbolType = SqliteQueryHelper::GetSymbolType(preIter->operFlag); + if (symbolType != COMPARE_SYMBOL && symbolType != RELATIONAL_SYMBOL && symbolType != LOGIC_SYMBOL && + symbolType != RANGE_SYMBOL && symbolType != PREFIXKEY_SYMBOL && symbolType != IN_KEYS_SYMBOL) { + LOGE("Must be a comparison operation before the connective! operFlag = %s", VNAME(preIter->operFlag)); + return -E_INVALID_QUERY_FORMAT; + } + return E_OK; +} + +bool QueryObject::IsRelationalQuery() const +{ + return isTableNameSpecified_; +} + +int QueryObject::CheckEqualFormat(const std::list::iterator &iter) const +{ + if (!schema_.IsSchemaValid()) { + LOGE("Schema is invalid!"); + return -E_NOT_SUPPORT; + } + + FieldPath fieldPath; + int errCode = SchemaUtils::ParseAndCheckFieldPath(iter->fieldName, fieldPath); + if (errCode != E_OK) { + return -E_INVALID_QUERY_FIELD; + } + + FieldType schemaFieldType = FieldType::LEAF_FIELD_BOOL; + errCode = schema_.CheckQueryableAndGetFieldType(fieldPath, schemaFieldType); + if (errCode != E_OK) { + LOGE("Get field type fail when check compare format! errCode = %d, fieldType = %u", + errCode, static_cast(schemaFieldType)); + return -E_INVALID_QUERY_FIELD; + } + + if (schemaFieldType == FieldType::LEAF_FIELD_BOOL && + SqliteQueryHelper::GetSymbolType(iter->operFlag) == COMPARE_SYMBOL && + iter->operFlag != QueryObjType::EQUALTO && iter->operFlag != QueryObjType::NOT_EQUALTO) { // bool can == or != + LOGE("Bool forbid compare!!!"); + return -E_INVALID_QUERY_FORMAT; + } + auto nextIter = std::next(iter, 1); + if (nextIter != queryObjNodes_.end()) { + SymbolType symbolType = SqliteQueryHelper::GetSymbolType(nextIter->operFlag); + if (symbolType == RELATIONAL_SYMBOL || symbolType == COMPARE_SYMBOL || symbolType == RANGE_SYMBOL) { + LOGE("After Compare you need, You need the conjunction like and or for connecting!"); + return -E_INVALID_QUERY_FORMAT; + } + } + return E_OK; +} + +int QueryObject::CheckLinkerFormat(const std::list::iterator &iter) const +{ + auto itPre = iter; + for (; itPre != queryObjNodes_.begin(); itPre = std::prev(itPre, 1)) { + SymbolType symbolType = SqliteQueryHelper::GetSymbolType(std::prev(itPre, 1)->operFlag); + if (symbolType != PREFIXKEY_SYMBOL && symbolType != IN_KEYS_SYMBOL) { + break; + } + } + if (itPre == queryObjNodes_.begin()) { + LOGE("Connectives are not allowed in the first place!"); + return -E_INVALID_QUERY_FORMAT; + } + auto nextIter = std::next(iter, 1); + if (nextIter == queryObjNodes_.end()) { + LOGE("Connectives are not allowed in the last place!"); + return -E_INVALID_QUERY_FORMAT; + } + SymbolType symbolType = SqliteQueryHelper::GetSymbolType(nextIter->operFlag); + if (symbolType == INVALID_SYMBOL || symbolType == LINK_SYMBOL || symbolType == SPECIAL_SYMBOL) { + LOGE("Must be followed by comparison operation! operflag[%u], symbolType[%u]", + static_cast(nextIter->operFlag), static_cast(symbolType)); + return -E_INVALID_QUERY_FORMAT; + } + return CheckLinkerBefore(iter); +} + +int QueryObject::CheckSuggestIndexFormat(const std::list::iterator &iter) const +{ + auto next = std::next(iter, 1); + if (next != queryObjNodes_.end()) { + LOGE("SuggestIndex only allowed once, and must appear at the end!"); + return -E_INVALID_QUERY_FORMAT; + } + return E_OK; +} + +int QueryObject::CheckOrderByFormat(const std::list::iterator &iter) +{ + if (!schema_.IsSchemaValid()) { + return -E_NOT_SUPPORT; + } + + FieldType schemaFieldType; + FieldPath fieldPath; + + int errCode = SchemaUtils::ParseAndCheckFieldPath(iter->fieldName, fieldPath); + if (errCode != E_OK) { + return -E_INVALID_QUERY_FIELD; + } + errCode = schema_.CheckQueryableAndGetFieldType(fieldPath, schemaFieldType); + if (errCode != E_OK) { + return -E_INVALID_QUERY_FIELD; + } + if (schemaFieldType == FieldType::LEAF_FIELD_BOOL) { + return -E_INVALID_QUERY_FORMAT; + } + hasOrderBy_ = true; + ++orderByCounts_; + LOGD("Need order by %d filed value!", orderByCounts_); + return E_OK; +} + +int QueryObject::CheckLimitFormat(const std::list::iterator &iter) const +{ + auto next = std::next(iter, 1); + if (next != queryObjNodes_.end() && SqliteQueryHelper::GetSymbolType(next->operFlag) != SUGGEST_INDEX_SYMBOL) { + LOGE("Limit should be last node or just before suggest-index node!"); + return -E_INVALID_QUERY_FORMAT; + } + return E_OK; +} + +bool QueryObject::IsQueryOnlyByKey() const +{ + return std::none_of(queryObjNodes_.begin(), queryObjNodes_.end(), [&](const QueryObjNode &node) { + return node.operFlag != QueryObjType::LIMIT && node.operFlag != QueryObjType::QUERY_BY_KEY_PREFIX && + node.operFlag != QueryObjType::IN_KEYS; + }); +} + +bool QueryObject::IsQueryForRelationalDB() const +{ + return isTableNameSpecified_ && + std::none_of(queryObjNodes_.begin(), queryObjNodes_.end(), [&](const QueryObjNode &node) { + return node.operFlag != QueryObjType::EQUALTO && node.operFlag != QueryObjType::NOT_EQUALTO && + node.operFlag != QueryObjType::AND && node.operFlag != QueryObjType::OR && + node.operFlag != QueryObjType::ORDERBY && node.operFlag != QueryObjType::LIMIT; + }); +} + +bool QueryObject::HasOrderBy() const +{ + return hasOrderBy_; +} + +bool QueryObject::Empty() const +{ + return queryObjNodes_.empty(); +} + +int QueryObject::CheckInKeys() const +{ + if (keys_.empty()) { + LOGE("Inkeys cannot be empty."); + return -E_INVALID_ARGS; + } + if (keys_.size() > DBConstant::MAX_INKEYS_SIZE) { + LOGE("Inkeys cannot be over 128."); + return -E_MAX_LIMITS; + } + for (const auto &key : keys_) { + if (key.empty() || key.size() > DBConstant::MAX_KEY_SIZE) { + LOGE("The key in Inkeys cannot be empty or overlong, size:%zu.", key.size()); + return -E_INVALID_ARGS; + } + } + return E_OK; +} + +#ifdef RELATIONAL_STORE +int QueryObject::SetSchema(const RelationalSchemaObject &schemaObj) +{ + if (!isTableNameSpecified_) { + return -E_INVALID_ARGS; + } + const auto &tableInfo = schemaObj.GetTable(tableName_); + SchemaObject schema(tableInfo); + schema_ = schema; + return E_OK; +} +#endif +} + diff --git a/mock/distributeddb/storage/src/sqlite/query_object.h b/mock/distributeddb/storage/src/sqlite/query_object.h new file mode 100644 index 00000000..84d2836e --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/query_object.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 QUERY_OBJECT_H +#define QUERY_OBJECT_H + +#include + +#include "query.h" +#include "relational_schema_object.h" +#include "schema_object.h" +#include "sqlite_query_helper.h" + +namespace DistributedDB { +class QueryObject { +public: + QueryObject(); + explicit QueryObject(const Query &query); + // for query sync + QueryObject(const std::list &queryObjNodes, const std::vector &prefixKey, + const std::set &keys); + virtual ~QueryObject(); + int Init(); + SqliteQueryHelper GetQueryHelper(int &errCode); + + // suggest: get those attributes after init or GetQueryHelper to parsed query + bool IsValid(); + bool HasLimit() const; + void GetLimitVal(int &limit, int &offset) const; + bool IsCountValid() const; + + const std::vector &GetPrefixKey() const; + void SetSchema(const SchemaObject &schema); + + bool IsQueryOnlyByKey() const; + bool IsQueryForRelationalDB() const; + + void SetTableName(const std::string &tableName) + { + tableName_ = tableName; + isTableNameSpecified_ = true; + } + + const std::string &GetTableName() const + { + return tableName_; + } + + bool HasOrderBy() const; + + int ParseQueryObjNodes(); + + bool Empty() const; + + bool HasInKeys() const + { + return hasInKeys_; + } + +#ifdef RELATIONAL_STORE + int SetSchema(const RelationalSchemaObject &schemaObj); // The interface can only be used in relational query. +#endif + +protected: + std::list queryObjNodes_; + std::vector prefixKey_; + std::string tableName_ = "sync_data"; + std::string suggestIndex_; + std::set keys_; + + bool isValid_ = true; + + bool initialized_ = false; // use function need after init + bool isTableNameSpecified_ = false; + +private: + int Parse(); + int ParseNode(const std::list::iterator &iter); + int ParseNodeByOperFlag(const std::list::iterator &iter); + int CheckEqualFormat(const std::list::iterator &iter) const; + int CheckLinkerFormat(const std::list::iterator &iter) const; + int CheckSuggestIndexFormat(const std::list::iterator &iter) const; + int CheckOrderByFormat(const std::list::iterator &iter); + int CheckLimitFormat(const std::list::iterator &iter) const; + int CheckLinkerBefore(const std::list::iterator &iter) const; + void ClearNodesFlag(); + void GetAttrFromQueryObjNodes(); + int CheckInKeys() const; + bool IsRelationalQuery() const; + + SchemaObject schema_; // used to check and parse schema filed + int limit_; + int offset_; + bool hasOrderBy_; + bool hasLimit_; + bool hasPrefixKey_; + bool hasInKeys_; + int orderByCounts_; +}; +} +#endif diff --git a/mock/distributeddb/storage/src/sqlite/query_sync_object.cpp b/mock/distributeddb/storage/src/sqlite/query_sync_object.cpp new file mode 100644 index 00000000..90a535be --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/query_sync_object.cpp @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "query_sync_object.h" + +#include "db_errno.h" +#include "log_print.h" +#include "db_common.h" +#include "version.h" + +namespace DistributedDB { +namespace { +const std::string MAGIC = "remote query"; + +int SerializeDataObjNode(Parcel &parcel, const QueryObjNode &objNode) +{ + if (objNode.operFlag == QueryObjType::OPER_ILLEGAL) { + return -E_INVALID_QUERY_FORMAT; + } + (void)parcel.WriteUInt32(static_cast(objNode.operFlag)); + parcel.EightByteAlign(); + (void)parcel.WriteString(objNode.fieldName); + (void)parcel.WriteInt(static_cast(objNode.type)); + (void)parcel.WriteUInt32(objNode.fieldValue.size()); + + for (const FieldValue &value : objNode.fieldValue) { + (void)parcel.WriteString(value.stringValue); + + // string may not closely arranged continuously + // longValue is maximum length in union + (void)parcel.WriteInt64(value.longValue); + } + if (parcel.IsError()) { + return -E_INVALID_ARGS; + } + return E_OK; +} + +int DeSerializeDataObjNode(Parcel &parcel, QueryObjNode &objNode) +{ + uint32_t readOperFlag = 0; + (void)parcel.ReadUInt32(readOperFlag); + objNode.operFlag = static_cast(readOperFlag); + parcel.EightByteAlign(); + + (void)parcel.ReadString(objNode.fieldName); + + int readInt = -1; + (void)parcel.ReadInt(readInt); + objNode.type = static_cast(readInt); + + uint32_t valueSize = 0; + (void)parcel.ReadUInt32(valueSize); + if (parcel.IsError()) { + return -E_INVALID_ARGS; + } + + for (size_t i = 0; i < valueSize; i++) { + FieldValue value; + (void)parcel.ReadString(value.stringValue); + + (void)parcel.ReadInt64(value.longValue); + if (parcel.IsError()) { + return -E_INVALID_ARGS; + } + objNode.fieldValue.push_back(value); + } + return E_OK; +} +} + +QuerySyncObject::QuerySyncObject() +{} + +QuerySyncObject::QuerySyncObject(const std::list &queryObjNodes, const std::vector &prefixKey, + const std::set &keys) + : QueryObject(queryObjNodes, prefixKey, keys) +{} + +QuerySyncObject::QuerySyncObject(const Query &query) + : QueryObject(query) +{} + +QuerySyncObject::~QuerySyncObject() +{} + +uint32_t QuerySyncObject::GetVersion() const +{ + uint32_t version = QUERY_SYNC_OBJECT_VERSION_0; + if (isTableNameSpecified_ || !keys_.empty()) { + version = QUERY_SYNC_OBJECT_VERSION_1; + } + return version; +} + +int QuerySyncObject::GetObjContext(ObjContext &objContext) const +{ + if (!isValid_) { + return -E_INVALID_QUERY_FORMAT; + } + objContext.version = GetVersion(); + objContext.prefixKey.assign(prefixKey_.begin(), prefixKey_.end()); + objContext.suggestIndex = suggestIndex_; + objContext.queryObjNodes = queryObjNodes_; + return E_OK; +} + +std::string QuerySyncObject::GetIdentify() const +{ + if (!isValid_) { + return std::string(); + } + // suggestionIndex is local attribute, do not need to be propagated to remote + uint64_t len = Parcel::GetVectorCharLen(prefixKey_); + for (const QueryObjNode &node : queryObjNodes_) { + if (node.operFlag == QueryObjType::LIMIT || node.operFlag == QueryObjType::ORDERBY || + node.operFlag == QueryObjType::SUGGEST_INDEX) { + continue; + } + // operFlag and valueType is int + len += Parcel::GetUInt32Len() + Parcel::GetIntLen() + Parcel::GetStringLen(node.fieldName); + for (const FieldValue &value : node.fieldValue) { + len += Parcel::GetStringLen(value.stringValue) + Parcel::GetInt64Len(); + } + } + + // QUERY_SYNC_OBJECT_VERSION_1 added. + len += isTableNameSpecified_ ? Parcel::GetStringLen(tableName_) : 0; + for (const auto &key : keys_) { + len += Parcel::GetVectorCharLen(key); + } // QUERY_SYNC_OBJECT_VERSION_1 end. + + std::vector buff(len, 0); // It will affect the hash result, the default value cannot be modified + Parcel parcel(buff.data(), len); + + // The order needs to be consistent, otherwise it will affect the hash result + (void)parcel.WriteVectorChar(prefixKey_); + for (const QueryObjNode &node : queryObjNodes_) { + if (node.operFlag == QueryObjType::LIMIT || node.operFlag == QueryObjType::ORDERBY || + node.operFlag == QueryObjType::SUGGEST_INDEX) { + continue; + } + (void)parcel.WriteUInt32(static_cast(node.operFlag)); + (void)parcel.WriteInt(static_cast(node.type)); + (void)parcel.WriteString(node.fieldName); + for (const FieldValue &value : node.fieldValue) { + (void)parcel.WriteInt64(value.longValue); + (void)parcel.WriteString(value.stringValue); + } + } + + // QUERY_SYNC_OBJECT_VERSION_1 added. + if (isTableNameSpecified_) { + (void)parcel.WriteString(tableName_); + } + for (const auto &key : keys_) { + (void)parcel.WriteVectorChar(key); + } // QUERY_SYNC_OBJECT_VERSION_1 end. + + std::vector hashBuff; + if (parcel.IsError() || DBCommon::CalcValueHash(buff, hashBuff) != E_OK) { + return std::string(); + } + + return DBCommon::VectorToHexString(hashBuff); +} + +uint32_t QuerySyncObject::CalculateParcelLen(uint32_t softWareVersion) const +{ + if (softWareVersion == SOFTWARE_VERSION_CURRENT) { + return CalculateLen(); + } + LOGE("current not support!"); + return 0; +} + +int QuerySyncObject::SerializeData(Parcel &parcel, uint32_t softWareVersion) +{ + ObjContext context; + int errCode = GetObjContext(context); + if (errCode != E_OK) { + return errCode; + } + + (void)parcel.WriteString(MAGIC); + (void)parcel.WriteUInt32(context.version); + (void)parcel.WriteVectorChar(context.prefixKey); + (void)parcel.WriteString(context.suggestIndex); + (void)parcel.WriteUInt32(context.queryObjNodes.size()); + + parcel.EightByteAlign(); + + for (const QueryObjNode &node : context.queryObjNodes) { + errCode = SerializeDataObjNode(parcel, node); + if (errCode != E_OK) { + return errCode; + } + } + + // QUERY_SYNC_OBJECT_VERSION_1 added. + if (context.version >= QUERY_SYNC_OBJECT_VERSION_1) { + (void)parcel.WriteUInt32(static_cast(isTableNameSpecified_)); + if (isTableNameSpecified_) { + (void)parcel.WriteString(tableName_); + } + (void)parcel.WriteUInt32(keys_.size()); + for (const auto &key : keys_) { + (void)parcel.WriteVectorChar(key); + } + } // QUERY_SYNC_OBJECT_VERSION_1 end. + + if (parcel.IsError()) { // parcel almost success + return -E_INVALID_ARGS; + } + parcel.EightByteAlign(); + return E_OK; +} + +namespace { +int DeSerializeVersion1Data(uint32_t version, Parcel &parcel, std::string &tableName, std::set &keys) +{ + if (version >= QUERY_SYNC_OBJECT_VERSION_1) { + uint32_t isTblNameExist = 0; + (void)parcel.ReadUInt32(isTblNameExist); + if (isTblNameExist) { + (void)parcel.ReadString(tableName); + } + uint32_t keysSize = 0; + (void)parcel.ReadUInt32(keysSize); + if (keysSize > DBConstant::MAX_INKEYS_SIZE) { + return -E_PARSE_FAIL; + } + for (uint32_t i = 0; i < keysSize; ++i) { + Key key; + (void)parcel.ReadVector(key); + keys.emplace(key); + } + } + return E_OK; +} +} + +int QuerySyncObject::DeSerializeData(Parcel &parcel, QuerySyncObject &queryObj) +{ + std::string magic; + (void)parcel.ReadString(magic); + if (magic != MAGIC) { + return -E_INVALID_ARGS; + } + + ObjContext context; + (void)parcel.ReadUInt32(context.version); + if (context.version > QUERY_SYNC_OBJECT_VERSION_CURRENT) { + LOGE("Parcel version and deserialize version not matched! ver=%u", context.version); + return -E_VERSION_NOT_SUPPORT; + } + + (void)parcel.ReadVectorChar(context.prefixKey); + (void)parcel.ReadString(context.suggestIndex); + + uint32_t nodesSize = 0; + (void)parcel.ReadUInt32(nodesSize); + if (parcel.IsError()) { // almost success + return -E_INVALID_ARGS; + } + parcel.EightByteAlign(); + for (size_t i = 0; i < nodesSize; i++) { + QueryObjNode node; + int errCode = DeSerializeDataObjNode(parcel, node); + if (errCode != E_OK) { + return errCode; + } + context.queryObjNodes.emplace_back(node); + } + + // QUERY_SYNC_OBJECT_VERSION_1 added. + std::string tableName; + std::set keys; + int errCode = DeSerializeVersion1Data(context.version, parcel, tableName, keys); + if (errCode != E_OK) { + return errCode; + } // QUERY_SYNC_OBJECT_VERSION_1 end. + + if (parcel.IsError()) { // almost success + return -E_INVALID_ARGS; + } + queryObj = QuerySyncObject(context.queryObjNodes, context.prefixKey, keys); + if (!tableName.empty()) { + queryObj.SetTableName(tableName); + } + return E_OK; +} + +uint32_t QuerySyncObject::CalculateLen() const +{ + uint64_t len = Parcel::GetStringLen(MAGIC); + len += Parcel::GetUInt32Len(); // version + len += Parcel::GetVectorCharLen(prefixKey_); + len += Parcel::GetStringLen(suggestIndex_); + len += Parcel::GetUInt32Len(); // nodes size + len = Parcel::GetEightByteAlign(len); + for (const QueryObjNode &node : queryObjNodes_) { + if (node.operFlag == QueryObjType::OPER_ILLEGAL) { + LOGE("contain illegal operator for query sync!"); + return 0; + } + // operflag, fieldName, query value type, value size, union max size, string value + len += Parcel::GetUInt32Len(); + len = Parcel::GetEightByteAlign(len); + len += Parcel::GetStringLen(node.fieldName) + + Parcel::GetIntLen() + Parcel::GetUInt32Len(); + for (size_t i = 0; i < node.fieldValue.size(); i++) { + len += Parcel::GetInt64Len() + Parcel::GetStringLen(node.fieldValue[i].stringValue); + } + } + + // QUERY_SYNC_OBJECT_VERSION_1 added. + len += Parcel::GetUInt32Len(); // whether the table name exists. + if (isTableNameSpecified_) { + len += Parcel::GetStringLen(tableName_); + } + len += Parcel::GetUInt32Len(); // size of keys_ + for (const auto &key : keys_) { + len += Parcel::GetVectorCharLen(key); + } // QUERY_SYNC_OBJECT_VERSION_1 end. + + len = Parcel::GetEightByteAlign(len); + if (len > INT32_MAX) { + return 0; + } + return static_cast(len); +} + +std::string QuerySyncObject::GetRelationTableName() const +{ + if (!isTableNameSpecified_) { + return {}; + } + return tableName_; +} +} \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/query_sync_object.h b/mock/distributeddb/storage/src/sqlite/query_sync_object.h new file mode 100644 index 00000000..7d8ca763 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/query_sync_object.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 QUERY_SYNC_OBJECT_H +#define QUERY_SYNC_OBJECT_H + +#include + +#include "query_object.h" +#include "parcel.h" + +namespace DistributedDB { +const uint32_t QUERY_SYNC_OBJECT_VERSION_0 = 0; +const uint32_t QUERY_SYNC_OBJECT_VERSION_1 = 1; // Add tableName_ and keys_. +const uint32_t QUERY_SYNC_OBJECT_VERSION_CURRENT = QUERY_SYNC_OBJECT_VERSION_1; // always point the last. + +struct ObjContext { + uint32_t version = QUERY_SYNC_OBJECT_VERSION_0; // serialized struct version + std::vector prefixKey{}; + std::string suggestIndex{}; + std::list queryObjNodes{}; + std::vector keys{}; +}; + +class QuerySyncObject : public QueryObject { +public: + QuerySyncObject(); + QuerySyncObject(const std::list &queryObjNodes, const std::vector &prefixKey, + const std::set &keys); + explicit QuerySyncObject(const Query &query); + ~QuerySyncObject() override; + + std::string GetIdentify() const; + + int SerializeData(Parcel &parcel, uint32_t softWareVersion); + // should call Parcel.IsError() to Get result. + static int DeSerializeData(Parcel &parcel, QuerySyncObject &queryObj); + uint32_t CalculateParcelLen(uint32_t softWareVersion) const; + + std::string GetRelationTableName() const; + +private: + uint32_t CalculateLen() const; + int GetObjContext(ObjContext &objContext) const; + uint32_t GetVersion() const; +}; +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store.cpp b/mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store.cpp new file mode 100644 index 00000000..58f4d6ed --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store.cpp @@ -0,0 +1,520 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "sqlite_relational_store.h" + +#include "db_common.h" +#include "db_dump_helper.h" +#include "db_errno.h" +#include "log_print.h" +#include "db_types.h" +#include "sqlite_relational_store_connection.h" +#include "storage_engine_manager.h" + +namespace DistributedDB { +namespace { + constexpr const char *RELATIONAL_SCHEMA_KEY = "relational_schema"; + constexpr const char *LOG_TABLE_VERSION_KEY = "log_table_version"; + constexpr const char *LOG_TABLE_VERSION_1 = "1.0"; +} + +SQLiteRelationalStore::~SQLiteRelationalStore() +{ + delete sqliteStorageEngine_; + sqliteStorageEngine_ = nullptr; +} + +// Called when a new connection created. +void SQLiteRelationalStore::IncreaseConnectionCounter() +{ + connectionCount_.fetch_add(1, std::memory_order_seq_cst); + if (connectionCount_.load() > 0) { + sqliteStorageEngine_->SetConnectionFlag(true); + } +} + +RelationalStoreConnection *SQLiteRelationalStore::GetDBConnection(int &errCode) +{ + std::lock_guard lock(connectMutex_); + RelationalStoreConnection* connection = new (std::nothrow) SQLiteRelationalStoreConnection(this); + if (connection == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + IncObjRef(this); + IncreaseConnectionCounter(); + return connection; +} + +static void InitDataBaseOption(const RelationalDBProperties &properties, OpenDbProperties &option) +{ + option.uri = properties.GetStringProp(DBProperties::DATA_DIR, ""); + option.createIfNecessary = properties.GetBoolProp(DBProperties::CREATE_IF_NECESSARY, false); +} + +int SQLiteRelationalStore::InitStorageEngine(const RelationalDBProperties &properties) +{ + OpenDbProperties option; + InitDataBaseOption(properties, option); + std::string identifier = properties.GetStringProp(DBProperties::IDENTIFIER_DATA, ""); + + StorageEngineAttr poolSize = {1, 1, 0, 16}; // at most 1 write 16 read. + int errCode = sqliteStorageEngine_->InitSQLiteStorageEngine(poolSize, option, identifier); + if (errCode != E_OK) { + LOGE("Init the sqlite storage engine failed:%d", errCode); + } + return errCode; +} + +void SQLiteRelationalStore::ReleaseResources() +{ + if (sqliteStorageEngine_ != nullptr) { + sqliteStorageEngine_->ClearEnginePasswd(); + (void)StorageEngineManager::ReleaseStorageEngine(sqliteStorageEngine_); + } + RefObject::DecObjRef(storageEngine_); +} + +int SQLiteRelationalStore::CheckDBMode() +{ + int errCode = E_OK; + auto *handle = GetHandle(false, errCode); + if (handle == nullptr) { + return errCode; + } + errCode = handle->CheckDBModeForRelational(); + if (errCode != E_OK) { + LOGE("check relational DB mode failed. %d", errCode); + } + + ReleaseHandle(handle); + return errCode; +} + +int SQLiteRelationalStore::GetSchemaFromMeta() +{ + const Key schemaKey(RELATIONAL_SCHEMA_KEY, RELATIONAL_SCHEMA_KEY + strlen(RELATIONAL_SCHEMA_KEY)); + Value schemaVal; + int errCode = storageEngine_->GetMetaData(schemaKey, schemaVal); + if (errCode != E_OK && errCode != -E_NOT_FOUND) { + LOGE("Get relational schema from meta table failed. %d", errCode); + return errCode; + } else if (errCode == -E_NOT_FOUND || schemaVal.empty()) { + LOGW("No relational schema info was found."); + return E_OK; + } + + std::string schemaStr; + DBCommon::VectorToString(schemaVal, schemaStr); + RelationalSchemaObject schema; + errCode = schema.ParseFromSchemaString(schemaStr); + if (errCode != E_OK) { + LOGE("Parse schema string from meta table failed."); + return errCode; + } + + sqliteStorageEngine_->SetSchema(schema); + return E_OK; +} + +int SQLiteRelationalStore::SaveSchemaToMeta() +{ + const Key schemaKey(RELATIONAL_SCHEMA_KEY, RELATIONAL_SCHEMA_KEY + strlen(RELATIONAL_SCHEMA_KEY)); + Value schemaVal; + DBCommon::StringToVector(sqliteStorageEngine_->GetSchemaRef().ToSchemaString(), schemaVal); + int errCode = storageEngine_->PutMetaData(schemaKey, schemaVal); + if (errCode != E_OK) { + LOGE("Save relational schema to meta table failed. %d", errCode); + } + return errCode; +} + +int SQLiteRelationalStore::SaveLogTableVersionToMeta() +{ + LOGD("save log table version to meta table, key: %s, val: %s", LOG_TABLE_VERSION_KEY, LOG_TABLE_VERSION_1); + const Key logVersionKey(LOG_TABLE_VERSION_KEY, LOG_TABLE_VERSION_KEY + strlen(LOG_TABLE_VERSION_KEY)); + Value logVersionVal(LOG_TABLE_VERSION_1, LOG_TABLE_VERSION_1 + strlen(LOG_TABLE_VERSION_1)); + int errCode = storageEngine_->PutMetaData(logVersionKey, logVersionVal); + if (errCode != E_OK) { + LOGE("save log table version to meta table failed. %d", errCode); + } + return errCode; +} + +int SQLiteRelationalStore::CleanDistributedDeviceTable() +{ + std::vector missingTables; + int errCode = sqliteStorageEngine_->CleanDistributedDeviceTable(missingTables); + if (errCode != E_OK) { + LOGE("Clean distributed device table failed. %d", errCode); + } + for (const auto &deviceTableName : missingTables) { + std::string deviceHash; + std::string tableName; + DBCommon::GetDeviceFromName(deviceTableName, deviceHash, tableName); + syncAbleEngine_->EraseDeviceWaterMark(deviceHash, false, tableName); + if (errCode != E_OK) { + LOGE("Erase water mark failed:%d", errCode); + return errCode; + } + } + return errCode; +} + +int SQLiteRelationalStore::Open(const RelationalDBProperties &properties) +{ + std::lock_guard lock(initalMutex_); + if (isInitialized_) { + LOGD("[RelationalStore][Open] relational db was already initialized."); + return E_OK; + } + + sqliteStorageEngine_ = new (std::nothrow) SQLiteSingleRelationalStorageEngine(properties); + if (sqliteStorageEngine_ == nullptr) { + LOGE("[RelationalStore][Open] Create storage engine failed"); + return -E_OUT_OF_MEMORY; + } + + int errCode = E_OK; + do { + errCode = InitStorageEngine(properties); + if (errCode != E_OK) { + LOGE("[RelationalStore][Open] Init database context fail! errCode = [%d]", errCode); + break; + } + + storageEngine_ = new (std::nothrow) RelationalSyncAbleStorage(sqliteStorageEngine_); + if (storageEngine_ == nullptr) { + LOGE("[RelationalStore][Open] Create syncable storage failed"); + errCode = -E_OUT_OF_MEMORY; + break; + } + + syncAbleEngine_ = std::make_unique(storageEngine_); + + errCode = CheckDBMode(); + if (errCode != E_OK) { + break; + } + + properties_ = properties; + errCode = GetSchemaFromMeta(); + if (errCode != E_OK) { + break; + } + + errCode = SaveLogTableVersionToMeta(); + if (errCode != E_OK) { + break; + } + + errCode = CleanDistributedDeviceTable(); + if (errCode != E_OK) { + break; + } + + isInitialized_ = true; + return E_OK; + } while (false); + + ReleaseResources(); + return errCode; +} + +void SQLiteRelationalStore::OnClose(const std::function ¬ifier) +{ + AutoLock lockGuard(this); + if (notifier) { + closeNotifiers_.push_back(notifier); + } else { + LOGW("Register 'Close()' notifier failed, notifier is null."); + } +} + +SQLiteSingleVerRelationalStorageExecutor *SQLiteRelationalStore::GetHandle(bool isWrite, int &errCode) const +{ + if (sqliteStorageEngine_ == nullptr) { + errCode = -E_INVALID_DB; + return nullptr; + } + + return static_cast(sqliteStorageEngine_->FindExecutor(isWrite, + OperatePerm::NORMAL_PERM, errCode)); +} +void SQLiteRelationalStore::ReleaseHandle(SQLiteSingleVerRelationalStorageExecutor *&handle) const +{ + if (handle == nullptr) { + return; + } + + if (sqliteStorageEngine_ != nullptr) { + StorageExecutor *databaseHandle = handle; + sqliteStorageEngine_->Recycle(databaseHandle); + handle = nullptr; + } +} + +int SQLiteRelationalStore::Sync(const ISyncer::SyncParma &syncParam, uint64_t connectionId) +{ + return syncAbleEngine_->Sync(syncParam, connectionId); +} + +// Called when a connection released. +void SQLiteRelationalStore::DecreaseConnectionCounter() +{ + int count = connectionCount_.fetch_sub(1, std::memory_order_seq_cst); + if (count <= 0) { + LOGF("Decrease db connection counter failed, count <= 0."); + return; + } + if (count != 1) { + return; + } + + LockObj(); + auto notifiers = std::move(closeNotifiers_); + UnlockObj(); + + for (auto ¬ifier : notifiers) { + if (notifier) { + notifier(); + } + } + + // Sync Close + syncAbleEngine_->Close(); + + if (sqliteStorageEngine_ != nullptr) { + delete sqliteStorageEngine_; + sqliteStorageEngine_ = nullptr; + } + // close will dec sync ref of storageEngine_ + DecObjRef(storageEngine_); +} + +void SQLiteRelationalStore::ReleaseDBConnection(RelationalStoreConnection *connection) +{ + if (connectionCount_.load() == 1) { + sqliteStorageEngine_->SetConnectionFlag(false); + } + + connectMutex_.lock(); + if (connection != nullptr) { + KillAndDecObjRef(connection); + DecreaseConnectionCounter(); + connectMutex_.unlock(); + KillAndDecObjRef(this); + } else { + connectMutex_.unlock(); + } +} + +void SQLiteRelationalStore::WakeUpSyncer() +{ + syncAbleEngine_->WakeUpSyncer(); +} + +int SQLiteRelationalStore::CreateDistributedTable(const std::string &tableName) +{ + bool schemaChanged = false; + int errCode = sqliteStorageEngine_->CreateDistributedTable(tableName, schemaChanged); + if (errCode != E_OK) { + LOGE("Create distributed table failed. %d", errCode); + } + if (schemaChanged) { + LOGD("Notify schema changed."); + storageEngine_->NotifySchemaChanged(); + } + return errCode; +} + +int SQLiteRelationalStore::RemoveDeviceData(const std::string &device, const std::string &tableName) +{ + std::map tables = sqliteStorageEngine_->GetSchemaRef().GetTables(); + if (!tableName.empty() && tables.find(tableName) == tables.end()) { + LOGW("Remove device data with table name which is not a distributed table or not exist."); + return E_OK; + } + + int errCode = E_OK; + auto *handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + ReleaseHandle(handle); + return errCode; + } + + errCode = handle->DeleteDistributedDeviceTable(device, tableName); + if (errCode != E_OK) { + LOGE("delete device data failed. %d", errCode); + (void)handle->Rollback(); + ReleaseHandle(handle); + return errCode; + } + errCode = handle->Commit(); + storageEngine_->NotifySchemaChanged(); + ReleaseHandle(handle); + return (errCode != E_OK) ? errCode : syncAbleEngine_->EraseDeviceWaterMark(device, true, tableName); +} + +void SQLiteRelationalStore::RegisterObserverAction(const RelationalObserverAction &action) +{ + storageEngine_->RegisterObserverAction(action); +} + +int SQLiteRelationalStore::StopLifeCycleTimer() +{ + auto runtimeCxt = RuntimeContext::GetInstance(); + if (runtimeCxt == nullptr) { + return -E_INVALID_ARGS; + } + if (lifeTimerId_ != 0) { + TimerId timerId = lifeTimerId_; + lifeTimerId_ = 0; + runtimeCxt->RemoveTimer(timerId, false); + } + return E_OK; +} + +int SQLiteRelationalStore::StartLifeCycleTimer(const DatabaseLifeCycleNotifier ¬ifier) +{ + auto runtimeCxt = RuntimeContext::GetInstance(); + if (runtimeCxt == nullptr) { + return -E_INVALID_ARGS; + } + RefObject::IncObjRef(this); + TimerId timerId = 0; + int errCode = runtimeCxt->SetTimer(DBConstant::DEF_LIFE_CYCLE_TIME, + [this](TimerId id) -> int { + std::lock_guard lock(lifeCycleMutex_); + if (lifeCycleNotifier_) { + // normal identifier mode + auto identifier = properties_.GetStringProp(DBProperties::IDENTIFIER_DATA, ""); + auto userId = properties_.GetStringProp(DBProperties::USER_ID, ""); + lifeCycleNotifier_(identifier, userId); + } + return 0; + }, + [this]() { + int ret = RuntimeContext::GetInstance()->ScheduleTask([this]() { + RefObject::DecObjRef(this); + }); + if (ret != E_OK) { + LOGE("SQLiteSingleVerNaturalStore timer finalizer ScheduleTask, errCode %d", ret); + } + }, + timerId); + if (errCode != E_OK) { + lifeTimerId_ = 0; + LOGE("SetTimer failed:%d", errCode); + RefObject::DecObjRef(this); + return errCode; + } + + lifeCycleNotifier_ = notifier; + lifeTimerId_ = timerId; + return E_OK; +} + +int SQLiteRelationalStore::RegisterLifeCycleCallback(const DatabaseLifeCycleNotifier ¬ifier) +{ + int errCode; + { + std::lock_guard lock(lifeCycleMutex_); + if (lifeTimerId_ != 0) { + errCode = StopLifeCycleTimer(); + if (errCode != E_OK) { + LOGE("Stop the life cycle timer failed:%d", errCode); + return errCode; + } + } + + if (!notifier) { + return E_OK; + } + errCode = StartLifeCycleTimer(notifier); + if (errCode != E_OK) { + LOGE("Register life cycle timer failed:%d", errCode); + return errCode; + } + } + auto listener = std::bind(&SQLiteRelationalStore::HeartBeat, this); + storageEngine_->RegisterHeartBeatListener(listener); + return errCode; +} + +void SQLiteRelationalStore::HeartBeat() +{ + std::lock_guard lock(lifeCycleMutex_); + int errCode = ResetLifeCycleTimer(); + if (errCode != E_OK) { + LOGE("Heart beat for life cycle failed:%d", errCode); + } +} + +int SQLiteRelationalStore::ResetLifeCycleTimer() +{ + if (lifeTimerId_ == 0) { + return E_OK; + } + auto lifeNotifier = lifeCycleNotifier_; + lifeCycleNotifier_ = nullptr; + int errCode = StopLifeCycleTimer(); + if (errCode != E_OK) { + LOGE("[Reset timer]Stop the life cycle timer failed:%d", errCode); + } + return StartLifeCycleTimer(lifeNotifier); +} + +std::string SQLiteRelationalStore::GetStorePath() const +{ + return properties_.GetStringProp(DBProperties::DATA_DIR, ""); +} + +RelationalDBProperties SQLiteRelationalStore::GetProperties() const +{ + return properties_; +} + +void SQLiteRelationalStore::StopSync(uint64_t connectionId) +{ + return syncAbleEngine_->StopSync(connectionId); +} + +void SQLiteRelationalStore::Dump(int fd) +{ + std::string userId = ""; + std::string appId = ""; + std::string storeId = ""; + std::string label = ""; + if (sqliteStorageEngine_ != nullptr) { + userId = sqliteStorageEngine_->GetProperties().GetStringProp(DBProperties::USER_ID, ""); + appId = sqliteStorageEngine_->GetProperties().GetStringProp(DBProperties::APP_ID, ""); + storeId = sqliteStorageEngine_->GetProperties().GetStringProp(DBProperties::STORE_ID, ""); + label = sqliteStorageEngine_->GetProperties().GetStringProp(DBProperties::IDENTIFIER_DATA, ""); + } + label = DBCommon::TransferStringToHex(label); + DBDumpHelper::Dump(fd, "\tdb userId = %s, appId = %s, storeId = %s, label = %s\n", + userId.c_str(), appId.c_str(), storeId.c_str(), label.c_str()); + if (syncAbleEngine_ != nullptr) { + syncAbleEngine_->Dump(fd); + } +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store.h b/mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store.h new file mode 100644 index 00000000..42b86798 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_RELATIONAL_STORE_H +#define SQLITE_RELATIONAL_STORE_H +#ifdef RELATIONAL_STORE + +#include +#include +#include + +#include "irelational_store.h" +#include "sqlite_single_relational_storage_engine.h" +#include "isyncer.h" +#include "sync_able_engine.h" +#include "relational_sync_able_storage.h" +#include "runtime_context.h" + +namespace DistributedDB { +using RelationalObserverAction = std::function; +class SQLiteRelationalStore : public IRelationalStore { +public: + SQLiteRelationalStore() = default; + ~SQLiteRelationalStore() override; + + RelationalStoreConnection *GetDBConnection(int &errCode) override; + int Open(const RelationalDBProperties &properties) override; + void OnClose(const std::function ¬ifier); + + SQLiteSingleVerRelationalStorageExecutor *GetHandle(bool isWrite, int &errCode) const; + void ReleaseHandle(SQLiteSingleVerRelationalStorageExecutor *&handle) const; + + int Sync(const ISyncer::SyncParma &syncParam, uint64_t connectionId); + + void ReleaseDBConnection(RelationalStoreConnection *connection); + + void WakeUpSyncer() override; + + // for test mock + const RelationalSyncAbleStorage *GetStorageEngine() + { + return storageEngine_; + } + + int CreateDistributedTable(const std::string &tableName); + + int RemoveDeviceData(const std::string &device, const std::string &tableName); + + void RegisterObserverAction(const RelationalObserverAction &action); + int RegisterLifeCycleCallback(const DatabaseLifeCycleNotifier ¬ifier); + + std::string GetStorePath() const override; + + RelationalDBProperties GetProperties() const override; + + void StopSync(uint64_t connectionId); + + void Dump(int fd) override; + +private: + void ReleaseResources(); + + // 1 store 1 connection + void DecreaseConnectionCounter(); + int CheckDBMode(); + int GetSchemaFromMeta(); + int SaveSchemaToMeta(); + + int SaveLogTableVersionToMeta(); + + int CleanDistributedDeviceTable(); + + int StopLifeCycleTimer(); + int StartLifeCycleTimer(const DatabaseLifeCycleNotifier ¬ifier); + void HeartBeat(); + int ResetLifeCycleTimer(); + + // use for sync Interactive + std::unique_ptr syncAbleEngine_ = nullptr; // For storage operate sync function + // use ref obj same as kv + RelationalSyncAbleStorage *storageEngine_ = nullptr; // For storage operate data + SQLiteSingleRelationalStorageEngine *sqliteStorageEngine_ = nullptr; + + void IncreaseConnectionCounter(); + int InitStorageEngine(const RelationalDBProperties &kvDBProp); + std::mutex connectMutex_; + std::atomic connectionCount_ = 0; + std::vector> closeNotifiers_; + + mutable std::mutex schemaMutex_; + RelationalDBProperties properties_; + + mutable std::mutex initalMutex_; + bool isInitialized_ = false; + + // lifeCycle + std::mutex lifeCycleMutex_; + DatabaseLifeCycleNotifier lifeCycleNotifier_; + TimerId lifeTimerId_; +}; +} // namespace DistributedDB +#endif +#endif // SQLITE_RELATIONAL_STORE_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store_connection.cpp b/mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store_connection.cpp new file mode 100644 index 00000000..0628f32e --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store_connection.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "sqlite_relational_store_connection.h" +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +SQLiteRelationalStoreConnection::SQLiteRelationalStoreConnection(SQLiteRelationalStore *store) + : RelationalStoreConnection(store) +{ + OnKill([this]() { + auto *store = GetDB(); + if (store == nullptr) { + return; + } + UnlockObj(); + store->StopSync(GetConnectionId()); + LockObj(); + }); +} +// Close and release the connection. +int SQLiteRelationalStoreConnection::Close() +{ + if (store_ == nullptr) { + return -E_INVALID_CONNECTION; + } + + if (isExclusive_.load()) { + return -E_BUSY; + } + + // check if transaction closed + { + std::lock_guard transactionLock(transactionMutex_); + if (writeHandle_ != nullptr) { + LOGW("Transaction started, need to rollback before close."); + int errCode = RollBack(); + if (errCode != E_OK) { + LOGE("Rollback transaction failed, %d.", errCode); + } + ReleaseExecutor(writeHandle_); + } + } + + static_cast(store_)->ReleaseDBConnection(this); + return E_OK; +} + +std::string SQLiteRelationalStoreConnection::GetIdentifier() +{ + return store_->GetProperties().GetStringProp(RelationalDBProperties::IDENTIFIER_DATA, ""); +} + +SQLiteSingleVerRelationalStorageExecutor *SQLiteRelationalStoreConnection::GetExecutor(bool isWrite, int &errCode) const +{ + auto *store = GetDB(); + if (store == nullptr) { + errCode = -E_NOT_INIT; + LOGE("[RelationalConnection] store is null, get executor failed! errCode = [%d]", errCode); + return nullptr; + } + + return store->GetHandle(isWrite, errCode); +} + +void SQLiteRelationalStoreConnection::ReleaseExecutor(SQLiteSingleVerRelationalStorageExecutor *&executor) const +{ + auto *store = GetDB(); + if (store != nullptr) { + store->ReleaseHandle(executor); + } +} + +int SQLiteRelationalStoreConnection::StartTransaction() +{ + std::lock_guard lock(transactionMutex_); + if (writeHandle_ != nullptr) { + LOGD("Transaction started already."); + return -E_TRANSACT_STATE; + } + + int errCode = E_OK; + auto *handle = GetExecutor(true, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->StartTransaction(TransactType::DEFERRED); + if (errCode != E_OK) { + ReleaseExecutor(handle); + return errCode; + } + + writeHandle_ = handle; + return E_OK; +} + +// Commit the transaction +int SQLiteRelationalStoreConnection::Commit() +{ + std::lock_guard lock(transactionMutex_); + if (writeHandle_ == nullptr) { + LOGE("single version database is null or the transaction has not been started"); + return -E_INVALID_DB; + } + + int errCode = writeHandle_->Commit(); + ReleaseExecutor(writeHandle_); + LOGD("connection commit transaction!"); + return errCode; +} + +// Roll back the transaction +int SQLiteRelationalStoreConnection::RollBack() +{ + std::lock_guard lock(transactionMutex_); + if (writeHandle_ == nullptr) { + LOGE("Invalid handle for rollback or the transaction has not been started."); + return -E_INVALID_DB; + } + + int errCode = writeHandle_->Rollback(); + ReleaseExecutor(writeHandle_); + LOGI("connection rollback transaction!"); + return errCode; +} + +int SQLiteRelationalStoreConnection::CreateDistributedTable(const std::string &tableName) +{ + auto *store = GetDB(); + if (store == nullptr) { + LOGE("[RelationalConnection] store is null, get DB failed!"); + return -E_INVALID_CONNECTION; + } + + int errCode = store->CreateDistributedTable(tableName); + if (errCode != E_OK) { + LOGE("[RelationalConnection] crete distributed table failed. %d", errCode); + } + return errCode; +} + +int SQLiteRelationalStoreConnection::RemoveDeviceData(const std::string &device) +{ + return RemoveDeviceData(device, {}); +} + +int SQLiteRelationalStoreConnection::RemoveDeviceData(const std::string &device, const std::string &tableName) +{ + auto *store = GetDB(); + if (store == nullptr) { + LOGE("[RelationalConnection] store is null, get DB failed!"); + return -E_INVALID_CONNECTION; + } + + int errCode = store->RemoveDeviceData(device, tableName); + if (errCode != E_OK) { + LOGE("[RelationalConnection] remove device data failed. %d", errCode); + } + return errCode; +} + +int SQLiteRelationalStoreConnection::Pragma(int cmd, void *parameter) // reserve for interface function fix +{ + (void)cmd; + (void)parameter; + return E_OK; +} + +int SQLiteRelationalStoreConnection::TriggerAutoSync() +{ + return E_OK; +} + +int SQLiteRelationalStoreConnection::SyncToDevice(SyncInfo &info) +{ + auto *store = GetDB(); + if (store == nullptr) { + LOGE("[RelationalConnection] store is null, get executor failed!"); + return -E_INVALID_CONNECTION; + } + + { + AutoLock lockGuard(this); + IncObjRef(this); + ISyncer::SyncParma syncParam; + syncParam.devices = info.devices; + syncParam.mode = info.mode; + syncParam.wait = info.wait; + syncParam.isQuerySync = true; + syncParam.relationOnComplete = info.onComplete; + syncParam.syncQuery = QuerySyncObject(info.query); + syncParam.onFinalize = [this]() { DecObjRef(this); }; + int errCode = store->Sync(syncParam, GetConnectionId()); + if (errCode != E_OK) { + DecObjRef(this); + return errCode; + } + } + return E_OK; +} + +int SQLiteRelationalStoreConnection::RegisterLifeCycleCallback(const DatabaseLifeCycleNotifier ¬ifier) +{ + auto *store = GetDB(); + if (store == nullptr) { + LOGE("[RelationalConnection] store is null, get executor failed!"); + return -E_INVALID_CONNECTION; + } + + return store->RegisterLifeCycleCallback(notifier); +} + +void SQLiteRelationalStoreConnection::RegisterObserverAction(const RelationalObserverAction &action) +{ + static_cast(store_)->RegisterObserverAction(action); +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store_connection.h b/mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store_connection.h new file mode 100644 index 00000000..13b2dbd2 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/relational/sqlite_relational_store_connection.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_RELATIONAL_STORE_CONNECTION_H +#define SQLITE_RELATIONAL_STORE_CONNECTION_H +#ifdef RELATIONAL_STORE + +#include +#include +#include "macro_utils.h" +#include "relational_store_connection.h" +#include "sqlite_single_ver_relational_storage_executor.h" +#include "sqlite_relational_store.h" + +namespace DistributedDB { +class SQLiteRelationalStoreConnection : public RelationalStoreConnection { +public: + explicit SQLiteRelationalStoreConnection(SQLiteRelationalStore *store); + + ~SQLiteRelationalStoreConnection() override = default; + + DISABLE_COPY_ASSIGN_MOVE(SQLiteRelationalStoreConnection); + + // Close and release the connection. + int Close() override; + int TriggerAutoSync() override; + int SyncToDevice(SyncInfo &info) override; + std::string GetIdentifier() override; + int CreateDistributedTable(const std::string &tableName) override; + int RegisterLifeCycleCallback(const DatabaseLifeCycleNotifier ¬ifier) override; + + int RemoveDeviceData(const std::string &device) override; + int RemoveDeviceData(const std::string &device, const std::string &tableName) override; + void RegisterObserverAction(const RelationalObserverAction &action) override; + +protected: + + int Pragma(int cmd, void *parameter) override; +private: + + SQLiteSingleVerRelationalStorageExecutor *GetExecutor(bool isWrite, int &errCode) const; + void ReleaseExecutor(SQLiteSingleVerRelationalStorageExecutor *&executor) const; + int StartTransaction(); + // Commit the transaction + int Commit(); + + // Roll back the transaction + int RollBack(); + + SQLiteSingleVerRelationalStorageExecutor *writeHandle_ = nullptr; + mutable std::mutex transactionMutex_; // used for transaction +}; +} // namespace DistributedDB +#endif +#endif // SQLITE_RELATIONAL_STORE_CONNECTION_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/relational/sqlite_single_relational_storage_engine.cpp b/mock/distributeddb/storage/src/sqlite/relational/sqlite_single_relational_storage_engine.cpp new file mode 100644 index 00000000..28078bfc --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/relational/sqlite_single_relational_storage_engine.cpp @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "sqlite_single_relational_storage_engine.h" + +#include "db_common.h" +#include "db_errno.h" +#include "res_finalizer.h" +#include "sqlite_single_ver_relational_storage_executor.h" + + +namespace DistributedDB { +namespace { + constexpr const char *RELATIONAL_SCHEMA_KEY = "relational_schema"; +} + +SQLiteSingleRelationalStorageEngine::SQLiteSingleRelationalStorageEngine(RelationalDBProperties properties) + : properties_(properties) +{} + +SQLiteSingleRelationalStorageEngine::~SQLiteSingleRelationalStorageEngine() {}; + +StorageExecutor *SQLiteSingleRelationalStorageEngine::NewSQLiteStorageExecutor(sqlite3 *dbHandle, bool isWrite, + bool isMemDb) +{ + return new (std::nothrow) SQLiteSingleVerRelationalStorageExecutor(dbHandle, isWrite); +} + +int SQLiteSingleRelationalStorageEngine::Upgrade(sqlite3 *db) +{ + return SQLiteUtils::CreateRelationalMetaTable(db); +} + +int SQLiteSingleRelationalStorageEngine::RegisterFunction(sqlite3 *db) const +{ + int errCode = SQLiteUtils::RegisterCalcHash(db); + if (errCode != E_OK) { + LOGE("[engine] register calculate hash failed!"); + return errCode; + } + + errCode = SQLiteUtils::RegisterGetSysTime(db); + if (errCode != E_OK) { + LOGE("[engine] register get sys time failed!"); + } + return E_OK; +} + +int SQLiteSingleRelationalStorageEngine::CreateNewExecutor(bool isWrite, StorageExecutor *&handle) +{ + sqlite3 *db = nullptr; + int errCode = SQLiteUtils::OpenDatabase(option_, db, false); + if (errCode != E_OK) { + return errCode; + } + do { + errCode = Upgrade(db); // create meta_data table. + if (errCode != E_OK) { + break; + } + + errCode = RegisterFunction(db); + if (errCode != E_OK) { + break; + } + + handle = NewSQLiteStorageExecutor(db, isWrite, false); + if (handle == nullptr) { + LOGE("[Relational] New SQLiteStorageExecutor[%d] for the pool failed.", isWrite); + errCode = -E_OUT_OF_MEMORY; + break; + } + return E_OK; + } while (false); + + (void)sqlite3_close_v2(db); + db = nullptr; + return errCode; +} + +int SQLiteSingleRelationalStorageEngine::ReleaseExecutor(SQLiteSingleVerRelationalStorageExecutor *&handle) +{ + if (handle == nullptr) { + return E_OK; + } + StorageExecutor *databaseHandle = handle; + Recycle(databaseHandle); + handle = nullptr; + return E_OK; +} + +void SQLiteSingleRelationalStorageEngine::SetSchema(const RelationalSchemaObject &schema) +{ + std::lock_guard lock(schemaMutex_); + schema_ = schema; +} + +const RelationalSchemaObject &SQLiteSingleRelationalStorageEngine::GetSchemaRef() const +{ + std::lock_guard lock(schemaMutex_); + return schema_; +} + +namespace { +int SaveSchemaToMetaTable(SQLiteSingleVerRelationalStorageExecutor *handle, const RelationalSchemaObject &schema) +{ + const Key schemaKey(RELATIONAL_SCHEMA_KEY, RELATIONAL_SCHEMA_KEY + strlen(RELATIONAL_SCHEMA_KEY)); + Value schemaVal; + DBCommon::StringToVector(schema.ToSchemaString(), schemaVal); + int errCode = handle->PutKvData(schemaKey, schemaVal); // save schema to meta_data + if (errCode != E_OK) { + LOGE("Save schema to meta table failed. %d", errCode); + } + return errCode; +} +} + +int SQLiteSingleRelationalStorageEngine::CreateDistributedTable(const std::string &tableName, bool &schemaChanged) +{ + std::lock_guard lock(schemaMutex_); + RelationalSchemaObject tmpSchema = schema_; + bool isUpgrade = false; + if (tmpSchema.GetTable(tableName).GetTableName() == tableName) { + LOGW("distributed table was already created."); + isUpgrade = true; + int errCode = UpgradeDistributedTable(tableName); + if (errCode != E_OK) { + LOGE("Upgrade distributed table failed. %d", errCode); + return errCode; + } + } + + if (tmpSchema.GetTables().size() >= DBConstant::MAX_DISTRIBUTED_TABLE_COUNT) { + LOGE("The number of distributed tables is exceeds limit."); + return -E_MAX_LIMITS; + } + + LOGD("Create distributed table."); + int errCode = E_OK; + auto *handle = static_cast(FindExecutor(true, OperatePerm::NORMAL_PERM, + errCode)); + if (handle == nullptr) { + return errCode; + } + ResFinalizer finalizer([&handle, this] { this->ReleaseExecutor(handle); }); + + errCode = handle->StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + return errCode; + } + + TableInfo table; + errCode = handle->CreateDistributedTable(tableName, table, isUpgrade); + if (errCode != E_OK) { + LOGE("create distributed table failed. %d", errCode); + (void)handle->Rollback(); + return errCode; + } + + tmpSchema.AddRelationalTable(table); + errCode = SaveSchemaToMetaTable(handle, tmpSchema); + if (errCode != E_OK) { + LOGE("Save schema to meta table for create distributed table failed. %d", errCode); + (void)handle->Rollback(); + return errCode; + } + + errCode = handle->Commit(); + if (errCode == E_OK) { + schema_ = tmpSchema; + schemaChanged = true; + } + return errCode; +} + +int SQLiteSingleRelationalStorageEngine::UpgradeDistributedTable(const std::string &tableName) +{ + LOGD("Upgrade distributed table."); + RelationalSchemaObject tmpSchema = schema_; + int errCode = E_OK; + auto *handle = static_cast(FindExecutor(true, OperatePerm::NORMAL_PERM, + errCode)); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + ReleaseExecutor(handle); + return errCode; + } + + TableInfo newTable; + errCode = handle->UpgradeDistributedTable(tmpSchema.GetTable(tableName), newTable); + if (errCode != E_OK) { + LOGE("Upgrade distributed table failed. %d", errCode); + (void)handle->Rollback(); + ReleaseExecutor(handle); + return errCode; + } + + tmpSchema.AddRelationalTable(newTable); + errCode = SaveSchemaToMetaTable(handle, tmpSchema); + if (errCode != E_OK) { + LOGE("Save schema to meta table for upgrade distributed table failed. %d", errCode); + (void)handle->Rollback(); + ReleaseExecutor(handle); + return errCode; + } + + errCode = handle->Commit(); + if (errCode == E_OK) { + schema_ = tmpSchema; + } + ReleaseExecutor(handle); + return errCode; +} + +int SQLiteSingleRelationalStorageEngine::CleanDistributedDeviceTable(std::vector &missingTables) +{ + int errCode = E_OK; + auto handle = static_cast(FindExecutor(true, OperatePerm::NORMAL_PERM, + errCode)); + if (handle == nullptr) { + return errCode; + } + + std::lock_guard lock(schemaMutex_); + errCode = handle->StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + ReleaseExecutor(handle); + return errCode; + } + + errCode = handle->CheckAndCleanDistributedTable(schema_.GetTableNames(), missingTables); + if (errCode == E_OK) { + errCode = handle->Commit(); + if (errCode == E_OK) { + // Remove non-existent tables from the schema + for (const auto &tableName : missingTables) { + schema_.RemoveRelationalTable(tableName); + } + SaveSchemaToMetaTable(handle, schema_); // save schema to meta_data + } + } else { + LOGE("Check distributed table failed. %d", errCode); + (void)handle->Rollback(); + } + + ReleaseExecutor(handle); + return errCode; +} + +const RelationalDBProperties &SQLiteSingleRelationalStorageEngine::GetProperties() const +{ + return properties_; +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/relational/sqlite_single_relational_storage_engine.h b/mock/distributeddb/storage/src/sqlite/relational/sqlite_single_relational_storage_engine.h new file mode 100644 index 00000000..34c89ab2 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/relational/sqlite_single_relational_storage_engine.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_RELATIONAL_ENGINE_H +#define SQLITE_RELATIONAL_ENGINE_H +#ifdef RELATIONAL_STORE + +#include "macro_utils.h" +#include "relationaldb_properties.h" +#include "sqlite_storage_engine.h" +#include "sqlite_single_ver_relational_storage_executor.h" + +namespace DistributedDB { +class SQLiteSingleRelationalStorageEngine : public SQLiteStorageEngine { +public: + explicit SQLiteSingleRelationalStorageEngine(RelationalDBProperties properties); + ~SQLiteSingleRelationalStorageEngine() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteSingleRelationalStorageEngine); + + void SetSchema(const RelationalSchemaObject &schema); + + const RelationalSchemaObject &GetSchemaRef() const; + + int CreateDistributedTable(const std::string &tableName, bool &schemaChanged); + + int CleanDistributedDeviceTable(std::vector &missingTables); + + const RelationalDBProperties &GetProperties() const; + +protected: + StorageExecutor *NewSQLiteStorageExecutor(sqlite3 *db, bool isWrite, bool isMemDb) override; + int Upgrade(sqlite3 *db) override; + int CreateNewExecutor(bool isWrite, StorageExecutor *&handle) override; + +private: + // For executor. + int ReleaseExecutor(SQLiteSingleVerRelationalStorageExecutor *&handle); + + // For db. + int RegisterFunction(sqlite3 *db) const; + + int UpgradeDistributedTable(const std::string &tableName); + + RelationalSchemaObject schema_; + mutable std::mutex schemaMutex_; + + RelationalDBProperties properties_; +}; +} // namespace DistributedDB +#endif +#endif // SQLITE_RELATIONAL_ENGINE_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_import.h b/mock/distributeddb/storage/src/sqlite/sqlite_import.h new file mode 100644 index 00000000..a21885cb --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_import.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_IMPORT_H +#define SQLITE_IMPORT_H + +// using the "sqlite3sym.h" in OHOS +#ifndef USE_SQLITE_SYMBOLS +#include "sqlite3.h" +#else +#include "sqlite3sym.h" +#endif +#endif // SQLITE_IMPORT_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb.cpp new file mode 100644 index 00000000..c7816daa --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb.cpp @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_local_kvdb.h" + +#include + +#include "db_constant.h" +#include "db_common.h" +#include "log_print.h" +#include "platform_specific.h" +#include "package_file.h" +#include "kvdb_utils.h" +#include "local_database_oper.h" +#include "sqlite_local_kvdb_connection.h" +#include "sqlite_local_storage_engine.h" + +namespace DistributedDB { +namespace { + const std::string CREATE_SQL = + "CREATE TABLE IF NOT EXISTS data(key BLOB PRIMARY key, value BLOB);"; +} + +SQLiteLocalKvDB::SQLiteLocalKvDB() + : storageEngine_(nullptr) +{} + +SQLiteLocalKvDB::~SQLiteLocalKvDB() +{ + if (storageEngine_ != nullptr) { + delete storageEngine_; + storageEngine_ = nullptr; + } +} + +int SQLiteLocalKvDB::Open(const KvDBProperties &kvDBProp) +{ + int databaseType = kvDBProp.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + if (databaseType == KvDBProperties::LOCAL_TYPE) { + std::unique_ptr operation = std::make_unique(this, nullptr); + (void)operation->ClearExportedTempFiles(kvDBProp); + int errCode = operation->RekeyRecover(kvDBProp); + if (errCode != E_OK) { + LOGE("Recover for open db failed in local db:%d", errCode); + return errCode; + } + + errCode = operation->ClearImportTempFile(kvDBProp); + if (errCode != E_OK) { + LOGE("Recover for open db failed in multi version:%d", errCode); + return errCode; + } + } + + bool createIfNecessary = kvDBProp.GetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + std::string subDir = KvDBProperties::GetStoreSubDirectory(databaseType); + std::string dataDir = kvDBProp.GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string identifierDir = kvDBProp.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + int errCode = DBCommon::CreateStoreDirectory(dataDir, identifierDir, subDir, createIfNecessary); + if (errCode != E_OK) { + LOGE("Create directory for local database failed:%d", errCode); + return errCode; + } + + errCode = InitStorageEngine(kvDBProp); + if (errCode != E_OK) { + return errCode; + } + MyProp() = kvDBProp; + return E_OK; +} + +GenericKvDBConnection *SQLiteLocalKvDB::NewConnection(int &errCode) +{ + auto connection = new (std::nothrow) SQLiteLocalKvDBConnection(this); + if (connection == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + + errCode = E_OK; + return connection; +} + +void SQLiteLocalKvDB::Close() {} + +int SQLiteLocalKvDB::Rekey(const CipherPassword &passwd) +{ + if (storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + + std::unique_ptr operation = std::make_unique(this, storageEngine_); + return operation->Rekey(passwd); +} + +int SQLiteLocalKvDB::Export(const std::string &filePath, const CipherPassword &passwd) +{ + int errCode = E_OK; + // Exclusively write resources + SQLiteLocalStorageExecutor *handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + std::string devId = "local"; + + std::unique_ptr operation = std::make_unique(this, storageEngine_); + operation->SetLocalDevId(DBCommon::TransferHashString(devId)); + errCode = operation->Export(filePath, passwd); + + ReleaseHandle(handle); + return errCode; +} + +int SQLiteLocalKvDB::Import(const std::string &filePath, const CipherPassword &passwd) +{ + if (storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = storageEngine_->TryToDisable(true, OperatePerm::IMPORT_MONOPOLIZE_PERM); + if (errCode != E_OK) { + LOGE("Failed to disable the database"); + return errCode; + } + + // Need to monopolize the entire process + std::unique_ptr operation = std::make_unique(this, storageEngine_); + errCode = operation->Import(filePath, passwd); + // restore the storage engine and the syncer. + storageEngine_->Enable(OperatePerm::IMPORT_MONOPOLIZE_PERM); + return errCode; +} + +int SQLiteLocalKvDB::InitDatabaseContext(const KvDBProperties &kvDBProp) +{ + return InitStorageEngine(kvDBProp); +} + +void SQLiteLocalKvDB::EnableAutonomicUpgrade() +{ + isAutonomicUpgradeEnable_ = true; +} + +int SQLiteLocalKvDB::RunExportLogic(CipherType type, const CipherPassword &passwd, const std::string &newDbName) +{ + OpenDbProperties option; + InitDataBaseOption(MyProp(), option); + option.createIfNecessary = true; + sqlite3 *db = nullptr; + int errCode = SQLiteUtils::OpenDatabase(option, db); + if (errCode != E_OK) { + LOGE("Open db for export error:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::ExportDatabase(db, type, passwd, newDbName); + if (errCode != E_OK) { + goto END; + } + +END: + (void)sqlite3_close_v2(db); + db = nullptr; + return errCode; +} + +int SQLiteLocalKvDB::RunRekeyLogic(CipherType type, const CipherPassword &passwd) +{ + OpenDbProperties option; + InitDataBaseOption(MyProp(), option); + option.createIfNecessary = true; + sqlite3 *db = nullptr; + int errCode = SQLiteUtils::OpenDatabase(option, db); + if (errCode != E_OK) { + LOGE("Open db for rekey error:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::Rekey(db, passwd); + if (errCode != E_OK) { + (void)sqlite3_close_v2(db); + db = nullptr; + return errCode; + } + (void)sqlite3_close_v2(db); + db = nullptr; + MyProp().SetPassword(option.cipherType, passwd); + if (storageEngine_ != nullptr) { + storageEngine_->Release(); + } + + return InitStorageEngine(MyProp()); +} + +SQLiteLocalStorageExecutor *SQLiteLocalKvDB::GetHandle(bool isWrite, int &errCode, OperatePerm perm) const +{ + if (storageEngine_ == nullptr) { + errCode = -E_INVALID_DB; + return nullptr; + } + + return static_cast(storageEngine_->FindExecutor(isWrite, perm, errCode)); +} + +int SQLiteLocalKvDB::GetVersion(const KvDBProperties &kvDBProp, int &version, bool &isDbExisted) const +{ + OpenDbProperties option; + InitDataBaseOption(kvDBProp, option); + isDbExisted = OS::CheckPathExistence(option.uri); + + int errCode = E_OK; + if (isDbExisted) { + errCode = SQLiteUtils::GetVersion(option, version); + } + return errCode; +} + +int SQLiteLocalKvDB::SetVersion(const KvDBProperties &kvDBProp, int version) +{ + OpenDbProperties option; + InitDataBaseOption(kvDBProp, option); + bool isDbExisted = OS::CheckPathExistence(option.uri); + if (!isDbExisted) { + return -E_NOT_FOUND; + } + return SQLiteUtils::SetUserVer(option, version); +} + +const KvDBProperties &SQLiteLocalKvDB::GetDbProperties() const +{ + return GetMyProperties(); +} + +KvDBProperties &SQLiteLocalKvDB::GetDbPropertyForUpdate() +{ + return MyProp(); +} + +void SQLiteLocalKvDB::ReleaseHandle(SQLiteLocalStorageExecutor *&handle) const +{ + if (storageEngine_ != nullptr) { + bool isCorrupted = handle->GetCorruptedStatus(); + StorageExecutor *databaseHandle = handle; + storageEngine_->Recycle(databaseHandle); + handle = nullptr; + if (isCorrupted) { + CorruptNotify(); + } + } +} + +int SQLiteLocalKvDB::InitStorageEngine(const KvDBProperties &kvDBProp) +{ + if (storageEngine_ == nullptr) { + // Create HandlePool + storageEngine_ = new (std::nothrow) SQLiteLocalStorageEngine(); + if (storageEngine_ == nullptr) { + LOGE("Create local sqlite storage engine OOM"); + return -E_OUT_OF_MEMORY; + } + } + + OpenDbProperties option; + InitDataBaseOption(kvDBProp, option); + StorageEngineAttr poolSize = {0, 1, 0, 4}; // 1 write 4 read at most. + int errCode = storageEngine_->InitSQLiteStorageEngine(poolSize, option); + if (errCode != E_OK) { + goto END; + } + + // We don't have to do version check here if the SQLiteLocalKvDB does not work as LocalStore. + // The isAutonomicUpgradeEnable_ true indicate that it work as LocalStore. Do version check in three case: + // Open the database, which call Open then InitStorageEngine. + // Import the database, which call InitDatabaseContext then InitStorageEngine. + // Rekey the database, which call RunRekeyLogic then InitStorageEngine. (This case is not necessary in fact) + errCode = CheckVersionAndUpgradeIfNeed(option); +END: + if (errCode != E_OK) { + LOGE("Init sqlite handler pool failed:%d", errCode); + // Transform the errCode. + } + return errCode; +} + +void SQLiteLocalKvDB::InitDataBaseOption(const KvDBProperties &kvDBProp, OpenDbProperties &option) const +{ + std::string dataDir = kvDBProp.GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string identifierDir = kvDBProp.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + std::string dbName = kvDBProp.GetStringProp(KvDBProperties::FILE_NAME, DBConstant::LOCAL_DATABASE_NAME); + int databaseType = kvDBProp.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + bool createIfNecessary = kvDBProp.GetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + std::string subDir = KvDBProperties::GetStoreSubDirectory(databaseType); + // Table name "data" should not be changed in the future, otherwise when an older software open a newer database + // with table of other name, we will create an second table as result which is not expected. + std::vector createTableSqls = {CREATE_SQL}; + CipherType cipherType; + CipherPassword passwd; + kvDBProp.GetPassword(cipherType, passwd); + std::string uri = dataDir + "/" + identifierDir + "/" + subDir + "/" + dbName + DBConstant::SQLITE_DB_EXTENSION; + option = {uri, createIfNecessary, false, createTableSqls, cipherType, passwd}; +} + +int SQLiteLocalKvDB::BackupCurrentDatabase(const KvDBProperties &properties, const std::string &dir) +{ + std::string baseDir; + int errCode = GetWorkDir(properties, baseDir); + if (errCode != E_OK) { + LOGE("[SqlLocalDb][Backup] GetWorkDir fail, errCode=%d.", errCode); + return errCode; + } + std::string dbName = properties.GetStringProp(KvDBProperties::FILE_NAME, DBConstant::LOCAL_DATABASE_NAME); + int databaseType = properties.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + std::string subDir = KvDBProperties::GetStoreSubDirectory(databaseType); + std::string currentDb = baseDir + "/" + subDir + "/" + dbName + DBConstant::SQLITE_DB_EXTENSION; + std::string dstDb = dir + "/" + dbName + DBConstant::SQLITE_DB_EXTENSION; + errCode = DBCommon::CopyFile(currentDb, dstDb); + if (errCode != E_OK) { + LOGE("Copy the local current db error:%d", errCode); + } + return errCode; +} + +int SQLiteLocalKvDB::ImportDatabase(const KvDBProperties &properties, const std::string &dir, + const CipherPassword &passwd) +{ + std::string baseDir; + int errCode = GetWorkDir(properties, baseDir); + if (errCode != E_OK) { + return errCode; + } + std::string dbName = properties.GetStringProp(KvDBProperties::FILE_NAME, DBConstant::LOCAL_DATABASE_NAME); + int databaseType = properties.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + std::string subDir = KvDBProperties::GetStoreSubDirectory(databaseType); + std::string dstDb = baseDir + "/" + subDir + "/" + dbName + DBConstant::SQLITE_DB_EXTENSION; + std::string currentDb = dir + "/" + dbName + DBConstant::SQLITE_DB_EXTENSION; + CipherType cipherType; + CipherPassword dstPasswd; + properties.GetPassword(cipherType, dstPasswd); + return SQLiteUtils::ExportDatabase(currentDb, cipherType, passwd, dstDb, dstPasswd); +} + +int SQLiteLocalKvDB::RemoveKvDB(const KvDBProperties &properties) +{ + // Only care the data directory and the db name. + std::string storeOnlyDir; + std::string storeDir; + GenericKvDB::GetStoreDirectory(properties, KvDBProperties::LOCAL_TYPE, storeDir, storeOnlyDir); + int dbType = properties.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + return KvDBUtils::RemoveKvDB(storeDir, storeOnlyDir, KvDBProperties::GetStoreSubDirectory(dbType)); +} + +int SQLiteLocalKvDB::GetKvDBSize(const KvDBProperties &properties, uint64_t &size) const +{ + std::string storeOnlyDir; + std::string storeDir; + GenericKvDB::GetStoreDirectory(properties, KvDBProperties::LOCAL_TYPE, storeDir, storeOnlyDir); + int dbType = properties.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + return KvDBUtils::GetKvDbSize(storeDir, storeOnlyDir, KvDBProperties::GetStoreSubDirectory(dbType), size); +} + +int SQLiteLocalKvDB::CheckVersionAndUpgradeIfNeed(const OpenDbProperties &openProp) +{ + if (!isAutonomicUpgradeEnable_) { + return E_OK; + } + int dbVersion = 0; + int errCode = SQLiteUtils::GetVersion(openProp, dbVersion); + if (errCode != E_OK) { + LOGE("[SqlLocalDb][CheckUpgrade] GetVersion fail, errCode=%d.", errCode); + return errCode; + } + LOGD("[SqlLocalDb][CheckUpgrade] DbFile Version=%d, CurVersion=%d.", dbVersion, LOCAL_STORE_VERSION_CURRENT); + if (dbVersion > LOCAL_STORE_VERSION_CURRENT) { + return -E_VERSION_NOT_SUPPORT; + } + // For version equal or less LOCAL_STORE_VERSION_CURRENT except zero, we can do nothing currently + if (dbVersion != 0) { + return E_OK; + } + errCode = SQLiteUtils::SetUserVer(openProp, LOCAL_STORE_VERSION_CURRENT); + if (errCode != E_OK) { + LOGE("[SqlLocalDb][CheckUpgrade] SetUserVer fail, errCode=%d.", errCode); + return errCode; + } + return E_OK; +} + +DEFINE_OBJECT_TAG_FACILITIES(SQLiteLocalKvDB) +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb.h b/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb.h new file mode 100644 index 00000000..631497a2 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_LOCAL_KV_DB_H +#define SQLITE_LOCAL_KV_DB_H + +#include +#include + +#include "local_kvdb.h" +#include "sqlite_local_storage_executor.h" +#include "sqlite_storage_engine.h" + +namespace DistributedDB { +class SQLiteLocalKvDB final : public LocalKvDB { +public: + SQLiteLocalKvDB(); + ~SQLiteLocalKvDB() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteLocalKvDB); + + // Save the option and uri for sqlite + int Open(const KvDBProperties &kvDBProp) override; + + // Create a connection object. + GenericKvDBConnection *NewConnection(int &errCode) override; + + // Invoked automatically when connection count is zero + void Close() override; + + int Rekey(const CipherPassword &passwd) override; + + int Export(const std::string &filePath, const CipherPassword &passwd) override; + + int Import(const std::string &filePath, const CipherPassword &passwd) override; + + int RunExportLogic(CipherType type, const CipherPassword &passwd, const std::string &newDbName); + + int RunRekeyLogic(CipherType type, const CipherPassword &passwd); + + SQLiteLocalStorageExecutor *GetHandle(bool isWrite, int &errCode, + OperatePerm perm = OperatePerm::NORMAL_PERM) const; + + void ReleaseHandle(SQLiteLocalStorageExecutor *&handle) const; + + int GetVersion(const KvDBProperties &kvDBProp, int &version, bool &isDbExisted) const; + + int SetVersion(const KvDBProperties &kvDBProp, int version); + + const KvDBProperties &GetDbProperties() const; + + KvDBProperties &GetDbPropertyForUpdate(); + + static int BackupCurrentDatabase(const KvDBProperties &properties, const std::string &dir); + + static int ImportDatabase(const KvDBProperties &properties, const std::string &dir, const CipherPassword &passwd); + + int RemoveKvDB(const KvDBProperties &properties) override; + + int GetKvDBSize(const KvDBProperties &properties, uint64_t &size) const override; + + int InitDatabaseContext(const KvDBProperties &kvDBProp); + + void EnableAutonomicUpgrade() override; + +private: + int InitStorageEngine(const KvDBProperties &kvDBProp); + + void InitDataBaseOption(const KvDBProperties &kvDBProp, OpenDbProperties &option) const; + + int CheckVersionAndUpgradeIfNeed(const OpenDbProperties &openProp); + + DECLARE_OBJECT_TAG(SQLiteLocalKvDB); + + bool isAutonomicUpgradeEnable_ = false; + SQLiteStorageEngine *storageEngine_; +}; +} // namespace DistributedDB + +#endif // SQLITE_LOCAL_KV_DB_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_connection.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_connection.cpp new file mode 100644 index 00000000..4379fa68 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_connection.cpp @@ -0,0 +1,498 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_local_kvdb_connection.h" + +#include + +#include "log_print.h" +#include "db_constant.h" +#include "sqlite_utils.h" +#include "sqlite_local_kvdb.h" +#include "sqlite_local_kvdb_snapshot.h" +#include "kvdb_commit_notify_filterable_data.h" +#include "sqlite_local_storage_executor.h" + +namespace DistributedDB { +SQLiteLocalKvDBConnection::SQLiteLocalKvDBConnection(SQLiteLocalKvDB *kvDB) + : GenericKvDBConnection(kvDB), + writeHandle_(nullptr) +{} + +SQLiteLocalKvDBConnection::~SQLiteLocalKvDBConnection() +{} + +int SQLiteLocalKvDBConnection::Get(const IOption &option, const Key &key, Value &value) const +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + if (key.empty() || key.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + { + std::lock_guard lock(transactionMutex_); + if (writeHandle_ != nullptr) { + return writeHandle_->Get(key, value); + } + } + int errCode = E_OK; + SQLiteLocalStorageExecutor *handle = GetDB()->GetHandle(false, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->Get(key, value); + GetDB()->ReleaseHandle(handle); + return errCode; +} + +int SQLiteLocalKvDBConnection::Put(const IOption &option, const Key &key, const Value &value) +{ + int errCode = CheckDataStatus(key, value, false); + if (errCode != E_OK) { + return errCode; + } + std::lock_guard lock(transactionMutex_); + bool isAuto = false; + errCode = StartTransactionInner(isAuto); + if (errCode != E_OK) { + LOGE("StartTransaction failed when Put error:%d", errCode); + return errCode; + } + + errCode = writeHandle_->Put(key, value); + if (errCode != E_OK) { + if (isAuto) { + int errCodeRollBack = RollBackInner(); + LOGI("Put failed,need rollback! errCode:[%d]", errCodeRollBack); + } + return errCode; + } + if (isAuto) { + errCode = CommitInner(); + if (errCode != E_OK) { + LOGE("CommitTransaction failed when Put error:%d", errCode); + return errCode; + } + } + + return errCode; +} + +int SQLiteLocalKvDBConnection::Delete(const IOption &option, const Key &key) +{ + int errCode = CheckDataStatus(key, {}, true); + if (errCode != E_OK) { + return errCode; + } + std::lock_guard lock(transactionMutex_); + bool isAuto = false; + errCode = StartTransactionInner(isAuto); + if (errCode != E_OK) { + LOGE("StartTransaction failed when Delete error:%d", errCode); + return errCode; + } + + errCode = writeHandle_->Delete(key); + if (errCode != E_OK) { + if (isAuto) { + int errCodeRollBack = RollBackInner(); + LOGI("Delete failed, need rollback! errcode:[%d]", errCodeRollBack); + } + return errCode; + } + + if (isAuto) { + errCode = CommitInner(); + if (errCode != E_OK) { + LOGE("CommitInner failed while delete:%d", errCode); + return errCode; + } + } + return E_OK; +} + +int SQLiteLocalKvDBConnection::Clear(const IOption &option) +{ + std::lock_guard lock(transactionMutex_); + bool isAuto = false; + int errCode = StartTransactionInner(isAuto); + if (errCode != E_OK) { + LOGE("StartTransaction failed when Clear error:%d", errCode); + return errCode; + } + + errCode = writeHandle_->Clear(); + if (errCode != E_OK) { + if (isAuto) { + int errCodeRollBack = RollBackInner(); + LOGI("Clear failed, need rollback! RollBack result is [%d]", errCodeRollBack); + } + return errCode; + } + + if (isAuto) { + errCode = CommitInner(); + if (errCode != E_OK) { + LOGE("CommitInner failed when Clear error:%d", errCode); + return errCode; + } + } + + return E_OK; +} + +int SQLiteLocalKvDBConnection::GetEntries(const IOption &option, const Key &keyPrefix, + std::vector &entries) const +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + if (keyPrefix.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + { + std::lock_guard lock(transactionMutex_); + if (writeHandle_ != nullptr) { + return writeHandle_->GetEntries(keyPrefix, entries); + } + } + int errCode = E_OK; + SQLiteLocalStorageExecutor *handle = GetDB()->GetHandle(false, errCode); + if (handle == nullptr) { + return errCode; + } + errCode = handle->GetEntries(keyPrefix, entries); + GetDB()->ReleaseHandle(handle); + return errCode; +} + +int SQLiteLocalKvDBConnection::PutBatch(const IOption &option, const std::vector &entries) +{ + if (entries.empty() || entries.size() > DBConstant::MAX_BATCH_SIZE) { + return -E_INVALID_ARGS; + } + for (const auto &item : entries) { + if (CheckDataStatus(item.key, item.value, false) != E_OK) { + return -E_INVALID_ARGS; + } + } + + bool isAuto = false; + std::lock_guard lock(transactionMutex_); + int errCode = StartTransactionInner(isAuto); + if (errCode != E_OK) { + LOGE("StartTransaction failed when PutBatch error:%d", errCode); + return errCode; + } + + for (const auto &entry : entries) { + // first argument is key and second argument is value. + errCode = writeHandle_->Put(entry.key, entry.value); + if (errCode != E_OK) { + if (isAuto) { + int errCodeRollBack = RollBackInner(); + LOGI("PutBatch failed,need rollback! RollBack result is %d", errCodeRollBack); + } + return errCode; + } + } + + if (isAuto) { + errCode = CommitInner(); + if (errCode != E_OK) { + LOGE("CommitTransaction failed when PutBatch error:%d", errCode); + return errCode; + } + } + + return E_OK; +} + +int SQLiteLocalKvDBConnection::DeleteBatch(const IOption &option, const std::vector &keys) +{ + if (keys.empty() || keys.size() > DBConstant::MAX_BATCH_SIZE) { + LOGE("[Local]DeleteBatch size[%zu]!", keys.size()); + return -E_INVALID_ARGS; + } + for (const auto &item : keys) { + if (item.empty() || item.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + } + + bool isAuto = false; + std::lock_guard lock(transactionMutex_); + int errCode = StartTransactionInner(isAuto); + if (errCode != E_OK) { + LOGE("StartTransaction failed when DeleteBatch error:%d", errCode); + return errCode; + } + + errCode = writeHandle_->DeleteBatch(keys); + + if (isAuto) { + if (errCode == E_OK) { + errCode = CommitInner(); + if (errCode != E_OK) { + LOGE("CommitTransaction failed when DeleteBatch error:%d", errCode); + return errCode; + } + } else { + int errCodeRollBack = RollBackInner(); + LOGI("DeleteBatchm need rollback! RollBack result is [%d]", errCodeRollBack); + return errCode; + } + } + + return errCode; +} + +// when GetSnapshot successfully, you must delete snapshot by ReleaseSnapshot +int SQLiteLocalKvDBConnection::GetSnapshot(IKvDBSnapshot *&snapshot) const +{ + if (kvDB_ == nullptr) { + snapshot = nullptr; + return -E_INVALID_DB; + } + + int errCode = E_OK; + IKvDBConnection *newConnect = kvDB_->GetDBConnection(errCode); + if (errCode != E_OK) { + LOGE("failed to get the new connection"); + return errCode; + } + + SQLiteLocalKvDBSnapshot *dbSnapshot = new (std::nothrow) SQLiteLocalKvDBSnapshot(newConnect); + if (dbSnapshot == nullptr) { + newConnect->Close(); + delete newConnect; + return -E_OUT_OF_MEMORY; + } + + snapshot = dbSnapshot; + { + std::lock_guard lock(snapshotMutex_); + snapshots_.insert(dbSnapshot); + } + + return E_OK; +} + +void SQLiteLocalKvDBConnection::ReleaseSnapshot(IKvDBSnapshot *&snapshot) +{ + if (snapshot != nullptr && kvDB_ != nullptr) { + std::lock_guard lock(snapshotMutex_); + SQLiteLocalKvDBSnapshot *sqliteSnapshot = static_cast(snapshot); + sqliteSnapshot->Close(); + snapshots_.erase(snapshot); + delete snapshot; + snapshot = nullptr; + } +} + +int SQLiteLocalKvDBConnection::StartTransaction() +{ + std::lock_guard lock(transactionMutex_); + if (writeHandle_ != nullptr) { + return -E_TRANSACT_STATE; + } + bool isAuto = false; + return StartTransactionInner(isAuto); +} + +int SQLiteLocalKvDBConnection::Commit() +{ + std::lock_guard lock(transactionMutex_); + return CommitInner(); +} + +int SQLiteLocalKvDBConnection::RollBack() +{ + std::lock_guard lock(transactionMutex_); + return RollBackInner(); +} + +bool SQLiteLocalKvDBConnection::IsTransactionStarted() const +{ + std::lock_guard lock(transactionMutex_); + if (writeHandle_ != nullptr) { + return true; + } + return false; +} + +int SQLiteLocalKvDBConnection::PreClose() +{ + { + std::lock_guard snapshotLock(snapshotMutex_); + if (snapshots_.size() != 0) { + LOGE("Close failed, the connection have unreleased snapshot."); + return -E_BUSY; + } + } + std::lock_guard transactionLock(transactionMutex_); + if (writeHandle_ != nullptr) { + writeHandle_->RollBack(); + GetDB()->ReleaseHandle(writeHandle_); + } + return E_OK; +} + +int SQLiteLocalKvDBConnection::TranslateObserverModeToEventTypes(unsigned mode, + std::list &eventTypes) const +{ + return E_OK; +} + +int SQLiteLocalKvDBConnection::StartTransactionInner(bool &isAuto) +{ + // if the transaction has been started, writeHandle wouldn't be nullptr. + if (writeHandle_ != nullptr) { + return E_OK; + } + + if (kvDB_ == nullptr) { + LOGE("local database is null"); + return -E_INVALID_DB; + } + + int errCode = E_OK; + SQLiteLocalStorageExecutor *handle = GetDB()->GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->StartTransaction(); + if (errCode != E_OK) { + GetDB()->ReleaseHandle(handle); + return errCode; + } + writeHandle_ = handle; + // only the transaction has not been started before, set the flag to true. + // the manual operation would ignore the flag. + isAuto = true; + return E_OK; +} + +int SQLiteLocalKvDBConnection::CommitInner() +{ + if (writeHandle_ == nullptr) { + LOGE("local database is null or the transaction has not been started"); + return -E_INVALID_DB; + } + + int errCode = writeHandle_->Commit(); + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + GetDB()->ReleaseHandle(writeHandle_); + return errCode; +} + +int SQLiteLocalKvDBConnection::RollBackInner() +{ + if (writeHandle_ == nullptr) { + LOGE("Invalid handle for rollback or the transaction has not been started."); + return -E_INVALID_DB; + } + + int errCode = writeHandle_->RollBack(); + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + GetDB()->ReleaseHandle(writeHandle_); + return errCode; +} + +int SQLiteLocalKvDBConnection::Rekey(const CipherPassword &passwd) +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + std::lock_guard lock(transactionMutex_); + // return BUSY if in transaction + if (writeHandle_ != nullptr) { + LOGE("Transaction exists for rekey failed"); + return -E_BUSY; + } + // Check the connection number. + int errCode = kvDB_->TryToDisableConnection(OperatePerm::REKEY_MONOPOLIZE_PERM); + if (errCode != E_OK) { + return errCode; + } + + // Check the observer. + errCode = GenericKvDBConnection::PreCheckExclusiveStatus(); + if (errCode != E_OK) { + kvDB_->ReEnableConnection(OperatePerm::REKEY_MONOPOLIZE_PERM); + return errCode; + } + // If only have one connection, just have the transactionMutex_; + // It means there would not be another operation on this connection. + errCode = kvDB_->Rekey(passwd); + + GenericKvDBConnection::ResetExclusiveStatus(); + kvDB_->ReEnableConnection(OperatePerm::REKEY_MONOPOLIZE_PERM); + return errCode; +} + +int SQLiteLocalKvDBConnection::Export(const std::string &filePath, const CipherPassword &passwd) +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + return kvDB_->Export(filePath, passwd); +} + +int SQLiteLocalKvDBConnection::Import(const std::string &filePath, const CipherPassword &passwd) +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + { + std::lock_guard lock(transactionMutex_); + // return BUSY if in transaction + if (writeHandle_ != nullptr) { + LOGE("Transaction exists for rekey failed"); + return -E_BUSY; + } + } + std::lock_guard importLock(importMutex_); + int errCode = kvDB_->TryToDisableConnection(OperatePerm::IMPORT_MONOPOLIZE_PERM); + if (errCode != E_OK) { + return errCode; + } + + errCode = GenericKvDBConnection::PreCheckExclusiveStatus(); + if (errCode != E_OK) { + kvDB_->ReEnableConnection(OperatePerm::IMPORT_MONOPOLIZE_PERM); + return errCode; + } + errCode = kvDB_->Import(filePath, passwd); + GenericKvDBConnection::ResetExclusiveStatus(); + kvDB_->ReEnableConnection(OperatePerm::IMPORT_MONOPOLIZE_PERM); + return errCode; +} + +int SQLiteLocalKvDBConnection::CheckDataStatus(const Key &key, const Value &value, bool isDeleted) const +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + return static_cast(kvDB_)->CheckDataStatus(key, value, isDeleted); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_connection.h b/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_connection.h new file mode 100644 index 00000000..f4efd05b --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_connection.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_LOCAL_KV_DB_CONNECTION_H +#define SQLITE_LOCAL_KV_DB_CONNECTION_H + +#include +#include + +#include "db_errno.h" +#include "kvdb_properties.h" +#include "generic_kvdb_connection.h" +#include "sqlite_local_storage_executor.h" + +namespace DistributedDB { +class SQLiteLocalKvDB; + +class SQLiteLocalKvDBConnection : public GenericKvDBConnection { +public: + explicit SQLiteLocalKvDBConnection(SQLiteLocalKvDB *kvDB); + ~SQLiteLocalKvDBConnection() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteLocalKvDBConnection); + + // Get the value from the sqlite database + int Get(const IOption &option, const Key &key, Value &value) const override; + + // Put the value to the sqlite database + int Put(const IOption &option, const Key &key, const Value &value) override; + + // Delete the value from the sqlite database + int Delete(const IOption &option, const Key &key) override; + + // Clear all the data from the sqlite database + int Clear(const IOption &option) override; + + // Get all the data which have the prefix key from the sqlite database + int GetEntries(const IOption &option, const Key &keyPrefix, std::vector &entries) const override; + + // Put the batch data to the sqlite database + int PutBatch(const IOption &option, const std::vector &entries) override; + + // Delete the batch data from the sqlite database according to the key from the set + int DeleteBatch(const IOption &option, const std::vector &keys) override; + + // Get the snapshot + int GetSnapshot(IKvDBSnapshot *&snapshot) const override; + + // Release the snapshot + void ReleaseSnapshot(IKvDBSnapshot *&snapshot) override; + + // Next step interface + // Start the transaction + int StartTransaction() override; + + // Commit the transaction + int Commit() override; + + // Roll back the transaction + int RollBack() override; + + // Check if the transaction already started manually + bool IsTransactionStarted() const override; + + // Called when close the connection + int PreClose() override; + + // Parse event types(from observer mode). + int TranslateObserverModeToEventTypes(unsigned mode, std::list &eventTypes) const override; + + int Rekey(const CipherPassword &passwd) override; + + int Export(const std::string &filePath, const CipherPassword &passwd) override; + + int Import(const std::string &filePath, const CipherPassword &passwd) override; + +private: + // Start the transaction + int StartTransactionInner(bool &isAuto); + + // Commit the transaction + int CommitInner(); + + // Roll back the transaction + int RollBackInner(); + + int CheckDataStatus(const Key &key, const Value &value, bool isDeleted) const; + + SQLiteLocalStorageExecutor *writeHandle_; // only existed while in transaction. + + mutable std::mutex transactionMutex_; + mutable std::set snapshots_; + mutable std::mutex snapshotMutex_; + std::mutex importMutex_; +}; +}; // namespace DistributedDB + +#endif // SQLITE_LOCAL_KV_DB_CONNECTION_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_snapshot.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_snapshot.cpp new file mode 100644 index 00000000..430ebd60 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_snapshot.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_local_kvdb_snapshot.h" +#include "sqlite_local_kvdb_connection.h" + +namespace DistributedDB { +SQLiteLocalKvDBSnapshot::SQLiteLocalKvDBSnapshot(IKvDBConnection *connect) + : connect_(connect) +{} + +SQLiteLocalKvDBSnapshot::~SQLiteLocalKvDBSnapshot() +{ + connect_ = nullptr; +} + +int SQLiteLocalKvDBSnapshot::Get(const Key &key, Value &value) const +{ + if (connect_ == nullptr) { + return -E_INVALID_DB; + } + IOption option; + return connect_->Get(option, key, value); +} + +int SQLiteLocalKvDBSnapshot::GetEntries(const Key &keyPrefix, std::vector &entries) const +{ + if (connect_ == nullptr) { + return -E_INVALID_DB; + } + IOption option; + return connect_->GetEntries(option, keyPrefix, entries); +} + +void SQLiteLocalKvDBSnapshot::Close() +{ + if (connect_ != nullptr) { + connect_->Close(); + connect_ = nullptr; + } +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_snapshot.h b/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_snapshot.h new file mode 100644 index 00000000..cc8b7a1c --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_local_kvdb_snapshot.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_LOCAL_KV_DB_SNAP_SHOT_H +#define SQLITE_LOCAL_KV_DB_SNAP_SHOT_H + +#include + +#include "macro_utils.h" +#include "db_errno.h" +#include "db_types.h" +#include "ikvdb_snapshot.h" +#include "sqlite_local_kvdb_connection.h" + +namespace DistributedDB { +class SQLiteLocalKvDBSnapshot : public IKvDBSnapshot { +public: + explicit SQLiteLocalKvDBSnapshot(IKvDBConnection *connect); + ~SQLiteLocalKvDBSnapshot(); + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteLocalKvDBSnapshot); + + // Get the value of the key + int Get(const Key &key, Value &value) const override; + + // Get the entries of the key set + int GetEntries(const Key &keyPrefix, std::vector &entries) const override; + + void Close(); + +private: + IKvDBConnection *connect_; +}; +} // namespace DistributedDB + +#endif // SQLITE_LOCAL_KV_DB_SNAP_SHOT_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_local_storage_engine.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_local_storage_engine.cpp new file mode 100644 index 00000000..87288785 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_local_storage_engine.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_local_storage_engine.h" +#include "sqlite_local_storage_executor.h" + +namespace DistributedDB { +SQLiteLocalStorageEngine::SQLiteLocalStorageEngine() +{} + +SQLiteLocalStorageEngine::~SQLiteLocalStorageEngine() +{} + +StorageExecutor *SQLiteLocalStorageEngine::NewSQLiteStorageExecutor(sqlite3 *dbHandle, bool isWrite, bool isMemDb) +{ + return new (std::nothrow) SQLiteLocalStorageExecutor(dbHandle, isWrite, isMemDb); +} +} diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_local_storage_engine.h b/mock/distributeddb/storage/src/sqlite/sqlite_local_storage_engine.h new file mode 100644 index 00000000..d36ea4b1 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_local_storage_engine.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_LOCAL_STORAGE_ENGINE_H +#define SQLITE_LOCAL_STORAGE_ENGINE_H + +#include "macro_utils.h" +#include "sqlite_storage_engine.h" + +namespace DistributedDB { +class SQLiteLocalStorageEngine : public SQLiteStorageEngine { +public: + SQLiteLocalStorageEngine(); + ~SQLiteLocalStorageEngine() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteLocalStorageEngine); + +protected: + StorageExecutor *NewSQLiteStorageExecutor(sqlite3 *dbHandle, bool isWrite, bool isMemDb) override; +}; +} // namespace DistributedDB + +#endif // SQLITE_DB_HANDLE_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_local_storage_executor.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_local_storage_executor.cpp new file mode 100644 index 00000000..c6bbc764 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_local_storage_executor.cpp @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_local_storage_executor.h" + +#include "log_print.h" +#include "db_errno.h" +#include "sqlite_utils.h" + +namespace DistributedDB { +namespace { + const std::string CLEAR_SQL = "DELETE FROM data;"; + const std::string SELECT_SQL = "SELECT value FROM data WHERE key=?;"; + const std::string SELECT_BATCH_SQL = + "SELECT key, value FROM data WHERE key>=? AND key<=? ORDER BY key ASC;"; + const std::string INSERT_SQL = "INSERT OR REPLACE INTO data VALUES(?,?);"; + const std::string DELETE_SQL = "DELETE FROM data WHERE key=?;"; + + const int BIND_KEY_INDEX = 1; + const int BIND_VAL_INDEX = 2; + + const int SELECT_BIND_KEY_INDEX = 1; // index of the binding key index for select one entry. + + const int SELECT_RESULT_KEY_INDEX = 0; + const int SELECT_RESULT_VAL_INDEX = 1; +} + +SQLiteLocalStorageExecutor::SQLiteLocalStorageExecutor(sqlite3 *dbHandle, bool writable, bool isMemDb) + : SQLiteStorageExecutor(dbHandle, writable, isMemDb) +{} + +SQLiteLocalStorageExecutor::~SQLiteLocalStorageExecutor() +{} + +int SQLiteLocalStorageExecutor::Get(const Key &key, Value &value) const +{ + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, SELECT_SQL, statement); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, SELECT_BIND_KEY_INDEX, key, false); + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = -E_NOT_FOUND; + goto END; + } else if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + goto END; + } + + errCode = SQLiteUtils::GetColumnBlobValue(statement, 0, value); + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteLocalStorageExecutor::Clear() +{ + int errCode = SQLiteUtils::ExecuteRawSQL(dbHandle_, CLEAR_SQL); + return CheckCorruptedStatus(errCode); +} + +int SQLiteLocalStorageExecutor::GetEntries(const Key &keyPrefix, + std::vector &entries) const +{ + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, SELECT_BATCH_SQL, statement); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + + Entry entry; + errCode = SQLiteUtils::BindPrefixKey(statement, SELECT_BIND_KEY_INDEX, keyPrefix); // first arg is prefix key + if (errCode != E_OK) { + goto END; + } + + do { + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + errCode = SQLiteUtils::GetColumnBlobValue(statement, SELECT_RESULT_KEY_INDEX, entry.key); + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::GetColumnBlobValue(statement, SELECT_RESULT_VAL_INDEX, entry.value); + if (errCode != E_OK) { + goto END; + } + + entries.push_back(std::move(entry)); + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + break; + } else { + LOGE("SQLite step failed:%d", errCode); + goto END; + } + } while (true); + + // if select no result, return the -E_NOT_FOUND. + if (entries.empty()) { + errCode = -E_NOT_FOUND; + } else { + errCode = E_OK; + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteLocalStorageExecutor::PutBatch(const std::vector &entries) +{ + if (entries.empty()) { + return -E_INVALID_ARGS; + } + + for (const auto &entry : entries) { + // first argument is key and second argument is value. + int errCode = Put(entry.key, entry.value); + if (errCode != E_OK) { + LOGE("PutBatch failed: %d", errCode); + return CheckCorruptedStatus(errCode); + } + } + + return E_OK; +} + +int SQLiteLocalStorageExecutor::DeleteBatch(const std::vector &keys) +{ + if (keys.empty()) { + return -E_INVALID_ARGS; + } + + bool isAllNoExisted = true; + + for (const auto &key : keys) { + int errCode = Delete(key); + if (errCode != E_OK && errCode != -E_NOT_FOUND) { + return CheckCorruptedStatus(errCode); + } + if (errCode != -E_NOT_FOUND && isAllNoExisted == true) { + isAllNoExisted = false; + } + } + + if (isAllNoExisted) { + return -E_NOT_FOUND; + } + + return E_OK; +} + +int SQLiteLocalStorageExecutor::StartTransaction() +{ + int errCode = SQLiteUtils::BeginTransaction(dbHandle_); + return CheckCorruptedStatus(errCode); +} + +int SQLiteLocalStorageExecutor::Commit() +{ + int errCode = SQLiteUtils::CommitTransaction(dbHandle_); + return CheckCorruptedStatus(errCode); +} + +int SQLiteLocalStorageExecutor::RollBack() +{ + int errCode = SQLiteUtils::RollbackTransaction(dbHandle_); + return CheckCorruptedStatus(errCode); +} + +int SQLiteLocalStorageExecutor::Put(const Key &key, const Value &value) +{ + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, INSERT_SQL, statement); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_KEY_INDEX, key, false); + if (errCode != E_OK) { + LOGE("Failed to bind the key."); + goto END; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_VAL_INDEX, value, true); + if (errCode != E_OK) { + LOGE("Failed to bind the value"); + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } else { + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteLocalStorageExecutor::Delete(const Key &key) +{ + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, DELETE_SQL, statement); + if (errCode != E_OK) { + LOGE("Failed to get the delete statememt."); + return CheckCorruptedStatus(errCode); + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_KEY_INDEX, key, false); + if (errCode != E_OK) { + LOGE("Bind key failed"); + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + LOGE("Delete step error:%d", errCode); + } else { + if (sqlite3_changes(dbHandle_) > 0) { + errCode = E_OK; + } else { + errCode = -E_NOT_FOUND; + } + } +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_local_storage_executor.h b/mock/distributeddb/storage/src/sqlite/sqlite_local_storage_executor.h new file mode 100644 index 00000000..a1e6b5d2 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_local_storage_executor.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_LOCAL_DB_HANDLE_H +#define SQLITE_LOCAL_DB_HANDLE_H + +#include "sqlite_import.h" +#include "macro_utils.h" +#include "db_types.h" +#include "sqlite_storage_executor.h" + +namespace DistributedDB { +class SQLiteLocalStorageExecutor : public SQLiteStorageExecutor { +public: + SQLiteLocalStorageExecutor(sqlite3 *dbHandle, bool writable, bool isMemDb); + ~SQLiteLocalStorageExecutor() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteLocalStorageExecutor); + + int Get(const Key &key, Value &value) const; + + // Put the value to the sqlite database + int Put(const Key &key, const Value &value); + + // Delete the value from the sqlite database + int Delete(const Key &key); + + // Clear all the data from the sqlite database + int Clear(); + + // Get all the data which have the prefix key from the sqlite database + int GetEntries(const Key &keyPrefix, std::vector &entries) const; + + // Put the batch data to the sqlite database + int PutBatch(const std::vector &entries); + + // Delete the batch data from the sqlite database according to the key from the set + int DeleteBatch(const std::vector &keys); + + // Next step interface + // Start the transaction + int StartTransaction(); + + // Commit the transaction + int Commit(); + + // Roll back the transaction + int RollBack(); + +private: + // Put the value to the sqlite database, used by Put &PutBach + int PutInner(const Key &key, const Value &value); + + // Delete the value from the sqlite database, used by Delete &DeleteBach + int DeleteInner(const Key &key); + + // Start the transaction + int StartTransactionInner(bool &isAuto); + + // Commit the transaction + int CommitInner(); + + // Roll back the transaction + int RollBackInner(); +}; +} // namespace DistributedDB + +#endif // SQLITE_DB_HANDLE_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_data_storage.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_data_storage.cpp new file mode 100644 index 00000000..6255829e --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_data_storage.cpp @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "sqlite_multi_ver_data_storage.h" + +#include +#include +#include +#include + +#include "db_constant.h" +#include "db_types.h" +#include "log_print.h" +#include "sqlite_utils.h" +#include "multi_ver_kv_entry.h" +#include "multi_ver_value_object.h" +#include "value_hash_calc.h" +#include "db_common.h" +#include "multi_ver_natural_store.h" +#include "platform_specific.h" + +namespace DistributedDB { +namespace { + const std::string CREATE_TABLE_SQL = + "CREATE TABLE IF NOT EXISTS version_data(key BLOB, value BLOB, oper_flag INTEGER, version INTEGER, " \ + "timestamp INTEGER, ori_timestamp INTEGER, hash_key BLOB, " \ + "PRIMARY key(hash_key, version));" \ + "CREATE INDEX IF NOT EXISTS version_index ON version_data (version);" \ + "CREATE INDEX IF NOT EXISTS flag_index ON version_data (oper_flag);"; + + const std::size_t MAX_READ_CONNECT_NUM = 16; +} + +SQLiteMultiVerDataStorage::SQLiteMultiVerDataStorage() + : writeTransaction_(nullptr), + writeTransactionUsed_(false) +{} + +SQLiteMultiVerDataStorage::~SQLiteMultiVerDataStorage() +{ + writeTransaction_ = nullptr; +} + +int SQLiteMultiVerDataStorage::CheckVersion(const Property &property, bool &isDbExist) const +{ + int dbVer = 0; + int errCode = GetVersion(property, dbVer, isDbExist); + if (errCode != E_OK) { + LOGE("[DataStorage][CheckVer] GetVersion failed, errCode=%d.", errCode); + return errCode; + } + if (!isDbExist) { + return E_OK; + } + LOGD("[DataStorage][CheckVer] DbVersion=%d, CurVersion=%d.", dbVer, MULTI_VER_DATA_STORAGE_VERSION_CURRENT); + if (dbVer > MULTI_VER_DATA_STORAGE_VERSION_CURRENT) { + LOGE("[DataStorage][CheckVer] Version Not Support!"); + return -E_VERSION_NOT_SUPPORT; + } + return E_OK; +} + +int SQLiteMultiVerDataStorage::GetVersion(const Property &property, int &version, bool &isDbExisted) const +{ + std::string uri = property.path + "/" + property.identifierName + "/" + DBConstant::MULTI_SUB_DIR + "/" + + DBConstant::MULTI_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + isDbExisted = OS::CheckPathExistence(uri); + if (isDbExisted) { + std::vector tableVect; + OpenDbProperties option = {uri, property.isNeedCreate, false, tableVect, property.cipherType, property.passwd}; + return SQLiteUtils::GetVersion(option, version); + } + return E_OK; +} + +int SQLiteMultiVerDataStorage::Open(const Property &property) +{ + // only set the property para or create the database and the table? + // whether create the transactions. + property_ = property; + uri_ = property.path + "/" + property_.identifierName + "/" + DBConstant::MULTI_SUB_DIR + "/" + + DBConstant::MULTI_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + std::vector tableVect; + tableVect.push_back(CREATE_TABLE_SQL); + + OpenDbProperties option = {uri_, property.isNeedCreate, false, tableVect, property.cipherType, property.passwd}; + sqlite3 *db = nullptr; + int errCode = SQLiteUtils::OpenDatabase(option, db); + if (errCode != E_OK) { + LOGE("Open the multi ver data store error:%d", errCode); + goto END; + } + + // Version had been check before open and currently no upgrade to do + errCode = SQLiteUtils::SetUserVer(option, MULTI_VER_DATA_STORAGE_VERSION_CURRENT); + if (errCode != E_OK) { + LOGE("Init the version multi ver store error:%d", errCode); + } + +END: + if (db != nullptr) { + (void)sqlite3_close_v2(db); + db = nullptr; + } + + return errCode; +} + +// start one write transaction +// do the transaction initialization and call the start transaction; +int SQLiteMultiVerDataStorage::StartWrite(KvDataType dataType, IKvDBMultiVerTransaction *&transaction) +{ + (void)dataType; + std::unique_lock lock(transactionMutex_); + // if same thread. return nullptr. + if (std::this_thread::get_id() == writeHolderId_) { + transaction = nullptr; + return -E_BUSY; + } + + if (writeTransaction_ != nullptr) { + writeCondition_.wait(lock, [&] { + return !writeTransactionUsed_; + }); + + writeTransactionUsed_ = true; + writeHolderId_ = std::this_thread::get_id(); + transaction = writeTransaction_; + return E_OK; + } + + transaction = new (std::nothrow) SQLiteMultiVerTransaction(); + if (transaction == nullptr) { + LOGE("Failed to create the SQLite write transaction"); + return -E_OUT_OF_MEMORY; + } + + // initialize the transaction. + int errCode = static_cast(transaction)->Initialize(uri_, false, + property_.cipherType, property_.passwd); + if (errCode != E_OK) { + LOGE("Init write transaction failed:%d", errCode); + delete transaction; + transaction = nullptr; + return errCode; + } + + writeTransaction_ = static_cast(transaction); + writeTransactionUsed_ = true; + writeHolderId_ = std::this_thread::get_id(); + return E_OK; +} + +// do the first step of commit record. +// commit the transaction, and avoid other operation reading the new data. +int SQLiteMultiVerDataStorage::CommitWritePhaseOne(IKvDBMultiVerTransaction *transaction, + const UpdateVerTimestamp &multiVerTimestamp) +{ + if (transaction == nullptr) { + LOGE("Invalid transaction!"); + return -E_INVALID_DB; + } + // Get versionInfo from transaction. + // Call the commit of the sqlite. + Version versionInfo = transaction->GetVersion(); + + if (multiVerTimestamp.isNeedUpdate) { + (void)transaction->UpdateTimestampByVersion(versionInfo, multiVerTimestamp.timestamp); + } + + int errCode = transaction->CommitTransaction(); + if (errCode != E_OK) { + auto sqliteTransaction = static_cast(transaction); + if (transaction != nullptr) { + (void)sqliteTransaction->Reset(property_.cipherType, property_.passwd); + } + LOGE("SQLite commit the transaction failed:%d", errCode); + } + return errCode; +} + +// when the commit history update failed, need delete the commit +int SQLiteMultiVerDataStorage::RollbackWritePhaseOne(IKvDBMultiVerTransaction *transaction, + const Version &versionInfo) +{ + if (transaction == nullptr) { + LOGE("Invalid transaction!"); + return -E_INVALID_DB; + } + + SQLiteMultiVerTransaction *sqliteTransaction = static_cast(transaction); + sqliteTransaction->StartTransaction(); + int errCode = sqliteTransaction->ClearEntriesByVersion(versionInfo); + if (errCode == E_OK) { + sqliteTransaction->CommitTransaction(); + } else { + sqliteTransaction->RollBackTransaction(); + } + + return errCode; +} + +// Rollback the write transaction. +int SQLiteMultiVerDataStorage::RollbackWrite(IKvDBMultiVerTransaction *transaction) +{ + if (transaction == nullptr) { + LOGE("Invalid transaction!"); + return -E_INVALID_DB; + } + // call the rollback of the sqlite. + int errCode = static_cast(transaction)->RollBackTransaction(); + if (errCode != E_OK) { + (void)static_cast(transaction)->Reset(property_.cipherType, property_.passwd); + LOGE("SQLite rollback failed:%d", errCode); + } + return errCode; +} + +// should update the flag indicated that other operating could read the new record. +void SQLiteMultiVerDataStorage::CommitWritePhaseTwo(IKvDBMultiVerTransaction *transaction) +{ + // just change the head version? + (void)transaction; +} + +// Get one start transaction. +IKvDBMultiVerTransaction *SQLiteMultiVerDataStorage::StartRead(KvDataType dataType, + const Version &versionInfo, int &errCode) +{ + (void)dataType; + std::unique_lock lock(transactionMutex_); + for (auto &iter : readTransactions_) { + if (iter.second) { + iter.second = false; + (iter.first)->SetVersion(versionInfo); + errCode = E_OK; + return iter.first; + } + } + // need wait. + if (readTransactions_.size() > MAX_READ_CONNECT_NUM) { + LOGE("Over the max transaction num"); + errCode = -E_BUSY; + return nullptr; + } + + IKvDBMultiVerTransaction *transaction = new (std::nothrow) SQLiteMultiVerTransaction; + if (transaction == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + errCode = static_cast(transaction)->Initialize(uri_, + true, property_.cipherType, property_.passwd); + if (errCode != E_OK) { + delete transaction; + transaction = nullptr; + return nullptr; + } + + transaction->SetVersion(versionInfo); + readTransactions_.insert(std::make_pair(transaction, false)); + return transaction; +} + +// Release the transaction created. +void SQLiteMultiVerDataStorage::ReleaseTransaction(IKvDBMultiVerTransaction *transaction) +{ + // whether need manage the transaction. + std::unique_lock lock(transactionMutex_); + if (transaction == nullptr) { + LOGE("Invalid transaction!"); + return; + } + + if (transaction == writeTransaction_) { + static_cast(writeTransaction_)->ResetVersion(); + writeTransactionUsed_ = false; + writeHolderId_ = std::thread::id(); + writeCondition_.notify_all(); + return; + } + + auto iter = readTransactions_.find(transaction); + if (iter != readTransactions_.end()) { + static_cast(iter->first)->ResetVersion(); + iter->second = true; + } + return; +} + +void SQLiteMultiVerDataStorage::Close() +{ + std::lock_guard lock(transactionMutex_); + // close all the transaction? + for (auto iter = readTransactions_.begin(); iter != readTransactions_.end(); iter++) { + if (iter->first != nullptr) { + delete iter->first; + } + } + readTransactions_.clear(); + + if (writeTransaction_ != nullptr) { + delete writeTransaction_; + writeTransaction_ = nullptr; + } +} + +int SQLiteMultiVerDataStorage::RunRekeyLogic(CipherType type, const CipherPassword &passwd) +{ + (void)type; + // openDatabase to get the sqlite3 pointer + std::vector tableVect; + tableVect.push_back(CREATE_TABLE_SQL); + sqlite3 *db = nullptr; + OpenDbProperties option = {uri_, property_.isNeedCreate, false, tableVect, property_.cipherType, property_.passwd}; + int errCode = SQLiteUtils::OpenDatabase(option, db); + if (errCode != E_OK) { + LOGE("Open db error:%d", errCode); + return errCode; + } + + // execute rekey + errCode = SQLiteUtils::Rekey(db, passwd); + if (errCode != E_OK) { + LOGE("multi ver data rekey failed:%d", errCode); + } + // close db + (void)sqlite3_close_v2(db); + db = nullptr; + + return errCode; +} + +int SQLiteMultiVerDataStorage::RunExportLogic(CipherType type, const CipherPassword &passwd, const std::string &dbDir) +{ + // openDatabase to get the sqlite3 pointer + std::vector tableVect; + sqlite3 *db = nullptr; + OpenDbProperties option = {uri_, true, false, tableVect, property_.cipherType, property_.passwd}; + int errCode = SQLiteUtils::OpenDatabase(option, db); + if (errCode != E_OK) { + LOGE("Open db error:%d", errCode); + return errCode; + } + + // execute export + std::string newDbName = dbDir + "/" + DBConstant::MULTI_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + errCode = SQLiteUtils::ExportDatabase(db, type, passwd, newDbName); + if (errCode != E_OK) { + LOGE("multi ver data export failed:%d", errCode); + } + // close db + (void)sqlite3_close_v2(db); + db = nullptr; + + return errCode; +} + +int SQLiteMultiVerDataStorage::BackupCurrentDatabase(const Property &property, const std::string &dir) +{ + std::string currentDb = property.path + "/" + property.identifierName + "/" + DBConstant::MULTI_SUB_DIR + "/" + + DBConstant::MULTI_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + std::string dstDb = dir + "/" + DBConstant::MULTI_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + int errCode = DBCommon::CopyFile(currentDb, dstDb); + if (errCode != E_OK) { + LOGE("Copy the local current db error:%d", errCode); + } + return errCode; +} + +int SQLiteMultiVerDataStorage::ImportDatabase(const Property &property, const std::string &dir, + const CipherPassword &passwd) +{ + std::string currentDb = property.path + "/" + property.identifierName + "/" + DBConstant::MULTI_SUB_DIR + "/" + + DBConstant::MULTI_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + std::string srcDb = dir + "/" + DBConstant::MULTI_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + int errCode = SQLiteUtils::ExportDatabase(srcDb, property.cipherType, passwd, currentDb, property.passwd); + if (errCode != E_OK) { + LOGE("import the multi ver data db error:%d", errCode); + } + return E_OK; +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_data_storage.h b/mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_data_storage.h new file mode 100644 index 00000000..d924196c --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_data_storage.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_KV_DB_MULTI_VER_DATA_STORAGE_H +#define SQLITE_KV_DB_MULTI_VER_DATA_STORAGE_H + +#ifndef OMIT_MULTI_VER +#include +#include +#include +#include +#include +#include + +#include "db_types.h" +#include "kvdb_properties.h" +#include "ikvdb_multi_ver_data_storage.h" +#include "macro_utils.h" +#include "multi_ver_value_object.h" +#include "generic_multi_ver_kv_entry.h" +#include "sqlite_multi_ver_transaction.h" + +namespace DistributedDB { +class SQLiteMultiVerDataStorage : public IKvDBMultiVerDataStorage { +public: + SQLiteMultiVerDataStorage(); + ~SQLiteMultiVerDataStorage() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteMultiVerDataStorage); + + int Open(const Property &property) override; + + int StartWrite(KvDataType dataType, IKvDBMultiVerTransaction *&transaction) override; + + int CommitWritePhaseOne(IKvDBMultiVerTransaction *transaction, + const UpdateVerTimestamp &multiVerTimestamp) override; + + int RollbackWritePhaseOne(IKvDBMultiVerTransaction *transaction, const Version &versionInfo) override; + + int RollbackWrite(IKvDBMultiVerTransaction *transaction) override; + + void CommitWritePhaseTwo(IKvDBMultiVerTransaction *transaction) override; + + IKvDBMultiVerTransaction *StartRead(KvDataType dataType, const Version &versionInfo, int &errCode) override; + + void ReleaseTransaction(IKvDBMultiVerTransaction *transaction) override; + + void Close() override; + + int RunRekeyLogic(CipherType type, const CipherPassword &passwd); + + int RunExportLogic(CipherType type, const CipherPassword &passwd, const std::string &dbDir); + + int CheckVersion(const Property &property, bool &isDbExist) const override; + + int GetVersion(const Property &property, int &version, bool &isDbExisted) const override; + + int BackupCurrentDatabase(const Property &property, const std::string &dir) override; + + int ImportDatabase(const Property &property, const std::string &dir, const CipherPassword &passwd) override; + +private: + Property property_; + std::string uri_; + std::map readTransactions_; + SQLiteMultiVerTransaction *writeTransaction_; + bool writeTransactionUsed_; + std::mutex transactionMutex_; + std::condition_variable readCondition_; + std::condition_variable writeCondition_; + std::thread::id writeHolderId_; +}; +} // namespace DistributedDB + +#endif // SQLITE_KV_DB_MULTI_VER_DATA_STORAGE_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_transaction.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_transaction.cpp new file mode 100644 index 00000000..37bcfe7d --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_transaction.cpp @@ -0,0 +1,1524 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "sqlite_multi_ver_transaction.h" + +#include +#include +#include +#include + +#include "securec.h" + +#include "db_common.h" +#include "db_constant.h" +#include "db_types.h" +#include "log_print.h" +#include "sqlite_utils.h" +#include "multi_ver_kv_entry.h" +#include "multi_ver_value_object.h" +#include "value_hash_calc.h" +#include "time_helper.h" + +namespace DistributedDB { +const std::string SQLiteMultiVerTransaction::CREATE_TABLE_SQL = + "CREATE TABLE IF NOT EXISTS version_data(key BLOB, value BLOB, oper_flag INTEGER, version INTEGER, " \ + "timestamp INTEGER, ori_timestamp INTEGER, hash_key BLOB, " \ + "PRIMARY key(hash_key, version));" \ + "CREATE INDEX IF NOT EXISTS version_index ON version_data (version);" \ + "CREATE INDEX IF NOT EXISTS flag_index ON version_data (oper_flag);"; + +const std::string SQLiteMultiVerTransaction::SELECT_ONE_SQL = + "SELECT oper_flag, key, value FROM version_data WHERE hash_key=? AND (timestamp>? OR (timestamp=? AND rowid>=?)) " \ + "AND version<=? AND (oper_flag&0x08=0x08) ORDER BY version DESC LIMIT 1;"; +const std::string SQLiteMultiVerTransaction::SELECT_BY_HASHKEY_VER_SQL = + "SELECT oper_flag, value FROM version_data WHERE hash_key=? AND version=? "; +const std::string SQLiteMultiVerTransaction::SELECT_BATCH_SQL = + "SELECT oper_flag, key, value, version FROM version_data WHERE key>=? AND key<=?" \ + "AND (timestamp>? OR (timestamp=? AND rowid>=?)) AND version<=? AND (oper_flag&0x08=0x08) " \ + "ORDER BY key ASC, version DESC;"; +// select the data whose hash key is same to the current data. +const std::string SQLiteMultiVerTransaction::SELECT_HASH_ENTRY_SQL = + "SELECT oper_flag FROM version_data WHERE hash_key=? AND version>? AND version<=? AND (oper_flag&0x08=0x08) " \ + "ORDER BY version DESC LIMIT 1;"; +const std::string SQLiteMultiVerTransaction::SELECT_ONE_VER_RAW_SQL = + "SELECT key, value, oper_flag, timestamp, ori_timestamp, hash_key FROM version_data " \ + "WHERE version=? ORDER BY rowid ASC;"; +const std::string SQLiteMultiVerTransaction::INSERT_SQL = + "INSERT OR REPLACE INTO version_data VALUES(?,?,?,?,?,?,?);"; +const std::string SQLiteMultiVerTransaction::DELETE_VER_SQL = + "DELETE FROM version_data WHERE version=?;"; +const std::string SQLiteMultiVerTransaction::DELETE_BY_VER_HASHKEY_SQL = + "DELETE FROM version_data WHERE version=? and hash_key=?;"; +const std::string SQLiteMultiVerTransaction::SELECT_PRE_PUT_VER_DATA_SQL = + "SELECT value FROM version_data WHERE version=? AND timestamp<=?;"; +const std::string SQLiteMultiVerTransaction::DELETE_PRE_PUT_VER_DATA_SQL = + "DELETE FROM version_data WHERE version=? AND timestamp<=?;"; + +const std::string SQLiteMultiVerTransaction::SELECT_ONE_BY_KEY_TIMESTAMP_SQL = + "SELECT timestamp, ori_timestamp, version, value FROM version_data WHERE hash_key=? AND (oper_flag&0x08=0x08) " \ + "ORDER BY version DESC LIMIT 1;"; + +const std::string SQLiteMultiVerTransaction::SELECT_LATEST_CLEAR_ID = + "SELECT rowid, timestamp FROM version_data WHERE (oper_flag&0x07=0x03) AND (oper_flag&0x08=0x08) " \ + "ORDER BY rowid DESC LIMIT 1;"; // clear flag and the local flag. + +const std::string SQLiteMultiVerTransaction::SELECT_MAX_LOCAL_VERSION = + "SELECT MAX(version) FROM version_data WHERE (oper_flag&0x08=0x08);"; + +const std::string SQLiteMultiVerTransaction::SELECT_MAX_VERSION = + "SELECT MAX(version) FROM version_data;"; + +const std::string SQLiteMultiVerTransaction::SELECT_MAX_TIMESTAMP = + "SELECT MAX(timestamp) FROM version_data;"; + +const std::string SQLiteMultiVerTransaction::UPDATE_VERSION_TIMESTAMP = + "UPDATE version_data SET timestamp=? WHERE version=?;"; + +const std::string SQLiteMultiVerTransaction::SELECT_OVERWRITTEN_CLEAR_TYPE = + "SELECT hash_key, oper_flag, version FROM version_data WHERE version tableVect; + tableVect.push_back(CREATE_TABLE_SQL); + OpenDbProperties option = {uri, true, false, tableVect, type, passwd}; + int errCode = SQLiteUtils::OpenDatabase(option, db_); + if (errCode != E_OK) { + LOGE("Init db error:%d", errCode); + return errCode; + } + + uri_ = uri; + isReadOnly_ = isReadOnly; + return E_OK; +} + +void SQLiteMultiVerTransaction::SetVersion(const Version &versionInfo) +{ + version_ = versionInfo; +} + +int SQLiteMultiVerTransaction::Put(const Key &key, const Value &value) +{ + // for only read transaction, not support for writing. + if (isReadOnly_) { + return -E_NOT_SUPPORT; + } + + uint64_t flag = ADD_FLAG | LOCAL_FLAG; + MultiVerEntryAuxData data = {flag, NO_TIMESTAMP, NO_TIMESTAMP}; + return AddRecord(key, value, data); +} + +int SQLiteMultiVerTransaction::Delete(const Key &key) +{ + if (key.empty() || key.size() > DBConstant::MAX_VALUE_SIZE) { + return -E_INVALID_ARGS; + } + Value valueRead; + int errCode = Get(key, valueRead); + if (errCode != E_OK) { + return errCode; + } + + valueRead.clear(); + MultiVerValueObject valueObject; + errCode = valueObject.SetValue(valueRead); + if (errCode != E_OK) { + return errCode; + } + + Value value; + errCode = valueObject.GetSerialData(value); + if (errCode != E_OK) { + return errCode; + } + + Key hashKey; + errCode = DBCommon::CalcValueHash(key, hashKey); + if (errCode != E_OK) { + return errCode; + } + + MultiVerEntryAuxData data = {DEL_FLAG | LOCAL_FLAG, NO_TIMESTAMP, NO_TIMESTAMP}; + return AddRecord(hashKey, value, data); +} + +int SQLiteMultiVerTransaction::Clear() +{ + if (isReadOnly_) { + return -E_NOT_SUPPORT; + } + + Key key = {'c', 'l', 'e', 'a', 'r'}; + Value emptyValue; + MultiVerValueObject valueObject; + int errCode = valueObject.SetValue(emptyValue); + if (errCode != E_OK) { + return errCode; + } + + Value value; + errCode = valueObject.GetSerialData(value); + if (errCode != E_OK) { + return errCode; + } + + MultiVerEntryAuxData data = {LOCAL_FLAG | CLEAR_FLAG, NO_TIMESTAMP, NO_TIMESTAMP}; + errCode = AddRecord(key, value, data); + + clearId_ = 0; + GetClearId(); + return errCode; +} + +int SQLiteMultiVerTransaction::Get(const Key &key, Value &value) const +{ + sqlite3_stmt *statement = nullptr; + std::lock_guard lock(readMutex_); + int errCode = SQLiteUtils::GetStatement(db_, SELECT_ONE_SQL, statement); + if (errCode != E_OK) { + return errCode; + } + + GetClearId(); // query the clear rowid, and only concern the later entry. + Key readKey; + Key hashKey; + errCode = DBCommon::CalcValueHash(key, hashKey); + if (errCode != E_OK) { + goto END; + } + errCode = GetKeyAndValueByHashKey(statement, hashKey, readKey, value, false); +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::GetValueForTrimSlice(const Key &hashKey, const Version version, Value &value) const +{ + sqlite3_stmt *statement = nullptr; + std::lock_guard lock(readMutex_); + int errCode = SQLiteUtils::GetStatement(db_, SELECT_BY_HASHKEY_VER_SQL, statement); + if (errCode != E_OK) { + return errCode; + } + + uint64_t operFlag; + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, hashKey, false); // bind the 1st para for hash key + if (errCode != E_OK) { + goto END; + } + + errCode = sqlite3_bind_int64(statement, 2, version); // bind the 2nd para for version + if (errCode != SQLITE_OK) { + LOGE("Bind the clear id for query error:%d", errCode); + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = -E_NOT_FOUND; + goto END; + } else if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + goto END; + } + + errCode = E_OK; + operFlag = static_cast(sqlite3_column_int64(statement, 0)); + // only the added data should be operated. + if ((OPERATE_MASK & operFlag) == ADD_FLAG) { + errCode = SQLiteUtils::GetColumnBlobValue(statement, 1, value); // Get the value. + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::GetEntries(const Key &keyPrefix, std::vector &entries) const +{ + GetEntriesStatements statements; + std::lock_guard lock(readMutex_); + int errCode = PrepareForGetEntries(keyPrefix, statements); + if (errCode != E_OK) { + return errCode; + } + + Entry entry; + Key lastKey; + int stepResult; + int innerCode; + + entries.clear(); + entries.shrink_to_fit(); + do { + errCode = SQLiteUtils::StepWithRetry(statements.getEntriesStatement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + stepResult = GetOneEntry(statements, lastKey, entry, errCode); + SQLiteUtils::ResetStatement(statements.hashFilterStatement, false, errCode); + if (stepResult == STEP_SUCCESS) { + lastKey = entry.key; + entries.push_back(std::move(entry)); + } else if (stepResult == STEP_NEXTKEY) { // this key would be dispatched + lastKey = entry.key; + } else if (stepResult == STEP_CONTINUE) { + continue; + } else { + goto END; + } + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + break; + } else { + LOGE("SQLite step failed:%d", errCode); + goto END; + } + } while (true); + + // if select no result, return -E_NOT_FOUND. + if (entries.empty()) { + errCode = -E_NOT_FOUND; + } else { + errCode = E_OK; + } +END: + innerCode = ReleaseGetEntriesStatements(statements); + if (innerCode != E_OK) { + errCode = innerCode; + } + return errCode; +} + +int SQLiteMultiVerTransaction::CheckToSaveRecord(const MultiVerKvEntry *entry, bool &isNeedSave, + std::vector &values) +{ + Value disVal; + int errCode = CheckIfNeedSaveRecord(entry, isNeedSave, disVal); + if (errCode != E_OK) { + return errCode; + } + + if (!isNeedSave) { + static_cast(entry)->GetValue(disVal); + return E_OK; + } + // Should erase the data inserted before the clear operation. + uint64_t operFlag = 0; + uint64_t timestamp = 0; + (static_cast(entry))->GetOperFlag(operFlag); + entry->GetTimestamp(timestamp); + if ((operFlag & OPERATE_MASK) == CLEAR_FLAG && version_ != 0) { + LOGD("Erase one version:%" PRIu64, version_); + errCode = GetPrePutValues(version_, timestamp, values); + if (errCode != E_OK) { + return errCode; + } + errCode = RemovePrePutEntries(version_, timestamp); + if (errCode != E_OK) { + LOGE("Delete version data before clear oper failed:%d", errCode); + return errCode; + } + clearId_ = 0; // Clear the clear id. + } + + return E_OK; +} + +int SQLiteMultiVerTransaction::PutBatch(const std::vector &entries) +{ + for (auto iter = entries.begin(); iter != entries.end(); iter++) { + int errCode = Put(iter->key, iter->value); + if (errCode != E_OK) { + LOGE("put failed:%d!", errCode); + return errCode; + } + } + return E_OK; +} + +int SQLiteMultiVerTransaction::PutBatch(const std::vector &entries, bool isLocal, + std::vector &values) +{ + for (const auto &item : entries) { + if (item == nullptr) { + continue; + } + + auto entry = static_cast(item); + MultiVerEntryAuxData data; + entry->GetOperFlag(data.operFlag); + entry->GetTimestamp(data.timestamp); + entry->GetOriTimestamp(data.oriTimestamp); + data.operFlag &= OPERATE_MASK; + + // isLocal means that the entries need merge. + if (isLocal) { + data.operFlag |= LOCAL_FLAG; // set to local + } + + bool isNeedSave = false; + int errCode = CheckToSaveRecord(item, isNeedSave, values); + if (errCode != E_OK) { + return errCode; + } + // already add to the values. + if (!isNeedSave) { + continue; + } + + Key key; + Value value; + (void)entry->GetKey(key); + errCode = entry->GetValue(value); + if (errCode != E_OK) { + return errCode; + } + + values.push_back(value); + errCode = AddRecord(key, value, data); + if (errCode != E_OK) { + LOGE("Put batch data failed:%d", errCode); + return errCode; + } + } + return E_OK; +} + +int SQLiteMultiVerTransaction::GetDiffEntries(const Version &begin, const Version &end, MultiVerDiffData &data) const +{ + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, SELECT_ONE_VER_RAW_SQL, statement); + if (errCode != E_OK) { + LOGE("Fail to get the version raw data statement:%d", errCode); + return errCode; + } + + Value value; + std::vector savedEntries; + errCode = GetRawDataByVersion(statement, end, savedEntries); // Get all the data of the end version. + if (errCode != E_OK) { + LOGE("Get raw data for diff version failed:%d", errCode); + goto ERROR; + } + + for (auto &item : savedEntries) { + if ((item.auxData.operFlag & OPERATE_MASK) == CLEAR_FLAG) { + data.Reset(); + data.isCleared = true; + continue; + } + value.clear(); + if (begin == 0) { // no begin version, means no value + errCode = -E_NOT_FOUND; + } else { + // Need get the origin key of the deleted data. + if ((item.auxData.operFlag & OPERATE_MASK) == ADD_FLAG) { + errCode = Get(item.key, value); + } else { + errCode = GetOriginKeyValueByHash(item, value); + } + } + + if (errCode == E_OK || errCode == -E_NOT_FOUND) { + ClassifyDiffEntries(errCode, (item.auxData.operFlag & OPERATE_MASK), value, item, data); + errCode = E_OK; + } else { + break; + } + } + +ERROR: + if (errCode != E_OK) { + data.Reset(); + } + + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::GetMaxVersion(MultiVerDataType type, Version &maxVersion) const +{ + std::string sql = SELECT_MAX_VERSION; + if (type == MultiVerDataType::NATIVE_TYPE) { + sql = SELECT_MAX_LOCAL_VERSION; + } + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, sql, statement); + if (errCode != E_OK) { + return errCode; + } + + // Step for getting the latest version + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + LOGI("Initial the new max local version"); + maxVersion = 0; + errCode = E_OK; + } else { + LOGE("Execute max version failed:%d", errCode); + } + } else { + maxVersion = static_cast(sqlite3_column_int64(statement, 0)); // only select the first result. + errCode = E_OK; + } + + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::ClearEntriesByVersion(const Version &versionInfo) +{ + // consider to get the statement. + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, DELETE_VER_SQL, statement); + if (errCode != E_OK) { + LOGE("Get delete version statement error:%d", errCode); + return errCode; + } + + // bind the version info. + errCode = sqlite3_bind_int64(statement, 1, versionInfo); // bind the first argument; + if (errCode != SQLITE_OK) { + LOGE("bind the delete version statement error:%d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + goto END; + } + + // Step for getting the latest version + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + LOGE("Delete records error:%d", errCode); + } else { + errCode = E_OK; + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::GetPrePutValues(const Version &versionInfo, Timestamp timestamp, + std::vector &values) const +{ + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, SELECT_PRE_PUT_VER_DATA_SQL, statement); + if (errCode != E_OK) { + LOGE("get delete version statement for clear error:%d", errCode); + return errCode; + } + + // bind the versioninfo + errCode = sqlite3_bind_int64(statement, 1, versionInfo); // bind the first argument; + if (errCode != SQLITE_OK) { + LOGE("bind the delete version statement for clear error:%d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + goto ERROR; + } + + // bind the clear timestamp + errCode = sqlite3_bind_int64(statement, 2, timestamp); // bind the second argument for timestamp; + if (errCode != SQLITE_OK) { + LOGE("bind the clear timestamp for delete ver data error:%d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + goto ERROR; + } + + do { + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + Value value; + errCode = SQLiteUtils::GetColumnBlobValue(statement, 0, value); // get the 1st for value. + if (errCode != E_OK) { + goto ERROR; + } + values.push_back(std::move(value)); + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + goto ERROR; + } else { + goto ERROR; + } + } while (true); + +ERROR: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::RemovePrePutEntries(const Version &versionInfo, Timestamp timestamp) +{ + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, DELETE_PRE_PUT_VER_DATA_SQL, statement); + if (errCode != E_OK) { + LOGE("get delete version statement for clear error:%d", errCode); + return errCode; + } + + // bind the versioninfo + errCode = sqlite3_bind_int64(statement, 1, versionInfo); // bind the first argument; + if (errCode != SQLITE_OK) { + LOGE("bind the delete version statement for clear error:%d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + goto ERROR; + } + + // bind the clear timestamp + errCode = sqlite3_bind_int64(statement, 2, timestamp); // bind the second argument for timestamp; + if (errCode != SQLITE_OK) { + LOGE("bind the clear timestamp for delete ver data error:%d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + goto ERROR; + } + + // Step for getting the latest version + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + LOGE("Delete records for clear error:%d", errCode); + } else { + errCode = E_OK; + } + +ERROR: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::StartTransaction() +{ + return SQLiteUtils::BeginTransaction(db_); +} + +int SQLiteMultiVerTransaction::RollBackTransaction() +{ + return SQLiteUtils::RollbackTransaction(db_); +} + +int SQLiteMultiVerTransaction::CommitTransaction() +{ + return SQLiteUtils::CommitTransaction(db_); +} + +int SQLiteMultiVerTransaction::GetEntriesByVersion(Version version, std::list &data) const +{ + std::lock_guard lock(readMutex_); + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, SELECT_ONE_VER_RAW_SQL, statement); + if (errCode != E_OK) { + return errCode; + } + + std::vector savedEntries; + errCode = GetRawDataByVersion(statement, version, savedEntries); + if (errCode != E_OK) { + LOGE("get raw data failed:%d", errCode); + goto ERROR; + } + + for (auto &item : savedEntries) { + MultiVerTrimedVersionData versionData; + versionData.operFlag = item.auxData.operFlag; + if ((versionData.operFlag & OPERATE_MASK) == ADD_FLAG) { + (void)DBCommon::CalcValueHash(item.key, versionData.key); + } else { + versionData.key = item.key; + } + versionData.version = version; + data.push_front(versionData); + } + +ERROR: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::GetEntriesByVersion(const Version &versionInfo, + std::vector &entries) const +{ + std::lock_guard lock(readMutex_); + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, SELECT_ONE_VER_RAW_SQL, statement); + if (errCode != E_OK) { + return errCode; + } + + std::vector savedEntries; + errCode = GetRawDataByVersion(statement, versionInfo, savedEntries); + if (errCode != E_OK) { + LOGE("get raw data failed:%d", errCode); + goto ERROR; + } + + for (auto &item : savedEntries) { + GenericMultiVerKvEntry *entry = new (std::nothrow) GenericMultiVerKvEntry; + if (entry == nullptr) { + errCode = -E_OUT_OF_MEMORY; + break; + } + entry->SetOperFlag(item.auxData.operFlag); + entry->SetKey(item.key); + entry->SetValue(item.value); + entry->SetTimestamp(item.auxData.timestamp); + entry->SetOriTimestamp(item.auxData.oriTimestamp); + entries.push_back(entry); + } + +ERROR: + if (errCode != E_OK) { + for (auto &entry : entries) { + delete entry; + entry = nullptr; + } + + entries.clear(); + entries.shrink_to_fit(); + } + + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +Timestamp SQLiteMultiVerTransaction::GetCurrentMaxTimestamp() const +{ + // consider to get the statement. + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, SELECT_MAX_TIMESTAMP, statement); + if (errCode != E_OK) { + LOGE("Get current max timestamp statement error:%d", errCode); + return 0; + } + Timestamp timestamp = 0; + // Step for getting the latest version + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + LOGI("Initial the current max timestamp"); + } + } else { + timestamp = static_cast(sqlite3_column_int64(statement, 0)); // the first result. + } + SQLiteUtils::ResetStatement(statement, true, errCode); + return timestamp; +} + +int SQLiteMultiVerTransaction::UpdateTimestampByVersion(const Version &version, + Timestamp stamp) const +{ + if (isReadOnly_) { + return -E_NOT_SUPPORT; + } + + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, UPDATE_VERSION_TIMESTAMP, statement); + if (errCode != E_OK) { + LOGE("Get update timestamp statement error:%d", errCode); + return errCode; + } + + // bind the timestamp + errCode = sqlite3_bind_int64(statement, 1, static_cast(stamp)); // bind the 1st for timestamp; + if (errCode != SQLITE_OK) { + LOGE("bind the updated timestamp error:%d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + goto END; + } + + // bind the versioninfo + errCode = sqlite3_bind_int64(statement, 2, static_cast(version)); // bind the 2nd for version; + if (errCode != SQLITE_OK) { + LOGE("bind the updated version error:%d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + goto END; + } + + // Step for getting the latest version + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + currentMaxTimestamp_ = (stamp > currentMaxTimestamp_) ? stamp : currentMaxTimestamp_; + LOGD("Update the timestamp of version:%" PRIu64 " - %" PRIu64, version, stamp); + } else { + LOGE("Failed to update the timestamp of the version:%d", errCode); + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +bool SQLiteMultiVerTransaction::IsDataChanged() const +{ + if (isReadOnly_) { + return false; + } + + return isDataChanged_; +} + +void SQLiteMultiVerTransaction::ResetVersion() +{ + if (db_ != nullptr) { + sqlite3_db_release_memory(db_); + } + + version_ = 0; + clearId_ = 0; + isDataChanged_ = false; +} + +int SQLiteMultiVerTransaction::Reset(CipherType type, const CipherPassword &passwd) +{ + std::lock_guard lock(resetMutex_); + std::vector tableVect = {CREATE_TABLE_SQL}; + OpenDbProperties option = {uri_, true, false, tableVect, type, passwd}; + sqlite3 *newConnection = nullptr; + int errCode = SQLiteUtils::OpenDatabase(option, newConnection); + if (errCode != E_OK) { + LOGE("Reset the transaction error:%d", errCode); + return errCode; + } + if (db_ != nullptr) { + (void)sqlite3_close_v2(db_); + } + db_ = newConnection; + return E_OK; +} + +Version SQLiteMultiVerTransaction::GetVersion() const +{ + return version_; +} + +int SQLiteMultiVerTransaction::GetOverwrittenClearTypeEntries(Version clearVersion, + std::list &data) const +{ + sqlite3_stmt *statement = nullptr; + std::lock_guard lock(readMutex_); + int errCode = SQLiteUtils::GetStatement(db_, SELECT_OVERWRITTEN_CLEAR_TYPE, statement); + if (errCode != E_OK) { + return errCode; + } + + errCode = sqlite3_bind_int64(statement, 1, clearVersion); // bind the 1st for the clear version + if (errCode != SQLITE_OK) { + LOGE("Bind the clear id for query error:%d", errCode); + goto END; + } + + errCode = sqlite3_bind_int64(statement, 2, clearVersion); // bind the 2nd for the clear version to get timestamp + if (errCode != SQLITE_OK) { + LOGE("Bind the clear id for query error:%d", errCode); + goto END; + } + + do { + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + uint64_t operFlag = static_cast(sqlite3_column_int64(statement, 1)); // get the 2nd for opr + + MultiVerTrimedVersionData trimedVerData; + errCode = SQLiteUtils::GetColumnBlobValue(statement, 0, trimedVerData.key); // get the 1st for key. + if (errCode != E_OK) { + goto END; + } + trimedVerData.operFlag = operFlag & OPERATE_MASK; + trimedVerData.version = static_cast(sqlite3_column_int64(statement, 2)); // get the 3rd for ver + data.push_front(trimedVerData); + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + goto END; + } else { + goto END; + } + } while (true); +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::GetOverwrittenNonClearTypeEntries(Version version, const Key &hashKey, + std::list &data) const +{ + sqlite3_stmt *statement = nullptr; + std::lock_guard lock(readMutex_); + int errCode = SQLiteUtils::GetStatement(db_, SELECT_OVERWRITTEN_NO_CLEAR_TYPE, statement); + if (errCode != E_OK) { + return errCode; + } + + errCode = sqlite3_bind_int64(statement, 1, version); // bind the 1st for the version + if (errCode != SQLITE_OK) { + LOGE("Bind the clear id for query error:%d", errCode); + goto END; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, 2, hashKey, false); // 2nd argument is hashKey + if (errCode != E_OK) { + goto END; + } + + do { + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + uint64_t operFlag = static_cast(sqlite3_column_int64(statement, 1)); // 2nd for oper flag. + MultiVerTrimedVersionData trimedVerData; + errCode = SQLiteUtils::GetColumnBlobValue(statement, 0, trimedVerData.key); // column result is key. + if (errCode != E_OK) { + goto END; + } + + trimedVerData.operFlag = operFlag & OPERATE_MASK; // get the meta flag + trimedVerData.version = static_cast(sqlite3_column_int64(statement, 2)); // get the 3rd for ver + data.push_front(trimedVerData); + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + goto END; + } else { + goto END; + } + } while (true); + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::DeleteEntriesByHashKey(Version version, const Key &hashKey) +{ + // consider to get the statement. + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, DELETE_BY_VER_HASHKEY_SQL, statement); + if (errCode != E_OK) { + LOGE("Get delete version statement error:%d", errCode); + return errCode; + } + + // bind the version info. + errCode = sqlite3_bind_int64(statement, 1, version); // bind the first argument; + if (errCode != SQLITE_OK) { + LOGE("bind the delete version statement error:%d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + goto END; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, 2, hashKey, false); // 2nd argument is hashKey + if (errCode != E_OK) { + goto END; + } + + // Step for getting the latest version + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + LOGE("Delete records error:%d", errCode); + } else { + errCode = E_OK; + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::GetRawMultiVerEntry(sqlite3_stmt *statement, MultiVerEntryData &keyEntry) +{ + int errCode = SQLiteUtils::GetColumnBlobValue(statement, 1, keyEntry.value); + if (errCode != E_OK) { + return errCode; + } + + uint64_t flag = static_cast(sqlite3_column_int64(statement, 2)); // oper flag index + keyEntry.auxData.operFlag = flag & OPERATE_MASK; // remove the local flag. + + keyEntry.auxData.timestamp = static_cast(sqlite3_column_int64(statement, 3)); // timestamp index + keyEntry.auxData.oriTimestamp = static_cast(sqlite3_column_int64(statement, 4)); // ori timestamp index + + // if the data is deleted data, just use the hash key. + if ((flag & OPERATE_MASK) != ADD_FLAG) { + errCode = SQLiteUtils::GetColumnBlobValue(statement, 5, keyEntry.key); // the hash key index. + if (errCode != E_OK) { + return errCode; + } + } else { + errCode = SQLiteUtils::GetColumnBlobValue(statement, 0, keyEntry.key); // the key index. + if (errCode != E_OK) { + return errCode; + } + } + if (keyEntry.key.empty()) { + return -E_INVALID_DATA; + } + return E_OK; +} + +int SQLiteMultiVerTransaction::GetRawDataByVersion(sqlite3_stmt *&statement, + const Version &version, std::vector &entries) +{ + // Bind the version + int errCode = sqlite3_bind_int64(statement, 1, static_cast(version)); // only one parameter. + if (errCode != SQLITE_OK) { + LOGE("Bind the ver for getting raw ver data error:%d", errCode); + return SQLiteUtils::MapSQLiteErrno(errCode); + } + + do { + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + MultiVerEntryData entry; + errCode = GetRawMultiVerEntry(statement, entry); + if (errCode == E_OK) { + entries.push_back(std::move(entry)); + } else { + break; + } + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + // if select no result, return the E_OK. + errCode = E_OK; + break; + } else { + LOGE("SQLite step failed:%d", errCode); + break; + } + } while (true); + + SQLiteUtils::ResetStatement(statement, false, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::GetDiffOperator(int errCode, uint64_t flag) +{ + int oper = EntryOperator::FAIL; + if (errCode == -E_NOT_FOUND) { + if (flag == ADD_FLAG) { + oper = EntryOperator::INSERT; + } + } else if (errCode == E_OK) { + if (flag == DEL_FLAG) { + oper = EntryOperator::DELETE; + } else if (flag == ADD_FLAG) { + oper = EntryOperator::UPDATE; + } + } + + return oper; +} + +int SQLiteMultiVerTransaction::AddRecord(const Key &key, const Value &value, + const MultiVerEntryAuxData &data) +{ + if (isReadOnly_) { + return -E_NOT_SUPPORT; + } + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, INSERT_SQL, statement); + if (errCode != E_OK) { + return errCode; + } + + // If the record has timestamp, it means the record is foreign. + MultiVerEntryAuxData dataCopy = data; + if (data.timestamp == NO_TIMESTAMP) { + if (currentMaxTimestamp_ == NO_TIMESTAMP) { + currentMaxTimestamp_ = std::max(GetCurrentMaxTimestamp(), currentMaxTimestamp_); + } + dataCopy.timestamp = currentMaxTimestamp_++; + if ((dataCopy.operFlag & LOCAL_FLAG) != 0) { + dataCopy.oriTimestamp = currentMaxTimestamp_; + LOGD("Origin timestamp:%" PRIu64, currentMaxTimestamp_); + } + } + + errCode = BindAddRecordArgs(statement, key, value, dataCopy); + if (errCode != E_OK) { + goto END; + } + + // Step for put the result. + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + currentMaxTimestamp_ = (dataCopy.timestamp > currentMaxTimestamp_) ? dataCopy.timestamp : currentMaxTimestamp_; + errCode = E_OK; + isDataChanged_ = true; + } else { + LOGE("SQLite step error: %d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +void SQLiteMultiVerTransaction::ClassifyDiffEntries(int errCode, uint64_t flag, + const Value &value, MultiVerEntryData &item, MultiVerDiffData &data) const +{ + int oper = GetDiffOperator(errCode, flag); + Entry entry; + entry.key.swap(item.key); + if (oper == EntryOperator::DELETE) { + if (value.empty()) { + MultiVerValueObject valueObject; + valueObject.SetValue(value); + Value newValue; + int returnCode = valueObject.GetSerialData(newValue); + if (returnCode != E_OK) { + entry.value.clear(); + } else { + entry.value.swap(newValue); + } + } else { + entry.value = value; + } + data.deleted.push_back(std::move(entry)); + } else if (oper == EntryOperator::INSERT) { + entry.value.swap(item.value); + data.inserted.push_back(std::move(entry)); + } else if (oper == EntryOperator::UPDATE) { + entry.value.swap(item.value); + data.updated.push_back(std::move(entry)); + } +} + +void SQLiteMultiVerTransaction::GetClearId() const +{ + if (clearId_ > 0) { // only changes at the begin or after clear operation. + return; + } + + // consider to get the statement. + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, SELECT_LATEST_CLEAR_ID, statement); + if (errCode != E_OK) { + LOGE("Get latest clear id error:%d", errCode); + clearId_ = 1; + clearTime_ = 0; + return; + } + + // Step for getting the latest version + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + LOGI("Initial the new version for clear"); + } + clearId_ = 1; + clearTime_ = 0; + } else { + clearId_ = sqlite3_column_int64(statement, 0); // Get the max rowid from the 1st column. + clearTime_ = sqlite3_column_int64(statement, 1); // Get the max timestamp from the 2nd column. + } + SQLiteUtils::ResetStatement(statement, true, errCode); +} + +int SQLiteMultiVerTransaction::BindClearIdAndVersion(sqlite3_stmt *statement, int index) const +{ + int errCode = sqlite3_bind_int64(statement, index, clearTime_); // bind the 1st for the clear time + if (errCode != SQLITE_OK) { + LOGE("Bind the clear id for query error:%d", errCode); + goto END; + } + + // bind the next argument for the clear time in the same transact + errCode = sqlite3_bind_int64(statement, index + 1, clearTime_); + if (errCode != SQLITE_OK) { + LOGE("Bind the clear id for query error:%d", errCode); + goto END; + } + + errCode = sqlite3_bind_int64(statement, index + 2, clearId_); // combination using with the clear time. + if (errCode != SQLITE_OK) { + LOGE("Bind the clear id for query error:%d", errCode); + goto END; + } + + errCode = sqlite3_bind_int64(statement, index + 3, version_); // version is after the clear rowid. + if (errCode != SQLITE_OK) { + LOGE("Bind the version for query error:%d", errCode); + goto END; + } +END: + return SQLiteUtils::MapSQLiteErrno(errCode); +} + +int SQLiteMultiVerTransaction::BindQueryEntryArgs(sqlite3_stmt *statement, + const Key &key) const +{ + int errCode = SQLiteUtils::BindBlobToStatement(statement, 1, key, false); // first argument is key + if (errCode != E_OK) { + return errCode; + } + + return BindClearIdAndVersion(statement, 2); // the third argument is clear id. +} + +int SQLiteMultiVerTransaction::BindQueryEntriesArgs(sqlite3_stmt *statement, + const Key &key) const +{ + // bind the prefix key for the first and second args. + int errCode = SQLiteUtils::BindPrefixKey(statement, 1, key); // first argument is key + if (errCode != E_OK) { + return errCode; + } + + return BindClearIdAndVersion(statement, 3); // the third argument is clear id. +} + +int SQLiteMultiVerTransaction::BindAddRecordKeysToStatement(sqlite3_stmt *statement, const Key &key, + const MultiVerEntryAuxData &data) +{ + if ((data.operFlag & OPERATE_MASK) != ADD_FLAG) { + Key emptyKey; + int errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_INSERT_KEY_INDEX, emptyKey, true); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_INSERT_HASH_KEY_INDEX, key, false); + if (errCode != E_OK) { + return errCode; + } + return errCode; + } + int errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_INSERT_KEY_INDEX, key, false); + if (errCode != E_OK) { + return errCode; + } + Key hashKey; + errCode = DBCommon::CalcValueHash(key, hashKey); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_INSERT_HASH_KEY_INDEX, hashKey, false); + if (errCode != E_OK) { + return errCode; + } + return errCode; +} + +int SQLiteMultiVerTransaction::BindAddRecordArgs(sqlite3_stmt *statement, + const Key &key, const Value &value, const MultiVerEntryAuxData &data) const +{ + int errCode = BindAddRecordKeysToStatement(statement, key, data); + if (errCode != E_OK) { + LOGE("Failed to bind the keys:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_INSERT_VAL_INDEX, value, true); + if (errCode != E_OK) { + return errCode; + } + + errCode = sqlite3_bind_int64(statement, BIND_INSERT_OPER_FLG_INDEX, static_cast(data.operFlag)); + if (errCode != SQLITE_OK) { + goto END; + } + + errCode = sqlite3_bind_int64(statement, BIND_INSERT_VER_INDEX, static_cast(version_)); + if (errCode != SQLITE_OK) { + goto END; + } + + errCode = sqlite3_bind_int64(statement, BIND_INSERT_TIME_INDEX, static_cast(data.timestamp)); + if (errCode != SQLITE_OK) { + goto END; + } + + errCode = sqlite3_bind_int64(statement, BIND_INSERT_ORI_TIME_INDEX, static_cast(data.oriTimestamp)); + if (errCode != SQLITE_OK) { + goto END; + } + +END: + if (errCode != SQLITE_OK) { + LOGE("Failed to bind the value:%d", errCode); + } + return SQLiteUtils::MapSQLiteErrno(errCode); +} + +int SQLiteMultiVerTransaction::GetOneEntry(const GetEntriesStatements &statements, + const Key &lastKey, Entry &entry, int &errCode) const +{ + // SQL: "select oper_flag, key, value, version from data;" + errCode = SQLiteUtils::GetColumnBlobValue(statements.getEntriesStatement, 1, entry.key); // 2th is key + if (errCode != E_OK) { + return STEP_ERROR; + } + + // if equal to the last key, just step to the next one. + if (lastKey == entry.key) { + entry.key.clear(); + return STEP_CONTINUE; + } + uint64_t flag = static_cast(sqlite3_column_int64(statements.getEntriesStatement, 0)); // 1th is flag + if ((flag & OPERATE_MASK) != ADD_FLAG) { + return STEP_NEXTKEY; + } + + errCode = SQLiteUtils::GetColumnBlobValue(statements.getEntriesStatement, 2, entry.value); // 3rd is value + if (errCode != E_OK) { + return STEP_ERROR; + } + + Version curVer = static_cast(sqlite3_column_int64(statements.getEntriesStatement, 3)); // 4th is ver + // select the version that is greater than the curEntryVer; + Key hashKey; + errCode = DBCommon::CalcValueHash(entry.key, hashKey); + if (errCode != E_OK) { + return STEP_ERROR; + } + errCode = SQLiteUtils::BindBlobToStatement(statements.hashFilterStatement, 1, hashKey, false); + if (errCode != E_OK) { + return STEP_ERROR; + } + + errCode = sqlite3_bind_int64(statements.hashFilterStatement, 2, static_cast(curVer)); + if (errCode != E_OK) { + return STEP_ERROR; + } + errCode = sqlite3_bind_int64(statements.hashFilterStatement, 3, static_cast(version_)); + if (errCode != E_OK) { + return STEP_ERROR; + } + errCode = SQLiteUtils::StepWithRetry(statements.hashFilterStatement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + return STEP_NEXTKEY; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + return STEP_SUCCESS; + } else { + LOGE("Filter the entries hash key error:%d", errCode); + return STEP_ERROR; + } +} + +bool SQLiteMultiVerTransaction::IsRecordCleared(const Timestamp timestamp) const +{ + GetClearId(); + if (clearTime_ < 0) { + return true; + } + if (timestamp <= static_cast(clearTime_)) { + return true; + } + return false; +} + +int SQLiteMultiVerTransaction::CheckIfNeedSaveRecord(sqlite3_stmt *statement, const MultiVerKvEntry *multiVerKvEntry, + bool &isNeedSave, Value &origVal) const +{ + // Bind the input args for sql + int errCode; + Key key; + Value value; + uint64_t operFlag = 0; + static_cast(multiVerKvEntry)->GetKey(key); + static_cast(multiVerKvEntry)->GetOperFlag(operFlag); + static_cast(multiVerKvEntry)->GetValue(value); + if ((operFlag & OPERATE_MASK) == ADD_FLAG) { + Key hashKey; + errCode = DBCommon::CalcValueHash(key, hashKey); + if (errCode != E_OK) { + return errCode; + } + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, hashKey, false); // key is the first arg + } else { + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, key, false); // key is the first arg + } + + if (errCode != E_OK) { + return errCode; + } + + // ori_stamp should diff from timstamp + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + isNeedSave = true; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + auto readTime = static_cast(sqlite3_column_int64(statement, 0)); // the first for time + auto readOriTime = static_cast(sqlite3_column_int64(statement, 1)); // the second for orig time. + auto readVersion = static_cast(sqlite3_column_int64(statement, 2)); // the third for version. + errCode = SQLiteUtils::GetColumnBlobValue(statement, 3, origVal); // the fourth for origin value. + if (errCode != E_OK) { + return errCode; + } + Timestamp timestamp = NO_TIMESTAMP; + static_cast(multiVerKvEntry)->GetTimestamp(timestamp); + Timestamp oriTimestamp = NO_TIMESTAMP; + static_cast(multiVerKvEntry)->GetOriTimestamp(oriTimestamp); + // Only the latest origin time is same or the reading time is bigger than putting time. + isNeedSave = ((readTime < timestamp) && (readOriTime != oriTimestamp || value != origVal)); + LOGD("Timestamp :%" PRIu64 " vs %" PRIu64 ", %" PRIu64 " vs %" PRIu64 ", readVersion:%" PRIu64 ", version:" + "%" PRIu64 ", %d", readOriTime, oriTimestamp, readTime, timestamp, readVersion, version_, isNeedSave); + // if the version of the data to be saved is same to the original, you should notify the caller. + if (readVersion != version_) { + origVal.resize(0); + } + } else { + LOGE("Check if need store sync entry failed:%d", errCode); + } + + return errCode; +} + +int SQLiteMultiVerTransaction::CheckIfNeedSaveRecord(const MultiVerKvEntry *multiVerKvEntry, bool &isNeedSave, + Value &value) const +{ + auto entry = static_cast(multiVerKvEntry); + Timestamp timestamp = NO_TIMESTAMP; + entry->GetTimestamp(timestamp); + if (IsRecordCleared(timestamp)) { + isNeedSave = false; + entry->GetValue(value); + return E_OK; + } + + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, SELECT_ONE_BY_KEY_TIMESTAMP_SQL, statement); + if (errCode != E_OK) { + return errCode; + } + + errCode = CheckIfNeedSaveRecord(statement, entry, isNeedSave, value); + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::PrepareForGetEntries(const Key &keyPrefix, GetEntriesStatements &statements) const +{ + int innerCode; + int errCode = SQLiteUtils::GetStatement(db_, SELECT_BATCH_SQL, statements.getEntriesStatement); + if (errCode != E_OK) { + goto END; + } + errCode = SQLiteUtils::GetStatement(db_, SELECT_HASH_ENTRY_SQL, statements.hashFilterStatement); + if (errCode != E_OK) { + goto END; + } + + GetClearId(); // for read data. + errCode = BindQueryEntriesArgs(statements.getEntriesStatement, keyPrefix); + if (errCode != E_OK) { + goto END; + } + return E_OK; +END: + innerCode = ReleaseGetEntriesStatements(statements); + if (errCode == E_OK) { + errCode = innerCode; + } + return errCode; +} + +int SQLiteMultiVerTransaction::ReleaseGetEntriesStatements(GetEntriesStatements &statements) const +{ + int errCode = E_OK; + SQLiteUtils::ResetStatement(statements.getEntriesStatement, true, errCode); + SQLiteUtils::ResetStatement(statements.hashFilterStatement, true, errCode); + return errCode; +} + +int SQLiteMultiVerTransaction::GetKeyAndValueByHashKey(sqlite3_stmt *statement, const Key &hashKey, + Key &key, Value &value, bool isNeedReadKey) const +{ + int errCode = BindQueryEntryArgs(statement, hashKey); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + return -E_NOT_FOUND; + } else if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + return errCode; + } + + uint64_t flag = static_cast(sqlite3_column_int64(statement, 0)); // get the flag + if ((flag & OPERATE_MASK) != ADD_FLAG) { // if not add or replace, + return -E_NOT_FOUND; + } + if (isNeedReadKey) { + errCode = SQLiteUtils::GetColumnBlobValue(statement, 1, key); // 2nd column result is key. + if (errCode != E_OK) { + return errCode; + } + } + + return SQLiteUtils::GetColumnBlobValue(statement, 2, value); // 3rd column result is value. +} + +int SQLiteMultiVerTransaction::GetOriginKeyValueByHash(MultiVerEntryData &item, Value &value) const +{ + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db_, SELECT_ONE_SQL, statement); + if (errCode != E_OK) { + return errCode; + } + Key origKey; + errCode = GetKeyAndValueByHashKey(statement, item.key, origKey, value, true); + if (errCode != E_OK) { + goto END; + } + item.key = origKey; +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_transaction.h b/mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_transaction.h new file mode 100644 index 00000000..bee608f7 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_multi_ver_transaction.h @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_MULTI_VER_TRANSACTION_H +#define SQLITE_MULTI_VER_TRANSACTION_H + +#ifndef OMIT_MULTI_VER +#include +#include +#include + +#include "sqlite_import.h" +#include "db_types.h" +#include "kvdb_properties.h" +#include "ikvdb_multi_ver_transaction.h" +#include "macro_utils.h" +#include "multi_ver_value_object.h" +#include "generic_multi_ver_kv_entry.h" + +namespace DistributedDB { +class SQLiteMultiVerTransaction : public IKvDBMultiVerTransaction { +public: + SQLiteMultiVerTransaction(); + ~SQLiteMultiVerTransaction() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteMultiVerTransaction); + + int Initialize(const std::string &uri, bool isReadOnly, CipherType type, const CipherPassword &passwd); + + int Put(const Key &key, const Value &value) override; + + int Delete(const Key &key) override; + + int Clear() override; + + int Get(const Key &key, Value &value) const override; + + int GetEntries(const Key &keyPrefix, std::vector &entries) const override; + + int PutBatch(const std::vector &entries); + + int PutBatch(const std::vector &entries, bool isLocal, std::vector &values) override; + + int GetDiffEntries(const Version &begin, const Version &end, MultiVerDiffData &data) const override; + + int GetMaxVersion(MultiVerDataType type, Version &maxVersion) const override; + + int ClearEntriesByVersion(const Version &versionInfo) override; + + int StartTransaction() override; + + int RollBackTransaction() override; + + int CommitTransaction() override; + + int GetEntriesByVersion(Version version, std::list &data) const override; + + // Get Entries from the version. + int GetEntriesByVersion(const Version &versionInfo, std::vector &entries) const override; + + // Update the timestamp of the version. + int UpdateTimestampByVersion(const Version &version, Timestamp stamp) const override; + + bool IsDataChanged() const override; + + // Get the max timestamp to generate the new version for the writing transaction + Timestamp GetCurrentMaxTimestamp() const override; + + // Reset the version. + void ResetVersion(); + + // Reset the transaction while committing failed. + int Reset(CipherType type, const CipherPassword &passwd); + + // Check if the entry already cleared + bool IsRecordCleared(const Timestamp timestamp) const override; + + void SetVersion(const Version &versionInfo) override; + + Version GetVersion() const override; + + int GetOverwrittenClearTypeEntries(Version clearVersion, std::list &data) const override; + + int GetOverwrittenNonClearTypeEntries(Version version, const Key &hashKey, + std::list &data) const override; + + int DeleteEntriesByHashKey(Version version, const Key &hashKey) override; + + int GetValueForTrimSlice(const Key &hashKey, const Version version, Value &value) const override; + + int CheckIfNeedSaveRecord(sqlite3_stmt *statement, const MultiVerKvEntry *multiVerKvEntry, + bool &isNeedSave, Value &origVal) const; + +private: + struct GetEntriesStatements { + sqlite3_stmt *getEntriesStatement = nullptr; + sqlite3_stmt *hashFilterStatement = nullptr; + }; + + enum EntryOperator { + INSERT, + UPDATE, + DELETE, + CLEAR, + FAIL, + }; + + static int GetRawMultiVerEntry(sqlite3_stmt *statement, MultiVerEntryData &keyEntry); + + static int GetRawDataByVersion(sqlite3_stmt *&statement, const Version &version, + std::vector &entries); + + static int GetDiffOperator(int errCode, uint64_t flag); + + static int BindAddRecordKeysToStatement(sqlite3_stmt *statement, const Key &key, + const MultiVerEntryAuxData &data); + + int AddRecord(const Key &key, const Value &value, const MultiVerEntryAuxData &data); + + void ClassifyDiffEntries(int errCode, uint64_t flag, const Value &value, + MultiVerEntryData &item, MultiVerDiffData &data) const; + + void GetClearId() const; + + int BindClearIdAndVersion(sqlite3_stmt *statement, int index) const; + + int BindQueryEntryArgs(sqlite3_stmt *statement, const Key &key) const; + + int BindQueryEntriesArgs(sqlite3_stmt *statement, const Key &key) const; + + int BindAddRecordArgs(sqlite3_stmt *statement, const Key &key, const Value &value, + const MultiVerEntryAuxData &data) const; + + int GetOneEntry(const GetEntriesStatements &statements, const Key &lastKey, Entry &entry, int &errCode) const; + + int RemovePrePutEntries(const Version &versionInfo, Timestamp timestamp); + + int CheckToSaveRecord(const MultiVerKvEntry *entry, bool &isNeedSave, std::vector &values); + + // Check if the entry with later timestamp already exist in the database + int CheckIfNeedSaveRecord(const MultiVerKvEntry *multiVerKvEntry, bool &isNeedSave, Value &value) const; + + int PrepareForGetEntries(const Key &keyPrefix, GetEntriesStatements &statements) const; + + int ReleaseGetEntriesStatements(GetEntriesStatements &statements) const; + + int GetKeyAndValueByHashKey(sqlite3_stmt *statement, const Key &hashKey, Key &key, Value &value, + bool isNeedReadKey) const; + + int GetOriginKeyValueByHash(MultiVerEntryData &item, Value &value) const; + + int GetPrePutValues(const Version &versionInfo, Timestamp timestamp, std::vector &values) const; + + static const std::string CREATE_TABLE_SQL; + static const std::string SELECT_ONE_SQL; // select the rowid + static const std::string SELECT_BATCH_SQL; // select the rowid and the key + static const std::string SELECT_HASH_ENTRY_SQL; // select the data according the hash key + static const std::string SELECT_ONE_VER_SQL; // select the rowid + static const std::string SELECT_BATCH_VER_SQL; // select the rowid and the key + static const std::string SELECT_ONE_VER_RAW_SQL; + static const std::string INSERT_SQL; // insert or replace the values + static const std::string DELETE_SQL; // delete the key-value record + static const std::string SELECT_ONE_BY_KEY_TIMESTAMP_SQL; // get one by key and timestamp + + static const std::string DELETE_VER_SQL; + static const std::string SELECT_PRE_PUT_VER_DATA_SQL; + static const std::string DELETE_PRE_PUT_VER_DATA_SQL; + static const std::string SELECT_LATEST_CLEAR_ID; + static const std::string SELECT_MAX_LOCAL_VERSION; + static const std::string SELECT_MAX_VERSION; + static const std::string SELECT_MAX_TIMESTAMP; + static const std::string UPDATE_VERSION_TIMESTAMP; + static const std::string SELECT_OVERWRITTEN_CLEAR_TYPE; + static const std::string SELECT_OVERWRITTEN_NO_CLEAR_TYPE; + static const std::string DELETE_BY_VER_HASHKEY_SQL; + static const std::string SELECT_BY_HASHKEY_VER_SQL; + + static const uint64_t ADD_FLAG = 0x01; // add or replace the record. + static const uint64_t DEL_FLAG = 0x02; // delete the record. + static const uint64_t CLEAR_FLAG = 0x03; // clear all the record. + + static const uint64_t LOCAL_FLAG = 0x08; // local flag for read. + static const uint64_t OPERATE_MASK = 0x07; // operate mask for add, delete + + std::mutex resetMutex_; + mutable std::mutex readMutex_; + + mutable int64_t clearId_; // for query the result after the clear operation in the same commit. + mutable int64_t clearTime_; // for query the result after the clear operation + mutable uint64_t currentMaxTimestamp_; + Version version_; // the read version or the current commit version. + sqlite3 *db_; + std::string uri_; + bool isReadOnly_; + bool isDataChanged_; // whether the transaction has new record. +}; +} // namespace DistributedDB + +#endif // SQLITE_MULTI_VER_TRANSACTION_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_query_helper.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_query_helper.cpp new file mode 100644 index 00000000..24b52f46 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_query_helper.cpp @@ -0,0 +1,1067 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_query_helper.h" + +#include + +#include "db_common.h" +#include "db_constant.h" +#include "db_errno.h" +#include "log_print.h" +#include "macro_utils.h" +#include "sqlite_utils.h" + +namespace DistributedDB { +using namespace TriggerMode; +namespace { +const std::string PRE_QUERY_KV_SQL = "SELECT key, value FROM sync_data "; +const std::string PRE_QUERY_ITEM_SQL = "SELECT * FROM "; +const std::string PRE_QUERY_ROWID_SQL = "SELECT rowid FROM sync_data "; +const std::string PRE_GET_COUNT_SQL = "SELECT count(*) FROM sync_data "; +const std::string FILTER_NATIVE_DATA_SQL = "WHERE (flag&0x01=0) "; +const std::string FILTER_REMOTE_QUERY = "WHERE (flag&0x03=0x02)"; +const std::string USING_INDEX = "INDEXED BY "; +const int MAX_SQL_LEN = 1024 * 1024; // 1M bytes +const int SINGLE_FIELD_VALUE_SIZE = 1; +const int MAX_CONDITIONS_SIZE = 128; +const int MAX_SQLITE_BIND_SIZE = 50000; +const uint32_t SYMBOL_TYPE_MASK = 0xff00; + +const std::map RELATIONAL_SYMBOL_TO_SQL { + {QueryObjType::EQUALTO, "= "}, + {QueryObjType::NOT_EQUALTO, "!= "}, + {QueryObjType::GREATER_THAN, "> "}, + {QueryObjType::LESS_THAN, "< "}, + {QueryObjType::GREATER_THAN_OR_EQUALTO, ">= "}, + {QueryObjType::LESS_THAN_OR_EQUALTO, "<= "}, + {QueryObjType::LIKE, " LIKE "}, + {QueryObjType::NOT_LIKE, " NOT LIKE "}, + {QueryObjType::IS_NULL, " IS NULL "}, + {QueryObjType::IS_NOT_NULL, " IS NOT NULL "}, + {QueryObjType::IN, " IN ("}, + {QueryObjType::NOT_IN, " NOT IN ("}, +}; + +const std::map LOGIC_SYMBOL_TO_SQL { + {QueryObjType::AND, " AND "}, + {QueryObjType::OR, " OR "}, + {QueryObjType::BEGIN_GROUP, "("}, + {QueryObjType::END_GROUP, ")"}, +}; + +std::string FieldValue2String(const FieldValue &val, QueryValueType type) +{ + std::stringstream ss; + switch (type) { + case QueryValueType::VALUE_TYPE_NULL: + return "NULL"; + case QueryValueType::VALUE_TYPE_BOOL: + return val.boolValue ? "1" : "0"; + case QueryValueType::VALUE_TYPE_INTEGER: + return std::to_string(val.integerValue); + case QueryValueType::VALUE_TYPE_LONG: + return std::to_string(val.longValue); + case QueryValueType::VALUE_TYPE_DOUBLE: + ss << std::setprecision(DBConstant::DOUBLE_PRECISION) << val.doubleValue; + return ss.str(); + case QueryValueType::VALUE_TYPE_STRING: + return "'" + val.stringValue + "'"; + case QueryValueType::VALUE_TYPE_INVALID: + default: + return ""; + } +} + +std::string GetSelectAndFromClauseForRDB(const std::string &tableName, const std::vector &fieldNames) +{ + std::string sql = "SELECT b.data_key," + "b.device," + "b.ori_device," + "b.timestamp as " + DBConstant::TIMESTAMP_ALIAS + "," + "b.wtimestamp," + "b.flag," + "b.hash_key,"; + if (fieldNames.empty()) { // For query check. If column count changed, can be discovered. + sql += "a.*"; + } else { + for (const auto &fieldName : fieldNames) { // For query data. + sql += "a." + fieldName + ","; + } + sql.pop_back(); + } + sql += " FROM " + tableName + " AS a INNER JOIN " + DBConstant::RELATIONAL_PREFIX + tableName + "_log AS b " + "ON a.rowid=b.data_key "; + return sql; +} + +std::string GetTimeRangeClauseForRDB() +{ + return " AND (" + DBConstant::TIMESTAMP_ALIAS + ">=? AND " + DBConstant::TIMESTAMP_ALIAS + "=? AND " + DBConstant::TIMESTAMP_ALIAS + "(static_cast(queryObjType) & SYMBOL_TYPE_MASK); +} + +bool SqliteQueryHelper::FilterSymbolToAddBracketLink(std::string &querySql, bool isNeedLink) const +{ + bool isNeedEndBracket = false; + for (const auto &iter : queryObjNodes_) { + SymbolType symbolType = GetSymbolType(iter.operFlag); + if (symbolType == COMPARE_SYMBOL || symbolType == RELATIONAL_SYMBOL || symbolType == RANGE_SYMBOL) { + querySql += isNeedLink ? " AND (" : " ("; + isNeedEndBracket = true; + break; + } else if (symbolType == LOGIC_SYMBOL || symbolType == PREFIXKEY_SYMBOL || symbolType == IN_KEYS_SYMBOL) { + continue; + } else { + break; + } + } + return isNeedEndBracket; +} + + +int SqliteQueryHelper::ParseQueryObjNodeToSQL(bool isQueryForSync) +{ + if (queryObjNodes_.empty()) { + if (!isQueryForSync) { + querySql_ += ";"; + } + return E_OK; + } + + bool isNeedEndBracket = FilterSymbolToAddBracketLink(querySql_); + + int errCode = E_OK; + for (const QueryObjNode &objNode : queryObjNodes_) { + SymbolType symbolType = GetSymbolType(objNode.operFlag); + if (symbolType == SPECIAL_SYMBOL && isNeedEndBracket) { + querySql_ += ") "; + isNeedEndBracket = false; + } + errCode = ParseQueryExpression(objNode, querySql_); + if (errCode != E_OK) { + querySql_.clear(); + return errCode; + } + } + + if (isNeedEndBracket) { + querySql_ += ") "; + } + + return errCode; +} + +int SqliteQueryHelper::ToQuerySql() +{ + int errCode = ParseQueryObjNodeToSQL(false); + if (errCode != E_OK) { + return errCode; + } + + // Limit needs to be placed after orderBy and processed separately in the limit branch + if (hasPrefixKey_ && !hasOrderBy_ && !hasLimit_ && isNeedOrderbyKey_) { + LOGD("Need add order by key at last when has prefixKey no need order by value and limit!"); + querySql_ += "ORDER BY key ASC"; + } + querySql_ += ";"; + return errCode; +} + +int SqliteQueryHelper::ToQuerySyncSql(bool hasSubQuery, bool useTimestampAlias) +{ + int errCode = ParseQueryObjNodeToSQL(true); + if (errCode != E_OK) { + return errCode; + } + + // Order by time when no order by and no limit and no need order by key. + if (!hasOrderBy_ && !hasLimit_ && !isNeedOrderbyKey_) { + querySql_ += (useTimestampAlias ? + ("ORDER BY " + DBConstant::TIMESTAMP_ALIAS + " ASC") : + "ORDER BY timestamp ASC"); + } + + if (!hasSubQuery) { + querySql_ += ";"; + } + return errCode; +} + +int SqliteQueryHelper::ToGetCountSql() +{ + countSql_.clear(); + if (queryObjNodes_.empty()) { + countSql_ += ";"; + return E_OK; + } + bool isNeedEndBracket = FilterSymbolToAddBracketLink(countSql_); + + int errCode = E_OK; + for (const QueryObjNode &objNode : queryObjNodes_) { + SymbolType symbolType = GetSymbolType(objNode.operFlag); + if (symbolType == SPECIAL_SYMBOL && isNeedEndBracket) { + countSql_ += ") "; + isNeedEndBracket = false; + } + + if (objNode.operFlag == QueryObjType::LIMIT) { + hasLimit_ = true; + continue; + } + if (objNode.operFlag == QueryObjType::ORDERBY) { + hasOrderBy_ = true; + continue; + } + errCode = ParseQueryExpression(objNode, countSql_); + if (errCode != E_OK) { + countSql_.clear(); + return errCode; + } + } + + if (isNeedEndBracket) { + countSql_ += ") "; + } + + // Limit needs to be placed after orderBy and processed separately in the limit branch + if (hasPrefixKey_ && !hasOrderBy_ && !hasLimit_ && isNeedOrderbyKey_) { + LOGD("Need add order by key at last when has prefixKey no need order by value and limit!"); + countSql_ += "ORDER BY key ASC"; + } + countSql_ += ";"; + return errCode; +} + +int SqliteQueryHelper::GetQuerySql(std::string &sql, bool onlyRowid) +{ + if (!isValid_) { + return -E_INVALID_QUERY_FORMAT; + } + + const std::string &querySqlForUse = (onlyRowid ? PRE_QUERY_ROWID_SQL : PRE_QUERY_KV_SQL); + sql = AssembleSqlForSuggestIndex(querySqlForUse, FILTER_NATIVE_DATA_SQL); + sql = !hasPrefixKey_ ? sql : (sql + " AND (key>=? AND key<=?) "); + sql = keys_.empty() ? sql : (sql + " AND " + MapKeysInToSql(keys_.size())); + if (transformed_) { + LOGD("This query object has been parsed."); + sql += querySql_; + return E_OK; + } + int errCode = ToQuerySql(); + if (errCode != E_OK) { + LOGE("Transfer to query sql failed! errCode[%d]", errCode); + return errCode; + } + transformed_ = true; + sql += querySql_; + return errCode; +} + +int SqliteQueryHelper::GetSyncDataCheckSql(std::string &sql) +{ + int errCode = E_OK; + if (!isValid_) { + return -E_INVALID_QUERY_FORMAT; + } + sql = PRE_QUERY_ITEM_SQL + tableName_ + " WHERE hash_key=? AND (flag&0x01=0) "; + sql += hasPrefixKey_ ? " AND (key>=? AND key<=?) " : ""; + sql = keys_.empty() ? sql : (sql + " AND " + MapKeysInToSql(keys_.size())); + if (!transformed_) { + errCode = ToQuerySql(); + if (errCode != E_OK) { + LOGE("Transfer query to sync data check sql failed! errCode[%d]", errCode); + return errCode; + } + transformed_ = true; + } + sql += querySql_; + return errCode; +} + +int SqliteQueryHelper::BindSyncDataCheckStmt(sqlite3_stmt *statement, const Key &hashKey) const +{ + if (statement == nullptr) { + return -E_INVALID_ARGS; + } + int index = 1; // bind statement start index 1 + int errCode = SQLiteUtils::BindBlobToStatement(statement, index++, hashKey, false); + if (errCode != E_OK) { + LOGE("Get sync data check statement failed when bind hash key, errCode = %d", errCode); + return errCode; + } + if (hasPrefixKey_) { + // bind the prefix key for the first and second args. + errCode = SQLiteUtils::BindPrefixKey(statement, index, prefixKey_); + if (errCode != E_OK) { + LOGE("Get sync data check statement failed when bind prefix key, errCode = %d", errCode); + return errCode; + } + index += 2; // prefixKey takes 2 position + } + + errCode = BindKeysToStmt(keys_, statement, index); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; + } + + for (const QueryObjNode &objNode : queryObjNodes_) { + errCode = BindFieldValue(statement, objNode, index); + if (errCode != E_OK) { + LOGE("Get sync data check statement failed when bind field value, errCode = %d", errCode); + return errCode; + } + } + return errCode; +} + +int SqliteQueryHelper::GetCountQuerySql(std::string &sql) +{ + if (!isValid_) { + return -E_INVALID_QUERY_FORMAT; + } + + int errCode = ToGetCountSql(); + if (errCode != E_OK) { + return errCode; + } + sql = AssembleSqlForSuggestIndex(PRE_GET_COUNT_SQL, FILTER_NATIVE_DATA_SQL); + sql = !hasPrefixKey_ ? sql : (sql + " AND (key>=? AND key<=?) "); + sql = keys_.empty() ? sql : (sql + " AND " + MapKeysInToSql(keys_.size())); + sql += countSql_; + return E_OK; +} + +int SqliteQueryHelper::GetQuerySqlStatement(sqlite3 *dbHandle, const std::string &sql, sqlite3_stmt *&statement) +{ + int errCode = SQLiteUtils::GetStatement(dbHandle, sql, statement); + if (errCode != E_OK) { + LOGE("[Query] Get statement fail!"); + return -E_INVALID_QUERY_FORMAT; + } + int index = 1; + if (hasPrefixKey_) { + // bind the prefix key for the first and second args. + errCode = SQLiteUtils::BindPrefixKey(statement, 1, prefixKey_); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(statement, true, errCode); + LOGE("[Query] Get statement when bind prefix key, errCode = %d", errCode); + return errCode; + } + index = 3; // begin from 3rd args + } + + errCode = BindKeysToStmt(keys_, statement, index); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; + } + + for (const QueryObjNode &objNode : queryObjNodes_) { + errCode = BindFieldValue(statement, objNode, index); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(statement, true, errCode); + LOGE("[Query] Get statement fail when bind field value, errCode = %d", errCode); + return errCode; + } + } + return errCode; +} + +int SqliteQueryHelper::GetQuerySqlStatement(sqlite3 *dbHandle, bool onlyRowid, sqlite3_stmt *&statement) +{ + std::string sql; + int errCode = GetQuerySql(sql, onlyRowid); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::GetStatement(dbHandle, sql, statement); + if (errCode != E_OK) { + LOGE("[Query] Get statement fail!"); + return -E_INVALID_QUERY_FORMAT; + } + int index = 1; + if (hasPrefixKey_) { + // bind the prefix key for the first and second args. + errCode = SQLiteUtils::BindPrefixKey(statement, 1, prefixKey_); + if (errCode != E_OK) { + LOGE("[Query] Get statement when bind prefix key, errCode = %d", errCode); + return errCode; + } + index = 3; // begin from 3rd args + } + + errCode = BindKeysToStmt(keys_, statement, index); + if (errCode != E_OK) { + return errCode; + } + + for (const QueryObjNode &objNode : queryObjNodes_) { + errCode = BindFieldValue(statement, objNode, index); + if (errCode != E_OK) { + LOGE("[Query] Get statement fail when bind field value, errCode = %d", errCode); + return errCode; + } + } + return errCode; +} + +int SqliteQueryHelper::GetCountSqlStatement(sqlite3 *dbHandle, sqlite3_stmt *&countStmt) +{ + std::string countSql; + int errCode = GetCountQuerySql(countSql); + if (errCode != E_OK) { + return errCode; + } + + // bind statement for count + errCode = SQLiteUtils::GetStatement(dbHandle, countSql, countStmt); + if (errCode != E_OK) { + LOGE("Get count statement error:%d", errCode); + return -E_INVALID_QUERY_FORMAT; + } + int index = 1; + if (hasPrefixKey_) { + // bind the prefix key for the first and second args. + errCode = SQLiteUtils::BindPrefixKey(countStmt, 1, prefixKey_); + if (errCode != E_OK) { + LOGE("[Query] Get count statement fail when bind prefix key, errCode = %d", errCode); + return errCode; + } + index = 3; // begin from 3rd args + } + + errCode = BindKeysToStmt(keys_, countStmt, index); + if (errCode != E_OK) { + return errCode; + } + + for (const QueryObjNode &objNode : queryObjNodes_) { + if (GetSymbolType(objNode.operFlag) == SPECIAL_SYMBOL) { + continue; + } + errCode = BindFieldValue(countStmt, objNode, index); + if (errCode != E_OK) { + LOGE("[Query] Get count statement fail when bind field value, errCode = %d", errCode); + return errCode; + } + } + return errCode; +} + +int SqliteQueryHelper::GetSyncDataQuerySql(std::string &sql, bool hasSubQuery) +{ + if (!isValid_) { + return -E_INVALID_QUERY_FORMAT; + } + + if (hasLimit_) { + hasSubQuery = true; // Need sub query. + } else { + isNeedOrderbyKey_ = false; // Need order by timestamp. + } + + sql = AssembleSqlForSuggestIndex(PRE_QUERY_ITEM_SQL + tableName_ + " ", FILTER_REMOTE_QUERY); + sql = !hasPrefixKey_ ? sql : (sql + " AND (key>=? AND key<=?) "); + sql = keys_.empty() ? sql : (sql + " AND " + MapKeysInToSql(keys_.size())); + sql = hasSubQuery ? sql : (sql + " AND (timestamp>=? AND timestamp= ? AND timestamp < ?) ORDER BY timestamp;"; + } + return errCode; +} + +int SqliteQueryHelper::BindTimeRange(sqlite3_stmt *&statement, int &index, uint64_t beginTime, uint64_t endTime) const +{ + int errCode = SQLiteUtils::BindInt64ToStatement(statement, index++, beginTime); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; + } + + errCode = SQLiteUtils::BindInt64ToStatement(statement, index++, endTime); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(statement, true, errCode); + } + return errCode; +} + +int SqliteQueryHelper::BindObjNodes(sqlite3_stmt *&statement, int &index) const +{ + int errCode = E_OK; + for (const QueryObjNode &objNode : queryObjNodes_) { + errCode = BindFieldValue(statement, objNode, index); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(statement, true, errCode); + LOGE("[Query] Get statement fail when bind field value, errCode = %d", errCode); + break; + } + } + return errCode; +} + +int SqliteQueryHelper::GetQuerySyncStatement(sqlite3 *dbHandle, uint64_t beginTime, uint64_t endTime, + sqlite3_stmt *&statement) +{ + bool hasSubQuery = false; + if (hasLimit_) { + hasSubQuery = true; // Need sub query. + } else { + isNeedOrderbyKey_ = false; // Need order by timestamp. + } + std::string sql; + int errCode = GetSyncDataQuerySql(sql, hasSubQuery); + if (errCode != E_OK) { + LOGE("[Query] Get SQL fail!"); + return -E_INVALID_QUERY_FORMAT; + } + + errCode = SQLiteUtils::GetStatement(dbHandle, sql, statement); + if (errCode != E_OK) { + LOGE("[Query] Get statement fail!"); + return -E_INVALID_QUERY_FORMAT; + } + + int index = 1; // begin with 1. + if (hasPrefixKey_) { + // bind the prefix key for the first and second args. + errCode = SQLiteUtils::BindPrefixKey(statement, index, prefixKey_); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(statement, true, errCode); + LOGE("[Query] Get statement when bind prefix key, errCode = %d", errCode); + return errCode; + } + index = 3; // begin with 3 next if prefix key exists. + } + + errCode = BindKeysToStmt(keys_, statement, index); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; + } + + if (hasSubQuery) { + // For sub query SQL, timestamp must be last : (prefix key), (objNodes), timestamp. + // SQL: SELECT * FROM ( SELECT * FROM sync_data WHERE (flag&0x03=0x02) LIMIT 10 OFFSET 0 ) WHERE (timestamp>=? + // AND timestamp=? AND timestamp MAX_SQLITE_BIND_SIZE) { + return -E_MAX_LIMITS; + } + errCode = sqlite3_bind_text(statement, index, queryNode.fieldValue[i].stringValue.c_str(), + queryNode.fieldValue[i].stringValue.size(), SQLITE_TRANSIENT); + } + if (errCode != SQLITE_OK) { + break; + } + index++; + } + return SQLiteUtils::MapSQLiteErrno(errCode); +} + +std::string SqliteQueryHelper::MapCastTypeSql(const FieldType &type) const +{ + switch (type) { + case FieldType::LEAF_FIELD_BOOL: + case FieldType::LEAF_FIELD_INTEGER: + case FieldType::LEAF_FIELD_LONG: + return "INT"; + case FieldType::LEAF_FIELD_DOUBLE: + return "REAL"; + case FieldType::LEAF_FIELD_STRING: + return "TEXT"; + case FieldType::LEAF_FIELD_NULL: + return "NULL"; + default: + return ""; + } +} + +std::string SqliteQueryHelper::GetFieldShape(const QueryObjNode &queryNode, const std::string &accessStr) +{ + if (isRelationalQuery_) { + // For relational query, $. prefix is not permitted, so need not extract json. Return directly will be OK. + return "a." + queryNode.fieldName + " "; + } + return MapCastFuncSql(queryNode, accessStr); +} + +int SqliteQueryHelper::ParseQueryExpression(const QueryObjNode &queryNode, std::string &querySql, + const std::string &accessStr, bool placeholder) +{ + SymbolType symbolType = GetSymbolType(queryNode.operFlag); + if (symbolType == RANGE_SYMBOL && queryNode.fieldValue.size() > MAX_CONDITIONS_SIZE) { + LOGE("[Query][Parse][Expression] conditions is too many!"); + return -E_MAX_LIMITS; + } + + if (symbolType == COMPARE_SYMBOL || symbolType == RELATIONAL_SYMBOL || symbolType == RANGE_SYMBOL) { + querySql += GetFieldShape(queryNode, accessStr); + querySql += MapRelationalSymbolToSql(queryNode, placeholder); + } else if (symbolType == LOGIC_SYMBOL || symbolType == LINK_SYMBOL) { + querySql += MapLogicSymbolToSql(queryNode); + } else { + querySql += MapKeywordSymbolToSql(queryNode); + } + + if (querySql.size() > MAX_SQL_LEN) { + LOGE("[Query][Parse][Expression] Sql is too long!"); + return -E_MAX_LIMITS; + } + return E_OK; +} + +std::string SqliteQueryHelper::AssembleSqlForSuggestIndex(const std::string &baseSql, const std::string &filter) const +{ + std::string formatIndex = CheckAndFormatSuggestIndex(); + if (formatIndex.empty()) { + return baseSql + filter; + } + + return baseSql + USING_INDEX + "'" + formatIndex + "' " + filter; +} + +std::string SqliteQueryHelper::CheckAndFormatSuggestIndex() const +{ + if (suggestIndex_.empty()) { + return ""; + } + IndexName indexName; + int errCode = SchemaUtils::ParseAndCheckFieldPath(suggestIndex_, indexName); + if (errCode != E_OK) { + LOGW("Check and format suggest index failed! %d", errCode); + return ""; + } + + if (!schema_.IsIndexExist(indexName)) { + LOGW("The suggest index not exist!"); + return ""; + } + return SchemaUtils::FieldPathString(indexName); +} + +std::string SqliteQueryHelper::MapKeysInSubCondition(const std::string &accessStr) const +{ + std::string resultStr = "hex(" + accessStr + "key) IN ("; + for (auto iter = keys_.begin(); iter != keys_.end(); iter++) { + if (iter != keys_.begin()) { + resultStr += ", "; + } + resultStr += "'" + DBCommon::VectorToHexString(*iter) + "' "; + } + resultStr += ")"; + return resultStr; +} + +int SqliteQueryHelper::GetSubscribeCondition(const std::string &accessStr, std::string &conditionStr) +{ + if (queryObjNodes_.empty()) { + conditionStr += " (1 = 1) "; + return E_OK; + } + conditionStr += "("; + if (hasPrefixKey_) { + conditionStr += "(hex(" + accessStr + "key) LIKE '" + DBCommon::VectorToHexString(prefixKey_) + "%')"; + } + + if (!keys_.empty()) { + if (hasPrefixKey_) { + conditionStr += " AND "; + } + conditionStr += "(" + MapKeysInSubCondition(accessStr) + ")"; + } + + bool isNeedEndBracket = FilterSymbolToAddBracketLink(conditionStr, hasPrefixKey_ || !keys_.empty()); + int errCode = E_OK; + for (const QueryObjNode &objNode : queryObjNodes_) { + SymbolType symbolType = GetSymbolType(objNode.operFlag); + if (symbolType == SPECIAL_SYMBOL && isNeedEndBracket) { + conditionStr += ") "; + isNeedEndBracket = false; + } + errCode = ParseQueryExpression(objNode, conditionStr, accessStr, false); + if (errCode != E_OK) { + conditionStr.clear(); + return errCode; + } + } + + if (isNeedEndBracket) { + conditionStr += ") "; + } + conditionStr += ")"; + return errCode; +} + +int SqliteQueryHelper::GetSubscribeSql(const std::string &subscribeId, TriggerModeEnum mode, + std::string &subscribeCondition) +{ + if (!isValid_) { + return -E_INVALID_QUERY_FORMAT; + } + int errCode = E_OK; + switch (mode) { + case TriggerModeEnum::INSERT: + errCode = GetSubscribeCondition(DBConstant::TRIGGER_REFERENCES_NEW, subscribeCondition); + break; + case TriggerModeEnum::UPDATE: + errCode = GetSubscribeCondition(DBConstant::TRIGGER_REFERENCES_OLD, subscribeCondition); + if (errCode != E_OK) { + break; + } + subscribeCondition += " OR "; + errCode = GetSubscribeCondition(DBConstant::TRIGGER_REFERENCES_NEW, subscribeCondition); + break; + case TriggerModeEnum::DELETE: + errCode = GetSubscribeCondition(DBConstant::TRIGGER_REFERENCES_OLD, subscribeCondition); + break; + default: + errCode = -INVALID_ARGS; + } + if (errCode != E_OK) { + LOGD("Get subscribe query condition failed. %d", errCode); + } + return errCode; +} + +int SqliteQueryHelper::GetRelationalMissQuerySql(const std::vector &fieldNames, std::string &sql) +{ + if (!isValid_) { + return -E_INVALID_QUERY_FORMAT; + } + + if (hasPrefixKey_) { + LOGE("For relational DB query, prefix key is not supported."); + return -E_NOT_SUPPORT; + } + + sql = GetSelectAndFromClauseForRDB(tableName_, fieldNames); + sql += GetMissQueryFlagClauseForRDB(); + sql += GetTimeRangeClauseForRDB(); + sql += "ORDER BY " + DBConstant::TIMESTAMP_ALIAS + " ASC;"; + return E_OK; +} + +int SqliteQueryHelper::GetRelationalSyncDataQuerySql(std::string &sql, bool hasSubQuery, + const std::vector &fieldNames) +{ + if (!isValid_) { + return -E_INVALID_QUERY_FORMAT; + } + + if (hasPrefixKey_) { + LOGE("For relational DB query, prefix key is not supported."); + return -E_NOT_SUPPORT; + } + + sql = AssembleSqlForSuggestIndex(GetSelectAndFromClauseForRDB(tableName_, fieldNames), GetFlagClauseForRDB()); + sql = hasSubQuery ? sql : (sql + GetTimeRangeClauseForRDB()); + + querySql_.clear(); // clear local query sql format + int errCode = ToQuerySyncSql(hasSubQuery, true); + if (errCode != E_OK) { + LOGE("To query sql fail! errCode[%d]", errCode); + return errCode; + } + sql += querySql_; + if (hasSubQuery) { + // The last timestamp in one query will be stored in continue token and used for next query. + // Therefore all query data must be ordered by timestamp. + // When there is limit in SQL, data should be ordered by key in sub query, and timestamp is ordered by outside. + sql = GetOuterQueryClauseForRDB(sql); + } + return errCode; +} + +int SqliteQueryHelper::GetRelationalMissQueryStatement(sqlite3 *dbHandle, uint64_t beginTime, uint64_t endTime, + const std::vector &fieldNames, sqlite3_stmt *&statement) +{ + std::string sql; + int errCode = GetRelationalMissQuerySql(fieldNames, sql); + if (errCode != E_OK) { + LOGE("[Query] Get SQL fail!"); + return -E_INVALID_QUERY_FORMAT; + } + + errCode = SQLiteUtils::GetStatement(dbHandle, sql, statement); + if (errCode != E_OK) { + LOGE("[Query] Get statement fail!"); + return -E_INVALID_QUERY_FORMAT; + } + + int index = 1; // begin with 1. + return BindTimeRange(statement, index, beginTime, endTime); +} + +int SqliteQueryHelper::GetRelationalQueryStatement(sqlite3 *dbHandle, uint64_t beginTime, uint64_t endTime, + const std::vector &fieldNames, sqlite3_stmt *&statement) +{ + bool hasSubQuery = false; + if (hasLimit_ || hasOrderBy_) { + hasSubQuery = true; // Need sub query. + } else { + isNeedOrderbyKey_ = false; // Need order by timestamp. + } + std::string sql; + int errCode = GetRelationalSyncDataQuerySql(sql, hasSubQuery, fieldNames); + if (errCode != E_OK) { + LOGE("[Query] Get SQL fail!"); + return -E_INVALID_QUERY_FORMAT; + } + + errCode = SQLiteUtils::GetStatement(dbHandle, sql, statement); + if (errCode != E_OK) { + LOGE("[Query] Get statement fail!"); + return -E_INVALID_QUERY_FORMAT; + } + + int index = 1; // begin with 1. + if (hasSubQuery) { + /** + * SELECT * FROM ( + * SELECT b.data_key,b.device,b.ori_device,b.timestamp as naturalbase_rdb_timestamp, + * b.wtimestamp,b.flag,b.hash_key,a.* + * FROM tableName AS a INNER JOIN naturalbase_rdb_log AS b + * ON a.rowid=b.data_key + * WHERE (b.flag&0x03=0x02) + * LIMIT ? OFFSET ? ) + * WHERE (naturalbase_rdb_timestamp>=? AND naturalbase_rdb_timestamp=? AND naturalbase_rdb_timestamp &keys, sqlite3_stmt *&statement, int &index) const +{ + if (!keys_.empty()) { + int errCode = E_OK; + for (const auto &key : keys) { + errCode = SQLiteUtils::BindBlobToStatement(statement, index, key); + if (errCode != E_OK) { + LOGE("[Query] Get statement when bind keys failed, errCode = %d", errCode); + return errCode; + } + index++; + } + } + return E_OK; +} +} \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_query_helper.h b/mock/distributeddb/storage/src/sqlite/sqlite_query_helper.h new file mode 100644 index 00000000..c2b19a8b --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_query_helper.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_QUERY_HELPER_H +#define SQLITE_QUERY_HELPER_H + +#include +#include +#include +#include + +#include "query_expression.h" +#include "schema_utils.h" +#include "sqlite_import.h" + +namespace DistributedDB { +namespace TriggerMode { +enum class TriggerModeEnum; +} +struct QueryObjInfo { + SchemaObject schema_; + std::list queryObjNodes_; + std::vector prefixKey_; + std::string suggestIndex_; + std::set keys_; + int orderByCounts_ = 0; // Record processing to which orderBy node + bool isValid_ = true; + bool hasOrderBy_ = false; + bool hasLimit_ = false; + bool hasPrefixKey_ = false; + std::string tableName_; + bool isRelationalQuery_ = false; +}; + +enum SymbolType : uint32_t { + INVALID_SYMBOL = 0x0000, + COMPARE_SYMBOL = 0x0100, // relation symbol use to compare + RELATIONAL_SYMBOL = 0x0200, + RANGE_SYMBOL = 0x0300, + PREFIXKEY_SYMBOL = 0x0400, + LOGIC_SYMBOL = 0x0500, + LINK_SYMBOL = 0x0600, // use to link relatonal symbol + SPECIAL_SYMBOL = 0x0700, // need special precess and need at the last + SUGGEST_INDEX_SYMBOL = 0x0800, + IN_KEYS_SYMBOL = 0x0900, +}; + +class SqliteQueryHelper final { +public: + explicit SqliteQueryHelper(const QueryObjInfo &info); + + // forbidden move constructor. + SqliteQueryHelper(SqliteQueryHelper &&) = delete; + SqliteQueryHelper &operator=(SqliteQueryHelper &&) = delete; + // forbidden copy constructor. + SqliteQueryHelper(const SqliteQueryHelper &) = delete; + SqliteQueryHelper &operator=(const SqliteQueryHelper &) = delete; + + ~SqliteQueryHelper() = default; + + int GetQuerySqlStatement(sqlite3 *dbHandle, bool onlyRowid, sqlite3_stmt *&statement); + int GetQuerySqlStatement(sqlite3 *dbHandle, const std::string &sql, sqlite3_stmt *&statement); + int GetCountSqlStatement(sqlite3 *dbHandle, sqlite3_stmt *&countStmt); + + // For query Sync + int GetQuerySyncStatement(sqlite3 *dbHandle, uint64_t beginTime, uint64_t endTime, sqlite3_stmt *&statement); + int GetSyncDataCheckSql(std::string &sql); + int BindSyncDataCheckStmt(sqlite3_stmt *statement, const Key &hashKey) const; + + int GetSubscribeSql(const std::string &subscribeId, TriggerMode::TriggerModeEnum mode, + std::string &subscribeCondition); + + static SymbolType GetSymbolType(const QueryObjType &queryObjType); + + // public for unit test + int GetQuerySql(std::string &sql, bool onlyRowid); + int GetCountQuerySql(std::string &sql); + + const std::string &GetTableName() + { + return tableName_; + } + + int GetRelationalMissQuerySql(const std::vector &fieldNames, std::string &sql); + int GetRelationalMissQueryStatement(sqlite3 *dbHandle, uint64_t beginTime, uint64_t endTime, + const std::vector &fieldNames, sqlite3_stmt *&statement); + int GetRelationalSyncDataQuerySql(std::string &sql, bool hasSubQuery, const std::vector &fieldNames); + int GetRelationalQueryStatement(sqlite3 *dbHandle, uint64_t beginTime, uint64_t endTime, + const std::vector &fieldNames, sqlite3_stmt *&statement); + +private: + int ToQuerySql(); + int ToQuerySyncSql(bool hasSubQuery, bool useTimestampAlias = false); + int ToGetCountSql(); + int ParseQueryExpression(const QueryObjNode &queryNode, std::string &querySql, + const std::string &accessStr = "", bool placeholder = true); + std::string MapRelationalSymbolToSql(const QueryObjNode &queryNode, bool placeholder = false) const; + std::string MapKeywordSymbolToSql(const QueryObjNode &queryNode); + std::string MapLogicSymbolToSql(const QueryObjNode &queryNode) const; + std::string MapValueToSql(const QueryObjNode &queryNode, bool placeholder) const; + std::string MapCastFuncSql(const QueryObjNode &queryNode, const std::string &accessStr = ""); + std::string MapCastTypeSql(const FieldType &type) const; + int BindFieldValue(sqlite3_stmt *statement, const QueryObjNode &queryNode, int &index) const; + bool FilterSymbolToAddBracketLink(std::string &querySql, bool isNeedLink = true) const; + std::string AssembleSqlForSuggestIndex(const std::string &baseSql, const std::string &filter) const; + std::string CheckAndFormatSuggestIndex() const; + int GetSyncDataQuerySql(std::string &sql, bool hasSubQuery); + int ParseQueryObjNodeToSQL(bool isQueryForSync); + int BindTimeRange(sqlite3_stmt *&statement, int &index, uint64_t beginTime, uint64_t endTime) const; + int BindObjNodes(sqlite3_stmt *&statement, int &index) const; + int GetSubscribeCondition(const std::string &accessStr, std::string &conditionStr); + std::string MapKeysInToSql(size_t keysNum) const; + int BindKeysToStmt(const std::set &keys, sqlite3_stmt *&countStmt, int &index) const; + + std::string MapKeysInSubCondition(const std::string &accessStr) const; // For InKeys. + // Return the left string of symbol in compare clause. + std::string GetFieldShape(const QueryObjNode &queryNode, const std::string &accessStr = ""); + + SchemaObject schema_; + std::list queryObjNodes_; + std::vector prefixKey_; + std::string suggestIndex_; + std::string tableName_; + std::set keys_; + + std::string querySql_; + std::string countSql_; + + int orderByCounts_; // Record processing to which orderBy node + bool isValid_; + bool transformed_; + bool hasOrderBy_; + bool hasLimit_; + bool isOrderByAppeared_; + bool hasPrefixKey_; + bool isNeedOrderbyKey_; // The tag field is used for prefix query filtering key sorting + bool isRelationalQuery_; +}; +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_continue_token.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_continue_token.cpp new file mode 100644 index 00000000..d585bf63 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_continue_token.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_single_ver_continue_token.h" + +namespace DistributedDB { +SQLiteSingleVerContinueToken::SQLiteSingleVerContinueToken(Timestamp begin, Timestamp end) + : timeRanges_(MulDevTimeRanges{{"", {begin, end}}}) +{} + +SQLiteSingleVerContinueToken::SQLiteSingleVerContinueToken( + const SyncTimeRange &timeRange, const QueryObject &queryObject) + : queryObject_(std::map{{"", queryObject}}), + timeRanges_(MulDevTimeRanges{{"", {timeRange.beginTime, timeRange.endTime}}}), + deleteTimeRanges_(MulDevTimeRanges{{"", {timeRange.deleteBeginTime, timeRange.deleteEndTime}}}) +{} + +SQLiteSingleVerContinueToken::SQLiteSingleVerContinueToken(MulDevTimeRanges timeRanges) + : timeRanges_(timeRanges) +{} + +SQLiteSingleVerContinueToken::~SQLiteSingleVerContinueToken() +{} + +bool SQLiteSingleVerContinueToken::CheckValid() const +{ + return ((magicBegin_ == MAGIC_BEGIN) && (magicEnd_ == MAGIC_END)); +} + +Timestamp SQLiteSingleVerContinueToken::GetQueryBeginTime() const +{ + return GetBeginTimestamp(timeRanges_); +} + +Timestamp SQLiteSingleVerContinueToken::GetQueryEndTime() const +{ + return GetEndTimestamp(timeRanges_); +} + +Timestamp SQLiteSingleVerContinueToken::GetDeletedBeginTime() const +{ + return GetBeginTimestamp(deleteTimeRanges_); +} + +Timestamp SQLiteSingleVerContinueToken::GetDeletedEndTime() const +{ + return GetEndTimestamp(deleteTimeRanges_); +} + +void SQLiteSingleVerContinueToken::SetNextBeginTime(const DeviceID &deviceID, Timestamp nextBeginTime) +{ + RemovePrevDevAndSetBeginTime(deviceID, nextBeginTime, timeRanges_); +} + +const MulDevTimeRanges& SQLiteSingleVerContinueToken::GetTimeRanges() +{ + return timeRanges_; +} + +void SQLiteSingleVerContinueToken::SetDeletedNextBeginTime(const DeviceID &deviceID, Timestamp nextBeginTime) +{ + RemovePrevDevAndSetBeginTime(deviceID, nextBeginTime, deleteTimeRanges_); +} + +const MulDevTimeRanges& SQLiteSingleVerContinueToken::GetDeletedTimeRanges() const +{ + return deleteTimeRanges_; +} + +void SQLiteSingleVerContinueToken::FinishGetQueryData() +{ + timeRanges_.clear(); +} + +void SQLiteSingleVerContinueToken::FinishGetDeletedData() +{ + deleteTimeRanges_.clear(); +} + +bool SQLiteSingleVerContinueToken::IsGetQueryDataFinished() const +{ + return timeRanges_.empty(); +} + +bool SQLiteSingleVerContinueToken::IsGetDeletedDataFinished() const +{ + return deleteTimeRanges_.empty(); +} + +bool SQLiteSingleVerContinueToken::IsQuerySync() const +{ + return !queryObject_.empty(); +} + +QueryObject SQLiteSingleVerContinueToken::GetQuery() const +{ + if (!queryObject_.empty()) { + return queryObject_.begin()->second; + } + return QueryObject{}; +} + +void SQLiteSingleVerContinueToken::RemovePrevDevAndSetBeginTime(const DeviceID &deviceID, Timestamp nextBeginTime, + MulDevTimeRanges &timeRanges) +{ + auto iter = timeRanges.find(deviceID); + if (iter == timeRanges.end()) { + return; + } + iter = timeRanges.erase(timeRanges.begin(), iter); + iter->second.first = nextBeginTime; +} + +Timestamp SQLiteSingleVerContinueToken::GetBeginTimestamp(const MulDevTimeRanges &timeRanges) const +{ + if (timeRanges.empty()) { + return 0; + } + return timeRanges.begin()->second.first; +} + +Timestamp SQLiteSingleVerContinueToken::GetEndTimestamp(const MulDevTimeRanges &timeRanges) const +{ + if (timeRanges.empty()) { + return static_cast(INT64_MAX); + } + return timeRanges.begin()->second.second; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_continue_token.h b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_continue_token.h new file mode 100644 index 00000000..5350cd34 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_continue_token.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_SINGLE_VER_CONTINUE_TOKEN_H +#define SQLITE_SINGLE_VER_CONTINUE_TOKEN_H + +#include + +#include "db_types.h" +#include "query_object.h" +#include "single_ver_kvdb_sync_interface.h" + +namespace DistributedDB { +class SQLiteSingleVerContinueToken { +public: + // For one device. + SQLiteSingleVerContinueToken(Timestamp begin, Timestamp end); + + // For one device in query sync. + SQLiteSingleVerContinueToken(const SyncTimeRange &timeRange, const QueryObject &queryObject); + + // For multiple device. + explicit SQLiteSingleVerContinueToken(MulDevTimeRanges timeRanges); + + ~SQLiteSingleVerContinueToken(); + + /* + * function: Check the magic number at the beginning and end of the SingleVerContinueToken. + * returnValue: Return true if the begin and end magic number is OK. + * Return false if the begin or end magic number is error. + */ + bool CheckValid() const; + + Timestamp GetQueryBeginTime() const; + Timestamp GetQueryEndTime() const; + Timestamp GetDeletedBeginTime() const; + Timestamp GetDeletedEndTime() const; + + void SetNextBeginTime(const DeviceID &deviceID, Timestamp nextBeginTime); + const MulDevTimeRanges &GetTimeRanges(); + void SetDeletedNextBeginTime(const DeviceID &deviceID, Timestamp nextBeginTime); + const MulDevTimeRanges &GetDeletedTimeRanges() const; + + void FinishGetQueryData(); + void FinishGetDeletedData(); + + bool IsGetQueryDataFinished() const; + bool IsGetDeletedDataFinished() const; + + bool IsQuerySync() const; + QueryObject GetQuery() const; + +private: + void RemovePrevDevAndSetBeginTime(const DeviceID &deviceID, Timestamp nextBeginTime, MulDevTimeRanges &timeRanges); + + Timestamp GetBeginTimestamp(const MulDevTimeRanges &timeRanges) const; + Timestamp GetEndTimestamp(const MulDevTimeRanges &timeRanges) const; + + static const unsigned int MAGIC_BEGIN = 0x600D0AC7; // for token guard + static const unsigned int MAGIC_END = 0x0AC7600D; // for token guard + unsigned int magicBegin_ = MAGIC_BEGIN; + std::map queryObject_; + MulDevTimeRanges timeRanges_; + MulDevTimeRanges deleteTimeRanges_; + unsigned int magicEnd_ = MAGIC_END; +}; +} // namespace DistributedDB +#endif // SQLITE_SINGLE_VER_CONTINUE_TOKEN_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_database_upgrader.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_database_upgrader.cpp new file mode 100644 index 00000000..f0ff27ee --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_database_upgrader.cpp @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_single_ver_database_upgrader.h" +#include "db_errno.h" +#include "log_print.h" +#include "version.h" +#include "db_constant.h" +#include "platform_specific.h" +#include "param_check_utils.h" +#include "runtime_context.h" + +namespace DistributedDB { +namespace { + const std::string CREATE_LOCAL_TABLE_SQL = + "CREATE TABLE IF NOT EXISTS local_data(" \ + "key BLOB PRIMARY KEY," \ + "value BLOB," \ + "timestamp INT," \ + "hash_key BLOB);"; + + const std::string CREATE_SYNC_TABLE_SQL = + "CREATE TABLE IF NOT EXISTS sync_data(" \ + "key BLOB NOT NULL," \ + "value BLOB," \ + "timestamp INT NOT NULL," \ + "flag INT NOT NULL," \ + "device BLOB," \ + "ori_device BLOB," \ + "hash_key BLOB PRIMARY KEY NOT NULL," \ + "w_timestamp INT);"; + + const std::string CREATE_META_TABLE_SQL = + "CREATE TABLE IF NOT EXISTS meta_data(" \ + "key BLOB PRIMARY KEY NOT NULL," \ + "value BLOB);"; + + const std::string CREATE_SINGLE_META_TABLE_SQL = + "CREATE TABLE IF NOT EXISTS meta.meta_data(" \ + "key BLOB PRIMARY KEY NOT NULL," \ + "value BLOB);"; + + const std::string CREATE_SYNC_TABLE_INDEX_SQL = + "CREATE INDEX IF NOT EXISTS key_index ON sync_data (key, flag);" \ + "CREATE INDEX IF NOT EXISTS time_index ON sync_data (timestamp);" \ + "CREATE INDEX IF NOT EXISTS dev_index ON sync_data (device);" \ + "CREATE INDEX IF NOT EXISTS local_hashkey_index ON local_data (hash_key);"; + + const std::string DROP_META_TABLE_SQL = "DROP TABLE IF EXISTS main.meta_data;"; + const std::string COPY_META_TABLE_SQL = "INSERT OR REPLACE INTO meta.meta_data SELECT * FROM meta_data " + "where (SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='main.meta_data') > 0;"; +} + +SQLiteSingleVerDatabaseUpgrader::SQLiteSingleVerDatabaseUpgrader(sqlite3 *db, + const SecurityOption &secopt, bool isMemDb) + : db_(db), + secOpt_(secopt), + isMemDB_(isMemDb), + isMetaUpgrade_(false) +{ +} + +SQLiteSingleVerDatabaseUpgrader::~SQLiteSingleVerDatabaseUpgrader() +{ + db_ = nullptr; +} + +int SQLiteSingleVerDatabaseUpgrader::TransferDatabasePath(const std::string &parentDir, + const OpenDbProperties &option) +{ + std::string dbFilePath = parentDir + "/" + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + std::string upgradeLockFile = parentDir + "/" + DBConstant::UPGRADE_POSTFIX; + + if (OS::CheckPathExistence(upgradeLockFile)) { + return MoveDatabaseToNewDir(parentDir, upgradeLockFile); + } + if (OS::CheckPathExistence(dbFilePath)) { + int currentVersion = 0; + int errCode = GetDbVersion(dbFilePath, option, currentVersion); + if (errCode != E_OK) { + LOGE("[SQLiteSinVerUp] Get version of old database failed"); + return errCode; + } + if (currentVersion == 0) { + LOGI("The database file has not been initialized, maybe invalid database"); + if (OS::RemoveFile(dbFilePath) != E_OK) { + LOGE("[SQLiteSinVerUp] Remove the uninitialized database failed, errno[%d]", errno); + return -E_SYSTEM_API_FAIL; + } + } + if (currentVersion >= SINGLE_VER_STORE_VERSION_V1 && currentVersion <= SINGLE_VER_STORE_VERSION_V2) { + LOGI("[SQLiteSinVerUp] Old version[%d] database exists.", currentVersion); + if (OS::CreateFileByFileName(upgradeLockFile) != E_OK) { + return -E_SYSTEM_API_FAIL; + } + return MoveDatabaseToNewDir(parentDir, upgradeLockFile); + } + } + return E_OK; +} + +int SQLiteSingleVerDatabaseUpgrader::BeginUpgrade() +{ + return SQLiteUtils::BeginTransaction(db_, TransactType::IMMEDIATE); +} + +int SQLiteSingleVerDatabaseUpgrader::EndUpgrade(bool isSuccess) +{ + if (isSuccess) { + return SQLiteUtils::CommitTransaction(db_); + } else { + int errCode = SQLiteUtils::RollbackTransaction(db_); + std::string secOptUpgradeFile = subDir_ + "/" + DBConstant::SET_SECOPT_POSTFIX; + if (errCode == E_OK && OS::CheckPathExistence(secOptUpgradeFile) && + (OS::RemoveFile(secOptUpgradeFile) != E_OK)) { + LOGW("[EndUpgrade] Delete secure upgrade file failed"); + return -E_SYSTEM_API_FAIL; + } + return errCode; + } +} + +int SQLiteSingleVerDatabaseUpgrader::GetDatabaseVersion(int &version) const +{ + return SQLiteUtils::GetVersion(db_, version); +} + +int SQLiteSingleVerDatabaseUpgrader::SetDatabaseVersion(int version) +{ + return SQLiteUtils::SetUserVer(db_, version); +} + +void SQLiteSingleVerDatabaseUpgrader::SetUpgradeSqls(int version, std::vector &sqls, + bool &isCreateUpgradeFile) const +{ + if (version == 0) { // no write version. + if ((!isMemDB_) && ParamCheckUtils::IsS3SECEOpt(secOpt_)) { + sqls = { + CREATE_LOCAL_TABLE_SQL, + CREATE_SINGLE_META_TABLE_SQL, + CREATE_SYNC_TABLE_SQL, + CREATE_SYNC_TABLE_INDEX_SQL + }; + } else { + sqls = { + CREATE_LOCAL_TABLE_SQL, + CREATE_META_TABLE_SQL, + CREATE_SYNC_TABLE_SQL, + CREATE_SYNC_TABLE_INDEX_SQL + }; + } + } else { + if (version <= SINGLE_VER_STORE_VERSION_V1) { + sqls = { + "DROP INDEX key_index;", + "CREATE INDEX IF NOT EXISTS key_index ON sync_data (key, flag);", + "ALTER TABLE sync_data ADD w_timestamp INT;", + "UPDATE sync_data SET w_timestamp=timestamp;", + "ALTER TABLE local_data ADD timestamp INT;", + "ALTER TABLE local_data ADD hash_key BLOB;", + "UPDATE local_data SET hash_key=calc_hash_key(key), timestamp=0;", + "CREATE INDEX IF NOT EXISTS local_hashkey_index ON local_data (hash_key);" + }; + } + if ((version <= SINGLE_VER_STORE_VERSION_V2 && ParamCheckUtils::IsS3SECEOpt(secOpt_)) || + (version == SINGLE_VER_STORE_VERSION_CURRENT && isMetaUpgrade_ == true)) { + sqls.push_back(CREATE_SINGLE_META_TABLE_SQL); + sqls.push_back(COPY_META_TABLE_SQL); + sqls.push_back(DROP_META_TABLE_SQL); + isCreateUpgradeFile = true; + } + } +} + +int SQLiteSingleVerDatabaseUpgrader::UpgradeFromDatabaseVersion(int version) +{ + std::vector sqls; + bool isCreateUpgradeFile = false; + LOGI("[SqlSingleUp] metaSplit[%d], secLabel[%d], secFlag[%d]", + isMetaUpgrade_, secOpt_.securityLabel, secOpt_.securityFlag); + SetUpgradeSqls(version, sqls, isCreateUpgradeFile); + for (const auto &item : sqls) { + int errCode = SQLiteUtils::ExecuteRawSQL(db_, item); + if (errCode != E_OK) { + LOGE("[SqlSingleUp][UpFrom] Execute upgrade sql failed:%d", errCode); + return errCode; + } + } + if (isCreateUpgradeFile) { + std::string secOptUpgradeFile = subDir_ + "/" + DBConstant::SET_SECOPT_POSTFIX; + if (!OS::CheckPathExistence(secOptUpgradeFile) && (OS::CreateFileByFileName(secOptUpgradeFile) != E_OK)) { + LOGE("[SqlSingleUp][UpFrom] Create s3sece flag file failed"); + return -E_SYSTEM_API_FAIL; + } + LOGD("[SqlSingleUp][UpFrom] Create s3sece mark file success"); + } + return E_OK; +} + +int SQLiteSingleVerDatabaseUpgrader::GetDbVersion(const std::string &dbPath, const OpenDbProperties &option, + int &version) +{ + OpenDbProperties optionTmp(option); + optionTmp.uri = dbPath; + sqlite3 *db = nullptr; + int errCode = SQLiteUtils::OpenDatabase(optionTmp, db); + if (errCode != E_OK) { + return errCode; + } + errCode = SQLiteUtils::GetVersion(db, version); + (void)sqlite3_close_v2(db); + db = nullptr; + return errCode; +} + +void SQLiteSingleVerDatabaseUpgrader::SetMetaUpgrade(const SecurityOption ¤tOpt, + const SecurityOption &expectOpt, const std::string &subDir) +{ + std::string secOptUpgradeFile = subDir + "/" + DBConstant::SET_SECOPT_POSTFIX; + // the same version should upgrade while user open db with s3sece. + if ((!OS::CheckPathExistence(secOptUpgradeFile)) && currentOpt.securityLabel == SecurityLabel::NOT_SET && + ParamCheckUtils::IsS3SECEOpt(expectOpt)) { + isMetaUpgrade_ = true; + } else { + isMetaUpgrade_ = false; + } +} + +void SQLiteSingleVerDatabaseUpgrader::SetSubdir(const std::string &subDir) +{ + subDir_ = subDir; +} + +int SQLiteSingleVerDatabaseUpgrader::SetPathSecOptWithCheck(const std::string &path, const SecurityOption &secOption, + const std::string &dbStore, bool isWithChecked) +{ + SecurityOption dbOpt; + std::vector dbFilePathVec {DBConstant::SQLITE_DB_EXTENSION}; + std::string dbFilePath = path + "/" + dbStore + DBConstant::SQLITE_DB_EXTENSION; + if (OS::CheckPathExistence(dbFilePath) && isWithChecked) { + int errCode = RuntimeContext::GetInstance()->GetSecurityOption(dbFilePath, dbOpt); + if (errCode != E_OK) { + LOGE("[SetPathSecOptWithCheck] GetSecurityOption failed:%d", errCode); + if (errCode == -E_NOT_SUPPORT) { + dbOpt = SecurityOption(); + } else { + return errCode; + } + } + } + + for (const auto &item : dbFilePathVec) { + std::string dbItemFilePath = path + "/" + dbStore + item; + if (!OS::CheckPathExistence(dbItemFilePath)) { + continue; + } + if (OS::CheckPathExistence(dbItemFilePath) && dbOpt.securityLabel == NOT_SET) { + int errCode = RuntimeContext::GetInstance()->SetSecurityOption(dbItemFilePath, secOption); + if (errCode != E_OK) { + LOGE("[SetPathSecOptWithCheck] SetSecurityOption failed."); + return errCode; + } + } else if (dbOpt == secOption) { + LOGI("[SetPathSecOptWithCheck] already set secoption"); + } else { + LOGE("[SetPathSecOptWithCheck] already set secoption,but different from early option."); + return -E_INVALID_ARGS; + } + } + return E_OK; +} + +int SQLiteSingleVerDatabaseUpgrader::SetSecOption(const std::string &path, const SecurityOption &secOption, + bool isWithChecked) +{ + if (!ParamCheckUtils::CheckSecOption(secOption)) { + return -E_INVALID_ARGS; + } + if (secOption.securityLabel == NOT_SET) { + return E_OK; + } + std::string secOptUpgradeFile = path + "/" + DBConstant::SET_SECOPT_POSTFIX; + if (OS::CheckPathExistence(secOptUpgradeFile) && !ParamCheckUtils::IsS3SECEOpt(secOption)) { + LOGE("[SingleVerUp][SetSec] Security option is invalid"); + return -E_INVALID_ARGS; + } + int errCode = E_OK; + if (secOption.securityLabel != NOT_SET) { + std::string mainDbPath = path + "/" + DBConstant::MAINDB_DIR; + std::string cacheDbPath = path + "/" + DBConstant::CACHEDB_DIR; + std::string metaDbPath = path + "/" + DBConstant::METADB_DIR; + errCode = SetPathSecOptWithCheck(mainDbPath, secOption, DBConstant::SINGLE_VER_DATA_STORE, isWithChecked); + if (errCode != E_OK) { + return errCode; + } + errCode = SetPathSecOptWithCheck(cacheDbPath, secOption, DBConstant::SINGLE_VER_CACHE_STORE, isWithChecked); + if (errCode != E_OK) { + LOGE("[SQLiteSingleVerDatabaseUpgrader] cacheDb SetSecurityOption failed."); + return errCode; + } + SecurityOption metaSecOpt; + metaSecOpt.securityLabel = ((secOption.securityLabel >= SecurityLabel::S2) ? + SecurityLabel::S2 : secOption.securityLabel); + errCode = SetPathSecOptWithCheck(metaDbPath, metaSecOpt, DBConstant::SINGLE_VER_META_STORE, false); + if (errCode != E_OK) { + LOGE("[SQLiteSingleVerDatabaseUpgrader] metaDb SetSecurityOption failed."); + return errCode; + } + } + if (OS::CheckPathExistence(secOptUpgradeFile) && (OS::RemoveFile(secOptUpgradeFile) != E_OK)) { + return -E_SYSTEM_API_FAIL; + } + + return errCode; +} + +int SQLiteSingleVerDatabaseUpgrader::MoveDatabaseToNewDir(const std::string &parentDir, + const std::string &upgradeLockFile) +{ + std::vector dbFilePathVec {DBConstant::SQLITE_DB_EXTENSION, ".db-wal", ".db-shm"}; + for (const auto &item : dbFilePathVec) { + std::string oldDbPath = parentDir + "/" + DBConstant::SINGLE_VER_DATA_STORE + item; + std::string currentDbPath = parentDir + "/" + DBConstant::MAINDB_DIR + "/" + + DBConstant::SINGLE_VER_DATA_STORE + item; + if (OS::CheckPathExistence(oldDbPath)) { + if (OS::RenameFilePath(oldDbPath, currentDbPath) != E_OK) { + LOGE("[SQLiteSinVerUp] Move database file to the new directory failed, errno:%d", errno); + return -E_SYSTEM_API_FAIL; + } + } + } + int errCode = OS::RemoveFile(upgradeLockFile); + if (errCode != E_OK) { + LOGE("[SQLiteSinVerUp] Remove upgrade flag file failed, errno:%d", errno); + } + return errCode; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_database_upgrader.h b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_database_upgrader.h new file mode 100644 index 00000000..3d5190e4 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_database_upgrader.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_SINGLE_VER_DATABASE_UPGRADER_H +#define SQLITE_SINGLE_VER_DATABASE_UPGRADER_H + +#include "macro_utils.h" +#include "sqlite_utils.h" +#include "single_ver_database_upgrader.h" + +namespace DistributedDB { +class SQLiteSingleVerDatabaseUpgrader : virtual public SingleVerDatabaseUpgrader { +public: + explicit SQLiteSingleVerDatabaseUpgrader(sqlite3 *db, const SecurityOption &secopt, bool isMemDb); + ~SQLiteSingleVerDatabaseUpgrader() override; + DISABLE_COPY_ASSIGN_MOVE(SQLiteSingleVerDatabaseUpgrader); + + // used for transferring db file to new dir while classifycation feature in SOFTWARE_VERSION_RELEASE_3_0 + static int TransferDatabasePath(const std::string &parentDir, const OpenDbProperties &option); + static int CreateDbDir(); + + void SetMetaUpgrade(const SecurityOption ¤tOpt, const SecurityOption &expectOpt, const std::string &subDir); + void SetSubdir(const std::string &subDir); + static int SetPathSecOptWithCheck(const std::string &path, const SecurityOption &secOption, + const std::string &dbStore, bool isWithChecked = false); + static int SetSecOption(const std::string &path, const SecurityOption &secOption, bool isWithChecked); +protected: + int BeginUpgrade() override; + int EndUpgrade(bool isSuccess) override; + int GetDatabaseVersion(int &version) const override; + int SetDatabaseVersion(int version) override; + int UpgradeFromDatabaseVersion(int version) override; + void SetUpgradeSqls(int version, std::vector &sqls, bool &isCreateUpgradeFile) const; + static int MoveDatabaseToNewDir(const std::string &parentDir, const std::string &upgradeLockFile); + static int GetDbVersion(const std::string &dbPath, const OpenDbProperties &option, int &version); + + sqlite3 *db_ = nullptr; + SecurityOption secOpt_; + bool isMemDB_; + bool isMetaUpgrade_; + std::string subDir_; +}; +} // namespace DistributedDB +#endif // SQLITE_SINGLE_VER_DATABASE_UPGRADER_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_forward_cursor.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_forward_cursor.cpp new file mode 100644 index 00000000..dc282384 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_forward_cursor.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_single_ver_forward_cursor.h" + +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +SQLiteSingleVerForwardCursor::SQLiteSingleVerForwardCursor(SQLiteSingleVerNaturalStore *kvDB, const Key &keyPrefix) + : kvDB_(kvDB), + keyPrefix_(keyPrefix), + handle_(nullptr), + count_(0), + isOpen_(false), + isQueryMode_(false) +{} + +SQLiteSingleVerForwardCursor::SQLiteSingleVerForwardCursor(SQLiteSingleVerNaturalStore *kvDB, + const QueryObject &queryObj) + : kvDB_(kvDB), + queryObj_(queryObj), + handle_(nullptr), + count_(0), + isOpen_(false), + isQueryMode_(true) +{} + +SQLiteSingleVerForwardCursor::~SQLiteSingleVerForwardCursor() +{ + kvDB_ = nullptr; + keyPrefix_.clear(); + handle_ = nullptr; + count_ = 0; +} + +int SQLiteSingleVerForwardCursor::Open() +{ + std::lock_guard lock(isOpenMutex_); + if (isOpen_) { + return E_OK; + } + int errCode = E_OK; + handle_ = kvDB_->GetHandle(false, errCode); + if (handle_ == nullptr) { + LOGE("Get handle failed."); + return errCode; + } + + if (isQueryMode_) { + errCode = handle_->OpenResultSet(queryObj_, count_); + } else { + errCode = handle_->OpenResultSet(keyPrefix_, count_); + } + if (errCode == E_OK) { + if (count_ == 0) { + handle_->CloseResultSet(); + kvDB_->ReleaseHandle(handle_); + } + isOpen_ = true; + } else { + handle_->CloseResultSet(); + kvDB_->ReleaseHandle(handle_); + LOGE("Handle open result set failed, errCode: %d", errCode); + } + + return errCode; +} + +void SQLiteSingleVerForwardCursor::Close() +{ + std::lock_guard lock(isOpenMutex_); + if (!isOpen_) { + return; + } + if (handle_ != nullptr) { + handle_->CloseResultSet(); + kvDB_->ReleaseHandle(handle_); + } + count_ = 0; + isOpen_ = false; +} + +int SQLiteSingleVerForwardCursor::Reload() +{ + std::lock_guard lock(isOpenMutex_); + if (!isOpen_) { + return -E_RESULT_SET_STATUS_INVALID; + } + if (count_ == 0) { + return E_OK; + } + int errCode = E_OK; + if (isQueryMode_) { + errCode = handle_->ReloadResultSet(queryObj_); + } else { + errCode = handle_->ReloadResultSet(keyPrefix_); + } + if (errCode != E_OK) { + handle_->CloseResultSet(); + kvDB_->ReleaseHandle(handle_); + isOpen_ = false; + } + return errCode; +} + +int SQLiteSingleVerForwardCursor::GetCount() const +{ + std::lock_guard lock(isOpenMutex_); + if (!isOpen_) { + return 0; + } + return count_; +} + +int SQLiteSingleVerForwardCursor::GetNext(Entry &entry, bool isCopy) const +{ + std::lock_guard lock(isOpenMutex_); + if (!isOpen_) { + return -E_RESULT_SET_STATUS_INVALID; + } + if (count_ == 0) { + return -E_RESULT_SET_EMPTY; + } + int errCode = handle_->GetNextEntryFromResultSet(entry.key, entry.value, isCopy); + if (errCode != E_OK && errCode != -E_FINISHED) { + handle_->CloseResultSet(); + kvDB_->ReleaseHandle(handle_); + isOpen_ = false; + } + return errCode; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_forward_cursor.h b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_forward_cursor.h new file mode 100644 index 00000000..49ccdaf1 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_forward_cursor.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_SINGLE_VER_FORWARD_CURSOR_H +#define SQLITE_SINGLE_VER_FORWARD_CURSOR_H + +#include + +#include "macro_utils.h" +#include "ikvdb_raw_cursor.h" +#include "sqlite_single_ver_storage_executor.h" +#include "sqlite_single_ver_natural_store.h" + +namespace DistributedDB { +class SQLiteSingleVerForwardCursor : public IKvDBRawCursor { +public: + SQLiteSingleVerForwardCursor(SQLiteSingleVerNaturalStore *kvDB, const Key &keyPrefix); + SQLiteSingleVerForwardCursor(SQLiteSingleVerNaturalStore *kvDB, const QueryObject &queryObj); + ~SQLiteSingleVerForwardCursor() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteSingleVerForwardCursor); + + // Open the raw cursor. + int Open() override; + + // Close the raw cursor before invoking operator delete. + void Close() override; + + // Reload all data. + int Reload() override; + + // Get total entries count. + int GetCount() const override; + + // Get next entry, return errCode if it fails. + int GetNext(Entry &entry, bool isCopy) const override; + +private: + SQLiteSingleVerNaturalStore *kvDB_; + Key keyPrefix_; + QueryObject queryObj_; + mutable SQLiteSingleVerStorageExecutor *handle_; + int count_; + mutable bool isOpen_; + bool isQueryMode_; + mutable std::mutex isOpenMutex_; +}; +} // namespace DistributedDB + +#endif // SQLITE_SINGLE_VER_FORWARD_CURSOR_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store.cpp new file mode 100644 index 00000000..2e01e970 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store.cpp @@ -0,0 +1,2345 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_single_ver_natural_store.h" + +#include +#include +#include + +#include "data_compression.h" +#include "db_common.h" +#include "db_constant.h" +#include "db_dump_helper.h" +#include "db_dfx_adapter.h" +#include "db_errno.h" +#include "generic_single_ver_kv_entry.h" +#include "intercepted_data_impl.h" +#include "kvdb_utils.h" +#include "log_print.h" +#include "platform_specific.h" +#include "schema_object.h" +#include "single_ver_database_oper.h" +#include "storage_engine_manager.h" +#include "sqlite_single_ver_natural_store_connection.h" +#include "value_hash_calc.h" + +namespace DistributedDB { +namespace { + constexpr int WAIT_DELEGATE_CALLBACK_TIME = 100; + + const std::string CREATE_DB_TIME = "createDBTime"; + + // Called when get multiple dev data. + // deviceID is the device which currently being getting. When getting one dev data, deviceID is "". + // dataItems is the DataItems which already be get from DB sorted by timestamp. + // token must not be null. + void ProcessContinueToken(const DeviceID &deviceID, const std::vector &dataItems, int &errCode, + SQLiteSingleVerContinueToken *&token) + { + if (errCode != -E_UNFINISHED) { // Error happened or get data finished. Token should be cleared. + delete token; + token = nullptr; + return; + } + + if (dataItems.empty()) { + errCode = -E_INTERNAL_ERROR; + LOGE("Get data unfinished but dataitems is empty."); + delete token; + token = nullptr; + return; + } + + Timestamp nextBeginTime = dataItems.back().timestamp + 1; + if (nextBeginTime > INT64_MAX) { + nextBeginTime = INT64_MAX; + } + token->SetNextBeginTime(deviceID, nextBeginTime); + return; + } + + // Called when get one dev data. + void ProcessContinueToken(const std::vector &dataItems, int &errCode, + SQLiteSingleVerContinueToken *&token) + { + ProcessContinueToken("", dataItems, errCode, token); + } + + // Called when get query sync data. + // dataItems is the DataItems which already be get from DB sorted by timestamp. + // token must not be null. + void ProcessContinueTokenForQuerySync(const std::vector &dataItems, int &errCode, + SQLiteSingleVerContinueToken *&token) + { + if (errCode != -E_UNFINISHED) { // Error happened or get data finished. Token should be cleared. + delete token; + token = nullptr; + return; + } + + if (dataItems.empty()) { + errCode = -E_INTERNAL_ERROR; + LOGE("Get data unfinished but dataitems is empty."); + delete token; + token = nullptr; + return; + } + + Timestamp nextBeginTime = dataItems.back().timestamp + 1; + if (nextBeginTime > INT64_MAX) { + nextBeginTime = INT64_MAX; + } + bool getDeleteData = ((dataItems.back().flag & DataItem::DELETE_FLAG) != 0); + if (getDeleteData) { + token->FinishGetQueryData(); + token->SetDeletedNextBeginTime("", nextBeginTime); + } else { + token->SetNextBeginTime("", nextBeginTime); + } + return; + } + + void UpdateSecProperties(KvDBProperties &properties, bool isReadOnly, const SchemaObject &savedSchemaObj, + const SQLiteSingleVerStorageEngine *engine) + { + if (isReadOnly) { + properties.SetSchema(savedSchemaObj); + properties.SetBoolProp(KvDBProperties::FIRST_OPEN_IS_READ_ONLY, true); + } + // Update the security option from the storage engine for that + // we will not update the security label and flag for the existed database. + // So the security label and flag are from the existed database. + if (engine == nullptr) { + return; + } + properties.SetIntProp(KvDBProperties::SECURITY_LABEL, engine->GetSecurityOption().securityLabel); + properties.SetIntProp(KvDBProperties::SECURITY_FLAG, engine->GetSecurityOption().securityFlag); + } + + int GetKvEntriesByDataItems(std::vector &entries, std::vector &dataItems) + { + int errCode = E_OK; + for (auto &item : dataItems) { + auto entry = new (std::nothrow) GenericSingleVerKvEntry(); + if (entry == nullptr) { + errCode = -E_OUT_OF_MEMORY; + LOGE("GetKvEntries failed, errCode:%d", errCode); + SingleVerKvEntry::Release(entries); + break; + } + entry->SetEntryData(std::move(item)); + entries.push_back(entry); + } + return errCode; + } + + bool CanHoldDeletedData(const std::vector &dataItems, const DataSizeSpecInfo &dataSizeInfo, + size_t appendLen) + { + bool reachThreshold = false; + size_t blockSize = 0; + for (size_t i = 0; !reachThreshold && i < dataItems.size(); i++) { + blockSize += SQLiteSingleVerStorageExecutor::GetDataItemSerialSize(dataItems[i], appendLen); + reachThreshold = (blockSize >= dataSizeInfo.blockSize * DBConstant::QUERY_SYNC_THRESHOLD); + } + return !reachThreshold; + } +} + +SQLiteSingleVerNaturalStore::SQLiteSingleVerNaturalStore() + : currentMaxTimestamp_(0), + storageEngine_(nullptr), + notificationEventsRegistered_(false), + notificationConflictEventsRegistered_(false), + isInitialized_(false), + isReadOnly_(false), + lifeCycleNotifier_(nullptr), + lifeTimerId_(0), + autoLifeTime_(DBConstant::DEF_LIFE_CYCLE_TIME), + createDBTime_(0), + dataInterceptor_(nullptr), + maxLogSize_(DBConstant::MAX_LOG_SIZE_DEFAULT) +{} + +SQLiteSingleVerNaturalStore::~SQLiteSingleVerNaturalStore() +{ + ReleaseResources(); +} + +std::string SQLiteSingleVerNaturalStore::GetDatabasePath(const KvDBProperties &kvDBProp) +{ + std::string filePath = GetSubDirPath(kvDBProp) + "/" + + DBConstant::MAINDB_DIR + "/" + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + return filePath; +} + +std::string SQLiteSingleVerNaturalStore::GetSubDirPath(const KvDBProperties &kvDBProp) +{ + std::string dataDir = kvDBProp.GetStringProp(KvDBProperties::DATA_DIR, ""); + std::string identifierDir = kvDBProp.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + std::string dirPath = dataDir + "/" + identifierDir + "/" + DBConstant::SINGLE_SUB_DIR; + return dirPath; +} + +int SQLiteSingleVerNaturalStore::SetUserVer(const KvDBProperties &kvDBProp, int version) +{ + OpenDbProperties properties; + properties.uri = GetDatabasePath(kvDBProp); + bool isEncryptedDb = kvDBProp.GetBoolProp(KvDBProperties::ENCRYPTED_MODE, false); + if (isEncryptedDb) { + kvDBProp.GetPassword(properties.cipherType, properties.passwd); + } + + int errCode = SQLiteUtils::SetUserVer(properties, version); + if (errCode != E_OK) { + LOGE("Recover for open db failed in single version:%d", errCode); + } + return errCode; +} + +int SQLiteSingleVerNaturalStore::InitDatabaseContext(const KvDBProperties &kvDBProp, bool isNeedUpdateSecOpt) +{ + int errCode = InitStorageEngine(kvDBProp, isNeedUpdateSecOpt); + if (errCode != E_OK) { + return errCode; + } + InitCurrentMaxStamp(); + return errCode; +} + +int SQLiteSingleVerNaturalStore::RegisterLifeCycleCallback(const DatabaseLifeCycleNotifier ¬ifier) +{ + std::lock_guard lock(lifeCycleMutex_); + int errCode; + if (!notifier) { + if (lifeTimerId_ == 0) { + return E_OK; + } + errCode = StopLifeCycleTimer(); + if (errCode != E_OK) { + LOGE("Stop the life cycle timer failed:%d", errCode); + } + return E_OK; + } + + if (lifeTimerId_ != 0) { + errCode = StopLifeCycleTimer(); + if (errCode != E_OK) { + LOGE("Stop the life cycle timer failed:%d", errCode); + } + } + errCode = StartLifeCycleTimer(notifier); + if (errCode != E_OK) { + LOGE("Register life cycle timer failed:%d", errCode); + } + return errCode; +} + +int SQLiteSingleVerNaturalStore::SetAutoLifeCycleTime(uint32_t time) +{ + std::lock_guard lock(lifeCycleMutex_); + if (lifeTimerId_ == 0) { + autoLifeTime_ = time; + } else { + auto runtimeCxt = RuntimeContext::GetInstance(); + if (runtimeCxt == nullptr) { + return -E_INVALID_ARGS; + } + LOGI("[SingleVer] Set life cycle to %u", time); + int errCode = runtimeCxt->ModifyTimer(lifeTimerId_, time); + if (errCode != E_OK) { + return errCode; + } + autoLifeTime_ = time; + } + return E_OK; +} + +int SQLiteSingleVerNaturalStore::GetSecurityOption(SecurityOption &option) const +{ + bool isMemDb = GetDbProperties().GetBoolProp(KvDBProperties::MEMORY_MODE, false); + if (isMemDb) { + LOGI("[GetSecurityOption] MemDb, no need to get security option"); + option = SecurityOption(); + return E_OK; + } + + option.securityLabel = GetDbProperties().GetSecLabel(); + option.securityFlag = GetDbProperties().GetSecFlag(); + + return E_OK; +} + +namespace { +inline bool OriValueCanBeUse(int errCode) +{ + return (errCode == -E_VALUE_MATCH); +} + +inline bool AmendValueShouldBeUse(int errCode) +{ + return (errCode == -E_VALUE_MATCH_AMENDED); +} + +inline bool IsValueMismatched(int errCode) +{ + return (errCode == -E_VALUE_MISMATCH_FEILD_COUNT || + errCode == -E_VALUE_MISMATCH_FEILD_TYPE || + errCode == -E_VALUE_MISMATCH_CONSTRAINT); +} +} + +int SQLiteSingleVerNaturalStore::CheckValueAndAmendIfNeed(ValueSource sourceType, const Value &oriValue, + Value &amendValue, bool &useAmendValue) const +{ + // oriValue size may already be checked previously, but check here const little + if (oriValue.size() > DBConstant::MAX_VALUE_SIZE) { + return -E_INVALID_ARGS; + } + const SchemaObject &schemaObjRef = MyProp().GetSchemaConstRef(); + if (!schemaObjRef.IsSchemaValid()) { + // Not a schema database, do not need to check more + return E_OK; + } + if (schemaObjRef.GetSchemaType() == SchemaType::JSON) { + ValueObject valueObj; + int errCode = valueObj.Parse(oriValue.data(), oriValue.data() + oriValue.size(), schemaObjRef.GetSkipSize()); + if (errCode != E_OK) { + return -E_INVALID_FORMAT; + } + errCode = schemaObjRef.CheckValueAndAmendIfNeed(sourceType, valueObj); + if (OriValueCanBeUse(errCode)) { + useAmendValue = false; + return E_OK; + } + if (AmendValueShouldBeUse(errCode)) { + std::string amended = valueObj.ToString(); + if (amended.size() > DBConstant::MAX_VALUE_SIZE) { + LOGE("[SqlSinStore][CheckAmendValue] ValueSize=%zu exceed limit after amend.", amended.size()); + return -E_INVALID_FORMAT; + } + amendValue.clear(); + amendValue.assign(amended.begin(), amended.end()); + useAmendValue = true; + return E_OK; + } + if (IsValueMismatched(errCode)) { + return errCode; + } + } else { + int errCode = schemaObjRef.VerifyValue(sourceType, oriValue); + if (errCode == E_OK) { + useAmendValue = false; + return E_OK; + } + } + // Any unexpected wrong + return -E_INVALID_FORMAT; +} + +int SQLiteSingleVerNaturalStore::ClearIncompleteDatabase(const KvDBProperties &kvDBPro) const +{ + std::string dbSubDir = SQLiteSingleVerNaturalStore::GetSubDirPath(kvDBPro); + if (OS::CheckPathExistence(dbSubDir + DBConstant::PATH_POSTFIX_DB_INCOMPLETE)) { + int errCode = DBCommon::RemoveAllFilesOfDirectory(dbSubDir); + if (errCode != E_OK) { + LOGE("Remove the incomplete database dir failed!"); + return -E_REMOVE_FILE; + } + } + return E_OK; +} + +int SQLiteSingleVerNaturalStore::CheckDatabaseRecovery(const KvDBProperties &kvDBProp) +{ + if (kvDBProp.GetBoolProp(KvDBProperties::MEMORY_MODE, false)) { // memory status not need recovery + return E_OK; + } + std::unique_ptr operation = std::make_unique(this, nullptr); + (void)operation->ClearExportedTempFiles(kvDBProp); + int errCode = operation->RekeyRecover(kvDBProp); + if (errCode != E_OK) { + LOGE("Recover from rekey failed in single version:%d", errCode); + return errCode; + } + + errCode = operation->ClearImportTempFile(kvDBProp); + if (errCode != E_OK) { + LOGE("Clear imported temp db failed in single version:%d", errCode); + return errCode; + } + + // Currently, Design for the consistency of directory and file setting secOption + errCode = ClearIncompleteDatabase(kvDBProp); + if (errCode != E_OK) { + LOGE("Clear incomplete database failed in single version:%d", errCode); + return errCode; + } + const std::string dataDir = kvDBProp.GetStringProp(KvDBProperties::DATA_DIR, ""); + const std::string identifierDir = kvDBProp.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + bool isCreate = kvDBProp.GetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + bool isMemoryDb = kvDBProp.GetBoolProp(KvDBProperties::MEMORY_MODE, false); + if (!isMemoryDb) { + errCode = DBCommon::CreateStoreDirectory(dataDir, identifierDir, DBConstant::SINGLE_SUB_DIR, isCreate); + if (errCode != E_OK) { + LOGE("Create single version natural store directory failed:%d", errCode); + } + } + return errCode; +} + +int SQLiteSingleVerNaturalStore::GetAndInitStorageEngine(const KvDBProperties &kvDBProp) +{ + int errCode = E_OK; + storageEngine_ = + static_cast(StorageEngineManager::GetStorageEngine(kvDBProp, errCode)); + if (storageEngine_ == nullptr) { + return errCode; + } + + if (storageEngine_->IsEngineCorrupted()) { + LOGE("[SqlSinStore][GetAndInitStorageEngine] database engine is corrupted, not need continue to open!"); + return -E_INVALID_PASSWD_OR_CORRUPTED_DB; + } + + errCode = InitDatabaseContext(kvDBProp); + if (errCode != E_OK) { + LOGE("[SqlSinStore][Open] Init database context fail! errCode = [%d]", errCode); + } + return errCode; +} + +int SQLiteSingleVerNaturalStore::Open(const KvDBProperties &kvDBProp) +{ + std::lock_guard lock(initialMutex_); + if (isInitialized_) { + return E_OK; // avoid the reopen operation. + } + + int errCode = CheckDatabaseRecovery(kvDBProp); + if (errCode != E_OK) { + return errCode; + } + + bool isReadOnly = false; + SchemaObject savedSchemaObj; + + errCode = GetAndInitStorageEngine(kvDBProp); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = RegisterNotification(); + if (errCode != E_OK) { + LOGE("Register notification failed:%d", errCode); + goto ERROR; + } + + errCode = RemoveAllSubscribe(); + if (errCode != E_OK) { + LOGE("[SqlSinStore][Open] remove subscribe fail! errCode = [%d]", errCode); + goto ERROR; + } + + // Here, the dbfile is created or opened, and upgrade of table structure has done. + // More, Upgrade of schema is also done in upgrader call in InitDatabaseContext, schema in dbfile updated if need. + // If inputSchema is empty, upgrader do nothing of schema, isReadOnly will be true if dbfile contain schema before. + // In this case, we should load the savedSchema for checking value from sync which not restricted by readOnly. + // If inputSchema not empty, isReadOnly will not be true, we should do nothing more. + errCode = DecideReadOnlyBaseOnSchema(kvDBProp, isReadOnly, savedSchemaObj); + if (errCode != E_OK) { + LOGE("[SqlSinStore][Open] DecideReadOnlyBaseOnSchema failed=%d", errCode); + goto ERROR; + } + // Set KvDBProperties and set Schema + MyProp() = kvDBProp; + UpdateSecProperties(MyProp(), isReadOnly, savedSchemaObj, storageEngine_); + + StartSyncer(); + OnKill([this]() { ReleaseResources(); }); + + errCode = SaveCreateDBTimeIfNotExisted(); + if (errCode != E_OK) { + goto ERROR; + } + + InitialLocalDataTimestamp(); + isInitialized_ = true; + isReadOnly_ = isReadOnly; + return E_OK; +ERROR: + ReleaseResources(); + return errCode; +} + +void SQLiteSingleVerNaturalStore::Close() +{ + ReleaseResources(); +} + +GenericKvDBConnection *SQLiteSingleVerNaturalStore::NewConnection(int &errCode) +{ + SQLiteSingleVerNaturalStoreConnection *connection = new (std::nothrow) SQLiteSingleVerNaturalStoreConnection(this); + if (connection == nullptr) { + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + errCode = E_OK; + return connection; +} + +// Get interface type of this kvdb. +int SQLiteSingleVerNaturalStore::GetInterfaceType() const +{ + return SYNC_SVD; +} + +// Get the interface ref-count, in order to access asynchronously. +void SQLiteSingleVerNaturalStore::IncRefCount() +{ + IncObjRef(this); +} + +// Drop the interface ref-count. +void SQLiteSingleVerNaturalStore::DecRefCount() +{ + DecObjRef(this); +} + +// Get the identifier of this kvdb. +std::vector SQLiteSingleVerNaturalStore::GetIdentifier() const +{ + std::string identifier = MyProp().GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + std::vector identifierVect(identifier.begin(), identifier.end()); + return identifierVect; +} + +std::vector SQLiteSingleVerNaturalStore::GetDualTupleIdentifier() const +{ + std::string identifier = MyProp().GetStringProp(KvDBProperties::DUAL_TUPLE_IDENTIFIER_DATA, ""); + std::vector identifierVect(identifier.begin(), identifier.end()); + return identifierVect; +} + +// Get interface for syncer. +IKvDBSyncInterface *SQLiteSingleVerNaturalStore::GetSyncInterface() +{ + return this; +} + +int SQLiteSingleVerNaturalStore::GetMetaData(const Key &key, Value &value) const +{ + if (storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + if (key.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + + int errCode = E_OK; + auto handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + Timestamp timestamp; + errCode = handle->GetKvData(SingleVerDataType::META_TYPE, key, value, timestamp); + ReleaseHandle(handle); + HeartBeatForLifeCycle(); + return errCode; +} + +int SQLiteSingleVerNaturalStore::PutMetaData(const Key &key, const Value &value) +{ + int errCode = SQLiteSingleVerNaturalStore::CheckDataStatus(key, value, false); + if (errCode != E_OK) { + return errCode; + } + + SQLiteSingleVerStorageExecutor *handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->PutKvData(SingleVerDataType::META_TYPE, key, value, 0, nullptr); // meta doesn't need time. + if (errCode != E_OK) { + LOGE("Put kv data err:%d", errCode); + } + + HeartBeatForLifeCycle(); + ReleaseHandle(handle); + return errCode; +} + +// Delete multiple meta data records in a transaction. +int SQLiteSingleVerNaturalStore::DeleteMetaData(const std::vector &keys) +{ + for (const auto &key : keys) { + if (key.empty() || key.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + } + int errCode = E_OK; + auto handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + handle->StartTransaction(TransactType::IMMEDIATE); + errCode = handle->DeleteMetaData(keys); + if (errCode != E_OK) { + handle->Rollback(); + LOGE("[SinStore] DeleteMetaData failed, errCode = %d", errCode); + } else { + handle->Commit(); + } + + ReleaseHandle(handle); + HeartBeatForLifeCycle(); + return errCode; +} + +int SQLiteSingleVerNaturalStore::GetAllMetaKeys(std::vector &keys) const +{ + if (storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->GetAllMetaKeys(keys); + ReleaseHandle(handle); + return errCode; +} + +void SQLiteSingleVerNaturalStore::CommitAndReleaseNotifyData(SingleVerNaturalStoreCommitNotifyData *&committedData, + bool isNeedCommit, int eventType) +{ + if (isNeedCommit) { + if (committedData != nullptr) { + if (!committedData->IsChangedDataEmpty()) { + CommitNotify(eventType, committedData); + } + if (!committedData->IsConflictedDataEmpty()) { + CommitNotify(SQLITE_GENERAL_CONFLICT_EVENT, committedData); + } + } + } + + if (committedData != nullptr) { + committedData->DecObjRef(committedData); + committedData = nullptr; + } +} + +int SQLiteSingleVerNaturalStore::GetSyncData(Timestamp begin, Timestamp end, std::vector &entries, + ContinueToken &continueStmtToken, const DataSizeSpecInfo &dataSizeInfo) const +{ + int errCode = CheckReadDataControlled(); + if (errCode != E_OK) { + LOGE("[GetSyncData] Existed cache database can not read data, errCode = [%d]!", errCode); + return errCode; + } + + std::vector dataItems; + errCode = GetSyncData(begin, end, dataItems, continueStmtToken, dataSizeInfo); + if (errCode != E_OK && errCode != -E_UNFINISHED) { + LOGE("GetSyncData errCode:%d", errCode); + goto ERROR; + } + + for (auto &item : dataItems) { + GenericSingleVerKvEntry *entry = new (std::nothrow) GenericSingleVerKvEntry(); + if (entry == nullptr) { + errCode = -E_OUT_OF_MEMORY; + LOGE("GetSyncData errCode:%d", errCode); + goto ERROR; + } + entry->SetEntryData(std::move(item)); + entries.push_back(entry); + } + +ERROR: + if (errCode != E_OK && errCode != -E_UNFINISHED) { + SingleVerKvEntry::Release(entries); + } + HeartBeatForLifeCycle(); + return errCode; +} + +int SQLiteSingleVerNaturalStore::GetSyncData(Timestamp begin, Timestamp end, std::vector &dataItems, + ContinueToken &continueStmtToken, const DataSizeSpecInfo &dataSizeInfo) const +{ + if (begin >= end || dataSizeInfo.blockSize > DBConstant::MAX_SYNC_BLOCK_SIZE) { + return -E_INVALID_ARGS; + } + + auto token = new (std::nothrow) SQLiteSingleVerContinueToken(begin, end); + if (token == nullptr) { + LOGE("[SQLiteSingleVerNaturalStore][NewToken] Bad alloc."); + return -E_OUT_OF_MEMORY; + } + + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(false, errCode); + if (handle == nullptr) { + goto ERROR; + } + + errCode = handle->GetSyncDataByTimestamp(dataItems, GetAppendedLen(), begin, end, dataSizeInfo); + if (errCode == -E_FINISHED) { + errCode = E_OK; + } + +ERROR: + if (errCode != -E_UNFINISHED && errCode != E_OK) { + dataItems.clear(); + } + ProcessContinueToken(dataItems, errCode, token); + continueStmtToken = static_cast(token); + + ReleaseHandle(handle); + return errCode; +} + +int SQLiteSingleVerNaturalStore::GetSyncData(QueryObject &query, const SyncTimeRange &timeRange, + const DataSizeSpecInfo &dataSizeInfo, ContinueToken &continueStmtToken, + std::vector &entries) const +{ + if (!timeRange.IsValid()) { + return -E_INVALID_ARGS; + } + int errCode = CheckReadDataControlled(); + if (errCode != E_OK) { + LOGE("[GetEntries] Existed cache prevents the reading from query sync[%d]!", errCode); + return errCode; + } + + query.SetSchema(GetSchemaObject()); + auto token = new (std::nothrow) SQLiteSingleVerContinueToken(timeRange, query); + if (token == nullptr) { + LOGE("[SingleVerNStore] Allocate continue token failed."); + return -E_OUT_OF_MEMORY; + } + + int innerCode; + std::vector dataItems; + errCode = GetSyncDataForQuerySync(dataItems, token, dataSizeInfo); + if (errCode != E_OK && errCode != -E_UNFINISHED) { // The code need be sent to outside except new error happened. + goto ERROR; + } + + innerCode = GetKvEntriesByDataItems(entries, dataItems); + if (innerCode != E_OK) { + errCode = innerCode; + delete token; + token = nullptr; + } + +ERROR: + continueStmtToken = static_cast(token); + return errCode; +} + +/** + * Caller must ensure that parameter continueStmtToken is valid. + * If error happened, token will be deleted here. + */ +int SQLiteSingleVerNaturalStore::GetSyncDataForQuerySync(std::vector &dataItems, + SQLiteSingleVerContinueToken *&continueStmtToken, const DataSizeSpecInfo &dataSizeInfo) const +{ + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(false, errCode); + if (handle == nullptr) { + goto ERROR; + } + + // Get query data. + if (!continueStmtToken->IsGetQueryDataFinished()) { + LOGD("[SingleVerNStore] Get query data between %" PRIu64 " and %" PRIu64 ".", + continueStmtToken->GetQueryBeginTime(), continueStmtToken->GetQueryEndTime()); + errCode = handle->GetSyncDataWithQuery(continueStmtToken->GetQuery(), GetAppendedLen(), dataSizeInfo, + std::make_pair(continueStmtToken->GetQueryBeginTime(), continueStmtToken->GetQueryEndTime()), dataItems); + } + + // Get query data finished. + if (errCode == E_OK || errCode == -E_FINISHED) { + // Clear query timeRange. + continueStmtToken->FinishGetQueryData(); + if (!continueStmtToken->IsGetDeletedDataFinished()) { + errCode = -E_UNFINISHED; + // Get delete time next. + if (CanHoldDeletedData(dataItems, dataSizeInfo, GetAppendedLen())) { + LOGD("[SingleVerNStore] Get deleted data between %" PRIu64 " and %" PRIu64 ".", + continueStmtToken->GetDeletedBeginTime(), continueStmtToken->GetDeletedEndTime()); + errCode = handle->GetDeletedSyncDataByTimestamp(dataItems, GetAppendedLen(), + continueStmtToken->GetDeletedBeginTime(), continueStmtToken->GetDeletedEndTime(), dataSizeInfo); + } + } + } + + if (errCode == -E_FINISHED) { + errCode = E_OK; + } + +ERROR: + if (errCode != -E_UNFINISHED && errCode != E_OK) { // Error happened. + dataItems.clear(); + } + ProcessContinueTokenForQuerySync(dataItems, errCode, continueStmtToken); + ReleaseHandle(handle); + return errCode; +} + +int SQLiteSingleVerNaturalStore::GetSyncDataNext(std::vector &entries, + ContinueToken &continueStmtToken, const DataSizeSpecInfo &dataSizeInfo) const +{ + int errCode = CheckReadDataControlled(); + if (errCode != E_OK) { + LOGE("[GetSyncDataNext] Existed cache database can not read data, errCode = [%d]!", errCode); + return errCode; + } + + std::vector dataItems; + auto token = static_cast(continueStmtToken); + if (token->IsQuerySync()) { + errCode = GetSyncDataForQuerySync(dataItems, token, dataSizeInfo); + continueStmtToken = static_cast(token); + } else { + errCode = GetSyncDataNext(dataItems, continueStmtToken, dataSizeInfo); + } + + if (errCode != E_OK && errCode != -E_UNFINISHED) { + LOGE("GetSyncDataNext errCode:%d", errCode); + return errCode; + } + + int innerErrCode = GetKvEntriesByDataItems(entries, dataItems); + if (innerErrCode != E_OK) { + errCode = innerErrCode; + ReleaseContinueToken(continueStmtToken); + } + return errCode; +} + +int SQLiteSingleVerNaturalStore::GetSyncDataNext(std::vector &dataItems, ContinueToken &continueStmtToken, + const DataSizeSpecInfo &dataSizeInfo) const +{ + if (dataSizeInfo.blockSize > DBConstant::MAX_SYNC_BLOCK_SIZE) { + return -E_INVALID_ARGS; + } + + auto token = static_cast(continueStmtToken); + if (token == nullptr || !(token->CheckValid())) { + LOGE("[SingleVerNaturalStore][GetSyncDataNext] invalid continue token."); + return -E_INVALID_ARGS; + } + + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(false, errCode); + if (handle == nullptr) { + ReleaseContinueToken(continueStmtToken); + return errCode; + } + + errCode = handle->GetSyncDataByTimestamp(dataItems, GetAppendedLen(), token->GetQueryBeginTime(), + token->GetQueryEndTime(), dataSizeInfo); + if (errCode == -E_FINISHED) { + errCode = E_OK; + } + + ProcessContinueToken(dataItems, errCode, token); + continueStmtToken = static_cast(token); + + ReleaseHandle(handle); + return errCode; +} + +void SQLiteSingleVerNaturalStore::ReleaseContinueToken(ContinueToken &continueStmtToken) const +{ + auto token = static_cast(continueStmtToken); + if (token == nullptr || !(token->CheckValid())) { + LOGE("[SQLiteSingleVerNaturalStore][ReleaseContinueToken] Input is not a continue token."); + return; + } + delete token; + continueStmtToken = nullptr; +} + +int SQLiteSingleVerNaturalStore::PutSyncDataWithQuery(const QueryObject &query, + const std::vector &entries, const std::string &deviceName) +{ + if (deviceName.length() > DBConstant::MAX_DEV_LENGTH) { + LOGW("Device length is invalid for sync put"); + return -E_INVALID_ARGS; + } + HeartBeatForLifeCycle(); + DeviceInfo deviceInfo = {false, deviceName}; + if (deviceName.empty()) { + deviceInfo.deviceName = "Unknown"; + } + + std::vector dataItems; + for (const auto itemEntry : entries) { + auto *entry = static_cast(itemEntry); + if (entry != nullptr) { + DataItem item; + item.origDev = entry->GetOrigDevice(); + item.flag = entry->GetFlag(); + item.timestamp = entry->GetTimestamp(); + item.writeTimestamp = entry->GetWriteTimestamp(); + entry->GetKey(item.key); + entry->GetValue(item.value); + dataItems.push_back(item); + } + } + + int errCode = SaveSyncDataItems(query, dataItems, deviceInfo, true); // Current is true to check value content + if (errCode != E_OK) { + LOGE("PutSyncData failed:%d", errCode); + } + + return errCode; +} + +void SQLiteSingleVerNaturalStore::GetMaxTimestamp(Timestamp &stamp) const +{ + std::lock_guard lock(maxTimestampMutex_); + stamp = currentMaxTimestamp_; +} + +int SQLiteSingleVerNaturalStore::SetMaxTimestamp(Timestamp timestamp) +{ + std::lock_guard lock(maxTimestampMutex_); + if (timestamp > currentMaxTimestamp_) { + currentMaxTimestamp_ = timestamp; + } + return E_OK; +} + +// In sync procedure, call this function +int SQLiteSingleVerNaturalStore::RemoveDeviceData(const std::string &deviceName, bool isNeedNotify) +{ + LOGI("[RemoveDeviceData] %s{private} rebuild, clear historydata", deviceName.c_str()); + return RemoveDeviceData(deviceName, isNeedNotify, true); +} + +// In local procedure, call this function +int SQLiteSingleVerNaturalStore::RemoveDeviceData(const std::string &deviceName, bool isNeedNotify, bool isInSync) +{ + if (deviceName.empty() || deviceName.length() > DBConstant::MAX_DEV_LENGTH) { + return -E_INVALID_ARGS; + } + if (!isInSync && !CheckWritePermission()) { + return -E_NOT_PERMIT; + } + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(true, errCode); + if (handle == nullptr) { + LOGE("[SingleVerNStore] RemoveDeviceData get handle failed:%d", errCode); + return errCode; + } + uint64_t logFileSize = handle->GetLogFileSize(); + ReleaseHandle(handle); + if (logFileSize > GetMaxLogSize()) { + LOGW("[SingleVerNStore] RmDevData log size[%" PRIu64 "] over the limit", logFileSize); + return -E_LOG_OVER_LIMITS; + } + + // Call the syncer module to erase the water mark. + errCode = EraseDeviceWaterMark(deviceName, true); + if (errCode != E_OK) { + LOGE("[SingleVerNStore] erase water mark failed:%d", errCode); + return errCode; + } + + if (IsExtendedCacheDBMode()) { + errCode = RemoveDeviceDataInCacheMode(deviceName, isNeedNotify); + } else { + errCode = RemoveDeviceDataNormally(deviceName, isNeedNotify); + } + if (errCode != E_OK) { + LOGE("[SingleVerNStore] RemoveDeviceData failed:%d", errCode); + } + + return errCode; +} + +int SQLiteSingleVerNaturalStore::RemoveDeviceDataInCacheMode(const std::string &deviceName, bool isNeedNotify) +{ + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(true, errCode); + if (handle == nullptr) { + LOGE("[SingleVerNStore] RemoveDeviceData get handle failed:%d", errCode); + return errCode; + } + uint64_t recordVersion = GetAndIncreaseCacheRecordVersion(); + LOGI("Remove device data in cache mode isNeedNotify:%d, recordVersion:%" PRIu64, isNeedNotify, recordVersion); + errCode = handle->RemoveDeviceDataInCacheMode(deviceName, isNeedNotify, recordVersion); + if (errCode != E_OK) { + LOGE("[SingleVerNStore] RemoveDeviceDataInCacheMode failed:%d", errCode); + } + ReleaseHandle(handle); + return errCode; +} + +int SQLiteSingleVerNaturalStore::RemoveDeviceDataNormally(const std::string &deviceName, bool isNeedNotify) +{ + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(true, errCode); + if (handle == nullptr) { + LOGE("[SingleVerNStore] RemoveDeviceData get handle failed:%d", errCode); + return errCode; + } + + std::vector entries; + if (isNeedNotify) { + handle->GetAllSyncedEntries(deviceName, entries); + } + + LOGI("Remove device data:%d", isNeedNotify); + errCode = handle->RemoveDeviceData(deviceName); + if (errCode == E_OK && isNeedNotify) { + NotifyRemovedData(entries); + } + ReleaseHandle(handle); + return errCode; +} + +void SQLiteSingleVerNaturalStore::NotifyRemovedData(std::vector &entries) +{ + if (entries.empty() || entries.size() > MAX_TOTAL_NOTIFY_ITEM_SIZE) { + return; + } + + size_t index = 0; + size_t totalSize = 0; + SingleVerNaturalStoreCommitNotifyData *notifyData = nullptr; + while (index < entries.size()) { + if (notifyData == nullptr) { + notifyData = new (std::nothrow) SingleVerNaturalStoreCommitNotifyData; + if (notifyData == nullptr) { + LOGE("Failed to do commit sync removing because of OOM"); + break; + } + } + + // ignore the invalid key. + if (entries[index].key.size() > DBConstant::MAX_KEY_SIZE || + entries[index].value.size() > DBConstant::MAX_VALUE_SIZE) { + index++; + continue; + } + + if ((entries[index].key.size() + entries[index].value.size() + totalSize) > MAX_TOTAL_NOTIFY_DATA_SIZE) { + CommitAndReleaseNotifyData(notifyData, true, SQLITE_GENERAL_NS_SYNC_EVENT); + totalSize = 0; + notifyData = nullptr; + continue; + } + + totalSize += (entries[index].key.size() + entries[index].value.size()); + notifyData->InsertCommittedData(std::move(entries[index]), DataType::DELETE, false); + index++; + } + if (notifyData != nullptr) { + CommitAndReleaseNotifyData(notifyData, true, SQLITE_GENERAL_NS_SYNC_EVENT); + } +} + +SQLiteSingleVerStorageExecutor *SQLiteSingleVerNaturalStore::GetHandle(bool isWrite, int &errCode, + OperatePerm perm) const +{ + if (storageEngine_ == nullptr) { + errCode = -E_INVALID_DB; + return nullptr; + } + // Use for check database corrupted in Asynchronous task, like cache data migrate to main database + if (storageEngine_->IsEngineCorrupted()) { + CorruptNotify(); + errCode = -E_INVALID_PASSWD_OR_CORRUPTED_DB; + LOGI("Handle is corrupted can not to get! errCode = [%d]", errCode); + return nullptr; + } + return static_cast(storageEngine_->FindExecutor(isWrite, perm, errCode)); +} + +void SQLiteSingleVerNaturalStore::ReleaseHandle(SQLiteSingleVerStorageExecutor *&handle) const +{ + if (handle == nullptr) { + return; + } + + if (storageEngine_ != nullptr) { + bool isCorrupted = handle->GetCorruptedStatus(); + StorageExecutor *databaseHandle = handle; + storageEngine_->Recycle(databaseHandle); + handle = nullptr; + if (isCorrupted) { + CorruptNotify(); + } + } +} + +int SQLiteSingleVerNaturalStore::RegisterNotification() +{ + static const std::vector events { + static_cast(SQLITE_GENERAL_NS_LOCAL_PUT_EVENT), + static_cast(SQLITE_GENERAL_NS_PUT_EVENT), + static_cast(SQLITE_GENERAL_NS_SYNC_EVENT), + static_cast(SQLITE_GENERAL_CONFLICT_EVENT), + }; + + for (auto event = events.begin(); event != events.end(); ++event) { + int errCode = RegisterNotificationEventType(*event); + if (errCode == E_OK) { + continue; + } + LOGE("Register single version event %d failed:%d!", *event, errCode); + for (auto iter = events.begin(); iter != event; ++iter) { + UnRegisterNotificationEventType(*iter); + } + return errCode; + } + + notificationEventsRegistered_ = true; + notificationConflictEventsRegistered_ = true; + return E_OK; +} + +void SQLiteSingleVerNaturalStore::ReleaseResources() +{ + SyncAbleKvDB::Close(); + if (notificationEventsRegistered_) { + UnRegisterNotificationEventType(static_cast(SQLITE_GENERAL_NS_SYNC_EVENT)); + UnRegisterNotificationEventType(static_cast(SQLITE_GENERAL_NS_PUT_EVENT)); + UnRegisterNotificationEventType(static_cast(SQLITE_GENERAL_NS_LOCAL_PUT_EVENT)); + notificationEventsRegistered_ = false; + } + + if (notificationConflictEventsRegistered_) { + UnRegisterNotificationEventType(static_cast(SQLITE_GENERAL_CONFLICT_EVENT)); + notificationConflictEventsRegistered_ = false; + } + { + std::lock_guard lock(syncerMutex_); + if (storageEngine_ != nullptr) { + storageEngine_->ClearEnginePasswd(); + (void)StorageEngineManager::ReleaseStorageEngine(storageEngine_); + storageEngine_ = nullptr; + } + } + + isInitialized_ = false; +} + +void SQLiteSingleVerNaturalStore::InitCurrentMaxStamp() +{ + if (storageEngine_ == nullptr) { + return; + } + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(true, errCode); + if (handle == nullptr) { + return; + } + + handle->InitCurrentMaxStamp(currentMaxTimestamp_); + LOGD("Init max timestamp:%" PRIu64, currentMaxTimestamp_); + ReleaseHandle(handle); +} + +void SQLiteSingleVerNaturalStore::InitConflictNotifiedFlag(SingleVerNaturalStoreCommitNotifyData *committedData) +{ + unsigned int conflictFlag = 0; + if (GetRegisterFunctionCount(CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ONLY) != 0) { + conflictFlag |= static_cast(SQLITE_GENERAL_NS_FOREIGN_KEY_ONLY); + } + if (GetRegisterFunctionCount(CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ORIG) != 0) { + conflictFlag |= static_cast(SQLITE_GENERAL_NS_FOREIGN_KEY_ORIG); + } + if (GetRegisterFunctionCount(CONFLICT_SINGLE_VERSION_NS_NATIVE_ALL) != 0) { + conflictFlag |= static_cast(SQLITE_GENERAL_NS_NATIVE_ALL); + } + committedData->SetConflictedNotifiedFlag(static_cast(conflictFlag)); +} + +// Currently this function only suitable to be call from sync in insert_record_from_sync procedure +// Take attention if future coder attempt to call it in other situation procedure +int SQLiteSingleVerNaturalStore::SaveSyncDataItems(const QueryObject &query, std::vector &dataItems, + const DeviceInfo &deviceInfo, bool checkValueContent) +{ + // Sync procedure does not care readOnly Flag + if (storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = E_OK; + for (const auto &item : dataItems) { + // Check only the key and value size + errCode = CheckDataStatus(item.key, item.value, (item.flag & DataItem::DELETE_FLAG) != 0); + if (errCode != E_OK) { + return errCode; + } + } + if (checkValueContent) { + CheckAmendValueContentForSyncProcedure(dataItems); + } + QueryObject queryInner = query; + queryInner.SetSchema(GetSchemaObjectConstRef()); + if (IsExtendedCacheDBMode()) { + errCode = SaveSyncDataToCacheDB(queryInner, dataItems, deviceInfo); + } else { + errCode = SaveSyncDataToMain(queryInner, dataItems, deviceInfo); + } + if (errCode != E_OK) { + LOGE("[SingleVerNStore] SaveSyncDataItems failed:%d", errCode); + } + return errCode; +} + +int SQLiteSingleVerNaturalStore::SaveSyncDataToMain(const QueryObject &query, std::vector &dataItems, + const DeviceInfo &deviceInfo) +{ + auto *committedData = new (std::nothrow) SingleVerNaturalStoreCommitNotifyData; + if (committedData == nullptr) { + LOGE("[SingleVerNStore] Failed to alloc single version notify data"); + return -E_OUT_OF_MEMORY; + } + InitConflictNotifiedFlag(committedData); + Timestamp maxTimestamp = 0; + bool isNeedCommit = false; + int errCode = SaveSyncItems(query, dataItems, deviceInfo, maxTimestamp, committedData); + if (errCode == E_OK) { + isNeedCommit = true; + (void)SetMaxTimestamp(maxTimestamp); + } + + CommitAndReleaseNotifyData(committedData, isNeedCommit, SQLITE_GENERAL_NS_SYNC_EVENT); + return errCode; +} + +// Currently, this function only suitable to be call from sync in insert_record_from_sync procedure +// Take attention if future coder attempt to call it in other situation procedure +int SQLiteSingleVerNaturalStore::SaveSyncItems(const QueryObject &query, std::vector &dataItems, + const DeviceInfo &deviceInfo, Timestamp &maxTimestamp, SingleVerNaturalStoreCommitNotifyData *commitData) const +{ + int errCode = E_OK; + int innerCode = E_OK; + LOGD("[SQLiteSingleVerNaturalStore::SaveSyncData] Get write handle."); + SQLiteSingleVerStorageExecutor *handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + DBDfxAdapter::StartTraceSQL(); + errCode = handle->StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + ReleaseHandle(handle); + DBDfxAdapter::FinishTraceSQL(); + return errCode; + } + bool isPermitForceWrite = !(GetDbProperties().GetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, false)); + errCode = handle->CheckDataWithQuery(query, dataItems, deviceInfo); + if (errCode != E_OK) { + goto END; + } + errCode = handle->PrepareForSavingData(SingleVerDataType::SYNC_TYPE); + if (errCode != E_OK) { + goto END; + } + for (auto &item: dataItems) { + if (item.neglect) { // Do not save this record if it is neglected + continue; + } + errCode = handle->SaveSyncDataItem(item, deviceInfo, maxTimestamp, commitData, isPermitForceWrite); + if (errCode != E_OK && errCode != -E_NOT_FOUND) { + break; + } + } + if (errCode == -E_NOT_FOUND) { + errCode = E_OK; + } + innerCode = handle->ResetForSavingData(SingleVerDataType::SYNC_TYPE); + if (innerCode != E_OK) { + errCode = innerCode; + } +END: + if (errCode == E_OK) { + errCode = handle->Commit(); + } else { + (void)handle->Rollback(); // Keep the error code of the first scene + } + DBDfxAdapter::FinishTraceSQL(); + ReleaseHandle(handle); + return errCode; +} + +int SQLiteSingleVerNaturalStore::SaveSyncDataToCacheDB(const QueryObject &query, std::vector &dataItems, + const DeviceInfo &deviceInfo) +{ + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + Timestamp maxTimestamp = 0; + DBDfxAdapter::StartTraceSQL(); + errCode = SaveSyncItemsInCacheMode(handle, query, dataItems, deviceInfo, maxTimestamp); + if (errCode != E_OK) { + LOGE("[SingleVerNStore] Failed to save sync data in cache mode, err : %d", errCode); + } else { + (void)SetMaxTimestamp(maxTimestamp); + } + DBDfxAdapter::FinishTraceSQL(); + ReleaseHandle(handle); + return errCode; +} + +Timestamp SQLiteSingleVerNaturalStore::GetCurrentTimestamp() +{ + return GetTimestamp(); +} + +int SQLiteSingleVerNaturalStore::InitStorageEngine(const KvDBProperties &kvDBProp, bool isNeedUpdateSecOpt) +{ + OpenDbProperties option; + InitDataBaseOption(kvDBProp, option); + + bool isMemoryMode = kvDBProp.GetBoolProp(KvDBProperties::MEMORY_MODE, false); + StorageEngineAttr poolSize = {1, 1, 1, 16}; // at most 1 write 16 read. + if (isMemoryMode) { + poolSize.minWriteNum = 1; // keep at least one connection. + } + + storageEngine_->SetNotifiedCallback( + [&](int eventType, KvDBCommitNotifyFilterAbleData *committedData) { + if (eventType == SQLITE_GENERAL_FINISH_MIGRATE_EVENT) { + return this->TriggerSync(eventType); + } + auto commitData = static_cast(committedData); + this->CommitAndReleaseNotifyData(commitData, true, eventType); + } + ); + + std::string identifier = kvDBProp.GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + storageEngine_->SetNeedUpdateSecOption(isNeedUpdateSecOpt); + int errCode = storageEngine_->InitSQLiteStorageEngine(poolSize, option, identifier); + if (errCode != E_OK) { + LOGE("Init the sqlite storage engine failed:%d", errCode); + } + return errCode; +} + +int SQLiteSingleVerNaturalStore::Rekey(const CipherPassword &passwd) +{ + // Check the storage engine and try to disable the engine. + if (storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + + std::unique_ptr operation; + + // stop the syncer + int errCode = storageEngine_->TryToDisable(false, OperatePerm::REKEY_MONOPOLIZE_PERM); + if (errCode != E_OK) { + return errCode; + } + LOGI("Stop the syncer for rekey"); + StopSyncer(true); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); // wait for 5 ms + errCode = storageEngine_->TryToDisable(true, OperatePerm::REKEY_MONOPOLIZE_PERM); + if (errCode != E_OK) { + LOGE("[Rekey] Failed to disable the database: %d", errCode); + goto END; + } + + if (storageEngine_->GetEngineState() != EngineState::MAINDB) { + LOGE("Rekey is not supported while cache exists! state = [%d]", storageEngine_->GetEngineState()); + errCode = (storageEngine_->GetEngineState() == EngineState::CACHEDB) ? -E_NOT_SUPPORT : -E_BUSY; + goto END; + } + + operation = std::make_unique(this, storageEngine_); + LOGI("Operation rekey"); + errCode = operation->Rekey(passwd); +END: + // Only maindb state have existed handle, if rekey fail other state will create error cache db + // Abort can forbid get new handle, requesting handle will return BUSY and nullptr handle + if (errCode != -E_FORBID_CACHEDB) { + storageEngine_->Enable(OperatePerm::REKEY_MONOPOLIZE_PERM); + } else { + storageEngine_->Abort(OperatePerm::REKEY_MONOPOLIZE_PERM); + errCode = E_OK; + } + StartSyncer(); + return errCode; +} + +int SQLiteSingleVerNaturalStore::Export(const std::string &filePath, const CipherPassword &passwd) +{ + if (storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + if (MyProp().GetBoolProp(KvDBProperties::MEMORY_MODE, false)) { + return -E_NOT_SUPPORT; + } + + // Exclusively write resources + std::string localDev; + int errCode = GetLocalIdentity(localDev); + if (errCode != E_OK) { + LOGE("Get local dev id err:%d", errCode); + localDev.resize(0); + } + + // The write handle is applied to prevent writing data during the export process. + SQLiteSingleVerStorageExecutor *handle = GetHandle(true, errCode, OperatePerm::NORMAL_PERM); + if (handle == nullptr) { + return errCode; + } + + // forbid migrate by hold write handle not release + if (storageEngine_->GetEngineState() != EngineState::MAINDB) { + LOGE("Not support export when cacheDB existed! state = [%d]", storageEngine_->GetEngineState()); + errCode = (storageEngine_->GetEngineState() == EngineState::CACHEDB) ? -E_NOT_SUPPORT : -E_BUSY; + ReleaseHandle(handle); + return errCode; + } + + std::unique_ptr operation = std::make_unique(this, storageEngine_); + operation->SetLocalDevId(localDev); + LOGI("Begin export the kv store"); + errCode = operation->Export(filePath, passwd); + + ReleaseHandle(handle); + return errCode; +} + +int SQLiteSingleVerNaturalStore::Import(const std::string &filePath, const CipherPassword &passwd) +{ + if (storageEngine_ == nullptr) { + return -E_INVALID_DB; + } + if (MyProp().GetBoolProp(KvDBProperties::MEMORY_MODE, false)) { + return -E_NOT_SUPPORT; + } + + std::string localDev; + int errCode = GetLocalIdentity(localDev); + if (errCode != E_OK) { + LOGE("Failed to GetLocalIdentity!"); + localDev.resize(0); + } + + // stop the syncer + errCode = storageEngine_->TryToDisable(false, OperatePerm::IMPORT_MONOPOLIZE_PERM); + if (errCode != E_OK) { + return errCode; + } + StopSyncer(true); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); // wait for 5 ms + std::unique_ptr operation; + + errCode = storageEngine_->TryToDisable(true, OperatePerm::IMPORT_MONOPOLIZE_PERM); + if (errCode != E_OK) { + LOGE("[Import] Failed to disable the database: %d", errCode); + goto END; + } + + if (storageEngine_->GetEngineState() != EngineState::MAINDB) { + LOGE("Not support import when cacheDB existed! state = [%d]", storageEngine_->GetEngineState()); + errCode = (storageEngine_->GetEngineState() == EngineState::CACHEDB) ? -E_NOT_SUPPORT : -E_BUSY; + goto END; + } + + operation = std::make_unique(this, storageEngine_); + operation->SetLocalDevId(localDev); + errCode = operation->Import(filePath, passwd); + if (errCode != E_OK) { + goto END; + } + + // Save create db time. + storageEngine_->Enable(OperatePerm::IMPORT_MONOPOLIZE_PERM); + errCode = SaveCreateDBTime(); + +END: + // restore the storage engine and the syncer. + storageEngine_->Enable(OperatePerm::IMPORT_MONOPOLIZE_PERM); + StartSyncer(); + return errCode; +} + +bool SQLiteSingleVerNaturalStore::CheckWritePermission() const +{ + return !isReadOnly_; +} + +SchemaObject SQLiteSingleVerNaturalStore::GetSchemaInfo() const +{ + return MyProp().GetSchemaConstRef(); +} + +SchemaObject SQLiteSingleVerNaturalStore::GetSchemaObject() const +{ + return MyProp().GetSchema(); +} + +const SchemaObject &SQLiteSingleVerNaturalStore::GetSchemaObjectConstRef() const +{ + return MyProp().GetSchemaConstRef(); +} + +bool SQLiteSingleVerNaturalStore::CheckCompatible(const std::string &schema, uint8_t type) const +{ + const SchemaObject &localSchema = MyProp().GetSchemaConstRef(); + if (!localSchema.IsSchemaValid() || schema.empty() || ReadSchemaType(type) == SchemaType::NONE) { + // If at least one of local or remote is normal-kvdb, then allow sync + LOGI("IsLocalSchemaDb=%d, IsRemoteSchemaDb=%d.", localSchema.IsSchemaValid(), !schema.empty()); + return true; + } + // Here both are schema-db, check their compatibility mutually + SchemaObject remoteSchema; + int errCode = remoteSchema.ParseFromSchemaString(schema); + if (errCode != E_OK) { + // Consider: if the parse errCode is SchemaVersionNotSupport, we can consider allow sync if schemaType equal. + LOGE("Parse remote schema fail, errCode=%d.", errCode); + return false; + } + // First, Compare remoteSchema based on localSchema + errCode = localSchema.CompareAgainstSchemaObject(remoteSchema); + if (errCode != -E_SCHEMA_UNEQUAL_INCOMPATIBLE) { + LOGI("Remote(Maybe newer) compatible based on local, result=%d.", errCode); + return true; + } + // Second, Compare localSchema based on remoteSchema + errCode = remoteSchema.CompareAgainstSchemaObject(localSchema); + if (errCode != -E_SCHEMA_UNEQUAL_INCOMPATIBLE) { + LOGI("Local(Newer) compatible based on remote, result=%d.", errCode); + return true; + } + LOGE("Local incompatible with remote mutually."); + return false; +} + +void SQLiteSingleVerNaturalStore::InitDataBaseOption(const KvDBProperties &kvDBProp, OpenDbProperties &option) +{ + std::string uri = GetDatabasePath(kvDBProp); + bool isMemoryDb = kvDBProp.GetBoolProp(KvDBProperties::MEMORY_MODE, false); + if (isMemoryDb) { + std::string identifierDir = kvDBProp.GetStringProp(KvDBProperties::IDENTIFIER_DIR, ""); + uri = identifierDir + DBConstant::SQLITE_MEMDB_IDENTIFY; + LOGD("Begin create memory natural store database"); + } + std::string subDir = GetSubDirPath(kvDBProp); + CipherType cipherType; + CipherPassword passwd; + kvDBProp.GetPassword(cipherType, passwd); + std::string schemaStr = kvDBProp.GetSchema().ToSchemaString(); + + bool isCreateNecessary = kvDBProp.GetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + std::vector createTableSqls; + + SecurityOption securityOpt; + if (RuntimeContext::GetInstance()->IsProcessSystemApiAdapterValid()) { + securityOpt.securityLabel = kvDBProp.GetSecLabel(); + securityOpt.securityFlag = kvDBProp.GetSecFlag(); + } + + option = {uri, isCreateNecessary, isMemoryDb, createTableSqls, cipherType, passwd, schemaStr, subDir, securityOpt}; + option.conflictReslovePolicy = kvDBProp.GetIntProp(KvDBProperties::CONFLICT_RESOLVE_POLICY, DEFAULT_LAST_WIN); + option.createDirByStoreIdOnly = kvDBProp.GetBoolProp(KvDBProperties::CREATE_DIR_BY_STORE_ID_ONLY, false); +} + +int SQLiteSingleVerNaturalStore::TransObserverTypeToRegisterFunctionType( + int observerType, RegisterFuncType &type) const +{ + static constexpr TransPair transMap[] = { + { static_cast(SQLITE_GENERAL_NS_PUT_EVENT), OBSERVER_SINGLE_VERSION_NS_PUT_EVENT }, + { static_cast(SQLITE_GENERAL_NS_SYNC_EVENT), OBSERVER_SINGLE_VERSION_NS_SYNC_EVENT }, + { static_cast(SQLITE_GENERAL_NS_LOCAL_PUT_EVENT), OBSERVER_SINGLE_VERSION_NS_LOCAL_EVENT }, + { static_cast(SQLITE_GENERAL_CONFLICT_EVENT), OBSERVER_SINGLE_VERSION_NS_CONFLICT_EVENT }, + }; + auto funcType = GetFuncType(observerType, transMap, sizeof(transMap) / sizeof(TransPair)); + if (funcType == REGISTER_FUNC_TYPE_MAX) { + return -E_NOT_SUPPORT; + } + type = funcType; + return E_OK; +} + +int SQLiteSingleVerNaturalStore::TransConflictTypeToRegisterFunctionType( + int conflictType, RegisterFuncType &type) const +{ + static constexpr TransPair transMap[] = { + { static_cast(SQLITE_GENERAL_NS_FOREIGN_KEY_ONLY), CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ONLY }, + { static_cast(SQLITE_GENERAL_NS_FOREIGN_KEY_ORIG), CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ORIG }, + { static_cast(SQLITE_GENERAL_NS_NATIVE_ALL), CONFLICT_SINGLE_VERSION_NS_NATIVE_ALL }, + }; + auto funcType = GetFuncType(conflictType, transMap, sizeof(transMap) / sizeof(TransPair)); + if (funcType == REGISTER_FUNC_TYPE_MAX) { + return -E_NOT_SUPPORT; + } + type = funcType; + return E_OK; +} + +RegisterFuncType SQLiteSingleVerNaturalStore::GetFuncType(int index, const TransPair *transMap, int32_t len) +{ + int32_t head = 0; + int32_t end = len - 1; + while (head <= end) { + int32_t mid = (head + end) / 2; + if (transMap[mid].index < index) { + head = mid + 1; + continue; + } + if (transMap[mid].index > index) { + end = mid - 1; + continue; + } + return transMap[mid].funcType; + } + return REGISTER_FUNC_TYPE_MAX; +} + +int SQLiteSingleVerNaturalStore::GetSchema(SchemaObject &schema) const +{ + int errCode = E_OK; + auto handle = GetHandle(true, errCode); // Only open kvdb use, no competition for write handle + if (handle == nullptr) { + return errCode; + } + + Timestamp timestamp; + std::string schemaKey = DBConstant::SCHEMA_KEY; + Key key(schemaKey.begin(), schemaKey.end()); + Value value; + errCode = handle->GetKvData(SingleVerDataType::META_TYPE, key, value, timestamp); + if (errCode == E_OK) { + std::string schemaValue(value.begin(), value.end()); + errCode = schema.ParseFromSchemaString(schemaValue); + } else { + LOGI("[SqlSinStore] Get schema error:%d.", errCode); + } + ReleaseHandle(handle); + return errCode; +} + +int SQLiteSingleVerNaturalStore::DecideReadOnlyBaseOnSchema(const KvDBProperties &kvDBProp, bool &isReadOnly, + SchemaObject &savedSchemaObj) const +{ + // Check whether it is a memory db + if (kvDBProp.GetBoolProp(KvDBProperties::MEMORY_MODE, false)) { + isReadOnly = false; + return E_OK; + } + SchemaObject inputSchemaObj = kvDBProp.GetSchema(); + if (!inputSchemaObj.IsSchemaValid()) { + int errCode = GetSchema(savedSchemaObj); + if (errCode != E_OK && errCode != -E_NOT_FOUND) { + LOGE("[SqlSinStore][DecideReadOnly] GetSchema fail=%d.", errCode); + return errCode; + } + if (savedSchemaObj.IsSchemaValid()) { + isReadOnly = true; + return E_OK; + } + } + // An valid schema will not lead to readonly + isReadOnly = false; + return E_OK; +} + +void SQLiteSingleVerNaturalStore::InitialLocalDataTimestamp() +{ + Timestamp timestamp = GetCurrentTimestamp(); + + int errCode = E_OK; + auto handle = GetHandle(true, errCode); + if (handle == nullptr) { + return; + } + + errCode = handle->UpdateLocalDataTimestamp(timestamp); + if (errCode != E_OK) { + LOGE("Update the timestamp for local data failed:%d", errCode); + } + ReleaseHandle(handle); +} + +const KvDBProperties &SQLiteSingleVerNaturalStore::GetDbProperties() const +{ + return GetMyProperties(); +} + +int SQLiteSingleVerNaturalStore::RemoveKvDB(const KvDBProperties &properties) +{ + // To avoid leakage, the engine resources are forced to be released + const std::string identifier = properties.GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + (void)StorageEngineManager::ForceReleaseStorageEngine(identifier); + + // Only care the data directory and the db name. + std::string storeOnlyDir; + std::string storeDir; + GenericKvDB::GetStoreDirectory(properties, KvDBProperties::SINGLE_VER_TYPE, storeDir, storeOnlyDir); + + const std::vector> dbDir { + {DBConstant::MAINDB_DIR, DBConstant::SINGLE_VER_DATA_STORE}, + {DBConstant::METADB_DIR, DBConstant::SINGLE_VER_META_STORE}, + {DBConstant::CACHEDB_DIR, DBConstant::SINGLE_VER_CACHE_STORE}}; + + bool isAllNotFound = true; + for (const auto &item : dbDir) { + std::string currentDir = storeDir + item.first + "/"; + std::string currentOnlyDir = storeOnlyDir + item.first + "/"; + int errCode = KvDBUtils::RemoveKvDB(currentDir, currentOnlyDir, item.second); + if (errCode != -E_NOT_FOUND) { + if (errCode != E_OK) { + return errCode; + } + isAllNotFound = false; + } + }; + if (isAllNotFound) { + return -E_NOT_FOUND; + } + + int errCode = DBCommon::RemoveAllFilesOfDirectory(storeDir, true); + if (errCode != E_OK) { + return errCode; + } + errCode = DBCommon::RemoveAllFilesOfDirectory(storeOnlyDir, true); + if (errCode != E_OK) { + return errCode; + } + return errCode; +} + +int SQLiteSingleVerNaturalStore::GetKvDBSize(const KvDBProperties &properties, uint64_t &size) const +{ + std::string storeOnlyIdentDir; + std::string storeIdentDir; + GenericKvDB::GetStoreDirectory(properties, KvDBProperties::SINGLE_VER_TYPE, storeIdentDir, storeOnlyIdentDir); + const std::vector> dbDir { + {DBConstant::MAINDB_DIR, DBConstant::SINGLE_VER_DATA_STORE}, + {DBConstant::METADB_DIR, DBConstant::SINGLE_VER_META_STORE}, + {DBConstant::CACHEDB_DIR, DBConstant::SINGLE_VER_CACHE_STORE}}; + int errCode = -E_NOT_FOUND; + for (const auto &item : dbDir) { + std::string storeDir = storeIdentDir + item.first; + std::string storeOnlyDir = storeOnlyIdentDir + item.first; + int err = KvDBUtils::GetKvDbSize(storeDir, storeOnlyDir, item.second, size); + if (err != -E_NOT_FOUND && err != E_OK) { + return err; + } + if (err == E_OK) { + errCode = E_OK; + } + } + return errCode; +} + +KvDBProperties &SQLiteSingleVerNaturalStore::GetDbPropertyForUpdate() +{ + return MyProp(); +} + +void SQLiteSingleVerNaturalStore::HeartBeatForLifeCycle() const +{ + std::lock_guard lock(lifeCycleMutex_); + int errCode = ResetLifeCycleTimer(); + if (errCode != E_OK) { + LOGE("Heart beat for life cycle failed:%d", errCode); + } +} + +int SQLiteSingleVerNaturalStore::StartLifeCycleTimer(const DatabaseLifeCycleNotifier ¬ifier) const +{ + auto runtimeCxt = RuntimeContext::GetInstance(); + if (runtimeCxt == nullptr) { + return -E_INVALID_ARGS; + } + RefObject::IncObjRef(this); + TimerId timerId = 0; + int errCode = runtimeCxt->SetTimer(autoLifeTime_, + [this](TimerId id) -> int { + std::lock_guard lock(lifeCycleMutex_); + if (lifeCycleNotifier_) { + std::string identifier; + if (GetMyProperties().GetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, false)) { + identifier = GetMyProperties().GetStringProp(KvDBProperties::DUAL_TUPLE_IDENTIFIER_DATA, ""); + } else { + identifier = GetMyProperties().GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + } + auto userId = GetMyProperties().GetStringProp(DBProperties::USER_ID, ""); + lifeCycleNotifier_(identifier, userId); + } + return 0; + }, + [this]() { + int ret = RuntimeContext::GetInstance()->ScheduleTask([this]() { + RefObject::DecObjRef(this); + }); + if (ret != E_OK) { + LOGE("SQLiteSingleVerNaturalStore timer finalizer ScheduleTask, errCode %d", ret); + } + }, + timerId); + if (errCode != E_OK) { + lifeTimerId_ = 0; + LOGE("SetTimer failed:%d", errCode); + RefObject::DecObjRef(this); + return errCode; + } + + lifeCycleNotifier_ = notifier; + lifeTimerId_ = timerId; + return E_OK; +} + +int SQLiteSingleVerNaturalStore::ResetLifeCycleTimer() const +{ + if (lifeTimerId_ == 0) { + return E_OK; + } + auto lifeNotifier = lifeCycleNotifier_; + lifeCycleNotifier_ = nullptr; + int errCode = StopLifeCycleTimer(); + if (errCode != E_OK) { + LOGE("[Reset timer]Stop the life cycle timer failed:%d", errCode); + } + return StartLifeCycleTimer(lifeNotifier); +} + +int SQLiteSingleVerNaturalStore::StopLifeCycleTimer() const +{ + auto runtimeCxt = RuntimeContext::GetInstance(); + if (runtimeCxt == nullptr) { + return -E_INVALID_ARGS; + } + if (lifeTimerId_ != 0) { + TimerId timerId = lifeTimerId_; + lifeTimerId_ = 0; + runtimeCxt->RemoveTimer(timerId, false); + } + return E_OK; +} + +bool SQLiteSingleVerNaturalStore::IsDataMigrating() const +{ + if (storageEngine_ == nullptr) { + return false; + } + + if (storageEngine_->IsMigrating()) { + LOGD("Migrating now."); + return true; + } + return false; +} + +void SQLiteSingleVerNaturalStore::SetConnectionFlag(bool isExisted) const +{ + if (storageEngine_ != nullptr) { + storageEngine_->SetConnectionFlag(isExisted); + } +} + +int SQLiteSingleVerNaturalStore::TriggerToMigrateData() const +{ + RefObject::IncObjRef(this); + int errCode = RuntimeContext::GetInstance()->ScheduleTask( + std::bind(&SQLiteSingleVerNaturalStore::AsyncDataMigration, this)); + if (errCode != E_OK) { + RefObject::DecObjRef(this); + LOGE("[SingleVerNStore] Trigger to migrate data failed : %d.", errCode); + } + return errCode; +} + +bool SQLiteSingleVerNaturalStore::IsCacheDBMode() const +{ + if (storageEngine_ == nullptr) { + LOGE("[SingleVerNStore] IsCacheDBMode storage engine is invalid."); + return false; + } + EngineState engineState = storageEngine_->GetEngineState(); + return (engineState == CACHEDB); +} + +bool SQLiteSingleVerNaturalStore::IsExtendedCacheDBMode() const +{ + if (storageEngine_ == nullptr) { + LOGE("[SingleVerNStore] storage engine is invalid."); + return false; + } + EngineState engineState = storageEngine_->GetEngineState(); + return (engineState == CACHEDB || engineState == MIGRATING || engineState == ATTACHING); +} + +int SQLiteSingleVerNaturalStore::CheckReadDataControlled() const +{ + if (IsExtendedCacheDBMode()) { + int err = IsCacheDBMode() ? -E_EKEYREVOKED : -E_BUSY; + LOGE("Existed cache database can not read data, errCode = [%d]!", err); + return err; + } + return E_OK; +} + +void SQLiteSingleVerNaturalStore::IncreaseCacheRecordVersion() const +{ + if (storageEngine_ == nullptr) { + LOGE("[SingleVerNStore] Increase cache version storage engine is invalid."); + return; + } + storageEngine_->IncreaseCacheRecordVersion(); +} + +uint64_t SQLiteSingleVerNaturalStore::GetCacheRecordVersion() const +{ + if (storageEngine_ == nullptr) { + LOGE("[SingleVerNStore] Get cache version storage engine is invalid."); + return 0; + } + return storageEngine_->GetCacheRecordVersion(); +} + +uint64_t SQLiteSingleVerNaturalStore::GetAndIncreaseCacheRecordVersion() const +{ + if (storageEngine_ == nullptr) { + LOGE("[SingleVerNStore] Get cache version storage engine is invalid."); + return 0; + } + return storageEngine_->GetAndIncreaseCacheRecordVersion(); +} + +void SQLiteSingleVerNaturalStore::AsyncDataMigration() const +{ + // Delay a little time to ensure the completion of the delegate callback + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_DELEGATE_CALLBACK_TIME)); + bool isLocked = RuntimeContext::GetInstance()->IsAccessControlled(); + if (!isLocked) { + LOGI("Begin to migrate cache data to manDb asynchronously!"); + (void)StorageEngineManager::ExecuteMigration(storageEngine_); + } + + RefObject::DecObjRef(this); +} + +void SQLiteSingleVerNaturalStore::CheckAmendValueContentForSyncProcedure(std::vector &dataItems) const +{ + const SchemaObject &schemaObjRef = MyProp().GetSchemaConstRef(); + if (!schemaObjRef.IsSchemaValid()) { + // Not a schema database, do not need to check more + return; + } + uint32_t deleteCount = 0; + uint32_t amendCount = 0; + uint32_t neglectCount = 0; + for (auto &eachItem : dataItems) { + if ((eachItem.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG || + (eachItem.flag & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) == DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) { + // Delete record not concerned + deleteCount++; + continue; + } + bool useAmendValue = false; + int errCode = CheckValueAndAmendIfNeed(ValueSource::FROM_SYNC, eachItem.value, eachItem.value, useAmendValue); + if (errCode != E_OK) { + eachItem.neglect = true; + neglectCount++; + continue; + } + if (useAmendValue) { + amendCount++; + } + } + LOGI("[SqlSinStore][CheckAmendForSync] OriCount=%zu, DeleteCount=%u, AmendCount=%u, NeglectCount=%u", + dataItems.size(), deleteCount, amendCount, neglectCount); +} + +int SQLiteSingleVerNaturalStore::SaveSyncItemsInCacheMode(SQLiteSingleVerStorageExecutor *handle, + const QueryObject &query, std::vector &dataItems, const DeviceInfo &deviceInfo, + Timestamp &maxTimestamp) const +{ + int errCode = handle->StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + return errCode; + } + + int innerCode; + const uint64_t recordVersion = GetCacheRecordVersion(); + errCode = handle->PrepareForSavingCacheData(SingleVerDataType::SYNC_TYPE); + if (errCode != E_OK) { + goto END; + } + + for (auto &item : dataItems) { + errCode = handle->SaveSyncDataItemInCacheMode(item, deviceInfo, maxTimestamp, recordVersion, query); + if (errCode != E_OK && errCode != -E_NOT_FOUND) { + break; + } + } + + if (errCode == -E_NOT_FOUND) { + errCode = E_OK; + } + + innerCode = handle->ResetForSavingCacheData(SingleVerDataType::SYNC_TYPE); + if (innerCode != E_OK) { + errCode = innerCode; + } +END: + if (errCode == E_OK) { + errCode = handle->Commit(); + storageEngine_->IncreaseCacheRecordVersion(); + } else { + (void)handle->Rollback(); // Keep the error code of the first scene + } + return errCode; +} + +void SQLiteSingleVerNaturalStore::NotifyRemotePushFinished(const std::string &targetId) const +{ + std::string identifier = DBCommon::VectorToHexString(GetIdentifier()); + LOGI("label:%s sourceTarget: %s{private} push finished", identifier.c_str(), targetId.c_str()); + NotifyRemotePushFinishedInner(targetId); +} + +int SQLiteSingleVerNaturalStore::GetDatabaseCreateTimestamp(Timestamp &outTime) const +{ + // Found in memory. + { + std::lock_guard autoLock(createDBTimeMutex_); + if (createDBTime_ != 0) { + outTime = createDBTime_; + return E_OK; + } + } + + const Key key(CREATE_DB_TIME.begin(), CREATE_DB_TIME.end()); + Value value; + int errCode = GetMetaData(key, value); + if (errCode != E_OK) { + LOGD("GetDatabaseCreateTimestamp failed, errCode = %d.", errCode); + return errCode; + } + + Timestamp createDBTime = 0; + Parcel parcel(value.data(), value.size()); + (void)parcel.ReadUInt64(createDBTime); + if (parcel.IsError()) { + return -E_INVALID_ARGS; + } + outTime = createDBTime; + std::lock_guard autoLock(createDBTimeMutex_); + createDBTime_ = createDBTime; + return E_OK; +} + +int SQLiteSingleVerNaturalStore::CheckIntegrity() const +{ + int errCode = E_OK; + auto handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->CheckIntegrity(); + ReleaseHandle(handle); + return errCode; +} + +int SQLiteSingleVerNaturalStore::SaveCreateDBTime() +{ + Timestamp createDBTime = GetCurrentTimestamp(); + const Key key(CREATE_DB_TIME.begin(), CREATE_DB_TIME.end()); + Value value(Parcel::GetUInt64Len()); + Parcel parcel(value.data(), Parcel::GetUInt64Len()); + (void)parcel.WriteUInt64(createDBTime); + if (parcel.IsError()) { + LOGE("SaveCreateDBTime failed, something wrong in parcel."); + return -E_PARSE_FAIL; + } + + int errCode = PutMetaData(key, value); + if (errCode != E_OK) { + LOGE("SaveCreateDBTime failed, errCode = %d", errCode); + return errCode; + } + + // save in memory. + std::lock_guard autoLock(createDBTimeMutex_); + createDBTime_ = createDBTime; + return errCode; +} + +int SQLiteSingleVerNaturalStore::SaveCreateDBTimeIfNotExisted() +{ + Timestamp createDBTime = 0; + int errCode = GetDatabaseCreateTimestamp(createDBTime); + if (errCode == -E_NOT_FOUND) { + errCode = SaveCreateDBTime(); + } + if (errCode != E_OK) { + LOGE("SaveCreateDBTimeIfNotExisted failed, errCode=%d.", errCode); + } + return errCode; +} + +int SQLiteSingleVerNaturalStore::DeleteMetaDataByPrefixKey(const Key &keyPrefix) const +{ + if (keyPrefix.empty() || keyPrefix.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + + int errCode = E_OK; + auto handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->DeleteMetaDataByPrefixKey(keyPrefix); + if (errCode != E_OK) { + LOGE("[SinStore] DeleteMetaData by prefix key failed, errCode = %d", errCode); + } + + ReleaseHandle(handle); + HeartBeatForLifeCycle(); + return errCode; +} + +int SQLiteSingleVerNaturalStore::GetCompressionOption(bool &needCompressOnSync, uint8_t &compressionRate) const +{ + needCompressOnSync = GetDbProperties().GetBoolProp(KvDBProperties::COMPRESS_ON_SYNC, false); + compressionRate = GetDbProperties().GetIntProp(KvDBProperties::COMPRESSION_RATE, + DBConstant::DEFAULT_COMPTRESS_RATE); + return E_OK; +} + +int SQLiteSingleVerNaturalStore::GetCompressionAlgo(std::set &algorithmSet) const +{ + algorithmSet.clear(); + DataCompression::GetCompressionAlgo(algorithmSet); + return E_OK; +} + +int SQLiteSingleVerNaturalStore::CheckAndInitQueryCondition(QueryObject &query) const +{ + const SchemaObject &localSchema = MyProp().GetSchemaConstRef(); + if (localSchema.GetSchemaType() != SchemaType::NONE && localSchema.GetSchemaType() != SchemaType::JSON) { + // Flatbuffer schema is not support subscribe + return -E_NOT_SUPPORT; + } + query.SetSchema(localSchema); + + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(false, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->CheckQueryObjectLegal(query); + if (errCode != E_OK) { + LOGE("Check query condition failed [%d]!", errCode); + } + ReleaseHandle(handle); + return errCode; +} + +void SQLiteSingleVerNaturalStore::SetDataInterceptor(const PushDataInterceptor &interceptor) +{ + std::unique_lock lock(dataInterceptorMutex_); + dataInterceptor_ = interceptor; +} + +int SQLiteSingleVerNaturalStore::InterceptData(std::vector &entries, const std::string &sourceID, + const std::string &targetID) const +{ + PushDataInterceptor interceptor = nullptr; + { + std::shared_lock lock(dataInterceptorMutex_); + if (dataInterceptor_ == nullptr) { + return E_OK; + } + interceptor = dataInterceptor_; + } + + InterceptedDataImpl data(entries, [this](const Value &newValue) -> int { + bool useAmendValue = false; + Value amendValue = newValue; + return this->CheckValueAndAmendIfNeed(ValueSource::FROM_LOCAL, newValue, amendValue, useAmendValue); + } + ); + + int errCode = interceptor(data, sourceID, targetID); + if (data.IsError()) { + SingleVerKvEntry::Release(entries); + LOGE("Intercept data failed:%d.", errCode); + return -E_INTERCEPT_DATA_FAIL; + } + return E_OK; +} + +int SQLiteSingleVerNaturalStore::AddSubscribe(const std::string &subscribeId, const QueryObject &query, + bool needCacheSubscribe) +{ + const SchemaObject &localSchema = MyProp().GetSchemaConstRef(); + if (localSchema.GetSchemaType() != SchemaType::NONE && localSchema.GetSchemaType() != SchemaType::JSON) { + // Flatbuffer schema is not support subscribe + return -E_NOT_SUPPORT; + } + QueryObject queryInner = query; + queryInner.SetSchema(localSchema); + if (IsExtendedCacheDBMode() && needCacheSubscribe) { // cache auto subscribe when engine state is in CACHEDB mode + LOGI("Cache subscribe query and return ok when in cacheDB."); + storageEngine_->CacheSubscribe(subscribeId, queryInner); + return E_OK; + } + + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + ReleaseHandle(handle); + return errCode; + } + + errCode = handle->AddSubscribeTrigger(queryInner, subscribeId); + if (errCode != E_OK) { + LOGE("Add subscribe trigger failed: %d", errCode); + (void)handle->Rollback(); + } else { + errCode = handle->Commit(); + } + ReleaseHandle(handle); + return errCode; +} + +int SQLiteSingleVerNaturalStore::RemoveSubscribe(const std::vector &subscribeIds) +{ + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + ReleaseHandle(handle); + return errCode; + } + errCode = handle->RemoveSubscribeTrigger(subscribeIds); + if (errCode != E_OK) { + LOGE("Remove subscribe trigger failed: %d", errCode); + goto ERR; + } + errCode = handle->RemoveSubscribeTriggerWaterMark(subscribeIds); + if (errCode != E_OK) { + LOGE("Remove subscribe data water mark failed: %d", errCode); + } +ERR: + if (errCode == E_OK) { + errCode = handle->Commit(); + } else { + (void)handle->Rollback(); + } + ReleaseHandle(handle); + return errCode; +} + +int SQLiteSingleVerNaturalStore::RemoveSubscribe(const std::string &subscribeId) +{ + return RemoveSubscribe(std::vector {subscribeId}); +} + +int SQLiteSingleVerNaturalStore::SetMaxLogSize(uint64_t limit) +{ + LOGI("Set the max log size to %" PRIu64, limit); + maxLogSize_.store(limit); + return E_OK; +} +uint64_t SQLiteSingleVerNaturalStore::GetMaxLogSize() const +{ + return maxLogSize_.load(); +} + +int SQLiteSingleVerNaturalStore::RemoveAllSubscribe() +{ + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetHandle(true, errCode); + if (handle == nullptr) { + return errCode; + } + std::vector triggers; + errCode = handle->GetTriggers(DBConstant::SUBSCRIBE_QUERY_PREFIX, triggers); + if (errCode != E_OK) { + LOGE("Get all subscribe triggers failed. %d", errCode); + ReleaseHandle(handle); + return errCode; + } + + errCode = handle->StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + ReleaseHandle(handle); + return errCode; + } + + Key prefixKey; + errCode = handle->RemoveTrigger(triggers); + if (errCode != E_OK) { + LOGE("remove all subscribe triggers failed. %d", errCode); + goto END; + } + + DBCommon::StringToVector(DBConstant::SUBSCRIBE_QUERY_PREFIX, prefixKey); + errCode = handle->DeleteMetaDataByPrefixKey(prefixKey); + if (errCode != E_OK) { + LOGE("remove all subscribe water mark failed. %d", errCode); + } +END: + if (errCode == E_OK) { + errCode = handle->Commit(); + } else { + (void)handle->Rollback(); + } + ReleaseHandle(handle); + return errCode; +} + +void SQLiteSingleVerNaturalStore::Dump(int fd) +{ + std::string userId = MyProp().GetStringProp(DBProperties::USER_ID, ""); + std::string appId = MyProp().GetStringProp(DBProperties::APP_ID, ""); + std::string storeId = MyProp().GetStringProp(DBProperties::STORE_ID, ""); + std::string label = MyProp().GetStringProp(DBProperties::IDENTIFIER_DATA, ""); + label = DBCommon::TransferStringToHex(label); + DBDumpHelper::Dump(fd, "\tdb userId = %s, appId = %s, storeId = %s, label = %s\n", + userId.c_str(), appId.c_str(), storeId.c_str(), label.c_str()); + SyncAbleKvDB::Dump(fd); +} + +DEFINE_OBJECT_TAG_FACILITIES(SQLiteSingleVerNaturalStore) +} diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store.h b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store.h new file mode 100644 index 00000000..03ecf201 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store.h @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_SINGLE_VER_NATURAL_STORE_H +#define SQLITE_SINGLE_VER_NATURAL_STORE_H +#include + +#include "sync_able_kvdb.h" +#include "sqlite_single_ver_storage_engine.h" +#include "sqlite_utils.h" +#include "isyncer.h" +#include "single_ver_natural_store_commit_notify_data.h" +#include "single_ver_kvdb_sync_interface.h" +#include "kv_store_nb_conflict_data_impl.h" +#include "runtime_context.h" +#include "sqlite_single_ver_continue_token.h" + +namespace DistributedDB { +class SQLiteSingleVerNaturalStore : public SyncAbleKvDB, public SingleVerKvDBSyncInterface { +public: + SQLiteSingleVerNaturalStore(); + ~SQLiteSingleVerNaturalStore() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteSingleVerNaturalStore); + + // Open the database + int Open(const KvDBProperties &kvDBProp) override; + + // Invoked automatically when connection count is zero + void Close() override; + + // Create a connection object. + GenericKvDBConnection *NewConnection(int &errCode) override; + + // Get interface type of this kvdb. + int GetInterfaceType() const override; + + // Get the interface ref-count, in order to access asynchronously. + void IncRefCount() override; + + // Drop the interface ref-count. + void DecRefCount() override; + + // Get the identifier of this kvdb. + std::vector GetIdentifier() const override; + // Get the dual tuple identifier of this kvdb. + std::vector GetDualTupleIdentifier() const override; + + // Get interface for syncer. + IKvDBSyncInterface *GetSyncInterface() override; + + int GetMetaData(const Key &key, Value &value) const override; + + int PutMetaData(const Key &key, const Value &value) override; + + // Delete multiple meta data records in a transaction. + int DeleteMetaData(const std::vector &keys) override; + // Delete multiple meta data records with key prefix in a transaction. + int DeleteMetaDataByPrefixKey(const Key &keyPrefix) const override; + + int GetAllMetaKeys(std::vector &keys) const override; + + int GetSyncData(Timestamp begin, Timestamp end, std::vector &dataItems, ContinueToken &continueStmtToken, + const DataSizeSpecInfo &dataSizeInfo) const override; + + int GetSyncData(Timestamp begin, Timestamp end, std::vector &entries, + ContinueToken &continueStmtToken, const DataSizeSpecInfo &dataSizeInfo) const override; + + int GetSyncData(QueryObject &query, const SyncTimeRange &timeRange, const DataSizeSpecInfo &dataSizeInfo, + ContinueToken &continueStmtToken, std::vector &entries) const override; + + int GetSyncDataNext(std::vector &dataItems, ContinueToken &continueStmtToken, + const DataSizeSpecInfo &dataSizeInfo) const override; + + int GetSyncDataNext(std::vector &entries, ContinueToken &continueStmtToken, + const DataSizeSpecInfo &dataSizeInfo) const override; + + void ReleaseContinueToken(ContinueToken &continueStmtToken) const override; + + int PutSyncDataWithQuery(const QueryObject &query, const std::vector &entries, + const std::string &deviceName) override; + + void GetMaxTimestamp(Timestamp &stamp) const override; + + int SetMaxTimestamp(Timestamp timestamp); + + int Rekey(const CipherPassword &passwd) override; + + int Export(const std::string &filePath, const CipherPassword &passwd) override; + + int Import(const std::string &filePath, const CipherPassword &passwd) override; + + // In sync procedure, call this function + int RemoveDeviceData(const std::string &deviceName, bool isNeedNotify) override; + + // In local procedure, call this function + int RemoveDeviceData(const std::string &deviceName, bool isNeedNotify, bool isInSync); + + SQLiteSingleVerStorageExecutor *GetHandle(bool isWrite, int &errCode, + OperatePerm perm = OperatePerm::NORMAL_PERM) const; + + void ReleaseHandle(SQLiteSingleVerStorageExecutor *&handle) const; + + int TransObserverTypeToRegisterFunctionType(int observerType, RegisterFuncType &type) const override; + + int TransConflictTypeToRegisterFunctionType(int conflictType, RegisterFuncType &type) const override; + + bool CheckWritePermission() const override; + + SchemaObject GetSchemaInfo() const override; + + bool CheckCompatible(const std::string &schema, uint8_t type) const override; + + Timestamp GetCurrentTimestamp(); + + SchemaObject GetSchemaObject() const; + + const SchemaObject &GetSchemaObjectConstRef() const; + + const KvDBProperties &GetDbProperties() const override; + + int RemoveKvDB(const KvDBProperties &properties) override; + + int GetKvDBSize(const KvDBProperties &properties, uint64_t &size) const override; + KvDBProperties &GetDbPropertyForUpdate(); + + int InitDatabaseContext(const KvDBProperties &kvDBProp, bool isNeedUpdateSecOpt = false); + + int RegisterLifeCycleCallback(const DatabaseLifeCycleNotifier ¬ifier); + + int SetAutoLifeCycleTime(uint32_t time); + + int GetSecurityOption(SecurityOption &option) const override; + + bool IsDataMigrating() const override; + + void SetConnectionFlag(bool isExisted) const override; + + int TriggerToMigrateData() const; + + int CheckValueAndAmendIfNeed(ValueSource sourceType, const Value &oriValue, Value &amendValue, + bool &useAmendValue) const; + + int CheckReadDataControlled() const; + bool IsCacheDBMode() const; + bool IsExtendedCacheDBMode() const; + + void IncreaseCacheRecordVersion() const; + uint64_t GetCacheRecordVersion() const; + uint64_t GetAndIncreaseCacheRecordVersion() const; + + void NotifyRemotePushFinished(const std::string &targetId) const override; + + int GetDatabaseCreateTimestamp(Timestamp &outTime) const override; + + int CheckIntegrity() const override; + + int GetCompressionOption(bool &needCompressOnSync, uint8_t &compressionRate) const override; + int GetCompressionAlgo(std::set &algorithmSet) const override; + + // Check and init query object for query sync and subscribe, flatbuffer schema will always return E_NOT_SUPPORT. + // return E_OK if subscribe is legal, ERROR on exception. + int CheckAndInitQueryCondition(QueryObject &query) const override; + + int InterceptData(std::vector &entries, const std::string &sourceID, + const std::string &targetID) const override; + + void SetDataInterceptor(const PushDataInterceptor &interceptor) override; + + int AddSubscribe(const std::string &subscribeId, const QueryObject &query, bool needCacheSubscribe) override; + + int RemoveSubscribe(const std::string &subscribeId) override; + + int RemoveSubscribe(const std::vector &subscribeIds) override; + + int SetMaxLogSize(uint64_t limit); + + uint64_t GetMaxLogSize() const; + + void Dump(int fd) override; + +private: + struct TransPair { + int index; + RegisterFuncType funcType; + }; + static RegisterFuncType GetFuncType(int index, const TransPair *transMap, int32_t len); + int CheckDatabaseRecovery(const KvDBProperties &kvDBProp); + + void CommitAndReleaseNotifyData(SingleVerNaturalStoreCommitNotifyData *&committedData, + bool isNeedCommit, int eventType); + + int RegisterNotification(); + + void ReleaseResources(); + + void InitCurrentMaxStamp(); + + int SaveSyncDataItems(const QueryObject &query, std::vector &dataItems, const DeviceInfo &deviceInfo, + bool checkValueContent); + + int InitStorageEngine(const KvDBProperties &kvDBProp, bool isNeedUpdateSecOpt); + + void InitialLocalDataTimestamp(); + + int GetSchema(SchemaObject &schema) const; + + static void InitDataBaseOption(const KvDBProperties &kvDBProp, OpenDbProperties &option); + + static int SetUserVer(const KvDBProperties &kvDBProp, int version); + + static std::string GetDatabasePath(const KvDBProperties &kvDBProp); + static std::string GetSubDirPath(const KvDBProperties &kvDBProp); + void NotifyRemovedData(std::vector &entries); + + // Decide read only based on schema situation + int DecideReadOnlyBaseOnSchema(const KvDBProperties &kvDBProp, bool &isReadOnly, + SchemaObject &savedSchemaObj) const; + + void HeartBeatForLifeCycle() const; + + int StartLifeCycleTimer(const DatabaseLifeCycleNotifier ¬ifier) const; + + int ResetLifeCycleTimer() const; + + int StopLifeCycleTimer() const; + void InitConflictNotifiedFlag(SingleVerNaturalStoreCommitNotifyData *committedData); + + void AsyncDataMigration() const; + + // Change value that should be amended, and neglect value that is incompatible + void CheckAmendValueContentForSyncProcedure(std::vector &dataItems) const; + + int RemoveDeviceDataInCacheMode(const std::string &deviceName, bool isNeedNotify); + + int RemoveDeviceDataNormally(const std::string &deviceName, bool isNeedNotify); + + int SaveSyncDataToMain(const QueryObject &query, std::vector &dataItems, const DeviceInfo &deviceInfo); + + // Currently, this function only suitable to be call from sync in insert_record_from_sync procedure + // Take attention if future coder attempt to call it in other situation procedure + int SaveSyncItems(const QueryObject& query, std::vector &dataItems, const DeviceInfo &deviceInfo, + Timestamp &maxTimestamp, SingleVerNaturalStoreCommitNotifyData *commitData) const; + + int SaveSyncDataToCacheDB(const QueryObject &query, std::vector &dataItems, + const DeviceInfo &deviceInfo); + + int SaveSyncItemsInCacheMode(SQLiteSingleVerStorageExecutor *handle, const QueryObject &query, + std::vector &dataItems, const DeviceInfo &deviceInfo, Timestamp &maxTimestamp) const; + + int ClearIncompleteDatabase(const KvDBProperties &kvDBPro) const; + + int GetSyncDataForQuerySync(std::vector &dataItems, SQLiteSingleVerContinueToken *&continueStmtToken, + const DataSizeSpecInfo &dataSizeInfo) const; + + int SaveCreateDBTime(); + int SaveCreateDBTimeIfNotExisted(); + + int GetAndInitStorageEngine(const KvDBProperties &kvDBProp); + + int RemoveAllSubscribe(); + + DECLARE_OBJECT_TAG(SQLiteSingleVerNaturalStore); + + Timestamp currentMaxTimestamp_ = 0; + SQLiteSingleVerStorageEngine *storageEngine_; + bool notificationEventsRegistered_; + bool notificationConflictEventsRegistered_; + bool isInitialized_; + bool isReadOnly_; + mutable std::mutex syncerMutex_; + mutable std::mutex initialMutex_; + mutable std::mutex maxTimestampMutex_; + mutable std::mutex lifeCycleMutex_; + mutable DatabaseLifeCycleNotifier lifeCycleNotifier_; + mutable TimerId lifeTimerId_; + uint32_t autoLifeTime_; + mutable Timestamp createDBTime_; + mutable std::mutex createDBTimeMutex_; + + mutable std::shared_mutex dataInterceptorMutex_; + PushDataInterceptor dataInterceptor_; + std::atomic maxLogSize_; +}; +} +#endif diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store_connection.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store_connection.cpp new file mode 100644 index 00000000..703ac71a --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store_connection.cpp @@ -0,0 +1,1796 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_single_ver_natural_store_connection.h" + +#include "db_constant.h" +#include "db_dfx_adapter.h" +#include "db_errno.h" +#include "log_print.h" +#include "kvdb_pragma.h" +#include "sqlite_single_ver_natural_store.h" +#include "kvdb_observer_handle.h" +#include "store_types.h" +#include "db_common.h" +#include "sqlite_single_ver_result_set.h" +#include "sqlite_single_ver_forward_cursor.h" + +namespace DistributedDB { +namespace { + enum { + LOCAL_OPR_NONE = 0, + LOCAL_OPR_DEL = 1, + LOCAL_OPR_PUT = 2 + }; + const uint32_t MAX_AUTO_LIFE_CYCLE = 1800000; // half an hour. + const uint32_t MIN_AUTO_LIFE_CYCLE = 5000; // 5s. +} + +SQLiteSingleVerNaturalStoreConnection::SQLiteSingleVerNaturalStoreConnection(SQLiteSingleVerNaturalStore *kvDB) + : SyncAbleKvDBConnection(kvDB), + cacheMaxSizeForNewResultSet_(DEFAULT_RESULT_SET_CACHE_MAX_SIZE), + conflictType_(0), + transactionEntrySize_(0), + currentMaxTimestamp_(0), + committedData_(nullptr), + localCommittedData_(nullptr), + transactionExeFlag_(false), + conflictListener_(nullptr), + writeHandle_(nullptr) +{} + +SQLiteSingleVerNaturalStoreConnection::~SQLiteSingleVerNaturalStoreConnection() +{ + if (conflictListener_ != nullptr) { + conflictListener_->Drop(true); + conflictListener_ = nullptr; + } +} + +inline bool SQLiteSingleVerNaturalStoreConnection::IsFileAccessControlled() const +{ + return RuntimeContext::GetInstance()->IsAccessControlled() && + kvDB_->GetMyProperties().GetSecLabel() > SecurityLabel::S2; +} + +int SQLiteSingleVerNaturalStoreConnection::CheckReadDataControlled() const +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + LOGE("[SingleVerConnection] natural store is nullptr in CheckReadDataControlled."); + return E_OK; + } + return naturalStore->CheckReadDataControlled(); +} + +int SQLiteSingleVerNaturalStoreConnection::Get(const IOption &option, const Key &key, Value &value) const +{ + if (key.size() > DBConstant::MAX_KEY_SIZE || key.empty()) { + return -E_INVALID_ARGS; + } + + SingleVerDataType dataType; + if (option.dataType == IOption::LOCAL_DATA) { + dataType = SingleVerDataType::LOCAL_TYPE; + } else if (option.dataType == IOption::SYNC_DATA) { + dataType = SingleVerDataType::SYNC_TYPE; + } else { + return -E_NOT_SUPPORT; + } + + int errCode = CheckReadDataControlled(); + if (errCode != E_OK) { + LOGE("[Get] Existed cache database can not read data, errCode = [%d]!", errCode); + return errCode; + } + + DBDfxAdapter::StartTraceSQL(); + { + // need to check if the transaction started + std::lock_guard lock(transactionMutex_); + if (writeHandle_ != nullptr) { + LOGD("Transaction started already."); + Timestamp recordTimestamp; + errCode = writeHandle_->GetKvData(dataType, key, value, recordTimestamp); + DBDfxAdapter::FinishTraceSQL(); + return errCode; + } + } + + SQLiteSingleVerStorageExecutor *handle = GetExecutor(false, errCode); + if (handle == nullptr) { + DBDfxAdapter::FinishTraceSQL(); + return errCode; + } + + Timestamp timestamp; + errCode = handle->GetKvData(dataType, key, value, timestamp); + ReleaseExecutor(handle); + DBDfxAdapter::FinishTraceSQL(); + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::Put(const IOption &option, const Key &key, const Value &value) +{ + std::vector entries; + Entry entry{key, value}; + entries.emplace_back(std::move(entry)); + + return PutBatch(option, entries); +} + +int SQLiteSingleVerNaturalStoreConnection::Delete(const IOption &option, const Key &key) +{ + std::vector keys; + keys.push_back(key); + + return DeleteBatch(option, keys); +} + +int SQLiteSingleVerNaturalStoreConnection::Clear(const IOption &option) +{ + return -E_NOT_SUPPORT; +} + +int SQLiteSingleVerNaturalStoreConnection::GetEntries(const IOption &option, const Key &keyPrefix, + std::vector &entries) const +{ + if (keyPrefix.size() > DBConstant::MAX_KEY_SIZE) { + return -E_INVALID_ARGS; + } + + SingleVerDataType type; + if (option.dataType == IOption::LOCAL_DATA) { + type = SingleVerDataType::LOCAL_TYPE; + } else if (option.dataType == IOption::SYNC_DATA) { + type = SingleVerDataType::SYNC_TYPE; + } else { + return -E_INVALID_ARGS; + } + + int errCode = CheckReadDataControlled(); + if (errCode != E_OK) { + LOGE("[GetEntries] Existed cache database can not read data, errCode = [%d]!", errCode); + return errCode; + } + + DBDfxAdapter::StartTraceSQL(); + { + std::lock_guard lock(transactionMutex_); + if (writeHandle_ != nullptr) { + LOGD("Transaction started already."); + errCode = writeHandle_->GetEntries(type, keyPrefix, entries); + DBDfxAdapter::FinishTraceSQL(); + return errCode; + } + } + + SQLiteSingleVerStorageExecutor *handle = GetExecutor(false, errCode); + if (handle == nullptr) { + LOGE("[Connection]::[GetEntries] Get executor failed, errCode = [%d]", errCode); + DBDfxAdapter::FinishTraceSQL(); + return errCode; + } + + errCode = handle->GetEntries(type, keyPrefix, entries); + ReleaseExecutor(handle); + DBDfxAdapter::FinishTraceSQL(); + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::GetEntries(const IOption &option, const Query &query, + std::vector &entries) const +{ + if (option.dataType != IOption::SYNC_DATA) { + return -E_NOT_SUPPORT; + } + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + int errCode = CheckReadDataControlled(); + if (errCode != E_OK) { + LOGE("[GetEntries] Existed cache database can not read data, errCode = [%d]!", errCode); + return errCode; + } + QueryObject queryObj(query); + // In readOnly mode, forbidden all schema related query + if (CheckWritePermission() == E_OK) { + const SchemaObject &schemaObjRef = naturalStore->GetSchemaObjectConstRef(); + queryObj.SetSchema(schemaObjRef); + } + DBDfxAdapter::StartTraceSQL(); + { + std::lock_guard lock(transactionMutex_); + if (writeHandle_ != nullptr) { + LOGD("Transaction started already."); + errCode = writeHandle_->GetEntries(queryObj, entries); + DBDfxAdapter::FinishTraceSQL(); + return errCode; + } + } + + SQLiteSingleVerStorageExecutor *handle = GetExecutor(false, errCode); + if (handle == nullptr) { + DBDfxAdapter::FinishTraceSQL(); + return errCode; + } + + errCode = handle->GetEntries(queryObj, entries); + ReleaseExecutor(handle); + DBDfxAdapter::FinishTraceSQL(); + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::GetCount(const IOption &option, const Query &query, int &count) const +{ + if (option.dataType != IOption::SYNC_DATA) { + return -E_NOT_SUPPORT; + } + int errCode = CheckReadDataControlled(); + if (errCode != E_OK) { + LOGE("[GetCount] Existed cache database can not read data, errCode = [%d]!", errCode); + return errCode; + } + + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + QueryObject queryObj(query); + // In readOnly mode, forbidden all schema related query + if (CheckWritePermission() == E_OK) { + const SchemaObject &schemaObjRef = naturalStore->GetSchemaObjectConstRef(); + queryObj.SetSchema(schemaObjRef); + } + DBDfxAdapter::StartTraceSQL(); + { + std::lock_guard lock(transactionMutex_); + if (writeHandle_ != nullptr) { + LOGD("Transaction started already."); + errCode = writeHandle_->GetCount(queryObj, count); + DBDfxAdapter::FinishTraceSQL(); + return errCode; + } + } + + SQLiteSingleVerStorageExecutor *handle = GetExecutor(false, errCode); + if (handle == nullptr) { + DBDfxAdapter::FinishTraceSQL(); + return errCode; + } + errCode = handle->GetCount(queryObj, count); + ReleaseExecutor(handle); + DBDfxAdapter::FinishTraceSQL(); + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::PutBatch(const IOption &option, const std::vector &entries) +{ + LOGD("[PutBatch] entries size is : %zu, dataType : %d", entries.size(), option.dataType); + if (option.dataType == IOption::LOCAL_DATA) { + int retCode = CheckLocalEntriesValid(entries); + if (retCode != E_OK) { + return retCode; + } + return PutBatchInner(option, entries); + } + + if (option.dataType == IOption::SYNC_DATA) { + int errCode = CheckSyncEntriesValid(entries); + if (errCode != E_OK) { + return errCode; + } + return PutBatchInner(option, entries); + } + + return -E_NOT_SUPPORT; +} + +int SQLiteSingleVerNaturalStoreConnection::DeleteBatch(const IOption &option, const std::vector &keys) +{ + LOGD("[DeleteBatch] keys size is : %zu, dataType : %d", keys.size(), option.dataType); + if (option.dataType == IOption::LOCAL_DATA) { + int retCode = CheckLocalKeysValid(keys); + if (retCode != E_OK) { + return retCode; + } + return DeleteBatchInner(option, keys); + } + + if (option.dataType == IOption::SYNC_DATA) { + int errCode = CheckSyncKeysValid(keys); + if (errCode != E_OK) { + return errCode; + } + return DeleteBatchInner(option, keys); + } + + return -E_NOT_SUPPORT; +} + +int SQLiteSingleVerNaturalStoreConnection::GetSnapshot(IKvDBSnapshot *&snapshot) const +{ + return -E_NOT_SUPPORT; +} + +void SQLiteSingleVerNaturalStoreConnection::ReleaseSnapshot(IKvDBSnapshot *&snapshot) +{} + +int SQLiteSingleVerNaturalStoreConnection::StartTransaction() +{ + std::lock_guard lock(transactionMutex_); + if (writeHandle_ != nullptr) { + LOGD("Transaction started already."); + return -E_TRANSACT_STATE; + } + + int errCode = StartTransactionInner(); + if (errCode == E_OK) { + transactionExeFlag_.store(true); + } + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::Commit() +{ + std::lock_guard lock(transactionMutex_); + if (writeHandle_ == nullptr) { + LOGE("single version database is null or the transaction has not been started"); + return -E_INVALID_DB; + } + + int errCode = CommitInner(); + if (errCode == E_OK) { + transactionExeFlag_.store(false); + } + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::RollBack() +{ + std::lock_guard lock(transactionMutex_); + if (writeHandle_ == nullptr) { + LOGE("Invalid handle for rollback or the transaction has not been started."); + return -E_INVALID_DB; + } + + int errCode = RollbackInner(); + if (errCode == E_OK) { + transactionExeFlag_.store(false); + } + return errCode; +} + +bool SQLiteSingleVerNaturalStoreConnection::IsTransactionStarted() const +{ + return transactionExeFlag_.load(); +} + +int SQLiteSingleVerNaturalStoreConnection::Pragma(int cmd, void *parameter) +{ + int errCode = E_OK; + switch (cmd) { + case PRAGMA_RM_DEVICE_DATA: { + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + auto deviceName = static_cast(parameter); + errCode = naturalStore->RemoveDeviceData(*deviceName, false, false); + break; + } + case PRAGMA_GET_IDENTIFIER_OF_DEVICE: { + if (parameter == nullptr) { + return -E_INVALID_ARGS; + } + return CalcHashDevID(*(static_cast(parameter))); + } + case PRAGMA_GET_DEVICE_IDENTIFIER_OF_ENTRY: + return GetDeviceIdentifier(static_cast(parameter)); + case PRAGMA_PUBLISH_LOCAL: + return PragmaPublish(parameter); + case PRAGMA_UNPUBLISH_SYNC: + errCode = PragmaUnpublish(parameter); + break; + case PRAGMA_SET_AUTO_LIFE_CYCLE: + return PragmaSetAutoLifeCycle(static_cast(parameter)); + case PRAGMA_RESULT_SET_CACHE_MODE: + return PragmaResultSetCacheMode(parameter); + case PRAGMA_RESULT_SET_CACHE_MAX_SIZE: + return PragmaResultSetCacheMaxSize(parameter); + case PRAGMA_TRIGGER_TO_MIGRATE_DATA: + return PragmaTriggerToMigrateData(*static_cast(parameter)); + case PRAGMA_SET_MAX_LOG_LIMIT: + return PragmaSetMaxLogSize(static_cast(parameter)); + case PRAGMA_EXEC_CHECKPOINT: + return ForceCheckPoint(); + default: + // Call Pragma() of super class. + errCode = SyncAbleKvDBConnection::Pragma(cmd, parameter); + break; + } + + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::TranslateObserverModeToEventTypes(unsigned mode, + std::list &eventTypes) const +{ + int errCode = E_OK; + switch (mode) { + case static_cast(SQLITE_GENERAL_NS_PUT_EVENT): + eventTypes.push_back(SQLITE_GENERAL_NS_PUT_EVENT); + break; + case static_cast(SQLITE_GENERAL_NS_SYNC_EVENT): + eventTypes.push_back(SQLITE_GENERAL_NS_SYNC_EVENT); + break; + case (static_cast(SQLITE_GENERAL_NS_PUT_EVENT) + | static_cast(SQLITE_GENERAL_NS_SYNC_EVENT)): + eventTypes.push_back(SQLITE_GENERAL_NS_PUT_EVENT); + eventTypes.push_back(SQLITE_GENERAL_NS_SYNC_EVENT); + break; + case static_cast(SQLITE_GENERAL_NS_LOCAL_PUT_EVENT): + eventTypes.push_back(SQLITE_GENERAL_NS_LOCAL_PUT_EVENT); + break; + default: + errCode = -E_NOT_SUPPORT; + break; + } + return errCode; +} + +void SQLiteSingleVerNaturalStoreConnection::ClearConflictNotifierCount() +{ + uint32_t conflictType = static_cast(conflictType_); + if ((conflictType & static_cast(SQLITE_GENERAL_NS_FOREIGN_KEY_ONLY)) != 0) { + (void)kvDB_->UnregisterFunction(CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ONLY); + } + if ((conflictType & static_cast(SQLITE_GENERAL_NS_FOREIGN_KEY_ORIG)) != 0) { + (void)kvDB_->UnregisterFunction(CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ORIG); + } + if ((conflictType & static_cast(SQLITE_GENERAL_NS_NATIVE_ALL)) != 0) { + (void)kvDB_->UnregisterFunction(CONFLICT_SINGLE_VERSION_NS_NATIVE_ALL); + } + return; +} + +void SQLiteSingleVerNaturalStoreConnection::ResetConflictNotifierCount(int target) +{ + // Clear the old conflict type function. + ClearConflictNotifierCount(); + + LOGD("Conflict type:%d to %d", conflictType_, target); + // Add the new conflict type function. + AddConflictNotifierCount(target); + conflictType_ = target; +} + +void SQLiteSingleVerNaturalStoreConnection::AddConflictNotifierCount(int target) +{ + LOGD("Conflict type:%u vs %u", conflictType_, target); + // Add the new conflict type function. + uint32_t targetTemp = static_cast(target); + if ((targetTemp & static_cast(SQLITE_GENERAL_NS_FOREIGN_KEY_ONLY)) != 0) { + (void)kvDB_->RegisterFunction(CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ONLY); + } + if ((targetTemp & static_cast(SQLITE_GENERAL_NS_FOREIGN_KEY_ORIG)) != 0) { + (void)kvDB_->RegisterFunction(CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ORIG); + } + if ((targetTemp & static_cast(SQLITE_GENERAL_NS_NATIVE_ALL)) != 0) { + (void)kvDB_->RegisterFunction(CONFLICT_SINGLE_VERSION_NS_NATIVE_ALL); + } +} + +int SQLiteSingleVerNaturalStoreConnection::SetConflictNotifier(int conflictType, + const KvDBConflictAction &action) +{ + std::lock_guard lock(conflictMutex_); + if (!action && conflictListener_ == nullptr) { + return -E_INVALID_ARGS; + } + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + + // prevent the rekey operation. + if (isExclusive_.load()) { + return -E_BUSY; + } + + int targetType = 0; + NotificationChain::Listener *listener = nullptr; + if (action) { + int errCode = E_OK; + Key key; + listener = RegisterSpecialListener(SQLITE_GENERAL_CONFLICT_EVENT, key, action, true, errCode); + if (listener == nullptr) { + LOGE("Register Conflict listener failed:'%d'.", errCode); + return errCode; + } + targetType = conflictType; + } + + ResetConflictNotifierCount(targetType); + // drop the old listener. + if (conflictListener_ != nullptr) { + conflictListener_->Drop(true); + } + conflictListener_ = listener; + return E_OK; +} + +int SQLiteSingleVerNaturalStoreConnection::Rekey(const CipherPassword &passwd) +{ + if (IsFileAccessControlled()) { + LOGE("Forbid Rekey when screen locked and security label [%d]!", kvDB_->GetMyProperties().GetSecLabel()); + return -E_NOT_SUPPORT; + } + std::lock_guard lock(rekeyMutex_); + int errCode = CheckMonoStatus(OperatePerm::REKEY_MONOPOLIZE_PERM); + if (errCode != E_OK) { + return errCode; + } + LOGI("Begin rekey operation"); + errCode = kvDB_->Rekey(passwd); + GenericKvDBConnection::ResetExclusiveStatus(); + kvDB_->ReEnableConnection(OperatePerm::REKEY_MONOPOLIZE_PERM); + EnableManualSync(); + LOGI("End rekey operation errCode = [%d]", errCode); + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::Export(const std::string &filePath, const CipherPassword &passwd) +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + + if (IsFileAccessControlled()) { + LOGE("Forbid Export when screen locked and security label [%d] file lock state [%d]", + kvDB_->GetMyProperties().GetSecLabel(), RuntimeContext::GetInstance()->IsAccessControlled()); + return -E_NOT_SUPPORT; + } // Avoid abnormal branch handling without affecting the business + return kvDB_->Export(filePath, passwd); +} + +int SQLiteSingleVerNaturalStoreConnection::Import(const std::string &filePath, const CipherPassword &passwd) +{ + if (IsFileAccessControlled()) { + LOGE("Forbid Import when screen locked and security label [%d]!", kvDB_->GetMyProperties().GetSecLabel()); + return -E_NOT_SUPPORT; + } + + std::lock_guard lock(importMutex_); + int errCode = CheckMonoStatus(OperatePerm::IMPORT_MONOPOLIZE_PERM); + if (errCode != E_OK) { + return errCode; + } + errCode = kvDB_->Import(filePath, passwd); + GenericKvDBConnection::ResetExclusiveStatus(); + kvDB_->ReEnableConnection(OperatePerm::IMPORT_MONOPOLIZE_PERM); + EnableManualSync(); + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::GetResultSet(const IOption &option, const Key &keyPrefix, + IKvDBResultSet *&resultSet) const +{ + // need to check if the transaction started + if (transactionExeFlag_.load()) { + LOGD("Transaction started already."); + return -E_BUSY; + } + + // maximum of result set size is 4 + std::lock_guard lock(kvDbResultSetsMutex_); + if (kvDbResultSets_.size() >= MAX_RESULT_SET_SIZE) { + LOGE("Over max result set size"); + return -E_MAX_LIMITS; + } + + int errCode = CheckReadDataControlled(); + if (errCode != E_OK) { + LOGE("[GetResultSet][keyPrefix] Existed cache database can not read data, errCode = [%d]!", errCode); + return errCode; + } + + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + bool isMemDb = naturalStore->GetMyProperties().GetBoolProp(KvDBProperties::MEMORY_MODE, false); + resultSet = new (std::nothrow) SQLiteSingleVerResultSet(naturalStore, keyPrefix, + SQLiteSingleVerResultSet::Option{cacheModeForNewResultSet_.load(), cacheMaxSizeForNewResultSet_.load()}); + if (resultSet == nullptr) { + LOGE("Create single version result set failed."); + return -E_OUT_OF_MEMORY; + } + errCode = resultSet->Open(isMemDb); + if (errCode != E_OK) { + delete resultSet; + resultSet = nullptr; + LOGE("Open result set failed."); + return errCode; + } + kvDbResultSets_.insert(resultSet); + return E_OK; +} + +int SQLiteSingleVerNaturalStoreConnection::GetResultSet(const IOption &option, const Query &query, + IKvDBResultSet *&resultSet) const +{ + // need to check if the transaction started + if (transactionExeFlag_.load()) { + LOGD("Transaction started already."); + return -E_BUSY; + } + + // maximum of result set size is 4 + std::lock_guard lock(kvDbResultSetsMutex_); + if (kvDbResultSets_.size() >= MAX_RESULT_SET_SIZE) { + LOGE("Over max result set size"); + return -E_MAX_LIMITS; + } + + int errCode = CheckReadDataControlled(); + if (errCode != E_OK) { + LOGE("[GetResultSet][query] Existed cache database can not read data, errCode = [%d]!", errCode); + return errCode; + } + + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); // Guarantee not nullptr + QueryObject queryObj(query); + // In readOnly mode, forbidden all schema related query + if (CheckWritePermission() == E_OK) { + const SchemaObject &schemaObjRef = naturalStore->GetSchemaObjectConstRef(); + queryObj.SetSchema(schemaObjRef); + } + bool isMemDb = naturalStore->GetMyProperties().GetBoolProp(KvDBProperties::MEMORY_MODE, false); + resultSet = new (std::nothrow) SQLiteSingleVerResultSet(naturalStore, queryObj, + SQLiteSingleVerResultSet::Option{cacheModeForNewResultSet_.load(), cacheMaxSizeForNewResultSet_.load()}); + if (resultSet == nullptr) { + LOGE("Create single version result set failed."); + return -E_OUT_OF_MEMORY; + } + errCode = resultSet->Open(isMemDb); + if (errCode != E_OK) { + delete resultSet; + resultSet = nullptr; + LOGE("Open result set failed."); + return errCode; + } + kvDbResultSets_.insert(resultSet); + return E_OK; +} + +void SQLiteSingleVerNaturalStoreConnection::ReleaseResultSet(IKvDBResultSet *&resultSet) +{ + std::lock_guard lock(kvDbResultSetsMutex_); + if (resultSet == nullptr) { + return; + } + resultSet->Close(); + kvDbResultSets_.erase(resultSet); + delete resultSet; + resultSet = nullptr; + return; +} + +int SQLiteSingleVerNaturalStoreConnection::RegisterLifeCycleCallback(const DatabaseLifeCycleNotifier ¬ifier) +{ + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + return static_cast(kvDB_)->RegisterLifeCycleCallback(notifier); +} + +int SQLiteSingleVerNaturalStoreConnection::PreClose() +{ + // check if result set closed + { + std::lock_guard kvDbResultLock(kvDbResultSetsMutex_); + if (kvDbResultSets_.size() > 0) { + LOGE("The connection have [%zu] active result set, can not close.", kvDbResultSets_.size()); + return -E_BUSY; + } + } + + // check if transaction closed + { + std::lock_guard transactionLock(transactionMutex_); + if (writeHandle_ != nullptr) { + LOGW("Transaction started, need to rollback before close."); + int errCode = RollbackInner(); + if (errCode != E_OK) { + LOGE("Rollback transaction failed, %d.", errCode); + } + ReleaseExecutor(writeHandle_); + } + } + + // Clear the conflict type function. + { + std::lock_guard lock(conflictMutex_); + ClearConflictNotifierCount(); + conflictType_ = 0; + } + return E_OK; +} + +int SQLiteSingleVerNaturalStoreConnection::CheckIntegrity() const +{ + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetExecutor(true, errCode); + if (handle == nullptr) { + LOGW("Failed to get the executor for the integrity check."); + return errCode; + } + + errCode = handle->CheckIntegrity(); + ReleaseExecutor(handle); + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::PragmaSetMaxLogSize(uint64_t *limit) +{ + if (limit == nullptr) { + return -E_INVALID_ARGS; + } + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + LOGE("[SingleVerConnection] db is nullptr for max log limit set."); + return -E_INVALID_DB; + } + if (*limit > DBConstant::MAX_LOG_SIZE_HIGH || *limit < DBConstant::MAX_LOG_SIZE_LOW) { + return -E_INVALID_ARGS; + } + return naturalStore->SetMaxLogSize(*limit); +} + +int SQLiteSingleVerNaturalStoreConnection::ForceCheckPoint() const +{ + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetExecutor(true, errCode); + if (handle == nullptr) { + LOGW("Failed to get the executor for the checkpoint."); + return errCode; + } + + errCode = handle->ForceCheckPoint(); + ReleaseExecutor(handle); + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::CheckMonoStatus(OperatePerm perm) +{ + // 1. Get the connection number + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = DisableManualSync(); + if (errCode != E_OK) { + LOGE("In manual sync"); + return -E_BUSY; + } + + // 2. check the result set number + { + std::lock_guard kvDbResultLock(kvDbResultSetsMutex_); + if (kvDbResultSets_.size() > 0) { + LOGE("Active result set exist."); + EnableManualSync(); + return -E_BUSY; + } + } + // 1. Get the connection number, and get the right to do the rekey operation. + errCode = kvDB_->TryToDisableConnection(perm); + if (errCode != E_OK) { + // If precheck failed, it means that there are more than one connection. + // No need reset the condition for the scene. + LOGE("More than one connection"); + EnableManualSync(); + return errCode; + } + // 2. Check the observer list. + errCode = GenericKvDBConnection::PreCheckExclusiveStatus(); + if (errCode != E_OK) { + kvDB_->ReEnableConnection(perm); + LOGE("Observer prevents."); + EnableManualSync(); + return errCode; + } + + // 3. Check the conflict notifier. + { + std::lock_guard conflictLock(conflictMutex_); + if (conflictListener_ != nullptr) { + errCode = -E_BUSY; + GenericKvDBConnection::ResetExclusiveStatus(); + kvDB_->ReEnableConnection(perm); + LOGE("Conflict notifier prevents"); + EnableManualSync(); + return errCode; + } + } + return E_OK; +} + +int SQLiteSingleVerNaturalStoreConnection::GetDeviceIdentifier(PragmaEntryDeviceIdentifier *identifier) +{ + if (identifier == nullptr) { + return -E_INVALID_ARGS; + } + + if (identifier->key.empty() || identifier->key.size() > DBConstant::MAX_VALUE_SIZE) { + return -E_INVALID_ARGS; + } + + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetExecutor(false, errCode); + if (handle == nullptr) { + return errCode; + } + + errCode = handle->GetDeviceIdentifier(identifier); + ReleaseExecutor(handle); + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::PutBatchInner(const IOption &option, const std::vector &entries) +{ + DBDfxAdapter::StartTraceSQL(); + std::lock_guard lock(transactionMutex_); + bool isAuto = false; + int errCode = E_OK; + if (writeHandle_ == nullptr) { + isAuto = true; + errCode = StartTransactionInner(); + if (errCode != E_OK) { + return errCode; + DBDfxAdapter::FinishTraceSQL(); + } + } + + if ((transactionEntrySize_ + entries.size()) > DBConstant::MAX_TRANSACTION_ENTRY_SIZE) { + DBDfxAdapter::FinishTraceSQL(); + return -E_MAX_LIMITS; + } + + if (option.dataType == IOption::SYNC_DATA) { + errCode = SaveSyncEntries(entries); + } else { + errCode = SaveLocalEntries(entries); + } + if (errCode == E_OK) { + transactionEntrySize_ += entries.size(); + } + + if (isAuto) { + if (errCode == E_OK) { + errCode = CommitInner(); + } else { + int innerCode = RollbackInner(); + errCode = (innerCode != E_OK) ? innerCode : errCode; + } + } + DBDfxAdapter::FinishTraceSQL(); + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::DeleteBatchInner(const IOption &option, const std::vector &keys) +{ + DBDfxAdapter::StartTraceSQL(); + std::lock_guard lock(transactionMutex_); + bool isAuto = false; + int errCode = E_OK; + + if (writeHandle_ == nullptr) { + isAuto = true; + errCode = StartTransactionInner(); + if (errCode != E_OK) { + DBDfxAdapter::FinishTraceSQL(); + return errCode; + } + } + + if ((transactionEntrySize_ + keys.size()) > DBConstant::MAX_TRANSACTION_ENTRY_SIZE) { + DBDfxAdapter::FinishTraceSQL(); + return -E_MAX_LIMITS; + } + + if (option.dataType == IOption::SYNC_DATA) { + errCode = DeleteSyncEntries(keys); + } else { + errCode = DeleteLocalEntries(keys); + } + if (errCode == E_OK) { + transactionEntrySize_ += keys.size(); + } + + if (isAuto) { + if (errCode == E_OK) { + errCode = CommitInner(); + } else { + int innerCode = RollbackInner(); + errCode = (innerCode != E_OK) ? innerCode : errCode; + } + } + DBDfxAdapter::FinishTraceSQL(); + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::SaveSyncEntries(const std::vector &entries) +{ + int errCode = E_OK; + for (const auto &entry : entries) { + errCode = SaveEntry(entry, false); + if (errCode != E_OK) { + break; + } + } + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::SaveLocalEntries(const std::vector &entries) +{ + int errCode = E_OK; + for (const auto &entry : entries) { + errCode = SaveLocalEntry(entry, false); + if (errCode != E_OK) { + break; + } + } + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::DeleteSyncEntries(const std::vector &keys) +{ + int errCode = E_OK; + for (const auto &key : keys) { + Entry entry; + DBCommon::CalcValueHash(key, entry.key); + errCode = SaveEntry(entry, true); + if ((errCode != E_OK) && (errCode != -E_NOT_FOUND)) { + LOGE("[DeleteSyncEntries] Delete data err:%d", errCode); + break; + } + } + return (errCode == -E_NOT_FOUND) ? E_OK : errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::DeleteLocalEntries(const std::vector &keys) +{ + int errCode = E_OK; + for (const auto &key : keys) { + Entry entry = {key, Value()}; + errCode = SaveLocalEntry(entry, true); + if ((errCode != E_OK) && (errCode != -E_NOT_FOUND)) { + LOGE("[DeleteLocalEntries] Delete data err:%d", errCode); + break; + } + } + return (errCode == -E_NOT_FOUND) ? E_OK : errCode; +} + +// This function currently only be called in local procedure to change sync_data table, do not use in sync procedure. +// It will check and amend value when need if it is a schema database. return error if some value disagree with the +// schema. But in sync procedure, we just neglect the value that disagree with schema. +int SQLiteSingleVerNaturalStoreConnection::SaveEntry(const Entry &entry, bool isDelete, Timestamp timestamp) +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + + DataItem dataItem; + dataItem.key = entry.key; + dataItem.value = entry.value; + dataItem.flag = DataItem::LOCAL_FLAG; + if (isDelete) { + dataItem.flag |= DataItem::DELETE_FLAG; + } else { + int errCode = CheckAmendValueContentForLocalProcedure(dataItem.value, dataItem.value); + if (errCode != E_OK) { + LOGE("[SqlSinCon][SaveEntry] CheckAmendValue fail, errCode=%d.", errCode); + return errCode; + } + } + + dataItem.timestamp = naturalStore->GetCurrentTimestamp(); + if (currentMaxTimestamp_ > dataItem.timestamp) { + dataItem.timestamp = currentMaxTimestamp_; + } + + if (timestamp != 0) { + dataItem.writeTimestamp = timestamp; + } else { + dataItem.writeTimestamp = dataItem.timestamp; + } + + if (IsExtendedCacheDBMode()) { + uint64_t recordVersion = naturalStore->GetCacheRecordVersion(); + return SaveEntryInCacheMode(dataItem, recordVersion); + } else { + return SaveEntryNormally(dataItem); + } +} + +int SQLiteSingleVerNaturalStoreConnection::SaveLocalEntry(const Entry &entry, bool isDelete) +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + + LocalDataItem dataItem; + dataItem.key = entry.key; + dataItem.value = entry.value; + (void)DBCommon::CalcValueHash(entry.key, dataItem.hashKey); + if (isDelete) { + dataItem.flag = DataItem::DELETE_FLAG; + } + dataItem.timestamp = naturalStore->GetCurrentTimestamp(); + LOGD("Timestamp is %" PRIu64, dataItem.timestamp); + + if (IsCacheDBMode()) { + return SaveLocalItemInCacheMode(dataItem); + } else { + return SaveLocalItem(dataItem); + } +} + +int SQLiteSingleVerNaturalStoreConnection::SaveLocalItem(const LocalDataItem &dataItem) const +{ + int errCode = E_OK; + if ((dataItem.flag & DataItem::DELETE_FLAG) == 0) { + errCode = writeHandle_->PutKvData(SingleVerDataType::LOCAL_TYPE, dataItem.key, dataItem.value, + dataItem.timestamp, localCommittedData_); + } else { + Value value; + Timestamp localTimestamp = 0; + errCode = writeHandle_->DeleteLocalKvData(dataItem.key, localCommittedData_, value, localTimestamp); + } + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::SaveLocalItemInCacheMode(const LocalDataItem &dataItem) const +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + + int errCode = writeHandle_->PutLocalDataToCacheDB(dataItem); + if (errCode != E_OK) { + LOGE("[PutLocalEntries] Put local data to cacheDB err:%d", errCode); + } + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::SaveEntryNormally(DataItem &dataItem) +{ + int errCode = writeHandle_->PrepareForSavingData(SingleVerDataType::SYNC_TYPE); + if (errCode != E_OK) { + LOGE("Prepare the saving sync data failed:%d", errCode); + return errCode; + } + + Timestamp maxTimestamp = 0; + DeviceInfo deviceInfo = {true, ""}; + errCode = writeHandle_->SaveSyncDataItem(dataItem, deviceInfo, maxTimestamp, committedData_); + if (errCode == E_OK) { + if (maxTimestamp > currentMaxTimestamp_) { + currentMaxTimestamp_ = maxTimestamp; + } + } else { + LOGE("Save entry failed, err:%d", errCode); + } + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::SaveEntryInCacheMode(DataItem &dataItem, uint64_t recordVersion) +{ + int errCode = writeHandle_->PrepareForSavingCacheData(SingleVerDataType::SYNC_TYPE); + if (errCode != E_OK) { + LOGE("Prepare the saving sync data failed:%d", errCode); + return errCode; + } + + Timestamp maxTimestamp = 0; + DeviceInfo deviceInfo = {true, ""}; + QueryObject query(Query::Select()); + errCode = writeHandle_->SaveSyncDataItemInCacheMode(dataItem, deviceInfo, maxTimestamp, recordVersion, query); + if (errCode == E_OK) { + if (maxTimestamp > currentMaxTimestamp_) { + currentMaxTimestamp_ = maxTimestamp; + } + } else { + LOGE("Save entry failed, err:%d", errCode); + } + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::CheckDataStatus(const Key &key, const Value &value, bool isDelete) const +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + + return naturalStore->CheckDataStatus(key, value, isDelete); +} + +int SQLiteSingleVerNaturalStoreConnection::CheckWritePermission() const +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + + if (!naturalStore->CheckWritePermission()) { + return -E_READ_ONLY; + } + return E_OK; +} + +int SQLiteSingleVerNaturalStoreConnection::CheckSyncEntriesValid(const std::vector &entries) const +{ + if (entries.size() > DBConstant::MAX_BATCH_SIZE) { + return -E_INVALID_ARGS; + } + + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + + if (!naturalStore->CheckWritePermission()) { + return -E_READ_ONLY; + } + + for (const auto &entry : entries) { + int errCode = naturalStore->CheckDataStatus(entry.key, entry.value, false); + if (errCode != E_OK) { + return errCode; + } + } + return E_OK; +} + +int SQLiteSingleVerNaturalStoreConnection::CheckSyncKeysValid(const std::vector &keys) const +{ + if (keys.size() > DBConstant::MAX_BATCH_SIZE) { + return -E_INVALID_ARGS; + } + + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + + if (!naturalStore->CheckWritePermission()) { + return -E_READ_ONLY; + } + + for (const auto &key : keys) { + int errCode = naturalStore->CheckDataStatus(key, {}, true); + if (errCode != E_OK) { + return errCode; + } + } + return E_OK; +} + +int SQLiteSingleVerNaturalStoreConnection::CheckLocalEntriesValid(const std::vector &entries) const +{ + if (entries.size() > DBConstant::MAX_BATCH_SIZE) { + return -E_INVALID_ARGS; + } + + GenericKvDB *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + + if (!naturalStore->GenericKvDB::CheckWritePermission()) { + return -E_READ_ONLY; + } + + for (const auto &entry : entries) { + int errCode = naturalStore->GenericKvDB::CheckDataStatus(entry.key, entry.value, false); + if (errCode != E_OK) { + return errCode; + } + } + return E_OK; +} + +int SQLiteSingleVerNaturalStoreConnection::CheckLocalKeysValid(const std::vector &keys) const +{ + if (keys.size() > DBConstant::MAX_BATCH_SIZE) { + return -E_INVALID_ARGS; + } + + GenericKvDB *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + + if (!naturalStore->GenericKvDB::CheckWritePermission()) { + return -E_READ_ONLY; + } + + for (const auto &key : keys) { + int errCode = naturalStore->GenericKvDB::CheckDataStatus(key, {}, true); + if (errCode != E_OK) { + return errCode; + } + } + return E_OK; +} + +void SQLiteSingleVerNaturalStoreConnection::CommitAndReleaseNotifyData( + SingleVerNaturalStoreCommitNotifyData *&committedData, bool isNeedCommit, int eventType) +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if ((naturalStore != nullptr) && (committedData != nullptr)) { + if (isNeedCommit) { + if (!committedData->IsChangedDataEmpty()) { + naturalStore->CommitNotify(eventType, committedData); + } + if (!committedData->IsConflictedDataEmpty()) { + naturalStore->CommitNotify(SQLITE_GENERAL_CONFLICT_EVENT, committedData); + } + } + } + ReleaseCommitData(committedData); +} + +int SQLiteSingleVerNaturalStoreConnection::StartTransactionInner() +{ + if (IsExtendedCacheDBMode()) { + return StartTransactionInCacheMode(); + } else { + return StartTransactionNormally(); + } +} + +int SQLiteSingleVerNaturalStoreConnection::StartTransactionInCacheMode() +{ + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetExecutor(true, errCode); + if (handle == nullptr) { + return errCode; + } + if (CheckLogOverLimit(handle)) { + LOGW("Over the log limit"); + ReleaseExecutor(handle); + return -E_LOG_OVER_LIMITS; + } + errCode = handle->StartTransaction(TransactType::DEFERRED); + if (errCode != E_OK) { + ReleaseExecutor(handle); + return errCode; + } + + writeHandle_ = handle; + transactionEntrySize_ = 0; + return E_OK; +} + +int SQLiteSingleVerNaturalStoreConnection::StartTransactionNormally() +{ + int errCode = E_OK; + SQLiteSingleVerStorageExecutor *handle = GetExecutor(true, errCode); + if (handle == nullptr) { + return errCode; + } + if (CheckLogOverLimit(handle)) { + LOGW("Over the log limit"); + ReleaseExecutor(handle); + return -E_LOG_OVER_LIMITS; + } + + if (committedData_ == nullptr) { + committedData_ = new (std::nothrow) SingleVerNaturalStoreCommitNotifyData; + if (committedData_ == nullptr) { + ReleaseExecutor(handle); + return -E_OUT_OF_MEMORY; + } + InitConflictNotifiedFlag(); + } + if (localCommittedData_ == nullptr) { + localCommittedData_ = new (std::nothrow) SingleVerNaturalStoreCommitNotifyData; + if (localCommittedData_ == nullptr) { + ReleaseExecutor(handle); + ReleaseCommitData(committedData_); + return -E_OUT_OF_MEMORY; + } + } + errCode = handle->StartTransaction(TransactType::DEFERRED); + if (errCode != E_OK) { + ReleaseExecutor(handle); + ReleaseCommitData(committedData_); + ReleaseCommitData(localCommittedData_); + return errCode; + } + + writeHandle_ = handle; + transactionEntrySize_ = 0; + return E_OK; +} + +void SQLiteSingleVerNaturalStoreConnection::InitConflictNotifiedFlag() +{ + unsigned int conflictFlag = 0; + if (kvDB_->GetRegisterFunctionCount(CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ONLY) != 0) { + conflictFlag |= static_cast(SQLITE_GENERAL_NS_FOREIGN_KEY_ONLY); + } + if (kvDB_->GetRegisterFunctionCount(CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ORIG) != 0) { + conflictFlag |= static_cast(SQLITE_GENERAL_NS_FOREIGN_KEY_ORIG); + } + if (kvDB_->GetRegisterFunctionCount(CONFLICT_SINGLE_VERSION_NS_NATIVE_ALL) != 0) { + conflictFlag |= static_cast(SQLITE_GENERAL_NS_NATIVE_ALL); + } + + committedData_->SetConflictedNotifiedFlag(static_cast(conflictFlag)); +} + +int SQLiteSingleVerNaturalStoreConnection::CommitInner() +{ + bool isCacheOrMigrating = IsExtendedCacheDBMode(); + + int errCode = writeHandle_->Commit(); + ReleaseExecutor(writeHandle_); + transactionEntrySize_ = 0; + + if (!isCacheOrMigrating) { + CommitAndReleaseNotifyData(committedData_, true, SQLITE_GENERAL_NS_PUT_EVENT); + CommitAndReleaseNotifyData(localCommittedData_, true, SQLITE_GENERAL_NS_LOCAL_PUT_EVENT); + } + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + naturalStore->SetMaxTimestamp(currentMaxTimestamp_); + + if (isCacheOrMigrating) { + naturalStore->IncreaseCacheRecordVersion(); + } + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::RollbackInner() +{ + int errCode = writeHandle_->Rollback(); + transactionEntrySize_ = 0; + currentMaxTimestamp_ = 0; + if (!IsExtendedCacheDBMode()) { + ReleaseCommitData(committedData_); + ReleaseCommitData(localCommittedData_); + } + ReleaseExecutor(writeHandle_); + return errCode; +} + +SQLiteSingleVerStorageExecutor *SQLiteSingleVerNaturalStoreConnection::GetExecutor(bool isWrite, int &errCode) const +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + errCode = -E_NOT_INIT; + LOGE("[SingleVerConnection] the store is null"); + return nullptr; + } + return naturalStore->GetHandle(isWrite, errCode); +} + +bool SQLiteSingleVerNaturalStoreConnection::IsCacheDBMode() const +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + LOGE("[SingleVerConnection] the store is null"); + return false; + } + return naturalStore->IsCacheDBMode(); +} + +bool SQLiteSingleVerNaturalStoreConnection::IsExtendedCacheDBMode() const +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + LOGE("[SingleVerConnection] the store is null"); + return false; + } + return naturalStore->IsExtendedCacheDBMode(); +} + +void SQLiteSingleVerNaturalStoreConnection::ReleaseExecutor(SQLiteSingleVerStorageExecutor *&executor) const +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore != nullptr) { + naturalStore->ReleaseHandle(executor); + } +} + +int SQLiteSingleVerNaturalStoreConnection::PublishLocal(const Key &key, bool deleteLocal, bool updateTimestamp, + const KvStoreNbPublishAction &onConflict) +{ + int errCode = CheckWritePermission(); + if (errCode != E_OK) { + return errCode; + } + + bool isNeedCallback = (onConflict != nullptr); + SingleVerRecord localRecord; + localRecord.key = key; + SingleVerRecord syncRecord; + { + if (IsTransactionStarted()) { + return -E_NOT_SUPPORT; + } + std::lock_guard lock(transactionMutex_); + errCode = StartTransactionInner(); + if (errCode != E_OK) { + return errCode; + } + + SingleVerNaturalStoreCommitNotifyData *localCommittedData = nullptr; + if (deleteLocal) { + localCommittedData = new (std::nothrow) SingleVerNaturalStoreCommitNotifyData; + if (localCommittedData == nullptr) { + errCode = -E_OUT_OF_MEMORY; + } + } + if (errCode == E_OK) { + errCode = PublishInner(localCommittedData, updateTimestamp, localRecord, syncRecord, isNeedCallback); + } + + if (errCode != E_OK || isNeedCallback) { + int innerCode = RollbackInner(); + errCode = (innerCode != E_OK) ? innerCode : errCode; + } else { + errCode = CommitInner(); + if (errCode == E_OK) { + CommitAndReleaseNotifyData(localCommittedData, true, SQLITE_GENERAL_NS_LOCAL_PUT_EVENT); + } + } + ReleaseCommitData(localCommittedData); + } + + // need to release the handle lock before callback invoked + if (isNeedCallback) { + return PublishLocalCallback(updateTimestamp, localRecord, syncRecord, onConflict); + } + + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::PublishLocalCallback(bool updateTimestamp, + const SingleVerRecord &localRecord, const SingleVerRecord &syncRecord, const KvStoreNbPublishAction &onConflict) +{ + bool isLocalLastest = updateTimestamp ? true : (localRecord.timestamp > syncRecord.writeTimestamp); + if ((syncRecord.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG) { + onConflict({localRecord.key, localRecord.value}, nullptr, isLocalLastest); + } else { + Entry syncEntry = {syncRecord.key, syncRecord.value}; + onConflict({localRecord.key, localRecord.value}, &syncEntry, isLocalLastest); + } + return E_OK; +} + +int SQLiteSingleVerNaturalStoreConnection::PublishInner(SingleVerNaturalStoreCommitNotifyData *committedData, + bool updateTimestamp, SingleVerRecord &localRecord, SingleVerRecord &syncRecord, bool &isNeedCallback) +{ + Key hashKey; + int errCode = DBCommon::CalcValueHash(localRecord.key, hashKey); + if (errCode != E_OK) { + return errCode; + } + + if (committedData != nullptr) { + errCode = writeHandle_->DeleteLocalKvData(localRecord.key, committedData, localRecord.value, + localRecord.timestamp); + if (errCode != E_OK) { + LOGE("Delete local kv data err:%d", errCode); + return errCode; + } + } else { + if (!writeHandle_->CheckIfKeyExisted(localRecord.key, true, localRecord.value, localRecord.timestamp)) { + LOGE("Record not found."); + return -E_NOT_FOUND; + } + } + + // begin to insert entry to sync table + errCode = CheckDataStatus(localRecord.key, localRecord.value, false); + if (errCode != E_OK) { + return errCode; + } + + errCode = writeHandle_->GetKvDataByHashKey(hashKey, syncRecord); + if (errCode == E_OK) { // has conflict record + if (isNeedCallback) { + return errCode; + } + // fix conflict with LAST_WIN policy + if (updateTimestamp) { // local win + errCode = SaveEntry({localRecord.key, localRecord.value}, false); + } else { + if (localRecord.timestamp <= syncRecord.writeTimestamp) { // sync win + errCode = -E_STALE; + } else { + errCode = SaveEntry({localRecord.key, localRecord.value}, false, localRecord.timestamp); + } + } + } else { + isNeedCallback = false; + if (errCode == -E_NOT_FOUND) { + errCode = SaveEntry({localRecord.key, localRecord.value}, false, localRecord.timestamp); + } + } + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::UnpublishToLocal(const Key &key, bool deletePublic, bool updateTimestamp) +{ + int errCode = CheckWritePermission(); + if (errCode != E_OK) { + return errCode; + } + + if (IsTransactionStarted()) { + return -E_NOT_SUPPORT; + } + + std::lock_guard lock(transactionMutex_); + + errCode = StartTransactionInner(); + if (errCode != E_OK) { + return errCode; + } + + Key hashKey; + int innerErrCode = E_OK; + SingleVerRecord syncRecord; + SingleVerNaturalStoreCommitNotifyData *localCommittedData = nullptr; + errCode = DBCommon::CalcValueHash(key, hashKey); + if (errCode != E_OK) { + goto END; + } + + errCode = writeHandle_->GetKvDataByHashKey(hashKey, syncRecord); + if (errCode != E_OK) { + goto END; + } + + syncRecord.key = key; + errCode = UnpublishInner(localCommittedData, syncRecord, updateTimestamp, innerErrCode); + if (errCode != E_OK) { + goto END; + } + + if (deletePublic && (syncRecord.flag & DataItem::DELETE_FLAG) != DataItem::DELETE_FLAG) { + errCode = SaveEntry({hashKey, {}}, true); + if (errCode != E_OK) { + goto END; + } + } + +END: + // finalize + if (errCode != E_OK) { + int rollbackRet = RollbackInner(); + errCode = (rollbackRet != E_OK) ? rollbackRet : errCode; + } else { + errCode = CommitInner(); + if (errCode == E_OK) { + CommitAndReleaseNotifyData(localCommittedData, true, SQLITE_GENERAL_NS_LOCAL_PUT_EVENT); + } + } + ReleaseCommitData(localCommittedData); + + return (errCode == E_OK) ? innerErrCode : errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::UnpublishInner(SingleVerNaturalStoreCommitNotifyData *&committedData, + const SingleVerRecord &syncRecord, bool updateTimestamp, int &innerErrCode) +{ + int errCode = E_OK; + int localOperation = LOCAL_OPR_NONE; + SingleVerRecord localRecord; + + innerErrCode = -E_LOCAL_DEFEAT; + if (writeHandle_->CheckIfKeyExisted(syncRecord.key, true, localRecord.value, localRecord.timestamp)) { + if ((syncRecord.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG) { + if (updateTimestamp || localRecord.timestamp <= syncRecord.writeTimestamp) { // sync win + innerErrCode = -E_LOCAL_DELETED; + localOperation = LOCAL_OPR_DEL; + } + } else if (updateTimestamp || localRecord.timestamp <= syncRecord.writeTimestamp) { // sync win + innerErrCode = -E_LOCAL_COVERED; + localOperation = LOCAL_OPR_PUT; + } + } else { // no conflict entry in local + innerErrCode = E_OK; + if ((syncRecord.flag & DataItem::DELETE_FLAG) != DataItem::DELETE_FLAG) { + localOperation = LOCAL_OPR_PUT; + } + } + + if (localOperation != LOCAL_OPR_NONE) { + errCode = UnpublishOper(committedData, syncRecord, updateTimestamp, localOperation); + } + + return errCode; +} + +int SQLiteSingleVerNaturalStoreConnection::UnpublishOper(SingleVerNaturalStoreCommitNotifyData *&committedData, + const SingleVerRecord &syncRecord, bool updateTimestamp, int operType) +{ + committedData = new (std::nothrow) SingleVerNaturalStoreCommitNotifyData; + if (committedData == nullptr) { + return -E_OUT_OF_MEMORY; + } + + int errCode = E_OK; + if (operType == LOCAL_OPR_PUT) { + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_DB; + } + + errCode = CheckDataStatus(syncRecord.key, syncRecord.value, false); + if (errCode != E_OK) { + return errCode; + } + + Timestamp time = updateTimestamp ? naturalStore->GetCurrentTimestamp() : syncRecord.writeTimestamp; + errCode = writeHandle_->PutKvData(SingleVerDataType::LOCAL_TYPE, syncRecord.key, syncRecord.value, time, + committedData); + } else if (operType == LOCAL_OPR_DEL) { + Timestamp localTimestamp = 0; + Value value; + errCode = writeHandle_->DeleteLocalKvData(syncRecord.key, committedData, value, localTimestamp); + } + + return errCode; +} + +void SQLiteSingleVerNaturalStoreConnection::ReleaseCommitData(SingleVerNaturalStoreCommitNotifyData *&committedData) +{ + if (committedData != nullptr) { + committedData->DecObjRef(committedData); + committedData = nullptr; + } +} + +int SQLiteSingleVerNaturalStoreConnection::PragmaPublish(void *parameter) +{ + PragmaPublishInfo *info = static_cast(parameter); + if (info == nullptr) { + return -E_INVALID_ARGS; + } + if (IsExtendedCacheDBMode()) { + int err = IsCacheDBMode() ? -E_EKEYREVOKED : -E_BUSY; + LOGE("[PragmaPublish]Existed cache database can not read data, errCode = [%d]!", err); + return err; + } + return PublishLocal(info->key, info->deleteLocal, info->updateTimestamp, info->action); +} + +int SQLiteSingleVerNaturalStoreConnection::PragmaUnpublish(void *parameter) +{ + PragmaUnpublishInfo *info = static_cast(parameter); + if (info == nullptr) { + return -E_INVALID_ARGS; + } + if (IsExtendedCacheDBMode()) { + int err = IsCacheDBMode() ? -E_EKEYREVOKED : -E_BUSY; + LOGE("[PragmaUnpublish]Existed cache database can not read data, errCode = [%d]!", err); + return err; + } + return UnpublishToLocal(info->key, info->isDeleteSync, info->isUpdateTime); +} + +int SQLiteSingleVerNaturalStoreConnection::PragmaSetAutoLifeCycle(const uint32_t *lifeTime) +{ + if (lifeTime == nullptr || *lifeTime > MAX_AUTO_LIFE_CYCLE || *lifeTime < MIN_AUTO_LIFE_CYCLE) { + return -E_INVALID_ARGS; + } + if (kvDB_ == nullptr) { + return -E_INVALID_DB; + } + return static_cast(kvDB_)->SetAutoLifeCycleTime(*lifeTime); +} + +int SQLiteSingleVerNaturalStoreConnection::PragmaResultSetCacheMode(PragmaData inMode) +{ + if (inMode == nullptr) { + return -E_INVALID_ARGS; + } + auto mode = *(static_cast(inMode)); + if (mode != ResultSetCacheMode::CACHE_FULL_ENTRY && mode != ResultSetCacheMode::CACHE_ENTRY_ID_ONLY) { + return -E_INVALID_ARGS; + } + cacheModeForNewResultSet_.store(mode); + return E_OK; +} + +int SQLiteSingleVerNaturalStoreConnection::PragmaResultSetCacheMaxSize(PragmaData inSize) +{ + if (inSize == nullptr) { + return -E_INVALID_ARGS; + } + int size = *(static_cast(inSize)); + if (size < RESULT_SET_CACHE_MAX_SIZE_MIN || size > RESULT_SET_CACHE_MAX_SIZE_MAX) { + return -E_INVALID_ARGS; + } + cacheMaxSizeForNewResultSet_.store(size); + return E_OK; +} + +// use for getkvstore migrating cache data +int SQLiteSingleVerNaturalStoreConnection::PragmaTriggerToMigrateData(const SecurityOption &secOption) const +{ + if ((secOption.securityLabel != S3) || (secOption.securityFlag != SECE)) { + LOGD("Only S3 SECE data need migrate data!"); + return E_OK; + } + + LOGI("Begin trigger the migration data while open the database!"); + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { + return -E_INVALID_CONNECTION; + } + return naturalStore->TriggerToMigrateData(); +} + +int SQLiteSingleVerNaturalStoreConnection::CheckAmendValueContentForLocalProcedure(const Value &oriValue, + Value &amendValue) const +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr) { // Not Likely + return -E_INVALID_DB; + } + bool useAmendValue = false; + return naturalStore->CheckValueAndAmendIfNeed(ValueSource::FROM_LOCAL, oriValue, amendValue, useAmendValue); +} + +bool SQLiteSingleVerNaturalStoreConnection::CheckLogOverLimit(SQLiteSingleVerStorageExecutor *executor) const +{ + SQLiteSingleVerNaturalStore *naturalStore = GetDB(); + if (naturalStore == nullptr || executor == nullptr) { // Not Likely + return false; + } + uint64_t logFileSize = executor->GetLogFileSize(); + bool result = logFileSize > naturalStore->GetMaxLogSize(); + if (result) { + LOGW("Log size[%" PRIu64 "] over the limit", logFileSize); + } + return result; +} + +int SQLiteSingleVerNaturalStoreConnection::CalcHashDevID(PragmaDeviceIdentifier &pragmaDev) +{ + if (pragmaDev.deviceID.empty()) { + return -E_INVALID_ARGS; + } + pragmaDev.deviceIdentifier = DBCommon::TransferHashString(pragmaDev.deviceID); + return E_OK; +} + +DEFINE_OBJECT_TAG_FACILITIES(SQLiteSingleVerNaturalStoreConnection) +} \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store_connection.h b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store_connection.h new file mode 100644 index 00000000..dfaf1d68 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_natural_store_connection.h @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_SINGLE_VER_NATURAL_STORE_CONNECTION_H +#define SQLITE_SINGLE_VER_NATURAL_STORE_CONNECTION_H + +#include "sync_able_kvdb_connection.h" +#include "sqlite_single_ver_storage_executor.h" +#include "db_types.h" +#include "runtime_context.h" + +namespace DistributedDB { +class SQLiteSingleVerNaturalStore; + +class SQLiteSingleVerNaturalStoreConnection : public SyncAbleKvDBConnection { +public: + explicit SQLiteSingleVerNaturalStoreConnection(SQLiteSingleVerNaturalStore *kvDB); + ~SQLiteSingleVerNaturalStoreConnection() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteSingleVerNaturalStoreConnection); + + // Get the value from the database + int Get(const IOption &option, const Key &key, Value &value) const override; + + // Put the value to the database + int Put(const IOption &option, const Key &key, const Value &value) override; + + // Delete the value from the database + int Delete(const IOption &option, const Key &key) override; + + // Clear all the data from the database + int Clear(const IOption &option) override; + + // Get all the data from the database + int GetEntries(const IOption &option, const Key &keyPrefix, std::vector &entries) const override; + + int GetEntries(const IOption &option, const Query &query, std::vector &entries) const override; + + int GetCount(const IOption &option, const Query &query, int &count) const override; + + // Put the batch values to the database. + int PutBatch(const IOption &option, const std::vector &entries) override; + + // Delete the batch values from the database. + int DeleteBatch(const IOption &option, const std::vector &keys) override; + + // Get the snapshot + int GetSnapshot(IKvDBSnapshot *&snapshot) const override; + + // Release the created snapshot + void ReleaseSnapshot(IKvDBSnapshot *&snapshot) override; + + // Start the transaction + int StartTransaction() override; + + // Commit the transaction + int Commit() override; + + // Roll back the transaction + int RollBack() override; + + // Check if the transaction already started manually + bool IsTransactionStarted() const override; + + // Pragma interface. + int Pragma(int cmd, void *parameter) override; + + // Parse event types(from observer mode). + int TranslateObserverModeToEventTypes(unsigned mode, std::list &eventTypes) const override; + + // Register a conflict notifier. + int SetConflictNotifier(int conflictType, const KvDBConflictAction &action) override; + + int Rekey(const CipherPassword &passwd) override; + + int Export(const std::string &filePath, const CipherPassword &passwd) override; + + int Import(const std::string &filePath, const CipherPassword &passwd) override; + + // Get the result set + int GetResultSet(const IOption &option, const Key &keyPrefix, IKvDBResultSet *&resultSet) const override; + + int GetResultSet(const IOption &option, const Query &query, IKvDBResultSet *&resultSet) const override; + + // Release the result set + void ReleaseResultSet(IKvDBResultSet *&resultSet) override; + + int RegisterLifeCycleCallback(const DatabaseLifeCycleNotifier ¬ifier) override; + + // Called when Close and delete the connection. + int PreClose() override; + + int CheckIntegrity() const override; + +private: + int CheckMonoStatus(OperatePerm perm); + + int GetDeviceIdentifier(PragmaEntryDeviceIdentifier *identifier); + + void ClearConflictNotifierCount(); + + int PutBatchInner(const IOption &option, const std::vector &entries); + int DeleteBatchInner(const IOption &option, const std::vector &keys); + + int SaveSyncEntries(const std::vector &entries); + int SaveLocalEntries(const std::vector &entries); + int DeleteSyncEntries(const std::vector &keys); + int DeleteLocalEntries(const std::vector &keys); + + int SaveEntry(const Entry &entry, bool isDelete, Timestamp timestamp = 0); + + int CheckDataStatus(const Key &key, const Value &value, bool isDelete) const; + + int CheckWritePermission() const; + + int CheckSyncEntriesValid(const std::vector &entries) const; + + int CheckSyncKeysValid(const std::vector &keys) const; + + int CheckLocalEntriesValid(const std::vector &entries) const; + + int CheckLocalKeysValid(const std::vector &keys) const; + + void CommitAndReleaseNotifyData(SingleVerNaturalStoreCommitNotifyData *&committedData, + bool isNeedCommit, int eventType); + + int StartTransactionInner(); + + int CommitInner(); + + int RollbackInner(); + + int PublishLocal(const Key &key, bool deleteLocal, bool updateTimestamp, + const KvStoreNbPublishAction &onConflict); + + int PublishLocalCallback(bool updateTimestamp, const SingleVerRecord &localRecord, + const SingleVerRecord &syncRecord, const KvStoreNbPublishAction &onConflict); + + int PublishInner(SingleVerNaturalStoreCommitNotifyData *committedData, bool updateTimestamp, + SingleVerRecord &localRecord, SingleVerRecord &syncRecord, bool &isNeedCallback); + + int UnpublishToLocal(const Key &key, bool deletePublic, bool updateTimestamp); + + int UnpublishInner(SingleVerNaturalStoreCommitNotifyData *&committedData, const SingleVerRecord &syncRecord, + bool updateTimestamp, int &innerErrCode); + + int UnpublishOper(SingleVerNaturalStoreCommitNotifyData *&committedData, const SingleVerRecord &syncRecord, + bool updateTimestamp, int operType); + + void ReleaseCommitData(SingleVerNaturalStoreCommitNotifyData *&committedData); + + int PragmaPublish(void *parameter); + + int PragmaUnpublish(void *parameter); + + SQLiteSingleVerStorageExecutor *GetExecutor(bool isWrite, int &errCode) const; + + void ReleaseExecutor(SQLiteSingleVerStorageExecutor *&executor) const; + + int PragmaSetAutoLifeCycle(const uint32_t *lifeTime); + void InitConflictNotifiedFlag(); + void AddConflictNotifierCount(int target); + void ResetConflictNotifierCount(int target); + + int PragmaResultSetCacheMode(PragmaData inMode); + int PragmaResultSetCacheMaxSize(PragmaData inSize); + + // use for getkvstore migrating cache data + int PragmaTriggerToMigrateData(const SecurityOption &secOption) const; + int CheckAmendValueContentForLocalProcedure(const Value &oriValue, Value &amendValue) const; + + int SaveLocalEntry(const Entry &entry, bool isDelete); + int SaveLocalItem(const LocalDataItem &dataItem) const; + int SaveLocalItemInCacheMode(const LocalDataItem &dataItem) const; + int SaveEntryNormally(DataItem &dataItem); + int SaveEntryInCacheMode(DataItem &dataItem, uint64_t recordVersion); + + int StartTransactionInCacheMode(); + int StartTransactionNormally(); + + bool IsCacheDBMode() const; + bool IsExtendedCacheDBMode() const; + int CheckReadDataControlled() const; + bool IsFileAccessControlled() const; + + int PragmaSetMaxLogSize(uint64_t *limit); + int ForceCheckPoint() const; + + bool CheckLogOverLimit(SQLiteSingleVerStorageExecutor *executor) const; + int CalcHashDevID(PragmaDeviceIdentifier &pragmaDev); + + DECLARE_OBJECT_TAG(SQLiteSingleVerNaturalStoreConnection); + + // ResultSet Related Info + static constexpr std::size_t MAX_RESULT_SET_SIZE = 4; // Max 4 ResultSet At The Same Time + std::atomic cacheModeForNewResultSet_{ResultSetCacheMode::CACHE_FULL_ENTRY}; + std::atomic cacheMaxSizeForNewResultSet_{0}; // Will be init to default value in constructor + + int conflictType_; + uint32_t transactionEntrySize_; // used for transaction + Timestamp currentMaxTimestamp_; // used for transaction + SingleVerNaturalStoreCommitNotifyData *committedData_; // used for transaction + SingleVerNaturalStoreCommitNotifyData *localCommittedData_; + std::atomic transactionExeFlag_; + + NotificationChain::Listener *conflictListener_; + SQLiteSingleVerStorageExecutor *writeHandle_; // only existed while in transaction. + mutable std::set kvDbResultSets_; + std::mutex conflictMutex_; + std::mutex rekeyMutex_; + std::mutex importMutex_; + mutable std::mutex kvDbResultSetsMutex_; + mutable std::mutex transactionMutex_; // used for transaction +}; +} + +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_continue_token.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_continue_token.cpp new file mode 100644 index 00000000..671d8732 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_continue_token.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "sqlite_single_ver_relational_continue_token.h" +#include "sqlite_utils.h" + +namespace DistributedDB { +SQLiteSingleVerRelationalContinueToken::SQLiteSingleVerRelationalContinueToken( + const SyncTimeRange &timeRange, const QueryObject &object) + : isGettingDeletedData_(false), queryObj_(object), tableName_(queryObj_.GetTableName()), timeRange_(timeRange) +{} + +bool SQLiteSingleVerRelationalContinueToken::CheckValid() const +{ + bool isValid = (magicBegin_ == MAGIC_BEGIN && magicEnd_ == MAGIC_END); + if (!isValid) { + LOGE("Invalid continue token."); + } + return isValid; +} + +int SQLiteSingleVerRelationalContinueToken::GetStatement(sqlite3 *db, sqlite3_stmt *&queryStmt, sqlite3_stmt *&fullStmt, + bool &isGettingDeletedData) +{ + isGettingDeletedData = isGettingDeletedData_; + if (isGettingDeletedData) { + return GetDeletedDataStmt(db, queryStmt); + } + + int errCode = GetQuerySyncStatement(db, queryStmt); + if (errCode != E_OK) { + return errCode; + } + + // if lastQueryTime equals 0, that means never sync before, need not to send miss query data. + // if queryObj is empty, that means to send all data now, need not to send miss query data. + if (timeRange_.lastQueryTime != 0 && !queryObj_.Empty()) { + errCode = GetMissQueryStatement(db, fullStmt); + } + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(queryStmt, true, errCode); + } + return errCode; +} + +void SQLiteSingleVerRelationalContinueToken::SetNextBeginTime(const DataItem &theLastItem) +{ + Timestamp nextBeginTime = theLastItem.timestamp + 1; + if (nextBeginTime > INT64_MAX) { + nextBeginTime = INT64_MAX; + } + if (!isGettingDeletedData_) { + timeRange_.beginTime = nextBeginTime; + timeRange_.lastQueryTime = std::max(nextBeginTime, timeRange_.lastQueryTime); + return; + } + if ((theLastItem.flag & DataItem::DELETE_FLAG) != 0) { // The last one could be non-deleted. + timeRange_.deleteBeginTime = nextBeginTime; + } +} + +void SQLiteSingleVerRelationalContinueToken::FinishGetData() +{ + if (isGettingDeletedData_) { + timeRange_.deleteEndTime = 0; + return; + } + isGettingDeletedData_ = true; + timeRange_.endTime = 0; + return; +} + +bool SQLiteSingleVerRelationalContinueToken::IsGetAllDataFinished() const +{ + return timeRange_.beginTime >= timeRange_.endTime && timeRange_.deleteBeginTime >= timeRange_.deleteEndTime; +} + +int SQLiteSingleVerRelationalContinueToken::GetQuerySyncStatement(sqlite3 *db, sqlite3_stmt *&stmt) +{ + int errCode = E_OK; + SqliteQueryHelper helper = queryObj_.GetQueryHelper(errCode); + if (errCode != E_OK) { + return errCode; + } + if (fieldNames_.empty()) { + LOGE("field names cannot be empty."); + return -E_INTERNAL_ERROR; + } + return helper.GetRelationalQueryStatement(db, timeRange_.beginTime, timeRange_.endTime, fieldNames_, stmt); +} + +int SQLiteSingleVerRelationalContinueToken::GetMissQueryStatement(sqlite3 *db, sqlite3_stmt *&stmt) +{ + int errCode = E_OK; + SqliteQueryHelper helper = queryObj_.GetQueryHelper(errCode); + if (errCode != E_OK) { + return errCode; + } + return helper.GetRelationalMissQueryStatement(db, timeRange_.lastQueryTime + 1, INT64_MAX, fieldNames_, stmt); +} + +int SQLiteSingleVerRelationalContinueToken::GetDeletedDataStmt(sqlite3 *db, sqlite3_stmt *&stmt) const +{ + // get stmt + const std::string sql = GetDeletedDataSQL(); + int errCode = SQLiteUtils::GetStatement(db, sql, stmt); + if (errCode != E_OK) { + goto ERROR; + } + + // bind stmt + errCode = SQLiteUtils::BindInt64ToStatement(stmt, 1, timeRange_.deleteBeginTime); // 1 means begin time + if (errCode != E_OK) { + goto ERROR; + } + errCode = SQLiteUtils::BindInt64ToStatement(stmt, 2, timeRange_.deleteEndTime); // 2 means end time + if (errCode != E_OK) { + goto ERROR; + } + return errCode; + +ERROR: + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; +} + +const QueryObject &SQLiteSingleVerRelationalContinueToken::GetQuery() const +{ + return queryObj_; +} + +std::string SQLiteSingleVerRelationalContinueToken::GetDeletedDataSQL() const +{ + std::string tableName = DBConstant::RELATIONAL_PREFIX + tableName_ + "_log"; + return "SELECT * FROM " + tableName + + " WHERE timestamp >= ? AND timestamp < ? AND (flag&0x03 = 0x03) ORDER BY timestamp ASC;"; +} + +void SQLiteSingleVerRelationalContinueToken::SetFieldNames(const std::vector &fieldNames) +{ + fieldNames_ = fieldNames; +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_continue_token.h b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_continue_token.h new file mode 100644 index 00000000..04d3ed37 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_continue_token.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_SINGLE_VER_RELATIONAL_CONTINUE_TOKEN_H +#define SQLITE_SINGLE_VER_RELATIONAL_CONTINUE_TOKEN_H +#ifdef RELATIONAL_STORE +#include +#include + +#include "db_types.h" +#include "query_object.h" + +namespace DistributedDB { +class SQLiteSingleVerRelationalContinueToken { +public: + SQLiteSingleVerRelationalContinueToken(const SyncTimeRange &timeRange, const QueryObject &queryObject); + ~SQLiteSingleVerRelationalContinueToken() = default; + + // Check the magic number at the beginning and end of the SQLiteSingleVerRelationalContinueToken. + bool CheckValid() const; + // The statement should be release by the caller. + int GetStatement(sqlite3 *db, sqlite3_stmt *&queryStmt, sqlite3_stmt *&fullStmt, bool &isGettingDeletedData); + void SetNextBeginTime(const DataItem &theLastItem); + void FinishGetData(); + bool IsGetAllDataFinished() const; + const QueryObject &GetQuery() const; + void SetFieldNames(const std::vector &fieldNames); + +private: + std::string GetDeletedDataSQL() const; + int GetQuerySyncStatement(sqlite3 *db, sqlite3_stmt *&stmt); + int GetDeletedDataStmt(sqlite3 *db, sqlite3_stmt *&stmt) const; + int GetMissQueryStatement(sqlite3 *db, sqlite3_stmt *&stmt); + + static const unsigned int MAGIC_BEGIN = 0x600D0AC7; // for token guard + static const unsigned int MAGIC_END = 0x0AC7600D; // for token guard + unsigned int magicBegin_ = MAGIC_BEGIN; + int isGettingDeletedData_ = false; + QueryObject queryObj_; + const std::string &tableName_; + SyncTimeRange timeRange_; + std::vector fieldNames_; + unsigned int magicEnd_ = MAGIC_END; +}; +} // namespace DistributedDB +#endif // RELATIONAL_STORE +#endif // SQLITE_SINGLE_VER_RELATIONAL_CONTINUE_TOKEN_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_storage_executor.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_storage_executor.cpp new file mode 100644 index 00000000..80566783 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_storage_executor.cpp @@ -0,0 +1,1316 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "sqlite_single_ver_relational_storage_executor.h" +#include +#include "data_transformer.h" +#include "db_common.h" + +namespace DistributedDB { +SQLiteSingleVerRelationalStorageExecutor::SQLiteSingleVerRelationalStorageExecutor(sqlite3 *dbHandle, bool writable) + : SQLiteStorageExecutor(dbHandle, writable, false) +{} + +int SQLiteSingleVerRelationalStorageExecutor::CreateDistributedTable(const std::string &tableName, TableInfo &table, + bool isUpgrade) +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = SQLiteUtils::AnalysisSchema(dbHandle_, tableName, table); + if (errCode != E_OK) { + LOGE("[CreateDistributedTable] analysis table schema failed. %d", errCode); + return errCode; + } + + if (table.GetCreateTableSql().find("WITHOUT ROWID") != std::string::npos) { + LOGE("[CreateDistributedTable] Not support create distributed table without rowid."); + return -E_NOT_SUPPORT; + } + + bool isTableEmpty = false; + errCode = SQLiteUtils::CheckTableEmpty(dbHandle_, tableName, isTableEmpty); + if (errCode != E_OK) { + LOGE("[CreateDistributedTable] Check table [%s] is empty failed. %d", tableName.c_str(), errCode); + return errCode; + } + + if (!isUpgrade && !isTableEmpty) { // create distributed table should on an empty table + LOGE("[CreateDistributedTable] Create distributed table should on an empty table when first create."); + return -E_NOT_SUPPORT; + } + + // create log table + errCode = SQLiteUtils::CreateRelationalLogTable(dbHandle_, tableName); + if (errCode != E_OK) { + LOGE("[CreateDistributedTable] create log table failed"); + return errCode; + } + + // add trigger + errCode = SQLiteUtils::AddRelationalLogTableTrigger(dbHandle_, table); + if (errCode != E_OK) { + LOGE("[CreateDistributedTable] Add relational log table trigger failed."); + return errCode; + } + return E_OK; +} + +int SQLiteSingleVerRelationalStorageExecutor::UpgradeDistributedTable(const TableInfo &tableInfo, + TableInfo &newTableInfo) +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = SQLiteUtils::AnalysisSchema(dbHandle_, tableInfo.GetTableName(), newTableInfo); + if (errCode != E_OK) { + LOGE("[UpgradeDistributedTable] analysis table schema failed. %d", errCode); + return errCode; + } + + if (newTableInfo.GetCreateTableSql().find("WITHOUT ROWID") != std::string::npos) { + LOGE("[UpgradeDistributedTable] Not support create distributed table without rowid."); + return -E_NOT_SUPPORT; + } + + // new table should has same or compatible upgrade + errCode = tableInfo.CompareWithTable(newTableInfo); + if (errCode == -E_RELATIONAL_TABLE_INCOMPATIBLE) { + LOGE("[UpgradeDistributedTable] Not support with incompatible upgrade."); + return -E_SCHEMA_MISMATCH; + } + + errCode = AlterAuxTableForUpgrade(tableInfo, newTableInfo); + if (errCode != E_OK) { + LOGE("[UpgradeDistributedTable] Alter aux table for upgrade failed. %d", errCode); + } + + return errCode; +} + +namespace { +int GetDeviceTableName(sqlite3 *handle, const std::string &tableName, const std::string &device, + std::vector &deviceTables) +{ + if (device.empty() && tableName.empty()) { // device and table name should not both be empty + return -E_INVALID_ARGS; + } + std::string deviceHash = DBCommon::TransferStringToHex(DBCommon::TransferHashString(device)); + std::string devicePattern = device.empty() ? "%" : deviceHash; + std::string tablePattern = tableName.empty() ? "%" : tableName; + std::string deviceTableName = DBConstant::RELATIONAL_PREFIX + tablePattern + "_" + devicePattern; + + const std::string checkSql = "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '" + + deviceTableName + "';"; + sqlite3_stmt *stmt = nullptr; + int errCode = SQLiteUtils::GetStatement(handle, checkSql, stmt); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; + } + + do { + errCode = SQLiteUtils::StepWithRetry(stmt, false); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + break; + } else if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + LOGE("Get table name failed. %d", errCode); + break; + } + std::string realTableName; + errCode = SQLiteUtils::GetColumnTextValue(stmt, 0, realTableName); // 0: table name result column index + if (errCode != E_OK || realTableName.empty()) { // sqlite might return a row with NULL + continue; + } + if (realTableName.rfind("_log") == (realTableName.length() - 4)) { // 4:suffix length of "_log" + continue; + } + deviceTables.emplace_back(realTableName); + } while (true); + + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; +} + +std::vector GetUpgradeFields(const TableInfo &oldTableInfo, const TableInfo &newTableInfo) +{ + std::vector fields; + auto itOld = oldTableInfo.GetFields().begin(); + auto itNew = newTableInfo.GetFields().begin(); + for (; itNew != newTableInfo.GetFields().end(); itNew++) { + if (itOld == oldTableInfo.GetFields().end() || itOld->first != itNew->first) { + fields.emplace_back(itNew->second); + continue; + } + itOld++; + } + return fields; +} + +int UpgradeFields(sqlite3 *db, const std::vector &tables, std::vector &fields) +{ + if (db == nullptr) { + return -E_INVALID_ARGS; + } + + std::sort(fields.begin(), fields.end(), [] (const FieldInfo &a, const FieldInfo &b) { + return a.GetColumnId()< b.GetColumnId(); + }); + int errCode = E_OK; + for (const auto &table : tables) { + for (const auto &field : fields) { + std::string alterSql = "ALTER TABLE " + table + " ADD " + field.GetFieldName() + " " + field.GetDataType(); + alterSql += field.IsNotNull() ? " NOT NULL" : ""; + alterSql += field.HasDefaultValue() ? " DEFAULT " + field.GetDefaultValue() : ""; + alterSql += ";"; + errCode = SQLiteUtils::ExecuteRawSQL(db, alterSql); + if (errCode != E_OK) { + LOGE("Alter table failed. %d", errCode); + break; + } + } + } + return errCode; +} + +std::map GetChangedIndexes(const TableInfo &oldTableInfo, const TableInfo &newTableInfo) +{ + std::map indexes; + auto itOld = oldTableInfo.GetIndexDefine().begin(); + auto itNew = newTableInfo.GetIndexDefine().begin(); + auto itOldEnd = oldTableInfo.GetIndexDefine().end(); + auto itNewEnd = newTableInfo.GetIndexDefine().end(); + + while (itOld != itOldEnd && itNew != itNewEnd) { + if (itOld->first == itNew->first) { + if (itOld->second != itNew->second) { + indexes.insert({itNew->first, itNew->second}); + } + itOld++; + itNew++; + } else if (itOld->first < itNew->first) { + indexes.insert({itOld->first, {}}); + itOld++; + } else if (itOld->first > itNew->first) { + indexes.insert({itNew->first, itNew->second}); + itNew++; + } + } + + while (itOld != itOldEnd) { + indexes.insert({itOld->first, {}}); + itOld++; + } + + while (itNew != itNewEnd) { + indexes.insert({itNew->first, itNew->second}); + itNew++; + } + + return indexes; +} + +int Upgradeindexes(sqlite3 *db, const std::vector &tables, + const std::map &indexes) +{ + if (db == nullptr) { + return -E_INVALID_ARGS; + } + + int errCode = E_OK; + for (const auto &table : tables) { + for (const auto &index : indexes) { + if (index.first.empty()) { + continue; + } + std::string realIndexName = table + "_" + index.first; + std::string deleteIndexSql = "DROP INDEX IF EXISTS " + realIndexName; + errCode = SQLiteUtils::ExecuteRawSQL(db, deleteIndexSql); + if (errCode != E_OK) { + LOGE("Drop index failed. %d", errCode); + return errCode; + } + + if (index.second.empty()) { // empty means drop index only + continue; + } + + auto it = index.second.begin(); + std::string indexDefine = *it++; + while (it != index.second.end()) { + indexDefine += ", " + *it++; + } + std::string createIndexSql = "CREATE INDEX IF NOT EXISTS " + realIndexName + " ON " + table + + "(" + indexDefine + ");"; + errCode = SQLiteUtils::ExecuteRawSQL(db, createIndexSql); + if (errCode != E_OK) { + LOGE("Create index failed. %d", errCode); + break; + } + } + } + return errCode; +} +} + +int SQLiteSingleVerRelationalStorageExecutor::AlterAuxTableForUpgrade(const TableInfo &oldTableInfo, + const TableInfo &newTableInfo) +{ + std::vector upgradeFields = GetUpgradeFields(oldTableInfo, newTableInfo); + std::map upgradeIndexces = GetChangedIndexes(oldTableInfo, newTableInfo); + std::vector deviceTables; + int errCode = GetDeviceTableName(dbHandle_, oldTableInfo.GetTableName(), {}, deviceTables); + if (errCode != E_OK) { + LOGE("Get device table name for alter table failed. %d", errCode); + return errCode; + } + + LOGD("Begin to alter table: upgrade fields[%zu], indexces[%zu], deviceTable[%zu]", upgradeFields.size(), + upgradeIndexces.size(), deviceTables.size()); + errCode = UpgradeFields(dbHandle_, deviceTables, upgradeFields); + if (errCode != E_OK) { + LOGE("upgrade fields failed. %d", errCode); + return errCode; + } + + errCode = Upgradeindexes(dbHandle_, deviceTables, upgradeIndexces); + if (errCode != E_OK) { + LOGE("upgrade indexes failed. %d", errCode); + } + + return E_OK; +} + +int SQLiteSingleVerRelationalStorageExecutor::StartTransaction(TransactType type) +{ + if (dbHandle_ == nullptr) { + LOGE("Begin transaction failed, dbHandle is null."); + return -E_INVALID_DB; + } + int errCode = SQLiteUtils::BeginTransaction(dbHandle_, type); + if (errCode != E_OK) { + LOGE("Begin transaction failed, errCode = %d", errCode); + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::Commit() +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + + return SQLiteUtils::CommitTransaction(dbHandle_); +} + +int SQLiteSingleVerRelationalStorageExecutor::Rollback() +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = SQLiteUtils::RollbackTransaction(dbHandle_); + if (errCode != E_OK) { + LOGE("sqlite single ver storage executor rollback fail! errCode = [%d]", errCode); + } + return errCode; +} + +void SQLiteSingleVerRelationalStorageExecutor::SetTableInfo(const TableInfo &tableInfo) +{ + table_ = tableInfo; +} + +static int GetDataValueByType(sqlite3_stmt *statement, DataValue &value, int cid) +{ + int errCode = E_OK; + int storageType = sqlite3_column_type(statement, cid); + switch (storageType) { + case SQLITE_INTEGER: { + value = static_cast(sqlite3_column_int64(statement, cid)); + break; + } + case SQLITE_FLOAT: { + value = sqlite3_column_double(statement, cid); + break; + } + case SQLITE_BLOB: { + std::vector blobValue; + errCode = SQLiteUtils::GetColumnBlobValue(statement, cid, blobValue); + if (errCode != E_OK) { + return errCode; + } + auto blob = new (std::nothrow) Blob; + if (blob == nullptr) { + return -E_OUT_OF_MEMORY; + } + blob->WriteBlob(blobValue.data(), static_cast(blobValue.size())); + errCode = value.Set(blob); + break; + } + case SQLITE_NULL: { + break; + } + case SQLITE3_TEXT: { + const char *colValue = reinterpret_cast(sqlite3_column_text(statement, cid)); + if (colValue == nullptr) { + value.ResetValue(); + } else { + value = std::string(colValue); + if (value.GetType() == StorageType::STORAGE_TYPE_NULL) { + errCode = -E_OUT_OF_MEMORY; + } + } + break; + } + default: { + break; + } + } + return errCode; +} + +static int BindDataValueByType(sqlite3_stmt *statement, const std::optional &data, int cid) +{ + int errCode = E_OK; + StorageType type = data.value().GetType(); + switch (type) { + case StorageType::STORAGE_TYPE_INTEGER: { + int64_t intData = 0; + (void)data.value().GetInt64(intData); + errCode = SQLiteUtils::MapSQLiteErrno(sqlite3_bind_int64(statement, cid, intData)); + break; + } + + case StorageType::STORAGE_TYPE_REAL: { + double doubleData = 0; + (void)data.value().GetDouble(doubleData); + errCode = SQLiteUtils::MapSQLiteErrno(sqlite3_bind_double(statement, cid, doubleData)); + break; + } + + case StorageType::STORAGE_TYPE_TEXT: { + std::string strData; + (void)data.value().GetText(strData); + errCode = SQLiteUtils::BindTextToStatement(statement, cid, strData); + break; + } + + case StorageType::STORAGE_TYPE_BLOB: { + Blob blob; + (void)data.value().GetBlob(blob); + std::vector blobData(blob.GetData(), blob.GetData() + blob.GetSize()); + errCode = SQLiteUtils::BindBlobToStatement(statement, cid, blobData, true); + break; + } + + case StorageType::STORAGE_TYPE_NULL: { + errCode = SQLiteUtils::MapSQLiteErrno(sqlite3_bind_null(statement, cid)); + break; + } + + default: + break; + } + return errCode; +} + +static int GetLogData(sqlite3_stmt *logStatement, LogInfo &logInfo) +{ + logInfo.dataKey = sqlite3_column_int64(logStatement, 0); // 0 means dataKey index + + std::vector dev; + int errCode = SQLiteUtils::GetColumnBlobValue(logStatement, 1, dev); // 1 means dev index + if (errCode != E_OK) { + return errCode; + } + logInfo.device = std::string(dev.begin(), dev.end()); + + std::vector oriDev; + errCode = SQLiteUtils::GetColumnBlobValue(logStatement, 2, oriDev); // 2 means ori_dev index + if (errCode != E_OK) { + return errCode; + } + logInfo.originDev = std::string(oriDev.begin(), oriDev.end()); + logInfo.timestamp = static_cast(sqlite3_column_int64(logStatement, 3)); // 3 means timestamp index + logInfo.wTimestamp = static_cast(sqlite3_column_int64(logStatement, 4)); // 4 means w_timestamp index + logInfo.flag = static_cast(sqlite3_column_int64(logStatement, 5)); // 5 means flag index + logInfo.flag &= (~DataItem::LOCAL_FLAG); + logInfo.flag &= (~DataItem::UPDATE_FLAG); + return SQLiteUtils::GetColumnBlobValue(logStatement, 6, logInfo.hashKey); // 6 means hashKey index +} + +static size_t GetDataItemSerialSize(DataItem &item, size_t appendLen) +{ + // timestamp and local flag: 3 * uint64_t, version(uint32_t), key, value, origin dev and the padding size. + // the size would not be very large. + static const size_t maxOrigDevLength = 40; + size_t devLength = std::max(maxOrigDevLength, item.origDev.size()); + size_t dataSize = (Parcel::GetUInt64Len() * 3 + Parcel::GetUInt32Len() + Parcel::GetVectorCharLen(item.key) + + Parcel::GetVectorCharLen(item.value) + devLength + appendLen); + return dataSize; +} + +int SQLiteSingleVerRelationalStorageExecutor::GetKvData(const Key &key, Value &value) const +{ + static const std::string SELECT_META_VALUE_SQL = "SELECT value FROM " + DBConstant::RELATIONAL_PREFIX + + "metadata WHERE key=?;"; + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, SELECT_META_VALUE_SQL, statement); + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, key, false); // first arg. + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = -E_NOT_FOUND; + goto END; + } else if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + goto END; + } + + errCode = SQLiteUtils::GetColumnBlobValue(statement, 0, value); // only one result. + END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::PutKvData(const Key &key, const Value &value) const +{ + static const std::string INSERT_META_SQL = "INSERT OR REPLACE INTO " + DBConstant::RELATIONAL_PREFIX + + "metadata VALUES(?,?);"; + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, INSERT_META_SQL, statement); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, key, false); // 1 means key index + if (errCode != E_OK) { + LOGE("[SingleVerExe][BindPutKv]Bind key error:%d", errCode); + goto ERROR; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, 2, value, true); // 2 means value index + if (errCode != E_OK) { + LOGE("[SingleVerExe][BindPutKv]Bind value error:%d", errCode); + goto ERROR; + } + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } +ERROR: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::DeleteMetaData(const std::vector &keys) const +{ + static const std::string REMOVE_META_VALUE_SQL = "DELETE FROM " + DBConstant::RELATIONAL_PREFIX + + "metadata WHERE key=?;"; + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, REMOVE_META_VALUE_SQL, statement); + if (errCode != E_OK) { + return errCode; + } + + for (const auto &key : keys) { + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, key, false); // first arg. + if (errCode != E_OK) { + break; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + break; + } + errCode = E_OK; + SQLiteUtils::ResetStatement(statement, false, errCode); + } + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerRelationalStorageExecutor::DeleteMetaDataByPrefixKey(const Key &keyPrefix) const +{ + static const std::string REMOVE_META_VALUE_BY_KEY_PREFIX_SQL = "DELETE FROM " + DBConstant::RELATIONAL_PREFIX + + "metadata WHERE key>=? AND key<=?;"; + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, REMOVE_META_VALUE_BY_KEY_PREFIX_SQL, statement); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::BindPrefixKey(statement, 1, keyPrefix); // 1 is first arg. + if (errCode == E_OK) { + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } + } + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +static int GetAllKeys(sqlite3_stmt *statement, std::vector &keys) +{ + if (statement == nullptr) { + return -E_INVALID_DB; + } + int errCode; + do { + errCode = SQLiteUtils::StepWithRetry(statement, false); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + Key key; + errCode = SQLiteUtils::GetColumnBlobValue(statement, 0, key); + if (errCode != E_OK) { + break; + } + + keys.push_back(std::move(key)); + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + break; + } else { + LOGE("SQLite step for getting all keys failed:%d", errCode); + break; + } + } while (true); + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::GetAllMetaKeys(std::vector &keys) const +{ + static const std::string SELECT_ALL_META_KEYS = "SELECT key FROM " + DBConstant::RELATIONAL_PREFIX + "metadata;"; + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, SELECT_ALL_META_KEYS, statement); + if (errCode != E_OK) { + LOGE("[Relational][GetAllKey] Get statement failed:%d", errCode); + return errCode; + } + errCode = GetAllKeys(statement, keys); + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::PrepareForSavingLog(const QueryObject &object, + const std::string &deviceName, sqlite3_stmt *&logStmt, sqlite3_stmt *&queryStmt) const +{ + std::string devName = DBCommon::TransferHashString(deviceName); + const std::string tableName = DBConstant::RELATIONAL_PREFIX + object.GetTableName() + "_log"; + std::string dataFormat = "?, '" + deviceName + "', ?, ?, ?, ?, ?"; + std::string columnList = "data_key, device, ori_device, timestamp, wtimestamp, flag, hash_key"; + std::string sql = "INSERT OR REPLACE INTO " + tableName + + " (" + columnList + ") VALUES (" + dataFormat + ");"; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, logStmt); + if (errCode != E_OK) { + LOGE("[info statement] Get log statement fail! errCode:%d", errCode); + return errCode; + } + std::string selectSql = "select " + columnList + " from " + tableName + " where hash_key = ? and device = ?;"; + errCode = SQLiteUtils::GetStatement(dbHandle_, selectSql, queryStmt); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(logStmt, true, errCode); + LOGE("[info statement] Get query statement fail! errCode:%d", errCode); + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::PrepareForSavingData(const QueryObject &object, + sqlite3_stmt *&statement) const +{ + std::string colName; + std::string dataFormat; + for (size_t colId = 0; colId < table_.GetFields().size(); ++colId) { + colName += table_.GetFieldName(colId) + ","; + dataFormat += "?,"; + } + colName.pop_back(); + dataFormat.pop_back(); + + const std::string sql = "INSERT OR REPLACE INTO " + table_.GetTableName() + + " (" + colName + ") VALUES (" + dataFormat + ");"; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + LOGE("[info statement] Get saving data statement fail! errCode:%d", errCode); + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::SaveSyncLog(sqlite3_stmt *statement, sqlite3_stmt *queryStmt, + const DataItem &dataItem, int64_t rowid) +{ + int errCode = SQLiteUtils::BindBlobToStatement(queryStmt, 1, dataItem.hashKey); // 1 means hashkey index. + if (errCode != E_OK) { + return errCode; + } + errCode = SQLiteUtils::BindTextToStatement(queryStmt, 2, dataItem.dev); // 2 means device index. + if (errCode != E_OK) { + return errCode; + } + + LogInfo logInfoGet; + errCode = SQLiteUtils::StepWithRetry(queryStmt, isMemDb_); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + errCode = -E_NOT_FOUND; + } else { + errCode = GetLogData(queryStmt, logInfoGet); + } + + LogInfo logInfoBind; + logInfoBind.hashKey = dataItem.hashKey; + logInfoBind.device = dataItem.dev; + logInfoBind.timestamp = dataItem.timestamp; + logInfoBind.flag = dataItem.flag; + + if (errCode == -E_NOT_FOUND) { // insert + logInfoBind.wTimestamp = dataItem.writeTimestamp; + logInfoBind.originDev = dataItem.dev; + } else if (errCode == E_OK) { // update + logInfoBind.wTimestamp = logInfoGet.wTimestamp; + logInfoBind.originDev = logInfoGet.originDev; + } else { + return errCode; + } + + // bind + SQLiteUtils::BindInt64ToStatement(statement, 1, rowid); // 1 means dataKey index + std::vector originDev(logInfoBind.originDev.begin(), logInfoBind.originDev.end()); + SQLiteUtils::BindBlobToStatement(statement, 2, originDev); // 2 means ori_dev index + SQLiteUtils::BindInt64ToStatement(statement, 3, logInfoBind.timestamp); // 3 means timestamp index + SQLiteUtils::BindInt64ToStatement(statement, 4, logInfoBind.wTimestamp); // 4 means w_timestamp index + SQLiteUtils::BindInt64ToStatement(statement, 5, logInfoBind.flag); // 5 means flag index + SQLiteUtils::BindBlobToStatement(statement, 6, logInfoBind.hashKey); // 6 means hashKey index + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + return E_OK; + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::DeleteSyncDataItem(const DataItem &dataItem, sqlite3_stmt *&stmt) +{ + if (stmt == nullptr) { + const std::string sql = "DELETE FROM " + table_.GetTableName() + " WHERE rowid IN (" + "SELECT data_key FROM " + DBConstant::RELATIONAL_PREFIX + baseTblName_ + "_log " + "WHERE hash_key=? AND device=? AND flag&0x01=0);"; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, stmt); + if (errCode != E_OK) { + LOGE("[DeleteSyncDataItem] Get statement fail!, errCode:%d", errCode); + return errCode; + } + } + + int errCode = SQLiteUtils::BindBlobToStatement(stmt, 1, dataItem.hashKey); // 1 means hash_key index + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; + } + errCode = SQLiteUtils::BindTextToStatement(stmt, 2, dataItem.dev); // 2 means device index + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; + } + errCode = SQLiteUtils::StepWithRetry(stmt, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } + SQLiteUtils::ResetStatement(stmt, false, errCode); // Finalize outside. + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::SaveSyncDataItem(const DataItem &dataItem, sqlite3_stmt *&saveDataStmt, + sqlite3_stmt *&rmDataStmt, const std::vector &fieldInfos, int64_t &rowid) +{ + if ((dataItem.flag & DataItem::DELETE_FLAG) != 0) { + return DeleteSyncDataItem(dataItem, rmDataStmt); + } + + // For no pk data, cannot replace. Must delete and insert. + if (table_.GetPrimaryKey() == "rowid") { + int errCode = DeleteSyncDataItem(dataItem, rmDataStmt); + if (errCode != E_OK) { + LOGE("Delete no pk data before insert failed, errCode=%d.", errCode); + return errCode; + } + } + + OptRowDataWithLog data; + int errCode = DataTransformer::DeSerializeDataItem(dataItem, data, fieldInfos); + if (errCode != E_OK) { + LOGE("[RelationalStorageExecutor] DeSerialize dataItem failed! errCode = [%d]", errCode); + return errCode; + } + + if (data.optionalData.size() != table_.GetFields().size()) { + LOGW("Remote data has different fields with local data. Remote size:%zu, local size:%zu", + data.optionalData.size(), table_.GetFields().size()); + } + + auto putSize = std::min(data.optionalData.size(), table_.GetFields().size()); + for (size_t cid = 0; cid < putSize; ++cid) { + const auto &fieldData = data.optionalData[cid]; + errCode = BindDataValueByType(saveDataStmt, fieldData, cid + 1); + if (errCode != E_OK) { + LOGE("Bind data failed, errCode:%d, cid:%zu.", errCode, cid + 1); + return errCode; + } + } + + errCode = SQLiteUtils::StepWithRetry(saveDataStmt, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + rowid = SQLiteUtils::GetLastRowId(dbHandle_); + errCode = E_OK; + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::DeleteSyncLog(const DataItem &dataItem, sqlite3_stmt *&stmt) +{ + if (stmt == nullptr) { + const std::string sql = "DELETE FROM " + DBConstant::RELATIONAL_PREFIX + baseTblName_ + "_log " + "WHERE hash_key=? AND device=?"; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, stmt); + if (errCode != E_OK) { + LOGE("[DeleteSyncLog] Get statement fail!"); + return errCode; + } + } + + int errCode = SQLiteUtils::BindBlobToStatement(stmt, 1, dataItem.hashKey); // 1 means hashkey index + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; + } + errCode = SQLiteUtils::BindTextToStatement(stmt, 2, dataItem.dev); // 2 means device index + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; + } + errCode = SQLiteUtils::StepWithRetry(stmt, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } + SQLiteUtils::ResetStatement(stmt, false, errCode); // Finalize outside. + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::ProcessMissQueryData(const DataItem &item, sqlite3_stmt *&rmDataStmt, + sqlite3_stmt *&rmLogStmt) +{ + int errCode = DeleteSyncDataItem(item, rmDataStmt); + if (errCode != E_OK) { + return errCode; + } + return DeleteSyncLog(item, rmLogStmt); +} + +int SQLiteSingleVerRelationalStorageExecutor::GetSyncDataPre(const DataItem &dataItem, DataItem &itemGet) +{ + if (saveStmt_.queryStmt == nullptr) { + return -E_INVALID_ARGS; + } + int errCode = SQLiteUtils::BindBlobToStatement(saveStmt_.queryStmt, 1, dataItem.hashKey); // 1 index for hashkey + if (errCode != E_OK) { + return errCode; + } + errCode = SQLiteUtils::BindTextToStatement(saveStmt_.queryStmt, 2, dataItem.dev); // 2 index for devices + if (errCode != E_OK) { + return errCode; + } + + LogInfo logInfoGet; + errCode = SQLiteUtils::StepWithRetry(saveStmt_.queryStmt, isMemDb_); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + errCode = -E_NOT_FOUND; + } else { + errCode = GetLogData(saveStmt_.queryStmt, logInfoGet); + } + itemGet.timestamp = logInfoGet.timestamp; + SQLiteUtils::ResetStatement(saveStmt_.queryStmt, false, errCode); + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::CheckDataConflictDefeated(const DataItem &dataItem, bool &isDefeated) +{ + if ((dataItem.flag & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) != DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) { + isDefeated = false; // no need to slove conflict except miss query data + return E_OK; + } + + DataItem itemGet; + int errCode = GetSyncDataPre(dataItem, itemGet); + if (errCode != E_OK && errCode != -E_NOT_FOUND) { + LOGE("Failed to get raw data. %d", errCode); + return errCode; + } + isDefeated = (dataItem.timestamp <= itemGet.timestamp); // defeated if item timestamp is earlier then raw data + return E_OK; +} + +int SQLiteSingleVerRelationalStorageExecutor::SaveSyncDataItem(const std::vector &fieldInfos, + const std::string &deviceName, DataItem &item) +{ + item.dev = deviceName; + bool isDefeated = false; + int errCode = CheckDataConflictDefeated(item, isDefeated); + if (errCode != E_OK) { + LOGE("check data conflict failed. %d", errCode); + return errCode; + } + + if (isDefeated) { + LOGD("Data was defeated."); + return E_OK; + } + if ((item.flag & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) != 0) { + return ProcessMissQueryData(item, saveStmt_.rmDataStmt, saveStmt_.rmLogStmt); + } + int64_t rowid = -1; + errCode = SaveSyncDataItem(item, saveStmt_.saveDataStmt, saveStmt_.rmDataStmt, fieldInfos, rowid); + if (errCode == E_OK || errCode == -E_NOT_FOUND) { + errCode = SaveSyncLog(saveStmt_.saveLogStmt, saveStmt_.queryStmt, item, rowid); + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::SaveSyncDataItems(const QueryObject &object, + std::vector &dataItems, const std::string &deviceName) +{ + int errCode = PrepareForSavingData(object, saveStmt_.saveDataStmt); + if (errCode != E_OK) { + return errCode; + } + errCode = PrepareForSavingLog(object, deviceName, saveStmt_.saveLogStmt, saveStmt_.queryStmt); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(saveStmt_.saveDataStmt, true, errCode); + return errCode; + } + std::vector fieldInfos; + for (const auto &col: table_.GetFields()) { + fieldInfos.push_back(col.second); + } + + for (auto &item : dataItems) { + if (item.neglect) { // Do not save this record if it is neglected + continue; + } + errCode = SaveSyncDataItem(fieldInfos, deviceName, item); + if (errCode != E_OK) { + break; + } + // Need not reset rmDataStmt and rmLogStmt here. + saveStmt_.ResetStatements(false); + } + if (errCode == -E_NOT_FOUND) { + errCode = E_OK; + } + saveStmt_.ResetStatements(true); + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::SaveSyncItems(const QueryObject &object, std::vector &dataItems, + const std::string &deviceName, const TableInfo &table) +{ + int errCode = StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + return errCode; + } + baseTblName_ = object.GetTableName(); + SetTableInfo(table); + const std::string tableName = DBCommon::GetDistributedTableName(deviceName, baseTblName_); + table_.SetTableName(tableName); + errCode = SaveSyncDataItems(object, dataItems, deviceName); + if (errCode == E_OK) { + errCode = Commit(); + } else { + (void)Rollback(); // Keep the error code of the first scene + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::GetDataItemForSync(sqlite3_stmt *stmt, DataItem &dataItem, + bool isGettingDeletedData) const +{ + RowDataWithLog data; + int errCode = GetLogData(stmt, data.logInfo); + if (errCode != E_OK) { + LOGE("relational data value transfer to kv fail"); + return errCode; + } + + if (!isGettingDeletedData) { + for (size_t cid = 0; cid < table_.GetFields().size(); ++cid) { + DataValue value; + errCode = GetDataValueByType(stmt, value, cid + DBConstant::RELATIONAL_LOG_TABLE_FIELD_NUM); + if (errCode != E_OK) { + return errCode; + } + data.rowData.push_back(std::move(value)); + } + } + + errCode = DataTransformer::SerializeDataItem(data, + isGettingDeletedData ? std::vector() : table_.GetFieldInfos(), dataItem); + if (errCode != E_OK) { + LOGE("relational data value transfer to kv fail"); + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::GetMissQueryData(sqlite3_stmt *fullStmt, DataItem &item) +{ + int errCode = GetDataItemForSync(fullStmt, item, false); + if (errCode != E_OK) { + return errCode; + } + item.value = {}; + item.flag |= DataItem::REMOTE_DEVICE_DATA_MISS_QUERY; + return errCode; +} + +namespace { +int StepNext(bool isMemDB, sqlite3_stmt *stmt, Timestamp ×tamp) +{ + if (stmt == nullptr) { + return -E_INVALID_ARGS; + } + int errCode = SQLiteUtils::StepWithRetry(stmt, isMemDB); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + timestamp = INT64_MAX; + errCode = E_OK; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + timestamp = static_cast(sqlite3_column_int64(stmt, 3)); // 3 means timestamp index + errCode = E_OK; + } + return errCode; +} + +int AppendData(const DataSizeSpecInfo &sizeInfo, size_t appendLength, size_t &overLongSize, size_t &dataTotalSize, + std::vector &dataItems, DataItem &&item) +{ + // If one record is over 4M, ignore it. + if (item.value.size() > DBConstant::MAX_VALUE_SIZE) { + overLongSize++; + } else { + // If dataTotalSize value is bigger than blockSize value , reserve the surplus data item. + dataTotalSize += GetDataItemSerialSize(item, appendLength); + if ((dataTotalSize > sizeInfo.blockSize && !dataItems.empty()) || dataItems.size() >= sizeInfo.packetSize) { + return -E_UNFINISHED; + } else { + dataItems.push_back(item); + } + } + return E_OK; +} +} + +int SQLiteSingleVerRelationalStorageExecutor::GetQueryDataAndStepNext(bool isFirstTime, bool isGettingDeletedData, + sqlite3_stmt *queryStmt, DataItem &item, Timestamp &queryTime) +{ + if (!isFirstTime) { // For the first time, never step before, can get nothing + int errCode = GetDataItemForSync(queryStmt, item, isGettingDeletedData); + if (errCode != E_OK) { + return errCode; + } + } + return StepNext(isMemDb_, queryStmt, queryTime); +} + +int SQLiteSingleVerRelationalStorageExecutor::GetMissQueryDataAndStepNext(sqlite3_stmt *fullStmt, DataItem &item, + Timestamp &missQueryTime) +{ + int errCode = GetMissQueryData(fullStmt, item); + if (errCode != E_OK) { + return errCode; + } + return StepNext(isMemDb_, fullStmt, missQueryTime); +} + +int SQLiteSingleVerRelationalStorageExecutor::GetSyncDataByQuery(std::vector &dataItems, size_t appendLength, + const DataSizeSpecInfo &sizeInfo, std::function getStmt, + const TableInfo &tableInfo) +{ + baseTblName_ = tableInfo.GetTableName(); + SetTableInfo(tableInfo); + sqlite3_stmt *queryStmt = nullptr; + sqlite3_stmt *fullStmt = nullptr; + bool isGettingDeletedData = false; + int errCode = getStmt(dbHandle_, queryStmt, fullStmt, isGettingDeletedData); + if (errCode != E_OK) { + return errCode; + } + + Timestamp queryTime = 0; + Timestamp missQueryTime = (fullStmt == nullptr ? INT64_MAX : 0); + + bool isFirstTime = true; + size_t dataTotalSize = 0; + size_t overLongSize = 0; + do { + DataItem item; + if (queryTime < missQueryTime) { + errCode = GetQueryDataAndStepNext(isFirstTime, isGettingDeletedData, queryStmt, item, queryTime); + } else if (queryTime == missQueryTime) { + errCode = GetQueryDataAndStepNext(isFirstTime, isGettingDeletedData, queryStmt, item, queryTime); + if (errCode != E_OK) { + break; + } + errCode = StepNext(isMemDb_, fullStmt, missQueryTime); + } else { + errCode = GetMissQueryDataAndStepNext(fullStmt, item, missQueryTime); + } + + if (errCode == E_OK && !isFirstTime) { + errCode = AppendData(sizeInfo, appendLength, overLongSize, dataTotalSize, dataItems, std::move(item)); + } + + if (errCode != E_OK) { + break; + } + + isFirstTime = false; + if (queryTime == INT64_MAX && missQueryTime == INT64_MAX) { + errCode = -E_FINISHED; + break; + } + } while (true); + LOGI("Get sync data finished, rc:%d, record size:%zu, overlong size:%zu, isDeleted:%d", + errCode, dataItems.size(), overLongSize, isGettingDeletedData); + SQLiteUtils::ResetStatement(queryStmt, true, errCode); + SQLiteUtils::ResetStatement(fullStmt, true, errCode); + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::CheckDBModeForRelational() +{ + std::string journalMode; + int errCode = SQLiteUtils::GetJournalMode(dbHandle_, journalMode); + + for (auto &c : journalMode) { // convert to lowercase + c = static_cast(std::tolower(c)); + } + + if (errCode == E_OK && journalMode != "wal") { + LOGE("Not support journal mode %s for relational db, expect wal mode.", journalMode.c_str()); + return -E_NOT_SUPPORT; + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::DeleteDistributedDeviceTable(const std::string &device, + const std::string &tableName) +{ + std::vector deviceTables; + int errCode = GetDeviceTableName(dbHandle_, tableName, device, deviceTables); + if (errCode != E_OK) { + LOGE("Get device table name for alter table failed. %d", errCode); + return errCode; + } + + LOGD("Begin to delete device table: deviceTable[%zu]", deviceTables.size()); + for (const auto &table : deviceTables) { + std::string deleteSql = "DROP TABLE IF EXISTS " + table + ";"; // drop the found table + errCode = SQLiteUtils::ExecuteRawSQL(dbHandle_, deleteSql); + if (errCode != E_OK) { + LOGE("Delete device data failed. %d", errCode); + break; + } + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::DeleteDistributedLogTable(const std::string &tableName) +{ + if (tableName.empty()) { + return -E_INVALID_ARGS; + } + std::string logTableName = DBConstant::RELATIONAL_PREFIX + tableName + "_log"; + std::string deleteSql = "DROP TABLE IF EXISTS " + logTableName + ";"; + int errCode = SQLiteUtils::ExecuteRawSQL(dbHandle_, deleteSql); + if (errCode != E_OK) { + LOGE("Delete distributed log table failed. %d", errCode); + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::CheckAndCleanDistributedTable(const std::vector &tableNames, + std::vector &missingTables) +{ + if (tableNames.empty()) { + return E_OK; + } + const std::string checkSql = "SELECT name FROM sqlite_master WHERE type='table' AND name=?;"; + sqlite3_stmt *stmt = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, checkSql, stmt); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; + } + for (const auto &tableName : tableNames) { + errCode = SQLiteUtils::BindTextToStatement(stmt, 1, tableName); // 1: tablename bind index + if (errCode != E_OK) { + LOGE("Bind table name to check distributed table statement failed. %d", errCode); + break; + } + + errCode = SQLiteUtils::StepWithRetry(stmt, false); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { // The table in schema was dropped + errCode = DeleteDistributedDeviceTable({}, tableName); // Clean the auxiliary tables for the dropped table + if (errCode != E_OK) { + LOGE("Delete device tables for missing distributed table failed. %d", errCode); + break; + } + errCode = DeleteDistributedLogTable(tableName); + if (errCode != E_OK) { + LOGE("Delete log tables for missing distributed table failed. %d", errCode); + break; + } + missingTables.emplace_back(tableName); + } else if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + LOGE("Check distributed table failed. %d", errCode); + break; + } + errCode = E_OK; // Check result ok for distributed table is still exists + SQLiteUtils::ResetStatement(stmt, false, errCode); + } + SQLiteUtils::ResetStatement(stmt, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerRelationalStorageExecutor::CreateDistributedDeviceTable(const std::string &device, + const TableInfo &baseTbl) +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + + if (device.empty() || !baseTbl.IsValid()) { + return -E_INVALID_ARGS; + } + + std::string deviceTableName = DBCommon::GetDistributedTableName(device, baseTbl.GetTableName()); + int errCode = SQLiteUtils::CreateSameStuTable(dbHandle_, baseTbl, deviceTableName); + if (errCode != E_OK) { + LOGE("Create device table failed. %d", errCode); + return errCode; + } + + errCode = SQLiteUtils::CloneIndexes(dbHandle_, baseTbl.GetTableName(), deviceTableName); + if (errCode != E_OK) { + LOGE("Copy index to device table failed. %d", errCode); + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::CheckQueryObjectLegal(const TableInfo &table, QueryObject &query) +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + + TableInfo newTable; + int errCode = SQLiteUtils::AnalysisSchema(dbHandle_, table.GetTableName(), newTable); + if (errCode != E_OK && errCode != -E_NOT_FOUND) { + LOGE("Check new schema failed. %d", errCode); + return errCode; + } else { + errCode = table.CompareWithTable(newTable); + if (errCode != -E_RELATIONAL_TABLE_EQUAL && errCode != -E_RELATIONAL_TABLE_COMPATIBLE) { + LOGE("Check schema failed, schema was changed. %d", errCode); + return -E_DISTRIBUTED_SCHEMA_CHANGED; + } else { + errCode = E_OK; + } + } + + SqliteQueryHelper helper = query.GetQueryHelper(errCode); + if (errCode != E_OK) { + LOGE("Get query helper for check query failed. %d", errCode); + return errCode; + } + + if (!query.IsQueryForRelationalDB()) { + LOGE("Not support for this query type."); + return -E_NOT_SUPPORT; + } + + SyncTimeRange defaultTimeRange; + sqlite3_stmt *stmt = nullptr; + errCode = helper.GetRelationalQueryStatement(dbHandle_, defaultTimeRange.beginTime, defaultTimeRange.endTime, {}, + stmt); + if (errCode != E_OK) { + LOGE("Get query statement for check query failed. %d", errCode); + } + + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::SaveSyncDataStmt::ResetStatements(bool isNeedFinalize) +{ + int errCode = E_OK; + if (saveDataStmt != nullptr) { + SQLiteUtils::ResetStatement(saveDataStmt, isNeedFinalize, errCode); + } + if (saveLogStmt != nullptr) { + SQLiteUtils::ResetStatement(saveLogStmt, isNeedFinalize, errCode); + } + if (queryStmt != nullptr) { + SQLiteUtils::ResetStatement(queryStmt, isNeedFinalize, errCode); + } + if (rmDataStmt != nullptr) { + SQLiteUtils::ResetStatement(rmDataStmt, isNeedFinalize, errCode); + } + if (rmLogStmt != nullptr) { + SQLiteUtils::ResetStatement(rmLogStmt, isNeedFinalize, errCode); + } + return errCode; +} + +int SQLiteSingleVerRelationalStorageExecutor::GetMaxTimestamp(const std::vector &tableNames, + Timestamp &maxTimestamp) const +{ + maxTimestamp = 0; + for (const auto &tableName : tableNames) { + const std::string sql = "SELECT max(timestamp) from " + DBConstant::RELATIONAL_PREFIX + tableName + "_log;"; + sqlite3_stmt *stmt = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, stmt); + if (errCode != E_OK) { + return errCode; + } + errCode = SQLiteUtils::StepWithRetry(stmt, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + maxTimestamp = std::max(maxTimestamp, static_cast(sqlite3_column_int64(stmt, 0))); // 0 is index + errCode = E_OK; + } + SQLiteUtils::ResetStatement(stmt, true, errCode); + if (errCode != E_OK) { + maxTimestamp = 0; + return errCode; + } + } + return E_OK; +} +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_storage_executor.h b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_storage_executor.h new file mode 100644 index 00000000..6a0db0d1 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_relational_storage_executor.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_SINGLE_VER_RELATIONAL_STORAGE_EXECUTOR_H +#define SQLITE_SINGLE_VER_RELATIONAL_STORAGE_EXECUTOR_H +#ifdef RELATIONAL_STORE + +#include "data_transformer.h" +#include "db_types.h" +#include "macro_utils.h" +#include "sqlite_utils.h" +#include "sqlite_storage_executor.h" +#include "relational_store_delegate.h" +#include "query_object.h" + +namespace DistributedDB { +class SQLiteSingleVerRelationalStorageExecutor : public SQLiteStorageExecutor { +public: + SQLiteSingleVerRelationalStorageExecutor(sqlite3 *dbHandle, bool writable); + ~SQLiteSingleVerRelationalStorageExecutor() override = default; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteSingleVerRelationalStorageExecutor); + + int CreateDistributedTable(const std::string &tableName, TableInfo &table, bool isUpgrade); + + int UpgradeDistributedTable(const TableInfo &tableInfo, TableInfo &newTableInfo); + + int StartTransaction(TransactType type); + int Commit(); + int Rollback(); + + // For Get sync data + int GetSyncDataByQuery(std::vector &dataItems, size_t appendLength, const DataSizeSpecInfo &sizeInfo, + std::function getStmt, const TableInfo &tableInfo); + + // operation of meta data + int GetKvData(const Key &key, Value &value) const; + int PutKvData(const Key &key, const Value &value) const; + int DeleteMetaData(const std::vector &keys) const; + int DeleteMetaDataByPrefixKey(const Key &keyPrefix) const; + int GetAllMetaKeys(std::vector &keys) const; + + // For Put sync data + int SaveSyncItems(const QueryObject &object, std::vector &dataItems, + const std::string &deviceName, const TableInfo &table); + + int AnalysisRelationalSchema(const std::string &tableName, TableInfo &tableInfo); + + int CheckDBModeForRelational(); + + int DeleteDistributedDeviceTable(const std::string &device, const std::string &tableName); + int DeleteDistributedLogTable(const std::string &tableName); + + int CheckAndCleanDistributedTable(const std::vector &tableNames, + std::vector &missingTables); + + int CreateDistributedDeviceTable(const std::string &device, const TableInfo &baseTbl); + + int CheckQueryObjectLegal(const TableInfo &table, QueryObject &query); + + int GetMaxTimestamp(const std::vector &tablesName, Timestamp &maxTimestamp) const; + +private: + struct SaveSyncDataStmt { + sqlite3_stmt *saveDataStmt = nullptr; + sqlite3_stmt *saveLogStmt = nullptr; + sqlite3_stmt *queryStmt = nullptr; + sqlite3_stmt *rmDataStmt = nullptr; + sqlite3_stmt *rmLogStmt = nullptr; + + int ResetStatements(bool isNeedFinalize); + }; + + int PrepareForSyncDataByTime(Timestamp begin, Timestamp end, + sqlite3_stmt *&statement, bool getDeletedData) const; + + int GetDataItemForSync(sqlite3_stmt *statement, DataItem &dataItem, bool isGettingDeletedData) const; + + int GetSyncDataPre(const DataItem &dataItem, DataItem &itemGet); + + int CheckDataConflictDefeated(const DataItem &item, bool &isDefeated); + + int SaveSyncDataItem(const std::vector &fieldInfos, const std::string &deviceName, DataItem &item); + + int SaveSyncDataItems(const QueryObject &object, std::vector &dataItems, const std::string &deviceName); + int SaveSyncDataItem(const DataItem &dataItem, sqlite3_stmt *&saveDataStmt, sqlite3_stmt *&rmDataStmt, + const std::vector &fieldInfos, int64_t &rowid); + + int DeleteSyncDataItem(const DataItem &dataItem, sqlite3_stmt *&rmDataStmt); + + int SaveSyncLog(sqlite3_stmt *statement, sqlite3_stmt *queryStmt, const DataItem &dataItem, int64_t rowid); + int PrepareForSavingData(const QueryObject &object, sqlite3_stmt *&statement) const; + int PrepareForSavingLog(const QueryObject &object, const std::string &deviceName, + sqlite3_stmt *&statement, sqlite3_stmt *&queryStmt) const; + + int AlterAuxTableForUpgrade(const TableInfo &oldTableInfo, const TableInfo &newTableInfo); + + int DeleteSyncLog(const DataItem &item, sqlite3_stmt *&rmLogStmt); + int ProcessMissQueryData(const DataItem &item, sqlite3_stmt *&rmDataStmt, sqlite3_stmt *&rmLogStmt); + int GetMissQueryData(sqlite3_stmt *fullStmt, DataItem &item); + int GetQueryDataAndStepNext(bool isFirstTime, bool isGettingDeletedData, sqlite3_stmt *queryStmt, DataItem &item, + Timestamp &queryTime); + int GetMissQueryDataAndStepNext(sqlite3_stmt *fullStmt, DataItem &item, Timestamp &missQueryTime); + + void SetTableInfo(const TableInfo &tableInfo); // When put or get sync data, must call the func first. + std::string baseTblName_; + TableInfo table_; // Always operating table, user table when get, device table when put. + + SaveSyncDataStmt saveStmt_; +}; +} // namespace DistributedDB +#endif +#endif // SQLITE_SINGLE_VER_RELATIONAL_STORAGE_EXECUTOR_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_result_set.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_result_set.cpp new file mode 100644 index 00000000..6f77bb14 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_result_set.cpp @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_single_ver_result_set.h" +#include +#include "log_print.h" +#include "db_errno.h" +#include "sqlite_single_ver_forward_cursor.h" +#include "sqlite_single_ver_natural_store.h" +#include "sqlite_single_ver_storage_executor.h" + +namespace DistributedDB { +namespace { + const int64_t MEM_WINDOW_SIZE = 0xFFFFFFFF; // 4G for max + const double MEM_WINDOW_SCALE = 0.5; // set default window size to 2G + const double DEFAULT_WINDOW_SCALE = 1; // For non-mem db + const int64_t WINDOW_SIZE_MB_UNIT = 1024 * 1024; // 1024 is scale +} + +SQLiteSingleVerResultSet::SQLiteSingleVerResultSet(SQLiteSingleVerNaturalStore *kvDB, const Key &keyPrefix, + const Option& option) : option_(option), type_(ResultSetType::KEYPREFIX), keyPrefix_(keyPrefix), kvDB_(kvDB) {} + +SQLiteSingleVerResultSet::SQLiteSingleVerResultSet(SQLiteSingleVerNaturalStore *kvDB, const QueryObject &queryObj, + const Option& option) : option_(option), type_(ResultSetType::QUERY), queryObj_(queryObj), kvDB_(kvDB) {} + +SQLiteSingleVerResultSet::~SQLiteSingleVerResultSet() +{ + isOpen_ = false; + count_ = 0; + position_ = INIT_POSTION; + kvDB_ = nullptr; + window_ = nullptr; + rawCursor_ = nullptr; + handle_ = nullptr; + cacheStartPosition_ = INIT_POSTION; +} + +// The user get KvStoreResultSet after Open function called, so no need mutex during open procedure +int SQLiteSingleVerResultSet::Open(bool isMemDb) +{ + if (isOpen_) { + return E_OK; + } + if (kvDB_ == nullptr) { // Unlikely + return -E_INVALID_ARGS; + } + if (option_.cacheMode == ResultSetCacheMode::CACHE_FULL_ENTRY) { + return OpenForCacheFullEntryMode(isMemDb); + } else { + return OpenForCacheEntryIdMode(); + } +} + +int SQLiteSingleVerResultSet::OpenForCacheFullEntryMode(bool isMemDb) +{ + if (type_ == ResultSetType::KEYPREFIX) { + rawCursor_ = new (std::nothrow) SQLiteSingleVerForwardCursor(kvDB_, keyPrefix_); + } else { + rawCursor_ = new (std::nothrow) SQLiteSingleVerForwardCursor(kvDB_, queryObj_); + } + if (rawCursor_ == nullptr) { + LOGE("[SqlSinResSet][OpenForEntry] OOM When Create ForwardCursor."); + return E_OUT_OF_MEMORY; + } + window_ = new (std::nothrow) ResultEntriesWindow(); + if (window_ == nullptr) { + LOGE("[SqlSinResSet][OpenForEntry] OOM When Create EntryWindow."); + delete rawCursor_; + rawCursor_ = nullptr; + return -E_OUT_OF_MEMORY; + } + // cacheMaxSize is within [1,16] + int64_t windowSize = isMemDb ? MEM_WINDOW_SIZE : (option_.cacheMaxSize * WINDOW_SIZE_MB_UNIT); + double scale = isMemDb ? MEM_WINDOW_SCALE : DEFAULT_WINDOW_SCALE; + int errCode = window_->Init(rawCursor_, windowSize, scale); + if (errCode != E_OK) { + LOGE("[SqlSinResSet][OpenForEntry] EntryWindow Init Fail, ErrCode=%d.", errCode); + delete window_; + window_ = nullptr; + delete rawCursor_; + rawCursor_ = nullptr; + return errCode; + } + count_ = window_->GetTotalCount(); + isOpen_ = true; + LOGD("[SqlSinResSet][OpenForEntry] Type=%d, CacheMaxSize=%d(MB), Count=%d, IsMem=%d.", static_cast(type_), + option_.cacheMaxSize, count_, isMemDb); + return E_OK; +} + +int SQLiteSingleVerResultSet::OpenForCacheEntryIdMode() +{ + int errCode = E_OK; + handle_ = kvDB_->GetHandle(false, errCode); + if (handle_ == nullptr) { + LOGE("[SqlSinResSet][OpenForRowId] Get handle fail, errCode=%d.", errCode); + return errCode; + } + // cacheMaxSize is within [1,16], rowId is of type int64_t + uint32_t cacheLimit = option_.cacheMaxSize * (WINDOW_SIZE_MB_UNIT / sizeof(int64_t)); + if (type_ == ResultSetType::KEYPREFIX) { + errCode = handle_->OpenResultSetForCacheRowIdMode(keyPrefix_, cachedRowIds_, cacheLimit, count_); + } else { + errCode = handle_->OpenResultSetForCacheRowIdMode(queryObj_, cachedRowIds_, cacheLimit, count_); + } + if (errCode != E_OK) { + LOGE("[SqlSinResSet][OpenForRowId] Open ResultSet fail, errCode=%d.", errCode); + kvDB_->ReleaseHandle(handle_); + cachedRowIds_.clear(); + return errCode; + } + // If no result, then nothing is cached, so the cacheStartPosition_ is still INIT_POSTION + if (count_ != 0) { + cacheStartPosition_ = 0; + } + isOpen_ = true; + LOGD("[SqlSinResSet][OpenForRowId] Type=%d, CacheMaxSize=%d(MB), Count=%d, Cached=%zu.", static_cast(type_), + option_.cacheMaxSize, count_, cachedRowIds_.size()); + return E_OK; +} + +int SQLiteSingleVerResultSet::GetCount() const +{ + // count_ never changed after ResultSet opened + return count_; +} + +int SQLiteSingleVerResultSet::GetPosition() const +{ + std::lock_guard lockGuard(mutex_); + return position_; +} + +int SQLiteSingleVerResultSet::MoveTo(int position) const +{ + std::lock_guard lockGuard(mutex_); + if (!isOpen_) { + return -E_RESULT_SET_STATUS_INVALID; + } + if (count_ == 0) { + position_ = (position >= 0) ? 0 : INIT_POSTION; + LOGW("[SqlSinResSet][MoveTo] Empty ResultSet."); + return -E_RESULT_SET_EMPTY; + } + if (position < 0) { + position_ = INIT_POSTION; + LOGW("[SqlSinResSet][MoveTo] Target Position=%d invalid.", position); + return -E_INVALID_ARGS; + } + if (position >= count_) { + position_ = count_; + LOGW("[SqlSinResSet][MoveTo] Target Position=%d Exceed Count=%d.", position, count_); + return -E_INVALID_ARGS; + } + if (position_ == position) { + return E_OK; + } + if (option_.cacheMode == ResultSetCacheMode::CACHE_FULL_ENTRY) { + return MoveToForCacheFullEntryMode(position); + } else { + return MoveToForCacheEntryIdMode(position); + } +} + +int SQLiteSingleVerResultSet::MoveToForCacheFullEntryMode(int position) const +{ + if (window_->MoveToPosition(position)) { + position_ = position; + return E_OK; + } + position_ = INIT_POSTION; + LOGE("[SqlSinResSet][MoveForEntry] Move to position=%d fail.", position); + return -E_UNEXPECTED_DATA; +} + +int SQLiteSingleVerResultSet::MoveToForCacheEntryIdMode(int position) const +{ + // The parameter position now is in [0, count_) with this resultSet not empty + // cacheEndPosition is just after cachedRowIds_, the cached range is [cacheStartPosition_, cacheEndPosition) + int cacheEndPosition = cacheStartPosition_ + cachedRowIds_.size(); + if (position >= cacheStartPosition_ && position < cacheEndPosition) { + // Already in the cachedRowId range, Just move position + position_ = position; + return E_OK; + } + // Not in the cachedRowId range, but valid position, we should reload the cachedRowIds to contain this position + int newCacheStartPos = position; + // cacheMaxSize is within [1,16], rowId is of type int64_t + uint32_t cacheLimit = option_.cacheMaxSize * (WINDOW_SIZE_MB_UNIT / sizeof(int64_t)); + if (position > cacheStartPosition_) { + // Move Forward + int newCacheEndPos = newCacheStartPos + cacheLimit; + if (newCacheEndPos > count_) { + // Since startPos in [0, count_), So the right in (0, cacheLimit), So position still in range + newCacheStartPos -= (newCacheEndPos - count_); + } + } else { + // Move Backward + newCacheStartPos -= (cacheLimit - 1); // Attention, subtract by 1 to ensure position still in range + } + newCacheStartPos = std::max(newCacheStartPos, 0); // Adjust to at least 0 if less then 0 + // Clear rowId cache to accept new rowIds + cachedRowIds_.clear(); + int errCode; + if (type_ == ResultSetType::KEYPREFIX) { + errCode = handle_->ReloadResultSetForCacheRowIdMode(keyPrefix_, cachedRowIds_, cacheLimit, newCacheStartPos); + } else { + errCode = handle_->ReloadResultSetForCacheRowIdMode(queryObj_, cachedRowIds_, cacheLimit, newCacheStartPos); + } + if (errCode != E_OK) { + LOGE("[SqlSinResSet][MoveForRowid] Move to position=%d, Reload fail, errCode=%d.", position, errCode); + // What else shall we do if error happened ? + cachedRowIds_.clear(); + cacheStartPosition_ = INIT_POSTION; + position_ = INIT_POSTION; // Reset Position As MoveForEntry Do + return -E_UNEXPECTED_DATA; + } + LOGD("[SqlSinResSet][MoveForRowid] Reload: position=%d, cacheStartPos=%d, cached=%zu, count=%d.", + position, newCacheStartPos, cachedRowIds_.size(), count_); + // Everything OK + position_ = position; + cacheStartPosition_ = newCacheStartPos; + return E_OK; +} + +int SQLiteSingleVerResultSet::GetEntry(Entry &entry) const +{ + std::lock_guard lockGuard(mutex_); + if (!isOpen_ || count_ == 0) { + return -E_NO_SUCH_ENTRY; + } + if (position_ > INIT_POSTION && position_ < count_) { + // If position_ in the valid range, it can be guaranteed that everything is ok without errors + if (option_.cacheMode == ResultSetCacheMode::CACHE_FULL_ENTRY) { + return window_->GetEntry(entry); + } else { + // It can be guaranteed position_ in the range [cacheStartPosition_, cacheEndPosition) + // For CodeDex false alarm, we still do the check which is not necessary + int cacheIndex = position_ - cacheStartPosition_; + if (cacheIndex < 0 || cacheIndex >= static_cast(cachedRowIds_.size())) { // Not Possible + LOGE("[SqlSinResSet][GetEntry] Internal Error: Position=%d, CacheStartPos=%d, cached=%zu.", position_, + cacheStartPosition_, cachedRowIds_.size()); + return -E_INTERNAL_ERROR; + } + int errCode = handle_->GetEntryByRowId(cachedRowIds_[cacheIndex], entry); + if (errCode != E_OK) { + LOGE("[SqlSinResSet][GetEntry] GetEntryByRowId fail, errCode=%d.", errCode); + return errCode; + } + return E_OK; + } + } + return -E_NO_SUCH_ENTRY; +} + +void SQLiteSingleVerResultSet::Close() +{ + std::lock_guard lockGuard(mutex_); + if (!isOpen_) { + return; + } + if (option_.cacheMode == ResultSetCacheMode::CACHE_FULL_ENTRY) { + CloseForCacheFullEntryMode(); + } else { + CloseForCacheEntryIdMode(); + } + isOpen_ = false; + count_ = 0; + position_ = INIT_POSTION; + LOGD("[SqlSinResSet][Close] Done, Type=%d, Mode=%d.", static_cast(type_), static_cast(option_.cacheMode)); +} + +void SQLiteSingleVerResultSet::CloseForCacheFullEntryMode() +{ + // Attention! Must Delete EntryWindow First(will call ForwardCursor::Close), then delete ForwardCursor. + // ForwardCursor::Close will call Executor::CloseResultSet(Reset the statement and rollback transaction) + delete window_; // It is defined behavior to delete even a nullptr + window_ = nullptr; + // Attention! Delete ForwardCursor Later. + delete rawCursor_; // It is defined behavior to delete even a nullptr + rawCursor_ = nullptr; +} + +void SQLiteSingleVerResultSet::CloseForCacheEntryIdMode() +{ + cacheStartPosition_ = INIT_POSTION; + cachedRowIds_.clear(); + // In Fact : handle_ and kvDB_ is guaranteed to be not nullptr + if (handle_ != nullptr) { + handle_->CloseResultSet(); + kvDB_->ReleaseHandle(handle_); + } +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_result_set.h b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_result_set.h new file mode 100644 index 00000000..d4c53830 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_result_set.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_SINGLE_VER_RESULT_SET_H +#define SQLITE_SINGLE_VER_RESULT_SET_H + +#include +#include + +#include "kvdb_windowed_result_set.h" +#include "ikvdb_raw_cursor.h" +#include "query_object.h" + +namespace DistributedDB { +constexpr int INIT_POSTION = -1; +constexpr int DEFAULT_RESULT_SET_CACHE_MAX_SIZE = 1; // Unit MB, default 1 MB +constexpr int RESULT_SET_CACHE_MAX_SIZE_MIN = 1; +constexpr int RESULT_SET_CACHE_MAX_SIZE_MAX = 16; +enum class ResultSetType : int { + KEYPREFIX = 0, + QUERY = 1, +}; +// Forward declaration +class SQLiteSingleVerNaturalStore; +class SQLiteSingleVerStorageExecutor; + +class SQLiteSingleVerResultSet : public KvDBWindowedResultSet { +public: + struct Option { + ResultSetCacheMode cacheMode = ResultSetCacheMode::CACHE_FULL_ENTRY; + int cacheMaxSize = DEFAULT_RESULT_SET_CACHE_MAX_SIZE; + }; + + SQLiteSingleVerResultSet(SQLiteSingleVerNaturalStore *kvDB, const Key &keyPrefix, const Option& option); + SQLiteSingleVerResultSet(SQLiteSingleVerNaturalStore *kvDB, const QueryObject &queryObj, const Option& option); + ~SQLiteSingleVerResultSet() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteSingleVerResultSet); + + // Initialize logic + int Open(bool isMemDb) override; + + // Get total entries count. + // >= 0: count, < 0: errCode. + int GetCount() const override; + + // Get current read position. + // >= 0: position, < 0: errCode + int GetPosition() const override; + + // Move the read position to an absolute position value. + int MoveTo(int position) const override; + + // Get the entry of current position. + int GetEntry(Entry &entry) const override; + + // Finalize logic + void Close() override; +private: + int OpenForCacheFullEntryMode(bool isMemDb); + int OpenForCacheEntryIdMode(); + + int MoveToForCacheFullEntryMode(int position) const; + int MoveToForCacheEntryIdMode(int position) const; + + void CloseForCacheFullEntryMode(); + void CloseForCacheEntryIdMode(); + + const Option option_; + + // Common Part Of Two ResultSet Mode. + bool isOpen_ = false; + int count_ = 0; + mutable int position_ = INIT_POSTION; // The position in the overall result + mutable std::mutex mutex_; + + // For KeyPrefix Type Or Query Type. + const ResultSetType type_ = ResultSetType::KEYPREFIX; + Key keyPrefix_; + mutable QueryObject queryObj_; // Some QueryObject member function need to call is not a const function(BAD...) + // Common Pointer For Use, Not Own it, Not Responsible To Release It. + SQLiteSingleVerNaturalStore *kvDB_ = nullptr; + + // Cache Full Entry Mode Using ResultEntriesWindow and IKvDBRawCursor, Own It, Responsible To Release It. + ResultEntriesWindow *window_ = nullptr; + IKvDBRawCursor *rawCursor_ = nullptr; + + // Cache EntryId Mode Using StorageExecutor, Own It, Responsible To Release It. + SQLiteSingleVerStorageExecutor *handle_ = nullptr; + mutable std::vector cachedRowIds_; + mutable int cacheStartPosition_ = INIT_POSTION; // The offset of the first cached rowid in all result rowids +}; +} // namespace DistributedDB + +#endif // SQLITE_SINGLE_VER_RESULT_SET_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_schema_database_upgrader.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_schema_database_upgrader.cpp new file mode 100644 index 00000000..4aaaf25c --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_schema_database_upgrader.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_single_ver_schema_database_upgrader.h" +#include "db_errno.h" +#include "log_print.h" +#include "schema_utils.h" +#include "db_constant.h" + +namespace DistributedDB { +SQLiteSingleVerSchemaDatabaseUpgrader::SQLiteSingleVerSchemaDatabaseUpgrader(sqlite3 *db, + const SchemaObject &newSchema, const SecurityOption &securityOpt, bool isMemDB) + : SQLiteSingleVerDatabaseUpgrader(db, securityOpt, isMemDB), SingleVerSchemaDatabaseUpgrader(newSchema) +{ +} + +int SQLiteSingleVerSchemaDatabaseUpgrader::GetDatabaseSchema(std::string &dbSchema) const +{ + int errCode = SQLiteUtils::GetSchema(db_, dbSchema); + if (errCode != E_OK && errCode != -E_NOT_FOUND) { + LOGE("[SqlSingleSchemaUp][GetSchema] ErrCode=%d", errCode); + return errCode; + } + return E_OK; +} + +int SQLiteSingleVerSchemaDatabaseUpgrader::SetDatabaseSchema(const std::string &dbSchema) +{ + int errCode = SQLiteUtils::SaveSchema(db_, dbSchema); + if (errCode != E_OK) { + LOGE("[SqlSingleSchemaUp][SetSchema] ErrCode=%d", errCode); + } + return errCode; +} + +struct ValueUpgradeContext { + SchemaObject schema; + uint32_t checkCount = 0; + uint32_t getCount = 0; + int errCode = E_OK; +}; + +namespace { +const std::string FUNC_NAME_CHECK_AMEND_VALUE = "check_amend_value"; +const std::string FUNC_NAME_GET_AMENDED_VALUE = "get_amended_value"; +// Current implementation is not of ideal performance: at first, we hope to use check_amend_value to filter out values +// that do not need amend, and call get_amended_value immediately for those value that need amend and obtain amended +// value from ValueUpgradeContext which is cache by check_amend_value just before. It works well for case upgrading from +// kv to schema database, but in the case the original schema having index, the sqlite will gather all rowid of values +// that after filtering at first, then call get_amended_value for each value of rowid later. +// Finally we can only parse value the twice in get_amended_value. +const std::string VALUE_UPGRADE_SQL = "UPDATE sync_data SET value=get_amended_value(value) " + "WHERE (flag&0x01=0) AND check_amend_value(value) != 0;"; +constexpr int USING_STR_LEN = -1; + +void CheckGetForJsonSchema(sqlite3_context *ctx, ValueUpgradeContext &context, const RawValue &inValue, + bool checkTrueGetFalse) +{ + ValueObject valueObj; + int errCode = valueObj.Parse(inValue.first, inValue.first + inValue.second, context.schema.GetSkipSize()); + if (errCode != E_OK) { // Unlikely + sqlite3_result_error(ctx, "[SqlSingleSchemaUp][CheckGet] Json value parse fail.", USING_STR_LEN); + LOGE("[SqlSingleSchemaUp][CheckGet] IsCheck=%d, Json value(cnt=%u) parse fail=%d.", checkTrueGetFalse, + (checkTrueGetFalse ? context.checkCount : context.getCount), errCode); + return; + } + errCode = context.schema.CheckValueAndAmendIfNeed(ValueSource::FROM_DBFILE, valueObj); + if (checkTrueGetFalse) { + if (errCode == -E_VALUE_MATCH) { + sqlite3_result_int(ctx, 0); // SQLiteResult 0 for check_ok_no_amend + } else if (errCode == -E_VALUE_MATCH_AMENDED) { + sqlite3_result_int(ctx, E_VALUE_MATCH_AMENDED); // SQLiteResult not 0 for check_ok_and_amend + } else { + sqlite3_result_error(ctx, "[SqlSingleSchemaUp][CheckGet] Json value check fail.", USING_STR_LEN); + LOGE("[SqlSingleSchemaUp][CheckGet] Json value(cnt=%u) check fail=%d.", context.checkCount, errCode); + context.errCode = -E_SCHEMA_VIOLATE_VALUE; + } + } else { + if (errCode != -E_VALUE_MATCH_AMENDED) { // Unlikely + sqlite3_result_error(ctx, "[SqlSingleSchemaUp][CheckGet] Json value no need amend.", USING_STR_LEN); + LOGE("[SqlSingleSchemaUp][CheckGet] Json value(cnt=%u) no need amend=%d.", context.getCount, errCode); + context.errCode = -E_INTERNAL_ERROR; + } + std::vector valueAmended; + valueObj.WriteIntoVector(valueAmended); + if (valueAmended.size() > DBConstant::MAX_VALUE_SIZE) { + sqlite3_result_error(ctx, "[SqlSingleSchemaUp][CheckGet] ValSize exceed limit after amend.", USING_STR_LEN); + LOGE("[SqlSingleSchemaUp][CheckGet] Value(cnt=%u) size=%zu exceed limit after amend.", context.getCount, + valueAmended.size()); + context.errCode = -E_SCHEMA_VIOLATE_VALUE; + return; + } + // For SQLITE_TRANSIENT, SQLite makes a copy of result into space obtained from sqlite3_malloc before it returns + sqlite3_result_blob(ctx, valueAmended.data(), valueAmended.size(), SQLITE_TRANSIENT); + } +} + +void CheckGetForFlatBufferSchema(sqlite3_context *ctx, ValueUpgradeContext &context, const RawValue &inValue, + bool checkTrueGetFalse) +{ + if (!checkTrueGetFalse) { + sqlite3_result_error(ctx, "[SqlSingleSchemaUp][CheckGet] FlatBuffer value no need amend.", USING_STR_LEN); + LOGE("[SqlSingleSchemaUp][CheckGet] FlatBuffer value(cnt=%u) no need amend.", context.getCount); + context.errCode = -E_INTERNAL_ERROR; + } + int errCode = context.schema.VerifyValue(ValueSource::FROM_DBFILE, inValue); + if (errCode != E_OK) { + sqlite3_result_error(ctx, "[SqlSingleSchemaUp][CheckGet] FlatBuffer value verify fail.", USING_STR_LEN); + LOGE("[SqlSingleSchemaUp][CheckGet] FlatBuffer value(cnt=%u) verify fail=%d.", context.checkCount, errCode); + context.errCode = -E_SCHEMA_VIOLATE_VALUE; + return; + } + sqlite3_result_int(ctx, 0); // SQLiteResult 0 for check_ok_no_amend +} + +// SQLiteResult 0 for check_ok_no_amend, SQLiteResult not 0 for check_ok_and_amend, SQLiteResult error for check_fail +void CheckValueOrGetAmendValue(sqlite3_context *ctx, int argc, sqlite3_value **argv, bool checkTrueGetFalse) +{ + if (ctx == nullptr || argc != 1 || argv == nullptr) { // 1 parameters, which are value. Unlikely + LOGE("[SqlSingleSchemaUp][CheckGet] Invalid parameter, argc=%d.", argc); + return; + } + auto context = static_cast(sqlite3_user_data(ctx)); + if (context == nullptr || !context->schema.IsSchemaValid()) { // Unlikely + sqlite3_result_error(ctx, "[SqlSingleSchemaUp][CheckGet] No context or schema invalid.", USING_STR_LEN); + LOGE("[SqlSingleSchemaUp][CheckGet] No context or schema invalid."); + return; + } + auto valueBlob = static_cast(sqlite3_value_blob(argv[0])); + int valueBlobLen = sqlite3_value_bytes(argv[0]); + if ((valueBlob == nullptr) || (valueBlobLen <= 0)) { // Is delete record, Unlikely + // Currently delete records are filtered out of value upgrade sql, so not allowed here. + sqlite3_result_error(ctx, "[SqlSingleSchemaUp][CheckGet] Delete record not allowed.", USING_STR_LEN); + LOGE("[SqlSingleSchemaUp][CheckGet] Delete record not allowed."); + return; + } + + if (context->schema.GetSchemaType() == SchemaType::JSON) { + CheckGetForJsonSchema(ctx, *context, RawValue{valueBlob, valueBlobLen}, checkTrueGetFalse); + } else { + CheckGetForFlatBufferSchema(ctx, *context, RawValue{valueBlob, valueBlobLen}, checkTrueGetFalse); + } + // Count only for non-delete value in check_func or get_func + if (checkTrueGetFalse) { + context->checkCount++; + } else { + context->getCount++; + } +} + +void CheckAmendValue(sqlite3_context *ctx, int argc, sqlite3_value **argv) +{ + CheckValueOrGetAmendValue(ctx, argc, argv, true); +} + +void GetAmendedValue(sqlite3_context *ctx, int argc, sqlite3_value **argv) +{ + CheckValueOrGetAmendValue(ctx, argc, argv, false); +} +} + +int SQLiteSingleVerSchemaDatabaseUpgrader::UpgradeValues() +{ + ValueUpgradeContext context; + context.schema = newSchema_; + LOGD("[SqlSingleSchemaUp][UpValue] Begin."); + int errCode = sqlite3_create_function_v2(db_, FUNC_NAME_CHECK_AMEND_VALUE.c_str(), + 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, &context, &CheckAmendValue, nullptr, nullptr, nullptr); // 1 args + if (errCode != SQLITE_OK) { + LOGE("[SqlSingleSchemaUp][UpValue] Create func=check_amend_value return=%d.", errCode); + return SQLiteUtils::MapSQLiteErrno(errCode); + } + // GetAmendedValue is better not be of deterministic type, otherwise sqlite may take it as constant + errCode = sqlite3_create_function_v2(db_, FUNC_NAME_GET_AMENDED_VALUE.c_str(), + 1, SQLITE_UTF8, &context, &GetAmendedValue, nullptr, nullptr, nullptr); // 1 args + if (errCode != SQLITE_OK) { + LOGE("[SqlSingleSchemaUp][UpValue] Create func=get_amended_value return=%d.", errCode); + return SQLiteUtils::MapSQLiteErrno(errCode); + } + errCode = SQLiteUtils::ExecuteRawSQL(db_, VALUE_UPGRADE_SQL); + if (errCode != E_OK) { + LOGE("[SqlSingleSchemaUp][UpValue] Execute value upgrade fail=%d, contextErr=%d.", errCode, context.errCode); + // If error caused by upgrade nor sqlite, using contextErr as the final errCode + errCode = ((context.errCode == E_OK ? errCode : context.errCode)); + } + LOGD("[SqlSingleSchemaUp][UpValue] End."); + return errCode; +} + +int SQLiteSingleVerSchemaDatabaseUpgrader::UpgradeIndexes(const IndexDifference &indexDiffer) +{ + uint32_t skipSize = newSchema_.GetSkipSize(); + SchemaType theType = newSchema_.GetSchemaType(); + // The order of index upgrade is not compulsory, we think order "decrease, change, increase" may be better. + for (const auto &entry : indexDiffer.decrease) { + LOGI("[SqlSingleSchemaUp][UpIndex] DecreaseIndex : indexName=%s.", SchemaUtils::FieldPathString(entry).c_str()); + int errCode = SQLiteUtils::DecreaseIndex(db_, entry); + if (errCode != E_OK) { + LOGE("[SqlSingleSchemaUp][UpIndex] DecreaseIndex fail, errCode=%d.", errCode); + return errCode; + } + } + for (const auto &entry : indexDiffer.change) { + LOGI("[SqlSingleSchemaUp][UpIndex] ChangeIndex : SkipSize=%u, indexName=%s, fieldCount=%zu, type=%s.", + skipSize, SchemaUtils::FieldPathString(entry.first).c_str(), entry.second.size(), + SchemaUtils::SchemaTypeString(theType).c_str()); + int errCode = SQLiteUtils::ChangeIndex(db_, entry.first, entry.second, theType, skipSize); + if (errCode != E_OK) { + LOGE("[SqlSingleSchemaUp][UpIndex] ChangeIndex fail, errCode=%d.", errCode); + return errCode; + } + } + for (const auto &entry : indexDiffer.increase) { + LOGI("[SqlSingleSchemaUp][UpIndex] IncreaseIndex : SkipSize=%u, indexName=%s, fieldCount=%zu, type=%s.", + skipSize, SchemaUtils::FieldPathString(entry.first).c_str(), entry.second.size(), + SchemaUtils::SchemaTypeString(theType).c_str()); + int errCode = SQLiteUtils::IncreaseIndex(db_, entry.first, entry.second, theType, skipSize); + if (errCode != E_OK) { + LOGE("[SqlSingleSchemaUp][UpIndex] IncreaseIndex fail, errCode=%d.", errCode); + return errCode; + } + } + return E_OK; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_schema_database_upgrader.h b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_schema_database_upgrader.h new file mode 100644 index 00000000..4a534881 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_schema_database_upgrader.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_SINGLE_VER_SCHEMA_DATABASE_UPGRADER_H +#define SQLITE_SINGLE_VER_SCHEMA_DATABASE_UPGRADER_H + +#include "sqlite_single_ver_database_upgrader.h" +#include "single_ver_schema_database_upgrader.h" + +namespace DistributedDB { +class SQLiteSingleVerSchemaDatabaseUpgrader final : public SQLiteSingleVerDatabaseUpgrader, + public SingleVerSchemaDatabaseUpgrader { +public: + // An invalid SchemaObject indicate no schema + SQLiteSingleVerSchemaDatabaseUpgrader(sqlite3 *db, const SchemaObject &newSchema, + const SecurityOption &securityOpt, bool isMemDB); + ~SQLiteSingleVerSchemaDatabaseUpgrader() override {}; +protected: + // Get an empty string with return_code E_OK indicate no schema but everything normally + int GetDatabaseSchema(std::string &dbSchema) const override; + + // Set or update schema into database file + int SetDatabaseSchema(const std::string &dbSchema) override; + + int UpgradeValues() override; + int UpgradeIndexes(const IndexDifference &indexDiffer) override; +}; +} // namespace DistributedDB +#endif // SQLITE_SINGLE_VER_SCHEMA_DATABASE_UPGRADER_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_engine.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_engine.cpp new file mode 100644 index 00000000..3c3e0759 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_engine.cpp @@ -0,0 +1,1141 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_single_ver_storage_engine.h" + +#include + +#include "db_errno.h" +#include "log_print.h" +#include "db_constant.h" +#include "sqlite_single_ver_database_upgrader.h" +#include "sqlite_single_ver_natural_store.h" +#include "sqlite_single_ver_schema_database_upgrader.h" +#include "platform_specific.h" +#include "runtime_context.h" +#include "db_common.h" +#include "kvdb_manager.h" +#include "param_check_utils.h" + +namespace DistributedDB { +namespace { + const uint64_t CACHE_RECORD_DEFAULT_VERSION = 1; + int GetPathSecurityOption(const std::string &filePath, SecurityOption &secOpt) + { + return RuntimeContext::GetInstance()->GetSecurityOption(filePath, secOpt); + } + + enum class DbType { + MAIN, + META, + CACHE + }; + + std::string GetDbDir(const std::string &subDir, DbType type) + { + static const std::map dbDirDic { + { DbType::MAIN, DBConstant::MAINDB_DIR }, + { DbType::META, DBConstant::METADB_DIR }, + { DbType::CACHE, DBConstant::CACHEDB_DIR }, + }; // for ensure static compilation order + + if (dbDirDic.find(type) == dbDirDic.end()) { + return std::string(); + } + return subDir + "/" + dbDirDic.at(type); + } +} // namespace + +SQLiteSingleVerStorageEngine::SQLiteSingleVerStorageEngine() + : cacheRecordVersion_(CACHE_RECORD_DEFAULT_VERSION), + executorState_(ExecutorState::INVALID), + isCorrupted_(false), + isNeedUpdateSecOpt_(false) +{} + +SQLiteSingleVerStorageEngine::~SQLiteSingleVerStorageEngine() +{} + +int SQLiteSingleVerStorageEngine::MigrateLocalData(SQLiteSingleVerStorageExecutor *handle) const +{ + return handle->MigrateLocalData(); +} + +int SQLiteSingleVerStorageEngine::EraseDeviceWaterMark(SQLiteSingleVerStorageExecutor *&handle, + const std::vector &dataItems) +{ + int errCode = E_OK; + for (const auto &dataItem : dataItems) { + if ((dataItem.flag & DataItem::REMOVE_DEVICE_DATA_FLAG) == DataItem::REMOVE_DEVICE_DATA_FLAG || + (dataItem.flag & DataItem::REMOVE_DEVICE_DATA_NOTIFY_FLAG) == DataItem::REMOVE_DEVICE_DATA_NOTIFY_FLAG) { + auto kvdbManager = KvDBManager::GetInstance(); + if (kvdbManager == nullptr) { + return -E_INVALID_DB; + } + + // sync module will use handle to fix water mark, if fix fail then migrate fail, not need hold write handle + errCode = ReleaseExecutor(handle); + if (errCode != E_OK) { + LOGE("release executor for erase water mark! errCode = [%d]", errCode); + return errCode; + } + + auto identifier = GetIdentifier(); + auto kvdb = kvdbManager->FindKvDB(identifier); + if (kvdb == nullptr) { + LOGE("[SingleVerEngine::EraseWaterMark] kvdb is null."); + return -E_INVALID_DB; + } + + auto kvStore = static_cast(kvdb); + errCode = kvStore->EraseDeviceWaterMark(dataItem.dev, false); + RefObject::DecObjRef(kvdb); + if (errCode != E_OK) { + LOGE("EraseDeviceWaterMark failed when migrating, errCode = [%d]", errCode); + return errCode; + } + + handle = static_cast(FindExecutor(true, OperatePerm::NORMAL_PERM, + errCode)); + if (errCode != E_OK) { + LOGE("Migrate sync data fail, Can not get available executor, errCode = [%d]", errCode); + return errCode; + } + } + } + return errCode; +} + +int SQLiteSingleVerStorageEngine::MigrateSyncDataByVersion(SQLiteSingleVerStorageExecutor *&handle, + NotifyMigrateSyncData &syncData, uint64_t &curMigrateVer) +{ + if (syncData.committedData == nullptr) { + syncData.committedData = new (std::nothrow) SingleVerNaturalStoreCommitNotifyData(); + if (syncData.committedData == nullptr) { + LOGE("[SQLiteSingleVerStorageEngine::MigrateSyncData] committedData is null."); + return -E_OUT_OF_MEMORY; + } + } + InitConflictNotifiedFlag(syncData.committedData); + + std::vector dataItems; + uint64_t minVerIncurCacheDb = 0; + int errCode = handle->GetMinVersionCacheData(dataItems, minVerIncurCacheDb); + if (errCode != E_OK) { + LOGE("[MigrateSyncDataByVersion]Fail to get cur data in cache! err[%d]", errCode); + return errCode; + } + + if (minVerIncurCacheDb == 0) { // min version in cache db is 1 + ++curMigrateVer; + return E_OK; + } + + if (minVerIncurCacheDb != curMigrateVer) { // double check for latest version is migrated + curMigrateVer = minVerIncurCacheDb; + } + + // Call the syncer module to erase the water mark. + errCode = EraseDeviceWaterMark(handle, dataItems); + if (errCode != E_OK) { + LOGE("[MigrateSyncData] Erase water mark failed:%d", errCode); + return errCode; + } + + // next version need process + LOGD("MigrateVer[%" PRIu64 "], minVer[%" PRIu64 "] maxVer[%" PRIu64 "]", + curMigrateVer, minVerIncurCacheDb, GetCacheRecordVersion()); + errCode = handle->MigrateSyncDataByVersion(curMigrateVer++, syncData, dataItems); + if (errCode != E_OK) { + LOGE("Migrate sync data fail and rollback, errCode = [%d]", errCode); + return errCode; + } + + CommitNotifyForMigrateCache(syncData); + + Timestamp timestamp = 0; + errCode = handle->GetMaxTimestampDuringMigrating(timestamp); + if (errCode == E_OK) { + SetMaxTimestamp(timestamp); + } + + errCode = ReleaseHandleTransiently(handle, 2ull); // temporary release handle 2ms + if (errCode != E_OK) { + return errCode; + } + + return E_OK; +} + +// Temporary release handle for idleTime ms, avoid long-term blocking +int SQLiteSingleVerStorageEngine::ReleaseHandleTransiently(SQLiteSingleVerStorageExecutor *&handle, uint64_t idleTime) +{ + int errCode = ReleaseExecutor(handle); + if (errCode != E_OK) { + LOGE("release executor for reopen database! errCode = [%d]", errCode); + return errCode; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(idleTime)); // Wait 2 ms to free this handle for put data + handle = static_cast(FindExecutor(true, OperatePerm::NORMAL_PERM, errCode)); + if (errCode != E_OK) { + LOGE("Migrate sync data fail, Can not get available executor, errCode = [%d]", errCode); + return errCode; + } + return errCode; +} + +int SQLiteSingleVerStorageEngine::AddSubscribeToMainDBInMigrate() +{ + LOGD("Add subscribe to mainDB from cache. %d", engineState_); + std::lock_guard lock(subscribeMutex_); + if (subscribeQuery_.empty()) { + return E_OK; + } + int errCode = E_OK; + auto handle = static_cast(FindExecutor(true, OperatePerm::NORMAL_PERM, errCode)); + if (errCode != E_OK || handle == nullptr) { + LOGE("Get available executor for add subscribe failed. %d", errCode); + return errCode; + } + errCode = handle->StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + goto END; + } + for (auto item : subscribeQuery_) { + errCode = handle->AddSubscribeTrigger(item.second, item.first); + if (errCode != E_OK) { + LOGE("Add subscribe trigger failed: %d id: %s", errCode, item.first.c_str()); + } + } + subscribeQuery_.clear(); + // Not rollback even if some triggers add failed. Users don’t perceive errors, add triggers as much as possible + (void)handle->Commit(); +END: + ReleaseExecutor(handle); + return errCode; +} + +int SQLiteSingleVerStorageEngine::MigrateSyncData(SQLiteSingleVerStorageExecutor *&handle, bool &isNeedTriggerSync) +{ + int errCode = E_OK; + if (handle == nullptr) { + handle = static_cast(FindExecutor(true, OperatePerm::NORMAL_PERM, errCode)); + if (errCode != E_OK) { + LOGE("Migrate sync data fail, Can not get available executor, errCode = [%d]", errCode); + return errCode; + } + } + + LOGD("Begin migrate sync data, need migrate version[%" PRIu64 "]", GetCacheRecordVersion() - 1); + uint64_t curMigrateVer = 0; // The migration process is asynchronous and continuous + NotifyMigrateSyncData syncData; + auto kvdbManager = KvDBManager::GetInstance(); + if (kvdbManager != nullptr) { + auto identifier = GetIdentifier(); + auto kvdb = kvdbManager->FindKvDB(identifier); + if (kvdb != nullptr) { + auto kvStore = static_cast(kvdb); + syncData.isPermitForceWrite = + !(kvStore->GetDbProperties().GetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, false)); + RefObject::DecObjRef(kvdb); + } else { + LOGE("[SingleVerEngine] kvdb is null."); + } + } + // cache atomic version represents version of cacheDb input next time + while (curMigrateVer < GetCacheRecordVersion()) { + errCode = MigrateSyncDataByVersion(handle, syncData, curMigrateVer); + if (errCode != E_OK) { + LOGE("Migrate version[%" PRIu64 "] failed! errCode = [%d]", curMigrateVer, errCode); + break; + } + if (!syncData.isRemote) { + isNeedTriggerSync = true; + } + } + if (syncData.committedData != nullptr) { + RefObject::DecObjRef(syncData.committedData); + syncData.committedData = nullptr; + } + // When finished Migrating sync data, will fix engine state + return errCode; +} + +int SQLiteSingleVerStorageEngine::AttachMainDbAndCacheDb(SQLiteSingleVerStorageExecutor *handle, + EngineState stateBeforeMigrate) +{ + LOGD("Begin attach main db and cache db by executor!"); + // Judge the file corresponding to db by the engine status and attach it to another file + int errCode = E_OK; + std::string attachAbsPath; + if (stateBeforeMigrate == EngineState::MAINDB) { + attachAbsPath = GetDbDir(option_.subdir, DbType::CACHE) + "/" + DBConstant::SINGLE_VER_CACHE_STORE + + DBConstant::SQLITE_DB_EXTENSION; + errCode = handle->AttachMainDbAndCacheDb(option_.cipherType, option_.passwd, attachAbsPath, stateBeforeMigrate); + } else if (stateBeforeMigrate == EngineState::CACHEDB) { + attachAbsPath = GetDbDir(option_.subdir, DbType::MAIN) + "/" + DBConstant::SINGLE_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION; + errCode = handle->AttachMainDbAndCacheDb(option_.cipherType, option_.passwd, attachAbsPath, stateBeforeMigrate); + } else { + return -E_NOT_SUPPORT; + } + if (errCode != E_OK) { + LOGE("Attached database failed, errCode = [%d] engine state = [%d]", errCode, stateBeforeMigrate); + return errCode; + } + + uint64_t maxVersion = 0; + errCode = handle->GetMaxVersionIncacheDb(maxVersion); + if (errCode != E_OK || maxVersion < CACHE_RECORD_DEFAULT_VERSION) { + maxVersion = CACHE_RECORD_DEFAULT_VERSION; + } + + (void)cacheRecordVersion_.store(maxVersion + 1, std::memory_order_seq_cst); + return errCode; +} + +int SQLiteSingleVerStorageEngine::AttachMainDbAndCacheDb(sqlite3 *dbHandle, EngineState stateBeforeMigrate) const +{ + LOGD("Begin attach main db and cache db by sqlite handle!"); + // Judge the file corresponding to db by the engine status and attach it to another file + int errCode = E_OK; + std::string attachAbsPath; + if (stateBeforeMigrate == EngineState::MAINDB) { + attachAbsPath = GetDbDir(option_.subdir, DbType::CACHE) + "/" + DBConstant::SINGLE_VER_CACHE_STORE + + DBConstant::SQLITE_DB_EXTENSION; + errCode = SQLiteUtils::AttachNewDatabase(dbHandle, option_.cipherType, option_.passwd, attachAbsPath, "cache"); + } else if (stateBeforeMigrate == EngineState::CACHEDB) { + attachAbsPath = GetDbDir(option_.subdir, DbType::MAIN) + "/" + DBConstant::SINGLE_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION; + errCode = SQLiteUtils::AttachNewDatabase(dbHandle, option_.cipherType, option_.passwd, attachAbsPath, "maindb"); + } else { + return -E_NOT_SUPPORT; + } + if (errCode != E_OK) { + LOGE("Attached database failed, errCode = [%d] engine state = [%d]", errCode, stateBeforeMigrate); + return errCode; + } + + return errCode; +} + +int SQLiteSingleVerStorageEngine::ReInit() +{ + return Init(); +} + +int SQLiteSingleVerStorageEngine::ReleaseExecutor(SQLiteSingleVerStorageExecutor *&handle) +{ + if (handle == nullptr) { + return E_OK; + } + StorageExecutor *databaseHandle = handle; + isCorrupted_ = isCorrupted_ || handle->GetCorruptedStatus(); + Recycle(databaseHandle); + handle = nullptr; + if (isCorrupted_) { + LOGE("Database is corrupted!"); + return -E_INVALID_PASSWD_OR_CORRUPTED_DB; // Externally imperceptible, used to terminate migration + } + return E_OK; +} + +int SQLiteSingleVerStorageEngine::FinishMigrateData(SQLiteSingleVerStorageExecutor *&handle, + EngineState stateBeforeMigrate) +{ + LOGI("Begin to finish migrate and reinit db state!"); + int errCode; + if (handle == nullptr) { + return -E_INVALID_ARGS; + } + + if (stateBeforeMigrate == EngineState::MAINDB) { + sqlite3 *dbHandle = nullptr; + errCode = handle->GetDbHandle(dbHandle); // use executor get sqlite3 handle to operating database + if (errCode != E_OK) { + LOGE("Get Db handle failed! errCode = [%d]", errCode); + return errCode; + } + + errCode = SQLiteUtils::ExecuteRawSQL(dbHandle, "DETACH 'cache'"); + if (errCode != E_OK) { + LOGE("Execute the SQLite detach failed:%d", errCode); + return errCode; + } + // delete cachedb + errCode = DBCommon::RemoveAllFilesOfDirectory(GetDbDir(option_.subdir, DbType::CACHE), false); + if (errCode != E_OK) { + LOGE("Remove files of cache database after detach:%d", errCode); + } + + SetEngineState(EngineState::MAINDB); + return errCode; + } + + errCode = ReleaseExecutor(handle); + if (errCode != E_OK) { + LOGE("Release executor for reopen database! errCode = [%d]", errCode); + return errCode; + } + + // close db for reinit this engine + Release(); + + // delete cache db + errCode = DBCommon::RemoveAllFilesOfDirectory(GetDbDir(option_.subdir, DbType::CACHE), false); + if (errCode != E_OK) { + LOGE("Remove files of cache database after release current db:%d", errCode); + return errCode; + } + + // reInit, it will reset engine state + errCode = ReInit(); + if (errCode != E_OK) { + LOGE("Reinit failed when finish migrate data! please try reopen kvstore! errCode = [%d]", errCode); + return errCode; + } + + return E_OK; +} + +int SQLiteSingleVerStorageEngine::InitExecuteMigrate(SQLiteSingleVerStorageExecutor *handle, + EngineState preMigrateState) +{ + // after attach main and cache need change operate data sql, changing state forbid operate database + SetEngineState(EngineState::MIGRATING); + + int errCode = E_OK; + // check if has been attach and attach cache and main for migrate + if (executorState_ == ExecutorState::MAINDB || executorState_ == ExecutorState::CACHEDB) { + errCode = AttachMainDbAndCacheDb(handle, preMigrateState); + if (errCode != E_OK) { + LOGE("[ExeMigrate] Attach main db and cache db failed!, errCode = [%d]", errCode); + // For lock state open db, can not attach main and cache + return errCode; + } + } else if (executorState_ == ExecutorState::MAIN_ATTACH_CACHE || + // Has been attach, maybe ever crashed, need update version + executorState_ == ExecutorState::CACHE_ATTACH_MAIN) { + uint64_t maxVersion = 0; + errCode = handle->GetMaxVersionIncacheDb(maxVersion); + if (errCode != E_OK || maxVersion < CACHE_RECORD_DEFAULT_VERSION) { + maxVersion = CACHE_RECORD_DEFAULT_VERSION; + } + (void)cacheRecordVersion_.store(maxVersion + 1, std::memory_order_seq_cst); + } else { + return -E_UNEXPECTED_DATA; + } + + return errCode; +} + +int SQLiteSingleVerStorageEngine::ExecuteMigrate() +{ + EngineState preState = GetEngineState(); + std::lock_guard lock(migrateLock_); + if (preState == EngineState::MIGRATING || preState == EngineState::INVALID || + !OS::CheckPathExistence(GetDbDir(option_.subdir, DbType::CACHE) + "/" + DBConstant::SINGLE_VER_CACHE_STORE + + DBConstant::SQLITE_DB_EXTENSION)) { + LOGD("[SqlSingleVerEngine] Being single ver migrating or never create db! engine state [%u]", preState); + return E_OK; + } + + // Get write executor for migrate + int errCode = E_OK; + auto handle = static_cast(FindExecutor(true, OperatePerm::NORMAL_PERM, errCode)); + if (errCode != E_OK) { + LOGE("Migrate data fail, Can not get available executor, errCode = [%d]", errCode); + return errCode; + } + + isMigrating_.store(true); + LOGD("Migrate start."); + bool isNeedTriggerSync = false; + errCode = InitExecuteMigrate(handle, preState); + if (errCode != E_OK) { + LOGE("Init migrate data fail, errCode = [%d]", errCode); + goto END; + } + + LOGD("[SqlSingleVerEngine] Current engineState [%u] executorState [%u], begin to executing singleVer db migrate!", + static_cast(preState), static_cast(executorState_)); + // has been attached, Mark start of migration and it can migrate data + errCode = MigrateLocalData(handle); + if (errCode != E_OK) { + LOGE("Migrate local data fail, errCode = [%d]", errCode); + goto END; + } + + errCode = MigrateSyncData(handle, isNeedTriggerSync); + if (errCode != E_OK) { + LOGE("Migrate Sync data fail, errCode = [%d]", errCode); + goto END; + } + + SetEngineState(EngineState::ENGINE_BUSY); // temp forbid use handle and engine for detach and close executor + + // detach database and delete cachedb + errCode = FinishMigrateData(handle, preState); + if (errCode != E_OK) { + LOGE("Finish migrating data fail, errCode = [%d]", errCode); + goto END; + } + +END: // after FinishMigrateData, it will reset engine state + // there is no need cover the errCode + EndMigrate(handle, preState, errCode, isNeedTriggerSync); + isMigrating_.store(false); + LOGD("Migrate stop."); + return errCode; +} + +void SQLiteSingleVerStorageEngine::EndMigrate(SQLiteSingleVerStorageExecutor *&handle, EngineState stateBeforeMigrate, + int errCode, bool isNeedTriggerSync) +{ + LOGD("Finish migrating data! errCode = [%d]", errCode); + if (errCode != E_OK) { + SetEngineState(stateBeforeMigrate); + } + if (handle != nullptr) { + handle->ClearMigrateData(); + } + errCode = ReleaseExecutor(handle); + if (errCode != E_OK) { + LOGE("release executor after migrating! errCode = [%d]", errCode); + } + + errCode = AddSubscribeToMainDBInMigrate(); + if (errCode != E_OK) { + LOGE("Add subscribe trigger after migrate sync data failed: %d", errCode); + } + // Notify max timestamp offset for SyncEngine. + // When time change offset equals 0, SyncEngine can adjust local time offset according to max timestamp. + RuntimeContext::GetInstance()->NotifyTimestampChanged(0); + if (isNeedTriggerSync) { + commitNotifyFunc_(SQLITE_GENERAL_FINISH_MIGRATE_EVENT, nullptr); + } + return; +} + +bool SQLiteSingleVerStorageEngine::IsEngineCorrupted() const +{ + return isCorrupted_; +} + +StorageExecutor *SQLiteSingleVerStorageEngine::NewSQLiteStorageExecutor(sqlite3 *dbHandle, bool isWrite, bool isMemDb) +{ + auto executor = new (std::nothrow) SQLiteSingleVerStorageExecutor(dbHandle, isWrite, isMemDb, executorState_); + if (executor == nullptr) { + return executor; + } + executor->SetConflictResolvePolicy(option_.conflictReslovePolicy); + return executor; +} + +int SQLiteSingleVerStorageEngine::TryToOpenMainDatabase(bool isWrite, sqlite3 *&db) +{ + // Only could get the main database handle in the uninitialized and the main status. + if (GetEngineState() != EngineState::INVALID && GetEngineState() != EngineState::MAINDB) { + LOGE("[SQLiteSinStoreEng][GetMainHandle] Can only create new handle for state[%d]", GetEngineState()); + return -E_EKEYREVOKED; + } + + if (!option_.isMemDb) { + option_.uri = GetDbDir(option_.subdir, DbType::MAIN) + "/" + DBConstant::SINGLE_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION; + } + + OpenDbProperties optionTemp = option_; + if (!isWrite) { + optionTemp.createIfNecessary = false; + } + + int errCode = SQLiteUtils::OpenDatabase(optionTemp, db); + if (errCode != E_OK) { + if (errno == EKEYREVOKED) { + LOGI("Failed to open the main database for key revoked[%d]", errCode); + errCode = -E_EKEYREVOKED; + } + return errCode; + } + + executorState_ = ExecutorState::MAINDB; + // Set the engine state to main status for that the main database is valid. + SetEngineState(EngineState::MAINDB); + + if (OS::CheckPathExistence(GetDbDir(option_.subdir, DbType::CACHE) + "/" + DBConstant::SINGLE_VER_CACHE_STORE + + DBConstant::SQLITE_DB_EXTENSION)) { + // In status cacheDb crash + errCode = AttachMainDbAndCacheDb(db, EngineState::MAINDB); + if (errCode != E_OK) { + LOGE("[SingleVerEngine][GetMain] Attach main db and cache db failed!, errCode = [%d]", errCode); + return E_OK; // not care err to return, only use for print log + } + executorState_ = ExecutorState::MAIN_ATTACH_CACHE; + // cache and main existed together, can not read data, must execute migrate first + SetEngineState(EngineState::ATTACHING); + } + + return errCode; +} + +int SQLiteSingleVerStorageEngine::GetDbHandle(bool isWrite, const SecurityOption &secOpt, sqlite3 *&dbHandle) +{ + int errCode = TryToOpenMainDatabase(isWrite, dbHandle); + LOGD("Finish to open the main database, write[%d], label[%d], flag[%d], id[%.6s], errCode[%d]", isWrite, + secOpt.securityLabel, secOpt.securityFlag, DBCommon::TransferStringToHex(identifier_).c_str(), errCode); + if (!(ParamCheckUtils::IsS3SECEOpt(secOpt) && errCode == -E_EKEYREVOKED)) { + return errCode; + } + + std::string cacheDbPath = GetDbDir(option_.subdir, DbType::CACHE) + "/" + DBConstant::SINGLE_VER_CACHE_STORE + + DBConstant::SQLITE_DB_EXTENSION; + if (!isWrite || GetEngineState() != EngineState::INVALID || + OS::CheckPathExistence(cacheDbPath)) { + LOGI("[SQLiteSingleStorageEng][GetDbHandle] Only use for first create cache db! [%d] [%d]", + isWrite, GetEngineState()); + return -E_EKEYREVOKED; + } + + errCode = GetCacheDbHandle(dbHandle); + if (errCode != E_OK) { + LOGE("singleVerStorageEngine::GetDbHandle get cache handle fail! errCode = [%d]", errCode); + return errCode; + } + SetEngineState(CACHEDB); + executorState_ = ExecutorState::CACHEDB; + + ResetCacheRecordVersion(); + // Get handle means maindb file ekeyevoked, not need attach to + return errCode; +} + +namespace CacheDbSqls { +const std::string CREATE_CACHE_LOCAL_TABLE_SQL = + "CREATE TABLE IF NOT EXISTS local_data(" \ + "key BLOB NOT NULL," \ + "value BLOB," \ + "timestamp INT," \ + "hash_key BLOB PRIMARY KEY NOT NULL," \ + "flag INT NOT NULL);"; + +const std::string CREATE_CACHE_SYNC_TABLE_SQL = + "CREATE TABLE IF NOT EXISTS sync_data(" \ + "key BLOB NOT NULL," \ + "value BLOB," \ + "timestamp INT NOT NULL," \ + "flag INT NOT NULL," \ + "device BLOB," \ + "ori_device BLOB," \ + "hash_key BLOB NOT NULL," \ + "w_timestamp INT," \ + "version INT NOT NULL," \ + "PRIMARY Key(version, hash_key));"; +} + +// Warning: Use error passwd create cache database can not check, it will create error passwd cache db, +// And make migrate data failed! This cache db will not be open correctly. +int SQLiteSingleVerStorageEngine::GetCacheDbHandle(sqlite3 *&db) +{ + option_.uri = GetDbDir(option_.subdir, DbType::CACHE) + "/" + DBConstant::SINGLE_VER_CACHE_STORE + + DBConstant::SQLITE_DB_EXTENSION; + // creatTable + option_.sqls = { + CacheDbSqls::CREATE_CACHE_LOCAL_TABLE_SQL, + CacheDbSqls::CREATE_CACHE_SYNC_TABLE_SQL + }; + + if (!option_.createIfNecessary) { + std::string mainDbPtah = GetDbDir(option_.subdir, DbType::MAIN) + "/" + DBConstant::SINGLE_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION; + if (!OS::CheckPathExistence(mainDbPtah)) { // Whether to create a cacheDb is based on whether the mainDb exists + return -E_INVALID_DB; + } + } + + OpenDbProperties option = option_; // copy for no change it + option.createIfNecessary = true; + int errCode = SQLiteUtils::OpenDatabase(option, db); + if (errCode != E_OK) { + LOGE("Get CacheDb handle failed, errCode = [%d], errno = [%d]", errCode, errno); + return errCode; + } + return errCode; +} + +int SQLiteSingleVerStorageEngine::CheckDatabaseSecOpt(const SecurityOption &secOption) const +{ + if (!(secOption == option_.securityOpt) && + secOption.securityLabel != SecurityLabel::NOT_SET && + option_.securityOpt.securityLabel != SecurityLabel::NOT_SET) { + LOGE("SecurityOption mismatch, existed:[%d-%d] vs input:[%d-%d]", secOption.securityLabel, + secOption.securityFlag, option_.securityOpt.securityLabel, option_.securityOpt.securityFlag); + return -E_SECURITY_OPTION_CHECK_ERROR; + } + return E_OK; +} + +int SQLiteSingleVerStorageEngine::CreateNewDirsAndSetSecOpt() const +{ + std::vector dbDir {DBConstant::MAINDB_DIR, DBConstant::METADB_DIR, DBConstant::CACHEDB_DIR}; + for (const auto &item : dbDir) { + if (OS::CheckPathExistence(option_.subdir + "/" + item)) { + continue; + } + + // Dir and old db file not existed, it means that the database is newly created + // need create flag of database not incomplete + if (!OS::CheckPathExistence(option_.subdir + DBConstant::PATH_POSTFIX_DB_INCOMPLETE) && + !OS::CheckPathExistence(option_.subdir + "/" + DBConstant::SINGLE_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION) && + OS::CreateFileByFileName(option_.subdir + DBConstant::PATH_POSTFIX_DB_INCOMPLETE) != E_OK) { + LOGE("Fail to create the token of database incompleted! errCode = [E_SYSTEM_API_FAIL]"); + return -E_SYSTEM_API_FAIL; + } + + if (DBCommon::CreateDirectory(option_.subdir + "/" + item) != E_OK) { + LOGE("Create sub-directory for single ver failed, errno:%d", errno); + return -E_SYSTEM_API_FAIL; + } + + if (option_.securityOpt.securityLabel == NOT_SET) { + continue; + } + + SecurityOption option = option_.securityOpt; + if (item == DBConstant::METADB_DIR) { + option.securityLabel = ((option_.securityOpt.securityLabel >= SecurityLabel::S2) ? + SecurityLabel::S2 : option_.securityOpt.securityLabel); + option.securityFlag = SecurityFlag::ECE; + } + + int errCode = RuntimeContext::GetInstance()->SetSecurityOption(option_.subdir + "/" + item, option); + if (errCode != E_OK && errCode != -E_NOT_SUPPORT) { + LOGE("Set the security option of sub-directory failed[%d]", errCode); + return errCode; + } + } + return E_OK; +} + +int SQLiteSingleVerStorageEngine::GetExistedSecOption(SecurityOption &secOption) const +{ + // Check the existence of the database, include the origin database and the database in the 'main' directory. + auto mainDbDir = GetDbDir(option_.subdir, DbType::MAIN); + auto mainDbFilePath = mainDbDir + "/" + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + auto origDbFilePath = option_.subdir + "/" + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + if (!OS::CheckPathExistence(origDbFilePath) && !OS::CheckPathExistence(mainDbFilePath)) { + secOption = option_.securityOpt; + return E_OK; + } + + // the main database file has high priority of the security option. + int errCode; + if (OS::CheckPathExistence(mainDbFilePath)) { + errCode = GetPathSecurityOption(mainDbFilePath, secOption); + } else { + errCode = GetPathSecurityOption(origDbFilePath, secOption); + } + if (errCode != E_OK) { + secOption = SecurityOption(); + if (errCode == -E_NOT_SUPPORT) { + return E_OK; + } + LOGE("Get the security option of the existed database failed."); + } + return errCode; +} + +void SQLiteSingleVerStorageEngine::ClearCorruptedFlag() +{ + isCorrupted_ = false; +} + +int SQLiteSingleVerStorageEngine::PreCreateExecutor(bool isWrite) +{ + // Assume that create the write executor firstly and the write one we will not be released. + // If the write one would be released in the future, should take care the pass through. + if (!isWrite) { + return E_OK; + } + + if (option_.isMemDb) { + return E_OK; + } + + // Get the existed database secure option. + SecurityOption existedSecOpt; + int errCode = GetExistedSecOption(existedSecOpt); + if (errCode != E_OK) { + return errCode; + } + + errCode = CheckDatabaseSecOpt(existedSecOpt); + if (errCode != E_OK) { + return errCode; + } + + // Judge whether need update the security option of the engine. + // Should update the security in the import or rekey scene(inner). + if (!isNeedUpdateSecOpt_) { + option_.securityOpt = existedSecOpt; + } + + errCode = CreateNewDirsAndSetSecOpt(); + if (errCode != E_OK) { + return errCode; + } + + if (!isUpdated_) { + errCode = SQLiteSingleVerDatabaseUpgrader::TransferDatabasePath(option_.subdir, option_); + if (errCode != E_OK) { + LOGE("[PreCreateExecutor] Transfer Db file path failed[%d].", errCode); + return errCode; + } + } + + return E_OK; +} + +int SQLiteSingleVerStorageEngine::EndCreateExecutor(bool isWrite) +{ + if (option_.isMemDb || !isWrite) { + return E_OK; + } + + int errCode = SQLiteSingleVerDatabaseUpgrader::SetSecOption(option_.subdir, option_.securityOpt, + isNeedUpdateSecOpt_); + if (errCode != E_OK) { + if (errCode == -E_NOT_SUPPORT) { + option_.securityOpt = SecurityOption(); + errCode = E_OK; + } + LOGE("SetSecOption failed:%d", errCode); + return errCode; + } + + // after setting secOption, the database file operation ends + // database create completed, delete the token + if (OS::CheckPathExistence(option_.subdir + DBConstant::PATH_POSTFIX_DB_INCOMPLETE) && + OS::RemoveFile(option_.subdir + DBConstant::PATH_POSTFIX_DB_INCOMPLETE) != E_OK) { + LOGE("Finish to create the complete database, but delete token fail! errCode = [E_SYSTEM_API_FAIL]"); + return -E_SYSTEM_API_FAIL; + } + return errCode; +} + +int SQLiteSingleVerStorageEngine::TryAttachMetaDb(sqlite3 *&dbHandle, bool &isAttachMeta) +{ + // attach or not depend on its true secOpt, but it's not permit while option_.secOpt different from true secOpt + if ((!option_.isMemDb) && (ParamCheckUtils::IsS3SECEOpt(option_.securityOpt))) { + int errCode = AttachMetaDatabase(dbHandle, option_); + if (errCode != E_OK) { + (void)sqlite3_close_v2(dbHandle); + dbHandle = nullptr; + return errCode; + } + isAttachMeta = true; + } + return E_OK; +} + +int SQLiteSingleVerStorageEngine::CreateNewExecutor(bool isWrite, StorageExecutor *&handle) +{ + int errCode = PreCreateExecutor(isWrite); + if (errCode != E_OK) { + return errCode; + } + + sqlite3 *dbHandle = nullptr; + errCode = GetDbHandle(isWrite, option_.securityOpt, dbHandle); + if (errCode != E_OK) { + return errCode; + } + + bool isAttachMeta = false; + errCode = TryAttachMetaDb(dbHandle, isAttachMeta); + if (errCode != E_OK) { + return errCode; + } + + RegisterFunctionIfNeed(dbHandle); + errCode = Upgrade(dbHandle); + if (errCode != E_OK) { + (void)sqlite3_close_v2(dbHandle); + dbHandle = nullptr; + return errCode; + } + + errCode = EndCreateExecutor(isWrite); + if (errCode != E_OK) { + LOGE("After create executor, set security option incomplete!"); + (void)sqlite3_close_v2(dbHandle); + dbHandle = nullptr; + return errCode; + } + + handle = NewSQLiteStorageExecutor(dbHandle, isWrite, option_.isMemDb); + if (handle == nullptr) { + LOGE("New SQLiteStorageExecutor[%d] for the pool failed.", isWrite); + (void)sqlite3_close_v2(dbHandle); + dbHandle = nullptr; + return -E_OUT_OF_MEMORY; + } + if (isAttachMeta) { + SQLiteSingleVerStorageExecutor *singleVerHandle = static_cast(handle); + singleVerHandle->SetAttachMetaMode(isAttachMeta); + } + return E_OK; +} + +int SQLiteSingleVerStorageEngine::Upgrade(sqlite3 *db) +{ + if (isUpdated_ || GetEngineState() == CACHEDB) { + LOGI("Storage engine is in cache status or has been upgraded[%d]!", isUpdated_); + return E_OK; + } + + std::unique_ptr upgrader; + LOGD("[SqlSingleEngine][Upgrade] NewSchemaStrSize=%zu", option_.schema.size()); + if (option_.schema.empty()) { + upgrader = std::make_unique(db, option_.securityOpt, option_.isMemDb); + } else { + SchemaObject schema; + int errCode = schema.ParseFromSchemaString(option_.schema); + if (errCode != E_OK) { + LOGE("Upgrader failed while parsing the origin schema:%d", errCode); + return errCode; + } + upgrader = std::make_unique(db, schema, + option_.securityOpt, option_.isMemDb); + } + + std::string mainDbDir = GetDbDir(option_.subdir, DbType::MAIN); + std::string mainDbFilePath = mainDbDir + "/" + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + SecurityOption secOpt = option_.securityOpt; + int errCode = E_OK; + if (isNeedUpdateSecOpt_) { + errCode = GetPathSecurityOption(mainDbFilePath, secOpt); + if (errCode != E_OK) { + LOGI("[SingleVerStorageEngine::Upgrade] Failed to get the path security option, errCode = [%d]", errCode); + if (errCode != -E_NOT_SUPPORT) { + return errCode; + } + secOpt = SecurityOption(); + } + } + + upgrader->SetMetaUpgrade(secOpt, option_.securityOpt, option_.subdir); + upgrader->SetSubdir(option_.subdir); + errCode = upgrader->Upgrade(); + if (errCode != E_OK) { + LOGE("Single ver database upgrade failed:%d", errCode); + return errCode; + } + + LOGD("Finish upgrade single ver database!"); + isUpdated_ = true; // Identification to avoid repeated upgrades + return errCode; +} + +// Attention: This function should be called before "Upgrade". +// Attention: This function should be called for each executor on the sqlite3 handle that the executor binds to. +void SQLiteSingleVerStorageEngine::RegisterFunctionIfNeed(sqlite3 *dbHandle) const +{ + // This function should accept a sqlite3 handle with no perception of database classification. That is, if it is + // not a newly created database, the meta-Table should exist and can be accessed. + std::string schemaStr = option_.schema; + if (schemaStr.empty()) { + // If schema from GetKvStore::Option is empty, we have to try to load it from database. ReadOnly mode if exist; + int errCode = SQLiteUtils::GetSchema(dbHandle, schemaStr); + if (errCode != E_OK) { + LOGD("[SqlSinEngine] Can't get schema from db[%d], maybe it is just created or not a schema-db.", errCode); + } + } + if (!schemaStr.empty()) { + // This must be a Schema-Database, if it is Json-Schema, the Register will do nothing and return E_OK + int errCode = SQLiteUtils::RegisterFlatBufferFunction(dbHandle, schemaStr); + if (errCode != E_OK) { // Not very likely + // Just warning, if no index had been or need to be created, then put or kv-get can still use. + LOGW("[SqlSinEngine] RegisterFlatBufferExtractFunction fail, errCode = %d", errCode); + } + } + + // This function is used to update meta_data in triggers when it's attached to mainDB + int errCode = SQLiteUtils::RegisterMetaDataUpdateFunction(dbHandle); + if (errCode != E_OK) { + LOGW("[SqlSinEngine] RegisterMetaDataUpdateFunction fail, errCode = %d", errCode); + } +} + +int SQLiteSingleVerStorageEngine::AttachMetaDatabase(sqlite3 *dbHandle, const OpenDbProperties &option) const +{ + int errCode; + LOGD("SQLiteSingleVerStorageEngine begin attach metaDb!"); + std::string metaDbPath = option.subdir + "/" + DBConstant::METADB_DIR + "/" + + DBConstant::SINGLE_VER_META_STORE + DBConstant::SQLITE_DB_EXTENSION; + // attach metaDb may failed while createIfNecessary is false, here need to create metaDb first. + if (!option.createIfNecessary && !OS::CheckPathExistence(metaDbPath)) { + errCode = SQLiteUtils::CreateMetaDatabase(metaDbPath); + if (errCode != E_OK) { + return errCode; + } + } + CipherPassword passwd; + errCode = SQLiteUtils::AttachNewDatabase(dbHandle, option.cipherType, passwd, metaDbPath, "meta"); + if (errCode != E_OK) { + LOGE("AttachNewDatabase fail, errCode = %d", errCode); + } + return errCode; +} + +void SQLiteSingleVerStorageEngine::ResetCacheRecordVersion() +{ + (void)cacheRecordVersion_.store(CACHE_RECORD_DEFAULT_VERSION, std::memory_order_seq_cst); +} + +void SQLiteSingleVerStorageEngine::IncreaseCacheRecordVersion() +{ + (void)cacheRecordVersion_.fetch_add(1, std::memory_order_seq_cst); +} + +uint64_t SQLiteSingleVerStorageEngine::GetAndIncreaseCacheRecordVersion() +{ + return cacheRecordVersion_.fetch_add(1, std::memory_order_seq_cst); +} + +uint64_t SQLiteSingleVerStorageEngine::GetCacheRecordVersion() const +{ + return cacheRecordVersion_.load(std::memory_order_seq_cst); +} + +void SQLiteSingleVerStorageEngine::CommitAndReleaseNotifyData(SingleVerNaturalStoreCommitNotifyData *&committedData, + int eventType) const +{ + std::shared_lock lock(notifyMutex_); + if (commitNotifyFunc_ == nullptr) { + LOGE("commitNotifyFunc_ is nullptr, can't notify now."); + RefObject::DecObjRef(committedData); + committedData = nullptr; + return; + } + commitNotifyFunc_(eventType, static_cast(committedData)); + committedData = nullptr; + return; +} + +void SQLiteSingleVerStorageEngine::InitConflictNotifiedFlag(SingleVerNaturalStoreCommitNotifyData *&committedData) const +{ + if (committedData == nullptr) { + LOGI("[SQLiteSingleVerStorageEngine::InitConflictNotifiedFlag] committedData is null."); + return; + } + auto identifier = GetIdentifier(); + auto kvdb = KvDBManager::GetInstance()->FindKvDB(identifier); + if (kvdb == nullptr) { + LOGE("[SQLiteSingleVerStorageEngine::InitConflictNotifiedFlag] kvdb is null."); + return; + } + unsigned int conflictFlag = 0; + if (static_cast(kvdb)->GetRegisterFunctionCount(CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ONLY) != 0) { + conflictFlag |= static_cast(SQLITE_GENERAL_NS_FOREIGN_KEY_ONLY); + } + if (static_cast(kvdb)->GetRegisterFunctionCount(CONFLICT_SINGLE_VERSION_NS_FOREIGN_KEY_ORIG) != 0) { + conflictFlag |= static_cast(SQLITE_GENERAL_NS_FOREIGN_KEY_ORIG); + } + if (static_cast(kvdb)->GetRegisterFunctionCount(CONFLICT_SINGLE_VERSION_NS_NATIVE_ALL) != 0) { + conflictFlag |= static_cast(SQLITE_GENERAL_NS_NATIVE_ALL); + } + RefObject::DecObjRef(kvdb); + LOGD("[SQLiteSingleVerStorageEngine::InitConflictNotifiedFlag] conflictFlag Flag: %u", conflictFlag); + committedData->SetConflictedNotifiedFlag(static_cast(conflictFlag)); +} + +void SQLiteSingleVerStorageEngine::SetMaxTimestamp(Timestamp maxTimestamp) const +{ + auto kvdbManager = KvDBManager::GetInstance(); + if (kvdbManager == nullptr) { + return; + } + auto identifier = GetIdentifier(); + auto kvdb = kvdbManager->FindKvDB(identifier); + if (kvdb == nullptr) { + LOGE("[SQLiteSingleVerStorageEngine::SetMaxTimestamp] kvdb is null."); + return; + } + + auto kvStore = static_cast(kvdb); + kvStore->SetMaxTimestamp(maxTimestamp); + RefObject::DecObjRef(kvdb); + return; +} + +void SQLiteSingleVerStorageEngine::CommitNotifyForMigrateCache(NotifyMigrateSyncData &syncData) const +{ + const auto &isRemote = syncData.isRemote; + const auto &isRemoveDeviceData = syncData.isRemoveDeviceData; + auto &committedData = syncData.committedData; + auto &entries = syncData.entries; + + // Put data. Including insert, update and delete. + if (!isRemoveDeviceData) { + if (committedData != nullptr) { + int eventType = isRemote ? SQLITE_GENERAL_NS_SYNC_EVENT : SQLITE_GENERAL_NS_PUT_EVENT; + CommitAndReleaseNotifyData(committedData, eventType); + } + return; + } + + // Remove device data. + if (entries.empty() || entries.size() > MAX_TOTAL_NOTIFY_ITEM_SIZE) { + return; + } + size_t totalSize = 0; + for (auto iter = entries.begin(); iter != entries.end();) { + auto &entry = *iter; + if (committedData == nullptr) { + committedData = new (std::nothrow) SingleVerNaturalStoreCommitNotifyData(); + if (committedData == nullptr) { + LOGE("Alloc committed notify data failed."); + return; + } + } + if (entry.key.size() > DBConstant::MAX_KEY_SIZE || entry.value.size() > DBConstant::MAX_VALUE_SIZE) { + iter++; + continue; + } + if (entry.key.size() + entry.value.size() + totalSize > MAX_TOTAL_NOTIFY_DATA_SIZE) { + CommitAndReleaseNotifyData(committedData, SQLITE_GENERAL_NS_SYNC_EVENT); + totalSize = 0; + continue; + } + totalSize += (entry.key.size() + entry.value.size()); + committedData->InsertCommittedData(std::move(entry), DataType::DELETE, false); + iter++; + } + if (committedData != nullptr) { + CommitAndReleaseNotifyData(committedData, SQLITE_GENERAL_NS_SYNC_EVENT); + } + return; +} + +// Cache subscribe when engine state is CACHE mode, and its will be applied at the beginning of migrate. +void SQLiteSingleVerStorageEngine::CacheSubscribe(const std::string &subscribeId, const QueryObject &query) +{ + std::lock_guard lock(subscribeMutex_); + subscribeQuery_[subscribeId] = query; +} +} diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_engine.h b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_engine.h new file mode 100644 index 00000000..1418ae4b --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_engine.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_SINGLE_VER_STORAGE_ENGINE_H +#define SQLITE_SINGLE_VER_STORAGE_ENGINE_H + +#include "macro_utils.h" +#include "sqlite_storage_engine.h" +#include "sqlite_single_ver_storage_executor.h" + +namespace DistributedDB { +enum SQLiteGeneralNSNotificationEventType { + SQLITE_GENERAL_NS_PUT_EVENT = 0x01, + SQLITE_GENERAL_NS_SYNC_EVENT = 0x02, + SQLITE_GENERAL_NS_LOCAL_PUT_EVENT = 0x04, + SQLITE_GENERAL_CONFLICT_EVENT = 0x08, // Conflict event + SQLITE_GENERAL_FINISH_MIGRATE_EVENT = 0x10, // Only trigger sync event +}; +enum SQLiteGeneralNSConflictType { + SQLITE_GENERAL_NS_FOREIGN_KEY_ONLY = 0x01, // sync conflict for same origin dev + SQLITE_GENERAL_NS_FOREIGN_KEY_ORIG = 0x02, // sync conflict for different origin dev + SQLITE_GENERAL_NS_NATIVE_ALL = 0x0c, // native conflict. +}; +class SQLiteSingleVerStorageEngine : public SQLiteStorageEngine { +public: + SQLiteSingleVerStorageEngine(); + ~SQLiteSingleVerStorageEngine() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteSingleVerStorageEngine); + + void IncreaseCacheRecordVersion() override; + uint64_t GetCacheRecordVersion() const override; + uint64_t GetAndIncreaseCacheRecordVersion() override; + + int ExecuteMigrate() override; + bool IsEngineCorrupted() const override; + + const SecurityOption &GetSecurityOption() const + { + return option_.securityOpt; + } + + void SetNeedUpdateSecOption(bool flag) + { + isNeedUpdateSecOpt_ = flag; + } + + void CacheSubscribe(const std::string &subscribeId, const QueryObject &query); + +protected: + StorageExecutor *NewSQLiteStorageExecutor(sqlite3 *dbHandle, bool isWrite, bool isMemDb) override; + + int Upgrade(sqlite3 *db) override; + + int CreateNewExecutor(bool isWrite, StorageExecutor *&handle) override; + +private: + // For executor. + int PreCreateExecutor(bool isWrite); + int EndCreateExecutor(bool isWrite); + int ReInit() override; + int ReleaseExecutor(SQLiteSingleVerStorageExecutor *&handle); + int ReleaseHandleTransiently(SQLiteSingleVerStorageExecutor *&handle, uint64_t idleTime); + + // For migrate. + int MigrateLocalData(SQLiteSingleVerStorageExecutor *handle) const; + int MigrateSyncDataByVersion(SQLiteSingleVerStorageExecutor *&handle, + NotifyMigrateSyncData &syncData, uint64_t &curMigrateVer); + int MigrateSyncData(SQLiteSingleVerStorageExecutor *&handle, bool &isNeedTriggerSync); + int FinishMigrateData(SQLiteSingleVerStorageExecutor *&handle, EngineState stateBeforeMigrate); + int InitExecuteMigrate(SQLiteSingleVerStorageExecutor *handle, EngineState preMigrateState); + void EndMigrate(SQLiteSingleVerStorageExecutor *&handle, EngineState stateBeforeMigrate, int errCode, + bool isNeedTriggerSync); + void ResetCacheRecordVersion(); + void SetMaxTimestamp(Timestamp maxTimestamp) const; + int EraseDeviceWaterMark(SQLiteSingleVerStorageExecutor *&handle, const std::vector &dataItems); + + // For db. + int TryToOpenMainDatabase(bool isWrite, sqlite3 *&db); + int GetCacheDbHandle(sqlite3 *&db); + int GetDbHandle(bool isWrite, const SecurityOption &secOpt, sqlite3 *&dbHandle); + int AttachMetaDatabase(sqlite3 *dbHandle, const OpenDbProperties &option) const; + int AttachMainDbAndCacheDb(SQLiteSingleVerStorageExecutor *handle, EngineState stateBeforeMigrate); + int AttachMainDbAndCacheDb(sqlite3 *db, EngineState stateBeforeMigrate) const; + void RegisterFunctionIfNeed(sqlite3 *dbHandle) const; + int TryAttachMetaDb(sqlite3 *&dbHandle, bool &isAttachMeta); + + // For secOpt. + int CreateNewDirsAndSetSecOpt() const; + int CheckDatabaseSecOpt(const SecurityOption &secOption) const; + int GetExistedSecOption(SecurityOption &secOption) const; + + void ClearCorruptedFlag() override; + + // For commit notify. + void CommitAndReleaseNotifyData(SingleVerNaturalStoreCommitNotifyData *&committedData, int eventType) const; + void InitConflictNotifiedFlag(SingleVerNaturalStoreCommitNotifyData *&committedData) const; + void CommitNotifyForMigrateCache(NotifyMigrateSyncData &syncData) const; + + // For subscribe + int AddSubscribeToMainDBInMigrate(); + + mutable std::mutex migrateLock_; + std::atomic cacheRecordVersion_; + ExecutorState executorState_; + bool isCorrupted_; + bool isNeedUpdateSecOpt_; // update the option_ + + std::mutex subscribeMutex_; + std::map subscribeQuery_; +}; +} // namespace DistributedDB + +#endif // SQLITE_SINGLE_VER_STORAGE_ENGINE_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor.cpp new file mode 100644 index 00000000..63d10262 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor.cpp @@ -0,0 +1,2168 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_single_ver_storage_executor.h" + +#include + +#include "log_print.h" +#include "db_constant.h" +#include "db_common.h" +#include "db_errno.h" +#include "parcel.h" +#include "platform_specific.h" +#include "runtime_context.h" +#include "sqlite_single_ver_storage_executor_sql.h" + +namespace DistributedDB { +namespace { +void InitCommitNotifyDataKeyStatus(SingleVerNaturalStoreCommitNotifyData *committedData, const Key &hashKey, + const DataOperStatus &dataStatus) +{ + if (committedData == nullptr) { + return; + } + + ExistStatus existedStatus = ExistStatus::NONE; + if (dataStatus.preStatus == DataStatus::DELETED) { + existedStatus = ExistStatus::DELETED; + } else if (dataStatus.preStatus == DataStatus::EXISTED) { + existedStatus = ExistStatus::EXIST; + } + + committedData->InitKeyPropRecord(hashKey, existedStatus); +} + +int ResetOrRegetStmt(sqlite3 *db, sqlite3_stmt *&stmt, const std::string &sql) +{ + int errCode = E_OK; + SQLiteUtils::ResetStatement(stmt, false, errCode); + if (errCode != E_OK) { + LOGE("[ResetOrRegetStmt] reset stmt failed:%d.", errCode); + // Finish current statement and remade one + SQLiteUtils::ResetStatement(stmt, true, errCode); + errCode = SQLiteUtils::GetStatement(db, sql, stmt); + if (errCode != E_OK) { + LOGE("[ResetOrRegetStmt] reget failed:%d.", errCode); + } + } + return errCode; +} +} + +SQLiteSingleVerStorageExecutor::SQLiteSingleVerStorageExecutor(sqlite3 *dbHandle, bool writable, bool isMemDb) + : SQLiteStorageExecutor(dbHandle, writable, isMemDb), + getSyncStatement_(nullptr), + getResultRowIdStatement_(nullptr), + getResultEntryStatement_(nullptr), + isTransactionOpen_(false), + attachMetaMode_(false), + executorState_(ExecutorState::INVALID), + maxTimestampInMainDB_(0), + migrateTimeOffset_(0), + isSyncMigrating_(false), + conflictResolvePolicy_(DEFAULT_LAST_WIN) +{} + +SQLiteSingleVerStorageExecutor::SQLiteSingleVerStorageExecutor(sqlite3 *dbHandle, bool writable, bool isMemDb, + ExecutorState executorState) + : SQLiteStorageExecutor(dbHandle, writable, isMemDb), + getSyncStatement_(nullptr), + getResultRowIdStatement_(nullptr), + getResultEntryStatement_(nullptr), + isTransactionOpen_(false), + attachMetaMode_(false), + executorState_(executorState), + maxTimestampInMainDB_(0), + migrateTimeOffset_(0), + isSyncMigrating_(false), + conflictResolvePolicy_(DEFAULT_LAST_WIN) +{} + +SQLiteSingleVerStorageExecutor::~SQLiteSingleVerStorageExecutor() +{ + if (isTransactionOpen_) { + Rollback(); + } + FinalizeAllStatements(); +} + +int SQLiteSingleVerStorageExecutor::GetKvData(SingleVerDataType type, const Key &key, Value &value, + Timestamp ×tamp) const +{ + std::string sql; + if (type == SingleVerDataType::LOCAL_TYPE) { + sql = SELECT_LOCAL_VALUE_TIMESTAMP_SQL; + } else if (type == SingleVerDataType::SYNC_TYPE) { + sql = SELECT_SYNC_VALUE_WTIMESTAMP_SQL; + } else if (type == SingleVerDataType::META_TYPE) { + if (attachMetaMode_) { + sql = SELECT_ATTACH_META_VALUE_SQL; + } else { + sql = SELECT_META_VALUE_SQL; + } + } else { + return -E_INVALID_ARGS; + } + + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, key, false); // first arg. + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = -E_NOT_FOUND; + goto END; + } else if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + goto END; + } + + errCode = SQLiteUtils::GetColumnBlobValue(statement, 0, value); // only one result. + + // get timestamp + if (type == SingleVerDataType::LOCAL_TYPE) { + timestamp = static_cast(sqlite3_column_int64(statement, GET_KV_RES_LOCAL_TIME_INDEX)); + } else if (type == SingleVerDataType::SYNC_TYPE) { + timestamp = static_cast(sqlite3_column_int64(statement, GET_KV_RES_SYNC_TIME_INDEX)); + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::BindPutKvData(sqlite3_stmt *statement, const Key &key, const Value &value, + Timestamp timestamp, SingleVerDataType type) +{ + int errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_KV_KEY_INDEX, key, false); + if (errCode != E_OK) { + LOGE("[SingleVerExe][BindPutKv]Bind key error:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_KV_VAL_INDEX, value, true); + if (errCode != E_OK) { + LOGE("[SingleVerExe][BindPutKv]Bind value error:%d", errCode); + return errCode; + } + + if (type == SingleVerDataType::LOCAL_TYPE) { + Key hashKey; + errCode = DBCommon::CalcValueHash(key, hashKey); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_LOCAL_HASH_KEY_INDEX, hashKey, false); + if (errCode != E_OK) { + LOGE("[SingleVerExe][BindPutKv]Bind hash key error:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindInt64ToStatement(statement, BIND_LOCAL_TIMESTAMP_INDEX, timestamp); + if (errCode != E_OK) { + LOGE("[SingleVerExe][BindPutKv]Bind timestamp error:%d", errCode); + return errCode; + } + } + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::GetKvDataByHashKey(const Key &hashKey, SingleVerRecord &result) const +{ + sqlite3_stmt *statement = nullptr; + std::vector devVect; + std::vector origDevVect; + int errCode = SQLiteUtils::GetStatement(dbHandle_, SELECT_SYNC_HASH_SQL, statement); + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, hashKey, false); // bind the first arg hashkey. + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + result.hashKey = hashKey; + result.timestamp = static_cast(sqlite3_column_int64(statement, SYNC_RES_TIME_INDEX)); + result.writeTimestamp = static_cast(sqlite3_column_int64(statement, SYNC_RES_W_TIME_INDEX)); + result.flag = static_cast(sqlite3_column_int64(statement, SYNC_RES_FLAG_INDEX)); + // get key + errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_KEY_INDEX, result.key); + if (errCode != E_OK) { + goto END; + } + // get value + errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_VAL_INDEX, result.value); + if (errCode != E_OK) { + goto END; + } + // get device + errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_DEVICE_INDEX, devVect); + if (errCode != E_OK) { + goto END; + } + result.device = std::string(devVect.begin(), devVect.end()); + // get original device + errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_ORI_DEV_INDEX, origDevVect); + if (errCode != E_OK) { + goto END; + } + result.origDevice = std::string(origDevVect.begin(), origDevVect.end()); + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = -E_NOT_FOUND; + goto END; + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::SaveKvData(SingleVerDataType type, const Key &key, const Value &value, + Timestamp timestamp) +{ + sqlite3_stmt *statement = nullptr; + std::string sql; + if (type == SingleVerDataType::LOCAL_TYPE) { + sql = (executorState_ == ExecutorState::CACHE_ATTACH_MAIN ? INSERT_LOCAL_SQL_FROM_CACHEHANDLE : + INSERT_LOCAL_SQL); + } else { + sql = (attachMetaMode_ ? INSERT_ATTACH_META_SQL : INSERT_META_SQL); + } + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = BindPutKvData(statement, key, value, timestamp, type); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } + +ERROR: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::PutKvData(SingleVerDataType type, const Key &key, const Value &value, + Timestamp timestamp, SingleVerNaturalStoreCommitNotifyData *committedData) +{ + if (type != SingleVerDataType::LOCAL_TYPE && type != SingleVerDataType::META_TYPE) { + return -E_INVALID_ARGS; + } + // committedData is only for local data, not for meta data. + bool isLocal = (SingleVerDataType::LOCAL_TYPE == type); + Timestamp localTimestamp = 0; + Value readValue; + bool isExisted = CheckIfKeyExisted(key, isLocal, readValue, localTimestamp); + if (isLocal && committedData != nullptr) { + ExistStatus existedStatus = isExisted ? ExistStatus::EXIST : ExistStatus::NONE; + Key hashKey; + int innerErrCode = DBCommon::CalcValueHash(key, hashKey); + if (innerErrCode != E_OK) { + return innerErrCode; + } + committedData->InitKeyPropRecord(hashKey, existedStatus); + } + int errCode = SaveKvData(type, key, value, timestamp); + if (errCode != E_OK) { + return errCode; + } + + if (isLocal && committedData != nullptr) { + Entry entry = {key, value}; + committedData->InsertCommittedData(std::move(entry), isExisted ? DataType::UPDATE : DataType::INSERT, true); + } + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::GetEntries(SingleVerDataType type, const Key &keyPrefix, + std::vector &entries) const +{ + if ((type != SingleVerDataType::LOCAL_TYPE) && (type != SingleVerDataType::SYNC_TYPE)) { + return -E_INVALID_ARGS; + } + + std::string sql = (type == SingleVerDataType::SYNC_TYPE) ? SELECT_SYNC_PREFIX_SQL : SELECT_LOCAL_PREFIX_SQL; + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + goto END; + } + + // bind the prefix key for the first and second args. + errCode = SQLiteUtils::BindPrefixKey(statement, 1, keyPrefix); // first argument is key + if (errCode != E_OK) { + goto END; + } + + errCode = StepForResultEntries(statement, entries); + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::GetEntries(QueryObject &queryObj, std::vector &entries) const +{ + int errCode = E_OK; + SqliteQueryHelper helper = queryObj.GetQueryHelper(errCode); + if (errCode != E_OK) { + return errCode; + } + + sqlite3_stmt *statement = nullptr; + errCode = helper.GetQuerySqlStatement(dbHandle_, false, statement); + if (errCode == E_OK) { + errCode = StepForResultEntries(statement, entries); + } + + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::GetCount(QueryObject &queryObj, int &count) const +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = E_OK; + SqliteQueryHelper helper = queryObj.GetQueryHelper(errCode); + if (errCode != E_OK) { + return errCode; + } + + if (!queryObj.IsCountValid()) { + LOGE("GetCount no need limit or orderby"); + return -E_INVALID_QUERY_FORMAT; + } + + std::string countSql; + errCode = helper.GetCountQuerySql(countSql); + if (errCode != E_OK) { + return errCode; + } + + sqlite3_stmt *countStatement = nullptr; + // get statement for count + errCode = helper.GetQuerySqlStatement(dbHandle_, countSql, countStatement); + if (errCode != E_OK) { + LOGE("Get count bind statement error:%d", errCode); + goto END; + } + // get count value + errCode = SQLiteUtils::StepWithRetry(countStatement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + uint64_t readCount = static_cast(sqlite3_column_int64(countStatement, 0)); + if (readCount > INT32_MAX) { + LOGW("total count is beyond the max count"); + count = 0; + errCode = -E_UNEXPECTED_DATA; + } else { + count = static_cast(readCount); + errCode = E_OK; + } + LOGD("Entry count in this result set is %d", count); + } else { + errCode = -E_UNEXPECTED_DATA; + } + +END: + SQLiteUtils::ResetStatement(countStatement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +void SQLiteSingleVerStorageExecutor::InitCurrentMaxStamp(Timestamp &maxStamp) +{ + if (dbHandle_ == nullptr) { + return; + } + std::string sql = ((executorState_ == ExecutorState::CACHE_ATTACH_MAIN) ? + SELECT_MAX_TIMESTAMP_SQL_FROM_CACHEHANDLE : SELECT_MAX_TIMESTAMP_SQL); + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + return; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + maxStamp = static_cast(sqlite3_column_int64(statement, 0)); // get the first column + } + SQLiteUtils::ResetStatement(statement, true, errCode); +} + +int SQLiteSingleVerStorageExecutor::PrepareForSyncDataByTime(Timestamp begin, Timestamp end, + sqlite3_stmt *&statement, bool getDeletedData) const +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + + const std::string sql = (getDeletedData ? SELECT_SYNC_DELETED_ENTRIES_SQL : SELECT_SYNC_ENTRIES_SQL); + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + LOGE("Prepare the sync entries statement error:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindInt64ToStatement(statement, BIND_BEGIN_STAMP_INDEX, begin); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = SQLiteUtils::BindInt64ToStatement(statement, BIND_END_STAMP_INDEX, end); + +ERROR: + if (errCode != E_OK) { + LOGE("Bind the timestamp for getting sync data error:%d", errCode); + SQLiteUtils::ResetStatement(statement, true, errCode); + } + + return CheckCorruptedStatus(errCode); +} + +void SQLiteSingleVerStorageExecutor::ReleaseContinueStatement() +{ + if (getSyncStatement_ != nullptr) { + int errCode = E_OK; + SQLiteUtils::ResetStatement(getSyncStatement_, true, errCode); + if (errCode == -E_INVALID_PASSWD_OR_CORRUPTED_DB) { + SetCorruptedStatus(); + } + } +} + +namespace { +int GetDataItemForSync(sqlite3_stmt *statement, DataItem &dataItem) +{ + dataItem.timestamp = static_cast(sqlite3_column_int64(statement, SYNC_RES_TIME_INDEX)); + dataItem.writeTimestamp = static_cast(sqlite3_column_int64(statement, SYNC_RES_W_TIME_INDEX)); + dataItem.flag = static_cast(sqlite3_column_int64(statement, SYNC_RES_FLAG_INDEX)); + dataItem.flag &= (~DataItem::LOCAL_FLAG); + std::vector devVect; + int errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_ORI_DEV_INDEX, devVect); + if (errCode != E_OK) { + return errCode; + } + dataItem.origDev = std::string(devVect.begin(), devVect.end()); + int keyIndex = SYNC_RES_KEY_INDEX; + // If the data has been deleted, just use the hash key for sync. + if ((dataItem.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG) { + keyIndex = SYNC_RES_HASH_KEY_INDEX; + } + errCode = SQLiteUtils::GetColumnBlobValue(statement, keyIndex, dataItem.key); + if (errCode != E_OK) { + return errCode; + } + return SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_VAL_INDEX, dataItem.value); +} +} + +int SQLiteSingleVerStorageExecutor::GetSyncDataItems(std::vector &dataItems, sqlite3_stmt *statement, + size_t appendLength, const DataSizeSpecInfo &dataSizeInfo) const +{ + int errCode; + size_t dataTotalSize = 0; + + do { + DataItem dataItem; + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + errCode = GetDataItemForSync(statement, dataItem); + if (errCode != E_OK) { + LOGE("GetDataItemForSync failed:%d", errCode); + return errCode; + } + } else { + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + LOGD("Get sync data finished, size of packet:%zu, number of item:%zu", dataTotalSize, dataItems.size()); + errCode = -E_FINISHED; + } else { + LOGE("Get sync data error:%d", errCode); + } + break; + } + + // If dataTotalSize value is bigger than blockSize value , reserve the surplus data item. + dataTotalSize += GetDataItemSerialSize(dataItem, appendLength); + if ((dataTotalSize > dataSizeInfo.blockSize && !dataItems.empty()) || + dataItems.size() >= dataSizeInfo.packetSize) { + errCode = -E_UNFINISHED; + break; + } else { + dataItems.push_back(std::move(dataItem)); + } + } while (true); + return errCode; +} + +int SQLiteSingleVerStorageExecutor::GetSyncDataByTimestamp(std::vector &dataItems, size_t appendLength, + Timestamp begin, Timestamp end, const DataSizeSpecInfo &dataSizeInfo) const +{ + sqlite3_stmt *statement = nullptr; + int errCode = PrepareForSyncDataByTime(begin, end, statement); + if (errCode != E_OK) { + return errCode; + } + + errCode = GetSyncDataItems(dataItems, statement, appendLength, dataSizeInfo); + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::GetDeletedSyncDataByTimestamp(std::vector &dataItems, size_t appendLength, + Timestamp begin, Timestamp end, const DataSizeSpecInfo &dataSizeInfo) const +{ + sqlite3_stmt *statement = nullptr; + int errCode = PrepareForSyncDataByTime(begin, end, statement, true); + if (errCode != E_OK) { + return errCode; + } + + errCode = GetSyncDataItems(dataItems, statement, appendLength, dataSizeInfo); + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +namespace { +int AppendDataItem(std::vector &dataItems, const DataItem &item, size_t &dataTotalSize, size_t appendLength, + const DataSizeSpecInfo &dataSizeInfo) +{ + // If dataTotalSize value is bigger than blockSize value , reserve the surplus data item. + size_t appendSize = dataTotalSize + SQLiteSingleVerStorageExecutor::GetDataItemSerialSize(item, appendLength); + if ((appendSize > dataSizeInfo.blockSize && !dataItems.empty()) || dataItems.size() >= dataSizeInfo.packetSize) { + return -E_UNFINISHED; + } + dataItems.push_back(item); + dataTotalSize = appendSize; + return E_OK; +} + +int GetFullDataStatement(sqlite3 *db, const std::pair &timeRange, sqlite3_stmt *&stmt) +{ + int errCode = SQLiteUtils::GetStatement(db, SELECT_SYNC_MODIFY_SQL, stmt); + if (errCode != E_OK) { + LOGE("Get statement failed. %d", errCode); + return errCode; + } + errCode = SQLiteUtils::BindInt64ToStatement(stmt, 1, timeRange.first); // 1 : Bind time rang index start + if (errCode != E_OK) { + LOGE("Bind time range to statement failed. %d", errCode); + goto ERR; + } + errCode = SQLiteUtils::BindInt64ToStatement(stmt, 2, timeRange.second); // 2 : Bind time rang index end + if (errCode != E_OK) { + LOGE("Bind time range to statement failed. %d", errCode); + goto ERR; + } + return E_OK; // do not release statement when success +ERR: + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; +} + +int GetQueryDataStatement(sqlite3 *db, QueryObject query, const std::pair &timeRange, + sqlite3_stmt *&stmt) +{ + int errCode = E_OK; + SqliteQueryHelper helper = query.GetQueryHelper(errCode); + if (errCode != E_OK) { + return errCode; + } + return helper.GetQuerySyncStatement(db, timeRange.first, timeRange.second, stmt); +} + +int GetNextDataItem(sqlite3_stmt *stmt, bool isMemDB, DataItem &item) +{ + int errCode = SQLiteUtils::StepWithRetry(stmt, isMemDB); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + errCode = GetDataItemForSync(stmt, item); + } + return errCode; +} +} + +int SQLiteSingleVerStorageExecutor::GetSyncDataWithQuery(const QueryObject &query, size_t appendLength, + const DataSizeSpecInfo &dataSizeInfo, const std::pair &timeRange, + std::vector &dataItems) const +{ + sqlite3_stmt *fullStmt = nullptr; // statement for get all modified data in the time range + sqlite3_stmt *queryStmt = nullptr; // statement for get modified data which is matched query in the time range + int errCode = GetQueryDataStatement(dbHandle_, query, timeRange, queryStmt); + if (errCode != E_OK) { + LOGE("Get query matched data statement failed. %d", errCode); + goto END; + } + if (query.IsQueryOnlyByKey()) { + // Query sync by prefixKey only should not deal with REMOTE_DEVICE_DATA_MISS_QUERY. Get the data directly. + errCode = GetSyncDataItems(dataItems, queryStmt, appendLength, dataSizeInfo); + goto END; + } + errCode = GetFullDataStatement(dbHandle_, timeRange, fullStmt); + if (errCode != E_OK) { + LOGE("Get full changed data statement failed. %d", errCode); + goto END; + } + errCode = GetSyncDataWithQuery(fullStmt, queryStmt, appendLength, dataSizeInfo, dataItems); + if (errCode != E_OK && errCode != -E_UNFINISHED && errCode != -E_FINISHED) { + LOGE("Get sync data with query failed. %d", errCode); + } +END: + SQLiteUtils::ResetStatement(fullStmt, true, errCode); + SQLiteUtils::ResetStatement(queryStmt, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::GetSyncDataWithQuery(sqlite3_stmt *fullStmt, sqlite3_stmt *queryStmt, + size_t appendLength, const DataSizeSpecInfo &dataSizeInfo, std::vector &dataItems) const +{ + int errCode = E_OK; + size_t dataTotalSize = 0; + DataItem fullItem; + DataItem matchItem; + bool isFullItemFinished = false; + bool isMatchItemFinished = false; + while (!isFullItemFinished || !isMatchItemFinished) { + errCode = GetNextDataItem(queryStmt, isMemDb_, matchItem); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { // query finished + isMatchItemFinished = true; + } else if (errCode != E_OK) { // step failed or get data failed + LOGE("Get next query matched data failed. %d", errCode); + return errCode; + } + while (!isFullItemFinished) { + errCode = GetNextDataItem(fullStmt, isMemDb_, fullItem); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { // queryStmt is a subset of fullStmt + isFullItemFinished = true; + break; + } else if (errCode != E_OK) { // step failed or get data failed + LOGE("Get next changed data failed. %d", errCode); + return errCode; + } + bool matchData = true; + if (isMatchItemFinished || matchItem.key != fullItem.key) { + matchData = false; // got miss query data + DBCommon::CalcValueHash(fullItem.key, fullItem.key); // set and send key with hash_key + Value().swap(fullItem.value); // not send value when data miss query + fullItem.flag |= DataItem::REMOTE_DEVICE_DATA_MISS_QUERY; // mark with miss query flag + } + errCode = AppendDataItem(dataItems, fullItem, dataTotalSize, appendLength, dataSizeInfo); + if (errCode == -E_UNFINISHED) { + goto END; + } + if (matchData) { + break; // step to next match data + } + } + } +END: + LOGD("Get sync data finished, size of packet:%zu, number of item:%zu", dataTotalSize, dataItems.size()); + return (isFullItemFinished && isMatchItemFinished) ? -E_FINISHED : errCode; +} + +int SQLiteSingleVerStorageExecutor::OpenResultSet(const Key &keyPrefix, int &count) +{ + sqlite3_stmt *countStatement = nullptr; + if (InitResultSet(keyPrefix, countStatement) != E_OK) { + LOGE("Initialize result set stat failed."); + return -E_INVALID_DB; + } + + int errCode = StartTransaction(TransactType::DEFERRED); + if (errCode != E_OK) { + goto END; + } + + // get count value + errCode = SQLiteUtils::StepWithRetry(countStatement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + uint64_t readCount = static_cast(sqlite3_column_int64(countStatement, 0)); + if (readCount > INT32_MAX) { + LOGW("total count is beyond the max count"); + count = 0; + errCode = -E_UNEXPECTED_DATA; + } else { + count = static_cast(readCount); + errCode = E_OK; + } + LOGD("Entry count in this result set is %d", count); + } else { + errCode = -E_UNEXPECTED_DATA; + } + +END: + SQLiteUtils::ResetStatement(countStatement, true, errCode); + if (errCode != E_OK) { + CloseResultSet(); + } + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::OpenResultSet(QueryObject &queryObj, int &count) +{ + sqlite3_stmt *countStatement = nullptr; + int errCode = InitResultSet(queryObj, countStatement); + if (errCode != E_OK) { + LOGE("Initialize result set stat failed."); + return errCode; + } + + errCode = StartTransaction(TransactType::DEFERRED); + if (errCode != E_OK) { + goto END; + } + + // get count value + errCode = SQLiteUtils::StepWithRetry(countStatement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + uint64_t readCount = static_cast(sqlite3_column_int64(countStatement, 0)); + if (queryObj.HasLimit()) { + int limit = 0; + int offset = 0; + queryObj.GetLimitVal(limit, offset); + offset = (offset < 0) ? 0 : offset; + limit = (limit < 0) ? 0 : limit; + if (readCount <= static_cast(offset)) { + readCount = 0; + } else { + readCount = std::min(readCount - offset, static_cast(limit)); + } + } + + if (readCount > INT32_MAX) { + LOGW("total count is beyond the max count"); + count = 0; + errCode = -E_UNEXPECTED_DATA; + } else { + count = static_cast(readCount); + errCode = E_OK; + } + LOGD("Entry count in this result set is %d", count); + } else { + errCode = -E_UNEXPECTED_DATA; + } + +END: + SQLiteUtils::ResetStatement(countStatement, true, errCode); + if (errCode != E_OK) { + CloseResultSet(); + } + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::OpenResultSetForCacheRowIdMode(const Key &keyPrefix, + std::vector &rowIdCache, uint32_t cacheLimit, int &count) +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = SQLiteUtils::GetStatement(dbHandle_, SELECT_SYNC_ROWID_PREFIX_SQL, getResultRowIdStatement_); + if (errCode != E_OK) { + LOGE("[SqlSinExe][OpenResSetRowId][PrefixKey] Get rowId stmt fail, errCode=%d", errCode); + return CheckCorruptedStatus(errCode); + } + errCode = SQLiteUtils::BindPrefixKey(getResultRowIdStatement_, 1, keyPrefix); // first argument index is 1 + if (errCode != E_OK) { + LOGE("[SqlSinExe][OpenResSetRowId][PrefixKey] Bind rowid stmt fail, errCode=%d", errCode); + SQLiteUtils::ResetStatement(getResultRowIdStatement_, true, errCode); + return CheckCorruptedStatus(errCode); + } + errCode = OpenResultSetForCacheRowIdModeCommon(rowIdCache, cacheLimit, count); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(getResultRowIdStatement_, true, errCode); + } + return errCode; +} + +int SQLiteSingleVerStorageExecutor::OpenResultSetForCacheRowIdMode(QueryObject &queryObj, + std::vector &rowIdCache, uint32_t cacheLimit, int &count) +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = E_OK; + SqliteQueryHelper helper = queryObj.GetQueryHelper(errCode); + if (errCode != E_OK) { + return errCode; + } + + if (!queryObj.IsValid()) { + LOGE("[SqlSinExe][OpenResSetRowId][Query] query object not Valid"); + return -E_INVALID_QUERY_FORMAT; + } + + errCode = helper.GetQuerySqlStatement(dbHandle_, true, getResultRowIdStatement_); + if (errCode != E_OK) { + LOGE("[SqlSinExe][OpenResSetRowId][Query] Get Stmt fail, errCode=%d", errCode); + // The GetQuerySqlStatement does not self rollback(BAD...), so we have to reset the stmt here. + SQLiteUtils::ResetStatement(getResultRowIdStatement_, true, errCode); + return errCode; + } + errCode = OpenResultSetForCacheRowIdModeCommon(rowIdCache, cacheLimit, count); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(getResultRowIdStatement_, true, errCode); + } + return errCode; +} + +int SQLiteSingleVerStorageExecutor::ReloadResultSet(const Key &keyPrefix) +{ + int errCode = ResetOrRegetStmt(dbHandle_, getResultRowIdStatement_, SELECT_SYNC_ROWID_PREFIX_SQL); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + + // No need to reset getResultEntryStatement_. Because the binding of it will be cleared in each get operation + errCode = SQLiteUtils::BindPrefixKey(getResultRowIdStatement_, 1, keyPrefix); // first argument is key + if (errCode != E_OK) { + LOGE("Rebind result set rowid statement of keyPrefix error:%d", errCode); + return CheckCorruptedStatus(errCode); + } + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::ReloadResultSet(QueryObject &queryObj) +{ + int errCode = E_OK; + SqliteQueryHelper helper = queryObj.GetQueryHelper(errCode); + if (errCode != E_OK) { + return errCode; + } + + if (!queryObj.IsValid()) { + return -E_INVALID_QUERY_FORMAT; + } + + std::string sql; + errCode = helper.GetQuerySql(sql, true); // only rowid sql + if (errCode != E_OK) { + return errCode; + } + + errCode = ResetOrRegetStmt(dbHandle_, getResultRowIdStatement_, sql); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + + // No need to reset getResultEntryStatement_. Because the binding of it will be cleared in each get operation + // GetQuerySqlStatement will not alter getResultRowIdStatement_ if it is not null + errCode = helper.GetQuerySqlStatement(dbHandle_, true, getResultRowIdStatement_); + if (errCode != E_OK) { + LOGE("Rebind result set rowid statement of query error:%d", errCode); + return CheckCorruptedStatus(errCode); + } + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::ReloadResultSetForCacheRowIdMode(const Key &keyPrefix, + std::vector &rowIdCache, uint32_t cacheLimit, uint32_t cacheStartPos) +{ + int errCode = ReloadResultSet(keyPrefix); // Reuse this function(A convenience) + if (errCode != E_OK) { + return errCode; + } + int count = 0; // Ignored + errCode = ResultSetLoadRowIdCache(rowIdCache, cacheLimit, cacheStartPos, count); + if (errCode != E_OK) { + LOGE("[SqlSinExe][ReloadResSet][KeyPrefix] Load fail, errCode=%d", errCode); + } + // We can just return, no need to reset the statement + return errCode; +} + +int SQLiteSingleVerStorageExecutor::ReloadResultSetForCacheRowIdMode(QueryObject &queryObj, + std::vector &rowIdCache, uint32_t cacheLimit, uint32_t cacheStartPos) +{ + int errCode = ReloadResultSet(queryObj); // Reuse this function(A convenience) + if (errCode != E_OK) { + return errCode; + } + int count = 0; // Ignored + errCode = ResultSetLoadRowIdCache(rowIdCache, cacheLimit, cacheStartPos, count); + if (errCode != E_OK) { + LOGE("[SqlSinExe][ReloadResSet][Query] Load fail, errCode=%d", errCode); + } + // We can just return, no need to reset the statement + return errCode; +} + +int SQLiteSingleVerStorageExecutor::GetNextEntryFromResultSet(Key &key, Value &value, bool isCopy) +{ + if (getResultRowIdStatement_ == nullptr || getResultEntryStatement_ == nullptr) { + return -E_RESULT_SET_STATUS_INVALID; + } + + int errCode = SQLiteUtils::StepWithRetry(getResultRowIdStatement_, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + if (!isCopy) { + return E_OK; + } + int64_t rowId = sqlite3_column_int64(getResultRowIdStatement_, 0); + errCode = E_OK; + SQLiteUtils::ResetStatement(getResultEntryStatement_, false, errCode); + if (errCode != E_OK) { + LOGE("[SqlSinExe][GetNext] Reset result set entry statement fail, errCode=%d.", errCode); + return CheckCorruptedStatus(errCode); + } + + SQLiteUtils::BindInt64ToStatement(getResultEntryStatement_, 1, rowId); + errCode = SQLiteUtils::StepWithRetry(getResultEntryStatement_, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + errCode = SQLiteUtils::GetColumnBlobValue(getResultEntryStatement_, 0, key); + if (errCode != E_OK) { + LOGE("[SqlSinExe][GetNext] Get key failed:%d", errCode); + return CheckCorruptedStatus(errCode); + } + errCode = SQLiteUtils::GetColumnBlobValue(getResultEntryStatement_, 1, value); + if (errCode != E_OK) { + LOGE("[SqlSinExe][GetNext] Get value failed:%d", errCode); + return CheckCorruptedStatus(errCode); + } + return E_OK; + } else { + return -E_UNEXPECTED_DATA; + } + } + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + return -E_FINISHED; + } + + LOGE("SQLite step failed:%d", errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::GetEntryByRowId(int64_t rowId, Entry &entry) +{ + if (getResultEntryStatement_ == nullptr) { + return -E_RESULT_SET_STATUS_INVALID; + } + int errCode = E_OK; + SQLiteUtils::ResetStatement(getResultEntryStatement_, false, errCode); + if (errCode != E_OK) { + LOGE("[SqlSinExe][GetEntryByRowid] Reset result set entry statement fail, errCode=%d.", errCode); + return CheckCorruptedStatus(errCode); + } + SQLiteUtils::BindInt64ToStatement(getResultEntryStatement_, 1, rowId); + errCode = SQLiteUtils::StepWithRetry(getResultEntryStatement_, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + errCode = SQLiteUtils::GetColumnBlobValue(getResultEntryStatement_, 0, entry.key); + if (errCode != E_OK) { + LOGE("[SqlSinExe][GetEntryByRowid] Get key failed, errCode=%d.", errCode); + return CheckCorruptedStatus(errCode); + } + errCode = SQLiteUtils::GetColumnBlobValue(getResultEntryStatement_, 1, entry.value); + if (errCode != E_OK) { + LOGE("[SqlSinExe][GetEntryByRowid] Get value failed, errCode=%d.", errCode); + return CheckCorruptedStatus(errCode); + } + return E_OK; + } else { + LOGE("[SqlSinExe][GetEntryByRowid] Step failed, errCode=%d.", errCode); + return -E_UNEXPECTED_DATA; + } +} + +void SQLiteSingleVerStorageExecutor::CloseResultSet() +{ + int errCode = E_OK; + SQLiteUtils::ResetStatement(getResultRowIdStatement_, true, errCode); + if (errCode == -E_INVALID_PASSWD_OR_CORRUPTED_DB) { + SetCorruptedStatus(); + } + SQLiteUtils::ResetStatement(getResultEntryStatement_, true, errCode); + if (errCode == -E_INVALID_PASSWD_OR_CORRUPTED_DB) { + SetCorruptedStatus(); + } + if (isTransactionOpen_) { + SQLiteUtils::RollbackTransaction(dbHandle_); + isTransactionOpen_ = false; + } +} + +int SQLiteSingleVerStorageExecutor::StartTransaction(TransactType type) +{ + if (dbHandle_ == nullptr) { + LOGE("Begin transaction failed, dbHandle is null."); + return -E_INVALID_DB; + } + int errCode = SQLiteUtils::BeginTransaction(dbHandle_, type); + if (errCode == E_OK) { + isTransactionOpen_ = true; + } else { + LOGE("Begin transaction failed, errCode = %d", errCode); + } + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::Commit() +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = SQLiteUtils::CommitTransaction(dbHandle_); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + isTransactionOpen_ = false; + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::Rollback() +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + int errCode = SQLiteUtils::RollbackTransaction(dbHandle_); + if (errCode != E_OK) { + LOGE("sqlite single ver storage executor rollback fail! errCode = [%d]", errCode); + return CheckCorruptedStatus(errCode); + } + isTransactionOpen_ = false; + return E_OK; +} + +bool SQLiteSingleVerStorageExecutor::CheckIfKeyExisted(const Key &key, bool isLocal, + Value &value, Timestamp ×tamp) const +{ + // not local value, no need to get the value. + if (!isLocal) { + return false; + } + + int errCode = GetKvData(SingleVerDataType::LOCAL_TYPE, key, value, timestamp); + if (errCode != E_OK) { + return false; + } + return true; +} + +int SQLiteSingleVerStorageExecutor::GetDeviceIdentifier(PragmaEntryDeviceIdentifier *identifier) +{ + if (identifier == nullptr) { + return -E_INVALID_ARGS; + } + + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, SELECT_ENTRY_DEVICE, statement); + if (errCode != E_OK) { + return errCode; + } + + int keyIndex = identifier->origDevice ? BIND_ORI_DEVICE_ID : BIND_PRE_DEVICE_ID; + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_KV_KEY_INDEX, identifier->key, false); + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + std::vector deviceId; + errCode = SQLiteUtils::GetColumnBlobValue(statement, keyIndex, deviceId); + identifier->deviceIdentifier.assign(deviceId.begin(), deviceId.end()); + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = -E_NOT_FOUND; + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +void SQLiteSingleVerStorageExecutor::PutIntoCommittedData(const DataItem &itemPut, const DataItem &itemGet, + const DataOperStatus &status, const Key &hashKey, SingleVerNaturalStoreCommitNotifyData *committedData) +{ + if (committedData == nullptr) { + return; + } + + Entry entry; + int errCode; + if (!status.isDeleted) { + entry.key = itemPut.key; + entry.value = itemPut.value; + DataType dataType = (status.preStatus == DataStatus::EXISTED) ? DataType::UPDATE : DataType::INSERT; + errCode = committedData->InsertCommittedData(std::move(entry), dataType, true); + } else { + entry.key = itemGet.key; + entry.value = itemGet.value; + errCode = committedData->InsertCommittedData(std::move(entry), DataType::DELETE, true); + } + + if (errCode != E_OK) { + LOGE("[SingleVerExe][PutCommitData]Insert failed:%d", errCode); + } +} + +int SQLiteSingleVerStorageExecutor::PrepareForSavingData(const std::string &readSql, const std::string &insertSql, + const std::string &updateSql, SaveRecordStatements &statements) const +{ + int errCode = SQLiteUtils::GetStatement(dbHandle_, readSql, statements.queryStatement); + if (errCode != E_OK) { + LOGE("Get query statement failed. errCode = [%d]", errCode); + goto ERR; + } + + errCode = SQLiteUtils::GetStatement(dbHandle_, insertSql, statements.insertStatement); + if (errCode != E_OK) { + LOGE("Get insert statement failed. errCode = [%d]", errCode); + goto ERR; + } + + errCode = SQLiteUtils::GetStatement(dbHandle_, updateSql, statements.updateStatement); + if (errCode != E_OK) { + LOGE("Get update statement failed. errCode = [%d]", errCode); + goto ERR; + } + return E_OK; +ERR: + (void)statements.ResetStatement(); + return errCode; +} + +int SQLiteSingleVerStorageExecutor::PrepareForSavingData(SingleVerDataType type) +{ + int errCode = -E_NOT_SUPPORT; + if (type == SingleVerDataType::LOCAL_TYPE) { + // currently, Local type has not been optimized, so pass updateSql parameter with INSERT_LOCAL_SQL + errCode = PrepareForSavingData(SELECT_LOCAL_HASH_SQL, INSERT_LOCAL_SQL, INSERT_LOCAL_SQL, saveLocalStatements_); + } else if (type == SingleVerDataType::SYNC_TYPE) { + errCode = PrepareForSavingData(SELECT_SYNC_HASH_SQL, INSERT_SYNC_SQL, UPDATE_SYNC_SQL, saveSyncStatements_); + } + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::ResetForSavingData(SingleVerDataType type) +{ + int errCode = E_OK; + if (type == SingleVerDataType::LOCAL_TYPE) { + SQLiteUtils::ResetStatement(saveLocalStatements_.insertStatement, false, errCode); + SQLiteUtils::ResetStatement(saveLocalStatements_.updateStatement, false, errCode); + SQLiteUtils::ResetStatement(saveLocalStatements_.queryStatement, false, errCode); + } else if (type == SingleVerDataType::SYNC_TYPE) { + SQLiteUtils::ResetStatement(saveSyncStatements_.insertStatement, false, errCode); + SQLiteUtils::ResetStatement(saveSyncStatements_.updateStatement, false, errCode); + SQLiteUtils::ResetStatement(saveSyncStatements_.queryStatement, false, errCode); + } + return CheckCorruptedStatus(errCode); +} + +std::string SQLiteSingleVerStorageExecutor::GetOriginDevName(const DataItem &dataItem, + const std::string &origDevGet) +{ + if (((dataItem.flag & DataItem::LOCAL_FLAG) != 0) && dataItem.origDev.empty()) { + return origDevGet; + } + return dataItem.origDev; +} + +int SQLiteSingleVerStorageExecutor::SaveSyncDataToDatabase(const DataItem &dataItem, const Key &hashKey, + const std::string &origDev, const std::string &deviceName, bool isUpdate) +{ + if ((dataItem.flag & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) == DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) { + LOGD("Find query data missing, erase local data."); + return EraseSyncData(hashKey); + } + auto statement = saveSyncStatements_.GetDataSaveStatement(isUpdate); + if (statement == nullptr) { + return -E_INVALID_ARGS; + } + + std::string devName = DBCommon::TransferHashString(deviceName); + int errCode = BindSavedSyncData(statement, dataItem, hashKey, {origDev, devName}, isUpdate); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } + return errCode; +} + +DataOperStatus SQLiteSingleVerStorageExecutor::JudgeSyncSaveType(DataItem &dataItem, + const DataItem &itemGet, const std::string &devName, bool isHashKeyExisted, bool isPermitForceWrite) +{ + DataOperStatus status; + status.isDeleted = ((dataItem.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG || + (dataItem.flag & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) == DataItem::REMOTE_DEVICE_DATA_MISS_QUERY); + if (isHashKeyExisted) { + if ((itemGet.flag & DataItem::DELETE_FLAG) != 0) { + status.preStatus = DataStatus::DELETED; + } else { + status.preStatus = DataStatus::EXISTED; + } + std::string deviceName = DBCommon::TransferHashString(devName); + if (itemGet.writeTimestamp >= dataItem.writeTimestamp) { + // for multi user mode, no permit to forcewrite + if ((!deviceName.empty()) && (itemGet.dev == deviceName) && isPermitForceWrite) { + LOGI("Force overwrite the data:%" PRIu64 " vs %" PRIu64, + itemGet.writeTimestamp, dataItem.writeTimestamp); + status.isDefeated = false; + dataItem.writeTimestamp = itemGet.writeTimestamp + 1; + dataItem.timestamp = itemGet.timestamp; + } else { + status.isDefeated = true; + } + } + } + return status; +} + +int SQLiteSingleVerStorageExecutor::GetSyncDataItemExt(const DataItem &dataItem, DataItem &itemGet, + const DataOperStatus &dataStatus) const +{ + if (dataStatus.preStatus != DataStatus::EXISTED) { + return E_OK; + } + auto statement = isSyncMigrating_ ? migrateSyncStatements_.queryStatement : saveSyncStatements_.queryStatement; + // only deleted item need origin value. + int errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_KEY_INDEX, itemGet.key); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_VAL_INDEX, itemGet.value); + if (errCode != E_OK) { + LOGE("Get column value data failed:%d", errCode); + } + + return errCode; +} + +int SQLiteSingleVerStorageExecutor::ResetSaveSyncStatements(int errCode) +{ + SQLiteUtils::ResetStatement(saveSyncStatements_.insertStatement, false, errCode); + SQLiteUtils::ResetStatement(saveSyncStatements_.updateStatement, false, errCode); + SQLiteUtils::ResetStatement(saveSyncStatements_.queryStatement, false, errCode); + return CheckCorruptedStatus(errCode); +} + +namespace { + inline bool IsNeedIgnoredData(const DataItem &itemPut, const DataItem &itemGet, + const DeviceInfo &devInfo, bool isHashKeyExisted, int policy) + { + // deny the data synced from other dev which the origin dev is current or the existed value is current dev data. + return (((itemGet.origDev.empty() && isHashKeyExisted) || itemPut.origDev.empty()) && + (!devInfo.isLocal && policy == DENY_OTHER_DEV_AMEND_CUR_DEV_DATA)); + } +} + +int SQLiteSingleVerStorageExecutor::PrepareForNotifyConflictAndObserver(DataItem &dataItem, + const DeviceInfo &deviceInfo, NotifyConflictAndObserverData ¬ify, bool isPermitForceWrite) +{ + // Check sava data existed info + int errCode = GetSyncDataItemPre(dataItem, notify.getData, notify.hashKey); + if (errCode != E_OK && errCode != -E_NOT_FOUND) { + LOGD("[SingleVerExe][PrepareForNotifyConflictAndObserver] failed:%d", errCode); + if (isSyncMigrating_) { + ResetForMigrateCacheData(); + return errCode; + } + return ResetSaveSyncStatements(errCode); + } + + bool isHashKeyExisted = (errCode != -E_NOT_FOUND); + if (IsNeedIgnoredData(dataItem, notify.getData, deviceInfo, isHashKeyExisted, conflictResolvePolicy_)) { + LOGD("[SingleVerExe] Ignore the sync data."); + if (isSyncMigrating_) { + ResetForMigrateCacheData(); + return -E_IGNORE_DATA; + } + return ResetSaveSyncStatements(-E_IGNORE_DATA); + } + + notify.dataStatus = JudgeSyncSaveType(dataItem, notify.getData, deviceInfo.deviceName, isHashKeyExisted, + isPermitForceWrite); + InitCommitNotifyDataKeyStatus(notify.committedData, notify.hashKey, notify.dataStatus); + + // Nonexistent data, but deleted by local. + if ((notify.dataStatus.preStatus == DataStatus::DELETED || notify.dataStatus.preStatus == DataStatus::NOEXISTED) && + (dataItem.flag & DataItem::DELETE_FLAG) != 0 && + (dataItem.flag & DataItem::LOCAL_FLAG) != 0) { + // For delete item in cacheDB, which not in mainDB. Cannot notify, but this is not error. + errCode = -E_NOT_FOUND; + LOGD("Nonexistent data, but deleted by local"); + if (isSyncMigrating_) { + ResetForMigrateCacheData(); + return errCode; + } + return ResetSaveSyncStatements(errCode); + } + + // get key and value from ori database + errCode = GetSyncDataItemExt(dataItem, notify.getData, notify.dataStatus); + if (errCode != E_OK) { + LOGD("GetSyncDataItemExt failed:%d", errCode); + if (isSyncMigrating_) { + ResetForMigrateCacheData(); + return errCode; + } + return ResetSaveSyncStatements(errCode); + } + + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::SaveSyncDataItem(DataItem &dataItem, const DeviceInfo &deviceInfo, + Timestamp &maxStamp, SingleVerNaturalStoreCommitNotifyData *committedData, bool isPermitForceWrite) +{ + NotifyConflictAndObserverData notify = { + .committedData = committedData + }; + + int errCode = PrepareForNotifyConflictAndObserver(dataItem, deviceInfo, notify, isPermitForceWrite); + if (errCode != E_OK) { + if (errCode == -E_IGNORE_DATA) { + errCode = E_OK; + } + return errCode; + } + + PutConflictData(dataItem, notify.getData, deviceInfo, notify.dataStatus, committedData); + if (notify.dataStatus.isDefeated) { + LOGD("Data status is defeated:%d", errCode); + return ResetSaveSyncStatements(errCode); + } + + bool isUpdate = (notify.dataStatus.preStatus != DataStatus::NOEXISTED); + std::string origDev = GetOriginDevName(dataItem, notify.getData.origDev); + errCode = SaveSyncDataToDatabase(dataItem, notify.hashKey, origDev, deviceInfo.deviceName, isUpdate); + if (errCode == E_OK) { + PutIntoCommittedData(dataItem, notify.getData, notify.dataStatus, notify.hashKey, committedData); + maxStamp = std::max(dataItem.timestamp, maxStamp); + } else { + LOGE("Save sync data to db failed:%d", errCode); + } + return ResetSaveSyncStatements(errCode); +} + +int SQLiteSingleVerStorageExecutor::GetAllMetaKeys(std::vector &keys) const +{ + sqlite3_stmt *statement = nullptr; + const std::string &sqlStr = (attachMetaMode_ ? SELECT_ATTACH_ALL_META_KEYS : SELECT_ALL_META_KEYS); + int errCode = SQLiteUtils::GetStatement(dbHandle_, sqlStr, statement); + if (errCode != E_OK) { + LOGE("[SingleVerExe][GetAllKey] Get statement failed:%d", errCode); + return errCode; + } + + errCode = GetAllKeys(statement, keys); + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteSingleVerStorageExecutor::GetAllSyncedEntries(const std::string &deviceName, + std::vector &entries) const +{ + sqlite3_stmt *statement = nullptr; + std::string sql = (executorState_ == ExecutorState::CACHE_ATTACH_MAIN ? + SELECT_ALL_SYNC_ENTRIES_BY_DEV_FROM_CACHEHANDLE : SELECT_ALL_SYNC_ENTRIES_BY_DEV); + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + LOGE("Get all entries statement failed:%d", errCode); + return errCode; + } + + // When removing device data in cache mode, key is "remove", value is deviceID's hash string. + // Therefore, no need to transfer hash string when migrating. + std::string devName = isSyncMigrating_ ? deviceName : DBCommon::TransferHashString(deviceName); + std::vector devVect(devName.begin(), devName.end()); + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, devVect, true); // bind the 1st to device. + if (errCode != E_OK) { + LOGE("Failed to bind the synced device for all entries:%d", errCode); + } else { + errCode = GetAllEntries(statement, entries); + } + + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteSingleVerStorageExecutor::GetAllEntries(sqlite3_stmt *statement, std::vector &entries) const +{ + if (statement == nullptr) { + return -E_INVALID_DB; + } + int errCode; + do { + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + Entry entry; + errCode = SQLiteUtils::GetColumnBlobValue(statement, 0, entry.key); // No.0 is the key + if (errCode != E_OK) { + break; + } + errCode = SQLiteUtils::GetColumnBlobValue(statement, 1, entry.value); // No.1 is the value + if (errCode != E_OK) { + break; + } + + entries.push_back(std::move(entry)); + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + break; + } else { + LOGE("SQLite step for all entries failed:%d", errCode); + break; + } + } while (true); + + return errCode; +} + +int SQLiteSingleVerStorageExecutor::GetAllKeys(sqlite3_stmt *statement, std::vector &keys) const +{ + if (statement == nullptr) { + return -E_INVALID_DB; + } + int errCode; + do { + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + Key key; + errCode = SQLiteUtils::GetColumnBlobValue(statement, 0, key); + if (errCode != E_OK) { + break; + } + + keys.push_back(std::move(key)); + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + break; + } else { + LOGE("SQLite step for getting all keys failed:%d", errCode); + break; + } + } while (true); + + return errCode; +} + +int SQLiteSingleVerStorageExecutor::BindSavedSyncData(sqlite3_stmt *statement, const DataItem &dataItem, + const Key &hashKey, const SyncDataDevices &devices, bool isUpdate) +{ + const int hashKeyIndex = isUpdate ? BIND_SYNC_UPDATE_HASH_KEY_INDEX : BIND_SYNC_HASH_KEY_INDEX; + int errCode = SQLiteUtils::BindBlobToStatement(statement, hashKeyIndex, hashKey, false); + if (errCode != E_OK) { + LOGE("Bind saved sync data hash key failed:%d", errCode); + return errCode; + } + + // if delete flag is set, just use the hash key instead of the key + if ((dataItem.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG) { + errCode = SQLiteUtils::MapSQLiteErrno(sqlite3_bind_zeroblob(statement, BIND_SYNC_KEY_INDEX, -1)); + } else { + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_SYNC_KEY_INDEX, dataItem.key, false); + } + + if (errCode != E_OK) { + LOGE("Bind saved sync data key failed:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_SYNC_VAL_INDEX, dataItem.value, true); + if (errCode != E_OK) { + LOGE("Bind saved sync data value failed:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindInt64ToStatement(statement, BIND_SYNC_STAMP_INDEX, dataItem.timestamp); + if (errCode != E_OK) { + LOGE("Bind saved sync data stamp failed:%d", errCode); + return errCode; + } + + const int writeTimeIndex = isUpdate ? BIND_SYNC_UPDATE_W_TIME_INDEX : BIND_SYNC_W_TIME_INDEX; + errCode = SQLiteUtils::BindInt64ToStatement(statement, writeTimeIndex, dataItem.writeTimestamp); + LOGD("Write timestamp:%" PRIu64 " timestamp:%" PRIu64 ", %" PRIu64, + dataItem.writeTimestamp, dataItem.timestamp, dataItem.flag); + if (errCode != E_OK) { + LOGE("Bind saved sync data write stamp failed:%d", errCode); + return errCode; + } + + return BindDevForSavedSyncData(statement, dataItem, devices.origDev, devices.dev); +} + +void SQLiteSingleVerStorageExecutor::PutConflictData(const DataItem &itemPut, const DataItem &itemGet, + const DeviceInfo &deviceInfo, const DataOperStatus &dataStatus, + SingleVerNaturalStoreCommitNotifyData *commitData) +{ + if (commitData == nullptr) { + return; + } + + bool conflictNotifyMatch = commitData->IsConflictedNotifyMatched(itemPut, itemGet); + if (!conflictNotifyMatch) { + return; + } + + if (dataStatus.preStatus == DataStatus::NOEXISTED || + ((dataStatus.preStatus == DataStatus::DELETED) && dataStatus.isDeleted)) { + return; + } + + Key origKey; + if ((itemPut.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG || + (itemPut.flag & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) == DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) { + origKey = itemGet.key; + } else { + origKey = itemPut.key; + } + + // insert db original entry + std::vector getDevVect(itemGet.dev.begin(), itemGet.dev.end()); + DataItemInfo orgItemInfo = {itemGet, true, getDevVect}; + orgItemInfo.dataItem.key = origKey; + commitData->InsertConflictedItem(orgItemInfo, true); + + // insert conflict entry + std::string putDeviceName = DBCommon::TransferHashString(deviceInfo.deviceName); + std::vector putDevVect(putDeviceName.begin(), putDeviceName.end()); + + DataItemInfo newItemInfo = {itemPut, deviceInfo.isLocal, putDevVect}; + newItemInfo.dataItem.key = origKey; + commitData->InsertConflictedItem(newItemInfo, false); +} + +int SQLiteSingleVerStorageExecutor::Reset() +{ + if (isTransactionOpen_) { + Rollback(); + } + + int errCode = ResetForSavingData(SingleVerDataType::SYNC_TYPE); + if (errCode != E_OK) { + LOGE("Finalize the sync resources for saving sync data failed: %d", errCode); + } + + errCode = ResetForSavingData(SingleVerDataType::LOCAL_TYPE); + if (errCode != E_OK) { + LOGE("Finalize the local resources for saving sync data failed: %d", errCode); + } + return SQLiteStorageExecutor::Reset(); +} + +int SQLiteSingleVerStorageExecutor::GetSyncDataItemPre(const DataItem &itemPut, DataItem &itemGet, + Key &hashKey) const +{ + if (isSyncMigrating_) { + hashKey = itemPut.hashKey; + } else if ((itemPut.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG || + ((itemPut.flag & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) == DataItem::REMOTE_DEVICE_DATA_MISS_QUERY)) { + hashKey = itemPut.key; + } else { + int errCode = DBCommon::CalcValueHash(itemPut.key, hashKey); + if (errCode != E_OK) { + return errCode; + } + } + + return GetSyncDataPreByHashKey(hashKey, itemGet); +} + +int SQLiteSingleVerStorageExecutor::GetSyncDataPreByHashKey(const Key &hashKey, DataItem &itemGet) const +{ + auto statement = isSyncMigrating_ ? migrateSyncStatements_.queryStatement : saveSyncStatements_.queryStatement; + int errCode = SQLiteUtils::BindBlobToStatement(statement, 1, hashKey, false); // 1st arg. + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { // no find the key + errCode = -E_NOT_FOUND; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + itemGet.timestamp = static_cast(sqlite3_column_int64(statement, SYNC_RES_TIME_INDEX)); + itemGet.writeTimestamp = static_cast(sqlite3_column_int64(statement, SYNC_RES_W_TIME_INDEX)); + itemGet.flag = static_cast(sqlite3_column_int64(statement, SYNC_RES_FLAG_INDEX)); + errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_KEY_INDEX, itemGet.key); + if (errCode != E_OK) { + return errCode; + } + std::vector devVect; + errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_DEVICE_INDEX, devVect); + if (errCode != E_OK) { + return errCode; + } + + std::vector origDevVect; + errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_ORI_DEV_INDEX, origDevVect); + if (errCode != E_OK) { + return errCode; + } + itemGet.dev.assign(devVect.begin(), devVect.end()); + itemGet.origDev.assign(origDevVect.begin(), origDevVect.end()); + } + return errCode; +} + +int SQLiteSingleVerStorageExecutor::DeleteLocalDataInner(SingleVerNaturalStoreCommitNotifyData *committedData, + const Key &key, const Value &value) +{ + if (committedData != nullptr) { + Key hashKey; + int innerErrCode = DBCommon::CalcValueHash(key, hashKey); + if (innerErrCode != E_OK) { + return innerErrCode; + } + committedData->InitKeyPropRecord(hashKey, ExistStatus::EXIST); + } + + std::string sql = DELETE_LOCAL_SQL; + if (executorState_ == ExecutorState::CACHE_ATTACH_MAIN) { + sql = DELETE_LOCAL_SQL_FROM_CACHEHANDLE; + } + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, key, false); + if (errCode != E_OK) { + LOGE("Bind the key error(%d) when delete kv data.", errCode); + goto ERROR; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + if (sqlite3_changes(dbHandle_) > 0) { + if (committedData != nullptr) { + Entry entry = {key, value}; + committedData->InsertCommittedData(std::move(entry), DataType::DELETE, true); + } else { + LOGE("DeleteLocalKvData failed to do commit notify because of OOM."); + } + errCode = E_OK; + } + } + +ERROR: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::DeleteLocalKvData(const Key &key, + SingleVerNaturalStoreCommitNotifyData *committedData, Value &value, Timestamp ×tamp) +{ + int errCode = GetKvData(SingleVerDataType::LOCAL_TYPE, key, value, timestamp); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + + return DeleteLocalDataInner(committedData, key, value); +} + +int SQLiteSingleVerStorageExecutor::EraseSyncData(const Key &hashKey) +{ + sqlite3_stmt *stmt = nullptr; + std::string sql = (executorState_ == ExecutorState::CACHE_ATTACH_MAIN) ? + DELETE_SYNC_DATA_WITH_HASHKEY_FROM_CACHEHANDLE : DELETE_SYNC_DATA_WITH_HASHKEY; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, stmt); + if (errCode != E_OK) { + LOGE("get erase statement failed:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindBlobToStatement(stmt, 1, hashKey, false); + if (errCode != E_OK) { + LOGE("bind hashKey failed:%d", errCode); + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(stmt, false); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } else { + LOGE("erase data failed:%d", errCode); + } +END: + SQLiteUtils::ResetStatement(stmt, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::RemoveDeviceData(const std::string &deviceName) +{ + // Transfer the device name. + std::string devName = DBCommon::TransferHashString(deviceName); + sqlite3_stmt *statement = nullptr; + std::vector devVect(devName.begin(), devName.end()); + + int errCode = SQLiteUtils::GetStatement(dbHandle_, REMOVE_DEV_DATA_SQL, statement); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, devVect, true); // only one arg. + if (errCode != E_OK) { + LOGE("Failed to bind the removed device:%d", errCode); + goto ERROR; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + LOGE("Failed to execute rm the device synced data:%d", errCode); + } else { + errCode = E_OK; + } + +ERROR: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::StepForResultEntries(sqlite3_stmt *statement, std::vector &entries) const +{ + entries.clear(); + entries.shrink_to_fit(); + Entry entry; + int errCode = E_OK; + do { + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + errCode = SQLiteUtils::GetColumnBlobValue(statement, 0, entry.key); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::GetColumnBlobValue(statement, 1, entry.value); + if (errCode != E_OK) { + return errCode; + } + + entries.push_back(std::move(entry)); + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + break; + } else { + LOGE("SQLite step failed:%d", errCode); + return errCode; + } + } while (true); + + // if select no result, return the -E_NOT_FOUND. + if (entries.empty()) { + errCode = -E_NOT_FOUND; + } + + return errCode; +} + +int SQLiteSingleVerStorageExecutor::BindDevForSavedSyncData(sqlite3_stmt *statement, const DataItem &dataItem, + const std::string &origDev, const std::string &deviceName) +{ + int errCode = SQLiteUtils::BindInt64ToStatement(statement, BIND_SYNC_FLAG_INDEX, + static_cast(dataItem.flag)); + if (errCode != E_OK) { + LOGE("Bind saved sync data flag failed:%d", errCode); + return errCode; + } + + std::vector devVect(deviceName.begin(), deviceName.end()); + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_SYNC_DEV_INDEX, devVect, true); + if (errCode != E_OK) { + LOGE("Bind dev for sync data failed:%d", errCode); + return errCode; + } + + std::vector origDevVect(origDev.begin(), origDev.end()); + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_SYNC_ORI_DEV_INDEX, origDevVect, true); + if (errCode != E_OK) { + LOGE("Bind orig dev for sync data failed:%d", errCode); + } + + return errCode; +} + +size_t SQLiteSingleVerStorageExecutor::GetDataItemSerialSize(const DataItem &item, size_t appendLen) +{ + // timestamp and local flag: 3 * uint64_t, version(uint32_t), key, value, origin dev and the padding size. + // the size would not be very large. + static const size_t maxOrigDevLength = 40; + size_t devLength = std::max(maxOrigDevLength, item.origDev.size()); + size_t dataSize = (Parcel::GetUInt64Len() * 3 + Parcel::GetUInt32Len() + Parcel::GetVectorCharLen(item.key) + + Parcel::GetVectorCharLen(item.value) + devLength + appendLen); + + return dataSize; +} + +int SQLiteSingleVerStorageExecutor::InitResultSet(const Key &keyPrefix, sqlite3_stmt *&countStmt) +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + // bind statement for count + int errCode = SQLiteUtils::GetStatement(dbHandle_, SELECT_COUNT_SYNC_PREFIX_SQL, countStmt); + if (errCode != E_OK) { + LOGE("Get count statement for resultset error:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindPrefixKey(countStmt, 1, keyPrefix); // first argument is key + if (errCode != E_OK) { + LOGE("Bind count key error:%d", errCode); + goto ERROR; + } + // bind statement for result set + errCode = SQLiteUtils::GetStatement(dbHandle_, SELECT_SYNC_ROWID_PREFIX_SQL, getResultRowIdStatement_); + if (errCode != E_OK) { + LOGE("Get result set rowid statement error:%d", errCode); + goto ERROR; + } + + errCode = SQLiteUtils::GetStatement(dbHandle_, SELECT_SYNC_DATA_BY_ROWID_SQL, getResultEntryStatement_); + if (errCode != E_OK) { + LOGE("Get result set entry statement error:%d", errCode); + goto ERROR; + } + + errCode = SQLiteUtils::BindPrefixKey(getResultRowIdStatement_, 1, keyPrefix); // first argument is key + if (errCode != E_OK) { + LOGE("Bind result set rowid statement error:%d", errCode); + goto ERROR; + } + return E_OK; + +ERROR: + SQLiteUtils::ResetStatement(countStmt, true, errCode); + SQLiteUtils::ResetStatement(getResultRowIdStatement_, true, errCode); + SQLiteUtils::ResetStatement(getResultEntryStatement_, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::InitResultSetCount(QueryObject &queryObj, sqlite3_stmt *&countStmt) +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = E_OK; + SqliteQueryHelper helper = queryObj.GetQueryHelper(errCode); + if (errCode != E_OK) { + return errCode; + } + + errCode = helper.GetCountSqlStatement(dbHandle_, countStmt); + if (errCode != E_OK) { + LOGE("Get count bind statement error:%d", errCode); + SQLiteUtils::ResetStatement(countStmt, true, errCode); + } + return errCode; +} + +int SQLiteSingleVerStorageExecutor::InitResultSetContent(QueryObject &queryObj) +{ + int errCode = E_OK; + SqliteQueryHelper helper = queryObj.GetQueryHelper(errCode); + if (errCode != E_OK) { + return errCode; + } + + // bind statement for result set + errCode = helper.GetQuerySqlStatement(dbHandle_, true, getResultRowIdStatement_); + if (errCode != E_OK) { + LOGE("[SqlSinExe][InitResSetContent] Bind result set rowid statement of query error:%d", errCode); + SQLiteUtils::ResetStatement(getResultRowIdStatement_, true, errCode); + return errCode; + } + errCode = SQLiteUtils::GetStatement(dbHandle_, SELECT_SYNC_DATA_BY_ROWID_SQL, getResultEntryStatement_); + if (errCode != E_OK) { + LOGE("[SqlSinExe][InitResSetContent] Get result set entry statement of query error:%d", errCode); + return CheckCorruptedStatus(errCode); + } + return errCode; +} + +int SQLiteSingleVerStorageExecutor::InitResultSet(QueryObject &queryObj, sqlite3_stmt *&countStmt) +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + + int errCode = E_OK; + SqliteQueryHelper helper = queryObj.GetQueryHelper(errCode); + if (errCode != E_OK) { + return errCode; + } + + if (!queryObj.IsValid()) { + return -E_INVALID_QUERY_FORMAT; + } + + errCode = InitResultSetCount(queryObj, countStmt); + if (errCode != E_OK) { + return CheckCorruptedStatus(errCode); + } + + errCode = InitResultSetContent(queryObj); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(countStmt, true, errCode); + } + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::UpdateLocalDataTimestamp(Timestamp timestamp) +{ + const std::string updateSql = "UPDATE local_data SET timestamp="; + std::string sql = updateSql + std::to_string(timestamp) + " WHERE timestamp=0;"; + int errCode = SQLiteUtils::ExecuteRawSQL(dbHandle_, sql); + return CheckCorruptedStatus(errCode); +} + +void SQLiteSingleVerStorageExecutor::SetAttachMetaMode(bool attachMetaMode) +{ + attachMetaMode_ = attachMetaMode; +} + +int SQLiteSingleVerStorageExecutor::GetOneRawDataItem(sqlite3_stmt *statement, DataItem &dataItem, + uint64_t &verInCurCacheDb, bool isCacheDb) const +{ + int errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_KEY_INDEX, dataItem.key); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_VAL_INDEX, dataItem.value); + if (errCode != E_OK) { + return errCode; + } + + dataItem.timestamp = static_cast(sqlite3_column_int64(statement, SYNC_RES_TIME_INDEX)); + dataItem.flag = static_cast(sqlite3_column_int64(statement, SYNC_RES_FLAG_INDEX)); + + std::vector devVect; + errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_DEVICE_INDEX, devVect); + if (errCode != E_OK) { + return errCode; + } + dataItem.dev = std::string(devVect.begin(), devVect.end()); + + devVect.clear(); + errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_ORI_DEV_INDEX, devVect); + if (errCode != E_OK) { + return errCode; + } + dataItem.origDev = std::string(devVect.begin(), devVect.end()); + + errCode = SQLiteUtils::GetColumnBlobValue(statement, SYNC_RES_HASH_KEY_INDEX, dataItem.hashKey); + if (errCode != E_OK) { + return errCode; + } + dataItem.writeTimestamp = static_cast(sqlite3_column_int64(statement, SYNC_RES_W_TIME_INDEX)); + if (errCode != E_OK) { + return errCode; + } + if (isCacheDb) { + verInCurCacheDb = static_cast(sqlite3_column_int64(statement, SYNC_RES_VERSION_INDEX)); + } + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::GetAllDataItems(sqlite3_stmt *statement, std::vector &dataItems, + uint64_t &verInCurCacheDb, bool isCacheDb) const +{ + dataItems.clear(); + dataItems.shrink_to_fit(); + DataItem dataItem; + int errCode; + do { + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + errCode = GetOneRawDataItem(statement, dataItem, verInCurCacheDb, isCacheDb); + if (errCode != E_OK) { + return errCode; + } + dataItems.push_back(std::move(dataItem)); + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + break; + } else { + LOGE("SQLite step failed:%d", errCode); + break; + } + } while (true); + + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::OpenResultSetForCacheRowIdModeCommon(std::vector &rowIdCache, + uint32_t cacheLimit, int &count) +{ + int errCode = SQLiteUtils::GetStatement(dbHandle_, SELECT_SYNC_DATA_BY_ROWID_SQL, getResultEntryStatement_); + if (errCode != E_OK) { + LOGE("[SqlSinExe][OpenResSetRowId][Common] Get entry stmt fail, errCode=%d", errCode); + return CheckCorruptedStatus(errCode); + } + errCode = StartTransaction(TransactType::DEFERRED); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(getResultEntryStatement_, true, errCode); + return CheckCorruptedStatus(errCode); + } + // Now Ready To Execute + errCode = ResultSetLoadRowIdCache(rowIdCache, cacheLimit, 0, count); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(getResultEntryStatement_, true, errCode); + Rollback(); + return CheckCorruptedStatus(errCode); + } + // Consider finalize getResultRowIdStatement_ here if count equal to size of rowIdCache. + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::ResultSetLoadRowIdCache(std::vector &rowIdCache, uint32_t cacheLimit, + uint32_t cacheStartPos, int &count) +{ + rowIdCache.clear(); + count = 0; + while (true) { + int errCode = SQLiteUtils::StepWithRetry(getResultRowIdStatement_, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + if (count >= static_cast(cacheStartPos) && rowIdCache.size() < cacheLimit) { + // If we can start cache, and, if we can still cache + int64_t rowid = sqlite3_column_int64(getResultRowIdStatement_, 0); + rowIdCache.push_back(rowid); + } + // Always increase the count + count++; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + break; + } else { + LOGE("[SqlSinExe][ResSetLoadCache] Step fail, errCode=%d", errCode); + rowIdCache.clear(); + count = 0; + return CheckCorruptedStatus(errCode); + } + } + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::SaveRecordStatements::ResetStatement() +{ + int errCode = E_OK; + SQLiteUtils::ResetStatement(insertStatement, true, errCode); + if (errCode != E_OK) { + LOGE("Finalize insert statements failed, error: %d", errCode); + } + + SQLiteUtils::ResetStatement(updateStatement, true, errCode); + if (errCode != E_OK) { + LOGE("Finalize update statements failed, error: %d", errCode); + } + + SQLiteUtils::ResetStatement(queryStatement, true, errCode); + if (errCode != E_OK) { + LOGE("Finalize query statement failed, error: %d", errCode); + } + return errCode; +} + +void SQLiteSingleVerStorageExecutor::FinalizeAllStatements() +{ + int errCode = saveLocalStatements_.ResetStatement(); + if (errCode != E_OK) { + LOGE("Finalize saveLocal statements failed, error: %d", errCode); + } + + errCode = saveSyncStatements_.ResetStatement(); + if (errCode != E_OK) { + LOGE("Finalize saveSync statement failed, error: %d", errCode); + } + + SQLiteUtils::ResetStatement(getResultRowIdStatement_, true, errCode); + if (errCode != E_OK) { + LOGE("Finalize getResultRowIdStatement_ failed, error: %d", errCode); + } + + SQLiteUtils::ResetStatement(getResultEntryStatement_, true, errCode); + if (errCode != E_OK) { + LOGE("Finalize getResultEntryStatement_ failed, error: %d", errCode); + } + + errCode = migrateSyncStatements_.ResetStatement(); + if (errCode != E_OK) { + LOGE("Finalize migrateSync statements failed, error: %d", errCode); + } + + ReleaseContinueStatement(); +} + +void SQLiteSingleVerStorageExecutor::SetConflictResolvePolicy(int policy) +{ + if (policy == DENY_OTHER_DEV_AMEND_CUR_DEV_DATA || policy == DEFAULT_LAST_WIN) { + conflictResolvePolicy_ = policy; + } +} + +int SQLiteSingleVerStorageExecutor::CheckIntegrity() const +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + + return SQLiteUtils::CheckIntegrity(dbHandle_, CHECK_DB_INTEGRITY_SQL); +} + +int SQLiteSingleVerStorageExecutor::ForceCheckPoint() const +{ + if (dbHandle_ == nullptr) { + return -E_INVALID_DB; + } + SQLiteUtils::ExecuteCheckPoint(dbHandle_); + return E_OK; +} + +uint64_t SQLiteSingleVerStorageExecutor::GetLogFileSize() const +{ + if (isMemDb_) { + return 0; + } + + const char *fileName = sqlite3_db_filename(dbHandle_, "main"); + if (fileName == nullptr) { + return 0; + } + std::string walName = std::string(fileName) + "-wal"; + uint64_t fileSize = 0; + int result = OS::CalFileSize(std::string(walName), fileSize); + if (result != E_OK) { + return 0; + } + return fileSize; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor.h b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor.h new file mode 100644 index 00000000..ddc9154c --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor.h @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_SINGLE_VER_STORAGE_EXECUTOR_H +#define SQLITE_SINGLE_VER_STORAGE_EXECUTOR_H + +#include "macro_utils.h" +#include "db_types.h" +#include "query_object.h" +#include "sqlite_utils.h" +#include "sqlite_storage_executor.h" +#include "single_ver_natural_store_commit_notify_data.h" + +namespace DistributedDB { +enum class SingleVerDataType { + META_TYPE, + LOCAL_TYPE, + SYNC_TYPE, +}; + +enum class DataStatus { + NOEXISTED, + DELETED, + EXISTED, +}; + +enum class ExecutorState { + INVALID = -1, + MAINDB, + CACHEDB, + MAIN_ATTACH_CACHE, // After process crash and cacheDb existed + CACHE_ATTACH_MAIN, // while cacheDb migrating to mainDb +}; + +struct DataOperStatus { + DataStatus preStatus = DataStatus::NOEXISTED; + bool isDeleted = false; + bool isDefeated = false; // whether the put data is defeated. +}; + +struct SingleVerRecord { + Key key; + Value value; + Timestamp timestamp = 0; + uint64_t flag = 0; + std::string device; + std::string origDevice; + Key hashKey; + Timestamp writeTimestamp = 0; +}; + +struct DeviceInfo { + bool isLocal = false; + std::string deviceName; +}; + +struct LocalDataItem { + Key key; + Value value; + Timestamp timestamp = 0; + Key hashKey; + uint64_t flag = 0; +}; + +struct NotifyConflictAndObserverData { + SingleVerNaturalStoreCommitNotifyData *committedData = nullptr; + DataItem getData; + Key hashKey; + DataOperStatus dataStatus; +}; + +struct NotifyMigrateSyncData { + bool isRemote = false; + bool isRemoveDeviceData = false; + bool isPermitForceWrite = true; + SingleVerNaturalStoreCommitNotifyData *committedData = nullptr; + std::vector entries{}; +}; + +struct SyncDataDevices { + std::string origDev; + std::string dev; +}; + +class SQLiteSingleVerStorageExecutor : public SQLiteStorageExecutor { +public: + SQLiteSingleVerStorageExecutor(sqlite3 *dbHandle, bool writable, bool isMemDb); + SQLiteSingleVerStorageExecutor(sqlite3 *dbHandle, bool writable, bool isMemDb, ExecutorState executorState); + ~SQLiteSingleVerStorageExecutor() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteSingleVerStorageExecutor); + + // Get the Kv data according the type(sync, meta, local data). + int GetKvData(SingleVerDataType type, const Key &key, Value &value, Timestamp ×tamp) const; + + // Get the sync data record by hash key. + int GetKvDataByHashKey(const Key &hashKey, SingleVerRecord &result) const; + + // Put the Kv data according the type(meta and the local data). + int PutKvData(SingleVerDataType type, const Key &key, const Value &value, + Timestamp timestamp, SingleVerNaturalStoreCommitNotifyData *committedData); + + int GetEntries(SingleVerDataType type, const Key &keyPrefix, std::vector &entries) const; + + int GetEntries(QueryObject &queryObj, std::vector &entries) const; + + int GetCount(QueryObject &queryObj, int &count) const; + + // Get all the meta keys. + int GetAllMetaKeys(std::vector &keys) const; + + int GetAllSyncedEntries(const std::string &deviceName, std::vector &entries) const; + + int SaveSyncDataItem(DataItem &dataItem, const DeviceInfo &deviceInfo, + Timestamp &maxStamp, SingleVerNaturalStoreCommitNotifyData *committedData, bool isPermitForceWrite = true); + + int DeleteLocalKvData(const Key &key, SingleVerNaturalStoreCommitNotifyData *committedData, Value &value, + Timestamp ×tamp); + + // delete a row data by hashKey, with no tombstone left. + int EraseSyncData(const Key &hashKey); + + int RemoveDeviceData(const std::string &deviceName); + + int RemoveDeviceDataInCacheMode(const std::string &deviceName, bool isNeedNotify, uint64_t recordVersion) const; + + void InitCurrentMaxStamp(Timestamp &maxStamp); + + void ReleaseContinueStatement(); + + int GetSyncDataByTimestamp(std::vector &dataItems, size_t appendedLength, Timestamp begin, + Timestamp end, const DataSizeSpecInfo &dataSizeInfo) const; + int GetDeletedSyncDataByTimestamp(std::vector &dataItems, size_t appendedLength, Timestamp begin, + Timestamp end, const DataSizeSpecInfo &dataSizeInfo) const; + + int GetDeviceIdentifier(PragmaEntryDeviceIdentifier *identifier); + + int OpenResultSet(const Key &keyPrefix, int &count); + + int OpenResultSet(QueryObject &queryObj, int &count); + + int OpenResultSetForCacheRowIdMode(const Key &keyPrefix, std::vector &rowIdCache, + uint32_t cacheLimit, int &count); + + int OpenResultSetForCacheRowIdMode(QueryObject &queryObj, std::vector &rowIdCache, + uint32_t cacheLimit, int &count); + + int ReloadResultSet(const Key &keyPrefix); + + int ReloadResultSet(QueryObject &queryObj); + + int ReloadResultSetForCacheRowIdMode(const Key &keyPrefix, std::vector &rowIdCache, + uint32_t cacheLimit, uint32_t cacheStartPos); + + int ReloadResultSetForCacheRowIdMode(QueryObject &queryObj, std::vector &rowIdCache, + uint32_t cacheLimit, uint32_t cacheStartPos); + + int GetNextEntryFromResultSet(Key &key, Value &value, bool isCopy); + + int GetEntryByRowId(int64_t rowId, Entry &entry); + + void CloseResultSet(); + + int StartTransaction(TransactType type); + + int Commit(); + + int Rollback(); + + bool CheckIfKeyExisted(const Key &key, bool isLocal, Value &value, Timestamp ×tamp) const; + + int PrepareForSavingData(SingleVerDataType type); + + int ResetForSavingData(SingleVerDataType type); + + int Reset() override; + + int UpdateLocalDataTimestamp(Timestamp timestamp); + + void SetAttachMetaMode(bool attachMetaMode); + + int PutLocalDataToCacheDB(const LocalDataItem &dataItem) const; + + int SaveSyncDataItemInCacheMode(DataItem &dataItem, const DeviceInfo &deviceInfo, Timestamp &maxStamp, + uint64_t recordVersion, const QueryObject &query); + + int PrepareForSavingCacheData(SingleVerDataType type); + int ResetForSavingCacheData(SingleVerDataType type); + + int MigrateLocalData(); + + int MigrateSyncDataByVersion(uint64_t recordVer, NotifyMigrateSyncData &syncData, + std::vector &dataItems); + int GetMinVersionCacheData(std::vector &dataItems, uint64_t &maxVerIncurCacheDb) const; + + int GetMaxVersionIncacheDb(uint64_t &maxVersion) const; + int AttachMainDbAndCacheDb(CipherType type, const CipherPassword &passwd, + const std::string &attachDbAbsPath, EngineState engineState); + + // Clear migrating data. + void ClearMigrateData(); + + // Get current max timestamp. + int GetMaxTimestampDuringMigrating(Timestamp &maxTimestamp) const; + + void SetConflictResolvePolicy(int policy); + + // Delete multiple meta data records in a transaction. + int DeleteMetaData(const std::vector &keys); + // Delete multiple meta data records with key prefix in a transaction. + int DeleteMetaDataByPrefixKey(const Key &keyPrefix); + + int CheckIntegrity() const; + + int CheckQueryObjectLegal(QueryObject &queryObj) const; + + int CheckDataWithQuery(QueryObject query, std::vector &dataItems, const DeviceInfo &deviceInfo); + + static size_t GetDataItemSerialSize(const DataItem &item, size_t appendLen); + + int AddSubscribeTrigger(QueryObject &query, const std::string &subscribeId); + + int RemoveSubscribeTrigger(const std::vector &subscribeIds); + + int RemoveSubscribeTriggerWaterMark(const std::vector &subscribeIds); + + int GetTriggers(const std::string &namePreFix, std::vector &triggerNames); + + int RemoveTrigger(const std::vector &triggers); + + int GetSyncDataWithQuery(const QueryObject &query, size_t appendLength, const DataSizeSpecInfo &dataSizeInfo, + const std::pair &timeRange, std::vector &dataItems) const; + + int ForceCheckPoint() const; + + uint64_t GetLogFileSize() const; + +private: + struct SaveRecordStatements { + sqlite3_stmt *queryStatement = nullptr; + sqlite3_stmt *insertStatement = nullptr; + sqlite3_stmt *updateStatement = nullptr; + + int ResetStatement(); + + inline sqlite3_stmt *GetDataSaveStatement(bool isUpdate) const + { + return isUpdate ? updateStatement : insertStatement; + } + }; + + void PutIntoCommittedData(const DataItem &itemPut, const DataItem &itemGet, const DataOperStatus &status, + const Key &hashKey, SingleVerNaturalStoreCommitNotifyData *committedData); + + static int BindSavedSyncData(sqlite3_stmt *statement, const DataItem &dataItem, const Key &hashKey, + const SyncDataDevices &devices, bool isUpdate); + + static int BindDevForSavedSyncData(sqlite3_stmt *statement, const DataItem &dataItem, const std::string &origDev, + const std::string &deviceName); + + static void PutConflictData(const DataItem &itemPut, const DataItem &itemGet, const DeviceInfo &deviceInfo, + const DataOperStatus &dataStatus, SingleVerNaturalStoreCommitNotifyData *commitData); + + static DataOperStatus JudgeSyncSaveType(DataItem &dataItem, const DataItem &itemGet, + const std::string &devName, bool isHashKeyExisted, bool isPermitForceWrite = true); + + static std::string GetOriginDevName(const DataItem &dataItem, const std::string &origDevGet); + + int GetSyncDataItemPre(const DataItem &itemPut, DataItem &itemGet, Key &hashKey) const; + + int GetSyncDataItemExt(const DataItem &dataItem, DataItem &itemGet, const DataOperStatus &dataStatus) const; + + int GetSyncDataPreByHashKey(const Key &hashKey, DataItem &itemGet) const; + + int PrepareForSyncDataByTime(Timestamp begin, Timestamp end, sqlite3_stmt *&statement, bool getDeletedData = false) + const; + + int StepForResultEntries(sqlite3_stmt *statement, std::vector &entries) const; + + int InitResultSet(const Key &keyPrefix, sqlite3_stmt *&countStmt); + + int InitResultSetCount(QueryObject &queryObj, sqlite3_stmt *&countStmt); + + int InitResultSetContent(QueryObject &queryObj); + + int InitResultSet(QueryObject &queryObj, sqlite3_stmt *&countStmt); + + int GetAllKeys(sqlite3_stmt *statement, std::vector &keys) const; + + int GetAllEntries(sqlite3_stmt *statement, std::vector &entries) const; + + int BindPutKvData(sqlite3_stmt *statement, const Key &key, const Value &value, Timestamp timestamp, + SingleVerDataType type); + + int SaveSyncDataToDatabase(const DataItem &dataItem, const Key &hashKey, const std::string &origDev, + const std::string &deviceName, bool isUpdate); + + int SaveKvData(SingleVerDataType type, const Key &key, const Value &value, Timestamp timestamp); + + int DeleteLocalDataInner(SingleVerNaturalStoreCommitNotifyData *committedData, + const Key &key, const Value &value); + + int PrepareForSavingData(const std::string &readSql, const std::string &insertSql, + const std::string &updateSql, SaveRecordStatements &statements) const; + + int OpenResultSetForCacheRowIdModeCommon(std::vector &rowIdCache, uint32_t cacheLimit, int &count); + + int ResultSetLoadRowIdCache(std::vector &rowIdCache, uint32_t cacheLimit, + uint32_t cacheStartPos, int &count); + + void FinalizeAllStatements(); + int ResetSaveSyncStatements(int errCode); + + int BindSyncDataInCacheMode(sqlite3_stmt *statement, + const DataItem &dataItem, const Key &hashKey, uint64_t recordVersion) const; + + int BindPrimaryKeySyncDataInCacheMode( + sqlite3_stmt *statement, const Key &hashKey, uint64_t recordVersion) const; + + int BindTimestampSyncDataInCacheMode(sqlite3_stmt *statement, const DataItem &dataItem) const; + + int BindDevSyncDataInCacheMode(sqlite3_stmt *statement, + const std::string &origDev, const std::string &deviceName) const; + + int SaveSyncDataToCacheDatabase(const DataItem &dataItem, const Key &hashKey, uint64_t recordVersion) const; + + int GetOneRawDataItem(sqlite3_stmt *statement, DataItem &dataItem, + uint64_t &verInCurCacheDb, bool isCacheDb) const; + int GetAllDataItems(sqlite3_stmt *statement, std::vector &dataItems, + uint64_t &verInCurCacheDb, bool isCacheDb) const; + int DelCacheDbDataByVersion(uint64_t version) const; + + // use for migrating data + int BindLocalDataInCacheMode(sqlite3_stmt *statement, const LocalDataItem &dataItem) const; + + // Process timestamp for syncdata in cacheDB when migrating. + int ProcessTimestampForSyncDataInCacheDB(std::vector &dataItems); + + // Get migrateTimeOffset_. + int InitMigrateTimestampOffset(); + + // Get min timestamp of local data in sync_data, cacheDB. + int GetMinTimestampInCacheDB(Timestamp &minStamp) const; + + // Prepare conflict notify and commit notify data. + int PrepareForNotifyConflictAndObserver(DataItem &dataItem, const DeviceInfo &deviceInfo, + NotifyConflictAndObserverData ¬ify, bool isPermitForceWrite = true); + + // Put observer and conflict data into commit notify when migrating cacheDB. + int PutIntoConflictAndCommitForMigrateCache(DataItem &dataItem, const DeviceInfo &deviceInfo, + NotifyConflictAndObserverData ¬ify, bool isPermitForceWrite); + + int MigrateDataItems(std::vector &dataItems, NotifyMigrateSyncData &syncData); + + int MigrateDataItem(DataItem &dataItem, NotifyMigrateSyncData &syncData); + + int GetEntriesForNotifyRemoveDevData(const DataItem &item, std::vector &entries) const; + + // Reset migrateSyncStatements_. + int ResetForMigrateCacheData(); + + // Init migrating data. + int InitMigrateData(); + + int MigrateRmDevData(const DataItem &dataItem) const; + int VacuumLocalData() const; + + int GetSyncDataItems(std::vector &dataItems, sqlite3_stmt *statement, + size_t appendLength, const DataSizeSpecInfo &dataSizeInfo) const; + + int GetSyncDataWithQuery(sqlite3_stmt *fullStmt, sqlite3_stmt *queryStmt, + size_t appendLength, const DataSizeSpecInfo &dataSizeInfo, std::vector &dataItems) const; + + int CheckMissQueryDataItems(sqlite3_stmt *&stmt, const SqliteQueryHelper &helper, const DeviceInfo &deviceInfo, + std::vector &dataItems); + + int CheckDataWithQuery(std::vector &dataItems); + + int GetExpandedCheckSql(QueryObject query, DataItem &dataItem); + + int CheckMissQueryDataItem(sqlite3_stmt *stmt, const std::string &deviceName, DataItem &item); + + sqlite3_stmt *getSyncStatement_; + sqlite3_stmt *getResultRowIdStatement_; + sqlite3_stmt *getResultEntryStatement_; + SaveRecordStatements saveSyncStatements_; + SaveRecordStatements saveLocalStatements_; + + // Used for migrating sync_data. + SaveRecordStatements migrateSyncStatements_; + bool isTransactionOpen_; + bool attachMetaMode_; // true for attach meta mode + ExecutorState executorState_; + + // Max timestamp in mainDB. Used for migrating. + Timestamp maxTimestampInMainDB_; + + // The offset between min timestamp in cacheDB and max timestamp in mainDB. Used for migrating. + TimeOffset migrateTimeOffset_; + + // Migrating sync flag. When the flag is true, mainDB and cacheDB are attached, migrateSyncStatements_ is set, + // maxTimestampInMainDB_ and migrateTimeOffset_ is meaningful. + bool isSyncMigrating_; + int conflictResolvePolicy_; +}; +} // namespace DistributedDB + +#endif // SQLITE_SINGLE_VER_STORAGE_EXECUTOR_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor_cache.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor_cache.cpp new file mode 100644 index 00000000..62e8f57a --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor_cache.cpp @@ -0,0 +1,995 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_single_ver_storage_executor.h" + +#include + +#include "log_print.h" +#include "db_constant.h" +#include "db_common.h" +#include "db_errno.h" +#include "parcel.h" +#include "runtime_context.h" +#include "sqlite_single_ver_storage_executor_sql.h" + +namespace DistributedDB { +int SQLiteSingleVerStorageExecutor::PrepareForSavingCacheData(SingleVerDataType type) +{ + int errCode = -E_NOT_SUPPORT; + if (type == SingleVerDataType::LOCAL_TYPE) { + std::string insertLocalSql = ((executorState_ == ExecutorState::CACHE_ATTACH_MAIN) ? + INSERT_LOCAL_SQL_FROM_CACHEHANDLE : INSERT_CACHE_LOCAL_SQL); + std::string updateLocalSql = ((executorState_ == ExecutorState::CACHE_ATTACH_MAIN) ? + UPDATE_LOCAL_SQL_FROM_CACHEHANDLE : UPDATE_CACHE_LOCAL_SQL); + errCode = PrepareForSavingData(SELECT_CACHE_LOCAL_HASH_SQL, insertLocalSql, updateLocalSql, + saveLocalStatements_); + } else if (type == SingleVerDataType::SYNC_TYPE) { + std::string insertSyncSql = ((executorState_ == ExecutorState::MAIN_ATTACH_CACHE) ? + INSERT_CACHE_SYNC_SQL_FROM_MAINHANDLE : INSERT_CACHE_SYNC_SQL); + std::string updateSyncSql = ((executorState_ == ExecutorState::MAIN_ATTACH_CACHE) ? + UPDATE_CACHE_SYNC_SQL_FROM_MAINHANDLE : UPDATE_CACHE_SYNC_SQL); + std::string selectSyncHashSql = ((executorState_ == ExecutorState::MAIN_ATTACH_CACHE) ? + SELECT_CACHE_SYNC_HASH_SQL_FROM_MAINHANDLE : SELECT_CACHE_SYNC_HASH_SQL); + errCode = PrepareForSavingData(selectSyncHashSql, insertSyncSql, updateSyncSql, saveSyncStatements_); + } + if (errCode != E_OK) { + LOGE("Prepare to save sync cache data failed:%d", errCode); + } + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::ResetForSavingCacheData(SingleVerDataType type) +{ + int errCode = E_OK; + if (type == SingleVerDataType::LOCAL_TYPE) { + SQLiteUtils::ResetStatement(saveLocalStatements_.insertStatement, false, errCode); + SQLiteUtils::ResetStatement(saveLocalStatements_.updateStatement, false, errCode); + SQLiteUtils::ResetStatement(saveLocalStatements_.queryStatement, false, errCode); + } else if (type == SingleVerDataType::SYNC_TYPE) { + SQLiteUtils::ResetStatement(saveSyncStatements_.insertStatement, false, errCode); + SQLiteUtils::ResetStatement(saveSyncStatements_.updateStatement, false, errCode); + SQLiteUtils::ResetStatement(saveSyncStatements_.queryStatement, false, errCode); + } + + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::ResetForMigrateCacheData() +{ + int errCode = E_OK; + SQLiteUtils::ResetStatement(migrateSyncStatements_.insertStatement, false, errCode); + SQLiteUtils::ResetStatement(migrateSyncStatements_.updateStatement, false, errCode); + SQLiteUtils::ResetStatement(migrateSyncStatements_.queryStatement, false, errCode); + + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::RemoveDeviceDataInCacheMode(const std::string &deviceName, + bool isNeedNotify, uint64_t recordVersion) const +{ + // Transfer the device name. + std::string devName = DBCommon::TransferHashString(deviceName); + std::vector devVect(devName.begin(), devName.end()); + + Key hashKey; + int errCode = DBCommon::CalcValueHash(REMOVE_DEVICE_DATA_KEY, hashKey); + if (errCode != E_OK) { + return errCode; + } + + DataItem dataItem; + dataItem.key = REMOVE_DEVICE_DATA_KEY; + dataItem.value = devVect; + if (isNeedNotify) { + dataItem.flag = DataItem::REMOVE_DEVICE_DATA_NOTIFY_FLAG; + } else { + dataItem.flag = DataItem::REMOVE_DEVICE_DATA_FLAG; + } + + sqlite3_stmt *statement = nullptr; + std::string sql = (executorState_ == ExecutorState::MAIN_ATTACH_CACHE) ? + INSERT_CACHE_SYNC_SQL_FROM_MAINHANDLE : INSERT_CACHE_SYNC_SQL; + errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = BindSyncDataInCacheMode(statement, dataItem, hashKey, recordVersion); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + LOGE("Failed to execute rm the device synced data:%d", errCode); + } else { + errCode = E_OK; + } + +ERROR: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::GetMinVersionCacheData( + std::vector &dataItems, uint64_t &minVerIncurCacheDb) const +{ + std::string sql; + if (executorState_ == ExecutorState::MAIN_ATTACH_CACHE) { + sql = MIGRATE_SELECT_MIN_VER_CACHEDATA_FROM_MAINHANDLE; + } else if (executorState_ == ExecutorState::CACHE_ATTACH_MAIN) { + sql = MIGRATE_SELECT_MIN_VER_CACHEDATA_FROM_CACHEHANDLE; + } else { + return -E_INVALID_ARGS; + } + + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + LOGE("GetStatement fail when get min version cache data! errCode = [%d]", errCode); + goto END; + } + + errCode = GetAllDataItems(statement, dataItems, minVerIncurCacheDb, true); + if (errCode != E_OK) { + LOGE("Failed to get all the data items by the min version:[%d]", errCode); + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::MigrateRmDevData(const DataItem &dataItem) const +{ + if (dataItem.key != REMOVE_DEVICE_DATA_KEY) { + LOGE("This item not means remove devices data, can not continue exe!"); + return -E_INVALID_ARGS; + } + + std::string sql; + if (executorState_ == ExecutorState::MAIN_ATTACH_CACHE) { + sql = REMOVE_DEV_DATA_SQL; + } else if (executorState_ == ExecutorState::CACHE_ATTACH_MAIN) { + sql = REMOVE_DEV_DATA_SQL_FROM_CACHEHANDLE; + } else { + return -E_INVALID_ARGS; + } + + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + LOGE("GetStatement fail when remove device data migrating-data to main! errCode = [%d]", errCode); + return CheckCorruptedStatus(errCode); + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, dataItem.value, true); + if (errCode != E_OK) { + LOGE("[singerVerExecutor][MiRmData] Bind dev for sync data failed:%d", errCode); + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::AttachMainDbAndCacheDb(CipherType type, const CipherPassword &passwd, + const std::string &attachDbAbsPath, EngineState engineState) +{ + std::string attachAsName; + if (engineState == EngineState::MAINDB) { + attachAsName = "cache"; + } else if (engineState == EngineState::CACHEDB) { + attachAsName = "maindb"; + } else if (engineState == EngineState::ATTACHING) { + executorState_ = ExecutorState::MAIN_ATTACH_CACHE; + return E_OK; + } else { + return -E_INVALID_ARGS; + } + + int errCode = SQLiteUtils::AttachNewDatabase(dbHandle_, type, passwd, attachDbAbsPath, attachAsName); + if (errCode != E_OK) { + LOGE("handle attach to [%s] fail! errCode = [%d]", attachAsName.c_str(), errCode); + return CheckCorruptedStatus(errCode); + } + + if (engineState == EngineState::MAINDB) { + executorState_ = ExecutorState::MAIN_ATTACH_CACHE; + } else if (engineState == EngineState::CACHEDB) { + executorState_ = ExecutorState::CACHE_ATTACH_MAIN; + } else { + return -E_INVALID_ARGS; + } + LOGD("[singleVerExecutor][attachDb] current engineState[%u], executorState[%u]", static_cast(engineState), + static_cast(executorState_)); + return errCode; +} + +int SQLiteSingleVerStorageExecutor::GetMaxVersionIncacheDb(uint64_t &maxVersion) const +{ + sqlite3_stmt *statement = nullptr; + std::string sql; + if (executorState_ == ExecutorState::MAIN_ATTACH_CACHE) { + sql = GET_MAX_VER_CACHEDATA_FROM_MAINHANDLE; + } else if (executorState_ == ExecutorState::CACHE_ATTACH_MAIN) { + sql = GET_MAX_VER_CACHEDATA_FROM_CACHEHANDLE; + } else { + return -E_INVALID_ARGS; + } + + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + LOGE("GetStatement fail when get max version in cache db! errCode = [%d]", errCode); + goto END; + } + + do { + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + maxVersion = static_cast(sqlite3_column_int64(statement, 0)); + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + break; + } else { + LOGE("SQLite step failed:%d", errCode); + break; + } + } while (true); + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::MigrateDataItem(DataItem &dataItem, NotifyMigrateSyncData &syncData) +{ + // Put or delete. Prepare notify data here. + NotifyConflictAndObserverData notify; + notify.committedData = syncData.committedData; + int errCode = PutIntoConflictAndCommitForMigrateCache(dataItem, {dataItem.dev.empty(), dataItem.dev}, notify, + syncData.isPermitForceWrite); + if (errCode != E_OK) { + ResetForMigrateCacheData(); + LOGE("PutIntoConflictAndCommitForMigrateCache failed, errCode = %d", errCode); + return errCode; + } + // after solving conflict, the item should not be saved into mainDB + if (notify.dataStatus.isDefeated) { + LOGD("Data status is defeated:%d", errCode); + return errCode; + } + bool isUpdate = notify.dataStatus.preStatus != DataStatus::NOEXISTED; + sqlite3_stmt *statement = migrateSyncStatements_.GetDataSaveStatement(isUpdate); + if (statement == nullptr) { + LOGE("GetStatement fail when put migrating-data to main! "); + return -E_INVALID_ARGS; + } + + if ((dataItem.flag & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) != 0) { + errCode = EraseSyncData(dataItem.key); + goto END; + } + + errCode = BindSavedSyncData(statement, dataItem, dataItem.hashKey, { dataItem.origDev, dataItem.dev }, isUpdate); + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } else { + LOGD("StepWithRetry fail when put migrating-data to main!"); + } +END: + ResetForMigrateCacheData(); + return errCode; +} + +int SQLiteSingleVerStorageExecutor::CheckDataWithQuery(std::vector &dataItems) +{ + int errCode = E_OK; + sqlite3_stmt *stmt = nullptr; + for (auto &item : dataItems) { + if ((item.flag & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) == 0) { + continue; + } + std::string sql; + DBCommon::VectorToString(item.value, sql); + if (executorState_ == ExecutorState::CACHE_ATTACH_MAIN) { + static const std::string SYNC_DATA_TABLE = "sync_data"; + static const std::string SYNC_DATA_TABLE_MAIN = "maindb.sync_data"; + std::string::size_type startPos = sql.find(SYNC_DATA_TABLE); + if (startPos != std::string::npos) { + sql.replace(startPos, SYNC_DATA_TABLE.length(), SYNC_DATA_TABLE_MAIN); + } + } + errCode = SQLiteUtils::GetStatement(dbHandle_, sql, stmt); + if (errCode != E_OK) { + LOGE("Get Check miss query data statement failed. %d", errCode); + return errCode; + } + + errCode = CheckMissQueryDataItem(stmt, item.dev, item); + if (errCode != E_OK) { + LOGE("Check miss query data item failed. %d", errCode); + break; + } + SQLiteUtils::ResetStatement(stmt, true, errCode); + } + SQLiteUtils::ResetStatement(stmt, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::MigrateDataItems(std::vector &dataItems, NotifyMigrateSyncData &syncData) +{ + syncData.isRemote = ((dataItems[0].flag & DataItem::LOCAL_FLAG) == 0); + syncData.isRemoveDeviceData = (dataItems[0].flag & DataItem::REMOVE_DEVICE_DATA_FLAG) != 0 || + (dataItems[0].flag & DataItem::REMOVE_DEVICE_DATA_NOTIFY_FLAG) != 0; + + int errCode = CheckDataWithQuery(dataItems); + if (errCode != E_OK) { + LOGE("Check migrate data with query failed! errCode = [%d]", errCode); + goto END; + } + + for (auto &item : dataItems) { + // Remove device data owns one version itself. + // Get entry here. Prepare notify data in storageEngine. + if (syncData.isRemoveDeviceData) { + errCode = GetEntriesForNotifyRemoveDevData(item, syncData.entries); + if (errCode != E_OK) { + LOGE("Failed to get remove devices data"); + return errCode; + } + errCode = MigrateRmDevData(item); + LOGI("[PutMigratingDataToMain]Execute remove devices data! errCode = [%d]", errCode); + if (errCode != E_OK) { + break; + } + continue; + } + + if (item.neglect) { // Do not save this record if it is neglected + continue; + } + + errCode = MigrateDataItem(item, syncData); + if (errCode != E_OK) { + LOGE("Migrate data item to main db failed! errCode = [%d]", errCode); + break; + } + } +END: + ResetForMigrateCacheData(); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::MigrateSyncDataByVersion(uint64_t recordVer, NotifyMigrateSyncData &syncData, + std::vector &dataItems) +{ + int errCode = StartTransaction(TransactType::IMMEDIATE); + if (errCode != E_OK) { + return errCode; + } + + // Init migrate data. + errCode = InitMigrateData(); + if (errCode != E_OK) { + LOGE("Init migrate data failed, errCode = [%d]", errCode); + goto END; + } + + // fix dataItem timestamp for migrate + errCode = ProcessTimestampForSyncDataInCacheDB(dataItems); + if (errCode != E_OK) { + LOGE("Change the time stamp for migrate failed! errCode = [%d]", errCode); + goto END; + } + + errCode = MigrateDataItems(dataItems, syncData); + if (errCode != E_OK) { + goto END; + } + + // delete recordVersion data + errCode = DelCacheDbDataByVersion(recordVer); + if (errCode != E_OK) { + LOGE("Delete the migrated data in cacheDb! errCode = [%d]", errCode); + goto END; + } + + errCode = Commit(); + if (errCode != E_OK) { + LOGE("Commit data error and rollback, errCode = [%d]", errCode); + goto END; + } + return E_OK; +END: + Rollback(); + return errCode; +} + +int SQLiteSingleVerStorageExecutor::DelCacheDbDataByVersion(uint64_t version) const +{ + std::string sql; + if (executorState_ == ExecutorState::MAIN_ATTACH_CACHE) { + sql = MIGRATE_DEL_DATA_BY_VERSION_FROM_MAINHANDLE; + } else if (executorState_ == ExecutorState::CACHE_ATTACH_MAIN) { + sql = MIGRATE_DEL_DATA_BY_VERSION_FROM_CACHEHANDLE; + } else { + return -E_INVALID_ARGS; + } + + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + LOGE("GetStatement fail when delete cache data by version! errCode = [%d]", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindInt64ToStatement(statement, 1, static_cast(version)); + if (errCode != E_OK) { + LOGE("[SingleVerExe] Bind destDbNickName error:[%d]", errCode); + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::VacuumLocalData() const +{ + std::string sql; + if (executorState_ == ExecutorState::MAIN_ATTACH_CACHE) { + sql = MIGRATE_VACUUM_LOCAL_SQL_FROM_MAINHANDLE; + } else if (executorState_ == ExecutorState::CACHE_ATTACH_MAIN) { + sql = MIGRATE_VACUUM_LOCAL_SQL_FROM_CACHEHANDLE; + } else { + return -E_INVALID_ARGS; + } + + int errCode = SQLiteUtils::ExecuteRawSQL(dbHandle_, sql); + if (errCode != E_OK) { + LOGE("SQLite sync mode failed: %d", errCode); + } + + return CheckCorruptedStatus(errCode); +} + +// The local table data is only for local reading and writing, which can be sensed by itself. +// The current migration process does not provide callback subscription function. +int SQLiteSingleVerStorageExecutor::MigrateLocalData() +{ + // Nick name "main" represent current database(dbhande) in sqlite grammar + std::string migrateLocaldataSql; + if (executorState_ == ExecutorState::MAIN_ATTACH_CACHE) { + migrateLocaldataSql = MIGRATE_LOCAL_SQL_FROM_MAINHANDLE; + } else if (executorState_ == ExecutorState::CACHE_ATTACH_MAIN) { + migrateLocaldataSql = MIGRATE_LOCAL_SQL_FROM_CACHEHANDLE; + } else { + return -E_INVALID_ARGS; + } + + int errCode = SQLiteUtils::ExecuteRawSQL(dbHandle_, migrateLocaldataSql); + if (errCode != E_OK) { + LOGW("Failed to migrate the local data:%d", errCode); + return CheckCorruptedStatus(errCode); + } + + return VacuumLocalData(); +} + +int SQLiteSingleVerStorageExecutor::BindSyncDataInCacheMode(sqlite3_stmt *statement, + const DataItem &dataItem, const Key &hashKey, uint64_t recordVersion) const +{ + int errCode = BindPrimaryKeySyncDataInCacheMode(statement, hashKey, recordVersion); + if (errCode != E_OK) { + LOGE("Bind saved sync data primary key failed:%d", errCode); + return errCode; + } + + // if delete flag is set, just use the hash key instead of the key + if ((dataItem.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG) { + errCode = SQLiteUtils::MapSQLiteErrno(sqlite3_bind_zeroblob(statement, BIND_CACHE_SYNC_KEY_INDEX, -1)); + } else { + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_CACHE_SYNC_KEY_INDEX, dataItem.key, false); + } + + if (errCode != E_OK) { + LOGE("Bind saved sync data key failed:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_CACHE_SYNC_VAL_INDEX, dataItem.value, true); + if (errCode != E_OK) { + LOGE("Bind saved sync data value failed:%d", errCode); + return errCode; + } + + LOGD("Write timestamp:%" PRIu64 " timestamp:%" PRIu64 ", flag:%" PRIu64 ", version:%" PRIu64, + dataItem.writeTimestamp, dataItem.timestamp, dataItem.flag, recordVersion); + errCode = SQLiteUtils::BindInt64ToStatement(statement, BIND_CACHE_SYNC_FLAG_INDEX, + static_cast(dataItem.flag)); + if (errCode != E_OK) { + LOGE("Bind saved sync data flag failed:%d", errCode); + return errCode; + } + errCode = BindTimestampSyncDataInCacheMode(statement, dataItem); + if (errCode != E_OK) { + LOGE("Bind saved sync data time stamp failed:%d", errCode); + return errCode; + } + return BindDevSyncDataInCacheMode(statement, dataItem.origDev, dataItem.dev); +} + +int SQLiteSingleVerStorageExecutor::BindPrimaryKeySyncDataInCacheMode( + sqlite3_stmt *statement, const Key &hashKey, uint64_t recordVersion) const +{ + int errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_CACHE_SYNC_HASH_KEY_INDEX, hashKey, false); + if (errCode != E_OK) { + LOGE("Bind saved sync data hash key failed:%d", errCode); + return errCode; + } + errCode = SQLiteUtils::BindInt64ToStatement(statement, BIND_CACHE_SYNC_VERSION_INDEX, recordVersion); + if (errCode != E_OK) { + LOGE("Bind saved sync data version failed:%d", errCode); + } + return errCode; +} + +int SQLiteSingleVerStorageExecutor::BindTimestampSyncDataInCacheMode( + sqlite3_stmt *statement, const DataItem &dataItem) const +{ + int errCode = SQLiteUtils::BindInt64ToStatement(statement, BIND_CACHE_SYNC_STAMP_INDEX, dataItem.timestamp); + if (errCode != E_OK) { + LOGE("Bind saved sync data stamp failed:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindInt64ToStatement(statement, BIND_CACHE_SYNC_W_TIME_INDEX, dataItem.writeTimestamp); + if (errCode != E_OK) { + LOGE("Bind saved sync data write stamp failed:%d", errCode); + } + return errCode; +} + +int SQLiteSingleVerStorageExecutor::BindDevSyncDataInCacheMode(sqlite3_stmt *statement, + const std::string &origDev, const std::string &deviceName) const +{ + std::string devName = DBCommon::TransferHashString(deviceName); + std::vector devVect(devName.begin(), devName.end()); + int errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_CACHE_SYNC_DEV_INDEX, devVect, true); + if (errCode != E_OK) { + LOGE("Bind dev for sync data failed:%d", errCode); + return errCode; + } + + std::vector origDevVect(origDev.begin(), origDev.end()); + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_CACHE_SYNC_ORI_DEV_INDEX, origDevVect, true); + if (errCode != E_OK) { + LOGE("Bind orig dev for sync data failed:%d", errCode); + } + return errCode; +} + +int SQLiteSingleVerStorageExecutor::GetExpandedCheckSql(QueryObject query, DataItem &dataItem) +{ + int errCode = E_OK; + SqliteQueryHelper helper = query.GetQueryHelper(errCode); + + std::string sql; + std::string expandedSql; + errCode = helper.GetSyncDataCheckSql(sql); + if (errCode != E_OK) { + LOGE("Get sync data check sql failed"); + return errCode; + } + sqlite3_stmt *stmt = nullptr; + errCode = SQLiteUtils::GetStatement(dbHandle_, sql, stmt); + if (errCode != E_OK) { + LOGE("Get statement fail. %d", errCode); + return -E_INVALID_QUERY_FORMAT; + } + + errCode = helper.BindSyncDataCheckStmt(stmt, dataItem.key); + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::ExpandedSql(stmt, expandedSql); + if (errCode != E_OK) { + LOGE("Get expand sql fail. %d", errCode); + } + DBCommon::StringToVector(expandedSql, dataItem.value); +END: + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; +} + +int SQLiteSingleVerStorageExecutor::SaveSyncDataItemInCacheMode(DataItem &dataItem, const DeviceInfo &deviceInfo, + Timestamp &maxStamp, uint64_t recordVersion, const QueryObject &query) +{ + Key hashKey; + int errCode = E_OK; + if ((dataItem.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG) { + hashKey = dataItem.key; + } else { + errCode = DBCommon::CalcValueHash(dataItem.key, hashKey); + if (errCode != E_OK) { + return errCode; + } + } + + if ((dataItem.flag & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) != 0) { + errCode = GetExpandedCheckSql(query, dataItem); // record check sql in value for miss query data + if (errCode != E_OK) { + LOGE("Get sync data check sql failed. %d", errCode); + return errCode; + } + } + + std::string origDev = dataItem.origDev; + if (((dataItem.flag & DataItem::LOCAL_FLAG) != 0) && dataItem.origDev.empty()) { + origDev.clear(); + } + dataItem.dev = deviceInfo.deviceName; + dataItem.origDev = origDev; + errCode = SaveSyncDataToCacheDatabase(dataItem, hashKey, recordVersion); + if (errCode == E_OK) { + maxStamp = std::max(dataItem.timestamp, maxStamp); + } else { + LOGE("Save sync data to db failed:%d", errCode); + } + return ResetForSavingCacheData(SingleVerDataType::SYNC_TYPE); +} + +int SQLiteSingleVerStorageExecutor::SaveSyncDataToCacheDatabase(const DataItem &dataItem, + const Key &hashKey, uint64_t recordVersion) const +{ + auto statement = saveSyncStatements_.GetDataSaveStatement(false); + if (statement == nullptr) { + return -E_INVALID_ARGS; + } + int errCode = BindSyncDataInCacheMode(statement, dataItem, hashKey, recordVersion); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } + return errCode; +} + +int SQLiteSingleVerStorageExecutor::PutLocalDataToCacheDB(const LocalDataItem &dataItem) const +{ + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, INSERT_CACHE_LOCAL_SQL, statement); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = BindLocalDataInCacheMode(statement, dataItem); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } + +ERROR: + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::BindLocalDataInCacheMode(sqlite3_stmt *statement, + const LocalDataItem &dataItem) const +{ + int errCode = SQLiteUtils::BindBlobToStatement(statement, + BIND_CACHE_LOCAL_HASH_KEY_INDEX, dataItem.hashKey, false); + if (errCode != E_OK) { + LOGE("[SingleVerExe][BindLocalData]Bind hash key error:%d", errCode); + return errCode; + } + + // if delete flag is set, just use the hash key instead of the key + if ((dataItem.flag & DataItem::DELETE_FLAG) == DataItem::DELETE_FLAG) { + errCode = SQLiteUtils::MapSQLiteErrno(sqlite3_bind_zeroblob(statement, BIND_CACHE_LOCAL_KEY_INDEX, -1)); + } else { + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_CACHE_LOCAL_KEY_INDEX, dataItem.key, false); + } + + if (errCode != E_OK) { + LOGE("Bind saved sync data key failed:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindBlobToStatement(statement, BIND_CACHE_LOCAL_VAL_INDEX, dataItem.value, true); + if (errCode != E_OK) { + LOGE("[SingleVerExe][BindLocalData]Bind value error:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindInt64ToStatement(statement, BIND_CACHE_LOCAL_TIMESTAMP_INDEX, dataItem.timestamp); + if (errCode != E_OK) { + LOGE("[SingleVerExe][BindLocalData]Bind timestamp error:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindInt64ToStatement(statement, BIND_CACHE_LOCAL_FLAG_INDEX, + static_cast(dataItem.flag)); + if (errCode != E_OK) { + LOGE("[SingleVerExe][BindLocalData]Bind local data flag failed:%d", errCode); + return errCode; + } + + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::PutIntoConflictAndCommitForMigrateCache(DataItem &dataItem, + const DeviceInfo &deviceInfo, NotifyConflictAndObserverData ¬ify, bool isPermitForceWrite) +{ + int errCode = PrepareForNotifyConflictAndObserver(dataItem, deviceInfo, notify, isPermitForceWrite); + if (errCode != E_OK) { + errCode = (errCode == -E_NOT_FOUND ? E_OK : errCode); + if (errCode == -E_IGNORE_DATA) { + notify.dataStatus.isDefeated = true; + errCode = E_OK; + } + return errCode; + } + + // If delete data, the key is empty. + if (isSyncMigrating_ && dataItem.key.empty()) { + dataItem.key = notify.getData.key; + } + + PutConflictData(dataItem, notify.getData, deviceInfo, notify.dataStatus, notify.committedData); + if (notify.dataStatus.isDefeated) { + LOGD("Data status is defeated:%d", errCode); + return ResetForMigrateCacheData(); + } + + PutIntoCommittedData(dataItem, notify.getData, notify.dataStatus, notify.hashKey, notify.committedData); + return ResetForMigrateCacheData(); +} + +int SQLiteSingleVerStorageExecutor::GetMinTimestampInCacheDB(Timestamp &minStamp) const +{ + if (dbHandle_ == nullptr) { + return E_OK; + } + std::string sql = ((executorState_ == ExecutorState::CACHE_ATTACH_MAIN) ? + SELECT_NATIVE_MIN_TIMESTAMP_IN_CACHE_SYNC_DATA_SQL : + SELECT_NATIVE_MIN_TIMESTAMP_IN_CACHE_SYNC_DATA_SQL_FROM_MAINHANDLE); + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + goto ERROR; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + minStamp = static_cast(sqlite3_column_int64(statement, 0)); // get the first column + LOGD("Min time stamp in cacheDB is %" PRIu64, minStamp); + errCode = E_OK; + } else { + LOGE("GetMinTimestampInCacheDB failed, errCode = %d.", errCode); + } + +ERROR: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteSingleVerStorageExecutor::InitMigrateTimestampOffset() +{ + // Not first migrate, migrateTimeOffset_ has been set. + if (migrateTimeOffset_ != 0) { + return E_OK; + } + + // Get min timestamp of local data in sync_data, cacheDB. + Timestamp minTimeInCache = 0; + int errCode = GetMinTimestampInCacheDB(minTimeInCache); + if (errCode != E_OK) { + return errCode; + } + + // There is no native data in cacheDB, cannot get accurate migrateTimeOffset_ now. + if (minTimeInCache == 0) { + migrateTimeOffset_ = -1; + LOGI("Time offset during migrating is -1."); + return E_OK; + } + + // Get max timestamp in mainDB. + Timestamp maxTimeInMain = 0; + InitCurrentMaxStamp(maxTimeInMain); + + // Get timestamp offset between mainDB and cacheDB. + // The purpose of -1 is to ensure that the first data record in the original cacheDB is 1 greater than + // the last data record in the original mainDB after the migration. + migrateTimeOffset_ = minTimeInCache - maxTimeInMain - 1; + LOGI("Min timestamp in cacheDB is %" PRIu64 ", max timestamp in mainDB is %" PRIu64 ". Time offset during migrating" + " is %" PRId64 ".", minTimeInCache, maxTimeInMain, migrateTimeOffset_); + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::ProcessTimestampForSyncDataInCacheDB(std::vector &dataItems) +{ + if (dataItems.empty()) { + LOGE("[SQLiteSingleVerStorageExecutor::ProcessTimestampForCacheDB] Invalid parameter : dataItems."); + return -E_INVALID_ARGS; + } + + // Get the offset between the min timestamp in dataitems and max timestamp in mainDB. + int errCode = InitMigrateTimestampOffset(); + if (errCode != E_OK) { + return errCode; + } + + // Set real timestamp for DataItem in dataItems and get the max timestamp in these dataitems. + Timestamp maxTimeInDataItems = 0; + for (auto &item : dataItems) { + item.timestamp -= migrateTimeOffset_; + maxTimeInDataItems = std::max(maxTimeInDataItems, item.timestamp); + } + + // Update max timestamp in mainDB. + maxTimestampInMainDB_ = maxTimeInDataItems; + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::GetEntriesForNotifyRemoveDevData(const DataItem &item, + std::vector &entries) const +{ + // When removing device data, key is 'remove', value is device name. + if (item.key != REMOVE_DEVICE_DATA_KEY) { + LOGE("Invalid key. Can not notify remove device data."); + return -E_INVALID_ARGS; + } + if ((item.flag & DataItem::REMOVE_DEVICE_DATA_NOTIFY_FLAG) == 0) { + LOGI("No need to notify remove device data."); + return E_OK; + } + entries.clear(); + std::string dev; + DBCommon::VectorToString(item.value, dev); + return GetAllSyncedEntries(dev, entries); +} + +int SQLiteSingleVerStorageExecutor::InitMigrateData() +{ + // Sync_data already in migrating. Need not to init data. + if (isSyncMigrating_) { + return E_OK; + } + ClearMigrateData(); + std::string querySQL; + std::string insertSQL; + std::string updateSQL; + if (executorState_ == ExecutorState::MAIN_ATTACH_CACHE) { + querySQL = SELECT_SYNC_HASH_SQL; + insertSQL = MIGRATE_INSERT_DATA_TO_MAINDB_FROM_MAINHANDLE; + updateSQL = MIGRATE_UPDATE_DATA_TO_MAINDB_FROM_MAINHANDLE; + } else if (executorState_ == ExecutorState::CACHE_ATTACH_MAIN) { + querySQL = SELECT_MAIN_SYNC_HASH_SQL_FROM_CACHEHANDLE; + insertSQL = MIGRATE_INSERT_DATA_TO_MAINDB_FROM_CACHEHANDLE; + updateSQL = MIGRATE_UPDATE_DATA_TO_MAINDB_FROM_CACHEHANDLE; + } else { + LOGE("[InitMigrateData] executor in an error state[%u]!", static_cast(executorState_)); + return -E_INVALID_DB; + } + int errCode = PrepareForSavingData(querySQL, insertSQL, updateSQL, migrateSyncStatements_); + if (errCode != E_OK) { + LOGE("Prepare migrateSyncStatements_ fail, errCode = %d", errCode); + return errCode; + } + isSyncMigrating_ = true; + return errCode; +} + +void SQLiteSingleVerStorageExecutor::ClearMigrateData() +{ + // Reset data. + migrateTimeOffset_ = 0; + maxTimestampInMainDB_ = 0; + + // Reset statement. + int errCode = migrateSyncStatements_.ResetStatement(); + if (errCode != E_OK) { + LOGE("Reset migrateSync Statements failed, errCode = %d", errCode); + } + + isSyncMigrating_ = false; +} + +int SQLiteSingleVerStorageExecutor::GetMaxTimestampDuringMigrating(Timestamp &maxTimestamp) const +{ + if (maxTimestampInMainDB_ == 0) { + return -E_NOT_INIT; + } + maxTimestamp = maxTimestampInMainDB_; + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::DeleteMetaData(const std::vector &keys) +{ + sqlite3_stmt *statement = nullptr; + const std::string sql = attachMetaMode_ ? REMOVE_ATTACH_META_VALUE_SQL : REMOVE_META_VALUE_SQL; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + return errCode; + } + + for (const auto &key : keys) { + errCode = SQLiteUtils::BindBlobToStatement(statement, 1, key, false); // first arg. + if (errCode != E_OK) { + break; + } + + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + break; + } + errCode = E_OK; + SQLiteUtils::ResetStatement(statement, false, errCode); + } + + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::DeleteMetaDataByPrefixKey(const Key &keyPrefix) +{ + sqlite3_stmt *statement = nullptr; + const std::string sql = attachMetaMode_ ? + REMOVE_ATTACH_META_VALUE_BY_KEY_PREFIX_SQL : REMOVE_META_VALUE_BY_KEY_PREFIX_SQL; + + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::BindPrefixKey(statement, 1, keyPrefix); // 1 is first arg. + if (errCode == E_OK) { + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } + } + + SQLiteUtils::ResetStatement(statement, true, errCode); + return CheckCorruptedStatus(errCode); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor_sql.h b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor_sql.h new file mode 100644 index 00000000..a0a071e9 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor_sql.h @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_SINGLE_VER_STORAGE_EXECUTOR_SQL_H +#define SQLITE_SINGLE_VER_STORAGE_EXECUTOR_SQL_H + +#include + +#include "types_export.h" + +namespace DistributedDB { + // cache.sync_data is design for migrating action after process restart + const std::string INSERT_LOCAL_SQL = + "INSERT OR REPLACE INTO local_data VALUES(?,?,?,?);"; + const std::string INSERT_LOCAL_SQL_FROM_CACHEHANDLE = + "INSERT OR REPLACE INTO maindb.local_data VALUES(?,?,?,?);"; + + const std::string INSERT_CACHE_LOCAL_SQL = + "INSERT OR REPLACE INTO local_data VALUES(?,?,?,?,?);"; + + const std::string UPDATE_LOCAL_SQL_FROM_CACHEHANDLE = + "UPDATE maindb.local_data SET key=?,value=?,timestamp=? where hash_key=?"; + + const std::string UPDATE_CACHE_LOCAL_SQL = + "UPDATE local_data SET key=?,value=?,timestamp=? where hash_key=?"; + + const std::string INSERT_META_SQL = + "INSERT OR REPLACE INTO meta_data VALUES(?,?);"; + + const std::string INSERT_ATTACH_META_SQL = + "INSERT OR REPLACE INTO meta.meta_data VALUES(?,?);"; + + const std::string INSERT_SYNC_SQL = + "INSERT INTO sync_data VALUES(?,?,?,?,?,?,?,?);"; + + const std::string UPDATE_SYNC_SQL = + "UPDATE sync_data SET key=?,value=?,timestamp=?,flag=?,device=?,ori_device=?,w_timestamp=? WHERE hash_key=?;"; + + const std::string INSERT_CACHE_SYNC_SQL = + "INSERT OR REPLACE INTO sync_data VALUES(?,?,?,?,?,?,?,?,?);"; + const std::string INSERT_CACHE_SYNC_SQL_FROM_MAINHANDLE = + "INSERT OR REPLACE INTO cache.sync_data VALUES(?,?,?,?,?,?,?,?,?);"; + + const std::string UPDATE_CACHE_SYNC_SQL = + "UPDATE sync_data SET key=?,value=?,timestamp=?,flag=?,device=?,ori_device=?,w_timestamp=? WHERE hash_key=?;"; + + const std::string UPDATE_CACHE_SYNC_SQL_FROM_MAINHANDLE = + "UPDATE cache.sync_data SET key=?,value=?,timestamp=?,flag=?,device=?,ori_device=?,w_timestamp=? " + "WHERE hash_key=?;"; + + const std::string DELETE_LOCAL_SQL = + "DELETE FROM local_data WHERE key=?;"; + const std::string DELETE_LOCAL_SQL_FROM_CACHEHANDLE = + "DELETE FROM maindb.local_data WHERE key=?;"; + + const std::string SELECT_ALL_META_KEYS = + "SELECT key FROM meta_data;"; + + const std::string SELECT_ATTACH_ALL_META_KEYS = + "SELECT key FROM meta.meta_data;"; + + const std::string SELECT_ALL_SYNC_ENTRIES_BY_DEV = + "SELECT key, value FROM sync_data WHERE device=? AND (flag&0x03=0);"; + + const std::string SELECT_ALL_SYNC_ENTRIES_BY_DEV_FROM_CACHEHANDLE = + "SELECT key, value FROM maindb.sync_data WHERE device=? AND (flag&0x03=0);"; + + const std::string SELECT_LOCAL_VALUE_TIMESTAMP_SQL = + "SELECT value, timestamp FROM local_data WHERE key=?;"; + + const std::string SELECT_SYNC_SQL = + "SELECT * FROM sync_data WHERE key=?;"; + + const std::string SELECT_SYNC_VALUE_WTIMESTAMP_SQL = + "SELECT value, w_timestamp FROM sync_data WHERE key=?;"; + + const std::string SELECT_SYNC_HASH_SQL = + "SELECT * FROM sync_data WHERE hash_key=?;"; + + const std::string SELECT_CACHE_SYNC_HASH_SQL = + "SELECT * FROM sync_data WHERE hash_key=? AND version=?;"; + const std::string SELECT_CACHE_SYNC_HASH_SQL_FROM_MAINHANDLE = + "SELECT * FROM cache.sync_data WHERE hash_key=? AND version=?;"; + + const std::string SELECT_LOCAL_HASH_SQL = + "SELECT * FROM local_data WHERE hash_key=?;"; + + const std::string SELECT_CACHE_LOCAL_HASH_SQL = + "SELECT * FROM local_data WHERE hash_key=?;"; + + const std::string SELECT_META_VALUE_SQL = + "SELECT value FROM meta_data WHERE key=?;"; + + const std::string SELECT_ATTACH_META_VALUE_SQL = + "SELECT value FROM meta.meta_data WHERE key=?;"; + + const std::string SELECT_MAX_TIMESTAMP_SQL = + "SELECT MAX(timestamp) FROM sync_data;"; + const std::string SELECT_MAX_TIMESTAMP_SQL_FROM_CACHEHANDLE = + "SELECT MAX(timestamp) FROM maindb.sync_data;"; + + const std::string SELECT_NATIVE_MIN_TIMESTAMP_IN_CACHE_SYNC_DATA_SQL = + "SELECT MIN(timestamp) FROM sync_data WHERE flag&0x02=0x02;"; + const std::string SELECT_NATIVE_MIN_TIMESTAMP_IN_CACHE_SYNC_DATA_SQL_FROM_MAINHANDLE = + "SELECT MIN(timestamp) FROM cache.sync_data WHERE flag&0x02=0x02;"; + + const std::string SELECT_SYNC_ENTRIES_SQL = + "SELECT * FROM sync_data WHERE timestamp >= ? AND timestamp < ? AND (flag&0x02=0x02) ORDER BY timestamp ASC;"; + + const std::string SELECT_SYNC_DELETED_ENTRIES_SQL = + "SELECT * FROM sync_data WHERE timestamp >= ? AND timestamp < ? AND (flag&0x03=0x03) ORDER BY timestamp ASC;"; + + const std::string SELECT_SYNC_MODIFY_SQL = + "SELECT * FROM sync_data WHERE timestamp >= ? AND timestamp < ? AND (flag&0x03=0x02) ORDER BY timestamp ASC;"; + + const std::string SELECT_SYNC_PREFIX_SQL = + "SELECT key, value FROM sync_data WHERE key>=? AND key<=? AND (flag&0x01=0) ORDER BY key ASC;"; + + const std::string SELECT_SYNC_ROWID_PREFIX_SQL = + "SELECT rowid FROM sync_data WHERE key>=? AND key<=? AND (flag&0x01=0) ORDER BY key ASC;"; + + const std::string SELECT_SYNC_DATA_BY_ROWID_SQL = + "SELECT key, value FROM sync_data WHERE rowid=?;"; + + const std::string SELECT_LOCAL_PREFIX_SQL = + "SELECT key, value FROM local_data WHERE key>=? AND key<=? ORDER BY key ASC;"; + + const std::string SELECT_COUNT_SYNC_PREFIX_SQL = + "SELECT count(key) FROM sync_data WHERE key>=? AND key<=? AND (flag&0x01=0);"; + + const std::string REMOVE_DEV_DATA_SQL = + "DELETE FROM sync_data WHERE device=? AND (flag&0x02=0);"; + const std::string REMOVE_DEV_DATA_SQL_FROM_CACHEHANDLE = + "DELETE FROM maindb.sync_data WHERE device=? AND (flag&0x02=0);"; + + const std::string SELECT_ENTRY_DEVICE = + "SELECT ori_device, device FROM sync_data WHERE key=?;"; + + // sql for migrating data + const std::string MIGRATE_LOCAL_SQL_FROM_CACHEHANDLE = + "INSERT OR REPLACE INTO maindb.local_data select key, value, timestamp, hash_key from main.local_data;"; + const std::string MIGRATE_LOCAL_SQL_FROM_MAINHANDLE = + "INSERT OR REPLACE INTO main.local_data select key, value, timestamp, hash_key from cache.local_data;"; + + const std::string MIGRATE_VACUUM_LOCAL_SQL_FROM_CACHEHANDLE = + "DELETE FROM maindb.local_data where hash_key in (select hash_key FROM maindb.local_data where key is null);"; + const std::string MIGRATE_VACUUM_LOCAL_SQL_FROM_MAINHANDLE = + "DELETE FROM main.local_data where hash_key in (select hash_key FROM main.local_data where key is null);"; + + // version is index, order by better than MIN() + const std::string MIGRATE_SELECT_MIN_VER_CACHEDATA_FROM_CACHEHANDLE = + "SELECT * FROM sync_data where version = (select version from sync_data order by version limit 1);"; + const std::string MIGRATE_SELECT_MIN_VER_CACHEDATA_FROM_MAINHANDLE = + "SELECT * FROM cache.sync_data where version = (select version from cache.sync_data order by version limit 1);"; + + const std::string GET_MAX_VER_CACHEDATA_FROM_CACHEHANDLE = + "select version from sync_data order by version DESC limit 1;"; + const std::string GET_MAX_VER_CACHEDATA_FROM_MAINHANDLE = + "select version from cache.sync_data order by version DESC limit 1;"; + + const std::string MIGRATE_INSERT_DATA_TO_MAINDB_FROM_CACHEHANDLE = + "INSERT INTO maindb.sync_data VALUES(?,?,?,?,?,?,?,?);"; + const std::string MIGRATE_UPDATE_DATA_TO_MAINDB_FROM_CACHEHANDLE = + "UPDATE maindb.sync_data SET key=?,value=?,timestamp=?,flag=?,device=?,ori_device=?,w_timestamp=? " + "WHERE hash_key=?;"; + + const std::string MIGRATE_INSERT_DATA_TO_MAINDB_FROM_MAINHANDLE = + "INSERT INTO sync_data VALUES(?,?,?,?,?,?,?,?);"; + const std::string MIGRATE_UPDATE_DATA_TO_MAINDB_FROM_MAINHANDLE = + "UPDATE sync_data SET key=?,value=?,timestamp=?,flag=?,device=?,ori_device=?,w_timestamp=? WHERE hash_key=?;"; + + const std::string MIGRATE_DEL_DATA_BY_VERSION_FROM_CACHEHANDLE = + "DELETE FROM sync_data WHERE version=?;"; + const std::string MIGRATE_DEL_DATA_BY_VERSION_FROM_MAINHANDLE = + "DELETE FROM cache.sync_data WHERE version=?;"; + + const std::string SELECT_MAIN_SYNC_HASH_SQL_FROM_CACHEHANDLE = "SELECT * FROM maindb.sync_data WHERE hash_key=?;"; + + const std::string REMOVE_META_VALUE_SQL = + "DELETE FROM meta_data WHERE key=?;"; + const std::string REMOVE_ATTACH_META_VALUE_SQL = + "DELETE FROM meta.meta_data WHERE key=?;"; + + const std::string CHECK_DB_INTEGRITY_SQL = "PRAGMA integrity_check;"; + + const std::string REMOVE_META_VALUE_BY_KEY_PREFIX_SQL = + "DELETE FROM meta_data WHERE key>=? AND key<=?;"; + const std::string REMOVE_ATTACH_META_VALUE_BY_KEY_PREFIX_SQL = + "DELETE FROM meta.meta_data WHERE key>=? AND key<=?;"; + + const std::string DELETE_SYNC_DATA_WITH_HASHKEY = "DELETE FROM sync_data where hash_key = ?;"; + + const std::string DELETE_SYNC_DATA_WITH_HASHKEY_FROM_CACHEHANDLE = + "DELETE FROM maindb.sync_data where hash_key = ?;"; + + const std::string GET_SYNC_DATA_TIRGGER_SQL = + "SELECT name FROM SQLITE_MASTER WHERE TYPE = 'trigger' AND TBL_NAME = 'sync_data' AND name like ?;"; + + const int BIND_KV_KEY_INDEX = 1; + const int BIND_KV_VAL_INDEX = 2; + const int BIND_LOCAL_TIMESTAMP_INDEX = 3; + const int BIND_LOCAL_HASH_KEY_INDEX = 4; + + // binding index just for the get sync data sql + const int BIND_BEGIN_STAMP_INDEX = 1; + const int BIND_END_STAMP_INDEX = 2; + + // mainDB + const int BIND_SYNC_KEY_INDEX = 1; + const int BIND_SYNC_VAL_INDEX = 2; + const int BIND_SYNC_STAMP_INDEX = 3; + const int BIND_SYNC_FLAG_INDEX = 4; + const int BIND_SYNC_DEV_INDEX = 5; + const int BIND_SYNC_ORI_DEV_INDEX = 6; + const int BIND_SYNC_HASH_KEY_INDEX = 7; + const int BIND_SYNC_W_TIME_INDEX = 8; + + const int BIND_SYNC_UPDATE_W_TIME_INDEX = 7; + const int BIND_SYNC_UPDATE_HASH_KEY_INDEX = 8; + + // cacheDB + const int BIND_CACHE_LOCAL_KEY_INDEX = 1; + const int BIND_CACHE_LOCAL_VAL_INDEX = 2; + const int BIND_CACHE_LOCAL_TIMESTAMP_INDEX = 3; + const int BIND_CACHE_LOCAL_HASH_KEY_INDEX = 4; + const int BIND_CACHE_LOCAL_FLAG_INDEX = 5; + + const int BIND_CACHE_SYNC_KEY_INDEX = 1; + const int BIND_CACHE_SYNC_VAL_INDEX = 2; + const int BIND_CACHE_SYNC_STAMP_INDEX = 3; + const int BIND_CACHE_SYNC_FLAG_INDEX = 4; + const int BIND_CACHE_SYNC_DEV_INDEX = 5; + const int BIND_CACHE_SYNC_ORI_DEV_INDEX = 6; + const int BIND_CACHE_SYNC_HASH_KEY_INDEX = 7; + const int BIND_CACHE_SYNC_W_TIME_INDEX = 8; + const int BIND_CACHE_SYNC_VERSION_INDEX = 9; + + // select result index for the item for sync database + const int SYNC_RES_KEY_INDEX = 0; + const int SYNC_RES_VAL_INDEX = 1; + const int SYNC_RES_TIME_INDEX = 2; + const int SYNC_RES_FLAG_INDEX = 3; + const int SYNC_RES_DEVICE_INDEX = 4; + const int SYNC_RES_ORI_DEV_INDEX = 5; + const int SYNC_RES_HASH_KEY_INDEX = 6; + const int SYNC_RES_W_TIME_INDEX = 7; + const int SYNC_RES_VERSION_INDEX = 8; // Available in cacheDB. + + // get kv data Response index + const int GET_KV_RES_LOCAL_TIME_INDEX = 1; + const int GET_KV_RES_SYNC_TIME_INDEX = 1; + + const int BIND_ORI_DEVICE_ID = 0; + const int BIND_PRE_DEVICE_ID = 1; + + const Key REMOVE_DEVICE_DATA_KEY = {'r', 'e', 'm', 'o', 'v', 'e'}; +} // namespace DistributedDB + +#endif // SQLITE_SINGLE_VER_STORAGE_EXECUTOR_SQL_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor_subscribe.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor_subscribe.cpp new file mode 100644 index 00000000..3ab1bd85 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_single_ver_storage_executor_subscribe.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_single_ver_storage_executor.h" + +#include "log_print.h" +#include "db_common.h" +#include "db_errno.h" +#include "sqlite_single_ver_storage_executor_sql.h" + +namespace DistributedDB { +using namespace TriggerMode; + +int SQLiteSingleVerStorageExecutor::CheckQueryObjectLegal(QueryObject &queryObj) const +{ + int errCode = E_OK; + SqliteQueryHelper helper = queryObj.GetQueryHelper(errCode); + if (errCode != E_OK) { + LOGE("Get query helper failed [%d]!", errCode); + return errCode; + } + + sqlite3_stmt *statement = nullptr; + errCode = helper.GetQuerySyncStatement(dbHandle_, 0, INT64_MAX, statement); // (0, INT64_MAX):max range + int ret = E_OK; + SQLiteUtils::ResetStatement(statement, true, ret); + if (ret != E_OK) { + LOGW("Failed to reset statement. error:%d", ret); + } + return CheckCorruptedStatus(errCode); +} + +int SQLiteSingleVerStorageExecutor::CheckMissQueryDataItem(sqlite3_stmt *stmt, const std::string &deviceName, + DataItem &item) +{ + int errCode = SQLiteUtils::StepWithRetry(stmt, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + // the value with same hashKey in DB matched the query + std::vector dev; + errCode = SQLiteUtils::GetColumnBlobValue(stmt, SYNC_RES_DEVICE_INDEX, dev); + if (errCode != E_OK) { + LOGE("Get data device info failed. %d", errCode); + return errCode; + } + auto timestamp = static_cast(sqlite3_column_int64(stmt, SYNC_RES_TIME_INDEX)); + std::string device = std::string(dev.begin(), dev.end()); + // this data item should be neglected when it's out of date of it's from same device + // otherwise, it should be erased after resolved the conflict + item.neglect = (timestamp > item.timestamp) || + (timestamp == item.timestamp && device == DBCommon::TransferHashString(deviceName)); + return E_OK; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + // the value with same hashKey in DB does not match the query, this data item should be neglected. + item.neglect = true; + return E_OK; + } + LOGE("Check sync data failed %d", errCode); + return errCode; +} + +// check the data with REMOTE_DEVICE_DATA_MISS_QUERY flag need neglect or not +int SQLiteSingleVerStorageExecutor::CheckMissQueryDataItems(sqlite3_stmt *&stmt, const SqliteQueryHelper &helper, + const DeviceInfo &deviceInfo, std::vector &dataItems) +{ + int errCode = E_OK; + for (auto &item : dataItems) { + if ((item.flag & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) != 0 && !item.key.empty()) { + errCode = helper.BindSyncDataCheckStmt(stmt, item.key); + if (errCode != E_OK) { + LOGE("Bind sync data check statement failed %d", errCode); + break; + } + errCode = CheckMissQueryDataItem(stmt, deviceInfo.deviceName, item); + if (errCode != E_OK) { + LOGE("Check miss query data item failed. %d", errCode); + return errCode; + } + SQLiteUtils::ResetStatement(stmt, false, errCode); + } + } + return errCode; +} + +int SQLiteSingleVerStorageExecutor::CheckDataWithQuery(QueryObject query, std::vector &dataItems, + const DeviceInfo &deviceInfo) +{ + int errCode = E_OK; + if (query.Empty()) { + LOGD("Query is empty, skip check."); + return E_OK; + } + SqliteQueryHelper helper = query.GetQueryHelper(errCode); + if (errCode != E_OK) { + LOGE("Get query helper failed [%d]!", errCode); + return errCode; + } + std::string sql; + errCode = helper.GetSyncDataCheckSql(sql); + if (errCode != E_OK) { + LOGE("Get sync data check sql failed"); + return errCode; + } + sqlite3_stmt *stmt = nullptr; + errCode = SQLiteUtils::GetStatement(dbHandle_, sql, stmt); + if (errCode != E_OK) { + LOGE("Get statement fail. %d", errCode); + return -E_INVALID_QUERY_FORMAT; + } + errCode = CheckMissQueryDataItems(stmt, helper, deviceInfo, dataItems); + if (errCode != E_OK) { + LOGE("check data with query failed. %d", errCode); + } + SQLiteUtils::ResetStatement(stmt, true, errCode); + return CheckCorruptedStatus(errCode); +} + +namespace { +std::string FormatSubscribeTriggerSql(const std::string &subscribeId, const std::string &subscribeCondition, + TriggerModeEnum mode) +{ + std::string triggerModeString = GetTriggerModeString(mode); + std::string accessString = ((mode == TriggerModeEnum::DELETE) ? + DBConstant::TRIGGER_REFERENCES_OLD : DBConstant::TRIGGER_REFERENCES_NEW); + std::string keyStr = DBConstant::SUBSCRIBE_QUERY_PREFIX + DBCommon::TransferHashString(subscribeId); + Key key {keyStr.begin(), keyStr.end()}; + std::string hexKeyStr = DBCommon::VectorToHexString(key); + std::string triggerName = DBConstant::SUBSCRIBE_QUERY_PREFIX + subscribeId + "_ON_" + triggerModeString; + return "CREATE TRIGGER IF NOT EXISTS " + triggerName + " AFTER " + triggerModeString + " \n" + "ON sync_data\n" + "WHEN " + subscribeCondition + "\n" + "BEGIN\n" + " SELECT " + DBConstant::UPDATE_META_FUNC + "(x'" + hexKeyStr + "', NEW.TIMESTAMP);\n" + "END;"; +} +} + +int SQLiteSingleVerStorageExecutor::AddSubscribeTrigger(QueryObject &query, const std::string &subscribeId) +{ + if (executorState_ == ExecutorState::CACHEDB || executorState_ == ExecutorState::CACHE_ATTACH_MAIN) { + LOGE("Not support add subscribe in cache db."); + return -E_NOT_SUPPORT; + } + int errCode = E_OK; + SqliteQueryHelper helper = query.GetQueryHelper(errCode); + if (errCode != E_OK) { + LOGE("Get query helper failed. %d", errCode); + return errCode; + } + // check if sqlite function is registered or not + sqlite3_stmt *stmt = nullptr; + errCode = SQLiteUtils::GetStatement(dbHandle_, "SELECT " + DBConstant::UPDATE_META_FUNC + "('K', 0);", stmt); + if (errCode != E_OK) { + LOGE("sqlite function %s has not been created.", DBConstant::UPDATE_META_FUNC.c_str()); + return -E_NOT_SUPPORT; + } + SQLiteUtils::ResetStatement(stmt, true, errCode); + + // Delete data API is actually an update operation, there is no need for DELETE trigger + for (auto mode : {TriggerModeEnum::INSERT, TriggerModeEnum::UPDATE}) { + std::string subscribeCondition; + errCode = helper.GetSubscribeSql(subscribeId, mode, subscribeCondition); + if (errCode != E_OK) { + LOGE("Get subscribe trigger create sql failed. mode: %u, errCode: %d", static_cast(mode), + errCode); + return errCode; + } + std::string sql = FormatSubscribeTriggerSql(subscribeId, subscribeCondition, mode); + errCode = SQLiteUtils::ExecuteRawSQL(dbHandle_, sql); + if (errCode != E_OK) { + LOGE("Add subscribe trigger failed. mode: %u, errCode: %d", static_cast(mode), errCode); + return errCode; + } + } + return E_OK; +} + +int SQLiteSingleVerStorageExecutor::RemoveSubscribeTrigger(const std::vector &subscribeIds) +{ + int errCode = E_OK; + for (const auto &id : subscribeIds) { + for (auto mode : {TriggerModeEnum::INSERT, TriggerModeEnum::UPDATE}) { + const std::string trigger = DBConstant::SUBSCRIBE_QUERY_PREFIX + id + "_ON_" + GetTriggerModeString(mode); + errCode = SQLiteUtils::DropTriggerByName(dbHandle_, trigger); + if (errCode != E_OK) { + LOGE("remove subscribe trigger failed. %d", errCode); + break; + } + } + if (errCode != E_OK) { + LOGE("remove subscribe trigger for id %s failed. %d", id.c_str(), errCode); + break; + } + } + return errCode; +} + +int SQLiteSingleVerStorageExecutor::RemoveTrigger(const std::vector &triggers) +{ + int errCode = E_OK; + for (const auto &trigger : triggers) { + errCode = SQLiteUtils::DropTriggerByName(dbHandle_, trigger); + if (errCode != E_OK) { + LOGE("remove trigger failed. %d", errCode); + break; + } + } + return errCode; +} + +int SQLiteSingleVerStorageExecutor::RemoveSubscribeTriggerWaterMark(const std::vector &subscribeIds) +{ + sqlite3_stmt *statement = nullptr; + const std::string sql = attachMetaMode_ ? REMOVE_ATTACH_META_VALUE_SQL : REMOVE_META_VALUE_SQL; + int errCode = SQLiteUtils::GetStatement(dbHandle_, sql, statement); + if (errCode != E_OK) { + LOGE("Get remove trigger water mark statement failed. %d", errCode); + return errCode; + } + for (const auto &id : subscribeIds) { + errCode = SQLiteUtils::BindTextToStatement(statement, 1, DBConstant::SUBSCRIBE_QUERY_PREFIX + id); + if (errCode != E_OK) { + LOGE("Bind mark key to statement failed. %d", errCode); + break; + } + errCode = SQLiteUtils::StepWithRetry(statement, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } else { + LOGE("Remove trigger water mark failed. %d", errCode); + break; + } + SQLiteUtils::ResetStatement(statement, false, errCode); + } + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteSingleVerStorageExecutor::GetTriggers(const std::string &namePreFix, std::vector &triggerNames) +{ + sqlite3_stmt *stmt = nullptr; + int errCode = SQLiteUtils::GetStatement(dbHandle_, GET_SYNC_DATA_TIRGGER_SQL, stmt); + if (errCode != E_OK) { + LOGE("Get trigger query statement failed. %d", errCode); + return errCode; + } + + errCode = SQLiteUtils::BindTextToStatement(stmt, 1, namePreFix + "%"); + if (errCode != E_OK) { + SQLiteUtils::ResetStatement(stmt, true, errCode); + LOGE("Bind trigger name prefix to statement failed. %d", errCode); + return errCode; + } + + do { + errCode = SQLiteUtils::StepWithRetry(stmt, isMemDb_); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + break; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + std::string name; + SQLiteUtils::GetColumnTextValue(stmt, 0, name); + triggerNames.emplace_back(name); + } else { + LOGE("Get trigger by name prefix failed. %d", errCode); + break; + } + } while (true); + + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_storage_engine.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_storage_engine.cpp new file mode 100644 index 00000000..b7ed3b1f --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_storage_engine.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_storage_engine.h" + +#include "db_errno.h" +#include "log_print.h" +#include "sqlite_storage_executor.h" +#include "sqlite_utils.h" +#include "runtime_context.h" + +namespace DistributedDB { +SQLiteStorageEngine::SQLiteStorageEngine() +{} + +SQLiteStorageEngine::~SQLiteStorageEngine() +{} + +int SQLiteStorageEngine::InitSQLiteStorageEngine(const StorageEngineAttr &poolSize, const OpenDbProperties &option, + const std::string &identifier) +{ + if (StorageEngine::CheckEngineAttr(poolSize)) { + LOGE("Invalid storage engine attributes!"); + return -E_INVALID_ARGS; + } + engineAttr_ = poolSize; + option_ = option; + identifier_ = identifier; + if (GetEngineState() == EngineState::CACHEDB) { + LOGI("Is alive! not need to create executor, only fix option."); + return E_OK; + } + int errCode = Init(); + if (errCode != E_OK) { + LOGI("Storage engine init fail! errCode = [%d]", errCode); + } + return errCode; +} + +StorageExecutor *SQLiteStorageEngine::NewSQLiteStorageExecutor(sqlite3 *dbHandle, bool isWrite, bool isMemDb) +{ + return new (std::nothrow) SQLiteStorageExecutor(dbHandle, isWrite, isMemDb); +} + +int SQLiteStorageEngine::Upgrade(sqlite3 *db) +{ + // SQLiteSingleVerStorageEngine override this function to do table structure and even content upgrade + // SQLiteLocalStorageEngine is used by SQLiteLocalKvDB, and SQLiteLocalKvDB is used as LocalStore, CommitStorage, + // ValueSliceStorage, MetaDataStorage, all of them will not change the table structure, + // so the version of these dbFile only represent for the content version. + // SQLiteLocalStorageEngine do not override this function so content upgrade can only be done by the Storage + // who use the SQLiteLocalKvDB. + // MultiVerStorageEngine do not inherit SQLiteStorageEngine. + (void)db; + return E_OK; +} + +int SQLiteStorageEngine::CreateNewExecutor(bool isWrite, StorageExecutor *&handle) +{ + sqlite3 *dbHandle = nullptr; + int errCode = SQLiteUtils::OpenDatabase(option_, dbHandle); + if (errCode != E_OK) { + return errCode; + } + bool isMemDb = option_.isMemDb; + if (!isUpdated_) { + errCode = Upgrade(dbHandle); + if (errCode != E_OK) { + (void)sqlite3_close_v2(dbHandle); + dbHandle = nullptr; + return errCode; + } + SQLiteUtils::ExecuteCheckPoint(dbHandle); + isUpdated_ = true; + } + + handle = NewSQLiteStorageExecutor(dbHandle, isWrite, isMemDb); + if (handle == nullptr) { + LOGE("New SQLiteStorageExecutor[%d] for the pool failed.", isWrite); + (void)sqlite3_close_v2(dbHandle); + dbHandle = nullptr; + return -E_OUT_OF_MEMORY; + } + return E_OK; +} + +int SQLiteStorageEngine::ReInit() +{ + return E_OK; +} + +bool SQLiteStorageEngine::IsNeedTobeReleased() const +{ + EngineState engineState = GetEngineState(); + return ((engineState == EngineState::MAINDB) || (engineState == EngineState::INVALID)); +} + +const std::string &SQLiteStorageEngine::GetIdentifier() const +{ + return identifier_; +} + +EngineState SQLiteStorageEngine::GetEngineState() const +{ + return engineState_; +} + +void SQLiteStorageEngine::SetEngineState(EngineState state) +{ + LOGD("[SQLiteStorageEngine::SetEngineState] Engine State : [%d]", state); + engineState_ = state; // Current usage logically can guarante no concurrency +} + +const OpenDbProperties &SQLiteStorageEngine::GetOpenOption() const +{ + return option_; +} + +bool SQLiteStorageEngine::IsNeedMigrate() const +{ + return false; +} + +int SQLiteStorageEngine::ExecuteMigrate() +{ + return -E_NOT_SUPPORT; +} + +void SQLiteStorageEngine::IncreaseCacheRecordVersion() +{ + return; +} + +uint64_t SQLiteStorageEngine::GetCacheRecordVersion() const +{ + return 0; +} + +uint64_t SQLiteStorageEngine::GetAndIncreaseCacheRecordVersion() +{ + return 0; +} + +bool SQLiteStorageEngine::IsEngineCorrupted() const +{ + return false; +} + +void SQLiteStorageEngine::ClearEnginePasswd() +{ + option_.passwd.Clear(); +} + +int SQLiteStorageEngine::CheckEngineOption(const KvDBProperties &kvDBProp) const +{ + SecurityOption securityOpt; + if (RuntimeContext::GetInstance()->IsProcessSystemApiAdapterValid()) { + securityOpt.securityLabel = kvDBProp.GetSecLabel(); + securityOpt.securityFlag = kvDBProp.GetSecFlag(); + } + + int conflictReslovePolicy = kvDBProp.GetIntProp(KvDBProperties::CONFLICT_RESOLVE_POLICY, DEFAULT_LAST_WIN); + bool createDirByStoreIdOnly = kvDBProp.GetBoolProp(KvDBProperties::CREATE_DIR_BY_STORE_ID_ONLY, false); + + if (kvDBProp.GetSchemaConstRef().IsSchemaValid() == option_.schema.empty()) { + // If both true, newSchema not empty, oriEngineSchema empty, not same + // If both false, newSchema empty, oriEngineSchema not empty, not same + LOGE("Engine and kvdb schema only one not empty! kvdb schema is [%d]", option_.schema.empty()); + return -E_SCHEMA_MISMATCH; + } + // Here both schema empty or not empty, be the same + if (kvDBProp.GetSchemaConstRef().IsSchemaValid()) { + int errCode = kvDBProp.GetSchemaConstRef().CompareAgainstSchemaString(option_.schema); + if (errCode != -E_SCHEMA_EQUAL_EXACTLY) { + LOGE("Engine and kvdb schema mismatch!"); + return -E_SCHEMA_MISMATCH; + } + } + + bool isMemDb = kvDBProp.GetBoolProp(KvDBProperties::MEMORY_MODE, false); + // Here both schema empty or "not empty and equal exactly", this is ok as required + if (isMemDb == false && + option_.createDirByStoreIdOnly == createDirByStoreIdOnly && + option_.securityOpt == securityOpt && + option_.conflictReslovePolicy == conflictReslovePolicy) { + return E_OK; + } + return -E_INVALID_ARGS; +} +} diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_storage_engine.h b/mock/distributeddb/storage/src/sqlite/sqlite_storage_engine.h new file mode 100644 index 00000000..068448da --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_storage_engine.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_KVDB_HANDLE_POOL_H +#define SQLITE_KVDB_HANDLE_POOL_H + +#include + +#include "macro_utils.h" +#include "storage_engine.h" +#include "sqlite_utils.h" + +namespace DistributedDB { +class SQLiteStorageEngine : public StorageEngine { +public: + SQLiteStorageEngine(); + ~SQLiteStorageEngine() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteStorageEngine); + + int InitSQLiteStorageEngine(const StorageEngineAttr &poolSize, const OpenDbProperties &option, + const std::string &identifier = std::string()); + + bool IsNeedTobeReleased() const override; + + const std::string &GetIdentifier() const override; + + EngineState GetEngineState() const override; + + int ExecuteMigrate() override; + + void SetEngineState(EngineState state) override; + + const OpenDbProperties &GetOpenOption() const; + + virtual void IncreaseCacheRecordVersion(); + virtual uint64_t GetCacheRecordVersion() const; + virtual uint64_t GetAndIncreaseCacheRecordVersion(); + + virtual bool IsEngineCorrupted() const; + + void ClearEnginePasswd() override; + + int CheckEngineOption(const KvDBProperties &kvdbOption) const override; + +protected: + + virtual int Upgrade(sqlite3 *db); + + virtual StorageExecutor *NewSQLiteStorageExecutor(sqlite3 *dbHandle, bool isWrite, bool isMemDb) = 0; + + int CreateNewExecutor(bool isWrite, StorageExecutor *&handle) override; + + virtual int ReInit(); + + bool IsNeedMigrate() const override; + + OpenDbProperties option_; +}; +} // namespace DistributedDB +#endif // SQLITE_DB_HANDLE_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_storage_executor.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_storage_executor.cpp new file mode 100644 index 00000000..9991bbea --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_storage_executor.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_storage_executor.h" + +#include "db_errno.h" +#include "log_print.h" +#include "sqlite_utils.h" + +namespace DistributedDB { +SQLiteStorageExecutor::SQLiteStorageExecutor(sqlite3 *dbHandle, bool writable, bool isMemDb) + : StorageExecutor(writable), + dbHandle_(dbHandle), + isMemDb_(isMemDb) +{} + +SQLiteStorageExecutor::~SQLiteStorageExecutor() +{ + if (dbHandle_ != nullptr) { + (void)sqlite3_close_v2(dbHandle_); + dbHandle_ = nullptr; + } +} + +int SQLiteStorageExecutor::Reset() +{ + if (dbHandle_ != nullptr) { + // Set the handle to be valid, release the heap memory as much as possible. + sqlite3_db_release_memory(dbHandle_); + return E_OK; + } + return -E_INVALID_DB; +} + +int SQLiteStorageExecutor::GetDbHandle(sqlite3 *&dbHandle) const +{ + if (dbHandle_ == nullptr) { + LOGE("Can not get dbhandle from executor!"); + return -E_NOT_FOUND; + } + dbHandle = dbHandle_; + return E_OK; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_storage_executor.h b/mock/distributeddb/storage/src/sqlite/sqlite_storage_executor.h new file mode 100644 index 00000000..10abd5db --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_storage_executor.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_STORAGE_EXECUTOR_H +#define SQLITE_STORAGE_EXECUTOR_H + +#include "sqlite_import.h" +#include "macro_utils.h" +#include "storage_executor.h" + +namespace DistributedDB { +class SQLiteStorageExecutor : public StorageExecutor { +public: + SQLiteStorageExecutor(sqlite3 *dbHandle, bool writable, bool isMemDb); + ~SQLiteStorageExecutor() override; + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(SQLiteStorageExecutor); + + int Reset() override; + int GetDbHandle(sqlite3 *&dbHandle) const; + +protected: + sqlite3 *dbHandle_; + bool isMemDb_; +}; +} // namespace DistributedDB + +#endif // SQLITE_STORAGE_EXECUTOR_H diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_utils.cpp b/mock/distributeddb/storage/src/sqlite/sqlite_utils.cpp new file mode 100644 index 00000000..1aef6224 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_utils.cpp @@ -0,0 +1,2154 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sqlite_utils.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "sqlite_import.h" +#include "securec.h" +#include "db_constant.h" +#include "db_common.h" +#include "db_errno.h" +#include "log_print.h" +#include "value_object.h" +#include "schema_utils.h" +#include "schema_constant.h" +#include "time_helper.h" +#include "platform_specific.h" + +namespace DistributedDB { + std::mutex SQLiteUtils::logMutex_; + std::string SQLiteUtils::lastErrorMsg_; +namespace { + const int BUSY_TIMEOUT_MS = 3000; // 3000ms for sqlite busy timeout. + const int BUSY_SLEEP_TIME = 50; // sleep for 50us + const int NO_SIZE_LIMIT = -1; + const int MAX_STEP_TIMES = 8000; + const int BIND_KEY_INDEX = 1; + const int BIND_VAL_INDEX = 2; + const int USING_STR_LEN = -1; + const std::string WAL_MODE_SQL = "PRAGMA journal_mode=WAL;"; + const std::string SYNC_MODE_FULL_SQL = "PRAGMA synchronous=FULL;"; + const std::string SYNC_MODE_NORMAL_SQL = "PRAGMA synchronous=NORMAL;"; + const std::string USER_VERSION_SQL = "PRAGMA user_version;"; + const std::string BEGIN_SQL = "BEGIN TRANSACTION"; + const std::string BEGIN_IMMEDIATE_SQL = "BEGIN IMMEDIATE TRANSACTION"; + const std::string COMMIT_SQL = "COMMIT TRANSACTION"; + const std::string ROLLBACK_SQL = "ROLLBACK TRANSACTION"; + const std::string JSON_EXTRACT_BY_PATH_TEST_CREATED = "SELECT json_extract_by_path('{\"field\":0}', '$.field', 0);"; + const std::string DEFAULT_ATTACH_CIPHER = "PRAGMA cipher_default_attach_cipher="; + const std::string DEFAULT_ATTACH_KDF_ITER = "PRAGMA cipher_default_attach_kdf_iter=5000"; + const std::string EXPORT_BACKUP_SQL = "SELECT export_database('backup');"; + const std::string CIPHER_CONFIG_SQL = "PRAGMA codec_cipher="; + const std::string KDF_ITER_CONFIG_SQL = "PRAGMA codec_kdf_iter=5000;"; + const std::string BACK_CIPHER_CONFIG_SQL = "PRAGMA backup.codec_cipher="; + const std::string BACK_KDF_ITER_CONFIG_SQL = "PRAGMA backup.codec_kdf_iter=5000;"; + const std::string META_CIPHER_CONFIG_SQL = "PRAGMA meta.codec_cipher="; + const std::string META_KDF_ITER_CONFIG_SQL = "PRAGMA meta.codec_kdf_iter=5000;"; + + const std::string DETACH_BACKUP_SQL = "DETACH 'backup'"; + const std::string UPDATE_META_SQL = "INSERT OR REPLACE INTO meta_data VALUES (?, ?);"; + + bool g_configLog = false; + + // statement must not be null + std::string GetColString(sqlite3_stmt *statement, int nCol) + { + std::string rowString; + for (int i = 0; i < nCol; i++) { + if (sqlite3_column_name(statement, i) != nullptr) { + rowString += sqlite3_column_name(statement, i); + } + int blankFill = (i + 1) * 16 - rowString.size(); // each column width 16 + rowString.append(static_cast((blankFill > 0) ? blankFill : 0), ' '); + } + return rowString; + } +} + +namespace TriggerMode { +const std::map TRIGGER_MODE_MAP = { + {TriggerModeEnum::NONE, ""}, + {TriggerModeEnum::INSERT, "INSERT"}, + {TriggerModeEnum::UPDATE, "UPDATE"}, + {TriggerModeEnum::DELETE, "DELETE"}, +}; + +std::string GetTriggerModeString(TriggerModeEnum mode) +{ + auto it = TRIGGER_MODE_MAP.find(mode); + return (it == TRIGGER_MODE_MAP.end()) ? "" : it->second; +} +} + +void SQLiteUtils::SqliteLogCallback(void *data, int err, const char *msg) +{ + bool verboseLog = (data != nullptr); + auto errType = static_cast(err); + errType &= 0xFF; + if (errType == 0 || errType == SQLITE_CONSTRAINT || errType == SQLITE_SCHEMA || + errType == SQLITE_NOTICE || err == SQLITE_WARNING_AUTOINDEX) { + if (verboseLog) { + LOGD("[SQLite] Error[%d] sys[%d] %s ", err, errno, sqlite3_errstr(err)); + } + } else if (errType == SQLITE_WARNING || errType == SQLITE_IOERR || + errType == SQLITE_CORRUPT || errType == SQLITE_CANTOPEN) { + LOGI("[SQLite] Error[%d], sys[%d], %s", err, errno, sqlite3_errstr(err)); + } else { + LOGE("[SQLite] Error[%d], sys[%d]", err, errno); + return; + } + + const char *errMsg = sqlite3_errstr(err); + std::lock_guard autoLock(logMutex_); + if (errMsg != nullptr) { + lastErrorMsg_ = std::string(errMsg); + } +} + +int SQLiteUtils::CreateDataBase(const OpenDbProperties &properties, sqlite3 *&dbTemp, bool setWal) +{ + uint64_t flag = SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE; + if (properties.createIfNecessary) { + flag |= SQLITE_OPEN_CREATE; + } + std::string cipherName = GetCipherName(properties.cipherType); + if (cipherName.empty()) { + LOGE("[SQLite] GetCipherName failed"); + return -E_INVALID_ARGS; + } + std::string defaultAttachCipher = DEFAULT_ATTACH_CIPHER + cipherName + ";"; + std::vector sqls {defaultAttachCipher, DEFAULT_ATTACH_KDF_ITER}; + if (setWal) { + sqls.push_back(WAL_MODE_SQL); + } + + std::string fileUrl = DBConstant::SQLITE_URL_PRE + properties.uri; + int errCode = sqlite3_open_v2(fileUrl.c_str(), &dbTemp, flag, nullptr); + if (errCode != SQLITE_OK) { + LOGE("[SQLite] open database failed: %d - sys err(%d)", errCode, errno); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + goto END; + } + + errCode = SetDataBaseProperty(dbTemp, properties, sqls); + if (errCode != SQLITE_OK) { + LOGE("[SQLite] SetDataBaseProperty failed: %d", errCode); + goto END; + } + +END: + if (errCode != E_OK && dbTemp != nullptr) { + (void)sqlite3_close_v2(dbTemp); + dbTemp = nullptr; + } + + return errCode; +} + +int SQLiteUtils::OpenDatabase(const OpenDbProperties &properties, sqlite3 *&db, bool setWal) +{ + { + // Only for register the sqlite3 log callback + std::lock_guard lock(logMutex_); + if (!g_configLog) { + sqlite3_config(SQLITE_CONFIG_LOG, &SqliteLogCallback, &properties.createIfNecessary); + g_configLog = true; + } + } + sqlite3 *dbTemp = nullptr; + int errCode = CreateDataBase(properties, dbTemp, setWal); + if (errCode != E_OK) { + goto END; + } + errCode = RegisterJsonFunctions(dbTemp); + if (errCode != E_OK) { + goto END; + } + // Set the synchroized mode, default for full mode. + errCode = ExecuteRawSQL(dbTemp, SYNC_MODE_FULL_SQL); + if (errCode != E_OK) { + LOGE("SQLite sync mode failed: %d", errCode); + goto END; + } + + if (!properties.isMemDb) { + errCode = SQLiteUtils::SetPersistWalMode(dbTemp); + if (errCode != E_OK) { + LOGE("SQLite set persist wall mode failed: %d", errCode); + } + } + +END: + if (errCode != E_OK && dbTemp != nullptr) { + (void)sqlite3_close_v2(dbTemp); + dbTemp = nullptr; + } + if (errCode != E_OK && errno == EKEYREVOKED) { + errCode = -E_EKEYREVOKED; + } + db = dbTemp; + return errCode; +} + +int SQLiteUtils::GetStatement(sqlite3 *db, const std::string &sql, sqlite3_stmt *&statement) +{ + if (db == nullptr) { + LOGE("Invalid db for statement"); + return -E_INVALID_DB; + } + + // Prepare the new statement only when the input parameter is not null + if (statement != nullptr) { + return E_OK; + } + + int errCode = sqlite3_prepare_v2(db, sql.c_str(), NO_SIZE_LIMIT, &statement, nullptr); + if (errCode != SQLITE_OK) { + LOGE("Prepare SQLite statement failed:%d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; + } + + if (statement == nullptr) { + return -E_INVALID_DB; + } + + return E_OK; +} + +int SQLiteUtils::BindTextToStatement(sqlite3_stmt *statement, int index, const std::string &str) +{ + if (statement == nullptr) { + return -E_INVALID_ARGS; + } + + // Check empty value. + if (str.empty()) { + sqlite3_bind_null(statement, index); + return E_OK; + } + + int errCode = sqlite3_bind_text(statement, index, str.c_str(), str.length(), SQLITE_TRANSIENT); + if (errCode != SQLITE_OK) { + LOGE("[SQLiteUtil][Bind text]Failed to bind the value:%d", errCode); + return SQLiteUtils::MapSQLiteErrno(errCode); + } + + return E_OK; +} + +int SQLiteUtils::BindInt64ToStatement(sqlite3_stmt *statement, int index, int64_t value) +{ + // statement check outSide + int errCode = sqlite3_bind_int64(statement, index, value); + if (errCode != SQLITE_OK) { + LOGE("[SQLiteUtil][Bind int64]Failed to bind the value:%d", errCode); + return SQLiteUtils::MapSQLiteErrno(errCode); + } + + return E_OK; +} + +int SQLiteUtils::BindBlobToStatement(sqlite3_stmt *statement, int index, const std::vector &value, + bool permEmpty) +{ + if (statement == nullptr) { + return -E_INVALID_ARGS; + } + + // Check empty value. + if (value.empty() && !permEmpty) { + LOGI("[SQLiteUtil][Bind blob]Invalid value"); + return -E_INVALID_ARGS; + } + + int errCode; + if (value.empty()) { + errCode = sqlite3_bind_zeroblob(statement, index, -1); // -1 for zero-length blob. + } else { + errCode = sqlite3_bind_blob(statement, index, static_cast(value.data()), + value.size(), SQLITE_TRANSIENT); + } + + if (errCode != SQLITE_OK) { + LOGE("[SQLiteUtil][Bind blob]Failed to bind the value:%d", errCode); + return SQLiteUtils::MapSQLiteErrno(errCode); + } + + return E_OK; +} + +void SQLiteUtils::ResetStatement(sqlite3_stmt *&statement, bool isNeedFinalize, int &errCode) +{ + if (statement == nullptr) { + return; + } + + int innerCode = SQLITE_OK; + // if need finalize the statement, just goto finalize. + if (isNeedFinalize) { + goto FINAL; + } + + // reset the statement firstly. + innerCode = sqlite3_reset(statement); + if (innerCode != SQLITE_OK) { + LOGE("[SQLiteUtils] reset statement error:%d, sys:%d", innerCode, errno); + isNeedFinalize = true; + } else { + sqlite3_clear_bindings(statement); + } + +FINAL: + if (isNeedFinalize) { + int finalizeResult = sqlite3_finalize(statement); + if (finalizeResult != SQLITE_OK) { + LOGD("[SQLiteUtils] finalize statement error:%d, sys:%d", finalizeResult, errno); + innerCode = finalizeResult; + } + statement = nullptr; + } + + if (innerCode != SQLITE_OK) { // the sqlite error code has higher priority. + errCode = SQLiteUtils::MapSQLiteErrno(innerCode); + } +} + +int SQLiteUtils::StepWithRetry(sqlite3_stmt *statement, bool isMemDb) +{ + if (statement == nullptr) { + return -E_INVALID_ARGS; + } + + int errCode = E_OK; + int retryCount = 0; + do { + errCode = sqlite3_step(statement); + if ((errCode == SQLITE_LOCKED) && isMemDb) { + std::this_thread::sleep_for(std::chrono::microseconds(BUSY_SLEEP_TIME)); + retryCount++; + } else { + break; + } + } while (retryCount <= MAX_STEP_TIMES); + + if (errCode != SQLITE_DONE && errCode != SQLITE_ROW) { + LOGE("[SQLiteUtils] Step error:%d, sys:%d", errCode, errno); + } + + return SQLiteUtils::MapSQLiteErrno(errCode); +} + +int SQLiteUtils::BindPrefixKey(sqlite3_stmt *statement, int index, const Key &keyPrefix) +{ + if (statement == nullptr) { + return -E_INVALID_ARGS; + } + + const size_t maxKeySize = DBConstant::MAX_KEY_SIZE; + // bind the first prefix key + int errCode = BindBlobToStatement(statement, index, keyPrefix, true); + if (errCode != SQLITE_OK) { + LOGE("Bind the prefix first error:%d", errCode); + return SQLiteUtils::MapSQLiteErrno(errCode); + } + + // bind the second prefix key + uint8_t end[maxKeySize]; + errno_t status = memset_s(end, maxKeySize, UCHAR_MAX, maxKeySize); // max byte value is 0xFF. + if (status != EOK) { + LOGE("memset error:%d", status); + return -E_SECUREC_ERROR; + } + + if (!keyPrefix.empty()) { + status = memcpy_s(end, maxKeySize, keyPrefix.data(), keyPrefix.size()); + if (status != EOK) { + LOGE("memcpy error:%d", status); + return -E_SECUREC_ERROR; + } + } + + // index wouldn't be too large, just add one to the first index. + errCode = sqlite3_bind_blob(statement, index + 1, end, maxKeySize, SQLITE_TRANSIENT); + if (errCode != SQLITE_OK) { + LOGE("Bind the prefix second error:%d", errCode); + return SQLiteUtils::MapSQLiteErrno(errCode); + } + return E_OK; +} + +int SQLiteUtils::BeginTransaction(sqlite3 *db, TransactType type) +{ + if (type == TransactType::IMMEDIATE) { + return ExecuteRawSQL(db, BEGIN_IMMEDIATE_SQL); + } + + return ExecuteRawSQL(db, BEGIN_SQL); +} + +int SQLiteUtils::CommitTransaction(sqlite3 *db) +{ + return ExecuteRawSQL(db, COMMIT_SQL); +} + +int SQLiteUtils::RollbackTransaction(sqlite3 *db) +{ + return ExecuteRawSQL(db, ROLLBACK_SQL); +} + +int SQLiteUtils::ExecuteRawSQL(sqlite3 *db, const std::string &sql) +{ + if (db == nullptr) { + return -E_INVALID_DB; + } + char *errMsg = nullptr; + int errCode = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errMsg); + if (errCode != SQLITE_OK) { + LOGE("[SQLiteUtils][ExecuteSQL] failed(%d), sys(%d)", errCode, errno); + } + + if (errMsg != nullptr) { + sqlite3_free(errMsg); + errMsg = nullptr; + } + return SQLiteUtils::MapSQLiteErrno(errCode); +} + +int SQLiteUtils::SetKey(sqlite3 *db, CipherType type, const CipherPassword &passwd, const bool &isMemDb) +{ + if (db == nullptr) { + return -E_INVALID_DB; + } + + // in memory mode no need cipher + if (isMemDb) { + return E_OK; + } + + if (passwd.GetSize() != 0) { +#ifndef OMIT_ENCRYPT + int errCode = sqlite3_key(db, static_cast(passwd.GetData()), static_cast(passwd.GetSize())); + if (errCode != SQLITE_OK) { + LOGE("[SQLiteUtils][Setkey] config key failed:(%d)", errCode); + return SQLiteUtils::MapSQLiteErrno(errCode); + } + + errCode = SQLiteUtils::SetCipherSettings(db, type); + if (errCode != E_OK) { + LOGE("[SQLiteUtils][Setkey] set cipher settings failed:%d", errCode); + return errCode; + } +#else + return -E_NOT_SUPPORT; +#endif + } + + // verify key + int errCode = SQLiteUtils::ExecuteRawSQL(db, USER_VERSION_SQL); + if (errCode != E_OK) { + LOGE("[SQLiteUtils][Setkey] verify version failed:%d", errCode); + if (errno == EKEYREVOKED) { + return -E_EKEYREVOKED; + } + if (errCode == -E_BUSY) { + return errCode; + } + return -E_INVALID_PASSWD_OR_CORRUPTED_DB; + } + return E_OK; +} + +int SQLiteUtils::GetColumnBlobValue(sqlite3_stmt *statement, int index, std::vector &value) +{ + if (statement == nullptr) { + return -E_INVALID_ARGS; + } + + int keySize = sqlite3_column_bytes(statement, index); + auto keyRead = static_cast(sqlite3_column_blob(statement, index)); + if (keySize < 0) { + LOGE("[SQLiteUtils][Column blob] size:%d", keySize); + return -E_INVALID_DATA; + } else if (keySize == 0 || keyRead == nullptr) { + value.resize(0); + } else { + value.resize(keySize); + value.assign(keyRead, keyRead + keySize); + } + + return E_OK; +} + +int SQLiteUtils::GetColumnTextValue(sqlite3_stmt *statement, int index, std::string &value) +{ + if (statement == nullptr) { + return -E_INVALID_ARGS; + } + const unsigned char *val = sqlite3_column_text(statement, index); + value = (val != nullptr) ? std::string(reinterpret_cast(val)) : std::string(); + return E_OK; +} + +int SQLiteUtils::AttachNewDatabase(sqlite3 *db, CipherType type, const CipherPassword &password, + const std::string &attachDbAbsPath, const std::string &attachAsName) +{ + // example: "ATTACH '../new.db' AS backup KEY XXXX;" + std::string attachSql = "ATTACH ? AS " + attachAsName + " KEY ?;"; // Internal interface not need verify alias name + + sqlite3_stmt* statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db, attachSql, statement); + if (errCode != E_OK) { + return errCode; + } + // 1st is name. + errCode = sqlite3_bind_text(statement, 1, attachDbAbsPath.c_str(), attachDbAbsPath.length(), SQLITE_TRANSIENT); + if (errCode != SQLITE_OK) { + LOGE("Bind the attached db name failed:%d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + goto END; + } + // Passwords do not allow vector operations, so we can not use function BindBlobToStatement here. + errCode = sqlite3_bind_blob(statement, 2, static_cast(password.GetData()), // 2 means password index. + password.GetSize(), SQLITE_TRANSIENT); + if (errCode != SQLITE_OK) { + LOGE("Bind the attached key failed:%d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + LOGE("Execute the SQLite attach failed:%d", errCode); + goto END; + } + errCode = SQLiteUtils::ExecuteRawSQL(db, WAL_MODE_SQL); + if (errCode != E_OK) { + LOGE("Set journal mode failed: %d", errCode); + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteUtils::CreateMetaDatabase(const std::string &metaDbPath) +{ + OpenDbProperties metaProperties {metaDbPath, true, false}; + sqlite3 *db = nullptr; + int errCode = SQLiteUtils::OpenDatabase(metaProperties, db); + if (errCode != E_OK) { + LOGE("[CreateMetaDatabase] Failed to create the meta database[%d]", errCode); + } + if (db != nullptr) { + (void)sqlite3_close_v2(db); + db = nullptr; + } + return errCode; +} + +int SQLiteUtils::CheckIntegrity(sqlite3 *db, const std::string &sql) +{ + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db, sql, statement); + if (errCode != E_OK) { + LOGE("Prepare the integrity check statement error:%d", errCode); + return errCode; + } + int resultCnt = 0; + bool checkResultOK = false; + do { + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + break; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + auto result = reinterpret_cast(sqlite3_column_text(statement, 0)); + if (result == nullptr) { + continue; + } + resultCnt = (resultCnt > 1) ? resultCnt : (resultCnt + 1); + if (strcmp(result, "ok") == 0) { + checkResultOK = true; + } + } else { + checkResultOK = false; + LOGW("Step for the integrity check failed:%d", errCode); + break; + } + } while (true); + if (resultCnt == 1 && checkResultOK) { + errCode = E_OK; + } else { + errCode = -E_INVALID_PASSWD_OR_CORRUPTED_DB; + } + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} +#ifdef RELATIONAL_STORE +namespace { // anonymous namespace for schema analysis +int AnalysisSchemaSqlAndTrigger(sqlite3 *db, const std::string &tableName, TableInfo &table) +{ + std::string sql = "select type, sql from sqlite_master where tbl_name = ?"; + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db, sql, statement); + if (errCode != E_OK) { + LOGE("[AnalysisSchema] Prepare the analysis schema sql and trigger statement error:%d", errCode); + return errCode; + } + errCode = SQLiteUtils::BindTextToStatement(statement, 1, tableName); + if (errCode != E_OK) { + LOGE("[AnalysisSchema] Bind table name failed:%d", errCode); + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; + } + + errCode = -E_NOT_FOUND; + std::vector triggerList; + do { + int err = SQLiteUtils::StepWithRetry(statement); + if (err == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + break; + } else if (err == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + errCode = E_OK; + std::string type; + (void) SQLiteUtils::GetColumnTextValue(statement, 0, type); + if (type == "table") { + std::string createTableSql; + (void) SQLiteUtils::GetColumnTextValue(statement, 1, createTableSql); // 1 means create table sql + table.SetCreateTableSql(createTableSql); + } + } else { + LOGE("[AnalysisSchema] Step for the analysis create table sql and trigger failed:%d", err); + errCode = SQLiteUtils::MapSQLiteErrno(err); + break; + } + } while (true); + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int GetSchemaIndexList(sqlite3 *db, const std::string &tableName, std::vector &indexList) +{ + std::string sql = "pragma index_list(" + tableName + ")"; + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db, sql, statement); + if (errCode != E_OK) { + LOGE("[AnalysisSchema] Prepare the get schema index list statement error:%d", errCode); + return errCode; + } + + do { + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + break; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + std::string indexName; + (void) SQLiteUtils::GetColumnTextValue(statement, 1, indexName); // 1 means index name + std::string origin; + (void) SQLiteUtils::GetColumnTextValue(statement, 3, origin); // 3 means index type, whether unique + if (origin == "c") { // 'c' means index created by user declare + indexList.push_back(indexName); + } + } else { + LOGW("[AnalysisSchema] Step for the get schema index list failed:%d", errCode); + break; + } + } while (true); + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int AnalysisSchemaIndexDefine(sqlite3 *db, const std::string &indexName, CompositeFields &indexDefine) +{ + auto sql = "pragma index_info(" + indexName + ")"; + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db, sql, statement); + if (errCode != E_OK) { + LOGE("[AnalysisSchema] Prepare the analysis schema index statement error:%d", errCode); + return errCode; + } + + do { + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + break; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + std::string indexField; + (void) SQLiteUtils::GetColumnTextValue(statement, 2, indexField); // 2 means index's column name. + indexDefine.push_back(indexField); + } else { + LOGW("[AnalysisSchema] Step for the analysis schema index failed:%d", errCode); + break; + } + } while (true); + + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int AnalysisSchemaIndex(sqlite3 *db, const std::string &tableName, TableInfo &table) +{ + std::vector indexList; + int errCode = GetSchemaIndexList(db, tableName, indexList); + if (errCode != E_OK) { + LOGE("[AnalysisSchema] get schema index list failed."); + return errCode; + } + + for (const auto &indexName : indexList) { + CompositeFields indexDefine; + errCode = AnalysisSchemaIndexDefine(db, indexName, indexDefine); + if (errCode != E_OK) { + LOGE("[AnalysisSchema] analysis schema index columns failed."); + return errCode; + } + table.AddIndexDefine(indexName, indexDefine); + } + return E_OK; +} + +namespace { +bool CheckFieldName(const std::string &fieldName) +{ + auto iter = std::find_if_not(fieldName.begin(), fieldName.end(), [](char c) { + return (std::isalnum(c) || c == '_'); + }); + return iter == fieldName.end(); +} +} + +int SetFieldInfo(sqlite3_stmt *statement, TableInfo &table) +{ + FieldInfo field; + field.SetColumnId(sqlite3_column_int(statement, 0)); // 0 means column id index + + std::string tmpString; + (void) SQLiteUtils::GetColumnTextValue(statement, 1, tmpString); // 1 means column name index + if (!CheckFieldName(tmpString)) { + LOGE("[AnalysisSchema] unsupported field name."); + return -E_NOT_SUPPORT; + } + field.SetFieldName(tmpString); + + (void) SQLiteUtils::GetColumnTextValue(statement, 2, tmpString); // 2 means datatype index + field.SetDataType(tmpString); + + field.SetNotNull(static_cast(sqlite3_column_int64(statement, 3))); // 3 means whether null index + + (void) SQLiteUtils::GetColumnTextValue(statement, 4, tmpString); // 4 means default value index + if (!tmpString.empty()) { + field.SetDefaultValue(tmpString); + } + + if (sqlite3_column_int64(statement, 5) != 0) { // 5 means primary key index + if (!table.GetPrimaryKey().empty()) { + // Primary key is already set, usually because the primary key has multiple fields, not support + LOGE("[AnalysisSchema] Not support for composite primary key"); + return -E_NOT_SUPPORT; + } + table.SetPrimaryKey(field.GetFieldName()); + } + table.AddField(field); + return E_OK; +} + +int AnalysisSchemaFieldDefine(sqlite3 *db, const std::string &tableName, TableInfo &table) +{ + std::string sql = "pragma table_info(" + tableName + ")"; + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db, sql, statement); + if (errCode != E_OK) { + LOGE("[AnalysisSchema] Prepare the analysis schema field statement error:%d", errCode); + return errCode; + } + + do { + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + break; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + errCode = SetFieldInfo(statement, table); + if (errCode != E_OK) { + break; + } + } else { + LOGW("[AnalysisSchema] Step for the analysis schema field failed:%d", errCode); + break; + } + } while (true); + + if (table.GetPrimaryKey().empty()) { + table.SetPrimaryKey("rowid"); + } + + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} +} // end of anonymous namespace for schema analysis + +int SQLiteUtils::AnalysisSchema(sqlite3 *db, const std::string &tableName, TableInfo &table) +{ + int errCode = AnalysisSchemaSqlAndTrigger(db, tableName, table); + if (errCode != E_OK) { + LOGE("[AnalysisSchema] Analysis sql and trigger failed. errCode = [%d]", errCode); + return errCode; + } + + errCode = AnalysisSchemaIndex(db, tableName, table); + if (errCode != E_OK) { + LOGE("[AnalysisSchema] Analysis index failed."); + return errCode; + } + + errCode = AnalysisSchemaFieldDefine(db, tableName, table); + if (errCode != E_OK) { + LOGE("[AnalysisSchema] Analysis field failed."); + return errCode; + } + + table.SetTableName(tableName); + return E_OK; +} +#endif +#ifndef OMIT_ENCRYPT +int SQLiteUtils::ExportDatabase(sqlite3 *db, CipherType type, const CipherPassword &passwd, + const std::string &newDbName) +{ + if (db == nullptr) { + return -E_INVALID_DB; + } + + int errCode = AttachNewDatabase(db, type, passwd, newDbName); + if (errCode != E_OK) { + LOGE("Attach New Db fail!"); + return errCode; + } + errCode = SQLiteUtils::ExecuteRawSQL(db, EXPORT_BACKUP_SQL); + if (errCode != E_OK) { + LOGE("Execute the SQLite export failed:%d", errCode); + } + + int detachError = SQLiteUtils::ExecuteRawSQL(db, DETACH_BACKUP_SQL); + if (errCode == E_OK) { + errCode = detachError; + if (detachError != E_OK) { + LOGE("Execute the SQLite detach failed:%d", errCode); + } + } + return errCode; +} + +int SQLiteUtils::Rekey(sqlite3 *db, const CipherPassword &passwd) +{ + if (db == nullptr) { + return -E_INVALID_DB; + } + + int errCode = sqlite3_rekey(db, static_cast(passwd.GetData()), static_cast(passwd.GetSize())); + if (errCode != E_OK) { + LOGE("SQLite rekey failed:(%d)", errCode); + return SQLiteUtils::MapSQLiteErrno(errCode); + } + + return E_OK; +} +#else +int SQLiteUtils::ExportDatabase(sqlite3 *db, CipherType type, const CipherPassword &passwd, + const std::string &newDbName) +{ + (void)db; + (void)type; + (void)passwd; + (void)newDbName; + return -E_NOT_SUPPORT; +} + +int SQLiteUtils::Rekey(sqlite3 *db, const CipherPassword &passwd) +{ + (void)db; + (void)passwd; + return -E_NOT_SUPPORT; +} +#endif + +int SQLiteUtils::GetVersion(const OpenDbProperties &properties, int &version) +{ + if (properties.uri.empty()) { + return -E_INVALID_ARGS; + } + + sqlite3 *dbTemp = nullptr; + // Please make sure the database file exists and is working properly + std::string fileUrl = DBConstant::SQLITE_URL_PRE + properties.uri; + int errCode = sqlite3_open_v2(fileUrl.c_str(), &dbTemp, SQLITE_OPEN_URI | SQLITE_OPEN_READONLY, nullptr); + if (errCode != SQLITE_OK) { + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + LOGE("Open database failed: %d, sys:%d", errCode, errno); + goto END; + } + + errCode = SQLiteUtils::SetKey(dbTemp, properties.cipherType, properties.passwd, properties.isMemDb); + if (errCode != E_OK) { + LOGE("Set key failed: %d", errCode); + goto END; + } + + errCode = GetVersion(dbTemp, version); + +END: + if (dbTemp != nullptr) { + (void)sqlite3_close_v2(dbTemp); + dbTemp = nullptr; + } + return errCode; +} + +int SQLiteUtils::GetVersion(sqlite3 *db, int &version) +{ + if (db == nullptr) { + return -E_INVALID_DB; + } + + std::string strSql = "PRAGMA user_version;"; + sqlite3_stmt *statement = nullptr; + int errCode = sqlite3_prepare(db, strSql.c_str(), -1, &statement, nullptr); + if (errCode != SQLITE_OK || statement == nullptr) { + LOGE("[SqlUtil][GetVer] sqlite3_prepare failed."); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + return errCode; + } + + if (sqlite3_step(statement) == SQLITE_ROW) { + // Get pragma user_version at first column + version = sqlite3_column_int(statement, 0); + } else { + LOGE("[SqlUtil][GetVer] Get db user_version failed."); + errCode = SQLiteUtils::MapSQLiteErrno(SQLITE_ERROR); + } + + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteUtils::GetJournalMode(sqlite3 *db, std::string &mode) +{ + if (db == nullptr) { + return -E_INVALID_DB; + } + + std::string sql = "PRAGMA journal_mode;"; + sqlite3_stmt *statement = nullptr; + int errCode = SQLiteUtils::GetStatement(db, sql, statement); + if (errCode != E_OK || statement == nullptr) { + return errCode; + } + + errCode = SQLiteUtils::StepWithRetry(statement); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + errCode = SQLiteUtils::GetColumnTextValue(statement, 0, mode); + } else { + LOGE("[SqlUtil][GetJournal] Get db journal_mode failed."); + } + + SQLiteUtils::ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteUtils::SetUserVer(const OpenDbProperties &properties, int version) +{ + if (properties.uri.empty()) { + return -E_INVALID_ARGS; + } + + // Please make sure the database file exists and is working properly + sqlite3 *db = nullptr; + int errCode = SQLiteUtils::OpenDatabase(properties, db); + if (errCode != E_OK) { + return errCode; + } + + // Set user version + errCode = SQLiteUtils::SetUserVer(db, version); + if (errCode != E_OK) { + LOGE("Set user version fail: %d", errCode); + goto END; + } + +END: + if (db != nullptr) { + (void)sqlite3_close_v2(db); + db = nullptr; + } + + return errCode; +} + +int SQLiteUtils::SetUserVer(sqlite3 *db, int version) +{ + if (db == nullptr) { + return -E_INVALID_DB; + } + std::string userVersionSql = "PRAGMA user_version=" + std::to_string(version) + ";"; + return SQLiteUtils::ExecuteRawSQL(db, userVersionSql); +} + +int SQLiteUtils::MapSQLiteErrno(int errCode) +{ + if (errCode == SQLITE_OK) { + return E_OK; + } else if (errCode == SQLITE_IOERR) { + if (errno == EKEYREVOKED) { + return -E_EKEYREVOKED; + } + } else if (errCode == SQLITE_CORRUPT || errCode == SQLITE_NOTADB) { + return -E_INVALID_PASSWD_OR_CORRUPTED_DB; + } else if (errCode == SQLITE_LOCKED || errCode == SQLITE_BUSY) { + return -E_BUSY; + } else if (errCode == SQLITE_ERROR && errno == EKEYREVOKED) { + return -E_EKEYREVOKED; + } + return -errCode; +} + +int SQLiteUtils::SetBusyTimeout(sqlite3 *db, int timeout) +{ + if (db == nullptr) { + return -E_INVALID_DB; + } + + // Set the default busy handler to retry automatically before returning SQLITE_BUSY. + int errCode = sqlite3_busy_timeout(db, timeout); + if (errCode != SQLITE_OK) { + LOGE("[SQLite] set busy timeout failed:%d", errCode); + } + + return SQLiteUtils::MapSQLiteErrno(errCode); +} + +#ifndef OMIT_ENCRYPT +int SQLiteUtils::ExportDatabase(const std::string &srcFile, CipherType type, const CipherPassword &srcPasswd, + const std::string &targetFile, const CipherPassword &passwd) +{ + std::vector createTableSqls; + OpenDbProperties option = {srcFile, true, false, createTableSqls, type, srcPasswd}; + sqlite3 *db = nullptr; + int errCode = SQLiteUtils::OpenDatabase(option, db); + if (errCode != E_OK) { + LOGE("Open db error while exporting:%d", errCode); + return errCode; + } + + errCode = SQLiteUtils::ExportDatabase(db, type, passwd, targetFile); + if (db != nullptr) { + (void)sqlite3_close_v2(db); + db = nullptr; + } + return errCode; +} +#else +int SQLiteUtils::ExportDatabase(const std::string &srcFile, CipherType type, const CipherPassword &srcPasswd, + const std::string &targetFile, const CipherPassword &passwd) +{ + (void)srcFile; + (void)type; + (void)srcPasswd; + (void)targetFile; + (void)passwd; + return -E_NOT_SUPPORT; +} +#endif + +int SQLiteUtils::SaveSchema(const OpenDbProperties &properties) +{ + if (properties.uri.empty()) { + return -E_INVALID_ARGS; + } + + sqlite3 *db = nullptr; + int errCode = OpenDatabase(properties, db); + if (errCode != E_OK) { + return errCode; + } + + errCode = SaveSchema(db, properties.schema); + (void)sqlite3_close_v2(db); + db = nullptr; + return errCode; +} + +int SQLiteUtils::SaveSchema(sqlite3 *db, const std::string &strSchema) +{ + if (db == nullptr) { + return -E_INVALID_DB; + } + + sqlite3_stmt *statement = nullptr; + std::string sql = "INSERT OR REPLACE INTO meta_data VALUES(?,?);"; + int errCode = GetStatement(db, sql, statement); + if (errCode != E_OK) { + return errCode; + } + + Key schemaKey; + DBCommon::StringToVector(DBConstant::SCHEMA_KEY, schemaKey); + errCode = BindBlobToStatement(statement, BIND_KEY_INDEX, schemaKey, false); + if (errCode != E_OK) { + ResetStatement(statement, true, errCode); + return errCode; + } + + Value schemaValue; + DBCommon::StringToVector(strSchema, schemaValue); + errCode = BindBlobToStatement(statement, BIND_VAL_INDEX, schemaValue, false); + if (errCode != E_OK) { + ResetStatement(statement, true, errCode); + return errCode; + } + + errCode = StepWithRetry(statement); // memory db does not support schema + if (errCode != MapSQLiteErrno(SQLITE_DONE)) { + LOGE("[SqlUtil][SetSchema] StepWithRetry fail, errCode=%d.", errCode); + ResetStatement(statement, true, errCode); + return errCode; + } + errCode = E_OK; + ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteUtils::GetSchema(const OpenDbProperties &properties, std::string &strSchema) +{ + sqlite3 *db = nullptr; + int errCode = OpenDatabase(properties, db); + if (errCode != E_OK) { + return errCode; + } + + int version = 0; + errCode = GetVersion(db, version); + if (version <= 0 || errCode != E_OK) { + // if version does exist, it represents database is error + (void)sqlite3_close_v2(db); + db = nullptr; + return -E_INVALID_VERSION; + } + + errCode = GetSchema(db, strSchema); + (void)sqlite3_close_v2(db); + db = nullptr; + return errCode; +} + +int SQLiteUtils::GetSchema(sqlite3 *db, std::string &strSchema) +{ + if (db == nullptr) { + return -E_INVALID_DB; + } + + sqlite3_stmt *statement = nullptr; + std::string sql = "SELECT value FROM meta_data WHERE key=?;"; + int errCode = GetStatement(db, sql, statement); + if (errCode != E_OK) { + return errCode; + } + + Key schemakey; + DBCommon::StringToVector(DBConstant::SCHEMA_KEY, schemakey); + errCode = BindBlobToStatement(statement, 1, schemakey, false); + if (errCode != E_OK) { + ResetStatement(statement, true, errCode); + return errCode; + } + + errCode = StepWithRetry(statement); // memory db does not support schema + if (errCode == MapSQLiteErrno(SQLITE_DONE)) { + ResetStatement(statement, true, errCode); + return -E_NOT_FOUND; + } else if (errCode != MapSQLiteErrno(SQLITE_ROW)) { + ResetStatement(statement, true, errCode); + return errCode; + } + + Value schemaValue; + errCode = GetColumnBlobValue(statement, 0, schemaValue); + if (errCode != E_OK) { + ResetStatement(statement, true, errCode); + return errCode; + } + DBCommon::VectorToString(schemaValue, strSchema); + ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteUtils::IncreaseIndex(sqlite3 *db, const IndexName &name, const IndexInfo &info, SchemaType type, + uint32_t skipSize) +{ + if (db == nullptr) { + LOGE("[IncreaseIndex] Sqlite DB not exists."); + return -E_INVALID_DB; + } + if (name.empty()) { + LOGE("[IncreaseIndex] Name can not be empty."); + return -E_NOT_PERMIT; + } + if (info.empty()) { + LOGE("[IncreaseIndex] Info can not be empty."); + return -E_NOT_PERMIT; + } + std::string indexName = SchemaUtils::FieldPathString(name); + std::string sqlCommand = "CREATE INDEX IF NOT EXISTS '" + indexName + "' ON sync_data ("; + for (uint32_t i = 0; i < info.size(); i++) { + if (i != 0) { + sqlCommand += ", "; + } + std::string extractSql = SchemaObject::GenerateExtractSQL(type, info[i].first, info[i].second, + skipSize); + if (extractSql.empty()) { // Unlikely + LOGE("[IncreaseIndex] GenerateExtractSQL fail at field=%u.", i); + return -E_INTERNAL_ERROR; + } + sqlCommand += extractSql; + } + sqlCommand += ") WHERE (flag&0x01=0);"; + return SQLiteUtils::ExecuteRawSQL(db, sqlCommand); +} + +int SQLiteUtils::ChangeIndex(sqlite3 *db, const IndexName &name, const IndexInfo &info, SchemaType type, + uint32_t skipSize) +{ + // Currently we change index by drop it then create it, SQLite "REINDEX" may be used in the future + int errCode = DecreaseIndex(db, name); + if (errCode != OK) { + LOGE("[ChangeIndex] Decrease fail=%d.", errCode); + return errCode; + } + errCode = IncreaseIndex(db, name, info, type, skipSize); + if (errCode != OK) { + LOGE("[ChangeIndex] Increase fail=%d.", errCode); + return errCode; + } + return E_OK; +} + +int SQLiteUtils::DecreaseIndex(sqlite3 *db, const IndexName &name) +{ + if (db == nullptr) { + LOGE("[DecreaseIndex] Sqlite DB not exists."); + return -E_INVALID_DB; + } + if (name.empty()) { + LOGE("[DecreaseIndex] Name can not be empty."); + return -E_NOT_PERMIT; + } + std::string indexName = SchemaUtils::FieldPathString(name); + std::string sqlCommand = "DROP INDEX IF EXISTS '" + indexName + "';"; + return ExecuteRawSQL(db, sqlCommand); +} + +int SQLiteUtils::RegisterJsonFunctions(sqlite3 *db) +{ + if (db == nullptr) { + LOGE("Sqlite DB not exists."); + return -E_INVALID_DB; + } + int errCode = sqlite3_create_function_v2(db, "calc_hash_key", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, + nullptr, &CalcHashKey, nullptr, nullptr, nullptr); + if (errCode != SQLITE_OK) { + LOGE("sqlite3_create_function_v2 about calc_hash_key returned %d", errCode); + return MapSQLiteErrno(errCode); + } +#ifdef USING_DB_JSON_EXTRACT_AUTOMATICALLY + errCode = ExecuteRawSQL(db, JSON_EXTRACT_BY_PATH_TEST_CREATED); + if (errCode == E_OK) { + LOGI("json_extract_by_path already created."); + } else { + // Specify need 3 parameter in json_extract_by_path function + errCode = sqlite3_create_function_v2(db, "json_extract_by_path", 3, SQLITE_UTF8 | SQLITE_DETERMINISTIC, + nullptr, &JsonExtractByPath, nullptr, nullptr, nullptr); + if (errCode != SQLITE_OK) { + LOGE("sqlite3_create_function_v2 about json_extract_by_path returned %d", errCode); + return MapSQLiteErrno(errCode); + } + } +#endif + return E_OK; +} + +namespace { +void SchemaObjectDestructor(SchemaObject *inObject) +{ + delete inObject; + inObject = nullptr; +} +} +#ifdef RELATIONAL_STORE +int SQLiteUtils::RegisterCalcHash(sqlite3 *db) +{ + TransactFunc func; + func.xFunc = &CalcHashKey; + return SQLiteUtils::RegisterFunction(db, "calc_hash", 1, nullptr, func); +} + +void SQLiteUtils::GetSysTime(sqlite3_context *ctx, int argc, sqlite3_value **argv) +{ + if (ctx == nullptr || argc != 1 || argv == nullptr) { + LOGE("Parameter does not meet restrictions."); + return; + } + + sqlite3_result_int64(ctx, (sqlite3_int64)TimeHelper::GetSysCurrentTime()); +} + +int SQLiteUtils::RegisterGetSysTime(sqlite3 *db) +{ + TransactFunc func; + func.xFunc = &GetSysTime; + return SQLiteUtils::RegisterFunction(db, "get_sys_time", 1, nullptr, func); +} + +int SQLiteUtils::CreateRelationalMetaTable(sqlite3 *db) +{ + std::string sql = + "CREATE TABLE IF NOT EXISTS " + DBConstant::RELATIONAL_PREFIX + "metadata(" \ + "key BLOB PRIMARY KEY NOT NULL," \ + "value BLOB);"; + + int errCode = SQLiteUtils::ExecuteRawSQL(db, sql); + if (errCode != E_OK) { + LOGE("[SQLite] execute create table sql failed"); + return errCode; + } + return E_OK; +} + +int SQLiteUtils::CreateRelationalLogTable(sqlite3 *db, const std::string &oriTableName) +{ + const std::string tableName = DBConstant::RELATIONAL_PREFIX + oriTableName + "_log"; + std::string sql = + "CREATE TABLE IF NOT EXISTS " + tableName + "(" \ + "data_key INT NOT NULL," \ + "device BLOB," \ + "ori_device BLOB," \ + "timestamp INT NOT NULL," \ + "wtimestamp INT NOT NULL," \ + "flag INT NOT NULL," \ + "hash_key BLOB NOT NULL," + "PRIMARY KEY(device,hash_key));" + "CREATE INDEX IF NOT EXISTS " + DBConstant::RELATIONAL_PREFIX + "time_flag_index ON " + tableName + + "(timestamp, flag);" + "CREATE INDEX IF NOT EXISTS " + DBConstant::RELATIONAL_PREFIX + "hashkey_index ON " + tableName + "(hash_key);"; + + int errCode = SQLiteUtils::ExecuteRawSQL(db, sql); + if (errCode != E_OK) { + LOGE("[SQLite] execute create table sql failed"); + } + return errCode; +} + +namespace { +std::string GetInsertTrigger(const TableInfo &table) +{ + std::string logTblName = DBConstant::RELATIONAL_PREFIX + table.GetTableName() + "_log"; + std::string insertTrigger = "CREATE TRIGGER IF NOT EXISTS "; + insertTrigger += "naturalbase_rdb_" + table.GetTableName() + "_ON_INSERT AFTER INSERT \n"; + insertTrigger += "ON " + table.GetTableName() + "\n"; + insertTrigger += "BEGIN\n"; + insertTrigger += "\t INSERT OR REPLACE INTO " + logTblName; + insertTrigger += " (data_key, device, ori_device, timestamp, wtimestamp, flag, hash_key)"; + insertTrigger += " VALUES (new.rowid, '', '',"; + insertTrigger += " get_sys_time(0), get_sys_time(0),"; + insertTrigger += " CASE WHEN (SELECT count(*)<>0 FROM " + logTblName + " WHERE hash_key=calc_hash(new." + + table.GetPrimaryKey() + ") AND flag&0x02=0x02) THEN 0x22 ELSE 0x02 END,"; + insertTrigger += " calc_hash(new." + table.GetPrimaryKey() + "));\n"; + insertTrigger += "END;"; + return insertTrigger; +} + +std::string GetUpdateTrigger(const TableInfo &table) +{ + std::string updateTrigger = "CREATE TRIGGER IF NOT EXISTS "; + updateTrigger += "naturalbase_rdb_" + table.GetTableName() + "_ON_UPDATE AFTER UPDATE \n"; + updateTrigger += "ON " + table.GetTableName() + "\n"; + updateTrigger += "BEGIN\n"; + updateTrigger += "\t UPDATE " + DBConstant::RELATIONAL_PREFIX + table.GetTableName() + "_log"; + updateTrigger += " SET timestamp=get_sys_time(0), device='', flag=0x22"; + updateTrigger += " where hash_key=calc_hash(old." + table.GetPrimaryKey() + ") and flag&0x02=0x02;\n"; + updateTrigger += "END;"; + return updateTrigger; +} + +std::string GetDeleteTrigger(const TableInfo &table) +{ + std::string deleteTrigger = "CREATE TRIGGER IF NOT EXISTS "; + deleteTrigger += "naturalbase_rdb_" + table.GetTableName() + "_ON_DELETE BEFORE DELETE \n"; + deleteTrigger += "ON " + table.GetTableName() + "\n"; + deleteTrigger += "BEGIN\n"; + deleteTrigger += "\t UPDATE " + DBConstant::RELATIONAL_PREFIX + table.GetTableName() + "_log"; + deleteTrigger += " SET flag=0x03,timestamp=get_sys_time(0)"; + deleteTrigger += " where hash_key=calc_hash(old." + table.GetPrimaryKey() + ") and flag&0x02=0x02;\n"; + deleteTrigger += "END;"; + return deleteTrigger; +} +} + +int SQLiteUtils::AddRelationalLogTableTrigger(sqlite3 *db, const TableInfo &table) +{ + std::vector sqls = {GetInsertTrigger(table), GetUpdateTrigger(table), GetDeleteTrigger(table)}; + // add insert,update,delete trigger + for (const auto &sql : sqls) { + int errCode = SQLiteUtils::ExecuteRawSQL(db, sql); + if (errCode != E_OK) { + LOGE("[SQLite] execute create log trigger sql failed"); + return errCode; + } + } + return E_OK; +} + +int SQLiteUtils::CreateSameStuTable(sqlite3 *db, const TableInfo &baseTbl, const std::string &newTableName) +{ + std::string sql = "CREATE TABLE IF NOT EXISTS " + newTableName + "("; + const std::map &fields = baseTbl.GetFields(); + for (uint32_t cid = 0; cid < fields.size(); ++cid) { + std::string fieldName = baseTbl.GetFieldName(cid); + sql += fieldName + " " + fields.at(fieldName).GetDataType(); + if (fields.at(fieldName).IsNotNull()) { + sql += " NOT NULL"; + } + if (fields.at(fieldName).HasDefaultValue()) { + sql += " DEFAULT " + fields.at(fieldName).GetDefaultValue(); + } + if (fieldName == baseTbl.GetPrimaryKey()) { + sql += " PRIMARY KEY"; + } + sql += ","; + } + sql.pop_back(); + sql += ");"; + int errCode = SQLiteUtils::ExecuteRawSQL(db, sql); + if (errCode != E_OK) { + LOGE("[SQLite] execute create table sql failed"); + } + return errCode; +} + +int SQLiteUtils::CloneIndexes(sqlite3 *db, const std::string &oriTableName, const std::string &newTableName) +{ + std::string sql = + "SELECT 'CREATE ' || CASE WHEN il.'unique' THEN 'UNIQUE ' ELSE '' END || 'INDEX IF NOT EXISTS ' || '" + + newTableName + "_' || il.name || ' ON ' || '" + newTableName + + "' || '(' || GROUP_CONCAT(ii.name) || ');' " + "FROM sqlite_master AS m," + "pragma_index_list(m.name) AS il," + "pragma_index_info(il.name) AS ii " + "WHERE m.type='table' AND m.name='" + oriTableName + "' AND il.origin='c' " + "GROUP BY il.name;"; + sqlite3_stmt *stmt = nullptr; + int errCode = SQLiteUtils::GetStatement(db, sql, stmt); + if (errCode != E_OK) { + LOGE("Prepare the clone sql failed:%d", errCode); + return errCode; + } + + sql.clear(); + while (true) { + errCode = SQLiteUtils::StepWithRetry(stmt, false); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + std::string indexSql; + (void)GetColumnTextValue(stmt, 0, indexSql); + sql += indexSql; + continue; + } + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = E_OK; + } + (void)ResetStatement(stmt, true, errCode); + break; + } + + if (errCode != E_OK) { + return errCode; + } + errCode = SQLiteUtils::ExecuteRawSQL(db, sql); + if (errCode != E_OK) { + LOGE("[SQLite] execute create table sql failed"); + } + return errCode; +} + +int SQLiteUtils::RegisterFunction(sqlite3 *db, const std::string &funcName, int nArg, void *uData, TransactFunc &func) +{ + if (db == nullptr) { + LOGE("Sqlite DB not exists."); + return -E_INVALID_DB; + } + + int errCode = sqlite3_create_function_v2(db, funcName.c_str(), nArg, SQLITE_UTF8 | SQLITE_DETERMINISTIC, uData, + func.xFunc, func.xStep, func.xFinal, func.xDestroy); + if (errCode != SQLITE_OK) { + LOGE("sqlite3_create_function_v2 about [%s] returned %d", funcName.c_str(), errCode); + return MapSQLiteErrno(errCode); + } + return E_OK; +} +#endif +int SQLiteUtils::RegisterFlatBufferFunction(sqlite3 *db, const std::string &inSchema) +{ + if (db == nullptr) { + LOGE("Sqlite DB not exists."); + return -E_INVALID_DB; + } + auto heapSchemaObj = new (std::nothrow) SchemaObject; + if (heapSchemaObj == nullptr) { + return -E_OUT_OF_MEMORY; + } + int errCode = heapSchemaObj->ParseFromSchemaString(inSchema); + if (errCode != E_OK) { // Unlikely, it has been parsed before + delete heapSchemaObj; + heapSchemaObj = nullptr; + return -E_INTERNAL_ERROR; + } + if (heapSchemaObj->GetSchemaType() != SchemaType::FLATBUFFER) { // Do not need to register FlatBufferExtract + delete heapSchemaObj; + heapSchemaObj = nullptr; + return E_OK; + } + errCode = sqlite3_create_function_v2(db, SchemaObject::GetExtractFuncName(SchemaType::FLATBUFFER).c_str(), + 3, SQLITE_UTF8 | SQLITE_DETERMINISTIC, heapSchemaObj, &FlatBufferExtractByPath, nullptr, nullptr, // 3 args + reinterpret_cast(SchemaObjectDestructor)); + // About the release of heapSchemaObj: SQLite guarantee that at following case, sqlite will invoke the destructor + // (that is SchemaObjectDestructor) we passed to it. See sqlite.org for more information. + // The destructor is invoked when the function is deleted, either by being overloaded or when the database + // connection closes. The destructor is also invoked if the call to sqlite3_create_function_v2() fails + if (errCode != SQLITE_OK) { + LOGE("sqlite3_create_function_v2 about flatbuffer_extract_by_path return=%d.", errCode); + // As mentioned above, SQLite had invoked the SchemaObjectDestructor to release the heapSchemaObj + return MapSQLiteErrno(errCode); + } + return E_OK; +} + +void SQLiteUtils::UpdateMetaDataWithinTrigger(sqlite3_context *ctx, int argc, sqlite3_value **argv) +{ + if (ctx == nullptr || argc != 2 || argv == nullptr) { // 2 : Number of parameters for sqlite register function + LOGE("[UpdateMetaDataWithinTrigger] Invalid parameter, argc=%d.", argc); + return; + } + auto *handle = static_cast(sqlite3_user_data(ctx)); + if (handle == nullptr) { + sqlite3_result_error(ctx, "Sqlite context is invalid.", USING_STR_LEN); + LOGE("Sqlite context is invalid."); + return; + } + auto *keyPtr = static_cast(sqlite3_value_blob(argv[0])); // 0 : first argv for key + int keyLen = sqlite3_value_bytes(argv[0]); // 0 : first argv for key + if (keyPtr == nullptr || keyLen <= 0 || keyLen > static_cast(DBConstant::MAX_KEY_SIZE)) { + sqlite3_result_error(ctx, "key is invalid.", USING_STR_LEN); + LOGE("key is invalid."); + return; + } + auto val = sqlite3_value_int64(argv[1]); // 1 : second argv for value + + sqlite3_stmt *stmt = nullptr; + int errCode = SQLiteUtils::GetStatement(handle, UPDATE_META_SQL, stmt); + if (errCode != E_OK) { + sqlite3_result_error(ctx, "Get update meta_data statement failed.", USING_STR_LEN); + LOGE("Get update meta_data statement failed. %d", errCode); + return; + } + + Key key(keyPtr, keyPtr + keyLen); + errCode = SQLiteUtils::BindBlobToStatement(stmt, BIND_KEY_INDEX, key, false); + if (errCode != E_OK) { + sqlite3_result_error(ctx, "Bind key to statement failed.", USING_STR_LEN); + LOGE("Bind key to statement failed. %d", errCode); + goto END; + } + + errCode = SQLiteUtils::BindInt64ToStatement(stmt, BIND_VAL_INDEX, val); + if (errCode != E_OK) { + sqlite3_result_error(ctx, "Bind value to statement failed.", USING_STR_LEN); + LOGE("Bind value to statement failed. %d", errCode); + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(stmt, false); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + sqlite3_result_error(ctx, "Execute the update meta_data attach failed.", USING_STR_LEN); + LOGE("Execute the update meta_data attach failed. %d", errCode); + } +END: + SQLiteUtils::ResetStatement(stmt, true, errCode); +} + +int SQLiteUtils::RegisterMetaDataUpdateFunction(sqlite3 *db) +{ + int errCode = sqlite3_create_function_v2(db, DBConstant::UPDATE_META_FUNC.c_str(), + 2, // 2: argc for register function + SQLITE_UTF8 | SQLITE_DETERMINISTIC, db, &SQLiteUtils::UpdateMetaDataWithinTrigger, nullptr, nullptr, nullptr); + if (errCode != SQLITE_OK) { + LOGE("sqlite3_create_function_v2 about %s returned %d", DBConstant::UPDATE_META_FUNC.c_str(), errCode); + } + return SQLiteUtils::MapSQLiteErrno(errCode); +} + +struct ValueParseCache { + ValueObject valueParsed; + std::vector valueOriginal; +}; + +namespace { +inline bool IsDeleteRecord(const uint8_t *valueBlob, int valueBlobLen) +{ + return (valueBlob == nullptr) || (valueBlobLen <= 0); // In fact, sqlite guarantee valueBlobLen not negative +} + +// Use the same cache id as sqlite use for json_extract which is substituted by our json_extract_by_path +// A negative cache-id enables sharing of cache between different operation during the same statement +constexpr int VALUE_CACHE_ID = -429938; + +void ValueParseCacheFree(ValueParseCache *inCache) +{ + delete inCache; + inCache = nullptr; +} + +// We don't use cache array since we only cache value column of sqlite table, see sqlite implementation for compare. +const ValueObject *ParseValueThenCacheOrGetFromCache(sqlite3_context *ctx, const uint8_t *valueBlob, + uint32_t valueBlobLen, uint32_t offset) +{ + // Note: All parameter had already been check inside JsonExtractByPath, only called by JsonExtractByPath + auto cached = static_cast(sqlite3_get_auxdata(ctx, VALUE_CACHE_ID)); + if (cached != nullptr) { // A previous cache exist + if (cached->valueOriginal.size() == valueBlobLen) { + if (std::memcmp(cached->valueOriginal.data(), valueBlob, valueBlobLen) == 0) { + // Cache match + return &(cached->valueParsed); + } + } + } + // No cache or cache mismatch + auto newCache = new (std::nothrow) ValueParseCache; + if (newCache == nullptr) { + sqlite3_result_error(ctx, "[ParseValueCache] OOM.", USING_STR_LEN); + LOGE("[ParseValueCache] OOM."); + return nullptr; + } + int errCode = newCache->valueParsed.Parse(valueBlob, valueBlob + valueBlobLen, offset); + if (errCode != E_OK) { + sqlite3_result_error(ctx, "[ParseValueCache] Parse fail.", USING_STR_LEN); + LOGE("[ParseValueCache] Parse fail, errCode=%d.", errCode); + delete newCache; + newCache = nullptr; + return nullptr; + } + newCache->valueOriginal.assign(valueBlob, valueBlob + valueBlobLen); + sqlite3_set_auxdata(ctx, VALUE_CACHE_ID, newCache, reinterpret_cast(ValueParseCacheFree)); + // If sqlite3_set_auxdata fail, it will immediately call ValueParseCacheFree to delete newCache; + // Next time sqlite3_set_auxdata will call ValueParseCacheFree to delete newCache of this time; + // At the end, newCache will be eventually deleted when call sqlite3_reset or sqlite3_finalize; + // Since sqlite3_set_auxdata may fail, we have to call sqlite3_get_auxdata other than return newCache directly. + auto cacheInAuxdata = static_cast(sqlite3_get_auxdata(ctx, VALUE_CACHE_ID)); + if (cacheInAuxdata == nullptr) { + return nullptr; + } + return &(cacheInAuxdata->valueParsed); +} +} + +void SQLiteUtils::JsonExtractByPath(sqlite3_context *ctx, int argc, sqlite3_value **argv) +{ + if (ctx == nullptr || argc != 3 || argv == nullptr) { // 3 parameters, which are value, path and offset + LOGE("[JsonExtract] Invalid parameter, argc=%d.", argc); + return; + } + auto valueBlob = static_cast(sqlite3_value_blob(argv[0])); + int valueBlobLen = sqlite3_value_bytes(argv[0]); + if (IsDeleteRecord(valueBlob, valueBlobLen)) { + // Currently delete records are filtered out of query and create-index sql, so not allowed here. + sqlite3_result_error(ctx, "[JsonExtract] Delete record not allowed.", USING_STR_LEN); + LOGE("[JsonExtract] Delete record not allowed."); + return; + } + auto path = reinterpret_cast(sqlite3_value_text(argv[1])); + int offset = sqlite3_value_int(argv[2]); // index 2 is the third parameter + if ((path == nullptr) || (offset < 0)) { + sqlite3_result_error(ctx, "[JsonExtract] Path nullptr or offset invalid.", USING_STR_LEN); + LOGE("[JsonExtract] Path nullptr or offset=%d invalid.", offset); + return; + } + FieldPath outPath; + int errCode = SchemaUtils::ParseAndCheckFieldPath(path, outPath); + if (errCode != E_OK) { + sqlite3_result_error(ctx, "[JsonExtract] Path illegal.", USING_STR_LEN); + LOGE("[JsonExtract] Path=%s illegal.", path); + return; + } + // Parameter Check Done Here + const ValueObject *valueObj = ParseValueThenCacheOrGetFromCache(ctx, valueBlob, static_cast(valueBlobLen), + static_cast(offset)); + if (valueObj == nullptr) { + return; // Necessary had been printed in ParseValueThenCacheOrGetFromCache + } + JsonExtractInnerFunc(ctx, *valueObj, outPath); +} + +namespace { +inline bool IsExtractableType(FieldType inType) +{ + return (inType != FieldType::LEAF_FIELD_NULL && inType != FieldType::LEAF_FIELD_ARRAY && + inType != FieldType::LEAF_FIELD_OBJECT && inType != FieldType::INTERNAL_FIELD_OBJECT); +} +} + +void SQLiteUtils::JsonExtractInnerFunc(sqlite3_context *ctx, const ValueObject &inValue, const FieldPath &inPath) +{ + FieldType outType = FieldType::LEAF_FIELD_NULL; // Default type null for invalid-path(path not exist) + int errCode = inValue.GetFieldTypeByFieldPath(inPath, outType); + if (errCode != E_OK && errCode != -E_INVALID_PATH) { + sqlite3_result_error(ctx, "[JsonExtract] GetFieldType fail.", USING_STR_LEN); + LOGE("[JsonExtract] GetFieldType fail, errCode=%d.", errCode); + return; + } + FieldValue outValue; + if (IsExtractableType(outType)) { + errCode = inValue.GetFieldValueByFieldPath(inPath, outValue); + if (errCode != E_OK) { + sqlite3_result_error(ctx, "[JsonExtract] GetFieldValue fail.", USING_STR_LEN); + LOGE("[JsonExtract] GetFieldValue fail, errCode=%d.", errCode); + return; + } + } + // FieldType null, array, object do not have value, all these FieldValue will be regarded as null in JsonReturn. + ExtractReturn(ctx, outType, outValue); +} + +// NOTE!!! This function is performance sensitive !!! Carefully not to allocate memory often!!! +void SQLiteUtils::FlatBufferExtractByPath(sqlite3_context *ctx, int argc, sqlite3_value **argv) +{ + if (ctx == nullptr || argc != 3 || argv == nullptr) { // 3 parameters, which are value, path and offset + LOGE("[FlatBufferExtract] Invalid parameter, argc=%d.", argc); + return; + } + auto schema = static_cast(sqlite3_user_data(ctx)); + if (schema == nullptr || !schema->IsSchemaValid() || (schema->GetSchemaType() != SchemaType::FLATBUFFER)) { + sqlite3_result_error(ctx, "[FlatBufferExtract] No SchemaObject or invalid.", USING_STR_LEN); + LOGE("[FlatBufferExtract] No SchemaObject or invalid."); + return; + } + // Get information from argv + auto valueBlob = static_cast(sqlite3_value_blob(argv[0])); + int valueBlobLen = sqlite3_value_bytes(argv[0]); + if (IsDeleteRecord(valueBlob, valueBlobLen)) { + // Currently delete records are filtered out of query and create-index sql, so not allowed here. + sqlite3_result_error(ctx, "[FlatBufferExtract] Delete record not allowed.", USING_STR_LEN); + LOGE("[FlatBufferExtract] Delete record not allowed."); + return; + } + auto path = reinterpret_cast(sqlite3_value_text(argv[1])); + int offset = sqlite3_value_int(argv[2]); // index 2 is the third parameter + if ((path == nullptr) || (offset < 0) || (static_cast(offset) != schema->GetSkipSize())) { + sqlite3_result_error(ctx, "[FlatBufferExtract] Path null or offset invalid.", USING_STR_LEN); + LOGE("[FlatBufferExtract] Path null or offset=%d(skipsize=%u) invalid.", offset, schema->GetSkipSize()); + return; + } + FlatBufferExtractInnerFunc(ctx, *schema, RawValue{valueBlob, valueBlobLen}, path); +} + +namespace { +constexpr uint32_t FLATBUFFER_MAX_CACHE_SIZE = 102400; // 100 KBytes + +void FlatBufferCacheFree(std::vector *inCache) +{ + delete inCache; + inCache = nullptr; +} +} + +void SQLiteUtils::FlatBufferExtractInnerFunc(sqlite3_context *ctx, const SchemaObject &schema, const RawValue &inValue, + RawString inPath) +{ + // All parameter had already been check inside FlatBufferExtractByPath, only called by FlatBufferExtractByPath + if (schema.GetSkipSize() % SchemaConstant::SECURE_BYTE_ALIGN == 0) { + TypeValue outExtract; + int errCode = schema.ExtractValue(ValueSource::FROM_DBFILE, inPath, inValue, outExtract, nullptr); + if (errCode != E_OK) { + sqlite3_result_error(ctx, "[FlatBufferExtract] ExtractValue fail.", USING_STR_LEN); + LOGE("[FlatBufferExtract] ExtractValue fail, errCode=%d.", errCode); + return; + } + ExtractReturn(ctx, outExtract.first, outExtract.second); + return; + } + // Not byte-align secure, we have to make a cache for copy. Check whether cache had already exist. + auto cached = static_cast *>(sqlite3_get_auxdata(ctx, VALUE_CACHE_ID)); // Share the same id + if (cached == nullptr) { + // Make the cache + auto newCache = new (std::nothrow) std::vector; + if (newCache == nullptr) { + sqlite3_result_error(ctx, "[FlatBufferExtract] OOM.", USING_STR_LEN); + LOGE("[FlatBufferExtract] OOM."); + return; + } + newCache->resize(FLATBUFFER_MAX_CACHE_SIZE); + sqlite3_set_auxdata(ctx, VALUE_CACHE_ID, newCache, reinterpret_cast(FlatBufferCacheFree)); + // If sqlite3_set_auxdata fail, it will immediately call FlatBufferCacheFree to delete newCache; + // Next time sqlite3_set_auxdata will call FlatBufferCacheFree to delete newCache of this time; + // At the end, newCache will be eventually deleted when call sqlite3_reset or sqlite3_finalize; + // Since sqlite3_set_auxdata may fail, we have to call sqlite3_get_auxdata other than return newCache directly. + // See sqlite.org for more information. + cached = static_cast *>(sqlite3_get_auxdata(ctx, VALUE_CACHE_ID)); + } + if (cached == nullptr) { + LOGW("[FlatBufferExtract] Something wrong with Auxdata, but it is no matter without cache."); + } + TypeValue outExtract; + int errCode = schema.ExtractValue(ValueSource::FROM_DBFILE, inPath, inValue, outExtract, cached); + if (errCode != E_OK) { + sqlite3_result_error(ctx, "[FlatBufferExtract] ExtractValue fail.", USING_STR_LEN); + LOGE("[FlatBufferExtract] ExtractValue fail, errCode=%d.", errCode); + return; + } + ExtractReturn(ctx, outExtract.first, outExtract.second); +} + +void SQLiteUtils::ExtractReturn(sqlite3_context *ctx, FieldType type, const FieldValue &value) +{ + if (ctx == nullptr) { + return; + } + switch (type) { + case FieldType::LEAF_FIELD_BOOL: + sqlite3_result_int(ctx, (value.boolValue ? 1 : 0)); + break; + case FieldType::LEAF_FIELD_INTEGER: + sqlite3_result_int(ctx, value.integerValue); + break; + case FieldType::LEAF_FIELD_LONG: + sqlite3_result_int64(ctx, value.longValue); + break; + case FieldType::LEAF_FIELD_DOUBLE: + sqlite3_result_double(ctx, value.doubleValue); + break; + case FieldType::LEAF_FIELD_STRING: + // The SQLITE_TRANSIENT value means that the content will likely change in the near future and + // that SQLite should make its own private copy of the content before returning. + sqlite3_result_text(ctx, value.stringValue.c_str(), -1, SQLITE_TRANSIENT); // -1 mean use the string length + break; + default: + // All other type regard as null + sqlite3_result_null(ctx); + } + return; +} + +void SQLiteUtils::CalcHashKey(sqlite3_context *ctx, int argc, sqlite3_value **argv) +{ + // 1 means that the function only needs one parameter, namely key + if (ctx == nullptr || argc != 1 || argv == nullptr) { + LOGE("Parameter does not meet restrictions."); + return; + } + auto keyBlob = static_cast(sqlite3_value_blob(argv[0])); + if (keyBlob == nullptr) { + sqlite3_result_error(ctx, "Parameters is invalid.", USING_STR_LEN); + LOGE("Parameters is invalid."); + return; + } + int blobLen = sqlite3_value_bytes(argv[0]); + std::vector value(keyBlob, keyBlob + blobLen); + std::vector hashValue; + int errCode = DBCommon::CalcValueHash(value, hashValue); + if (errCode != E_OK) { + sqlite3_result_error(ctx, "Get hash value error.", USING_STR_LEN); + LOGE("Get hash value error."); + return; + } + sqlite3_result_blob(ctx, hashValue.data(), hashValue.size(), SQLITE_TRANSIENT); + return; +} + +int SQLiteUtils::GetDbSize(const std::string &dir, const std::string &dbName, uint64_t &size) +{ + std::string dataDir = dir + "/" + dbName + DBConstant::SQLITE_DB_EXTENSION; + uint64_t localDbSize = 0; + int errCode = OS::CalFileSize(dataDir, localDbSize); + if (errCode != E_OK) { + LOGD("Failed to get the db file size, errCode:%d", errCode); + return errCode; + } + + std::string shmFileName = dataDir + "-shm"; + uint64_t localshmFileSize = 0; + errCode = OS::CalFileSize(shmFileName, localshmFileSize); + if (errCode != E_OK) { + localshmFileSize = 0; + } + + std::string walFileName = dataDir + "-wal"; + uint64_t localWalFileSize = 0; + errCode = OS::CalFileSize(walFileName, localWalFileSize); + if (errCode != E_OK) { + localWalFileSize = 0; + } + + // 64-bit system is Suffice. Computer storage is less than uint64_t max + size += (localDbSize + localshmFileSize + localWalFileSize); + return E_OK; +} + +int SQLiteUtils::ExplainPlan(sqlite3 *db, const std::string &execSql, bool isQueryPlan) +{ + if (db == nullptr) { + return -E_INVALID_DB; + } + + sqlite3_stmt *statement = nullptr; + std::string explainSql = (isQueryPlan ? "explain query plan " : "explain ") + execSql; + int errCode = GetStatement(db, explainSql, statement); + if (errCode != E_OK) { + return errCode; + } + + bool isFirst = true; + errCode = StepWithRetry(statement); // memory db does not support schema + while (errCode == MapSQLiteErrno(SQLITE_ROW)) { + int nCol = sqlite3_column_count(statement); + nCol = std::min(nCol, 8); // Read 8 column at most + + if (isFirst) { + LOGD("#### %s", GetColString(statement, nCol).c_str()); + isFirst = false; + } + + std::string rowString; + for (int i = 0; i < nCol; i++) { + if (sqlite3_column_text(statement, i) != nullptr) { + rowString += reinterpret_cast(sqlite3_column_text(statement, i)); + } + int blankFill = (i + 1) * 16 - rowString.size(); // each column width 16 + rowString.append(static_cast((blankFill > 0) ? blankFill : 0), ' '); + } + LOGD("#### %s", rowString.c_str()); + errCode = StepWithRetry(statement); + } + if (errCode != MapSQLiteErrno(SQLITE_DONE)) { + LOGE("[SqlUtil][Explain] StepWithRetry fail, errCode=%d.", errCode); + ResetStatement(statement, true, errCode); + return errCode; + } + errCode = E_OK; + ResetStatement(statement, true, errCode); + return errCode; +} + +int SQLiteUtils::SetDataBaseProperty(sqlite3 *db, const OpenDbProperties &properties, + const std::vector &sqls) +{ + // Set the default busy handler to retry automatically before returning SQLITE_BUSY. + int errCode = SetBusyTimeout(db, BUSY_TIMEOUT_MS); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::SetKey(db, properties.cipherType, properties.passwd, properties.isMemDb); + if (errCode != E_OK) { + LOGD("SQLiteUtils::SetKey fail!!![%d]", errCode); + return errCode; + } + + for (const auto &sql : sqls) { + errCode = SQLiteUtils::ExecuteRawSQL(db, sql); + if (errCode != E_OK) { + LOGE("[SQLite] execute sql failed: %d", errCode); + return errCode; + } + } + // Create table if not exist according the sqls. + if (properties.createIfNecessary) { + for (const auto &sql : properties.sqls) { + errCode = SQLiteUtils::ExecuteRawSQL(db, sql); + if (errCode != E_OK) { + LOGE("[SQLite] execute preset sqls failed"); + return errCode; + } + } + } + return E_OK; +} + +#ifndef OMIT_ENCRYPT +int SQLiteUtils::SetCipherSettings(sqlite3 *db, CipherType type) +{ + if (db == nullptr) { + return -E_INVALID_DB; + } + std::string cipherName = GetCipherName(type); + if (cipherName.empty()) { + return -E_INVALID_ARGS; + } + std::string cipherConfig = CIPHER_CONFIG_SQL + cipherName + ";"; + int errCode = SQLiteUtils::ExecuteRawSQL(db, cipherConfig); + if (errCode != E_OK) { + LOGE("[SQLiteUtils][SetCipherSettings] config cipher failed:%d", errCode); + return errCode; + } + errCode = SQLiteUtils::ExecuteRawSQL(db, KDF_ITER_CONFIG_SQL); + if (errCode != E_OK) { + LOGE("[SQLiteUtils][SetCipherSettings] config iter failed:%d", errCode); + return errCode; + } + return errCode; +} + +std::string SQLiteUtils::GetCipherName(CipherType type) +{ + if (type == CipherType::AES_256_GCM || type == CipherType::DEFAULT) { + return "'aes-256-gcm'"; + } + return ""; +} +#endif + +int SQLiteUtils::DropTriggerByName(sqlite3 *db, const std::string &name) +{ + const std::string dropTriggerSql = "DROP TRIGGER " + name + ";"; + int errCode = SQLiteUtils::ExecuteRawSQL(db, dropTriggerSql); + if (errCode != E_OK) { + LOGE("Remove trigger failed. %d", errCode); + } + return errCode; +} + +int SQLiteUtils::ExpandedSql(sqlite3_stmt *stmt, std::string &basicString) +{ + if (stmt == nullptr) { + return -E_INVALID_ARGS; + } + char *eSql = sqlite3_expanded_sql(stmt); + if (eSql == nullptr) { + LOGE("expand statement to sql failed."); + return -E_INVALID_DATA; + } + basicString = std::string(eSql); + sqlite3_free(eSql); + return E_OK; +} + +void SQLiteUtils::ExecuteCheckPoint(sqlite3 *db) +{ + if (db == nullptr) { + return; + } + + int chkResult = sqlite3_wal_checkpoint_v2(db, nullptr, SQLITE_CHECKPOINT_TRUNCATE, nullptr, nullptr); + LOGI("SQLite checkpoint result:%d", chkResult); +} + +int SQLiteUtils::CheckTableEmpty(sqlite3 *db, const std::string &tableName, bool &isEmpty) +{ + if (db == nullptr) { + return -E_INVALID_ARGS; + } + + std::string cntSql = "SELECT min(rowid) FROM " + tableName + ";"; + sqlite3_stmt *stmt = nullptr; + int errCode = SQLiteUtils::GetStatement(db, cntSql, stmt); + if (errCode != E_OK) { + return errCode; + } + + errCode = SQLiteUtils::StepWithRetry(stmt, false); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + if (sqlite3_column_type(stmt, 0) == SQLITE_NULL) { + isEmpty = true; + } else { + isEmpty = false; + } + errCode = E_OK; + } + + SQLiteUtils::ResetStatement(stmt, true, errCode); + return SQLiteUtils::MapSQLiteErrno(errCode); +} + +int SQLiteUtils::SetPersistWalMode(sqlite3 *db) +{ + if (db == nullptr) { + return -E_INVALID_ARGS; + } + int opCode = 1; + int errCode = sqlite3_file_control(db, "main", SQLITE_FCNTL_PERSIST_WAL, &opCode); + if (errCode != SQLITE_OK) { + LOGE("Set persist wal mode failed. %d", errCode); + } + return SQLiteUtils::MapSQLiteErrno(errCode); +} + +int SQLiteUtils::CheckSchemaChanged(sqlite3_stmt *stmt, const TableInfo &table, int offset) +{ + if (stmt == nullptr) { + return -E_INVALID_ARGS; + } + + int columnNum = sqlite3_column_count(stmt); + if (columnNum - offset != static_cast(table.GetFields().size())) { + LOGE("Schema field number does not match."); + return -E_DISTRIBUTED_SCHEMA_CHANGED; + } + + auto fields = table.GetFields(); + for (int i = offset; i < columnNum; i++) { + const char *name = sqlite3_column_name(stmt, i); + std::string colName = (name == nullptr) ? std::string() : name; + const char *declType = sqlite3_column_decltype(stmt, i); + std::string colType = (declType == nullptr) ? std::string() : declType; + transform(colType.begin(), colType.end(), colType.begin(), ::tolower); + + auto it = fields.find(colName); + if (it == fields.end() || it->second.GetDataType() != colType) { + LOGE("Schema field define does not match."); + return -E_DISTRIBUTED_SCHEMA_CHANGED; + } + } + return E_OK; +} + +int64_t SQLiteUtils::GetLastRowId(sqlite3 *db) +{ + if (db == nullptr) { + return -1; + } + return sqlite3_last_insert_rowid(db); +} + +std::string SQLiteUtils::GetLastErrorMsg() +{ + std::lock_guard autoLock(logMutex_); + return lastErrorMsg_; +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sqlite/sqlite_utils.h b/mock/distributeddb/storage/src/sqlite/sqlite_utils.h new file mode 100644 index 00000000..048d1097 --- /dev/null +++ b/mock/distributeddb/storage/src/sqlite/sqlite_utils.h @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SQLITE_UTILS_H +#define SQLITE_UTILS_H + +#include +#include +#include +#include "sqlite_import.h" + +#include "db_types.h" +#include "schema_object.h" +#include "store_types.h" +#ifdef RELATIONAL_STORE +#include "relational_schema_object.h" +#endif + +namespace DistributedDB { +enum class TransactType { + DEFERRED, + IMMEDIATE, +}; + +struct TransactFunc { + void (*xFunc)(sqlite3_context*, int, sqlite3_value**) = nullptr; + void (*xStep)(sqlite3_context*, int, sqlite3_value**) = nullptr; + void (*xFinal)(sqlite3_context*) = nullptr; + void (*xDestroy)(void*) = nullptr; +}; + +namespace TriggerMode { +enum class TriggerModeEnum { + NONE, + INSERT, + UPDATE, + DELETE +}; + +std::string GetTriggerModeString(TriggerModeEnum mode); +} + +struct OpenDbProperties { + std::string uri {}; + bool createIfNecessary = true; + bool isMemDb = false; + std::vector sqls {}; + CipherType cipherType = CipherType::AES_256_GCM; + CipherPassword passwd {}; + std::string schema {}; + std::string subdir {}; + SecurityOption securityOpt {}; + int conflictReslovePolicy = DEFAULT_LAST_WIN; + bool createDirByStoreIdOnly = false; +}; + +class SQLiteUtils { +public: + // Initialize the SQLiteUtils with the given properties. + static int OpenDatabase(const OpenDbProperties &properties, sqlite3 *&db, bool setWal = true); + + // Check the statement and prepare the new if statement is null + static int GetStatement(sqlite3 *db, const std::string &sql, sqlite3_stmt *&statement); + + // Bind the Text to the statement + static int BindTextToStatement(sqlite3_stmt *statement, int index, const std::string &str); + + static int BindInt64ToStatement(sqlite3_stmt *statement, int index, int64_t value); + + // Bind the blob to the statement + static int BindBlobToStatement(sqlite3_stmt *statement, int index, const std::vector &value, + bool permEmpty = true); + + // Reset the statement + static void ResetStatement(sqlite3_stmt *&statement, bool isNeedFinalize, int &errCode); + + // Step the statement + static int StepWithRetry(sqlite3_stmt *statement, bool isMemDb = false); + + // Bind the prefix key range + static int BindPrefixKey(sqlite3_stmt *statement, int index, const Key &keyPrefix); + + static int BeginTransaction(sqlite3 *db, TransactType type = TransactType::DEFERRED); + + static int CommitTransaction(sqlite3 *db); + + static int RollbackTransaction(sqlite3 *db); + + static int ExecuteRawSQL(sqlite3 *db, const std::string &sql); + + static int SetKey(sqlite3 *db, CipherType type, const CipherPassword &passwd, const bool &isMemDb); + + static int GetColumnBlobValue(sqlite3_stmt *statement, int index, std::vector &value); + + static int GetColumnTextValue(sqlite3_stmt *statement, int index, std::string &value); + + static int ExportDatabase(sqlite3 *db, CipherType type, const CipherPassword &passwd, const std::string &newDbName); + + static int ExportDatabase(const std::string &srcFile, CipherType type, const CipherPassword &srcPasswd, + const std::string &targetFile, const CipherPassword &passwd); + + static int Rekey(sqlite3 *db, const CipherPassword &passwd); + + static int GetVersion(const OpenDbProperties &properties, int &version); + + static int GetVersion(sqlite3 *db, int &version); + + static int GetJournalMode(sqlite3 *db, std::string &mode); + + static int SetUserVer(const OpenDbProperties &properties, int version); + + static int SetUserVer(sqlite3 *db, int version); + + static int MapSQLiteErrno(int errCode); + + static int SaveSchema(const OpenDbProperties &properties); + + static int SaveSchema(sqlite3 *db, const std::string &strSchema); + + static int GetSchema(const OpenDbProperties &properties, std::string &strSchema); + + static int GetSchema(sqlite3 *db, std::string &strSchema); + + static int IncreaseIndex(sqlite3 *db, const IndexName &name, const IndexInfo &info, SchemaType type, + uint32_t skipSize); + + static int ChangeIndex(sqlite3 *db, const IndexName &name, const IndexInfo &info, SchemaType type, + uint32_t skipSize); + + static int DecreaseIndex(sqlite3 *db, const IndexName &name); + + static int RegisterJsonFunctions(sqlite3 *db); + // Register the flatBufferExtract function if the schema is of flatBuffer-type(To be refactor) + static int RegisterFlatBufferFunction(sqlite3 *db, const std::string &inSchema); + + static int RegisterMetaDataUpdateFunction(sqlite3 *db); + + static int GetDbSize(const std::string &dir, const std::string &dbName, uint64_t &size); + + static int ExplainPlan(sqlite3 *db, const std::string &execSql, bool isQueryPlan); + + static int AttachNewDatabase(sqlite3 *db, CipherType type, const CipherPassword &password, + const std::string &attachDbAbsPath, const std::string &attachAsName = "backup"); + + static int CreateMetaDatabase(const std::string &metaDbPath); + + static int CheckIntegrity(sqlite3 *db, const std::string &sql); +#ifdef RELATIONAL_STORE + static int RegisterCalcHash(sqlite3 *db); + + static int RegisterGetSysTime(sqlite3 *db); + + static int CreateRelationalLogTable(sqlite3 *db, const std::string &oriTableName); + static int CreateRelationalMetaTable(sqlite3 *db); + + static int AddRelationalLogTableTrigger(sqlite3 *db, const TableInfo &table); + static int AnalysisSchema(sqlite3 *db, const std::string &tableName, TableInfo &table); + + static int CreateSameStuTable(sqlite3 *db, const TableInfo &baseTbl, const std::string &newTableName); + static int CloneIndexes(sqlite3 *db, const std::string &oriTableName, const std::string &newTableName); +#endif + + static int DropTriggerByName(sqlite3 *db, const std::string &name); + + static int ExpandedSql(sqlite3_stmt *stmt, std::string &basicString); + + static void ExecuteCheckPoint(sqlite3 *db); + + static int CheckTableEmpty(sqlite3 *db, const std::string &tableName, bool &isEmpty); + + static int SetPersistWalMode(sqlite3 *db); + + static int CheckSchemaChanged(sqlite3_stmt *stmt, const TableInfo &table, int offset); + + static int64_t GetLastRowId(sqlite3 *db); + + static std::string GetLastErrorMsg(); +private: + + static int CreateDataBase(const OpenDbProperties &properties, sqlite3 *&dbTemp, bool setWal); + + static int SetBusyTimeout(sqlite3 *db, int timeout); + + static void JsonExtractByPath(sqlite3_context *ctx, int argc, sqlite3_value **argv); + + static void JsonExtractInnerFunc(sqlite3_context *ctx, const ValueObject &inValue, const FieldPath &inPath); + + static void FlatBufferExtractByPath(sqlite3_context *ctx, int argc, sqlite3_value **argv); + + static void FlatBufferExtractInnerFunc(sqlite3_context *ctx, const SchemaObject &schema, const RawValue &inValue, + RawString inPath); + + static void ExtractReturn(sqlite3_context *ctx, FieldType type, const FieldValue &value); + + static void CalcHashKey(sqlite3_context *ctx, int argc, sqlite3_value **argv); + + static void GetSysTime(sqlite3_context *ctx, int argc, sqlite3_value **argv); + + static int SetDataBaseProperty(sqlite3 *db, const OpenDbProperties &properties, + const std::vector &sqls); + + static int RegisterFunction(sqlite3 *db, const std::string &funcName, int nArg, void *uData, TransactFunc &func); + +#ifndef OMIT_ENCRYPT + static int SetCipherSettings(sqlite3 *db, CipherType type); + + static std::string GetCipherName(CipherType type); +#endif + + static void UpdateMetaDataWithinTrigger(sqlite3_context *ctx, int argc, sqlite3_value **argv); + + static void SqliteLogCallback(void *data, int err, const char *msg); + + static std::mutex logMutex_; + static std::string lastErrorMsg_; +}; +} // namespace DistributedDB + +#endif // SQLITE_UTILS_H diff --git a/mock/distributeddb/storage/src/storage_engine.cpp b/mock/distributeddb/storage/src/storage_engine.cpp new file mode 100644 index 00000000..73727a8d --- /dev/null +++ b/mock/distributeddb/storage/src/storage_engine.cpp @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "storage_engine.h" + +#include + +#include "db_common.h" +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +const int StorageEngine::MAX_WAIT_TIME = 30; +const int StorageEngine::MAX_WRITE_SIZE = 1; +const int StorageEngine::MAX_READ_SIZE = 16; + +StorageEngine::StorageEngine() + : isUpdated_(false), + isMigrating_(false), + engineState_(EngineState::INVALID), + commitNotifyFunc_(nullptr), + isInitialized_(false), + perm_(OperatePerm::NORMAL_PERM), + operateAbort_(false), + isExistConnection_(false) +{} + +StorageEngine::~StorageEngine() +{ + Release(); +} + +int StorageEngine::Init() +{ + if (isInitialized_) { + LOGD("Storage engine has been initialized!"); + return E_OK; + } + + // only for create the database avoid the minimum number is 0. + int errCode = E_OK; + StorageExecutor *handle = nullptr; + if (engineAttr_.minReadNum == 0 && engineAttr_.minWriteNum == 0) { + errCode = CreateNewExecutor(true, handle); + if (errCode != E_OK) { + goto ERROR; + } + + if (handle != nullptr) { + delete handle; + handle = nullptr; + } + } + + for (uint32_t i = 0; i < engineAttr_.minWriteNum; i++) { + handle = nullptr; + errCode = CreateNewExecutor(true, handle); + if (errCode != E_OK) { + goto ERROR; + } + AddStorageExecutor(handle); + } + + for (uint32_t i = 0; i < engineAttr_.minReadNum; i++) { + handle = nullptr; + errCode = CreateNewExecutor(false, handle); + if (errCode != E_OK) { + goto ERROR; + } + AddStorageExecutor(handle); + } + isInitialized_ = true; + +ERROR: + if (errCode != E_OK) { + // Assumed file system has classification function, can only get one write handle + if (errCode == -E_EKEYREVOKED && !writeIdleList_.empty()) { + return E_OK; + } + Release(); + } + return errCode; +} + +StorageExecutor *StorageEngine::FindExecutor(bool writable, OperatePerm perm, int &errCode, int waitTime) +{ + if (GetEngineState() == EngineState::ENGINE_BUSY) { + LOGI("Storage engine is busy!"); + errCode = -E_BUSY; + return nullptr; + } + + if (writable) { + return FindWriteExecutor(perm, errCode, waitTime); + } + + return FindReadExecutor(perm, errCode, waitTime); +} + +StorageExecutor *StorageEngine::FindWriteExecutor(OperatePerm perm, int &errCode, int waitTime) +{ + std::unique_lock lock(writeMutex_); + errCode = -E_BUSY; + if (perm_ == OperatePerm::DISABLE_PERM || perm_ != perm) { + LOGI("Not permitted to get the executor[%u]", static_cast(perm_)); + return nullptr; + } + if (waitTime <= 0) { // non-blocking. + if (writeIdleList_.empty() && + writeIdleList_.size() + writeUsingList_.size() == engineAttr_.maxWriteNum) { + return nullptr; + } + return FetchStorageExecutor(true, writeIdleList_, writeUsingList_, errCode); + } + + // Not prohibited and there is an available handle + bool result = writeCondition_.wait_for(lock, std::chrono::seconds(waitTime), + [this, &perm]() { + return (perm_ == OperatePerm::NORMAL_PERM || perm_ == perm) && (!writeIdleList_.empty() || + (writeIdleList_.size() + writeUsingList_.size() < engineAttr_.maxWriteNum) || + operateAbort_); + }); + if (operateAbort_) { + LOGI("Abort write executor and executor and busy for operate!"); + return nullptr; + } + if (!result) { + LOGI("Get write handle result[%d], permissType[%u], operType[%u], write[%zu-%zu-%" PRIu32 "]", result, + static_cast(perm_), static_cast(perm), writeIdleList_.size(), writeUsingList_.size(), + engineAttr_.maxWriteNum); + return nullptr; + } + return FetchStorageExecutor(true, writeIdleList_, writeUsingList_, errCode); +} + +StorageExecutor *StorageEngine::FindReadExecutor(OperatePerm perm, int &errCode, int waitTime) +{ + std::unique_lock lock(readMutex_); + errCode = -E_BUSY; + if (perm_ == OperatePerm::DISABLE_PERM || perm_ != perm) { + LOGI("Not permitted to get the executor[%u]", static_cast(perm_)); + return nullptr; + } + + if (waitTime <= 0) { // non-blocking. + if (readIdleList_.empty() && + readIdleList_.size() + readUsingList_.size() == engineAttr_.maxReadNum) { + return nullptr; + } + return FetchStorageExecutor(false, readIdleList_, readUsingList_, errCode); + } + + // Not prohibited and there is an available handle + bool result = readCondition_.wait_for(lock, std::chrono::seconds(waitTime), + [this, &perm]() { + return (perm_ == OperatePerm::NORMAL_PERM || perm_ == perm) && + (!readIdleList_.empty() || (readIdleList_.size() + readUsingList_.size() < engineAttr_.maxReadNum) || + operateAbort_); + }); + if (operateAbort_) { + LOGI("Abort find read executor and busy for operate!"); + return nullptr; + } + if (!result) { + LOGI("Get read handle result[%d], permissType[%u], operType[%u], read[%zu-%zu-%" PRIu32 "]", result, + static_cast(perm_), static_cast(perm), readIdleList_.size(), readUsingList_.size(), + engineAttr_.maxReadNum); + return nullptr; + } + return FetchStorageExecutor(false, readIdleList_, readUsingList_, errCode); +} + +void StorageEngine::Recycle(StorageExecutor *&handle) +{ + if (handle == nullptr) { + return; + } + std::string id = DBCommon::TransferStringToHex(identifier_); + LOGD("Recycle executor[%d] for id[%.6s]", handle->GetWritable(), id.c_str()); + if (handle->GetWritable()) { + std::unique_lock lock(writeMutex_); + auto iter = std::find(writeUsingList_.begin(), writeUsingList_.end(), handle); + if (iter != writeUsingList_.end()) { + writeUsingList_.remove(handle); + if (writeIdleList_.size() >= 1) { + delete handle; + handle = nullptr; + return; + } + handle->Reset(); + writeIdleList_.push_back(handle); + writeCondition_.notify_one(); + } + } else { + std::unique_lock lock(readMutex_); + auto iter = std::find(readUsingList_.begin(), readUsingList_.end(), handle); + if (iter != readUsingList_.end()) { + readUsingList_.remove(handle); + if (readIdleList_.size() >= 1) { + delete handle; + handle = nullptr; + return; + } + handle->Reset(); + readIdleList_.push_back(handle); + readCondition_.notify_one(); + } + } + handle = nullptr; +} + +void StorageEngine::ClearCorruptedFlag() +{ + return; +} + +void StorageEngine::Release() +{ + CloseExecutor(); + isInitialized_ = false; + isUpdated_ = false; + ClearCorruptedFlag(); + SetEngineState(EngineState::INVALID); +} + +int StorageEngine::TryToDisable(bool isNeedCheckAll, OperatePerm disableType) +{ + if (engineState_ != EngineState::MAINDB && engineState_ != EngineState::INVALID) { + LOGE("Not support disable handle when cacheDB existed! state = [%d]", engineState_); + return(engineState_ == EngineState::CACHEDB) ? -E_NOT_SUPPORT : -E_BUSY; + } + + std::lock(writeMutex_, readMutex_); + std::lock_guard writeLock(writeMutex_, std::adopt_lock); + std::lock_guard readLock(readMutex_, std::adopt_lock); + + if (!isNeedCheckAll) { + goto END; + } + + if (!writeUsingList_.empty() || !readUsingList_.empty()) { + LOGE("Database handle used"); + return -E_BUSY; + } +END: + if (perm_ == OperatePerm::NORMAL_PERM) { + LOGI("database is disable for re-build:%d", static_cast(disableType)); + perm_ = disableType; + writeCondition_.notify_all(); + readCondition_.notify_all(); + } + return E_OK; +} + +void StorageEngine::Enable(OperatePerm enableType) +{ + std::lock(writeMutex_, readMutex_); + std::lock_guard writeLock(writeMutex_, std::adopt_lock); + std::lock_guard readLock(readMutex_, std::adopt_lock); + if (perm_ == enableType) { + LOGI("Re-enable the database"); + perm_ = OperatePerm::NORMAL_PERM; + writeCondition_.notify_all(); + readCondition_.notify_all(); + } +} + +void StorageEngine::Abort(OperatePerm enableType) +{ + std::lock(writeMutex_, readMutex_); + std::lock_guard writeLock(writeMutex_, std::adopt_lock); + std::lock_guard readLock(readMutex_, std::adopt_lock); + if (perm_ == enableType) { + LOGI("Abort the handle occupy, release all!"); + perm_ = OperatePerm::NORMAL_PERM; + operateAbort_ = true; + + writeCondition_.notify_all(); + readCondition_.notify_all(); + } +} + +bool StorageEngine::IsNeedTobeReleased() const +{ + return true; +} + +const std::string &StorageEngine::GetIdentifier() const +{ + return identifier_; +} + +EngineState StorageEngine::GetEngineState() const +{ + return engineState_; +} + +void StorageEngine::SetEngineState(EngineState state) +{ + LOGI("Storage engine state to [%d]!", state); + engineState_ = state; +} + +bool StorageEngine::IsNeedMigrate() const +{ + LOGI("No need to migrate!"); + return false; +} + +int StorageEngine::ExecuteMigrate() +{ + LOGW("Migration is not supported!"); + return -E_NOT_SUPPORT; +} + +void StorageEngine::SetNotifiedCallback(const std::function &callback) +{ + std::unique_lock lock(notifyMutex_); + commitNotifyFunc_ = callback; + return; +} + +void StorageEngine::SetConnectionFlag(bool isExisted) +{ + return isExistConnection_.store(isExisted); +} + +bool StorageEngine::IsExistConnection() const +{ + return isExistConnection_.load(); +} + +void StorageEngine::ClearEnginePasswd() +{ + return; +} + +int StorageEngine::CheckEngineOption(const KvDBProperties &kvdbOption) const +{ + return E_OK; +} + +void StorageEngine::AddStorageExecutor(StorageExecutor *handle) +{ + if (handle == nullptr) { + return; + } + + if (handle->GetWritable()) { + writeIdleList_.push_back(handle); + } else { + readIdleList_.push_back(handle); + } +} + +void StorageEngine::CloseExecutor() +{ + { + std::lock_guard lock(writeMutex_); + for (auto &item : writeIdleList_) { + if (item != nullptr) { + delete item; + item = nullptr; + } + } + writeIdleList_.clear(); + } + + { + std::lock_guard lock(readMutex_); + for (auto &item : readIdleList_) { + if (item != nullptr) { + delete item; + item = nullptr; + } + } + readIdleList_.clear(); + } +} + +StorageExecutor *StorageEngine::FetchStorageExecutor(bool isWrite, std::list &idleList, + std::list &usingList, int &errCode) +{ + if (idleList.empty()) { + StorageExecutor *handle = nullptr; + errCode = CreateNewExecutor(isWrite, handle); + if ((errCode != E_OK) || (handle == nullptr)) { + if (errCode != -E_EKEYREVOKED) { + return nullptr; + } + LOGE("Key revoked status, couldn't create the new executor"); + if (!usingList.empty()) { + LOGE("Can't create new executor for revoked"); + errCode = -E_BUSY; + } + return nullptr; + } + + AddStorageExecutor(handle); + } + auto item = idleList.front(); + usingList.push_back(item); + idleList.remove(item); + LOGD("Get executor[%d] from [%.6s], using[%zu]", isWrite, + DBCommon::TransferStringToHex(identifier_).c_str(), usingList.size()); + errCode = E_OK; + return item; +} + +bool StorageEngine::CheckEngineAttr(const StorageEngineAttr &poolSize) +{ + return (poolSize.maxReadNum > MAX_READ_SIZE || + poolSize.maxWriteNum > MAX_WRITE_SIZE || + poolSize.minReadNum > poolSize.maxReadNum || + poolSize.minWriteNum > poolSize.maxWriteNum); +} + +bool StorageEngine::IsMigrating() const +{ + return isMigrating_.load(); +} +} diff --git a/mock/distributeddb/storage/src/storage_engine.h b/mock/distributeddb/storage/src/storage_engine.h new file mode 100644 index 00000000..a948e236 --- /dev/null +++ b/mock/distributeddb/storage/src/storage_engine.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 STORAGE_ENGINE_H +#define STORAGE_ENGINE_H + +#include +#include +#include +#include + +#include "db_types.h" +#include "macro_utils.h" +#include "storage_executor.h" +#include "kvdb_commit_notify_filterable_data.h" + +namespace DistributedDB { +struct StorageEngineAttr { + uint32_t minWriteNum = 1; + uint32_t maxWriteNum = 1; + uint32_t minReadNum = 1; + uint32_t maxReadNum = 1; +}; + +class StorageEngine { +public: + StorageEngine(); + virtual ~StorageEngine(); + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(StorageEngine); + + int Init(); + + StorageExecutor *FindExecutor(bool writable, OperatePerm perm, int &errCode, int waitTime = MAX_WAIT_TIME); + + void Recycle(StorageExecutor *&handle); + + void Release(); + + int TryToDisable(bool isNeedCheckAll, OperatePerm disableType = OperatePerm::DISABLE_PERM); + + void Enable(OperatePerm enableType = OperatePerm::NORMAL_PERM); + + void Abort(OperatePerm enableType = OperatePerm::NORMAL_PERM); + + virtual bool IsNeedTobeReleased() const; + + virtual const std::string &GetIdentifier() const; + + virtual EngineState GetEngineState() const; + + virtual void SetEngineState(EngineState state); + + virtual bool IsNeedMigrate() const; + + virtual int ExecuteMigrate(); + + virtual void SetNotifiedCallback(const std::function &callback); + + void SetConnectionFlag(bool isExisted); + + bool IsExistConnection() const; + + virtual void ClearEnginePasswd(); + + virtual int CheckEngineOption(const KvDBProperties &kvdbOption) const; + + virtual bool IsMigrating() const; + +protected: + virtual int CreateNewExecutor(bool isWrite, StorageExecutor *&handle) = 0; + + void CloseExecutor(); + + virtual void AddStorageExecutor(StorageExecutor *handle); + + static bool CheckEngineAttr(const StorageEngineAttr &poolSize); + + StorageEngineAttr engineAttr_; + bool isUpdated_; + std::atomic isMigrating_; + std::string identifier_; + EngineState engineState_; + + // Mutex for commitNotifyFunc_. + mutable std::shared_mutex notifyMutex_; + + // Callback function for commit notify. + std::function commitNotifyFunc_; + +private: + StorageExecutor *FetchStorageExecutor(bool isWrite, std::list &idleList, + std::list &usingList, int &errCode); + + StorageExecutor *FindWriteExecutor(OperatePerm perm, int &errCode, int waitTime); + StorageExecutor *FindReadExecutor(OperatePerm perm, int &errCode, int waitTime); + + virtual void ClearCorruptedFlag(); + + static const int MAX_WAIT_TIME; + static const int MAX_WRITE_SIZE; + static const int MAX_READ_SIZE; + + bool isInitialized_; + OperatePerm perm_; + bool operateAbort_; + + std::mutex readMutex_; + std::mutex writeMutex_; + std::condition_variable writeCondition_; + std::condition_variable readCondition_; + std::list writeUsingList_; + std::list writeIdleList_; + std::list readUsingList_; + std::list readIdleList_; + std::atomic isExistConnection_; +}; +} // namespace DistributedDB +#endif // STORAGE_ENGINE_H diff --git a/mock/distributeddb/storage/src/storage_engine_manager.cpp b/mock/distributeddb/storage/src/storage_engine_manager.cpp new file mode 100644 index 00000000..369873ff --- /dev/null +++ b/mock/distributeddb/storage/src/storage_engine_manager.cpp @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "storage_engine_manager.h" +#include "log_print.h" +#include "db_errno.h" +#include "runtime_context.h" +#include "sqlite_single_ver_storage_engine.h" + +namespace DistributedDB { +bool StorageEngineManager::isRegLockStatusListener_ = false; +std::mutex StorageEngineManager::instanceLock_; +std::atomic StorageEngineManager::instance_{nullptr}; +std::mutex StorageEngineManager::storageEnginesLock_; + +namespace { + std::string GetIdentifier(const KvDBProperties &property) + { + return property.GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + } + + int GetDatabaseType(const KvDBProperties &property) + { + return property.GetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + } +} + +StorageEngineManager::StorageEngineManager() : lockStatusListener_(nullptr) +{} + +StorageEngineManager::~StorageEngineManager() +{ + if (lockStatusListener_ != nullptr) { + lockStatusListener_->Drop(true); + } +} + +StorageEngine *StorageEngineManager::GetStorageEngine(const KvDBProperties &property, int &errCode) +{ + StorageEngineManager *manager = GetInstance(); + if (manager == nullptr) { + LOGE("[StorageEngineManager] GetInstance failed"); + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + std::string identifier = GetIdentifier(property); + manager->EnterGetEngineProcess(identifier); + auto storageEngine = manager->FindStorageEngine(identifier); + if (storageEngine == nullptr) { + storageEngine = manager->CreateStorageEngine(property, errCode); + if (errCode == E_OK) { + manager->InsertStorageEngine(identifier, storageEngine); + } + } else { + errCode = storageEngine->CheckEngineOption(property); + if (errCode != E_OK) { + LOGE("kvdb property mismatch engine option! errCode = [%d]", errCode); + storageEngine = nullptr; + } + } + + manager->ExitGetEngineProcess(identifier); + return storageEngine; +} + +int StorageEngineManager::ReleaseStorageEngine(StorageEngine *storageEngine) +{ + if (storageEngine == nullptr) { + LOGE("[StorageEngineManager] The engine to be released is nullptr"); + return -E_INVALID_ARGS; + } + + // Clear commit notify callback function. + storageEngine->SetNotifiedCallback(nullptr); + + // If the cacheDB is valid, the storageEngine is not released to prevent the cache mechanism failed + bool isRelease = storageEngine->IsNeedTobeReleased(); + if (!isRelease) { + LOGW("[StorageEngineManager] storageEngine do not need to be released."); + return E_OK; + } + + StorageEngineManager *manager = GetInstance(); + if (manager == nullptr) { + LOGE("[StorageEngineManager] Release GetInstance failed"); + return -E_OUT_OF_MEMORY; + } + + LOGD("[StorageEngineManager] storageEngine to be released."); + return manager->ReleaseEngine(storageEngine); +} + +int StorageEngineManager::ForceReleaseStorageEngine(const std::string &identifier) +{ + StorageEngineManager *manager = GetInstance(); + if (manager == nullptr) { + LOGE("[StorageEngineManager] Force release GetInstance failed"); + return -E_OUT_OF_MEMORY; + } + + LOGD("[StorageEngineManager] Force release engine."); + manager->ReleaseResources(identifier); + return E_OK; +} + +int StorageEngineManager::ExecuteMigration(StorageEngine *storageEngine) +{ + if (storageEngine == nullptr) { + LOGE("storage engine is nullptr can not execute migration!"); + return -E_INVALID_ARGS; + } + if (storageEngine->IsExistConnection()) { + return storageEngine->ExecuteMigrate(); + } + LOGI("connection is not existed, not need execute migration!"); + return -E_INVALID_DB; +} + +StorageEngineManager *StorageEngineManager::GetInstance() +{ + // For Double-Checked Locking, we need check instance_ twice + if (instance_ == nullptr) { + std::lock_guard lockGuard(instanceLock_); + if (instance_ == nullptr) { + instance_ = new (std::nothrow) StorageEngineManager(); + if (instance_ == nullptr) { + LOGE("[StorageEngineManager] Failed to alloc the engine manager!"); + return nullptr; + } + } + } + + if (!isRegLockStatusListener_) { + int errCode = (instance_.load())->RegisterLockStatusListener(); + if (errCode != E_OK) { + LOGW("[StorageEngineManager] Failed to regitster lock status listener:%d", errCode); + } else { + isRegLockStatusListener_ = true; + } + } + return instance_; +} + +int StorageEngineManager::RegisterLockStatusListener() +{ + int errCode = E_OK; + lockStatusListener_ = RuntimeContext::GetInstance()->RegisterLockStatusLister( + [this](void *lockStatus) { + if (lockStatus == nullptr) { + return; + } + bool isLocked = *static_cast(lockStatus); + LOGD("[StorageEngineManager] Lock status to %d", isLocked); + if (isLocked) { + return; + } + int taskErrCode = RuntimeContext::GetInstance()->ScheduleTask( + std::bind(&StorageEngineManager::LockStatusNotifier, this, isLocked)); + if (taskErrCode != E_OK) { + LOGE("[StorageEngineManager] LockStatusNotifier ScheduleTask failed : %d", taskErrCode); + } + }, errCode); + if (errCode != E_OK) { + LOGW("[StorageEngineManager] Failed to register lock status listener: %d.", errCode); + } + return errCode; +} + +void StorageEngineManager::LockStatusNotifier(bool isAccessControlled) +{ + (void)isAccessControlled; + std::lock_guard lockGuard(storageEnginesLock_); + StorageEngine *storageEngine = nullptr; + for (const auto &item : storageEngines_) { + storageEngine = item.second; + LOGD("Begin to migrate for lock status change"); + (void)ExecuteMigration(storageEngine); + } +} + +void StorageEngineManager::RemoveEngineFromCache(const std::string &identifier) +{ + StorageEngineManager *manager = GetInstance(); + if (manager != nullptr) { + manager->EraseStorageEngine(identifier); + } +} + +StorageEngine *StorageEngineManager::CreateStorageEngine(const KvDBProperties &property, int &errCode) +{ + int databaseType = GetDatabaseType(property); + if (databaseType != KvDBProperties::SINGLE_VER_TYPE) { + LOGE("[StorageEngineManager] Database type error : %d", databaseType); + errCode = -E_NOT_SUPPORT; + return nullptr; + } + + auto storageEngine = new (std::nothrow) SQLiteSingleVerStorageEngine(); + if (storageEngine == nullptr) { + LOGE("[StorageEngineManager] Create storage engine failed"); + errCode = -E_OUT_OF_MEMORY; + return nullptr; + } + errCode = E_OK; + return storageEngine; +} + +StorageEngine *StorageEngineManager::FindStorageEngine(const std::string &identifier) +{ + std::lock_guard lockGuard(storageEnginesLock_); + auto iter = storageEngines_.find(identifier); + if (iter != storageEngines_.end()) { + auto storageEngine = iter->second; + if (storageEngine == nullptr) { + LOGE("[StorageEngineManager] storageEngine in cache is nullptr"); + storageEngines_.erase(identifier); + return nullptr; + } + + return storageEngine; + } + + return nullptr; +} + +void StorageEngineManager::InsertStorageEngine(const std::string &identifier, StorageEngine *&storageEngine) +{ + std::lock_guard lockGuard(storageEnginesLock_); + storageEngines_.insert(std::pair(identifier, storageEngine)); +} + +void StorageEngineManager::EraseStorageEngine(const std::string &identifier) +{ + std::lock_guard lockGuard(storageEnginesLock_); + storageEngines_.erase(identifier); +} + +void StorageEngineManager::ReleaseResources(const std::string &identifier) +{ + StorageEngine *storageEngine = nullptr; + + { + std::lock_guard lockGuard(storageEnginesLock_); + auto iter = storageEngines_.find(identifier); + if (iter != storageEngines_.end()) { + storageEngine = iter->second; + storageEngines_.erase(identifier); + } + } + + if (storageEngine != nullptr) { + LOGI("[StorageEngineManager] Release storage engine"); + delete storageEngine; + storageEngine = nullptr; + } + + return; +} + +int StorageEngineManager::ReleaseEngine(StorageEngine *releaseEngine) +{ + const std::string identifier = releaseEngine->GetIdentifier(); + StorageEngine *cacheEngine = nullptr; + + { + std::lock_guard lockGuard(storageEnginesLock_); + auto iter = storageEngines_.find(identifier); + if (iter != storageEngines_.end()) { + cacheEngine = iter->second; + storageEngines_.erase(identifier); + } + } + + if (cacheEngine == nullptr) { + LOGE("[StorageEngineManager] cache engine is null"); + return -E_ALREADY_RELEASE; + } + if (cacheEngine != releaseEngine) { + LOGE("[StorageEngineManager] cache engine is not equal the input engine"); + return -E_INVALID_ARGS; + } + + delete releaseEngine; + releaseEngine = nullptr; + return E_OK; +} + +void StorageEngineManager::EnterGetEngineProcess(const std::string &identifier) +{ + std::unique_lock lock(getEngineMutex_); + getEngineCondition_.wait(lock, [this, &identifier]() { + return this->getEngineSet_.count(identifier) == 0; + }); + (void)getEngineSet_.insert(identifier); +} + +void StorageEngineManager::ExitGetEngineProcess(const std::string &identifier) +{ + std::unique_lock lock(getEngineMutex_); + (void)getEngineSet_.erase(identifier); + getEngineCondition_.notify_all(); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/storage_executor.cpp b/mock/distributeddb/storage/src/storage_executor.cpp new file mode 100644 index 00000000..9b6a9faf --- /dev/null +++ b/mock/distributeddb/storage/src/storage_executor.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "storage_executor.h" + +#include "db_errno.h" + +namespace DistributedDB { +StorageExecutor::StorageExecutor(bool writable) + : writable_(writable), + isCorrupted_(false) +{} + +StorageExecutor::~StorageExecutor() +{} + +bool StorageExecutor::GetWritable() const +{ + return writable_; +} + +bool StorageExecutor::GetCorruptedStatus() const +{ + return isCorrupted_; +} + +void StorageExecutor::SetCorruptedStatus() const +{ + isCorrupted_ = true; +} + +int StorageExecutor::CheckCorruptedStatus(int errCode) const +{ + if (errCode == -E_INVALID_PASSWD_OR_CORRUPTED_DB) { + SetCorruptedStatus(); + } + return errCode; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/storage_executor.h b/mock/distributeddb/storage/src/storage_executor.h new file mode 100644 index 00000000..9645cef7 --- /dev/null +++ b/mock/distributeddb/storage/src/storage_executor.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 STORAGE_EXECUTOR_H +#define STORAGE_EXECUTOR_H + +#include "macro_utils.h" + +namespace DistributedDB { +enum EngineState { + INVALID = -1, // default value, representative database is not generated + CACHEDB, + ATTACHING, // main db and cache db attach together + MIGRATING, // begine to Migrate data + MAINDB, + ENGINE_BUSY, // In order to change handle during the migration process, it is temporarily unavailable +}; + +class StorageExecutor { +public: + explicit StorageExecutor(bool writable); + virtual ~StorageExecutor(); + + // Delete the copy and assign constructors + DISABLE_COPY_ASSIGN_MOVE(StorageExecutor); + + virtual bool GetWritable() const; + + virtual int CheckCorruptedStatus(int errCode) const; + + virtual bool GetCorruptedStatus() const; + + virtual void SetCorruptedStatus() const; + + virtual int Reset() = 0; + +protected: + bool writable_; + mutable bool isCorrupted_; +}; +} // namespace DistributedDB + +#endif // STORAGE_EXECUTOR_H diff --git a/mock/distributeddb/storage/src/sync_able_engine.cpp b/mock/distributeddb/storage/src/sync_able_engine.cpp new file mode 100644 index 00000000..ff02fea2 --- /dev/null +++ b/mock/distributeddb/storage/src/sync_able_engine.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sync_able_engine.h" + +#include "db_dump_helper.h" +#include "db_errno.h" +#include "log_print.h" +#include "parcel.h" +#include "runtime_context.h" + +namespace DistributedDB { +SyncAbleEngine::SyncAbleEngine(ISyncInterface *store) + : syncer_(), + started_(false), + store_(store) +{} + +SyncAbleEngine::~SyncAbleEngine() +{} + +// Start a sync action. +int SyncAbleEngine::Sync(const ISyncer::SyncParma &parm, uint64_t connectionId) +{ + if (!started_) { + StartSyncer(); + if (!started_) { + return -E_NOT_INIT; + } + } + return syncer_.Sync(parm, connectionId); +} + +void SyncAbleEngine::WakeUpSyncer() +{ + StartSyncer(); +} + +void SyncAbleEngine::Close() +{ + StopSyncer(); +} + +void SyncAbleEngine::EnableAutoSync(bool enable) +{ + if (!started_) { + StartSyncer(); + } + return syncer_.EnableAutoSync(enable); +} + +int SyncAbleEngine::EnableManualSync(void) +{ + return syncer_.EnableManualSync(); +} + +int SyncAbleEngine::DisableManualSync(void) +{ + return syncer_.DisableManualSync(); +} + +// Get The current virtual timestamp +uint64_t SyncAbleEngine::GetTimestamp() +{ + if (!started_) { + StartSyncer(); + } + return syncer_.GetTimestamp(); +} + +int SyncAbleEngine::EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash, const std::string &tableName) +{ + if (!started_) { + StartSyncer(); + } + return syncer_.EraseDeviceWaterMark(deviceId, isNeedHash, tableName); +} + +// Start syncer +void SyncAbleEngine::StartSyncer() +{ + if (store_ == nullptr) { + LOGF("KvDB got null sync interface."); + return; + } + + int errCode = syncer_.Initialize(store_, true); + if (errCode == E_OK) { + started_ = true; + } else { + LOGE("KvDB start syncer failed, err:'%d'.", errCode); + } +} + +// Stop syncer +void SyncAbleEngine::StopSyncer() +{ + if (started_) { + syncer_.Close(true); + started_ = false; + } +} + +void SyncAbleEngine::TriggerSync(int notifyEvent) +{ + if (!started_) { + StartSyncer(); + } + if (started_) { + int errCode = RuntimeContext::GetInstance()->ScheduleTask([this, notifyEvent] { + syncer_.LocalDataChanged(notifyEvent); + }); + if (errCode != E_OK) { + LOGE("[TriggerSync] SyncAbleEngine TriggerSync LocalDataChanged retCode:%d", errCode); + } + } +} + +int SyncAbleEngine::GetLocalIdentity(std::string &outTarget) +{ + if (!started_) { + StartSyncer(); + } + return syncer_.GetLocalIdentity(outTarget); +} + +void SyncAbleEngine::StopSync(uint64_t connectionId) +{ + if (started_) { + syncer_.StopSync(connectionId); + } +} + +void SyncAbleEngine::Dump(int fd) +{ + SyncerBasicInfo basicInfo = syncer_.DumpSyncerBasicInfo(); + DBDumpHelper::Dump(fd, "\tisSyncActive = %d, isAutoSync = %d\n\n", basicInfo.isSyncActive, + basicInfo.isAutoSync); + if (basicInfo.isSyncActive) { + DBDumpHelper::Dump(fd, "\tDistributedDB Database Sync Module Message Info:\n"); + syncer_.Dump(fd); + } +} +} diff --git a/mock/distributeddb/storage/src/sync_able_engine.h b/mock/distributeddb/storage/src/sync_able_engine.h new file mode 100644 index 00000000..30e2c9da --- /dev/null +++ b/mock/distributeddb/storage/src/sync_able_engine.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNC_ABLE_ENGINE_H +#define SYNC_ABLE_ENGINE_H + +#include + +#include "ref_object.h" +#include "syncer_proxy.h" + +namespace DistributedDB { +class SyncAbleEngine final { +public: + explicit SyncAbleEngine(ISyncInterface *store); + ~SyncAbleEngine(); + void TriggerSync(int notifyEvent); + + // Start a sync action. + int Sync(const ISyncer::SyncParma &parm, uint64_t connectionId); + + void WakeUpSyncer(); + void Close(); + + // Enable auto sync + void EnableAutoSync(bool enable); + + int EnableManualSync(void); + int DisableManualSync(void); + + // Get The current virtual timestamp + uint64_t GetTimestamp(); + + int EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash, const std::string &tableName = ""); + + int GetLocalIdentity(std::string &outTarget); + + // Stop a sync action in progress + void StopSync(uint64_t connectionId); + + void Dump(int fd); +private: + // Start syncer + void StartSyncer(); + + // Stop syncer + void StopSyncer(); + + SyncerProxy syncer_; // use for sync Interactive + std::atomic started_; + ISyncInterface *store_; +}; +} // namespace DistributedDB +#endif // SYNC_ABLE_ENGINE_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/sync_able_kvdb.cpp b/mock/distributeddb/storage/src/sync_able_kvdb.cpp new file mode 100644 index 00000000..fd2bb563 --- /dev/null +++ b/mock/distributeddb/storage/src/sync_able_kvdb.cpp @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sync_able_kvdb.h" + +#include "db_dump_helper.h" +#include "db_errno.h" +#include "log_print.h" +#include "parcel.h" +#include "runtime_context.h" +#include "user_change_monitor.h" + +namespace DistributedDB { +const EventType SyncAbleKvDB::REMOTE_PUSH_FINISHED = 1; + +SyncAbleKvDB::SyncAbleKvDB() + : started_(false), + closed_(false), + isSyncModuleActiveCheck_(false), + isSyncNeedActive_(true), + notifyChain_(nullptr), + userChangeListerner_(nullptr) +{} + +SyncAbleKvDB::~SyncAbleKvDB() +{ + if (notifyChain_ != nullptr) { + (void)notifyChain_->UnRegisterEventType(REMOTE_PUSH_FINISHED); + KillAndDecObjRef(notifyChain_); + notifyChain_ = nullptr; + } + if (userChangeListerner_ != nullptr) { + userChangeListerner_->Drop(true); + userChangeListerner_ = nullptr; + } +} + +void SyncAbleKvDB::DelConnection(GenericKvDBConnection *connection) +{ + auto realConnection = static_cast(connection); + if (realConnection != nullptr) { + KillAndDecObjRef(realConnection); + realConnection = nullptr; + } +} + +void SyncAbleKvDB::TriggerSync(int notifyEvent) +{ + if (!started_) { + StartSyncer(); + } + if (started_) { + syncer_.LocalDataChanged(notifyEvent); + } +} + +void SyncAbleKvDB::CommitNotify(int notifyEvent, KvDBCommitNotifyFilterAbleData *data) +{ + SyncAbleKvDB::TriggerSync(notifyEvent); + + GenericKvDB::CommitNotify(notifyEvent, data); +} + +void SyncAbleKvDB::Close() +{ + StopSyncer(true); +} + +// Start a sync action. +int SyncAbleKvDB::Sync(const ISyncer::SyncParma &parma, uint64_t connectionId) +{ + if (!started_) { + int errCode = StartSyncer(); + if (!started_) { + return errCode; + } + } + return syncer_.Sync(parma, connectionId); +} + +void SyncAbleKvDB::EnableAutoSync(bool enable) +{ + if (!started_) { + StartSyncer(); + } + return syncer_.EnableAutoSync(enable); +} + +void SyncAbleKvDB::WakeUpSyncer() +{ + StartSyncer(); +} + +// Stop a sync action in progress. +void SyncAbleKvDB::StopSync(uint64_t connectionId) +{ + if (started_) { + syncer_.StopSync(connectionId); + } +} + +void SyncAbleKvDB::SetSyncModuleActive() +{ + if (isSyncModuleActiveCheck_) { + return; + } + IKvDBSyncInterface *syncInterface = GetSyncInterface(); + if (syncInterface == nullptr) { + LOGF("KvDB got null sync interface."); + return; + } + bool isSyncDualTupleMode = syncInterface->GetDbProperties().GetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, + false); + if (!isSyncDualTupleMode) { + isSyncNeedActive_ = true; + isSyncModuleActiveCheck_ = true; + return; + } + std::string userId = syncInterface->GetDbProperties().GetStringProp(KvDBProperties::USER_ID, ""); + std::string appId = syncInterface->GetDbProperties().GetStringProp(KvDBProperties::APP_ID, ""); + std::string storeId = syncInterface->GetDbProperties().GetStringProp(KvDBProperties::STORE_ID, ""); + isSyncNeedActive_ = RuntimeContext::GetInstance()->IsSyncerNeedActive(userId, appId, storeId); + if (!isSyncNeedActive_) { + LOGI("syncer no need to active"); + } + isSyncModuleActiveCheck_ = true; +} + +bool SyncAbleKvDB::GetSyncModuleActive() +{ + return isSyncNeedActive_; +} + +void SyncAbleKvDB::ReSetSyncModuleActive() +{ + isSyncModuleActiveCheck_ = false; + isSyncNeedActive_ = true; +} + +// Start syncer +int SyncAbleKvDB::StartSyncer(bool isCheckSyncActive, bool isNeedActive) +{ + std::unique_lock lock(syncerOperateLock_); + int errCode = StartSyncerWithNoLock(isCheckSyncActive, isNeedActive); + closed_ = false; + return errCode; +} + +int SyncAbleKvDB::StartSyncerWithNoLock(bool isCheckSyncActive, bool isNeedActive) +{ + IKvDBSyncInterface *syncInterface = GetSyncInterface(); + if (syncInterface == nullptr) { + LOGF("KvDB got null sync interface."); + return -E_INVALID_ARGS; + } + if (!isCheckSyncActive) { + SetSyncModuleActive(); + isNeedActive = GetSyncModuleActive(); + } + int errCode = syncer_.Initialize(syncInterface, isNeedActive); + if (errCode == E_OK) { + started_ = true; + } else { + LOGE("KvDB start syncer failed, err:'%d'.", errCode); + } + bool isSyncDualTupleMode = syncInterface->GetDbProperties().GetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, + false); + if (isSyncDualTupleMode && isCheckSyncActive && !isNeedActive && (userChangeListerner_ == nullptr)) { + // active to non_active + userChangeListerner_ = RuntimeContext::GetInstance()->RegisterUserChangedListerner( + std::bind(&SyncAbleKvDB::ChangeUserListerner, this), UserChangeMonitor::USER_ACTIVE_TO_NON_ACTIVE_EVENT); + } else if (isSyncDualTupleMode && (userChangeListerner_ == nullptr)) { + EventType event = isNeedActive ? + UserChangeMonitor::USER_ACTIVE_EVENT : UserChangeMonitor::USER_NON_ACTIVE_EVENT; + userChangeListerner_ = RuntimeContext::GetInstance()->RegisterUserChangedListerner( + std::bind(&SyncAbleKvDB::UserChangeHandle, this), event); + } + return errCode; +} + +// Stop syncer +void SyncAbleKvDB::StopSyncer(bool isClosedOperation) +{ + std::unique_lock lock(syncerOperateLock_); + StopSyncerWithNoLock(isClosedOperation); +} + +void SyncAbleKvDB::StopSyncerWithNoLock(bool isClosedOperation) +{ + ReSetSyncModuleActive(); + syncer_.Close(isClosedOperation); + if (started_) { + started_ = false; + } + closed_ = isClosedOperation; + if (userChangeListerner_ != nullptr) { + userChangeListerner_->Drop(false); + userChangeListerner_ = nullptr; + } +} + +void SyncAbleKvDB::UserChangeHandle() +{ + bool isNeedChange; + bool isNeedActive = true; + IKvDBSyncInterface *syncInterface = GetSyncInterface(); + if (syncInterface == nullptr) { + LOGF("KvDB got null sync interface."); + return; + } + std::unique_lock lock(syncerOperateLock_); + if (closed_) { + LOGI("kvDB is already closed"); + return; + } + std::string userId = syncInterface->GetDbProperties().GetStringProp(KvDBProperties::USER_ID, ""); + std::string appId = syncInterface->GetDbProperties().GetStringProp(KvDBProperties::APP_ID, ""); + std::string storeId = syncInterface->GetDbProperties().GetStringProp(KvDBProperties::STORE_ID, ""); + isNeedActive = RuntimeContext::GetInstance()->IsSyncerNeedActive(userId, appId, storeId); + isNeedChange = (isNeedActive != isSyncNeedActive_) ? true : false; + // non_active to active or active to non_active + if (isNeedChange) { + StopSyncerWithNoLock(); // will drop userChangeListerner; + isSyncModuleActiveCheck_ = true; + isSyncNeedActive_ = isNeedActive; + StartSyncerWithNoLock(true, isNeedActive); + } +} + +void SyncAbleKvDB::ChangeUserListerner() +{ + // only active to non_active call, put into USER_NON_ACTIVE_EVENT listerner from USER_ACTIVE_TO_NON_ACTIVE_EVENT + if (userChangeListerner_ != nullptr) { + userChangeListerner_->Drop(false); + userChangeListerner_ = nullptr; + } + if (userChangeListerner_ == nullptr) { + userChangeListerner_ = RuntimeContext::GetInstance()->RegisterUserChangedListerner( + std::bind(&SyncAbleKvDB::UserChangeHandle, this), UserChangeMonitor::USER_NON_ACTIVE_EVENT); + } +} + +// Get The current virtual timestamp +uint64_t SyncAbleKvDB::GetTimestamp() +{ + if (!started_ && !isSyncModuleActiveCheck_) { + StartSyncer(); + } + return syncer_.GetTimestamp(); +} + +// Get the dataItem's append length +uint32_t SyncAbleKvDB::GetAppendedLen() const +{ + return Parcel::GetAppendedLen(); +} + +int SyncAbleKvDB::EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash) +{ + if (!started_) { + StartSyncer(); + } + return syncer_.EraseDeviceWaterMark(deviceId, isNeedHash); +} + +int SyncAbleKvDB::GetQueuedSyncSize(int *queuedSyncSize) const +{ + return syncer_.GetQueuedSyncSize(queuedSyncSize); +} + +int SyncAbleKvDB::SetQueuedSyncLimit(const int *queuedSyncLimit) +{ + return syncer_.SetQueuedSyncLimit(queuedSyncLimit); +} + +int SyncAbleKvDB::GetQueuedSyncLimit(int *queuedSyncLimit) const +{ + return syncer_.GetQueuedSyncLimit(queuedSyncLimit); +} + +int SyncAbleKvDB::DisableManualSync(void) +{ + return syncer_.DisableManualSync(); +} + +int SyncAbleKvDB::EnableManualSync(void) +{ + return syncer_.EnableManualSync(); +} + +int SyncAbleKvDB::GetLocalIdentity(std::string &outTarget) +{ + return syncer_.GetLocalIdentity(outTarget); +} + +int SyncAbleKvDB::SetStaleDataWipePolicy(WipePolicy policy) +{ + return syncer_.SetStaleDataWipePolicy(policy); +} + +int SyncAbleKvDB::RegisterEventType(EventType type) +{ + if (notifyChain_ == nullptr) { + notifyChain_ = new (std::nothrow) NotificationChain; + if (notifyChain_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + } + + int errCode = notifyChain_->RegisterEventType(type); + if (errCode == -E_ALREADY_REGISTER) { + return E_OK; + } + if (errCode != E_OK) { + LOGE("[SyncAbleKvDB] Register event type %u failed! err %d", type, errCode); + KillAndDecObjRef(notifyChain_); + notifyChain_ = nullptr; + } + return errCode; +} + +NotificationChain::Listener *SyncAbleKvDB::AddRemotePushFinishedNotify(const RemotePushFinishedNotifier ¬ifier, + int &errCode) +{ + std::unique_lock lock(notifyChainLock_); + errCode = RegisterEventType(REMOTE_PUSH_FINISHED); + if (errCode != E_OK) { + return nullptr; + } + + auto listener = notifyChain_->RegisterListener(REMOTE_PUSH_FINISHED, + [notifier](void *arg) { + if (arg == nullptr) { + LOGE("PragmaRemotePushNotify is null."); + return; + } + notifier(*static_cast(arg)); + }, nullptr, errCode); + if (errCode != E_OK) { + LOGE("[SyncAbleKvDB] Add remote push finished notifier failed! err %d", errCode); + } + return listener; +} + +void SyncAbleKvDB::NotifyRemotePushFinishedInner(const std::string &targetId) const +{ + { + std::shared_lock lock(notifyChainLock_); + if (notifyChain_ == nullptr) { + return; + } + } + RemotePushNotifyInfo info; + info.deviceId = targetId; + notifyChain_->NotifyEvent(REMOTE_PUSH_FINISHED, static_cast(&info)); +} + +int SyncAbleKvDB::SetSyncRetry(bool isRetry) +{ + return syncer_.SetSyncRetry(isRetry); +} + +int SyncAbleKvDB::SetEqualIdentifier(const std::string &identifier, const std::vector &targets) +{ + return syncer_.SetEqualIdentifier(identifier, targets); +} + +void SyncAbleKvDB::Dump(int fd) +{ + SyncerBasicInfo basicInfo = syncer_.DumpSyncerBasicInfo(); + DBDumpHelper::Dump(fd, "\tisSyncActive = %d, isAutoSync = %d\n\n", basicInfo.isSyncActive, + basicInfo.isAutoSync); + if (basicInfo.isSyncActive) { + DBDumpHelper::Dump(fd, "\tDistributedDB Database Sync Module Message Info:\n"); + syncer_.Dump(fd); + } +} +} diff --git a/mock/distributeddb/storage/src/sync_able_kvdb.h b/mock/distributeddb/storage/src/sync_able_kvdb.h new file mode 100644 index 00000000..133a242c --- /dev/null +++ b/mock/distributeddb/storage/src/sync_able_kvdb.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNC_ABLE_KVDB_H +#define SYNC_ABLE_KVDB_H + +#include + +#include "generic_kvdb.h" +#include "ikvdb_sync_interface.h" +#include "intercepted_data.h" +#include "sync_able_kvdb_connection.h" +#include "syncer_proxy.h" + +namespace DistributedDB { +class SyncAbleKvDB : public GenericKvDB { +public: + SyncAbleKvDB(); + ~SyncAbleKvDB() override; + DISABLE_COPY_ASSIGN_MOVE(SyncAbleKvDB); + + // Delete a connection object. + void DelConnection(GenericKvDBConnection *connection) override; + + // Used to notify Syncer and other listeners data has changed + void CommitNotify(int notifyEvent, KvDBCommitNotifyFilterAbleData *data) override; + + // Invoked automatically when connection count is zero + void Close() override; + + // Start a sync action. + int Sync(const ISyncer::SyncParma &parma, uint64_t connectionId); + + // Enable auto sync + void EnableAutoSync(bool enable); + + // Stop a sync action in progress. + void StopSync(uint64_t connectionId); + + // Get The current virtual timestamp + uint64_t GetTimestamp(); + + void WakeUpSyncer() override; + + // Get manual sync queue size + int GetQueuedSyncSize(int *queuedSyncSize) const; + + // Set manual sync queue limit + int SetQueuedSyncLimit(const int *queuedSyncLimit); + + // Get manual sync queue limit + int GetQueuedSyncLimit(int *queuedSyncLimit) const; + + // Disable add new manual sync , for rekey + int DisableManualSync(void); + + // Enable add new manual sync , for rekey + int EnableManualSync(void); + + int SetStaleDataWipePolicy(WipePolicy policy); + + int EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash); + + NotificationChain::Listener *AddRemotePushFinishedNotify(const RemotePushFinishedNotifier ¬ifier, int &errCode); + + void NotifyRemotePushFinishedInner(const std::string &targetId) const; + + int SetSyncRetry(bool isRetry); + // Set an equal identifier for this database, After this called, send msg to the target will use this identifier + int SetEqualIdentifier(const std::string &identifier, const std::vector &targets); + + virtual void SetDataInterceptor(const PushDataInterceptor &interceptor) = 0; + + void Dump(int fd) override; + +protected: + virtual IKvDBSyncInterface *GetSyncInterface() = 0; + + void SetSyncModuleActive(); + + bool GetSyncModuleActive(); + + void ReSetSyncModuleActive(); + // Start syncer + int StartSyncer(bool isCheckSyncActive = false, bool isNeedActive = true); + + int StartSyncerWithNoLock(bool isCheckSyncActive, bool isNeedActive); + + // Stop syncer + void StopSyncer(bool isClosedOperation = false); + + void StopSyncerWithNoLock(bool isClosedOperation = false); + + void UserChangeHandle(); + + void ChangeUserListerner(); + + // Get the dataItem's append length, the append length = after-serialized-len - original-dataItem-len + uint32_t GetAppendedLen() const; + + int GetLocalIdentity(std::string &outTarget); + + void TriggerSync(int notifyEvent); + +private: + int RegisterEventType(EventType type); + + SyncerProxy syncer_; + std::atomic started_; + std::atomic closed_; + std::atomic isSyncModuleActiveCheck_; + std::atomic isSyncNeedActive_; + mutable std::shared_mutex notifyChainLock_; + NotificationChain *notifyChain_; + + mutable std::mutex syncerOperateLock_; + NotificationChain::Listener *userChangeListerner_; + + static const EventType REMOTE_PUSH_FINISHED; +}; +} + +#endif // SYNC_ABLE_KVDB_H diff --git a/mock/distributeddb/storage/src/sync_able_kvdb_connection.cpp b/mock/distributeddb/storage/src/sync_able_kvdb_connection.cpp new file mode 100644 index 00000000..ca10da6d --- /dev/null +++ b/mock/distributeddb/storage/src/sync_able_kvdb_connection.cpp @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sync_able_kvdb_connection.h" + +#include "log_print.h" +#include "db_errno.h" +#include "db_constant.h" +#include "kvdb_pragma.h" +#include "performance_analysis.h" +#include "runtime_context.h" +#include "sync_able_kvdb.h" + +namespace DistributedDB { +SyncAbleKvDBConnection::SyncAbleKvDBConnection(SyncAbleKvDB *kvDB) + : GenericKvDBConnection(kvDB), + remotePushFinishedListener_(nullptr) +{ + OnKill([this]() { + auto *db = GetDB(); + if (db == nullptr) { + return; + } + // Drop the lock before we call RemoveSyncOperation(). + UnlockObj(); + db->StopSync(GetConnectionId()); + LockObj(); + }); +} + +SyncAbleKvDBConnection::~SyncAbleKvDBConnection() +{ + if (remotePushFinishedListener_ != nullptr) { + remotePushFinishedListener_->Drop(true); + } + remotePushFinishedListener_ = nullptr; +} + +void SyncAbleKvDBConnection::InitPragmaFunc() +{ + if (!pragmaFunc_.empty()) { + return; + } + pragmaFunc_ = { + {PRAGMA_SYNC_DEVICES, [this](void *parameter, int &errCode) { + errCode = PragmaSyncAction(static_cast(parameter)); }}, + {PRAGMA_AUTO_SYNC, [this](void *parameter, int &errCode) { + errCode = EnableAutoSync(*(static_cast(parameter))); }}, + {PRAGMA_PERFORMANCE_ANALYSIS_GET_REPORT, [](void *parameter, int &errCode) { + *(static_cast(parameter)) = PerformanceAnalysis::GetInstance()->GetStatistics(); }}, + {PRAGMA_PERFORMANCE_ANALYSIS_OPEN, [](void *parameter, int &errCode) { + PerformanceAnalysis::GetInstance()->OpenPerformanceAnalysis(); }}, + {PRAGMA_PERFORMANCE_ANALYSIS_CLOSE, [](void *parameter, int &errCode) { + PerformanceAnalysis::GetInstance()->ClosePerformanceAnalysis(); }}, + {PRAGMA_PERFORMANCE_ANALYSIS_SET_REPORTFILENAME, [](void *parameter, int &errCode) { + PerformanceAnalysis::GetInstance()->SetFileNumber(*(static_cast(parameter))); }}, + {PRAGMA_GET_QUEUED_SYNC_SIZE, [this](void *parameter, int &errCode) { + errCode = GetQueuedSyncSize(static_cast(parameter)); }}, + {PRAGMA_SET_QUEUED_SYNC_LIMIT, [this](void *parameter, int &errCode) { + errCode = SetQueuedSyncLimit(static_cast(parameter)); }}, + {PRAGMA_GET_QUEUED_SYNC_LIMIT, [this](void *parameter, int &errCode) { + errCode = GetQueuedSyncLimit(static_cast(parameter)); }}, + {PRAGMA_SET_WIPE_POLICY, [this](void *parameter, int &errCode) { + errCode = SetStaleDataWipePolicy(static_cast(parameter)); }}, + {PRAGMA_REMOTE_PUSH_FINISHED_NOTIFY, [this](void *parameter, int &errCode) { + errCode = SetRemotePushFinishedNotify(static_cast(parameter)); }}, + {PRAGMA_SET_SYNC_RETRY, [this](void *parameter, int &errCode) { + errCode = SetSyncRetry(*(static_cast(parameter))); }}, + {PRAGMA_ADD_EQUAL_IDENTIFIER, [this](void *parameter, int &errCode) { + errCode = SetEqualIdentifier(static_cast(parameter)); }}, + {PRAGMA_INTERCEPT_SYNC_DATA, [this](void *parameter, int &errCode) { + errCode = SetPushDataInterceptor(*static_cast(parameter)); }}, + {PRAGMA_SUBSCRIBE_QUERY, [this](void *parameter, int &errCode) { + errCode = PragmaSyncAction(static_cast(parameter)); }}, + }; +} + +int SyncAbleKvDBConnection::Pragma(int cmd, void *parameter) +{ + int errCode = PragmaParamCheck(cmd, parameter); + if (errCode != E_OK) { + return -E_INVALID_ARGS; + } + + InitPragmaFunc(); + auto iter = pragmaFunc_.find(cmd); + if (iter != pragmaFunc_.end()) { + iter->second(parameter, errCode); + return errCode; + } + + // Call Pragma() of super class. + return GenericKvDBConnection::Pragma(cmd, parameter); +} + +int SyncAbleKvDBConnection::PragmaParamCheck(int cmd, const void *parameter) +{ + switch (cmd) { + case PRAGMA_AUTO_SYNC: + case PRAGMA_PERFORMANCE_ANALYSIS_GET_REPORT: + case PRAGMA_PERFORMANCE_ANALYSIS_SET_REPORTFILENAME: + if (parameter == nullptr) { + return -E_INVALID_ARGS; + } + return E_OK; + default: + return E_OK; + } +} + +int SyncAbleKvDBConnection::PragmaSyncAction(const PragmaSync *syncParameter) +{ + if (syncParameter == nullptr) { + return -E_INVALID_ARGS; + } + SyncAbleKvDB *kvDB = GetDB(); + if (kvDB == nullptr) { + return -E_INVALID_CONNECTION; + } + + if (isExclusive_.load()) { + return -E_BUSY; + } + { + AutoLock lockGuard(this); + if (IsKilled()) { + // If this happens, users are using a closed connection. + LOGE("Pragma sync on a closed connection."); + return -E_STALE; + } + IncObjRef(this); + } + + ISyncer::SyncParma syncParam; + syncParam.devices = syncParameter->devices_; + syncParam.mode = syncParameter->mode_; + syncParam.wait = syncParameter->wait_; + syncParam.isQuerySync = syncParameter->isQuerySync_; + syncParam.syncQuery = syncParameter->query_; + syncParam.onFinalize = [this]() { DecObjRef(this); }; + syncParam.onComplete = std::bind(&SyncAbleKvDBConnection::OnSyncComplete, this, std::placeholders::_1, + syncParameter->onComplete_, syncParameter->wait_); + int errCode = kvDB->Sync(syncParam, GetConnectionId()); + if (errCode != E_OK) { + DecObjRef(this); + } + return errCode; +} + +int SyncAbleKvDBConnection::EnableAutoSync(bool enable) +{ + SyncAbleKvDB *kvDB = GetDB(); + if (kvDB == nullptr) { + return -E_INVALID_CONNECTION; + } + kvDB->EnableAutoSync(enable); + return E_OK; +} + +void SyncAbleKvDBConnection::OnSyncComplete(const std::map &statuses, + const std::function &devicesMap)> &onComplete, bool wait) +{ + AutoLock lockGuard(this); + if (!IsKilled() && onComplete) { + // Drop the lock before invoking the callback. + // Do pragma-sync again in the prev sync callback is supported. + UnlockObj(); + // The connection may be closed after UnlockObj(). + // RACE: 'KillObj()' against 'onComplete()'. + if (!IsKilled()) { + onComplete(statuses); + } + LockObj(); + } +} + +int SyncAbleKvDBConnection::GetQueuedSyncSize(int *queuedSyncSize) const +{ + if (queuedSyncSize == nullptr) { + return -E_INVALID_ARGS; + } + SyncAbleKvDB *kvDB = GetDB(); + if (kvDB == nullptr) { + return -E_INVALID_CONNECTION; + } + return kvDB->GetQueuedSyncSize(queuedSyncSize); +} + +int SyncAbleKvDBConnection::SetQueuedSyncLimit(const int *queuedSyncLimit) +{ + if (queuedSyncLimit == nullptr) { + return -E_INVALID_ARGS; + } + if ((*queuedSyncLimit > DBConstant::QUEUED_SYNC_LIMIT_MAX) || + (*queuedSyncLimit < DBConstant::QUEUED_SYNC_LIMIT_MIN)) { + return -E_INVALID_ARGS; + } + SyncAbleKvDB *kvDB = GetDB(); + if (kvDB == nullptr) { + return -E_INVALID_CONNECTION; + } + return kvDB->SetQueuedSyncLimit(queuedSyncLimit); +} + +int SyncAbleKvDBConnection::GetQueuedSyncLimit(int *queuedSyncLimit) const +{ + if (queuedSyncLimit == nullptr) { + return -E_INVALID_ARGS; + } + SyncAbleKvDB *kvDB = GetDB(); + if (kvDB == nullptr) { + return -E_INVALID_CONNECTION; + } + return kvDB->GetQueuedSyncLimit(queuedSyncLimit); +} + +int SyncAbleKvDBConnection::DisableManualSync(void) +{ + SyncAbleKvDB *kvDB = GetDB(); + if (kvDB == nullptr) { + return -E_INVALID_CONNECTION; + } + return kvDB->DisableManualSync(); +} + +int SyncAbleKvDBConnection::EnableManualSync(void) +{ + SyncAbleKvDB *kvDB = GetDB(); + if (kvDB == nullptr) { + return -E_INVALID_CONNECTION; + } + return kvDB->EnableManualSync(); +} + +int SyncAbleKvDBConnection::SetStaleDataWipePolicy(const WipePolicy *policy) +{ + if (policy == nullptr) { + return -E_INVALID_ARGS; + } + SyncAbleKvDB *kvDB = GetDB(); + if (kvDB == nullptr) { + return -E_INVALID_CONNECTION; + } + return kvDB->SetStaleDataWipePolicy(*policy); +} + +int SyncAbleKvDBConnection::SetRemotePushFinishedNotify(PragmaRemotePushNotify *notifyParma) +{ + if (notifyParma == nullptr) { + return -E_INVALID_ARGS; + } + + SyncAbleKvDB *kvDB = GetDB(); + if (kvDB == nullptr) { + return -E_INVALID_CONNECTION; + } + + int errCode = E_OK; + NotificationChain::Listener *tmpListener = nullptr; + if (notifyParma->notifier_ != nullptr) { + tmpListener = kvDB->AddRemotePushFinishedNotify(notifyParma->notifier_, errCode); + if (tmpListener == nullptr) { + return errCode; + } + } + + std::lock_guard lock(remotePushFinishedListenerLock_); + // Drop old listener and set the new listener + if (remotePushFinishedListener_ != nullptr) { + errCode = remotePushFinishedListener_->Drop(); + if (errCode != E_OK) { + LOGE("[SyncAbleConnection] Drop Remote push finished listener failed %d", errCode); + if (tmpListener != nullptr) { + tmpListener->Drop(); + } + return errCode; + } + } + remotePushFinishedListener_ = tmpListener; + return errCode; +} + +int SyncAbleKvDBConnection::SetSyncRetry(bool isRetry) +{ + SyncAbleKvDB *kvDB = GetDB(); + if (kvDB == nullptr) { + return -E_INVALID_CONNECTION; + } + return kvDB->SetSyncRetry(isRetry); +} + +int SyncAbleKvDBConnection::SetEqualIdentifier(const PragmaSetEqualIdentifier *param) +{ + if (param == nullptr) { + return -E_INVALID_ARGS; + } + + SyncAbleKvDB *kvDB = GetDB(); + if (kvDB == nullptr) { + return -E_INVALID_CONNECTION; + } + return kvDB->SetEqualIdentifier(param->identifier_, param->targets_); +} + +int SyncAbleKvDBConnection::SetPushDataInterceptor(const PushDataInterceptor &interceptor) +{ + SyncAbleKvDB *kvDB = GetDB(); + if (kvDB == nullptr) { + return -E_INVALID_CONNECTION; + } + kvDB->SetDataInterceptor(interceptor); + return E_OK; +} +} diff --git a/mock/distributeddb/storage/src/sync_able_kvdb_connection.h b/mock/distributeddb/storage/src/sync_able_kvdb_connection.h new file mode 100644 index 00000000..a640bbe0 --- /dev/null +++ b/mock/distributeddb/storage/src/sync_able_kvdb_connection.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNC_ABLE_KVDB_CONNECTION_H +#define SYNC_ABLE_KVDB_CONNECTION_H + +#include "generic_kvdb_connection.h" +#include "intercepted_data.h" +#include "ref_object.h" + +namespace DistributedDB { +class SyncAbleKvDB; +struct PragmaSync; +struct PragmaRemotePushNotify; +struct PragmaSetEqualIdentifier; + +class SyncAbleKvDBConnection : public GenericKvDBConnection, public virtual RefObject { +public: + explicit SyncAbleKvDBConnection(SyncAbleKvDB *kvDB); + ~SyncAbleKvDBConnection() override; + DISABLE_COPY_ASSIGN_MOVE(SyncAbleKvDBConnection); + + // Pragma interface. + int Pragma(int cmd, void *parameter) override; + +protected: + int DisableManualSync(); + + int EnableManualSync(); + +private: + // Do pragma-sync action. + int PragmaParamCheck(int cmd, const void *parameter); + int PragmaSyncAction(const PragmaSync *syncParameter); + void InitPragmaFunc(); + + // If enable is true, it will enable auto sync + int EnableAutoSync(bool enable); + + void OnSyncComplete(const std::map &statuses, + const std::function &devicesMap)> &onComplete, bool wait); + + int GetQueuedSyncSize(int *queuedSyncSize) const; + + int SetQueuedSyncLimit(const int *queuedSyncLimit); + + int GetQueuedSyncLimit(int *queuedSyncLimit) const; + + int SetStaleDataWipePolicy(const WipePolicy *policy); + + int SetRemotePushFinishedNotify(PragmaRemotePushNotify *notifyParma); + + int SetSyncRetry(bool isRetry); + // Set an equal identifier for this database, After this called, send msg to the target will use this identifier + int SetEqualIdentifier(const PragmaSetEqualIdentifier *param); + + int SetPushDataInterceptor(const PushDataInterceptor &interceptor); + + std::mutex remotePushFinishedListenerLock_; + NotificationChain::Listener *remotePushFinishedListener_; + std::map> pragmaFunc_{}; +}; +} +#endif // SYNC_ABLE_KVDB_CONNECTION_H diff --git a/mock/distributeddb/storage/src/upgrader/database_upgrader.h b/mock/distributeddb/storage/src/upgrader/database_upgrader.h new file mode 100644 index 00000000..2877a3d0 --- /dev/null +++ b/mock/distributeddb/storage/src/upgrader/database_upgrader.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DATABASE_UPGRADER_H +#define DATABASE_UPGRADER_H + +namespace DistributedDB { +class DatabaseUpgrader { +public: + virtual ~DatabaseUpgrader() {}; + virtual int Upgrade() = 0; +}; +} // namespace DistributedDB +#endif // DATABASE_UPGRADER_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/upgrader/single_ver_database_upgrader.cpp b/mock/distributeddb/storage/src/upgrader/single_ver_database_upgrader.cpp new file mode 100644 index 00000000..2af6ecd9 --- /dev/null +++ b/mock/distributeddb/storage/src/upgrader/single_ver_database_upgrader.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_database_upgrader.h" +#include "db_errno.h" +#include "log_print.h" +#include "version.h" + +namespace DistributedDB { +int SingleVerDatabaseUpgrader::Upgrade() +{ + int errCode = GetDatabaseVersion(dbVersion_); + if (errCode != E_OK) { + LOGE("[SingleUp][Upgrade] GetVersion error:%d.", errCode); + return errCode; + } + if (dbVersion_ > SINGLE_VER_STORE_VERSION_CURRENT) { + LOGE("[SingleUp][Upgrade] DbVersion=%d is newer.", dbVersion_); + return -E_VERSION_NOT_SUPPORT; + } + LOGI("[SingleUp][Upgrade] from %d to %d.", dbVersion_, SINGLE_VER_STORE_VERSION_CURRENT); + errCode = BeginUpgrade(); + if (errCode != E_OK) { + LOGE("[SingleUp][Upgrade] Begin error:%d.", errCode); + return errCode; + } + errCode = ExecuteUpgrade(); + if (errCode != E_OK) { + LOGE("[SingleUp][Upgrade] Execute error:%d.", errCode); + EndUpgrade(false); + return errCode; + } + + if (dbVersion_ < SINGLE_VER_STORE_VERSION_CURRENT) { + errCode = SetDatabaseVersion(SINGLE_VER_STORE_VERSION_CURRENT); + if (errCode != E_OK) { + LOGE("[SingleUp][Upgrade] SetVersion error:%d.", errCode); + EndUpgrade(false); + return errCode; + } + } + + errCode = EndUpgrade(true); + if (errCode != E_OK) { + LOGE("[SingleUp][Upgrade] End error:%d.", errCode); + return errCode; + } + + return E_OK; +} + +int SingleVerDatabaseUpgrader::ExecuteUpgrade() +{ + if (dbVersion_ <= SINGLE_VER_STORE_VERSION_CURRENT) { + return UpgradeFromDatabaseVersion(dbVersion_); + } + return E_OK; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/upgrader/single_ver_database_upgrader.h b/mock/distributeddb/storage/src/upgrader/single_ver_database_upgrader.h new file mode 100644 index 00000000..fcdbae46 --- /dev/null +++ b/mock/distributeddb/storage/src/upgrader/single_ver_database_upgrader.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_DATABASE_UPGRADER_H +#define SINGLE_VER_DATABASE_UPGRADER_H + +#include "database_upgrader.h" + +namespace DistributedDB { +class SingleVerDatabaseUpgrader : virtual public DatabaseUpgrader { +public: + ~SingleVerDatabaseUpgrader() override {}; + int Upgrade() override; +protected: + virtual int BeginUpgrade() = 0; + virtual int ExecuteUpgrade(); + virtual int EndUpgrade(bool isSuccess) = 0; + + // Database structure related upgrade + virtual int GetDatabaseVersion(int &version) const = 0; + virtual int SetDatabaseVersion(int version) = 0; + virtual int UpgradeFromDatabaseVersion(int version) = 0; + + // Database version only increased when the structure of database changed + int dbVersion_ = 0; +}; +} // namespace DistributedDB +#endif // SINGLE_VER_DATABASE_UPGRADER_H \ No newline at end of file diff --git a/mock/distributeddb/storage/src/upgrader/single_ver_schema_database_upgrader.cpp b/mock/distributeddb/storage/src/upgrader/single_ver_schema_database_upgrader.cpp new file mode 100644 index 00000000..00b0560a --- /dev/null +++ b/mock/distributeddb/storage/src/upgrader/single_ver_schema_database_upgrader.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_schema_database_upgrader.h" +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +SingleVerSchemaDatabaseUpgrader::SingleVerSchemaDatabaseUpgrader(const SchemaObject &newSchema) + : newSchema_(newSchema) +{ +} + +int SingleVerSchemaDatabaseUpgrader::ExecuteUpgrade() +{ + // Calling the base class to upgrade the database structure first + int errCode = SingleVerDatabaseUpgrader::ExecuteUpgrade(); + if (errCode != E_OK) { + LOGE("[SingleSchemaUp][ExecUp] Upgrade database structure fail, errCode=%d.", errCode); + return errCode; + } + return ExecuteUpgradeSchema(); +} + +int SingleVerSchemaDatabaseUpgrader::ExecuteUpgradeSchema() +{ + // Upgrade value or index according to newSchema and oriDbSchema + LOGD("[SingleSchemaUp][ExecUp] Enter."); + if (!newSchema_.IsSchemaValid()) { + LOGI("[SingleSchemaUp][ExecUp] No schema newly designated."); + return E_OK; + } + SchemaObject oriSchemaObject; + int errCode = RestoreSchemaObjectFromDatabase(oriSchemaObject); + if (errCode != E_OK) { + return errCode; + } + // Judge and gather upgrade information + bool valueNeedUpgrade = false; + IndexDifference indexDiffer; + if (oriSchemaObject.IsSchemaValid()) { + errCode = oriSchemaObject.CompareAgainstSchemaObject(newSchema_, indexDiffer); + if (errCode == -E_SCHEMA_EQUAL_EXACTLY) { + LOGI("[SingleSchemaUp][ExecUp] NewSchema equal exactly with oriDbSchema."); + return E_OK; + } else if (errCode == -E_SCHEMA_UNEQUAL_INCOMPATIBLE) { + return -E_SCHEMA_MISMATCH; + } else if (errCode == -E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE) { + valueNeedUpgrade = true; + } + // ValueNeedUpgrade false for case E_SCHEMA_UNEQUAL_COMPATIBLE + } else { + // Upgrade normalDb to schemaDb + valueNeedUpgrade = true; + indexDiffer.increase = newSchema_.GetIndexInfo(); + } + // Remember to upgrade value first, upgrade index later, for both Json-Schema and FlatBuffer-Schema + if (valueNeedUpgrade) { + errCode = UpgradeValues(); + if (errCode != E_OK) { + return errCode; + } + } + errCode = UpgradeIndexes(indexDiffer); + if (errCode != E_OK) { + return errCode; + } + // Update schema into database file + errCode = SetDatabaseSchema(newSchema_.ToSchemaString()); + if (errCode != E_OK) { + return errCode; + } + return E_OK; +} + +int SingleVerSchemaDatabaseUpgrader::RestoreSchemaObjectFromDatabase(SchemaObject &outOriSchema) const +{ + std::string oriDbSchema; + int errCode = GetDatabaseSchema(oriDbSchema); // If no schema in db should return E_OK with an empty string + if (errCode != E_OK) { + return errCode; + } + if (!oriDbSchema.empty()) { + errCode = outOriSchema.ParseFromSchemaString(oriDbSchema); + if (errCode != E_OK) { + LOGW("[SingleSchemaUp][ExecUp] Schema in dbFile parse fail, regard as no schema."); + } + } + return E_OK; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/storage/src/upgrader/single_ver_schema_database_upgrader.h b/mock/distributeddb/storage/src/upgrader/single_ver_schema_database_upgrader.h new file mode 100644 index 00000000..2054671f --- /dev/null +++ b/mock/distributeddb/storage/src/upgrader/single_ver_schema_database_upgrader.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_SCHEMA_DATABASE_UPGRADER_H +#define SINGLE_VER_SCHEMA_DATABASE_UPGRADER_H + +#include "schema_object.h" +#include "single_ver_database_upgrader.h" + +namespace DistributedDB { +class SingleVerSchemaDatabaseUpgrader : virtual public SingleVerDatabaseUpgrader { +public: + // An invalid SchemaObject indicate no schema + explicit SingleVerSchemaDatabaseUpgrader(const SchemaObject &newSchema); + ~SingleVerSchemaDatabaseUpgrader() override {}; +protected: + int ExecuteUpgrade() override; + + // Database content related upgrade + int ExecuteUpgradeSchema(); + + // Get an empty string with return_code E_OK indicate no schema but everything normally + virtual int GetDatabaseSchema(std::string &dbSchema) const = 0; + + // Set or update schema into database file + virtual int SetDatabaseSchema(const std::string &dbSchema) = 0; + + virtual int UpgradeValues() = 0; + virtual int UpgradeIndexes(const IndexDifference &indexDiffer) = 0; + + SchemaObject newSchema_; +private: + int RestoreSchemaObjectFromDatabase(SchemaObject &outOriSchema) const; +}; +} // namespace DistributedDB +#endif // SINGLE_VER_SCHEMA_DATABASE_UPGRADER_H diff --git a/mock/distributeddb/syncer/include/isyncer.h b/mock/distributeddb/syncer/include/isyncer.h new file mode 100644 index 00000000..96571937 --- /dev/null +++ b/mock/distributeddb/syncer/include/isyncer.h @@ -0,0 +1,126 @@ + +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_SYNCER_H +#define I_SYNCER_H + +#include +#include +#include + +#include "isync_interface.h" +#include "types_export.h" +#include "query_sync_object.h" +#include "store_types.h" + +namespace DistributedDB { +struct SyncerBasicInfo { + bool isSyncActive = false; + bool isAutoSync = false; + bool isClearHistoryData = false; +}; +class ISyncer { +public: + struct SyncParma { + std::vector devices; + std::function &devicesMap)> onComplete; + SyncStatusCallback relationOnComplete; + std::function onFinalize; + int mode = 0; + bool wait = false; + bool isQuerySync = false; + QuerySyncObject syncQuery; + }; + + virtual ~ISyncer() {}; + + // Init the Syncer modules + virtual int Initialize(ISyncInterface *syncInterface, bool isNeedActive) + { + return -E_NOT_SUPPORT; + } + + // Close + virtual int Close(bool isClosedOperation) = 0; + + // Sync function. + // param devices: The device id list. + // param mode: Sync mode, see SyncMode. + // param onComplete: The syncer finish callback. set by caller + // param onFinalize: will be callback when this Sync Operation finalized. + // return a Sync id. It will return a positive value if failed, + virtual int Sync(const std::vector &devices, int mode, + const std::function &)> &onComplete, + const std::function &onFinalize, bool wait) = 0; + + // Sync function. use SyncParma to reduce parameter. + virtual int Sync(const SyncParma ¶m, uint64_t connectionId) = 0; + + // Remove the operation, with the given syncId, used to clean resource if sync finished or failed. + virtual int RemoveSyncOperation(int syncId) = 0; + + virtual int StopSync(uint64_t connectionId) = 0; + + // Get The current virtual timestamp + virtual uint64_t GetTimestamp() = 0; + + // Enable auto sync function + virtual void EnableAutoSync(bool enable) = 0; + + // delete specified device's watermark + virtual int EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash) = 0; + + // delete specified device's and table's watermark + virtual int EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash, + const std::string &tableName) = 0; + + // Local data changed callback + virtual void LocalDataChanged(int notifyEvent) = 0; + + // Get manual sync queue size + virtual int GetQueuedSyncSize(int *queuedSyncSize) const = 0; + + // Set manual sync queue limit + virtual int SetQueuedSyncLimit(const int *queuedSyncLimit) = 0; + + // Get manual sync queue limit + virtual int GetQueuedSyncLimit(int *queuedSyncLimit) const = 0; + + // Disable add new manual sync, for rekey + virtual int DisableManualSync(void) = 0; + + // Enable add new manual sync, for rekey + virtual int EnableManualSync(void) = 0; + + // Get local deviceId, is hashed + virtual int GetLocalIdentity(std::string &outTarget) const = 0; + + // Set stale data wipe policy + virtual int SetStaleDataWipePolicy(WipePolicy policy) = 0; + + // Set Manual Sync retry config + virtual int SetSyncRetry(bool isRetry) = 0; + + // Set an equal identifier for this database, After this called, send msg to the target will use this identifier + virtual int SetEqualIdentifier(const std::string &identifier, const std::vector &targets) = 0; + + virtual void Dump(int fd) = 0; + + virtual SyncerBasicInfo DumpSyncerBasicInfo() = 0; +}; +} // namespace DistributedDB + +#endif // I_SYNCER_H diff --git a/mock/distributeddb/syncer/include/syncer_proxy.h b/mock/distributeddb/syncer/include/syncer_proxy.h new file mode 100644 index 00000000..d8ce88ce --- /dev/null +++ b/mock/distributeddb/syncer/include/syncer_proxy.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNCER_PROXY_H +#define SYNCER_PROXY_H + +#include +#include +#include +#include + +#include "isyncer.h" + +namespace DistributedDB { +class SyncerProxy : public ISyncer { +public: + SyncerProxy(); + ~SyncerProxy() {}; + + // Init the Syncer modules + int Initialize(ISyncInterface *syncInterface, bool isNeedActive) override; + + // Close the syncer + int Close(bool isClosedOperation) override; + + // Sync function. + // param devices: The device id list. + // param mode: Sync mode, see SyncMode. + // param onComplete: The syncer finish callback. set by caller + // param onFinalize: will be callback when this Sync Operation finalized. + // return a Sync id. It will return a positive value if failed, + int Sync(const std::vector &devices, int mode, + const std::function &)> &onComplete, + const std::function &onFinalize, bool wait) override; + + // Sync function. use SyncParma to reduce parameter. + int Sync(const SyncParma ¶m, uint64_t connectionId) override; + + // Remove the operation, with the given syncId, used to clean resource if sync finished or failed. + int RemoveSyncOperation(int syncId) override; + + int StopSync(uint64_t connectionId) override; + + // Get The current virtual timestamp + uint64_t GetTimestamp() override; + + // Enable auto sync function + void EnableAutoSync(bool enable) override; + + // delete specified device's watermark + int EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash) override; + + // delete specified device's and table's watermark + int EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash, + const std::string &tableName) override; + + // Local data changed callback + void LocalDataChanged(int notifyEvent) override; + + // Get manual sync queue size + int GetQueuedSyncSize(int *queuedSyncSize) const override; + + // Set manual sync queue limit + int SetQueuedSyncLimit(const int *queuedSyncLimit) override; + + // Get manual sync queue limit + int GetQueuedSyncLimit(int *queuedSyncLimit) const override; + + // Disable add new manual sync, for rekey + int DisableManualSync(void) override; + + // Enable add new manual sync, for rekey + int EnableManualSync(void) override; + + // Get local deviceId, is hashed + int GetLocalIdentity(std::string &outTarget) const override; + + // Set stale data wipe policy + int SetStaleDataWipePolicy(WipePolicy policy) override; + + // Set Manual Sync retry config + int SetSyncRetry(bool isRetry) override; + + // Set an equal identifier for this database, After this called, send msg to the target will use this identifier + int SetEqualIdentifier(const std::string &identifier, const std::vector &targets) override; + + // Dump syncer info + void Dump(int fd) override; + + // Dump syncer basic info + SyncerBasicInfo DumpSyncerBasicInfo() override; + +private: + std::mutex syncerLock_; + std::shared_ptr syncer_; +}; +} // namespace DistributedDB + +#endif // SYNCER_PROXY_H diff --git a/mock/distributeddb/syncer/src/ability_sync.cpp b/mock/distributeddb/syncer/src/ability_sync.cpp new file mode 100644 index 00000000..94d75105 --- /dev/null +++ b/mock/distributeddb/syncer/src/ability_sync.cpp @@ -0,0 +1,1208 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "ability_sync.h" + +#include "message_transform.h" +#include "version.h" +#include "db_errno.h" +#include "log_print.h" +#include "sync_types.h" +#include "db_common.h" +#include "single_ver_kvdb_sync_interface.h" +#include "single_ver_sync_task_context.h" +#include "single_ver_kv_sync_task_context.h" +#ifdef RELATIONAL_STORE +#include "relational_db_sync_interface.h" +#include "single_ver_relational_sync_task_context.h" +#endif + +namespace DistributedDB { +AbilitySyncRequestPacket::AbilitySyncRequestPacket() + : protocolVersion_(ABILITY_SYNC_VERSION_V1), + sendCode_(E_OK), + softwareVersion_(SOFTWARE_VERSION_CURRENT), + secLabel_(0), + secFlag_(0), + schemaType_(0), + dbCreateTime_(0) +{ +} + +AbilitySyncRequestPacket::~AbilitySyncRequestPacket() +{ +} + +void AbilitySyncRequestPacket::SetProtocolVersion(uint32_t protocolVersion) +{ + protocolVersion_ = protocolVersion; +} + +uint32_t AbilitySyncRequestPacket::GetProtocolVersion() const +{ + return protocolVersion_; +} + +void AbilitySyncRequestPacket::SetSendCode(int32_t sendCode) +{ + sendCode_ = sendCode; +} + +int32_t AbilitySyncRequestPacket::GetSendCode() const +{ + return sendCode_; +} + +void AbilitySyncRequestPacket::SetSoftwareVersion(uint32_t swVersion) +{ + softwareVersion_ = swVersion; +} + +uint32_t AbilitySyncRequestPacket::GetSoftwareVersion() const +{ + return softwareVersion_; +} + +void AbilitySyncRequestPacket::SetSchema(const std::string &schema) +{ + schema_ = schema; +} + +std::string AbilitySyncRequestPacket::GetSchema() const +{ + return schema_; +} + +void AbilitySyncRequestPacket::SetSchemaType(uint32_t schemaType) +{ + schemaType_ = schemaType; +} + +uint32_t AbilitySyncRequestPacket::GetSchemaType() const +{ + return schemaType_; +} + +void AbilitySyncRequestPacket::SetSecLabel(int32_t secLabel) +{ + secLabel_ = secLabel; +} + +int32_t AbilitySyncRequestPacket::GetSecLabel() const +{ + return secLabel_; +} + +void AbilitySyncRequestPacket::SetSecFlag(int32_t secFlag) +{ + secFlag_ = secFlag; +} + +int32_t AbilitySyncRequestPacket::GetSecFlag() const +{ + return secFlag_; +} + +void AbilitySyncRequestPacket::SetDbCreateTime(uint64_t dbCreateTime) +{ + dbCreateTime_ = dbCreateTime; +} + +uint64_t AbilitySyncRequestPacket::GetDbCreateTime() const +{ + return dbCreateTime_; +} + +uint32_t AbilitySyncRequestPacket::CalculateLen() const +{ + uint64_t len = 0; + len += Parcel::GetUInt32Len(); // protocolVersion_ + len += Parcel::GetIntLen(); // sendCode_ + len += Parcel::GetUInt32Len(); // softwareVersion_ + uint32_t schemLen = Parcel::GetStringLen(schema_); + if (schemLen == 0) { + LOGE("[AbilitySyncRequestPacket][CalculateLen] schemLen err!"); + return 0; + } + len += schemLen; + len += Parcel::GetIntLen(); // secLabel_ + len += Parcel::GetIntLen(); // secFlag_ + len += Parcel::GetUInt32Len(); // schemaType_ + len += Parcel::GetUInt64Len(); // dbCreateTime_ + len += DbAbility::CalculateLen(dbAbility_); // dbAbility_ + // the reason why not 8-byte align is that old version is not 8-byte align + // so it is not possible to set 8-byte align for high version. + if (len > INT32_MAX) { + LOGE("[AbilitySyncRequestPacket][CalculateLen] err len:%" PRIu64, len); + return 0; + } + return len; +} + +DbAbility AbilitySyncRequestPacket::GetDbAbility() const +{ + return dbAbility_; +} + +void AbilitySyncRequestPacket::SetDbAbility(const DbAbility &dbAbility) +{ + dbAbility_ = dbAbility; +} + +AbilitySyncAckPacket::AbilitySyncAckPacket() + : protocolVersion_(ABILITY_SYNC_VERSION_V1), + softwareVersion_(SOFTWARE_VERSION_CURRENT), + ackCode_(E_OK), + secLabel_(0), + secFlag_(0), + schemaType_(0), + permitSync_(0), + requirePeerConvert_(0), + dbCreateTime_(0) +{ +} + +AbilitySyncAckPacket::~AbilitySyncAckPacket() +{ +} + +void AbilitySyncAckPacket::SetProtocolVersion(uint32_t protocolVersion) +{ + protocolVersion_ = protocolVersion; +} + +void AbilitySyncAckPacket::SetSoftwareVersion(uint32_t swVersion) +{ + softwareVersion_ = swVersion; +} + +uint32_t AbilitySyncAckPacket::GetSoftwareVersion() const +{ + return softwareVersion_; +} + +uint32_t AbilitySyncAckPacket::GetProtocolVersion() const +{ + return protocolVersion_; +} + +void AbilitySyncAckPacket::SetAckCode(int32_t ackCode) +{ + ackCode_ = ackCode; +} + +int32_t AbilitySyncAckPacket::GetAckCode() const +{ + return ackCode_; +} + +void AbilitySyncAckPacket::SetSchema(const std::string &schema) +{ + schema_ = schema; +} + +std::string AbilitySyncAckPacket::GetSchema() const +{ + return schema_; +} + +void AbilitySyncAckPacket::SetSchemaType(uint32_t schemaType) +{ + schemaType_ = schemaType; +} + +uint32_t AbilitySyncAckPacket::GetSchemaType() const +{ + return schemaType_; +} + +void AbilitySyncAckPacket::SetSecLabel(int32_t secLabel) +{ + secLabel_ = secLabel; +} + +int32_t AbilitySyncAckPacket::GetSecLabel() const +{ + return secLabel_; +} + +void AbilitySyncAckPacket::SetSecFlag(int32_t secFlag) +{ + secFlag_ = secFlag; +} + +int32_t AbilitySyncAckPacket::GetSecFlag() const +{ + return secFlag_; +} + +void AbilitySyncAckPacket::SetPermitSync(uint32_t permitSync) +{ + permitSync_ = permitSync; +} + +uint32_t AbilitySyncAckPacket::GetPermitSync() const +{ + return permitSync_; +} + +void AbilitySyncAckPacket::SetRequirePeerConvert(uint32_t requirePeerConvert) +{ + requirePeerConvert_ = requirePeerConvert; +} + +uint32_t AbilitySyncAckPacket::GetRequirePeerConvert() const +{ + return requirePeerConvert_; +} + +void AbilitySyncAckPacket::SetDbCreateTime(uint64_t dbCreateTime) +{ + dbCreateTime_ = dbCreateTime; +} + +uint64_t AbilitySyncAckPacket::GetDbCreateTime() const +{ + return dbCreateTime_; +} + +uint32_t AbilitySyncAckPacket::CalculateLen() const +{ + uint64_t len = 0; + len += Parcel::GetUInt32Len(); + len += Parcel::GetUInt32Len(); + len += Parcel::GetIntLen(); + uint32_t schemLen = Parcel::GetStringLen(schema_); + if (schemLen == 0) { + LOGE("[AbilitySyncAckPacket][CalculateLen] schemLen err!"); + return 0; + } + len += schemLen; + len += Parcel::GetIntLen(); // secLabel_ + len += Parcel::GetIntLen(); // secFlag_ + len += Parcel::GetUInt32Len(); // schemaType_ + len += Parcel::GetUInt32Len(); // permitSync_ + len += Parcel::GetUInt32Len(); // requirePeerConvert_ + len += Parcel::GetUInt64Len(); // dbCreateTime_ + len += DbAbility::CalculateLen(dbAbility_); // dbAbility_ + len += SchemaNegotiate::CalculateParcelLen(relationalSyncOpinion_); + if (len > INT32_MAX) { + LOGE("[AbilitySyncAckPacket][CalculateLen] err len:%" PRIu64, len); + return 0; + } + return len; +} + +DbAbility AbilitySyncAckPacket::GetDbAbility() const +{ + return dbAbility_; +} + +void AbilitySyncAckPacket::SetDbAbility(const DbAbility &dbAbility) +{ + dbAbility_ = dbAbility; +} + +void AbilitySyncAckPacket::SetRelationalSyncOpinion(const RelationalSyncOpinion &relationalSyncOpinion) +{ + relationalSyncOpinion_ = relationalSyncOpinion; +} + +RelationalSyncOpinion AbilitySyncAckPacket::GetRelationalSyncOpinion() const +{ + return relationalSyncOpinion_; +} + +AbilitySync::AbilitySync() + : communicator_(nullptr), + storageInterface_(nullptr), + metadata_(nullptr), + syncFinished_(false) +{ +} + +AbilitySync::~AbilitySync() +{ + communicator_ = nullptr; + storageInterface_ = nullptr; + metadata_ = nullptr; +} + +int AbilitySync::Initialize(ICommunicator *inCommunicator, ISyncInterface *inStorage, + std::shared_ptr &inMetadata, const std::string &deviceId) +{ + if (inCommunicator == nullptr || inStorage == nullptr || deviceId.empty() || inMetadata == nullptr) { + return -E_INVALID_ARGS; + } + communicator_ = inCommunicator; + storageInterface_ = inStorage; + metadata_ = inMetadata; + deviceId_ = deviceId; + return E_OK; +} + +int AbilitySync::SyncStart(uint32_t sessionId, uint32_t sequenceId, uint16_t remoteCommunicatorVersion, + const CommErrHandler &handler) +{ + AbilitySyncRequestPacket packet; + int errCode = SetAbilityRequestBodyInfo(packet, remoteCommunicatorVersion); + if (errCode != E_OK) { + return errCode; + } + Message *message = new (std::nothrow) Message(ABILITY_SYNC_MESSAGE); + if (message == nullptr) { + return -E_OUT_OF_MEMORY; + } + message->SetMessageType(TYPE_REQUEST); + errCode = message->SetCopiedObject<>(packet); + if (errCode != E_OK) { + LOGE("[AbilitySync][SyncStart] SetCopiedObject failed, err %d", errCode); + delete message; + message = nullptr; + return errCode; + } + message->SetVersion(MSG_VERSION_EXT); + message->SetSessionId(sessionId); + message->SetSequenceId(sequenceId); + SendConfig conf; + SetSendConfigParam(storageInterface_->GetDbProperties(), deviceId_, false, SEND_TIME_OUT, conf); + errCode = communicator_->SendMessage(deviceId_, message, conf, handler); + if (errCode != E_OK) { + LOGE("[AbilitySync][SyncStart] SendPacket failed, err %d", errCode); + delete message; + message = nullptr; + } + return errCode; +} + +int AbilitySync::AckRecv(const Message *message, ISyncTaskContext *context) +{ + int errCode = AckMsgCheck(message, context); + if (errCode != E_OK) { + return errCode; + } + const AbilitySyncAckPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + uint32_t remoteSoftwareVersion = packet->GetSoftwareVersion(); + context->SetRemoteSoftwareVersion(remoteSoftwareVersion); + if (remoteSoftwareVersion > SOFTWARE_VERSION_RELEASE_2_0) { + errCode = AckRecvWithHighVersion(message, context, packet); + } else { + std::string schema = packet->GetSchema(); + uint8_t schemaType = packet->GetSchemaType(); + bool isCompatible = static_cast(storageInterface_)->CheckCompatible(schema, schemaType); + if (!isCompatible) { + (static_cast(context))->SetTaskErrCode(-E_SCHEMA_MISMATCH); + LOGE("[AbilitySync][AckRecv] scheme check failed"); + return -E_SCHEMA_MISMATCH; + } + LOGI("[AbilitySync][AckRecv]remoteSoftwareVersion = %u, isCompatible = %d,", remoteSoftwareVersion, + isCompatible); + } + return errCode; +} + +int AbilitySync::RequestRecv(const Message *message, ISyncTaskContext *context) +{ + if (message == nullptr || context == nullptr) { + return -E_INVALID_ARGS; + } + const AbilitySyncRequestPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + if (packet->GetSendCode() == -E_VERSION_NOT_SUPPORT) { + AbilitySyncAckPacket ackPacket; + (void)SendAck(message, -E_VERSION_NOT_SUPPORT, false, ackPacket); + LOGI("[AbilitySync][RequestRecv] version can not support, remote version is %u", packet->GetProtocolVersion()); + return -E_VERSION_NOT_SUPPORT; + } + + std::string schema = packet->GetSchema(); + uint8_t schemaType = packet->GetSchemaType(); + bool isCompatible = static_cast(storageInterface_)->CheckCompatible(schema, schemaType); + if (!isCompatible) { + (static_cast(context))->SetTaskErrCode(-E_SCHEMA_MISMATCH); + } + uint32_t remoteSoftwareVersion = packet->GetSoftwareVersion(); + context->SetRemoteSoftwareVersion(remoteSoftwareVersion); + return HandleRequestRecv(message, context, isCompatible); +} + +int AbilitySync::AckNotifyRecv(const Message *message, ISyncTaskContext *context) +{ + if (message == nullptr || context == nullptr) { + return -E_INVALID_ARGS; + } + if (message->GetErrorNo() == E_FEEDBACK_UNKNOWN_MESSAGE) { + LOGE("[AbilitySync][AckNotifyRecv] Remote device dose not support this message id"); + context->SetRemoteSoftwareVersion(SOFTWARE_VERSION_EARLIEST); + return -E_FEEDBACK_UNKNOWN_MESSAGE; + } + const AbilitySyncAckPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + int errCode = packet->GetAckCode(); + if (errCode != E_OK) { + LOGE("[AbilitySync][AckNotifyRecv] received an errCode %d", errCode); + return errCode; + } + std::string schema = packet->GetSchema(); + uint32_t remoteSoftwareVersion = packet->GetSoftwareVersion(); + context->SetRemoteSoftwareVersion(remoteSoftwareVersion); + AbilitySyncAckPacket sendPacket; + errCode = HandleVersionV3AckSchemaParam(packet, sendPacket, context, false); + int ackCode = errCode; + LOGI("[AckNotifyRecv] receive dev = %s ack notify, remoteSoftwareVersion = %u, ackCode = %d", + STR_MASK(deviceId_), remoteSoftwareVersion, errCode); + if (errCode == E_OK) { + (static_cast(context))->SetIsSchemaSync(true); + ackCode = AbilitySync::LAST_NOTIFY; + } + (void)SendAckWithEmptySchema(message, ackCode, true); + return errCode; +} + +bool AbilitySync::GetAbilitySyncFinishedStatus() const +{ + return syncFinished_; +} + +void AbilitySync::SetAbilitySyncFinishedStatus(bool syncFinished) +{ + syncFinished_ = syncFinished; +} + +bool AbilitySync::SecLabelCheck(const AbilitySyncRequestPacket *packet) const +{ + int32_t remoteSecLabel = packet->GetSecLabel(); + int32_t remoteSecFlag = packet->GetSecFlag(); + if (remoteSecLabel == NOT_SURPPORT_SEC_CLASSIFICATION || remoteSecLabel == SecurityLabel::NOT_SET) { + return true; + } + SecurityOption option; + int errCode = (static_cast(storageInterface_))->GetSecurityOption(option); + LOGI("[AbilitySync][RequestRecv] local l:%d, f:%d, errCode:%d", option.securityLabel, option.securityFlag, errCode); + if (errCode == -E_NOT_SUPPORT || option.securityLabel == SecurityLabel::NOT_SET) { + return true; + } + if (remoteSecLabel == FAILED_GET_SEC_CLASSIFICATION || errCode != E_OK) { + LOGE("[AbilitySync][RequestRecv] check error remoteL:%d, errCode:%d", remoteSecLabel, errCode); + return false; + } + if (remoteSecLabel == option.securityLabel) { + return true; + } else { + LOGE("[AbilitySync][RequestRecv] check error remote:%d , %d local:%d , %d", + remoteSecLabel, remoteSecFlag, option.securityLabel, option.securityFlag); + return false; + } +} + +void AbilitySync::HandleVersionV3RequestParam(const AbilitySyncRequestPacket *packet, ISyncTaskContext *context) const +{ + int32_t remoteSecLabel = packet->GetSecLabel(); + int32_t remoteSecFlag = packet->GetSecFlag(); + DbAbility remoteDbAbility = packet->GetDbAbility(); + (static_cast(context))->SetDbAbility(remoteDbAbility); + (static_cast(context))->SetRemoteSeccurityOption({remoteSecLabel, remoteSecFlag}); + (static_cast(context))->SetReceivcPermitCheck(false); + LOGI("[AbilitySync][HandleVersionV3RequestParam] remoteSecLabel = %d, remoteSecFlag = %d, remoteSchemaType = %u", + remoteSecLabel, remoteSecFlag, packet->GetSchemaType()); +} + +void AbilitySync::HandleVersionV3AckSecOptionParam(const AbilitySyncAckPacket *packet, + ISyncTaskContext *context) const +{ + int32_t remoteSecLabel = packet->GetSecLabel(); + int32_t remoteSecFlag = packet->GetSecFlag(); + SecurityOption secOption = {remoteSecLabel, remoteSecFlag}; + (static_cast(context))->SetRemoteSeccurityOption(secOption); + (static_cast(context))->SetSendPermitCheck(false); + LOGI("[AbilitySync][AckRecv] remoteSecLabel = %d, remoteSecFlag = %d", remoteSecLabel, remoteSecFlag); +} + +int AbilitySync::HandleVersionV3AckSchemaParam(const AbilitySyncAckPacket *recvPacket, + AbilitySyncAckPacket &sendPacket, ISyncTaskContext *context, bool sendOpinion) const +{ + if (IsSingleRelationalVer()) { + return HandleRelationAckSchemaParam(recvPacket, sendPacket, context, sendOpinion); + } + HandleKvAckSchemaParam(recvPacket, context, sendPacket); + return E_OK; +} + +void AbilitySync::GetPacketSecOption(SecurityOption &option) const +{ + int errCode = -E_NOT_SUPPORT; + if (IsSingleKvVer()) { + errCode = + (static_cast(storageInterface_))->GetSecurityOption(option); + } + if (errCode == -E_NOT_SUPPORT) { + LOGE("[AbilitySync][SyncStart] GetSecOpt not surpport sec classification"); + option.securityLabel = NOT_SURPPORT_SEC_CLASSIFICATION; + } else if (errCode != E_OK) { + LOGE("[AbilitySync][SyncStart] GetSecOpt errCode:%d", errCode); + option.securityLabel = FAILED_GET_SEC_CLASSIFICATION; + } +} + +int AbilitySync::RegisterTransformFunc() +{ + TransformFunc func; + func.computeFunc = std::bind(&AbilitySync::CalculateLen, std::placeholders::_1); + func.serializeFunc = std::bind(&AbilitySync::Serialization, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + func.deserializeFunc = std::bind(&AbilitySync::DeSerialization, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + return MessageTransform::RegTransformFunction(ABILITY_SYNC_MESSAGE, func); +} + +uint32_t AbilitySync::CalculateLen(const Message *inMsg) +{ + if ((inMsg == nullptr) || (inMsg->GetMessageId() != ABILITY_SYNC_MESSAGE)) { + return 0; + } + int errCode; + uint32_t len = 0; + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + errCode = RequestPacketCalculateLen(inMsg, len); + if (errCode != E_OK) { + LOGE("[AbilitySync][CalculateLen] request packet calc length err %d", errCode); + } + break; + case TYPE_RESPONSE: + errCode = AckPacketCalculateLen(inMsg, len); + if (errCode != E_OK) { + LOGE("[AbilitySync][CalculateLen] ack packet calc length err %d", errCode); + } + break; + case TYPE_NOTIFY: + errCode = AckPacketCalculateLen(inMsg, len); + if (errCode != E_OK) { + LOGE("[AbilitySync][CalculateLen] ack packet calc length err %d", errCode); + } + break; + default: + LOGE("[AbilitySync][CalculateLen] message type not support, type %d", inMsg->GetMessageType()); + break; + } + return len; +} + +int AbilitySync::Serialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + if ((buffer == nullptr) || (inMsg == nullptr)) { + return -E_INVALID_ARGS; + } + + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return RequestPacketSerialization(buffer, length, inMsg); + case TYPE_RESPONSE: + return AckPacketSerialization(buffer, length, inMsg); + case TYPE_NOTIFY: + return AckPacketSerialization(buffer, length, inMsg); + default: + return -E_MESSAGE_TYPE_ERROR; + } +} + +int AbilitySync::DeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + if ((buffer == nullptr) || (inMsg == nullptr)) { + return -E_INVALID_ARGS; + } + + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return RequestPacketDeSerialization(buffer, length, inMsg); + case TYPE_RESPONSE: + return AckPacketDeSerialization(buffer, length, inMsg); + case TYPE_NOTIFY: + return AckPacketDeSerialization(buffer, length, inMsg); + default: + return -E_MESSAGE_TYPE_ERROR; + } +} + +int AbilitySync::RequestPacketCalculateLen(const Message *inMsg, uint32_t &len) +{ + const AbilitySyncRequestPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + + len = packet->CalculateLen(); + return E_OK; +} + +int AbilitySync::AckPacketCalculateLen(const Message *inMsg, uint32_t &len) +{ + const AbilitySyncAckPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + + len = packet->CalculateLen(); + return E_OK; +} + +int AbilitySync::RequestPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + const AbilitySyncRequestPacket *packet = inMsg->GetObject(); + if ((packet == nullptr) || (length != packet->CalculateLen())) { + return -E_INVALID_ARGS; + } + + Parcel parcel(buffer, length); + parcel.WriteUInt32(packet->GetProtocolVersion()); + parcel.WriteInt(packet->GetSendCode()); + parcel.WriteUInt32(packet->GetSoftwareVersion()); + parcel.WriteString(packet->GetSchema()); + parcel.WriteInt(packet->GetSecLabel()); + parcel.WriteInt(packet->GetSecFlag()); + parcel.WriteUInt32(packet->GetSchemaType()); + parcel.WriteUInt64(packet->GetDbCreateTime()); + int errCode = DbAbility::Serialize(parcel, packet->GetDbAbility()); + if (parcel.IsError() || errCode != E_OK) { + return -E_PARSE_FAIL; + } + return E_OK; +} + +int AbilitySync::AckPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + const AbilitySyncAckPacket *packet = inMsg->GetObject(); + if ((packet == nullptr) || (length != packet->CalculateLen())) { + return -E_INVALID_ARGS; + } + + Parcel parcel(buffer, length); + parcel.WriteUInt32(ABILITY_SYNC_VERSION_V1); + parcel.WriteUInt32(SOFTWARE_VERSION_CURRENT); + parcel.WriteInt(packet->GetAckCode()); + parcel.WriteString(packet->GetSchema()); + parcel.WriteInt(packet->GetSecLabel()); + parcel.WriteInt(packet->GetSecFlag()); + parcel.WriteUInt32(packet->GetSchemaType()); + parcel.WriteUInt32(packet->GetPermitSync()); + parcel.WriteUInt32(packet->GetRequirePeerConvert()); + parcel.WriteUInt64(packet->GetDbCreateTime()); + int errCode = DbAbility::Serialize(parcel, packet->GetDbAbility()); + if (parcel.IsError() || errCode != E_OK) { + return -E_PARSE_FAIL; + } + errCode = SchemaNegotiate::SerializeData(packet->GetRelationalSyncOpinion(), parcel); + if (parcel.IsError() || errCode != E_OK) { + return -E_PARSE_FAIL; + } + return E_OK; +} + +int AbilitySync::RequestPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + auto *packet = new (std::nothrow) AbilitySyncRequestPacket(); + if (packet == nullptr) { + return -E_OUT_OF_MEMORY; + } + + Parcel parcel(const_cast(buffer), length); + uint32_t version = 0; + uint32_t softwareVersion = 0; + std::string schema; + int32_t sendCode = 0; + int errCode = -E_PARSE_FAIL; + + parcel.ReadUInt32(version); + if (parcel.IsError()) { + goto ERROR_OUT; + } + packet->SetProtocolVersion(version); + if (version > ABILITY_SYNC_VERSION_V1) { + packet->SetSendCode(-E_VERSION_NOT_SUPPORT); + errCode = inMsg->SetExternalObject<>(packet); + if (errCode != E_OK) { + goto ERROR_OUT; + } + return errCode; + } + parcel.ReadInt(sendCode); + parcel.ReadUInt32(softwareVersion); + parcel.ReadString(schema); + errCode = RequestPacketDeSerializationTailPart(parcel, packet, softwareVersion); + if (parcel.IsError() || errCode != E_OK) { + goto ERROR_OUT; + } + packet->SetSendCode(sendCode); + packet->SetSoftwareVersion(softwareVersion); + packet->SetSchema(schema); + + errCode = inMsg->SetExternalObject<>(packet); + if (errCode == E_OK) { + return E_OK; + } + +ERROR_OUT: + delete packet; + packet = nullptr; + return errCode; +} + +int AbilitySync::RequestPacketDeSerializationTailPart(Parcel &parcel, AbilitySyncRequestPacket *packet, + uint32_t version) +{ + if (!parcel.IsError() && version > SOFTWARE_VERSION_RELEASE_2_0) { + int32_t secLabel = 0; + int32_t secFlag = 0; + uint32_t schemaType = 0; + parcel.ReadInt(secLabel); + parcel.ReadInt(secFlag); + parcel.ReadUInt32(schemaType); + packet->SetSecLabel(secLabel); + packet->SetSecFlag(secFlag); + packet->SetSchemaType(schemaType); + } + if (!parcel.IsError() && version > SOFTWARE_VERSION_RELEASE_3_0) { + uint64_t dbCreateTime = 0; + parcel.ReadUInt64(dbCreateTime); + packet->SetDbCreateTime(dbCreateTime); + } + DbAbility remoteDbAbility; + int errCode = DbAbility::DeSerialize(parcel, remoteDbAbility); + if (errCode != E_OK) { + LOGE("[AbilitySync] request packet DeSerializ failed."); + return errCode; + } + packet->SetDbAbility(remoteDbAbility); + return E_OK; +} + +int AbilitySync::AckPacketDeSerializationTailPart(Parcel &parcel, AbilitySyncAckPacket *packet, uint32_t version) +{ + if (!parcel.IsError() && version > SOFTWARE_VERSION_RELEASE_2_0) { + int32_t secLabel = 0; + int32_t secFlag = 0; + uint32_t schemaType = 0; + uint32_t permitSync = 0; + uint32_t requirePeerConvert = 0; + parcel.ReadInt(secLabel); + parcel.ReadInt(secFlag); + parcel.ReadUInt32(schemaType); + parcel.ReadUInt32(permitSync); + parcel.ReadUInt32(requirePeerConvert); + packet->SetSecLabel(secLabel); + packet->SetSecFlag(secFlag); + packet->SetSchemaType(schemaType); + packet->SetPermitSync(permitSync); + packet->SetRequirePeerConvert(requirePeerConvert); + } + if (!parcel.IsError() && version > SOFTWARE_VERSION_RELEASE_3_0) { + uint64_t dbCreateTime = 0; + parcel.ReadUInt64(dbCreateTime); + packet->SetDbCreateTime(dbCreateTime); + } + DbAbility remoteDbAbility; + int errCode = DbAbility::DeSerialize(parcel, remoteDbAbility); + if (errCode != E_OK) { + LOGE("[AbilitySync] ack packet DeSerializ failed."); + return errCode; + } + packet->SetDbAbility(remoteDbAbility); + RelationalSyncOpinion relationalSyncOpinion; + errCode = SchemaNegotiate::DeserializeData(parcel, relationalSyncOpinion); + if (errCode != E_OK) { + LOGE("[AbilitySync] ack packet DeSerializ RelationalSyncOpinion failed."); + return errCode; + } + packet->SetRelationalSyncOpinion(relationalSyncOpinion); + return E_OK; +} + +int AbilitySync::AckPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + auto *packet = new (std::nothrow) AbilitySyncAckPacket(); + if (packet == nullptr) { + return -E_OUT_OF_MEMORY; + } + + Parcel parcel(const_cast(buffer), length); + uint32_t version = 0; + uint32_t softwareVersion = 0; + int32_t ackCode = E_OK; + std::string schema; + int errCode; + parcel.ReadUInt32(version); + if (parcel.IsError()) { + LOGE("[AbilitySync][RequestDeSerialization] read version failed!"); + errCode = -E_PARSE_FAIL; + goto ERROR_OUT; + } + packet->SetProtocolVersion(version); + if (version > ABILITY_SYNC_VERSION_V1) { + packet->SetAckCode(-E_VERSION_NOT_SUPPORT); + errCode = inMsg->SetExternalObject<>(packet); + if (errCode != E_OK) { + goto ERROR_OUT; + } + return errCode; + } + parcel.ReadUInt32(softwareVersion); + parcel.ReadInt(ackCode); + parcel.ReadString(schema); + errCode = AckPacketDeSerializationTailPart(parcel, packet, softwareVersion); + if (parcel.IsError() || errCode != E_OK) { + LOGE("[AbilitySync][RequestDeSerialization] DeSerialization failed!"); + errCode = -E_PARSE_FAIL; + goto ERROR_OUT; + } + packet->SetSoftwareVersion(softwareVersion); + packet->SetAckCode(ackCode); + packet->SetSchema(schema); + errCode = inMsg->SetExternalObject<>(packet); + if (errCode == E_OK) { + return E_OK; + } + +ERROR_OUT: + delete packet; + packet = nullptr; + return errCode; +} + +int AbilitySync::SetAbilityRequestBodyInfo(AbilitySyncRequestPacket &packet, uint16_t remoteCommunicatorVersion) const +{ + uint64_t dbCreateTime; + int errCode = + (static_cast(storageInterface_))->GetDatabaseCreateTimestamp(dbCreateTime); + if (errCode != E_OK) { + LOGE("[AbilitySync][FillAbilityRequest] GetDatabaseCreateTimestamp failed, err %d", errCode); + return errCode; + } + SecurityOption option; + GetPacketSecOption(option); + std::string schemaStr; + uint32_t schemaType = 0; + if (IsSingleKvVer()) { + SchemaObject schemaObj = (static_cast(storageInterface_))->GetSchemaInfo(); + schemaStr = schemaObj.ToSchemaString(); + schemaType = static_cast(schemaObj.GetSchemaType()); + } else if (IsSingleRelationalVer()) { + auto schemaObj = (static_cast(storageInterface_))->GetSchemaInfo(); + schemaStr = schemaObj.ToSchemaString(); + schemaType = static_cast(schemaObj.GetSchemaType()); + } + DbAbility dbAbility; + errCode = GetDbAbilityInfo(dbAbility); + if (errCode != E_OK) { + LOGE("[AbilitySync][FillAbilityRequest] GetDbAbility failed, err %d", errCode); + return errCode; + } + // 102 version is forbidden to sync with 103 json-schema or flatbuffer-schema + // so schema should put null string while remote is 102 version to avoid this bug. + if (remoteCommunicatorVersion == 1) { + packet.SetSchema(""); + packet.SetSchemaType(0); + } else { + packet.SetSchema(schemaStr); + packet.SetSchemaType(schemaType); + } + packet.SetProtocolVersion(ABILITY_SYNC_VERSION_V1); + packet.SetSoftwareVersion(SOFTWARE_VERSION_CURRENT); + packet.SetSecLabel(option.securityLabel); + packet.SetSecFlag(option.securityFlag); + packet.SetDbCreateTime(dbCreateTime); + packet.SetDbAbility(dbAbility); + LOGI("[AbilitySync][FillRequest] ver=%u,Lab=%d,Flag=%d,dbCreateTime=%" PRId64, SOFTWARE_VERSION_CURRENT, + option.securityLabel, option.securityFlag, dbCreateTime); + return E_OK; +} + +int AbilitySync::SetAbilityAckBodyInfo(AbilitySyncAckPacket &ackPacket, int ackCode, bool isAckNotify) const +{ + int errCode = E_OK; + ackPacket.SetProtocolVersion(ABILITY_SYNC_VERSION_V1); + ackPacket.SetSoftwareVersion(SOFTWARE_VERSION_CURRENT); + if (!isAckNotify) { + SecurityOption option; + GetPacketSecOption(option); + ackPacket.SetSecLabel(option.securityLabel); + ackPacket.SetSecFlag(option.securityFlag); + uint64_t dbCreateTime = 0; + errCode = + (static_cast(storageInterface_))->GetDatabaseCreateTimestamp(dbCreateTime); + if (errCode != E_OK) { + LOGE("[AbilitySync][SyncStart] GetDatabaseCreateTimestamp failed, err %d", errCode); + ackCode = errCode; + } + DbAbility dbAbility; + errCode = GetDbAbilityInfo(dbAbility); + if (errCode != E_OK) { + LOGE("[AbilitySync][FillAbilityRequest] GetDbAbility failed, err %d", errCode); + return errCode; + } + ackPacket.SetDbCreateTime(dbCreateTime); + ackPacket.SetDbAbility(dbAbility); + } + ackPacket.SetAckCode(ackCode); + return E_OK; +} + +void AbilitySync::SetAbilityAckSchemaInfo(AbilitySyncAckPacket &ackPacket, const ISchema &schemaObj) const +{ + ackPacket.SetSchema(schemaObj.ToSchemaString()); + ackPacket.SetSchemaType(static_cast(schemaObj.GetSchemaType())); +} + +void AbilitySync::SetAbilityAckSyncOpinionInfo(AbilitySyncAckPacket &ackPacket, SyncOpinion localOpinion) const +{ + ackPacket.SetPermitSync(localOpinion.permitSync); + ackPacket.SetRequirePeerConvert(localOpinion.requirePeerConvert); +} + +int AbilitySync::GetDbAbilityInfo(DbAbility &dbAbility) const +{ + int errCode = E_OK; + for (const auto &item : SyncConfig::ABILITYBITS) { + errCode = dbAbility.SetAbilityItem(item, SUPPORT_MARK); + if (errCode != E_OK) { + return errCode; + } + } + return errCode; +} + +int AbilitySync::AckMsgCheck(const Message *message, ISyncTaskContext *context) const +{ + if (message == nullptr || context == nullptr) { + return -E_INVALID_ARGS; + } + if (message->GetErrorNo() == E_FEEDBACK_UNKNOWN_MESSAGE) { + LOGE("[AbilitySync][AckMsgCheck] Remote device dose not support this message id"); + context->SetRemoteSoftwareVersion(SOFTWARE_VERSION_EARLIEST); + context->SetTaskErrCode(-E_FEEDBACK_UNKNOWN_MESSAGE); + return -E_FEEDBACK_UNKNOWN_MESSAGE; + } + if (message->GetErrorNo() == E_FEEDBACK_COMMUNICATOR_NOT_FOUND) { + LOGE("[AbilitySync][AckMsgCheck] Remote db is closed"); + context->SetTaskErrCode(-E_FEEDBACK_COMMUNICATOR_NOT_FOUND); + return -E_FEEDBACK_COMMUNICATOR_NOT_FOUND; + } + const AbilitySyncAckPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + int ackCode = packet->GetAckCode(); + if (ackCode != E_OK) { + LOGE("[AbilitySync][AckMsgCheck] received an errCode %d", ackCode); + context->SetTaskErrCode(ackCode); + return ackCode; + } + return E_OK; +} + +bool AbilitySync::IsSingleKvVer() const +{ + return storageInterface_->GetInterfaceType() == ISyncInterface::SYNC_SVD; +} +bool AbilitySync::IsSingleRelationalVer() const +{ +#ifdef RELATIONAL_STORE + return storageInterface_->GetInterfaceType() == ISyncInterface::SYNC_RELATION; +#elif + return false; +#endif +} + +int AbilitySync::HandleRequestRecv(const Message *message, ISyncTaskContext *context, bool isCompatible) +{ + const AbilitySyncRequestPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + uint32_t remoteSoftwareVersion = packet->GetSoftwareVersion(); + int ackCode; + std::string schema = packet->GetSchema(); + if (remoteSoftwareVersion <= SOFTWARE_VERSION_RELEASE_2_0) { + LOGI("[AbilitySync][RequestRecv] remote version = %u, CheckSchemaCompatible = %d", + remoteSoftwareVersion, isCompatible); + return SendAckWithEmptySchema(message, E_OK, false); + } + HandleVersionV3RequestParam(packet, context); + if (SecLabelCheck(packet)) { + ackCode = E_OK; + } else { + ackCode = -E_SECURITY_OPTION_CHECK_ERROR; + } + if (ackCode == E_OK && remoteSoftwareVersion > SOFTWARE_VERSION_RELEASE_3_0) { + ackCode = metadata_->SetDbCreateTime(deviceId_, packet->GetDbCreateTime(), true); + } + AbilitySyncAckPacket ackPacket; + if (IsSingleRelationalVer()) { + ackPacket.SetRelationalSyncOpinion(MakeRelationSyncOpnion(packet, schema)); + } else { + SetAbilityAckSyncOpinionInfo(ackPacket, MakeKvSyncOpnion(packet, schema)); + } + LOGI("[AbilitySync][RequestRecv] remote dev=%s,ver=%u,schemaCompatible=%d", STR_MASK(deviceId_), + remoteSoftwareVersion, isCompatible); + return SendAck(message, ackCode, false, ackPacket); +} + +int AbilitySync::SendAck(const Message *message, int ackCode, bool isAckNotify, AbilitySyncAckPacket &ackPacket) +{ + int errCode = SetAbilityAckBodyInfo(ackPacket, ackCode, isAckNotify); + if (errCode != E_OK) { + return errCode; + } + if (IsSingleRelationalVer()) { + auto schemaObj = (static_cast(storageInterface_))->GetSchemaInfo(); + SetAbilityAckSchemaInfo(ackPacket, schemaObj); + } else if (IsSingleKvVer()) { + SchemaObject schemaObject = static_cast(storageInterface_)->GetSchemaInfo(); + SetAbilityAckSchemaInfo(ackPacket, schemaObject); + } + return SendAck(message, ackPacket, isAckNotify); +} + +int AbilitySync::SendAckWithEmptySchema(const Message *message, int ackCode, + bool isAckNotify) +{ + AbilitySyncAckPacket ackPacket; + int errCode = SetAbilityAckBodyInfo(ackPacket, ackCode, isAckNotify); + if (errCode != E_OK) { + return errCode; + } + SetAbilityAckSchemaInfo(ackPacket, SchemaObject()); + return SendAck(message, ackPacket, isAckNotify); +} + +int AbilitySync::SendAck(const Message *inMsg, const AbilitySyncAckPacket &ackPacket, bool isAckNotify) +{ + Message *ackMessage = new (std::nothrow) Message(ABILITY_SYNC_MESSAGE); + if (ackMessage == nullptr) { + LOGE("[AbilitySync][SendAck] message create failed, may be memleak!"); + return -E_OUT_OF_MEMORY; + } + int errCode = ackMessage->SetCopiedObject<>(ackPacket); + if (errCode != E_OK) { + LOGE("[AbilitySync][SendAck] SetCopiedObject failed, err %d", errCode); + delete ackMessage; + ackMessage = nullptr; + return errCode; + } + (!isAckNotify) ? ackMessage->SetMessageType(TYPE_RESPONSE) : ackMessage->SetMessageType(TYPE_NOTIFY); + ackMessage->SetTarget(deviceId_); + ackMessage->SetSessionId(inMsg->GetSessionId()); + ackMessage->SetSequenceId(inMsg->GetSequenceId()); + SendConfig conf; + SetSendConfigParam(storageInterface_->GetDbProperties(), deviceId_, false, SEND_TIME_OUT, conf); + errCode = communicator_->SendMessage(deviceId_, ackMessage, conf); + if (errCode != E_OK) { + LOGE("[AbilitySync][SendAck] SendPacket failed, err %d", errCode); + delete ackMessage; + ackMessage = nullptr; + } + return errCode; +} + +SyncOpinion AbilitySync::MakeKvSyncOpnion(const AbilitySyncRequestPacket *packet, const std::string &remoteSchema) const +{ + uint8_t remoteSchemaType = packet->GetSchemaType(); + SchemaObject localSchema = (static_cast(storageInterface_))->GetSchemaInfo(); + SyncOpinion localSyncOpinion = SchemaNegotiate::MakeLocalSyncOpinion(localSchema, remoteSchema, remoteSchemaType); + return localSyncOpinion; +} + +RelationalSyncOpinion AbilitySync::MakeRelationSyncOpnion(const AbilitySyncRequestPacket *packet, + const std::string &remoteSchema) const +{ + uint8_t remoteSchemaType = packet->GetSchemaType(); + RelationalSchemaObject localSchema = (static_cast(storageInterface_))->GetSchemaInfo(); + return SchemaNegotiate::MakeLocalSyncOpinion(localSchema, remoteSchema, remoteSchemaType); +} + +void AbilitySync::HandleKvAckSchemaParam(const AbilitySyncAckPacket *recvPacket, + ISyncTaskContext *context, AbilitySyncAckPacket &sendPacket) const +{ + std::string remoteSchema = recvPacket->GetSchema(); + uint8_t remoteSchemaType = recvPacket->GetSchemaType(); + bool permitSync = static_cast(recvPacket->GetPermitSync()); + bool requirePeerConvert = static_cast(recvPacket->GetRequirePeerConvert()); + SyncOpinion remoteOpinion = {permitSync, requirePeerConvert, true}; + SchemaObject localSchema = (static_cast(storageInterface_))->GetSchemaInfo(); + SyncOpinion syncOpinion = SchemaNegotiate::MakeLocalSyncOpinion(localSchema, remoteSchema, remoteSchemaType); + SyncStrategy localStrategy = SchemaNegotiate::ConcludeSyncStrategy(syncOpinion, remoteOpinion); + SetAbilityAckSyncOpinionInfo(sendPacket, syncOpinion); + (static_cast(context))->SetSyncStrategy(localStrategy); +} + +int AbilitySync::HandleRelationAckSchemaParam(const AbilitySyncAckPacket *recvPacket, AbilitySyncAckPacket &sendPacket, + ISyncTaskContext *context, bool sendOpinion) const +{ + std::string remoteSchema = recvPacket->GetSchema(); + uint8_t remoteSchemaType = recvPacket->GetSchemaType(); + auto localSchema = (static_cast(storageInterface_))->GetSchemaInfo(); + auto localOpinion = SchemaNegotiate::MakeLocalSyncOpinion(localSchema, remoteSchema, remoteSchemaType); + auto localStrategy = SchemaNegotiate::ConcludeSyncStrategy(localOpinion, + recvPacket->GetRelationalSyncOpinion()); + (static_cast(context))->SetRelationalSyncStrategy(localStrategy); + int errCode = (static_cast(storageInterface_))-> + CreateDistributedDeviceTable(context->GetDeviceId(), localStrategy); + if (errCode != E_OK) { + LOGE("[AbilitySync][AckRecv] create distributed device table failed,errCode=%d", errCode); + } + if (sendOpinion) { + sendPacket.SetRelationalSyncOpinion(localOpinion); + } + return errCode; +} + +int AbilitySync::AckRecvWithHighVersion(const Message *message, ISyncTaskContext *context, + const AbilitySyncAckPacket *packet) +{ + HandleVersionV3AckSecOptionParam(packet, context); + AbilitySyncAckPacket ackPacket; + int errCode = HandleVersionV3AckSchemaParam(packet, ackPacket, context, true); + if (errCode != E_OK) { + return errCode; + } + auto singleVerContext = static_cast(context); + auto query = singleVerContext->GetQuery(); + bool permitSync = (singleVerContext->GetSyncStrategy(query)).permitSync; + if (!permitSync) { + (static_cast(context))->SetTaskErrCode(-E_SCHEMA_MISMATCH); + LOGE("[AbilitySync][AckRecv] scheme check failed"); + return -E_SCHEMA_MISMATCH; + } + if (context->GetRemoteSoftwareVersion() > SOFTWARE_VERSION_RELEASE_3_0) { + errCode = metadata_->SetDbCreateTime(deviceId_, packet->GetDbCreateTime(), true); + if (errCode != E_OK) { + LOGE("[AbilitySync][AckRecv] set db create time failed,errCode=%d", errCode); + context->SetTaskErrCode(errCode); + return errCode; + } + } + DbAbility remoteDbAbility = packet->GetDbAbility(); + (static_cast(context))->SetDbAbility(remoteDbAbility); + (void)SendAck(message, AbilitySync::CHECK_SUCCESS, true, ackPacket); + (static_cast(context))->SetIsSchemaSync(true); + return E_OK; +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/ability_sync.h b/mock/distributeddb/syncer/src/ability_sync.h new file mode 100644 index 00000000..05f0465f --- /dev/null +++ b/mock/distributeddb/syncer/src/ability_sync.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 ABILITY_SYNC_H +#define ABILITY_SYNC_H + +#include +#include + +#include "db_ability.h" +#include "icommunicator.h" +#include "ikvdb_sync_interface.h" +#include "isync_task_context.h" +#include "parcel.h" +#ifdef RELATIONAL_STORE +#include "ischema.h" +#endif +#include "schema_negotiate.h" + +namespace DistributedDB { +class AbilitySyncRequestPacket { +public: + AbilitySyncRequestPacket(); + ~AbilitySyncRequestPacket(); + + void SetProtocolVersion(uint32_t protocolVersion); + uint32_t GetProtocolVersion() const; + + void SetSendCode(int32_t sendCode); + int32_t GetSendCode() const; + + void SetSoftwareVersion(uint32_t swVersion); + uint32_t GetSoftwareVersion() const; + + void SetSchema(const std::string &schema); + std::string GetSchema() const; + + void SetSchemaType(uint32_t schemaType); + uint32_t GetSchemaType() const; + + void SetSecLabel(int32_t secLabel); + int32_t GetSecLabel() const; + + void SetSecFlag(int32_t secFlag); + int32_t GetSecFlag() const; + + void SetDbCreateTime(uint64_t dbCreateTime); + uint64_t GetDbCreateTime() const; + + uint32_t CalculateLen() const; + + DbAbility GetDbAbility() const; + + void SetDbAbility(const DbAbility &dbAbility); + +private: + uint32_t protocolVersion_; + int32_t sendCode_; + uint32_t softwareVersion_; + std::string schema_; + int32_t secLabel_; + int32_t secFlag_; + uint32_t schemaType_; + uint64_t dbCreateTime_; + DbAbility dbAbility_; +}; + +class AbilitySyncAckPacket { +public: + AbilitySyncAckPacket(); + ~AbilitySyncAckPacket(); + + void SetProtocolVersion(uint32_t protocolVersion); + uint32_t GetProtocolVersion() const; + + void SetSoftwareVersion(uint32_t swVersion); + uint32_t GetSoftwareVersion() const; + + void SetAckCode(int32_t ackCode); + int32_t GetAckCode() const; + + void SetSchema(const std::string &schema); + std::string GetSchema() const; + + void SetSchemaType(uint32_t schemaType); + uint32_t GetSchemaType() const; + + void SetSecLabel(int32_t secLabel); + int32_t GetSecLabel() const; + + void SetSecFlag(int32_t secFlag); + int32_t GetSecFlag() const; + + void SetPermitSync(uint32_t permitSync); + uint32_t GetPermitSync() const; + + void SetRequirePeerConvert(uint32_t requirePeerConvert); + uint32_t GetRequirePeerConvert() const; + + void SetDbCreateTime(uint64_t dbCreateTime); + uint64_t GetDbCreateTime() const; + + uint32_t CalculateLen() const; + + DbAbility GetDbAbility() const; + + void SetDbAbility(const DbAbility &dbAbility); + + void SetRelationalSyncOpinion(const RelationalSyncOpinion &relationalSyncOpinion); + + RelationalSyncOpinion GetRelationalSyncOpinion() const; + +private: + uint32_t protocolVersion_; + uint32_t softwareVersion_; + int32_t ackCode_; + std::string schema_; + int32_t secLabel_; + int32_t secFlag_; + uint32_t schemaType_; + uint32_t permitSync_; + uint32_t requirePeerConvert_; + uint64_t dbCreateTime_; + DbAbility dbAbility_; + RelationalSyncOpinion relationalSyncOpinion_; +}; + +class AbilitySync { +public: + static const int CHECK_SUCCESS = 0; + static const int LAST_NOTIFY = 0xfe; + AbilitySync(); + ~AbilitySync(); + + // Start Ability Sync + int SyncStart(uint32_t sessionId, uint32_t sequenceId, uint16_t remoteCommunicatorVersion, + const CommErrHandler &handler = nullptr); + + int Initialize(ICommunicator *inCommunicator, ISyncInterface *inStorage, std::shared_ptr &inMetadata, + const std::string &deviceId); + + int AckRecv(const Message *message, ISyncTaskContext *context); + + int RequestRecv(const Message *message, ISyncTaskContext *context); + + int AckNotifyRecv(const Message *message, ISyncTaskContext *context); + + bool GetAbilitySyncFinishedStatus() const; + + void SetAbilitySyncFinishedStatus(bool syncFinished); + + static int RegisterTransformFunc(); + + static uint32_t CalculateLen(const Message *inMsg); + + static int Serialization(uint8_t *buffer, uint32_t length, const Message *inMsg); // register to communicator + + static int DeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); // register to communicator + +private: + + static int RequestPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int AckPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int RequestPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static int AckPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static int RequestPacketCalculateLen(const Message *inMsg, uint32_t &len); + + static int AckPacketCalculateLen(const Message *inMsg, uint32_t &len); + + static int RequestPacketDeSerializationTailPart(Parcel &parcel, AbilitySyncRequestPacket *packet, + uint32_t version); + + static int AckPacketDeSerializationTailPart(Parcel &parcel, AbilitySyncAckPacket *packet, uint32_t version); + + bool SecLabelCheck(const AbilitySyncRequestPacket *packet) const; + + void HandleVersionV3RequestParam(const AbilitySyncRequestPacket *packet, ISyncTaskContext *context) const; + + void HandleVersionV3AckSecOptionParam(const AbilitySyncAckPacket *packet, + ISyncTaskContext *context) const; + + int HandleVersionV3AckSchemaParam(const AbilitySyncAckPacket *recvPacket, + AbilitySyncAckPacket &sendPacket, ISyncTaskContext *context, bool sendOpinion) const; + + void HandleKvAckSchemaParam(const AbilitySyncAckPacket *recvPacket, + ISyncTaskContext *context, AbilitySyncAckPacket &sendPacket) const; + + int HandleRelationAckSchemaParam(const AbilitySyncAckPacket *recvPacket, + AbilitySyncAckPacket &sendPacket, ISyncTaskContext *context, bool sendOpinion) const; + + void GetPacketSecOption(SecurityOption &option) const; + + int SetAbilityRequestBodyInfo(AbilitySyncRequestPacket &packet, uint16_t remoteCommunicatorVersion) const; + + int SetAbilityAckBodyInfo(AbilitySyncAckPacket &ackPacket, int ackCode, bool isAckNotify) const; + + void SetAbilityAckSchemaInfo(AbilitySyncAckPacket &ackPacket, const ISchema &schemaObj) const; + + void SetAbilityAckSyncOpinionInfo(AbilitySyncAckPacket &ackPacket, SyncOpinion localOpinion) const; + + int GetDbAbilityInfo(DbAbility &dbAbility) const; + + int AckMsgCheck(const Message *message, ISyncTaskContext *context) const; + + bool IsSingleKvVer() const; + + bool IsSingleRelationalVer() const; + + int SendAck(const Message *message, int ackCode, bool isAckNotify, AbilitySyncAckPacket &ackPacket); + + int SendAckWithEmptySchema(const Message *message, int ackCode, bool isAckNotify); + + int SendAck(const Message *message, const AbilitySyncAckPacket &ackPacket, bool isAckNotify); + + int HandleRequestRecv(const Message *message, ISyncTaskContext *context, bool isCompatible); + + SyncOpinion MakeKvSyncOpnion(const AbilitySyncRequestPacket *packet, const std::string &remoteSchema) const; + + RelationalSyncOpinion MakeRelationSyncOpnion(const AbilitySyncRequestPacket *packet, + const std::string &remoteSchema) const; + + int AckRecvWithHighVersion(const Message *message, ISyncTaskContext *context, const AbilitySyncAckPacket *packet); + + ICommunicator *communicator_; + ISyncInterface *storageInterface_; + std::shared_ptr metadata_; + std::string deviceId_; + bool syncFinished_; + static const int FAILED_GET_SEC_CLASSIFICATION = 0x55; +}; +} // namespace DistributedDB +#endif // ABILITY_SYNC_H diff --git a/mock/distributeddb/syncer/src/commit_history_sync.cpp b/mock/distributeddb/syncer/src/commit_history_sync.cpp new file mode 100644 index 00000000..cb5f3a16 --- /dev/null +++ b/mock/distributeddb/syncer/src/commit_history_sync.cpp @@ -0,0 +1,717 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "commit_history_sync.h" + +#include "sync_engine.h" +#include "parcel.h" +#include "log_print.h" +#include "message_transform.h" +#include "performance_analysis.h" +#include "db_constant.h" + +namespace DistributedDB { +// Class CommitHistorySyncRequestPacket +uint32_t CommitHistorySyncRequestPacket::CalculateLen() const +{ + uint64_t len = Parcel::GetUInt64Len(); + // commitMap len + for (const auto &iter : commitMap_) { + len += Parcel::GetStringLen(iter.first); + len += Parcel::GetMultiVerCommitLen(iter.second); + if (len > INT32_MAX) { + return 0; + } + } + len += Parcel::GetUInt32Len(); // version + len += Parcel::GetVectorLen(reserved_); // reserved + len = Parcel::GetEightByteAlign(len); + if (len > INT32_MAX) { + return 0; + } + return len; +} + +void CommitHistorySyncRequestPacket::SetCommitMap(std::map &inMap) +{ + commitMap_ = std::move(inMap); +} + +void CommitHistorySyncRequestPacket::GetCommitMap(std::map &outMap) const +{ + outMap = commitMap_; +} + +void CommitHistorySyncRequestPacket::SetVersion(uint32_t version) +{ + version_ = version; +} + +uint32_t CommitHistorySyncRequestPacket::GetVersion() const +{ + return version_; +} + +void CommitHistorySyncRequestPacket::SetReserved(std::vector &reserved) +{ + reserved_ = std::move(reserved); +} + +std::vector CommitHistorySyncRequestPacket::GetReserved() const +{ + return reserved_; +} + +uint32_t CommitHistorySyncAckPacket::CalculateLen() const +{ + uint64_t len = Parcel::GetIntLen(); // errCode + len += Parcel::GetUInt32Len(); // version + len = Parcel::GetEightByteAlign(len); + + // commits vector len + len += Parcel::GetMultiVerCommitsLen(commits_); + len += Parcel::GetVectorLen(reserved_); // reserved + len = Parcel::GetEightByteAlign(len); + if (len > INT32_MAX) { + return 0; + } + return len; +} + +void CommitHistorySyncAckPacket::SetData(std::vector &inData) +{ + commits_ = std::move(inData); +} + +void CommitHistorySyncAckPacket::GetData(std::vector &outData) const +{ + outData = commits_; +} + +void CommitHistorySyncAckPacket::SetErrorCode(int32_t errCode) +{ + errorCode_ = errCode; +} + +void CommitHistorySyncAckPacket::GetErrorCode(int32_t &errCode) const +{ + errCode = errorCode_; +} + +void CommitHistorySyncAckPacket::SetVersion(uint32_t version) +{ + version_ = version; +} + +uint32_t CommitHistorySyncAckPacket::GetVersion() const +{ + return version_; +} + +void CommitHistorySyncAckPacket::SetReserved(std::vector &reserved) +{ + reserved_ = std::move(reserved); +} + +std::vector CommitHistorySyncAckPacket::GetReserved() const +{ + return reserved_; +} + +// Class CommitHistorySync +CommitHistorySync::~CommitHistorySync() +{ + storagePtr_ = nullptr; + communicateHandle_ = nullptr; +} + +int CommitHistorySync::Serialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + if ((buffer == nullptr) || !(IsPacketValid(inMsg, TYPE_RESPONSE) || IsPacketValid(inMsg, TYPE_REQUEST))) { + return -E_MESSAGE_ID_ERROR; + } + + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return RequestPacketSerialization(buffer, length, inMsg); + case TYPE_RESPONSE: + return AckPacketSerialization(buffer, length, inMsg); + default: + return -E_MESSAGE_TYPE_ERROR; + } +} + +int CommitHistorySync::DeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + if ((buffer == nullptr) || !(IsPacketValid(inMsg, TYPE_RESPONSE) || IsPacketValid(inMsg, TYPE_REQUEST))) { + return -E_MESSAGE_ID_ERROR; + } + + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return RequestPacketDeSerialization(buffer, length, inMsg); + case TYPE_RESPONSE: + return AckPacketDeSerialization(buffer, length, inMsg); + default: + return -E_MESSAGE_TYPE_ERROR; + } +} + +uint32_t CommitHistorySync::CalculateLen(const Message *inMsg) +{ + if (!(IsPacketValid(inMsg, TYPE_RESPONSE) || IsPacketValid(inMsg, TYPE_REQUEST))) { + return 0; + } + + uint32_t len = 0; + int errCode = E_OK; + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + errCode = RequestPacketCalculateLen(inMsg, len); + if (errCode != E_OK) { + return 0; + } + return len; + case TYPE_RESPONSE: + errCode = AckPacketCalculateLen(inMsg, len); + if (errCode != E_OK) { + return 0; + } + return len; + default: + return 0; + } +} + +int CommitHistorySync::RegisterTransformFunc() +{ + TransformFunc func; + func.computeFunc = std::bind(&CommitHistorySync::CalculateLen, std::placeholders::_1); + func.serializeFunc = std::bind(&CommitHistorySync::Serialization, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + func.deserializeFunc = std::bind(&CommitHistorySync::DeSerialization, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + return MessageTransform::RegTransformFunction(COMMIT_HISTORY_SYNC_MESSAGE, func); +} + +int CommitHistorySync::Initialize(MultiVerKvDBSyncInterface *storagePtr, ICommunicator *communicateHandle) +{ + if ((storagePtr == nullptr) || (communicateHandle == nullptr)) { + return -E_INVALID_ARGS; + } + storagePtr_ = storagePtr; + communicateHandle_ = communicateHandle; + return E_OK; +} + +void CommitHistorySync::TimeOutCallback(MultiVerSyncTaskContext *context, const Message *message) const +{ + (void)context; + (void)message; + return; +} + +int CommitHistorySync::SyncStart(MultiVerSyncTaskContext *context) +{ + if (context == nullptr) { + return -E_INVALID_ARGS; + } + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_GET_DEVICE_LATEST_COMMIT); + } + std::map commitMap; + int errCode = GetDeviceLatestCommit(commitMap); + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_GET_DEVICE_LATEST_COMMIT); + } + if ((errCode != E_OK) && (errCode != -E_NOT_FOUND)) { + return errCode; + } + + LOGD("CommitHistorySync::commitMap size = %zu, dst=%s{private}", commitMap.size(), context->GetDeviceId().c_str()); + return SendRequestPacket(context, commitMap); +} + +int CommitHistorySync::RequestRecvCallback(const MultiVerSyncTaskContext *context, const Message *message) +{ + if (!IsPacketValid(message, TYPE_REQUEST) || context == nullptr) { + return -E_INVALID_ARGS; + } + const CommitHistorySyncRequestPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + std::vector commits; + int errCode = RunPermissionCheck(context->GetDeviceId()); + if (errCode == -E_NOT_PERMIT) { + LOGE("CommitHistorySync::RequestRecvCallback RunPermissionCheck not pass"); + SendAckPacket(context, commits, errCode, message); + return errCode; + } + std::map commitMap; + packet->GetCommitMap(commitMap); + uint32_t ver = packet->GetVersion(); + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_GET_COMMIT_TREE); + } + errCode = GetCommitTree(commitMap, commits); + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_GET_COMMIT_TREE); + } + if (errCode != E_OK) { + LOGE("CommitHistorySync::RequestRecvCallback : GetCommitTree ERR, errno = %d", errCode); + } + + errCode = SendAckPacket(context, commits, errCode, message); + LOGD("CommitHistorySync::RequestRecvCallback:SendAckPacket, errno = %d, dst=%s{private}, ver = %u, myversion = %u", + errCode, context->GetDeviceId().c_str(), ver, SOFTWARE_VERSION_CURRENT); + if (errCode == E_OK) { + if (commitMap.empty()) { + LOGD("[CommitHistorySync][RequestRecvCallback] no need to start SyncResponse"); + return -E_NOT_FOUND; + } + } + return errCode; +} + +int CommitHistorySync::AckRecvCallback(MultiVerSyncTaskContext *context, const Message *message) +{ + if (!IsPacketValid(message, TYPE_RESPONSE) || (context == nullptr)) { + return -E_INVALID_ARGS; + } + + std::vector commits; + int32_t errCode; + + const CommitHistorySyncAckPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + packet->GetErrorCode(errCode); + if (errCode == -E_NOT_PERMIT) { + LOGE("CommitHistorySync::AckRecvCallback RunPermissionCheck not pass"); + return errCode; + } + packet->GetData(commits); + uint32_t ver = packet->GetVersion(); + context->SetCommits(commits); + context->SetCommitIndex(0); + context->SetCommitsSize(static_cast(commits.size())); + LOGD("CommitHistorySync::AckRecvCallback end, CommitsSize = %zu, dst = %s{private}, ver = %d, myversion = %u", + commits.size(), context->GetDeviceId().c_str(), ver, SOFTWARE_VERSION_CURRENT); + return E_OK; +} + +int CommitHistorySync::RequestPacketCalculateLen(const Message *inMsg, uint32_t &len) +{ + if (inMsg == nullptr) { + return -E_INVALID_ARGS; + } + const CommitHistorySyncRequestPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + + if ((inMsg->GetMessageId() != COMMIT_HISTORY_SYNC_MESSAGE) || (inMsg->GetMessageType() != TYPE_REQUEST)) { + return -E_INVALID_ARGS; + } + len = packet->CalculateLen(); + return E_OK; +} + +int CommitHistorySync::RequestPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + if ((buffer == nullptr) || (inMsg == nullptr)) { + return -E_INVALID_ARGS; + } + const CommitHistorySyncRequestPacket *packet = inMsg->GetObject(); + if ((packet == nullptr) || (length != packet->CalculateLen())) { + return -E_INVALID_ARGS; + } + + Parcel parcel(buffer, length); + std::map commitMap; + packet->GetCommitMap(commitMap); + + int errCode = parcel.WriteUInt64(commitMap.size()); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + // commitMap Serialization + for (auto &iter : commitMap) { + errCode = parcel.WriteString(iter.first); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + errCode = parcel.WriteMultiVerCommit(iter.second); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + } + errCode = parcel.WriteUInt32(packet->GetVersion()); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + errCode = parcel.WriteVector(packet->GetReserved()); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + parcel.EightByteAlign(); + return errCode; +} + +int CommitHistorySync::RequestPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + if ((buffer == nullptr) || (inMsg == nullptr)) { + return -E_INVALID_ARGS; + } + + uint64_t packLen = 0; + uint64_t len = 0; + Parcel parcel(const_cast(buffer), length); + packLen += parcel.ReadUInt64(len); + if (len > DBConstant::MAX_DEVICES_SIZE) { + LOGE("CommitHistorySync::RequestPacketDeSerialization : commitMap size too large = %" PRIu64, len); + return -E_INVALID_ARGS; + } + // commitMap DeSerialization + std::map commitMap; + while (len > 0) { + std::string key; + MultiVerCommitNode val; + packLen += parcel.ReadString(key); + packLen += parcel.ReadMultiVerCommit(val); + commitMap[key] = val; + len--; + if (parcel.IsError()) { + return -E_INVALID_ARGS; + } + } + uint32_t version; + std::vector reserved; + packLen += parcel.ReadUInt32(version); + packLen += parcel.ReadVector(reserved); + packLen = Parcel::GetEightByteAlign(packLen); + if (packLen != length || parcel.IsError()) { + LOGE("CommitHistorySync::RequestPacketDeSerialization : length error, input len = %" PRIu32 + ", cac len = %" PRIu64, length, packLen); + return -E_INVALID_ARGS; + } + CommitHistorySyncRequestPacket *packet = new (std::nothrow) CommitHistorySyncRequestPacket(); + if (packet == nullptr) { + LOGE("CommitHistorySync::RequestPacketDeSerialization : new packet error"); + return -E_OUT_OF_MEMORY; + } + packet->SetCommitMap(commitMap); + packet->SetVersion(version); + packet->SetReserved(reserved); + int errCode = inMsg->SetExternalObject<>(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + } + return errCode; +} + +int CommitHistorySync::AckPacketCalculateLen(const Message *inMsg, uint32_t &len) +{ + if (inMsg == nullptr) { + return -E_INVALID_ARGS; + } + const CommitHistorySyncAckPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + + if ((inMsg->GetMessageId() != COMMIT_HISTORY_SYNC_MESSAGE) || (inMsg->GetMessageType() != TYPE_RESPONSE)) { + return -E_INVALID_ARGS; + } + len = packet->CalculateLen(); + return E_OK; +} + +int CommitHistorySync::AckPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + if ((buffer == nullptr) || (inMsg == nullptr)) { + return -E_INVALID_ARGS; + } + const CommitHistorySyncAckPacket *packet = inMsg->GetObject(); + if ((packet == nullptr) || (length != packet->CalculateLen())) { + return -E_INVALID_ARGS; + } + + Parcel parcel(buffer, length); + int32_t ackErrCode; + std::vector commits; + + packet->GetData(commits); + packet->GetErrorCode(ackErrCode); + // errCode Serialization + int errCode = parcel.WriteInt(ackErrCode); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + errCode = parcel.WriteUInt32(packet->GetVersion()); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + parcel.EightByteAlign(); + + // commits vector Serialization + errCode = parcel.WriteMultiVerCommits(commits); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + errCode = parcel.WriteVector(packet->GetReserved()); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + parcel.EightByteAlign(); + return errCode; +} + +int CommitHistorySync::AckPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + std::vector commits; + uint32_t packLen = 0; + Parcel parcel(const_cast(buffer), length); + int32_t pktErrCode; + uint32_t version; + std::vector reserved; + + // errCode DeSerialization + packLen += parcel.ReadInt(pktErrCode); + packLen += parcel.ReadUInt32(version); + parcel.EightByteAlign(); + packLen = Parcel::GetEightByteAlign(packLen); + // commits vector DeSerialization + packLen += parcel.ReadMultiVerCommits(commits); + packLen += parcel.ReadVector(reserved); + packLen = Parcel::GetEightByteAlign(packLen); + if (packLen != length || parcel.IsError()) { + LOGE("CommitHistorySync::AckPacketDeSerialization : packet len error, input len = %u, cal len = %u", + length, packLen); + return -E_INVALID_ARGS; + } + CommitHistorySyncAckPacket *packet = new (std::nothrow) CommitHistorySyncAckPacket(); + if (packet == nullptr) { + LOGE("CommitHistorySync::AckPacketDeSerialization : new packet error"); + return -E_OUT_OF_MEMORY; + } + packet->SetData(commits); + packet->SetErrorCode(pktErrCode); + packet->SetVersion(version); + packet->SetReserved(reserved); + int errCode = inMsg->SetExternalObject<>(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + } + return errCode; +} + +bool CommitHistorySync::IsPacketValid(const Message *inMsg, uint16_t messageType) +{ + if ((inMsg == nullptr) || (inMsg->GetMessageId() != COMMIT_HISTORY_SYNC_MESSAGE)) { + return false; + } + if (messageType != inMsg->GetMessageType()) { + return false; + } + return true; +} + +int CommitHistorySync::Send(const DeviceID &deviceId, const Message *inMsg) +{ + SendConfig conf = {false, false, SEND_TIME_OUT, {}}; + int errCode = communicateHandle_->SendMessage(deviceId, inMsg, conf); + if (errCode != E_OK) { + LOGE("CommitHistorySync::Send ERR! err = %d", errCode); + } + return errCode; +} + +int CommitHistorySync::GetDeviceLatestCommit(std::map &commitMap) +{ + std::map readCommitMap; + int errCode = storagePtr_->GetDeviceLatestCommit(readCommitMap); + if (errCode != E_OK) { + return errCode; + } + + std::string localDevice; + errCode = GetLocalDeviceInfo(localDevice); + LOGD("GetLocalDeviceInfo : %s{private}, errCode = %d", localDevice.c_str(), errCode); + if (errCode != E_OK) { + return errCode; + } + + for (auto &item : readCommitMap) { + errCode = storagePtr_->TransferSyncCommitDevInfo(item.second, localDevice, false); + if (errCode != E_OK) { + break; + } + commitMap.insert(std::make_pair(item.second.deviceInfo, item.second)); + } + + return errCode; +} + +int CommitHistorySync::GetCommitTree(const std::map &commitMap, + std::vector &commits) +{ + std::map newCommitMap; + + std::string localDevice; + int errCode = GetLocalDeviceInfo(localDevice); + LOGD("GetLocalDeviceInfo : %s{private}, errCode = %d", localDevice.c_str(), errCode); + if (errCode != E_OK) { + return errCode; + } + + for (const auto &item : commitMap) { + MultiVerCommitNode commitNode = item.second; + errCode = storagePtr_->TransferSyncCommitDevInfo(commitNode, localDevice, true); + if (errCode != E_OK) { + return errCode; + } + newCommitMap.insert(std::make_pair(commitNode.deviceInfo, commitNode)); + } + + errCode = storagePtr_->GetCommitTree(newCommitMap, commits); + if (errCode != E_OK) { + return errCode; + } + for (auto &commit : commits) { + errCode = storagePtr_->TransferSyncCommitDevInfo(commit, localDevice, false); + if (errCode != E_OK) { + break; + } + } + return errCode; +} + +int CommitHistorySync::SendRequestPacket(const MultiVerSyncTaskContext *context, + std::map &commitMap) +{ + CommitHistorySyncRequestPacket *packet = new (std::nothrow) CommitHistorySyncRequestPacket(); + if (packet == nullptr) { + LOGE("CommitHistorySync::SendRequestPacket : new packet error"); + return -E_OUT_OF_MEMORY; + } + packet->SetCommitMap(commitMap); + packet->SetVersion(SOFTWARE_VERSION_CURRENT); + Message *message = new (std::nothrow) Message(COMMIT_HISTORY_SYNC_MESSAGE); + if (message == nullptr) { + LOGE("CommitHistorySync::SendRequestPacket : new message error"); + delete packet; + packet = nullptr; + return -E_OUT_OF_MEMORY; + } + message->SetMessageType(TYPE_REQUEST); + message->SetTarget(context->GetDeviceId()); + int errCode = message->SetExternalObject(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + delete message; + message = nullptr; + LOGE("CommitHistorySync::SendRequestPacket : SetExternalObject failed errCode:%d", errCode); + return errCode; + } + message->SetSessionId(context->GetRequestSessionId()); + message->SetSequenceId(context->GetSequenceId()); + + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_COMMIT_SEND_REQUEST_TO_ACK_RECV); + } + errCode = Send(message->GetTarget(), message); + if (errCode != E_OK) { + LOGE("CommitHistorySync::SendRequestPacket : Send failed errCode:%d", errCode); + delete message; + message = nullptr; + } + return errCode; +} + +int CommitHistorySync::SendAckPacket(const MultiVerSyncTaskContext *context, + std::vector &commits, int ackCode, const Message *message) +{ + if (message == nullptr) { + LOGE("CommitHistorySync::SendAckPacket : message is nullptr"); + return -E_INVALID_ARGS; + } + CommitHistorySyncAckPacket *packet = new (std::nothrow) CommitHistorySyncAckPacket(); + if (packet == nullptr) { + LOGE("CommitHistorySync::SendAckPacket : packet is nullptr"); + return -E_OUT_OF_MEMORY; + } + Message *ackMessage = new (std::nothrow) Message(COMMIT_HISTORY_SYNC_MESSAGE); + if (ackMessage == nullptr) { + LOGE("CommitHistorySync::SendAckPacket : new message error"); + delete packet; + packet = nullptr; + return -E_OUT_OF_MEMORY; + } + + packet->SetData(commits); + packet->SetErrorCode(static_cast(ackCode)); + packet->SetVersion(SOFTWARE_VERSION_CURRENT); + ackMessage->SetMessageType(TYPE_RESPONSE); + ackMessage->SetTarget(context->GetDeviceId()); + int errCode = ackMessage->SetExternalObject(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + delete ackMessage; + ackMessage = nullptr; + LOGE("CommitHistorySync::SendAckPacket : SetExternalObject failed errCode:%d", errCode); + return errCode; + } + ackMessage->SetSequenceId(message->GetSequenceId()); + ackMessage->SetSessionId(message->GetSessionId()); + errCode = Send(ackMessage->GetTarget(), ackMessage); + if (errCode != E_OK) { + LOGE("CommitHistorySync::SendAckPacket : Send failed errCode:%d", errCode); + delete ackMessage; + ackMessage = nullptr; + } + return errCode; +} + +int CommitHistorySync::GetLocalDeviceInfo(std::string &deviceInfo) +{ + return communicateHandle_->GetLocalIdentity(deviceInfo); +} + +int CommitHistorySync::RunPermissionCheck(const std::string &deviceId) const +{ + std::string appId = storagePtr_->GetDbProperties().GetStringProp(KvDBProperties::APP_ID, ""); + std::string userId = storagePtr_->GetDbProperties().GetStringProp(KvDBProperties::USER_ID, ""); + std::string storeId = storagePtr_->GetDbProperties().GetStringProp(KvDBProperties::STORE_ID, ""); + uint8_t flag = CHECK_FLAG_SEND; + int errCode = RuntimeContext::GetInstance()->RunPermissionCheck(userId, appId, storeId, deviceId, flag); + if (errCode != E_OK) { + LOGE("[CommitHistorySync] RunPermissionCheck not pass errCode:%d, flag:%d", errCode, flag); + return -E_NOT_PERMIT; + } + return errCode; +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/commit_history_sync.h b/mock/distributeddb/syncer/src/commit_history_sync.h new file mode 100644 index 00000000..5c7869fe --- /dev/null +++ b/mock/distributeddb/syncer/src/commit_history_sync.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 COMMIT_HISTORY_SYNC_H +#define COMMIT_HISTORY_SYNC_H + +#ifndef OMIT_MULTI_VER +#include +#include + +#include "icommunicator.h" +#include "multi_ver_kvdb_sync_interface.h" +#include "multi_ver_sync_task_context.h" +#include "sync_types.h" +#include "version.h" + +namespace DistributedDB { +class CommitHistorySyncRequestPacket { +public: + CommitHistorySyncRequestPacket() {}; + ~CommitHistorySyncRequestPacket() {}; + + uint32_t CalculateLen() const; + + void SetCommitMap(std::map &inMap); + + void GetCommitMap(std::map &outMap) const; + + void SetVersion(uint32_t version); + + uint32_t GetVersion() const; + + void SetReserved(std::vector &reserved); + + std::vector GetReserved() const; + +private: + std::map commitMap_; + uint32_t version_ = SOFTWARE_VERSION_CURRENT; + std::vector reserved_; +}; + +class CommitHistorySyncAckPacket { +public: + CommitHistorySyncAckPacket() : errorCode_(0) {}; + ~CommitHistorySyncAckPacket() {}; + + uint32_t CalculateLen() const; + + void SetData(std::vector &inData); + + void GetData(std::vector &outData) const; + + void SetErrorCode(int32_t errCode); + + void GetErrorCode(int32_t &errCode) const; + + void SetVersion(uint32_t version); + + uint32_t GetVersion() const; + + void SetReserved(std::vector &reserved); + + std::vector GetReserved() const; + +private: + int32_t errorCode_; + uint32_t version_ = SOFTWARE_VERSION_CURRENT; + std::vector commits_; + std::vector reserved_; +}; + +class CommitHistorySync { +public: + CommitHistorySync() : storagePtr_(nullptr), communicateHandle_(nullptr) {}; + ~CommitHistorySync(); + DISABLE_COPY_ASSIGN_MOVE(CommitHistorySync); + + static int RegisterTransformFunc(); + + int Initialize(MultiVerKvDBSyncInterface *storagePtr, ICommunicator *communicateHandle); + + static int Serialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int DeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static uint32_t CalculateLen(const Message *inMsg); + + void TimeOutCallback(MultiVerSyncTaskContext *context, const Message *message) const; + + int SyncStart(MultiVerSyncTaskContext *context); + + int RequestRecvCallback(const MultiVerSyncTaskContext *context, const Message *message); + + int AckRecvCallback(MultiVerSyncTaskContext *context, const Message *message); + +private: + static int RequestPacketCalculateLen(const Message *inMsg, uint32_t &len); + + static int RequestPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int RequestPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static int AckPacketCalculateLen(const Message *inMsg, uint32_t &len); + + static int AckPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int AckPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static bool IsPacketValid(const Message *inMsg, uint16_t messageType); + + int Send(const DeviceID &deviceId, const Message *inMsg); + + int GetDeviceLatestCommit(std::map &); + + int GetCommitTree(const std::map &, std::vector &); + + int SendRequestPacket(const MultiVerSyncTaskContext *context, + std::map &commitMap); + + int SendAckPacket(const MultiVerSyncTaskContext *context, std::vector &commits, + int ackCode, const Message *message); + + int GetLocalDeviceInfo(std::string &deviceInfo); + + int RunPermissionCheck(const std::string &deviceId) const; + + MultiVerKvDBSyncInterface *storagePtr_; + ICommunicator *communicateHandle_; +}; +} // namespace DistributedDB + +#endif +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/communicator_proxy.cpp b/mock/distributeddb/syncer/src/communicator_proxy.cpp new file mode 100644 index 00000000..6e1f0b29 --- /dev/null +++ b/mock/distributeddb/syncer/src/communicator_proxy.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "communicator_proxy.h" +#include "db_constant.h" +#include "db_common.h" +#include "db_dump_helper.h" +#include "log_print.h" + +namespace DistributedDB { +CommunicatorProxy::CommunicatorProxy() : mainComm_(nullptr) +{ +} + +CommunicatorProxy::~CommunicatorProxy() +{ + if (mainComm_ != nullptr) { + RefObject::DecObjRef(mainComm_); + } + mainComm_ = nullptr; + + std::lock_guard lock(devCommMapLock_); + for (const auto &iter : devCommMap_) { + RefObject::DecObjRef(devCommMap_[iter.first].second); + } + devCommMap_.clear(); +} + +int CommunicatorProxy::RegOnMessageCallback(const OnMessageCallback &onMessage, const Finalizer &inOper) +{ + if (mainComm_ != nullptr) { + (void) mainComm_->RegOnMessageCallback(onMessage, inOper); + } + + std::lock_guard lock(devCommMapLock_); + for (const auto &iter : devCommMap_) { + (void) devCommMap_[iter.first].second->RegOnMessageCallback(onMessage, inOper); + } + return E_OK; +} + +int CommunicatorProxy::RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) +{ + if (mainComm_ != nullptr) { + (void) mainComm_->RegOnConnectCallback(onConnect, inOper); + } + + std::lock_guard lock(devCommMapLock_); + for (const auto &iter : devCommMap_) { + (void) devCommMap_[iter.first].second->RegOnConnectCallback(onConnect, inOper); + } + + return E_OK; +} + +int CommunicatorProxy::RegOnSendableCallback(const std::function &onSendable, const Finalizer &inOper) +{ + if (mainComm_ != nullptr) { + (void) mainComm_->RegOnSendableCallback(onSendable, inOper); + } + + std::lock_guard lock(devCommMapLock_); + for (const auto &iter : devCommMap_) { + (void) devCommMap_[iter.first].second->RegOnSendableCallback(onSendable, inOper); + } + + return E_OK; +} + +void CommunicatorProxy::Activate() +{ + if (mainComm_ != nullptr) { + mainComm_->Activate(); + } + + // use temp map to avoid active in lock + std::map tempMap; + { + std::lock_guard lock(devCommMapLock_); + for (const auto &iter : devCommMap_) { + tempMap[iter.first] = devCommMap_[iter.first].second; + RefObject::IncObjRef(devCommMap_[iter.first].second); + } + } + + for (const auto &iter : tempMap) { + tempMap[iter.first]->Activate(); + RefObject::DecObjRef(tempMap[iter.first]); + } +} + +uint32_t CommunicatorProxy::GetCommunicatorMtuSize() const +{ + if (mainComm_ == nullptr) { + return DBConstant::MIN_MTU_SIZE; + } + return mainComm_->GetCommunicatorMtuSize(); +} + +uint32_t CommunicatorProxy::GetCommunicatorMtuSize(const std::string &target) const +{ + ICommunicator *targetCommunicator = nullptr; + { + std::lock_guard lock(devCommMapLock_); + if (devCommMap_.count(target) != 0) { + targetCommunicator = devCommMap_.at(target).second; + RefObject::IncObjRef(targetCommunicator); + } + } + if (targetCommunicator != nullptr) { + uint32_t mtuSize = targetCommunicator->GetCommunicatorMtuSize(target); + RefObject::DecObjRef(targetCommunicator); + return mtuSize; + } + + if (mainComm_ != nullptr) { + return mainComm_->GetCommunicatorMtuSize(target); + } + + return DBConstant::MIN_MTU_SIZE; +} + +uint32_t CommunicatorProxy::GetTimeout() const +{ + if (mainComm_ == nullptr) { + return DBConstant::MIN_TIMEOUT; + } + return mainComm_->GetTimeout(); +} + +uint32_t CommunicatorProxy::GetTimeout(const std::string &target) const +{ + ICommunicator *targetCommunicator = nullptr; + { + std::lock_guard lock(devCommMapLock_); + if (devCommMap_.count(target) != 0) { + targetCommunicator = devCommMap_.at(target).second; + RefObject::IncObjRef(targetCommunicator); + } + } + if (targetCommunicator != nullptr) { + uint32_t timeout = targetCommunicator->GetTimeout(target); + RefObject::DecObjRef(targetCommunicator); + return timeout; + } + + if (mainComm_ != nullptr) { + return mainComm_->GetTimeout(target); + } + + return DBConstant::MIN_TIMEOUT; +} + +bool CommunicatorProxy::IsDeviceOnline(const std::string &device) const +{ + return mainComm_->IsDeviceOnline(device); +} + +int CommunicatorProxy::GetLocalIdentity(std::string &outTarget) const +{ + return mainComm_->GetLocalIdentity(outTarget); +} + +int CommunicatorProxy::GetRemoteCommunicatorVersion(const std::string &target, uint16_t &outVersion) const +{ + ICommunicator *targetCommunicator = nullptr; + { + std::lock_guard lock(devCommMapLock_); + if (devCommMap_.count(target) != 0) { + targetCommunicator = devCommMap_.at(target).second; + RefObject::IncObjRef(targetCommunicator); + } + } + if (targetCommunicator != nullptr) { + int errCode = targetCommunicator->GetRemoteCommunicatorVersion(target, outVersion); + RefObject::DecObjRef(targetCommunicator); + return errCode; + } + + if (mainComm_ != nullptr) { + return mainComm_->GetRemoteCommunicatorVersion(target, outVersion); + } + + return -E_NOT_INIT; +} + +int CommunicatorProxy::SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config) +{ + return SendMessage(dstTarget, inMsg, config, nullptr); +} + +int CommunicatorProxy::SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config, + const OnSendEnd &onEnd) +{ + ICommunicator *targetCommunicator = nullptr; + { + std::lock_guard lock(devCommMapLock_); + if (devCommMap_.count(dstTarget) != 0) { + targetCommunicator = devCommMap_[dstTarget].second; + RefObject::IncObjRef(targetCommunicator); + } + } + if (targetCommunicator != nullptr) { + LOGD("[CommProxy] use equal label to send data"); + int errCode = targetCommunicator->SendMessage(dstTarget, inMsg, config, onEnd); + RefObject::DecObjRef(targetCommunicator); + return errCode; + } + + if (mainComm_ != nullptr) { + return mainComm_->SendMessage(dstTarget, inMsg, config, onEnd); + } + + return -E_NOT_INIT; +} + +void CommunicatorProxy::SetMainCommunicator(ICommunicator *communicator) +{ + mainComm_ = communicator; + RefObject::IncObjRef(mainComm_); +} + +void CommunicatorProxy::SetEqualCommunicator(ICommunicator *communicator, const std::string &identifier, + const std::vector &targets) +{ + std::lock_guard lock(devCommMapLock_); + // Clear offline target + for (auto dev = devCommMap_.begin(); dev != devCommMap_.end();) { + if (identifier != dev->second.first) { + dev++; + continue; + } + auto iter = std::find_if(targets.begin(), targets.end(), + [&dev](const std::string &target) { + return target == dev->first; + }); + if (iter == targets.end()) { + RefObject::DecObjRef(devCommMap_[dev->first].second); + dev = devCommMap_.erase(dev); + continue; + } + dev++; + } + + // Add new online target + for (const auto &target : targets) { + if (devCommMap_.count(target) != 0) { + // change the identifier and dev relation + RefObject::DecObjRef(devCommMap_[target].second); + } + RefObject::IncObjRef(communicator); + devCommMap_[target] = {identifier, communicator}; + } +} + +void CommunicatorProxy::Dump(int fd) +{ + std::lock_guard lock(devCommMapLock_); + for (const auto &[target, communicator] : devCommMap_) { + std::string label = DBCommon::TransferStringToHex(communicator.first); + DBDumpHelper::Dump(fd, "\t\ttarget = %s, label = %s\n", target.c_str(), label.c_str()); + } +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/communicator_proxy.h b/mock/distributeddb/syncer/src/communicator_proxy.h new file mode 100644 index 00000000..47f7fc19 --- /dev/null +++ b/mock/distributeddb/syncer/src/communicator_proxy.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 COMMUNICATOR_PROXY_H +#define COMMUNICATOR_PROXY_H + +#include +#include +#include +#include +#include +#include +#include "icommunicator.h" +#include "message.h" + +namespace DistributedDB { +class CommunicatorProxy : public ICommunicator { +public: + CommunicatorProxy(); + ~CommunicatorProxy(); + + int RegOnMessageCallback(const OnMessageCallback &onMessage, const Finalizer &inOper) override; + int RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) override; + int RegOnSendableCallback(const std::function &onSendable, const Finalizer &inOper) override; + void Activate() override; + uint32_t GetCommunicatorMtuSize() const override; + uint32_t GetCommunicatorMtuSize(const std::string &target) const override; + uint32_t GetTimeout() const override; + uint32_t GetTimeout(const std::string &target) const override; + bool IsDeviceOnline(const std::string &device) const override; + int GetLocalIdentity(std::string &outTarget) const override; + int GetRemoteCommunicatorVersion(const std::string &target, uint16_t &outVersion) const override; + int SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config) override; + int SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config, + const OnSendEnd &onEnd) override; + + // Set an Main communicator for this database, used userid & appId & storeId + void SetMainCommunicator(ICommunicator *communicator); + + // Set an equal communicator for this database, After this called, send msg to the target will use this communicator + void SetEqualCommunicator(ICommunicator *communicator, const std::string &identifier, + const std::vector &targets); + + void Dump(int fd); + +private: + ICommunicator *mainComm_; + mutable std::mutex devCommMapLock_; + // key: device value: + std::map> devCommMap_; +}; +} // namespace DistributedDB +#endif // COMMUNICATOR_PROXY_H diff --git a/mock/distributeddb/syncer/src/db_ability.cpp b/mock/distributeddb/syncer/src/db_ability.cpp new file mode 100644 index 00000000..54631a3c --- /dev/null +++ b/mock/distributeddb/syncer/src/db_ability.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_ability.h" + +#include +#include "db_errno.h" +#include "types_export.h" + +namespace DistributedDB { +DbAbility::DbAbility() +{ + for (const auto &item : SyncConfig::ABILITYBITS) { + dbAbilityItemSet_.insert(item); + } + dbAbility_.resize(SyncConfig::ABILITYBITS.back().first + SyncConfig::ABILITYBITS.back().second); +} + +DbAbility::DbAbility(const DbAbility &other) +{ + if (&other != this) { + dbAbility_ = other.dbAbility_; + dbAbilityItemSet_ = other.dbAbilityItemSet_; + } +} + +DbAbility& DbAbility::operator=(const DbAbility &other) +{ + if (&other != this) { + dbAbility_ = other.dbAbility_; + dbAbilityItemSet_ = other.dbAbilityItemSet_; + } + return *this; +} + +bool DbAbility::operator==(const DbAbility &other) const +{ + return (dbAbility_ == other.dbAbility_) && (dbAbilityItemSet_ == other.dbAbilityItemSet_); +} + +int DbAbility::Serialize(Parcel &parcel, const DbAbility &curAbility) +{ + uint32_t div = curAbility.GetAbilityBitsLen() / SERIALIZE_BIT_SIZE; + uint32_t buffLen = (curAbility.GetAbilityBitsLen() % SERIALIZE_BIT_SIZE) ? div + 1 : div; + std::vector dstBuf(buffLen, 0); + uint32_t buffOffset = 0; + uint32_t innerBuffOffset = 0; + const std::vector &abilityBuff = curAbility.GetDbAbilityBuff(); + for (uint32_t pos = 0; pos < curAbility.GetAbilityBitsLen(); pos++, innerBuffOffset++) { + if (innerBuffOffset >= SERIALIZE_BIT_SIZE) { + innerBuffOffset = 0; + buffOffset++; + } + uint64_t value = static_cast(abilityBuff[pos]) << innerBuffOffset; + dstBuf[buffOffset] = dstBuf[buffOffset] | value; + } + int errCode = parcel.WriteVector(dstBuf); + if (errCode != E_OK) { + return errCode; + } + return E_OK; +} + +int DbAbility::DeSerialize(Parcel &parcel, DbAbility &curAbility) +{ + if (!parcel.IsContinueRead()) { + return E_OK; + } + std::vector dstBuf; + parcel.ReadVector(dstBuf); + if (parcel.IsError()) { + LOGE("[DbAbility][DeSerialize] deserialize failed."); + return -E_LENGTH_ERROR; + } + if (dstBuf.size() == 0) { + LOGE("[DbAbility][DeSerialize] buf length get failed."); + return -E_LENGTH_ERROR; + } + std::vector targetBuff(SyncConfig::ABILITYBITS.back().first + SyncConfig::ABILITYBITS.back().second); + uint32_t buffOffset = 0; + uint32_t innerBuffOffset = 0; + for (uint32_t pos = 0; pos < targetBuff.size() && pos < SERIALIZE_BIT_SIZE * dstBuf.size(); pos++) { + if (innerBuffOffset >= SERIALIZE_BIT_SIZE) { + innerBuffOffset = 0; + buffOffset++; + } + targetBuff[pos] = (dstBuf[buffOffset] >> innerBuffOffset) & 0x1; + innerBuffOffset++; + } + curAbility.SetDbAbilityBuff(targetBuff); + return E_OK; +} + +uint32_t DbAbility::CalculateLen(const DbAbility &curAbility) +{ + uint32_t div = curAbility.GetAbilityBitsLen() / SERIALIZE_BIT_SIZE; + uint32_t buffLen = (curAbility.GetAbilityBitsLen() % SERIALIZE_BIT_SIZE) ? div + 1 : div; + return Parcel::GetVectorLen(std::vector(buffLen, 0)); +} + +void DbAbility::SetDbAbilityBuff(std::vector &buff) +{ + dbAbility_ = buff; +} + +const std::vector &DbAbility::GetDbAbilityBuff() const +{ + return dbAbility_; +} + +uint32_t DbAbility::GetAbilityBitsLen() const +{ + return dbAbility_.size(); +} + +uint8_t DbAbility::GetAbilityItem(const AbilityItem &abilityType) const +{ + uint8_t data = 0; + auto iter = dbAbilityItemSet_.find(abilityType); + if (iter != dbAbilityItemSet_.end()) { + if ((iter->first + iter->second) > dbAbility_.size()) { + LOGE("[DbAbility] abilityType is error, start=%" PRIu32 ", use_bit=%" PRIu32 ", totalLen=%zu", + iter->first, iter->second, dbAbility_.size()); + return 0; + } + uint32_t skip = 0; + // dbAbility_ bit[0..len] : low-->high, skip range 0..7 + for (uint32_t pos = iter->first; pos < (iter->first + iter->second); pos++, skip++) { + if (dbAbility_[pos]) { + data += (static_cast(dbAbility_[pos])) << skip; + } + } + } + return data; +} + +int DbAbility::SetAbilityItem(const AbilityItem &abilityType, uint8_t data) +{ + auto iter = dbAbilityItemSet_.find(abilityType); + if (iter != dbAbilityItemSet_.end()) { + if (data >= pow(2, iter->second)) { // 2: means binary + LOGE("[DbAbility] value is invalid, data=%d, use_bit=%d", data, iter->second); + return -E_INTERNAL_ERROR; + } + if ((iter->first + iter->second) > dbAbility_.size()) { + dbAbility_.resize(iter->first + iter->second); + } + int pos = iter->first; + while (data) { + dbAbility_[pos] = data % 2; // 2: means binary + data = (data >> 1); + pos++; + } + } + return E_OK; +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/db_ability.h b/mock/distributeddb/syncer/src/db_ability.h new file mode 100644 index 00000000..a1bb45eb --- /dev/null +++ b/mock/distributeddb/syncer/src/db_ability.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DB_ABILITY_H +#define DB_ABILITY_H + +#include +#include +#include +#include "macro_utils.h" +#include "parcel.h" +#include "sync_config.h" + +namespace DistributedDB { +class DbAbility { +public: + DbAbility(); + DbAbility(const DbAbility &other); + DbAbility& operator=(const DbAbility &other); + ~DbAbility() = default; + + bool operator==(const DbAbility &other) const; + // translate dbAbility_ to std::vector + static int Serialize(Parcel &parcel, const DbAbility &curAbility); + + static int DeSerialize(Parcel &parcel, DbAbility &curAbility); + + static uint32_t CalculateLen(const DbAbility &curAbility); + + void SetDbAbilityBuff(std::vector &buff); + + const std::vector &GetDbAbilityBuff() const; + + uint32_t GetAbilityBitsLen() const; + + uint8_t GetAbilityItem(const AbilityItem &abilityType) const; + + int SetAbilityItem(const AbilityItem &abilityType, uint8_t data); +private: + constexpr static int SERIALIZE_BIT_SIZE = 64; // uint64_t bit size + + std::vector dbAbility_; + std::set dbAbilityItemSet_; +}; +} // namespace DistributedDB + +#endif // DB_ABILITY_H \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/device_manager.cpp b/mock/distributeddb/syncer/src/device_manager.cpp new file mode 100644 index 00000000..b773d69a --- /dev/null +++ b/mock/distributeddb/syncer/src/device_manager.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "device_manager.h" + +#include + +#include "message_transform.h" +#include "parcel.h" +#include "db_errno.h" +#include "message.h" +#include "log_print.h" +#include "performance_analysis.h" +#include "sync_types.h" + +namespace DistributedDB { +DeviceManager::DeviceManager() : communicator_(nullptr) +{ +} + +DeviceManager::~DeviceManager() +{ + if (communicator_ != nullptr) { + RefObject::DecObjRef(communicator_); + communicator_ = nullptr; + } +} + +uint32_t DeviceManager::CalculateLen() +{ + return Parcel::GetUInt64Len(); +} + +int DeviceManager::RegisterTransformFunc() +{ + TransformFunc func; + func.computeFunc = [](const Message *msg) { + (void) msg; + return DeviceManager::CalculateLen(); + }; + // LocalDataChanged has no dataPct + func.serializeFunc = [](uint8_t *buffer, uint32_t length, const Message *inMsg) { + (void) buffer; + (void) length; + (void) inMsg; + return E_OK; + }; + func.deserializeFunc = [](const uint8_t *buffer, uint32_t length, Message *inMsg) { + (void) buffer; + (void) length; + (void) inMsg; + return E_OK; + }; + return MessageTransform::RegTransformFunction(LOCAL_DATA_CHANGED, func); +} + +// Initialize the DeviceManager +int DeviceManager::Initialize(ICommunicator *communicator, const std::function &onlineCallback, + const std::function &offlineCallback) +{ + if (communicator == nullptr) { + return -E_INVALID_ARGS; + } + RefObject::IncObjRef(communicator); + communicator_ = communicator; + RegDeviceOnLineCallBack(onlineCallback); + RegDeviceOffLineCallBack(offlineCallback); + return E_OK; +} + +void DeviceManager::RegDeviceOnLineCallBack(const std::function &callback) +{ + onlineCallback_ = callback; +} + +void DeviceManager::RegDeviceOffLineCallBack(const std::function &callback) +{ + offlineCallback_ = callback; +} + +void DeviceManager::OnDeviceConnectCallback(const std::string &targetDev, bool isConnect) +{ + LOGD("[DeviceManager] DeviceConnectCallback dev = %s{private}, status = %d", targetDev.c_str(), isConnect); + if (targetDev.empty()) { + LOGE("[DeviceManager] DeviceConnectCallback invalid device!"); + } + if (isConnect) { + { + std::lock_guard lockOnline(devicesLock_); + devices_.insert(targetDev); + } + if (onlineCallback_) { + onlineCallback_(targetDev); + LOGD("[DeviceManager] DeviceConnectCallback call online callback"); + } + } else { + { + std::lock_guard lockOffline(devicesLock_); + devices_.erase(targetDev); + } + if (offlineCallback_) { + offlineCallback_(targetDev); + LOGD("[DeviceManager] DeviceConnectCallback call offline callback"); + } + } +} + +void DeviceManager::GetOnlineDevices(std::vector &devices) const +{ + std::lock_guard lock(devicesLock_); + devices.assign(devices_.begin(), devices_.end()); +} + +int DeviceManager::SendBroadCast(uint32_t msgId) +{ + if (msgId == LOCAL_DATA_CHANGED) { + return SendLocalDataChanged(); + } + LOGE("[DeviceManager] invalid BroadCast msgId:%u", msgId); + return -E_INVALID_ARGS; +} + +int DeviceManager::SendLocalDataChanged() +{ + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_SEND_LOCAL_DATA_CHANGED_TO_COMMIT_REQUEST_RECV); + } + std::vector copyDevices; + GetOnlineDevices(copyDevices); + if (copyDevices.empty()) { + LOGI("[DeviceManager] no device online to SendLocalDataChanged!"); + } + for (const auto &deviceId : copyDevices) { + Message *msg = new (std::nothrow) Message(); + if (msg == nullptr) { + LOGE("[DeviceManager] Message alloc failed when SendBroadCast!"); + return -E_OUT_OF_MEMORY; + } + msg->SetMessageId(LOCAL_DATA_CHANGED); + msg->SetTarget(deviceId); + SendConfig conf = {false, false, SEND_TIME_OUT, {}}; + int errCode = communicator_->SendMessage(deviceId, msg, conf); + if (errCode != E_OK) { + LOGE("[DeviceManager] SendLocalDataChanged to dev %s{private} failed. err %d", + deviceId.c_str(), errCode); + delete msg; + msg = nullptr; + } + } + return E_OK; +} + +bool DeviceManager::IsDeviceOnline(const std::string &deviceId) const +{ + std::lock_guard lock(devicesLock_); + auto iter = std::find(devices_.begin(), devices_.end(), deviceId); + return (iter != devices_.end()); +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/device_manager.h b/mock/distributeddb/syncer/src/device_manager.h new file mode 100644 index 00000000..5b638b1e --- /dev/null +++ b/mock/distributeddb/syncer/src/device_manager.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DEVICE_MANAGER_H +#define DEVICE_MANAGER_H + +#include +#include + +#include "icommunicator.h" + +namespace DistributedDB { +class DeviceManager final { +public: + DeviceManager(); + ~DeviceManager(); + + DISABLE_COPY_ASSIGN_MOVE(DeviceManager); + + static int RegisterTransformFunc(); + + // Calculate the length of message. + static uint32_t CalculateLen(); + + // Initialize the DeviceManager. + int Initialize(ICommunicator *communicator, const std::function &onlineCallback, + const std::function &offlineCallback); + + // Set The Device online Callback. + void RegDeviceOnLineCallBack(const std::function &callback); + + // Set The Device offline Callback. + void RegDeviceOffLineCallBack(const std::function &callback); + + // The Device connect message callback, registered to the ICommunicator + void OnDeviceConnectCallback(const std::string &targetDev, bool isConnect); + + // Get The online devices list. + void GetOnlineDevices(std::vector &devices) const; + + // Send a BroadCast to all online device. + int SendBroadCast(uint32_t msgId); + + // Determine if the device is online. + bool IsDeviceOnline(const std::string &deviceId) const; + +private: + + // Send a local data changed broadcast. + int SendLocalDataChanged(); + + std::set devices_; + std::function onlineCallback_; + std::function offlineCallback_; + ICommunicator *communicator_; + mutable std::mutex devicesLock_; +}; +} // namespace DistributedDB + +#endif // DEVICE_MANAGER_H diff --git a/mock/distributeddb/syncer/src/generic_syncer.cpp b/mock/distributeddb/syncer/src/generic_syncer.cpp new file mode 100644 index 00000000..a0d817a3 --- /dev/null +++ b/mock/distributeddb/syncer/src/generic_syncer.cpp @@ -0,0 +1,818 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "generic_syncer.h" + +#include "db_common.h" +#include "db_errno.h" +#include "log_print.h" +#include "ref_object.h" +#include "sqlite_single_ver_natural_store.h" +#include "time_sync.h" +#include "single_ver_data_sync.h" +#ifndef OMIT_MULTI_VER +#include "commit_history_sync.h" +#include "multi_ver_data_sync.h" +#include "value_slice_sync.h" +#endif +#include "device_manager.h" +#include "db_constant.h" +#include "ability_sync.h" +#include "single_ver_serialize_manager.h" + +namespace DistributedDB { +const int GenericSyncer::MIN_VALID_SYNC_ID = 1; +std::mutex GenericSyncer::moduleInitLock_; +int GenericSyncer::currentSyncId_ = 0; +std::mutex GenericSyncer::syncIdLock_; +GenericSyncer::GenericSyncer() + : syncEngine_(nullptr), + syncInterface_(nullptr), + timeHelper_(nullptr), + metadata_(nullptr), + initialized_(false), + queuedManualSyncSize_(0), + queuedManualSyncLimit_(DBConstant::QUEUED_SYNC_LIMIT_DEFAULT), + manualSyncEnable_(true), + closing_(false), + engineFinalize_(false) +{ +} + +GenericSyncer::~GenericSyncer() +{ + LOGD("[GenericSyncer] ~GenericSyncer!"); + if (syncEngine_ != nullptr) { + syncEngine_->OnKill([this]() { this->syncEngine_->Close(); }); + RefObject::KillAndDecObjRef(syncEngine_); + // waiting all thread exist + std::mutex engineMutex; + std::unique_lock cvLock(engineMutex); + bool engineFinalize = engineFinalizeCv_.wait_for(cvLock, std::chrono::milliseconds(DBConstant::MIN_TIMEOUT), + [this]() { return engineFinalize_; }); + if (!engineFinalize) { + LOGW("syncer finalize before engine finalize!"); + } + syncEngine_ = nullptr; + } + timeHelper_ = nullptr; + metadata_ = nullptr; + syncInterface_ = nullptr; +} + +int GenericSyncer::Initialize(ISyncInterface *syncInterface, bool isNeedActive) +{ + if (syncInterface == nullptr) { + LOGE("[Syncer] Init failed, the syncInterface is null!"); + return -E_INVALID_ARGS; + } + + { + std::lock_guard lock(syncerLock_); + if (initialized_) { + return E_OK; + } + if (closing_) { + LOGE("[Syncer] Syncer is closing, return!"); + return -E_BUSY; + } + std::vector label = syncInterface->GetIdentifier(); + label.resize(3); // only show 3 Bytes enough + label_ = DBCommon::VectorToHexString(label); + + // As metadata_ will be used in EraseDeviceWaterMark, it should not be clear even if engine init failed. + // It will be clear in destructor. + int errCodeMetadata = InitMetaData(syncInterface); + + // As timeHelper_ will be used in GetTimestamp, it should not be clear even if engine init failed. + // It will be clear in destructor. + int errCodeTimeHelper = InitTimeHelper(syncInterface); + if (errCodeMetadata != E_OK || errCodeTimeHelper != E_OK) { + return -E_INTERNAL_ERROR; + } + int errCode = CheckSyncActive(syncInterface, isNeedActive); + if (errCode != E_OK) { + return errCode; + } + + if (!RuntimeContext::GetInstance()->IsCommunicatorAggregatorValid()) { + LOGW("[Syncer] Communicator component not ready!"); + return -E_NOT_INIT; + } + + errCode = SyncModuleInit(); + if (errCode != E_OK) { + LOGE("[Syncer] Sync ModuleInit ERR!"); + return -E_INTERNAL_ERROR; + } + + errCode = InitSyncEngine(syncInterface); + if (errCode != E_OK) { + return errCode; + } + syncEngine_->SetEqualIdentifier(); + initialized_ = true; + } + + // RegConnectCallback may start an auto sync, this function can not in syncerLock_ + syncEngine_->RegConnectCallback(); + return E_OK; +} + +int GenericSyncer::Close(bool isClosedOperation) +{ + { + std::lock_guard lock(syncerLock_); + if (!initialized_) { + LOGW("[Syncer] Syncer[%s] don't need to close, because it has no been init", label_.c_str()); + timeHelper_ = nullptr; + metadata_ = nullptr; + return -E_NOT_INIT; + } + initialized_ = false; + if (closing_) { + LOGE("[Syncer] Syncer is closing, return!"); + return -E_BUSY; + } + closing_ = true; + } + ClearSyncOperations(isClosedOperation); + if (syncEngine_ != nullptr) { + syncEngine_->Close(); + LOGD("[Syncer] Close SyncEngine!"); + std::lock_guard lock(syncerLock_); + closing_ = false; + } + timeHelper_ = nullptr; + metadata_ = nullptr; + return E_OK; +} + +int GenericSyncer::Sync(const std::vector &devices, int mode, + const std::function &)> &onComplete, + const std::function &onFinalize, bool wait = false) +{ + SyncParma param; + param.devices = devices; + param.mode = mode; + param.onComplete = onComplete; + param.onFinalize = onFinalize; + param.wait = wait; + return Sync(param); +} + +int GenericSyncer::Sync(const InternalSyncParma ¶m) +{ + SyncParma syncParam; + syncParam.devices = param.devices; + syncParam.mode = param.mode; + syncParam.isQuerySync = param.isQuerySync; + syncParam.syncQuery = param.syncQuery; + return Sync(syncParam); +} + +int GenericSyncer::Sync(const SyncParma ¶m) +{ + return Sync(param, DBConstant::IGNORE_CONNECTION_ID); +} + +int GenericSyncer::Sync(const SyncParma ¶m, uint64_t connectionId) +{ + int errCode = SyncParamCheck(param); + if (errCode != E_OK) { + return errCode; + } + errCode = AddQueuedManualSyncSize(param.mode, param.wait); + if (errCode != E_OK) { + return errCode; + } + + uint32_t syncId = GenerateSyncId(); + errCode = PrepareSync(param, syncId, connectionId); + if (errCode != E_OK) { + LOGE("[Syncer] PrepareSync failed when sync called, err %d", errCode); + return errCode; + } + PerformanceAnalysis::GetInstance()->StepTimeRecordEnd(PT_TEST_RECORDS::RECORD_SYNC_TOTAL); + return E_OK; +} + +int GenericSyncer::PrepareSync(const SyncParma ¶m, uint32_t syncId, uint64_t connectionId) +{ + auto *operation = + new (std::nothrow) SyncOperation(syncId, param.devices, param.mode, param.onComplete, param.wait); + if (operation == nullptr) { + SubQueuedSyncSize(); + return -E_OUT_OF_MEMORY; + } + operation->SetIdentifier(syncInterface_->GetIdentifier()); + { + std::lock_guard autoLock(syncerLock_); + PerformanceAnalysis::GetInstance()->StepTimeRecordStart(PT_TEST_RECORDS::RECORD_SYNC_TOTAL); + InitSyncOperation(operation, param); + LOGI("[Syncer] GenerateSyncId %" PRIu32 ", mode = %d, wait = %d, label = %s, devices = %s", syncId, param.mode, + param.wait, label_.c_str(), GetSyncDevicesStr(param.devices).c_str()); + AddSyncOperation(operation); + PerformanceAnalysis::GetInstance()->StepTimeRecordEnd(PT_TEST_RECORDS::RECORD_SYNC_TOTAL); + } + if (!param.wait && connectionId != DBConstant::IGNORE_CONNECTION_ID) { + std::lock_guard lockGuard(syncIdLock_); + connectionIdMap_[connectionId].push_back(static_cast(syncId)); + syncIdMap_[static_cast(syncId)] = connectionId; + } + if (operation->CheckIsAllFinished()) { + operation->Finished(); + RefObject::KillAndDecObjRef(operation); + } else { + operation->WaitIfNeed(); + RefObject::DecObjRef(operation); + } + return E_OK; +} + +int GenericSyncer::RemoveSyncOperation(int syncId) +{ + SyncOperation *operation = nullptr; + std::unique_lock lock(operationMapLock_); + auto iter = syncOperationMap_.find(syncId); + if (iter != syncOperationMap_.end()) { + LOGD("[Syncer] RemoveSyncOperation id:%d.", syncId); + operation = iter->second; + syncOperationMap_.erase(syncId); + lock.unlock(); + if ((!operation->IsAutoSync()) && (!operation->IsBlockSync()) && (!operation->IsAutoControlCmd())) { + SubQueuedSyncSize(); + } + operation->NotifyIfNeed(); + RefObject::KillAndDecObjRef(operation); + operation = nullptr; + std::lock_guard lockGuard(syncIdLock_); + if (syncIdMap_.find(syncId) == syncIdMap_.end()) { + return E_OK; + } + uint64_t connectionId = syncIdMap_[syncId]; + if (connectionIdMap_.find(connectionId) != connectionIdMap_.end()) { + connectionIdMap_[connectionId].remove(syncId); + } + syncIdMap_.erase(syncId); + return E_OK; + } + return -E_INVALID_ARGS; +} + +int GenericSyncer::StopSync(uint64_t connectionId) +{ + std::list syncIdList; + { + std::lock_guard lockGuard(syncIdLock_); + if (connectionIdMap_.find(connectionId) == connectionIdMap_.end()) { + return E_OK; + } + syncIdList = connectionIdMap_[connectionId]; + connectionIdMap_.erase(connectionId); + } + for (auto syncId : syncIdList) { + RemoveSyncOperation(syncId); + } + return E_OK; +} + +uint64_t GenericSyncer::GetTimestamp() +{ + if (timeHelper_ == nullptr) { + return TimeHelper::GetSysCurrentTime(); + } + return timeHelper_->GetTime(); +} + +void GenericSyncer::QueryAutoSync(const InternalSyncParma ¶m) +{ + (void)param; +} + +void GenericSyncer::AddSyncOperation(SyncOperation *operation) +{ + if (operation == nullptr) { + return; + } + + LOGD("[Syncer] AddSyncOperation."); + syncEngine_->AddSyncOperation(operation); + + if (operation->CheckIsAllFinished()) { + return; + } + + std::lock_guard lock(operationMapLock_); + syncOperationMap_.insert(std::pair(operation->GetSyncId(), operation)); + // To make sure operation alive before WaitIfNeed out + RefObject::IncObjRef(operation); +} + +void GenericSyncer::SyncOperationKillCallbackInner(int syncId) +{ + if (syncEngine_ != nullptr) { + LOGI("[Syncer] Operation on kill id = %d", syncId); + syncEngine_->RemoveSyncOperation(syncId); + } +} + +void GenericSyncer::SyncOperationKillCallback(int syncId) +{ + SyncOperationKillCallbackInner(syncId); +} + +int GenericSyncer::InitMetaData(ISyncInterface *syncInterface) +{ + if (metadata_ != nullptr) { + return E_OK; + } + + metadata_ = std::make_shared(); + int errCode = metadata_->Initialize(syncInterface); + if (errCode != E_OK) { + LOGE("[Syncer] metadata Initializeate failed! err %d.", errCode); + metadata_ = nullptr; + } + return errCode; +} + +int GenericSyncer::InitTimeHelper(ISyncInterface *syncInterface) +{ + if (timeHelper_ != nullptr) { + return E_OK; + } + + timeHelper_ = std::make_shared(); + int errCode = timeHelper_->Initialize(syncInterface, metadata_); + if (errCode != E_OK) { + LOGE("[Syncer] TimeHelper init failed! err:%d.", errCode); + timeHelper_ = nullptr; + } + return errCode; +} + +int GenericSyncer::InitSyncEngine(ISyncInterface *syncInterface) +{ + if (syncEngine_ != nullptr && syncEngine_->IsEngineActive()) { + LOGI("[Syncer] syncEngine is active"); + return E_OK; + } + int errCode = BuildSyncEngine(); + if (errCode != E_OK) { + return errCode; + } + const std::function onlineFunc = std::bind(&GenericSyncer::RemoteDataChanged, + this, std::placeholders::_1); + const std::function offlineFunc = std::bind(&GenericSyncer::RemoteDeviceOffline, + this, std::placeholders::_1); + const std::function queryAutoSyncFunc = + std::bind(&GenericSyncer::QueryAutoSync, this, std::placeholders::_1); + errCode = syncEngine_->Initialize(syncInterface, metadata_, onlineFunc, offlineFunc, queryAutoSyncFunc); + if (errCode == E_OK) { + syncInterface_ = syncInterface; + syncInterface->IncRefCount(); + label_ = syncEngine_->GetLabel(); + return E_OK; + } else { + LOGE("[Syncer] SyncEngine init failed! err:%d.", errCode); + if (syncEngine_ != nullptr) { + RefObject::KillAndDecObjRef(syncEngine_); + syncEngine_ = nullptr; + } + return errCode; + } +} + +int GenericSyncer::CheckSyncActive(ISyncInterface *syncInterface, bool isNeedActive) +{ + bool isSyncDualTupleMode = syncInterface->GetDbProperties().GetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, + false); + if (!isSyncDualTupleMode || isNeedActive) { + return E_OK; + } + LOGI("[Syncer] syncer no need to active"); + int errCode = BuildSyncEngine(); + if (errCode != E_OK) { + return errCode; + } + return -E_NO_NEED_ACTIVE; +} + +uint32_t GenericSyncer::GenerateSyncId() +{ + std::lock_guard lock(syncIdLock_); + currentSyncId_++; + // if overflow, reset to 1 + if (currentSyncId_ <= 0) { + currentSyncId_ = MIN_VALID_SYNC_ID; + } + return currentSyncId_; +} + +bool GenericSyncer::IsValidMode(int mode) const +{ + if ((mode >= SyncModeType::INVALID_MODE) || (mode < SyncModeType::PUSH)) { + LOGE("[Syncer] Sync mode is not valid!"); + return false; + } + return true; +} + +int GenericSyncer::SyncConditionCheck(QuerySyncObject &query, int mode, bool isQuerySync, + const std::vector &devices) const +{ + (void)query; + (void)mode; + (void)isQuerySync; + (void)(devices); + return E_OK; +} + +bool GenericSyncer::IsValidDevices(const std::vector &devices) const +{ + if (devices.empty()) { + LOGE("[Syncer] devices is empty!"); + return false; + } + return true; +} + +void GenericSyncer::ClearSyncOperations(bool isClosedOperation) +{ + std::vector syncOperation; + { + std::lock_guard lock(operationMapLock_); + for (auto &item : syncOperationMap_) { + bool isBlockSync = item.second->IsBlockSync(); + if (isBlockSync || !isClosedOperation) { + int status = (!isClosedOperation) ? SyncOperation::OP_USER_CHANGED : SyncOperation::OP_FAILED; + item.second->SetUnfinishedDevStatus(status); + RefObject::IncObjRef(item.second); + syncOperation.push_back(item.second); + } + } + } + for (auto &operation : syncOperation) { + // block sync operation or userChange will trigger remove sync operation + // caller won't blocked for block sync + // caller won't blocked for userChange operation no mater it is block or non-block sync + TriggerSyncFinished(operation); + RefObject::DecObjRef(operation); + } + { + std::lock_guard lock(operationMapLock_); + for (auto &iter : syncOperationMap_) { + RefObject::KillAndDecObjRef(iter.second); + iter.second = nullptr; + } + syncOperationMap_.clear(); + } + { + std::lock_guard lock(syncIdLock_); + connectionIdMap_.clear(); + syncIdMap_.clear(); + } +} + +void GenericSyncer::TriggerSyncFinished(SyncOperation *operation) +{ + if (operation != nullptr && operation->CheckIsAllFinished()) { + operation->Finished(); + } +} + +void GenericSyncer::OnSyncFinished(int syncId) +{ + (void)(RemoveSyncOperation(syncId)); +} + +int GenericSyncer::SyncModuleInit() +{ + static bool isInit = false; + std::lock_guard lock(moduleInitLock_); + if (!isInit) { + int errCode = SyncResourceInit(); + if (errCode != E_OK) { + return errCode; + } + isInit = true; + return E_OK; + } + return E_OK; +} + +int GenericSyncer::SyncResourceInit() +{ + int errCode = TimeSync::RegisterTransformFunc(); + if (errCode != E_OK) { + LOGE("Register timesync message transform func ERR!"); + return errCode; + } + errCode = SingleVerSerializeManager::RegisterTransformFunc(); + if (errCode != E_OK) { + LOGE("Register SingleVerDataSync message transform func ERR!"); + return errCode; + } +#ifndef OMIT_MULTI_VER + errCode = CommitHistorySync::RegisterTransformFunc(); + if (errCode != E_OK) { + LOGE("Register CommitHistorySync message transform func ERR!"); + return errCode; + } + errCode = MultiVerDataSync::RegisterTransformFunc(); + if (errCode != E_OK) { + LOGE("Register MultiVerDataSync message transform func ERR!"); + return errCode; + } + errCode = ValueSliceSync::RegisterTransformFunc(); + if (errCode != E_OK) { + LOGE("Register ValueSliceSync message transform func ERR!"); + return errCode; + } +#endif + errCode = DeviceManager::RegisterTransformFunc(); + if (errCode != E_OK) { + LOGE("Register DeviceManager message transform func ERR!"); + return errCode; + } + errCode = AbilitySync::RegisterTransformFunc(); + if (errCode != E_OK) { + LOGE("Register AbilitySync message transform func ERR!"); + return errCode; + } + return E_OK; +} + +int GenericSyncer::GetQueuedSyncSize(int *queuedSyncSize) const +{ + if (queuedSyncSize == nullptr) { + return -E_INVALID_ARGS; + } + std::lock_guard lock(queuedManualSyncLock_); + *queuedSyncSize = queuedManualSyncSize_; + LOGI("[GenericSyncer] GetQueuedSyncSize:%d", queuedManualSyncSize_); + return E_OK; +} + +int GenericSyncer::SetQueuedSyncLimit(const int *queuedSyncLimit) +{ + if (queuedSyncLimit == nullptr) { + return -E_INVALID_ARGS; + } + std::lock_guard lock(queuedManualSyncLock_); + queuedManualSyncLimit_ = *queuedSyncLimit; + LOGI("[GenericSyncer] SetQueuedSyncLimit:%d", queuedManualSyncLimit_); + return E_OK; +} + +int GenericSyncer::GetQueuedSyncLimit(int *queuedSyncLimit) const +{ + if (queuedSyncLimit == nullptr) { + return -E_INVALID_ARGS; + } + std::lock_guard lock(queuedManualSyncLock_); + *queuedSyncLimit = queuedManualSyncLimit_; + LOGI("[GenericSyncer] GetQueuedSyncLimit:%d", queuedManualSyncLimit_); + return E_OK; +} + +bool GenericSyncer::IsManualSync(int inMode) const +{ + int mode = SyncOperation::TransferSyncMode(inMode); + if ((mode == SyncModeType::PULL) || (mode == SyncModeType::PUSH) || (mode == SyncModeType::PUSH_AND_PULL) || + (mode == SyncModeType::SUBSCRIBE_QUERY) || (mode == SyncModeType::UNSUBSCRIBE_QUERY)) { + return true; + } + return false; +} + +int GenericSyncer::AddQueuedManualSyncSize(int mode, bool wait) +{ + if (IsManualSync(mode) && (!wait)) { + std::lock_guard lock(queuedManualSyncLock_); + if (!manualSyncEnable_) { + LOGI("[GenericSyncer] manualSyncEnable is Disable"); + return -E_BUSY; + } + queuedManualSyncSize_++; + } + return E_OK; +} + +bool GenericSyncer::IsQueuedManualSyncFull(int mode, bool wait) const +{ + std::lock_guard lock(queuedManualSyncLock_); + if (IsManualSync(mode) && (!manualSyncEnable_)) { + LOGI("[GenericSyncer] manualSyncEnable_:false"); + return true; + } + if (IsManualSync(mode) && (!wait)) { + if (queuedManualSyncSize_ < queuedManualSyncLimit_) { + return false; + } else { + LOGD("[GenericSyncer] queuedManualSyncSize_:%d < queuedManualSyncLimit_:%d", queuedManualSyncSize_, + queuedManualSyncLimit_); + return true; + } + } else { + return false; + } +} + +void GenericSyncer::SubQueuedSyncSize(void) +{ + std::lock_guard lock(queuedManualSyncLock_); + queuedManualSyncSize_--; + if (queuedManualSyncSize_ < 0) { + LOGE("[GenericSyncer] queuedManualSyncSize_ < 0!"); + queuedManualSyncSize_ = 0; + } +} + +int GenericSyncer::DisableManualSync(void) +{ + std::lock_guard lock(queuedManualSyncLock_); + if (queuedManualSyncSize_ > 0) { + LOGD("[GenericSyncer] DisableManualSync fail, queuedManualSyncSize_:%d", queuedManualSyncSize_); + return -E_BUSY; + } + manualSyncEnable_ = false; + LOGD("[GenericSyncer] DisableManualSync ok"); + return E_OK; +} + +int GenericSyncer::EnableManualSync(void) +{ + std::lock_guard lock(queuedManualSyncLock_); + manualSyncEnable_ = true; + LOGD("[GenericSyncer] EnableManualSync ok"); + return E_OK; +} + +int GenericSyncer::GetLocalIdentity(std::string &outTarget) const +{ + std::string deviceId; + int errCode = RuntimeContext::GetInstance()->GetLocalIdentity(deviceId); + if (errCode != E_OK) { + LOGE("[GenericSyncer] GetLocalIdentity fail errCode:%d", errCode); + return errCode; + } + outTarget = DBCommon::TransferHashString(deviceId); + return E_OK; +} + +void GenericSyncer::GetOnlineDevices(std::vector &devices) const +{ + // Get devices from AutoLaunch first. + if (syncInterface_ == nullptr) { + LOGI("[Syncer] GetOnlineDevices syncInterface_ is nullptr"); + return; + } + bool isSyncDualTupleMode = syncInterface_->GetDbProperties().GetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, + false); + std::string identifier; + if (isSyncDualTupleMode) { + identifier = syncInterface_->GetDbProperties().GetStringProp(KvDBProperties::DUAL_TUPLE_IDENTIFIER_DATA, ""); + } else { + identifier = syncInterface_->GetDbProperties().GetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + } + RuntimeContext::GetInstance()->GetAutoLaunchSyncDevices(identifier, devices); + if (!devices.empty()) { + return; + } + std::lock_guard lock(syncerLock_); + if (closing_) { + LOGE("[Syncer] Syncer is closing, return!"); + return; + } + if (syncEngine_ != nullptr) { + syncEngine_->GetOnlineDevices(devices); + } +} + +int GenericSyncer::SetSyncRetry(bool isRetry) +{ + if (syncEngine_ == nullptr) { + return -E_NOT_INIT; + } + syncEngine_->SetSyncRetry(isRetry); + return E_OK; +} + +int GenericSyncer::SetEqualIdentifier(const std::string &identifier, const std::vector &targets) +{ + std::lock_guard lock(syncerLock_); + if (syncEngine_ == nullptr) { + return -E_NOT_INIT; + } + int errCode = syncEngine_->SetEqualIdentifier(identifier, targets); + if (errCode == E_OK) { + syncEngine_->SetEqualIdentifierMap(identifier, targets); + } + return errCode; +} + +std::string GenericSyncer::GetSyncDevicesStr(const std::vector &devices) const +{ + std::string syncDevices; + for (const auto &dev:devices) { + syncDevices += STR_MASK(dev); + syncDevices += ","; + } + return syncDevices.substr(0, syncDevices.size() - 1); +} + +int GenericSyncer::StatusCheck() const +{ + if (!initialized_) { + LOGE("[Syncer] Syncer is not initialized, return!"); + return -E_NOT_INIT; + } + if (closing_) { + LOGE("[Syncer] Syncer is closing, return!"); + return -E_BUSY; + } + return E_OK; +} + +int GenericSyncer::SyncParamCheck(const SyncParma ¶m) const +{ + std::lock_guard lock(syncerLock_); + int errCode = StatusCheck(); + if (errCode != E_OK) { + return errCode; + } + if (!IsValidDevices(param.devices) || !IsValidMode(param.mode)) { + return -E_INVALID_ARGS; + } + if (IsQueuedManualSyncFull(param.mode, param.wait)) { + LOGE("[Syncer] -E_BUSY"); + return -E_BUSY; + } + QuerySyncObject syncQuery = param.syncQuery; + return SyncConditionCheck(syncQuery, param.mode, param.isQuerySync, param.devices); +} + +void GenericSyncer::InitSyncOperation(SyncOperation *operation, const SyncParma ¶m) +{ + operation->SetIdentifier(syncInterface_->GetIdentifier()); + operation->Initialize(); + operation->OnKill(std::bind(&GenericSyncer::SyncOperationKillCallback, this, operation->GetSyncId())); + std::function onFinished = std::bind(&GenericSyncer::OnSyncFinished, this, std::placeholders::_1); + operation->SetOnSyncFinished(onFinished); + operation->SetOnSyncFinalize(param.onFinalize); + if (param.isQuerySync) { + operation->SetQuery(param.syncQuery); + } +} + +int GenericSyncer::BuildSyncEngine() +{ + if (syncEngine_ != nullptr) { + return E_OK; + } + syncEngine_ = CreateSyncEngine(); + if (syncEngine_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + syncEngine_->OnLastRef([this]() { + LOGD("[Syncer] SyncEngine finalized"); + engineFinalize_ = true; + engineFinalizeCv_.notify_all(); + }); + return E_OK; +} + +void GenericSyncer::Dump(int fd) +{ + if (syncEngine_ == nullptr) { + return; + } + syncEngine_->Dump(fd); +} + +SyncerBasicInfo GenericSyncer::DumpSyncerBasicInfo() +{ + SyncerBasicInfo baseInfo; + RefObject::IncObjRef(syncEngine_); + if (syncEngine_ == nullptr) { + return baseInfo; + } + baseInfo.isSyncActive = syncEngine_->IsEngineActive(); + RefObject::DecObjRef(syncEngine_); + return baseInfo; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/syncer/src/generic_syncer.h b/mock/distributeddb/syncer/src/generic_syncer.h new file mode 100644 index 00000000..0d399e90 --- /dev/null +++ b/mock/distributeddb/syncer/src/generic_syncer.h @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 GENRIC_SYNCER_H +#define GENRIC_SYNCER_H + +#include +#include +#include + +#include "isyncer.h" +#include "isync_engine.h" +#include "meta_data.h" +#include "sync_operation.h" +#include "time_helper.h" + +namespace DistributedDB { +class GenericSyncer : public virtual ISyncer { +using DataChangedFunc = std::function; + +public: + GenericSyncer(); + ~GenericSyncer() override; + + // Init the Syncer modules + int Initialize(ISyncInterface *syncInterface, bool isNeedActive) override; + + // Close + int Close(bool isClosedOperation) override; + + // Sync function. + // param devices: The device id list. + // param mode: Sync mode, see SyncMode. + // param onComplete: The syncer finish callback. set by caller + // param onFinalize: will be callback when this Sync Operation finalized. + // return a Sync id. It will return a positive value if failed, + int Sync(const std::vector &devices, int mode, + const std::function &)> &onComplete, + const std::function &onFinalize, bool wait) override; + + // Sync function. use SyncParma to reduce parameter. + int Sync(const SyncParma ¶m); + + int Sync(const SyncParma ¶m, uint64_t connectionId) override; + + // Remove the operation, with the given syncId, used to clean resource if sync finished or failed. + int RemoveSyncOperation(int syncId) override; + + int StopSync(uint64_t connectionId) override; + + // Get The current virtual timestamp + uint64_t GetTimestamp() override; + + // Get manual sync queue size + int GetQueuedSyncSize(int *queuedSyncSize) const override; + + // Set manual sync queue limit + int SetQueuedSyncLimit(const int *queuedSyncLimit) override; + + // Get manual sync queue limit + int GetQueuedSyncLimit(int *queuedSyncLimit) const override; + + // Disable add new manual sync, for rekey + int DisableManualSync(void) override; + + // Enable add new manual sync, for rekey + int EnableManualSync(void) override; + + // Get local deviceId, is hashed + int GetLocalIdentity(std::string &outTarget) const override; + + // Set Manual Sync retry config + int SetSyncRetry(bool isRetry) override; + + // Set an equal identifier for this database, After this called, send msg to the target will use this identifier + int SetEqualIdentifier(const std::string &identifier, const std::vector &targets) override; + + // Inner function, Used for subscribe sync + int Sync(const InternalSyncParma ¶m); + + // Remote data changed callback + virtual void RemoteDataChanged(const std::string &device) = 0; + + virtual void RemoteDeviceOffline(const std::string &device) = 0; + + void Dump(int fd) override; + + SyncerBasicInfo DumpSyncerBasicInfo() override; + +protected: + + // trigger query auto sync or auto subscribe + // trigger auto subscribe only when subscribe task is failed triggered by remote db opened + // it won't be triggered again when subscribe task success + virtual void QueryAutoSync(const InternalSyncParma ¶m); + + // Create a sync engine, if has memory error, will return nullptr. + virtual ISyncEngine *CreateSyncEngine() = 0; + + virtual int PrepareSync(const SyncParma ¶m, uint32_t syncId, uint64_t connectionId); + + // Add a Sync Operation, after call this function, the operation will be start + virtual void AddSyncOperation(SyncOperation *operation); + + // Used to set to the SyncOperation Onkill + virtual void SyncOperationKillCallbackInner(int syncId); + + // Used to set to the SyncOperation Onkill + void SyncOperationKillCallback(int syncId); + + // Init the metadata + int InitMetaData(ISyncInterface *syncInterface); + + // Init the TimeHelper + int InitTimeHelper(ISyncInterface *syncInterface); + + // Init the Sync engine + int InitSyncEngine(ISyncInterface *syncInterface); + + int CheckSyncActive(ISyncInterface *syncInterface, bool isNeedActive); + + // Used to general a sync id, maybe it is currentSyncId++; + // The return value is sync id. + uint32_t GenerateSyncId(); + + // Check if the mode arg is valid + bool IsValidMode(int mode) const; + + virtual int SyncConditionCheck(QuerySyncObject &query, int mode, bool isQuerySync, + const std::vector &devices) const; + + // Check if the devices arg is valid + bool IsValidDevices(const std::vector &devices) const; + + // Used Clear all SyncOperations. + // isClosedOperation is false while userChanged + void ClearSyncOperations(bool isClosedOperation); + + void TriggerSyncFinished(SyncOperation *operation); + + // Callback when the special sync finished. + void OnSyncFinished(int syncId); + + bool IsManualSync(int inMode) const; + + int AddQueuedManualSyncSize(int mode, bool wait); + + bool IsQueuedManualSyncFull(int mode, bool wait) const; + + void SubQueuedSyncSize(void); + + void GetOnlineDevices(std::vector &devices) const; + + std::string GetSyncDevicesStr(const std::vector &devices) const; + + void InitSyncOperation(SyncOperation *operation, const SyncParma ¶m); + + int StatusCheck() const; + + int SyncParamCheck(const SyncParma ¶m) const; + + int BuildSyncEngine(); + + static int SyncModuleInit(); + + static int SyncResourceInit(); + + static const int MIN_VALID_SYNC_ID; + static std::mutex moduleInitLock_; + + // Used to general the next sync id. + static int currentSyncId_; + static std::mutex syncIdLock_; + // For sync in progress. + std::map> connectionIdMap_; + std::map syncIdMap_; + + ISyncEngine *syncEngine_; + ISyncInterface *syncInterface_; + std::shared_ptr timeHelper_; + std::shared_ptr metadata_; + bool initialized_; + std::mutex operationMapLock_; + std::map syncOperationMap_; + int queuedManualSyncSize_; + int queuedManualSyncLimit_; + bool manualSyncEnable_; + bool closing_; + mutable std::mutex queuedManualSyncLock_; + mutable std::mutex syncerLock_; + std::string label_; + bool engineFinalize_; + std::condition_variable engineFinalizeCv_; +}; +} // namespace DistributedDB + +#endif // GENRIC_SYNCER_H diff --git a/mock/distributeddb/syncer/src/isync_engine.h b/mock/distributeddb/syncer/src/isync_engine.h new file mode 100644 index 00000000..dcf47dff --- /dev/null +++ b/mock/distributeddb/syncer/src/isync_engine.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_SYNC_ENGINE_H +#define I_SYNC_ENGINE_H + +#include +#include + +#include "ikvdb_sync_interface.h" +#include "meta_data.h" +#include "ref_object.h" +#include "sync_operation.h" + +namespace DistributedDB { +class ISyncEngine : public virtual RefObject { +public: + // Do some init things + virtual int Initialize(ISyncInterface *syncInterface, std::shared_ptr &metadata, + const std::function &onRemoteDataChanged, + const std::function &offlineChanged, + const std::function &queryAutoSyncCallback) = 0; + + // Do some things, when db close. + virtual int Close() = 0; + + // Alloc and Add sync SyncTarget + // return E_OK if operator success. + virtual int AddSyncOperation(SyncOperation *operation) = 0; + + // Clear the SyncTarget matched the syncId. + virtual void RemoveSyncOperation(int syncId) = 0; + + // notify other devices data has changed + virtual void BroadCastDataChanged() const = 0; + + // Get Online devices + virtual void GetOnlineDevices(std::vector &devices) const = 0; + + // Register the device connect callback, this function must be called after Engine initted + virtual void RegConnectCallback() = 0; + + // Get the database identifier + virtual std::string GetLabel() const = 0; + + // Set Manual Sync retry config + virtual void SetSyncRetry(bool isRetry) = 0; + + // Set an equal identifier for this database, After this called, send msg to the target will use this identifier + virtual int SetEqualIdentifier(const std::string &identifier, const std::vector &targets) = 0; + + // Set record device equal identifier when called in import/rekey scene when restart syncer + virtual void SetEqualIdentifier() = 0; + + virtual void SetEqualIdentifierMap(const std::string &identifier, const std::vector &targets) = 0; + + // Add auto subscribe timer when start sync engine, used for auto subscribe failed subscribe task when db online + virtual int StartAutoSubscribeTimer() = 0; + + // Stop auto subscribe timer when start sync engine + virtual void StopAutoSubscribeTimer() = 0; + + // Check if number of subscriptions out of limit + virtual int SubscribeLimitCheck(const std::vector &devices, QuerySyncObject &query) const = 0; + + // Check if the Sync Engine is active, some times synchronization is not allowed + virtual bool IsEngineActive() const = 0; + + virtual void SchemaChange() = 0; + + virtual void Dump(int fd) = 0; + +protected: + virtual ~ISyncEngine() {}; +}; +} // namespace DistributedDB + +#endif // I_SYNC_ENGINE_H \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/isync_state_machine.h b/mock/distributeddb/syncer/src/isync_state_machine.h new file mode 100644 index 00000000..ac0ed90d --- /dev/null +++ b/mock/distributeddb/syncer/src/isync_state_machine.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_SYNC_STATE_MACHINE_H +#define I_SYNC_STATE_MACHINE_H + +#include + +#include "icommunicator.h" +#include "ikvdb_sync_interface.h" +#include "query_sync_object.h" +#include "sync_target.h" +#include "sync_task_context.h" + +namespace DistributedDB { +class ISyncStateMachine { +public: + virtual ~ISyncStateMachine() {}; + + // Init the SyncStateMachine, this function must be called before any other call. + virtual int Initialize(ISyncTaskContext *context, ISyncInterface *syncInterface, + std::shared_ptr &metadata, ICommunicator *communicator) = 0; + + // start a sync step + virtual int StartSync() = 0; + + // send Message to the StateMachine + virtual int ReceiveMessageCallback(Message *inMsg) = 0; + + // call when timeout + virtual int TimeoutCallback(TimerId timerId) = 0; + + // Force stop the state machine + virtual void Abort() = 0; + + // Called by CommErrHandler, Sub class should realize this function to abort sync when handle err + virtual void CommErrAbort() = 0; + + // start a timer to ResetWatchDog when sync data one (key,value) size bigger than mtu + virtual bool StartFeedDogForSync(uint32_t time, SyncDirectionFlag flag) = 0; + + // stop timer to ResetWatchDog when sync data one (key,value) size bigger than mtu + virtual void StopFeedDogForSync(SyncDirectionFlag flag) = 0; + + // check if need trigger query auto sync and get query from inMsg + virtual bool IsNeedTriggerQueryAutoSync(Message *inMsg, QuerySyncObject &query) = 0; +}; +} // namespace DistributedDB + +#endif // I_SYNC_STATE_MACHINE_H diff --git a/mock/distributeddb/syncer/src/isync_target.h b/mock/distributeddb/syncer/src/isync_target.h new file mode 100644 index 00000000..e8fd05cf --- /dev/null +++ b/mock/distributeddb/syncer/src/isync_target.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_SYNC_TARGET_H +#define I_SYNC_TARGET_H + +#include "sync_operation.h" + +namespace DistributedDB { +class ISyncTarget { +public: + enum TaskType { + REQUEST = 1, + RESPONSE + }; + + virtual ~ISyncTarget() {}; + + // Get the Sync Id of this task + virtual int GetSyncId() const = 0; + + // Set the type of this task request or response + virtual void SetTaskType(int taskType) = 0; + + // Get the type of this task request or response + virtual int GetTaskType() const = 0; + + // Set the mode of this task request or response + virtual void SetMode(int mode) = 0; + + // Get the mode of this task request or response + virtual int GetMode() const = 0; + + // Set a SyncOperation + virtual void SetSyncOperation(SyncOperation *operation) = 0; + + // Get a SyncOperation + virtual void GetSyncOperation(SyncOperation *&operation) const = 0; + + // Is this target is an auto sync + virtual bool IsAutoSync() const = 0; + + virtual uint32_t GetResponseSessionId() const = 0; +}; +} // namespace DistributedDB + +#endif // I_SYNC_TARGET_H diff --git a/mock/distributeddb/syncer/src/isync_task_context.h b/mock/distributeddb/syncer/src/isync_task_context.h new file mode 100644 index 00000000..6ae77de1 --- /dev/null +++ b/mock/distributeddb/syncer/src/isync_task_context.h @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 I_SYNC_TASK_CONTEXT_H +#define I_SYNC_TASK_CONTEXT_H + + +#include "icommunicator.h" +#include "ikvdb_sync_interface.h" +#include "meta_data.h" +#include "query_sync_object.h" +#include "runtime_context.h" +#include "sync_operation.h" +#include "sync_target.h" +#include "time_helper.h" +namespace DistributedDB { +using CommErrHandler = std::function; + +class ISyncTaskContext : public virtual RefObject { +public: + enum RETRY_STATUS { NO_NEED_RETRY, NEED_RETRY }; + + enum TASK_EXEC_STATUS { INIT, RUNNING, FAILED, FINISHED }; + + // Initialize the context + virtual int Initialize(const std::string &deviceId, ISyncInterface *syncInterface, + std::shared_ptr &metadata, ICommunicator *communicator) = 0; + + // Add a sync task target with the operation to the queue + virtual int AddSyncOperation(SyncOperation *operation) = 0; + + // Add a sync task target to the queue + virtual int AddSyncTarget(ISyncTarget *target) = 0; + + // Set the status of this task cotext + virtual void SetOperationStatus(int status) = 0; + + // Clear the data of this context + virtual void Clear() = 0; + + // Remove a sync target by syncId + virtual int RemoveSyncOperation(int syncId) = 0; + + // If the targetQueue is empty + virtual bool IsTargetQueueEmpty() const = 0; + + // Get the status of this task + virtual int GetOperationStatus() const = 0; + + // Set the mode of this task + virtual void SetMode(int mode) = 0; + + // Get the mode of this task + virtual int GetMode() const = 0; + + // Move to next target to sync + virtual void MoveToNextTarget() = 0; + + // Get the current task syncId + virtual uint32_t GetSyncId() const = 0; + + // Get the current task deviceId. + virtual std::string GetDeviceId() const = 0; + + virtual void SetTaskExecStatus(int status) = 0; + + virtual int GetTaskExecStatus() const = 0; + + virtual bool IsAutoSync() const = 0; + + virtual bool IsSyncTaskNeedRetry() const = 0; + + virtual void SetSyncRetry(bool isRetry) = 0; + + virtual int GetSyncRetryTimes() const = 0; + + virtual int GetSyncRetryTimeout(int retryTime) const = 0; + + // Set a Timer used for timeout + virtual int StartTimer() = 0; + + // delete timer + virtual void StopTimer() = 0; + + // modify timer + virtual int ModifyTimer(int milliSeconds) = 0; + + // Set a RetryTime for the sync task + virtual void SetRetryTime(int retryTime) = 0; + + // Get a RetryTime for the sync task + virtual int GetRetryTime() const = 0; + + // Set Retry status for the sync task + virtual void SetRetryStatus(int isNeedRetry) = 0; + + // Get Retry status for the sync task + virtual int GetRetryStatus() const = 0; + + virtual TimerId GetTimerId() const = 0; + + virtual void IncSequenceId() = 0; + + virtual uint32_t GetSequenceId() const = 0; + + virtual void ReSetSequenceId() = 0; + + virtual uint32_t GetRequestSessionId() const = 0; + + virtual int GetTimeoutTime() const = 0; + + virtual void SetTimeOffset(TimeOffset offset) = 0; + + virtual TimeOffset GetTimeOffset() const = 0; + + virtual void SetTimeoutCallback(const TimerAction &timeOutCallback) = 0; + + virtual int StartStateMachine() = 0; + + virtual int ReceiveMessageCallback(Message *inMsg) = 0; + + virtual void RegOnSyncTask(const std::function &callback) = 0; + + virtual int IncUsedCount() = 0; + + virtual void SafeExit() = 0; + + // Get current localtime from TimeHelper + virtual Timestamp GetCurrentLocalTime() const = 0; + + // Set the remount software version num + virtual void SetRemoteSoftwareVersion(uint32_t version) = 0; + + // Get the remount software version num + virtual uint32_t GetRemoteSoftwareVersion() const = 0; + + // Get the remount software version id, when called GetRemoteSoftwareVersion this id will be increase. + // Used to check if the version num is is overdue + virtual uint64_t GetRemoteSoftwareVersionId() const = 0; + + // Judge if the communicator is normal + virtual bool IsCommNormal() const = 0; + + // Judge if the sec option check is err + virtual int GetTaskErrCode() const = 0; + + virtual void SetTaskErrCode(int errCode) = 0; + + virtual void ClearAllSyncTask() = 0; + + virtual bool IsAutoLiftWaterMark() const = 0; + + virtual void IncNegotiationCount() = 0; + + virtual bool IsNeedTriggerQueryAutoSync(Message *inMsg, QuerySyncObject &query) = 0; + + virtual bool IsAutoSubscribe() const = 0; + + // some sync task can be skipped if there is no change in data base since last sync + virtual bool IsCurrentSyncTaskCanBeSkipped() const = 0; + + virtual void SetIsNeedResetAbilitySync(bool isNeedReset) = 0; + + virtual void SchemaChange() = 0; + + virtual void Dump(int fd) = 0; +protected: + virtual ~ISyncTaskContext() {}; +}; +} // namespace DistributedDB + +#endif // I_SYNC_TASK_CONTEXT_H diff --git a/mock/distributeddb/syncer/src/meta_data.cpp b/mock/distributeddb/syncer/src/meta_data.cpp new file mode 100644 index 00000000..bc97fb44 --- /dev/null +++ b/mock/distributeddb/syncer/src/meta_data.cpp @@ -0,0 +1,603 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "meta_data.h" + +#include +#include "db_common.h" +#include "db_constant.h" +#include "db_errno.h" +#include "hash.h" +#include "log_print.h" +#include "securec.h" +#include "sync_types.h" +#include "time_helper.h" + +namespace DistributedDB { +namespace { + const int STR_TO_LL_BY_DEVALUE = 10; + // store local timeoffset;this is a special key; + const std::string LOCALTIME_OFFSET_KEY = "localTimeOffset"; + const std::string DEVICEID_PREFIX_KEY = "deviceId"; +} + +Metadata::Metadata() + : localTimeOffset_(0), + naturalStoragePtr_(nullptr), + lastLocalTime_(0) +{} + +Metadata::~Metadata() +{ + naturalStoragePtr_ = nullptr; + metadataMap_.clear(); +} + +int Metadata::Initialize(ISyncInterface* storage) +{ + naturalStoragePtr_ = storage; + std::vector key; + std::vector timeOffset; + DBCommon::StringToVector(LOCALTIME_OFFSET_KEY, key); + + int errCode = GetMetadataFromDb(key, timeOffset); + if (errCode == -E_NOT_FOUND) { + int err = SaveLocalTimeOffset(TimeHelper::BASE_OFFSET); + if (err != E_OK) { + LOGD("[Metadata][Initialize]SaveLocalTimeOffset failed errCode:%d", err); + return err; + } + } else if (errCode == E_OK) { + localTimeOffset_ = StringToLong(timeOffset); + } else { + LOGE("Metadata::Initialize get meatadata from db failed,err=%d", errCode); + return errCode; + } + { + std::lock_guard lockGuard(metadataLock_); + metadataMap_.clear(); + } + (void)querySyncWaterMarkHelper_.Initialize(storage); + return LoadAllMetadata(); +} + +int Metadata::SaveTimeOffset(const DeviceID &deviceId, TimeOffset inValue) +{ + MetaDataValue metadata; + std::lock_guard lockGuard(metadataLock_); + GetMetaDataValue(deviceId, metadata, true); + metadata.timeOffset = inValue; + metadata.lastUpdateTime = TimeHelper::GetSysCurrentTime(); + LOGD("Metadata::SaveTimeOffset = %" PRId64 " dev %s", inValue, STR_MASK(deviceId)); + return SaveMetaDataValue(deviceId, metadata); +} + +void Metadata::GetTimeOffset(const DeviceID &deviceId, TimeOffset &outValue) +{ + MetaDataValue metadata; + std::lock_guard lockGuard(metadataLock_); + GetMetaDataValue(deviceId, metadata, true); + outValue = metadata.timeOffset; +} + +void Metadata::GetLocalWaterMark(const DeviceID &deviceId, uint64_t &outValue) +{ + MetaDataValue metadata; + std::lock_guard lockGuard(metadataLock_); + GetMetaDataValue(deviceId, metadata, true); + outValue = metadata.localWaterMark; +} + +int Metadata::SaveLocalWaterMark(const DeviceID &deviceId, uint64_t inValue) +{ + MetaDataValue metadata; + std::lock_guard lockGuard(metadataLock_); + GetMetaDataValue(deviceId, metadata, true); + metadata.localWaterMark = inValue; + LOGD("Metadata::SaveLocalWaterMark = %" PRIu64, inValue); + return SaveMetaDataValue(deviceId, metadata); +} + +void Metadata::GetPeerWaterMark(const DeviceID &deviceId, uint64_t &outValue) +{ + MetaDataValue metadata; + std::lock_guard lockGuard(metadataLock_); + GetMetaDataValue(deviceId, metadata, true); + outValue = metadata.peerWaterMark; +} + +int Metadata::SavePeerWaterMark(const DeviceID &deviceId, uint64_t inValue, bool isNeedHash) +{ + MetaDataValue metadata; + std::lock_guard lockGuard(metadataLock_); + GetMetaDataValue(deviceId, metadata, isNeedHash); + metadata.peerWaterMark = inValue; + LOGD("Metadata::SavePeerWaterMark = %" PRIu64, inValue); + return SaveMetaDataValue(deviceId, metadata); +} + +int Metadata::SaveLocalTimeOffset(TimeOffset timeOffset) +{ + std::string timeOffsetString = std::to_string(timeOffset); + std::vector timeOffsetValue(timeOffsetString.begin(), timeOffsetString.end()); + std::vector localTimeOffsetValue( + LOCALTIME_OFFSET_KEY.begin(), LOCALTIME_OFFSET_KEY.end()); + + std::lock_guard lockGuard(localTimeOffsetLock_); + localTimeOffset_ = timeOffset; + LOGD("Metadata::SaveLocalTimeOffset offset = %" PRId64, timeOffset); + int errCode = SetMetadataToDb(localTimeOffsetValue, timeOffsetValue); + if (errCode != E_OK) { + LOGE("Metadata::SaveLocalTimeOffset SetMetadataToDb failed errCode:%d", errCode); + } + return errCode; +} + +TimeOffset Metadata::GetLocalTimeOffset() const +{ + TimeOffset localTimeOffset = localTimeOffset_.load(std::memory_order_seq_cst); + return localTimeOffset; +} + +int Metadata::EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash) +{ + return EraseDeviceWaterMark(deviceId, isNeedHash, ""); +} + +int Metadata::EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash, const std::string &tableName) +{ + // try to erase all the waterMark + // erase deleteSync recv waterMark + WaterMark waterMark = 0; + int errCodeDeleteSync = SetRecvDeleteSyncWaterMark(deviceId, waterMark); + // erase querySync recv waterMark + int errCodeQuerySync = ResetRecvQueryWaterMark(deviceId, tableName); + // peerWaterMark must be erased at last + int errCode = SavePeerWaterMark(deviceId, 0, isNeedHash); + if (errCode != E_OK) { + LOGE("[Metadata] erase peerWaterMark failed errCode:%d", errCode); + return errCode; + } + if (errCodeQuerySync != E_OK) { + LOGE("[Metadata] erase queryWaterMark failed errCode:%d", errCodeQuerySync); + return errCodeQuerySync; + } + if (errCodeDeleteSync != E_OK) { + LOGE("[Metadata] erase deleteWaterMark failed errCode:%d", errCodeDeleteSync); + return errCodeDeleteSync; + } + return E_OK; +} + +void Metadata::SetLastLocalTime(Timestamp lastLocalTime) +{ + std::lock_guard lock(lastLocalTimeLock_); + if (lastLocalTime > lastLocalTime_) { + lastLocalTime_ = lastLocalTime; + } +} + +Timestamp Metadata::GetLastLocalTime() const +{ + std::lock_guard lock(lastLocalTimeLock_); + return lastLocalTime_; +} + +int Metadata::SaveMetaDataValue(const DeviceID &deviceId, const MetaDataValue &inValue) +{ + std::vector value; + int errCode = SerializeMetaData(inValue, value); + if (errCode != E_OK) { + return errCode; + } + + DeviceID hashDeviceId; + GetHashDeviceId(deviceId, hashDeviceId, true); + std::vector key; + DBCommon::StringToVector(hashDeviceId, key); + errCode = SetMetadataToDb(key, value); + if (errCode != E_OK) { + LOGE("Metadata::SetMetadataToDb failed errCode:%d", errCode); + return errCode; + } + PutMetadataToMap(hashDeviceId, inValue); + return E_OK; +} + +void Metadata::GetMetaDataValue(const DeviceID &deviceId, MetaDataValue &outValue, bool isNeedHash) +{ + DeviceID hashDeviceId; + GetHashDeviceId(deviceId, hashDeviceId, isNeedHash); + GetMetadataFromMap(hashDeviceId, outValue); +} + +int Metadata::SerializeMetaData(const MetaDataValue &inValue, std::vector &outValue) +{ + outValue.resize(sizeof(MetaDataValue)); + errno_t err = memcpy_s(&outValue[0], outValue.size(), &inValue, sizeof(MetaDataValue)); + if (err != EOK) { + return -E_SECUREC_ERROR; + } + return E_OK; +} + +int Metadata::DeSerializeMetaData(const std::vector &inValue, MetaDataValue &outValue) const +{ + if (inValue.empty()) { + return -E_INVALID_ARGS; + } + + errno_t err = memcpy_s(&outValue, sizeof(MetaDataValue), &inValue[0], inValue.size()); + if (err != EOK) { + return -E_SECUREC_ERROR; + } + return E_OK; +} + +int Metadata::GetMetadataFromDb(const std::vector &key, std::vector &outValue) const +{ + if (naturalStoragePtr_ == nullptr) { + return -E_INVALID_DB; + } + return naturalStoragePtr_->GetMetaData(key, outValue); +} + +int Metadata::SetMetadataToDb(const std::vector &key, const std::vector &inValue) +{ + if (naturalStoragePtr_ == nullptr) { + return -E_INVALID_DB; + } + return naturalStoragePtr_->PutMetaData(key, inValue); +} + +int Metadata::DeleteMetaDataFromDB(const std::vector &keys) const +{ + if (naturalStoragePtr_ == nullptr) { + return -E_INVALID_DB; + } + return naturalStoragePtr_->DeleteMetaData(keys); +} + +void Metadata::PutMetadataToMap(const DeviceID &deviceId, const MetaDataValue &value) +{ + metadataMap_[deviceId] = value; +} + +void Metadata::GetMetadataFromMap(const DeviceID &deviceId, MetaDataValue &outValue) +{ + outValue = metadataMap_[deviceId]; +} + +int64_t Metadata::StringToLong(const std::vector &value) const +{ + std::string valueString(value.begin(), value.end()); + int64_t longData = std::strtoll(valueString.c_str(), nullptr, STR_TO_LL_BY_DEVALUE); + LOGD("Metadata::StringToLong longData = %" PRId64, longData); + return longData; +} + +int Metadata::GetAllMetadataKey(std::vector> &keys) +{ + if (naturalStoragePtr_ == nullptr) { + return -E_INVALID_DB; + } + return naturalStoragePtr_->GetAllMetaKeys(keys); +} + +namespace { +bool IsMetaDataKey(const Key &inKey, const std::string &expectPrefix) +{ + if (inKey.size() < expectPrefix.size()) { + return false; + } + std::string prefixInKey(inKey.begin(), inKey.begin() + expectPrefix.size()); + if (prefixInKey != expectPrefix) { + return false; + } + return true; +} +} + +int Metadata::LoadAllMetadata() +{ + std::vector> metaDataKeys; + int errCode = GetAllMetadataKey(metaDataKeys); + if (errCode != E_OK) { + LOGE("[Metadata] get all metadata key failed err=%d", errCode); + return errCode; + } + + std::vector> querySyncIds; + for (const auto &deviceId : metaDataKeys) { + if (IsMetaDataKey(deviceId, DEVICEID_PREFIX_KEY)) { + errCode = LoadDeviceIdDataToMap(deviceId); + if (errCode != E_OK) { + return errCode; + } + } else if (IsMetaDataKey(deviceId, QuerySyncWaterMarkHelper::GetQuerySyncPrefixKey())) { + querySyncIds.push_back(deviceId); + } else if (IsMetaDataKey(deviceId, QuerySyncWaterMarkHelper::GetDeleteSyncPrefixKey())) { + errCode = querySyncWaterMarkHelper_.LoadDeleteSyncDataToCache(deviceId); + if (errCode != E_OK) { + return errCode; + } + } + } + return querySyncWaterMarkHelper_.RemoveLeastUsedQuerySyncItems(querySyncIds); +} + +int Metadata::LoadDeviceIdDataToMap(const Key &key) +{ + std::vector value; + int errCode = GetMetadataFromDb(key, value); + if (errCode != E_OK) { + return errCode; + } + MetaDataValue metaValue; + std::string metaKey(key.begin(), key.end()); + errCode = DeSerializeMetaData(value, metaValue); + if (errCode != E_OK) { + return errCode; + } + std::lock_guard lockGuard(metadataLock_); + PutMetadataToMap(metaKey, metaValue); + return errCode; +} + +uint64_t Metadata::GetRandTimeOffset() const +{ + const int randOffsetLength = 2; // 2 byte + uint8_t randBytes[randOffsetLength] = { 0 }; + RAND_bytes(randBytes, randOffsetLength); + + // use a 16 bit rand data to make a rand timeoffset + uint64_t randTimeOffset = (static_cast(randBytes[1]) << 8) | randBytes[0]; // 16 bit data, 8 is offset + randTimeOffset = randTimeOffset * 1000 * 1000 * 10; // second, 1000 is scale + LOGD("[Metadata] GetRandTimeOffset %" PRIu64, randTimeOffset); + return randTimeOffset; +} + +void Metadata::GetHashDeviceId(const DeviceID &deviceId, DeviceID &hashDeviceId, bool isNeedHash) +{ + if (!isNeedHash) { + hashDeviceId = deviceId; + return; + } + if (deviceIdToHashDeviceIdMap_.count(deviceId) == 0) { + hashDeviceId = DEVICEID_PREFIX_KEY + DBCommon::TransferHashString(deviceId); + deviceIdToHashDeviceIdMap_.insert(std::pair(deviceId, hashDeviceId)); + } else { + hashDeviceId = deviceIdToHashDeviceIdMap_[deviceId]; + } +} + +int Metadata::GetRecvQueryWaterMark(const std::string &queryIdentify, + const std::string &deviceId, WaterMark &waterMark) +{ + QueryWaterMark queryWaterMark; + int errCode = querySyncWaterMarkHelper_.GetQueryWaterMark(queryIdentify, deviceId, queryWaterMark); + if (errCode != E_OK) { + return errCode; + } + WaterMark peerWaterMark; + GetPeerWaterMark(deviceId, peerWaterMark); + waterMark = std::max(queryWaterMark.recvWaterMark, peerWaterMark); + return E_OK; +} + +int Metadata::SetRecvQueryWaterMark(const std::string &queryIdentify, + const std::string &deviceId, const WaterMark &waterMark) +{ + return querySyncWaterMarkHelper_.SetRecvQueryWaterMark(queryIdentify, deviceId, waterMark); +} + +int Metadata::GetSendQueryWaterMark(const std::string &queryIdentify, + const std::string &deviceId, WaterMark &waterMark, bool isAutoLift) +{ + QueryWaterMark queryWaterMark; + int errCode = querySyncWaterMarkHelper_.GetQueryWaterMark(queryIdentify, deviceId, queryWaterMark); + if (errCode != E_OK) { + return errCode; + } + if (isAutoLift) { + WaterMark localWaterMark; + GetLocalWaterMark(deviceId, localWaterMark); + waterMark = std::max(queryWaterMark.sendWaterMark, localWaterMark); + } else { + waterMark = queryWaterMark.sendWaterMark; + } + return E_OK; +} + +int Metadata::SetSendQueryWaterMark(const std::string &queryIdentify, + const std::string &deviceId, const WaterMark &waterMark) +{ + return querySyncWaterMarkHelper_.SetSendQueryWaterMark(queryIdentify, deviceId, waterMark); +} + +int Metadata::GetLastQueryTime(const std::string &queryIdentify, const std::string &deviceId, Timestamp ×tamp) +{ + QueryWaterMark queryWaterMark; + int errCode = querySyncWaterMarkHelper_.GetQueryWaterMark(queryIdentify, deviceId, queryWaterMark); + if (errCode != E_OK) { + return errCode; + } + timestamp = queryWaterMark.lastQueryTime; + return E_OK; +} + +int Metadata::SetLastQueryTime(const std::string &queryIdentify, const std::string &deviceId, + const Timestamp ×tamp) +{ + return querySyncWaterMarkHelper_.SetLastQueryTime(queryIdentify, deviceId, timestamp); +} + +int Metadata::GetSendDeleteSyncWaterMark(const DeviceID &deviceId, WaterMark &waterMark, bool isAutoLift) +{ + DeleteWaterMark deleteWaterMark; + int errCode = querySyncWaterMarkHelper_.GetDeleteSyncWaterMark(deviceId, deleteWaterMark); + if (errCode != E_OK) { + return errCode; + } + if (isAutoLift) { + WaterMark localWaterMark; + GetLocalWaterMark(deviceId, localWaterMark); + waterMark = std::max(deleteWaterMark.sendWaterMark, localWaterMark); + } else { + waterMark = deleteWaterMark.sendWaterMark; + } + return E_OK; +} + +int Metadata::SetSendDeleteSyncWaterMark(const DeviceID &deviceId, const WaterMark &waterMark) +{ + return querySyncWaterMarkHelper_.SetSendDeleteSyncWaterMark(deviceId, waterMark); +} + +int Metadata::GetRecvDeleteSyncWaterMark(const DeviceID &deviceId, WaterMark &waterMark) +{ + DeleteWaterMark deleteWaterMark; + int errCode = querySyncWaterMarkHelper_.GetDeleteSyncWaterMark(deviceId, deleteWaterMark); + if (errCode != E_OK) { + return errCode; + } + WaterMark peerWaterMark; + GetPeerWaterMark(deviceId, peerWaterMark); + waterMark = std::max(deleteWaterMark.recvWaterMark, peerWaterMark); + return E_OK; +} + +int Metadata::SetRecvDeleteSyncWaterMark(const DeviceID &deviceId, const WaterMark &waterMark) +{ + return querySyncWaterMarkHelper_.SetRecvDeleteSyncWaterMark(deviceId, waterMark); +} + +int Metadata::ResetRecvQueryWaterMark(const DeviceID &deviceId, const std::string &tableName) +{ + return querySyncWaterMarkHelper_.ResetRecvQueryWaterMark(deviceId, tableName); +} + +void Metadata::GetDbCreateTime(const DeviceID &deviceId, uint64_t &outValue) +{ + MetaDataValue metadata; + std::lock_guard lockGuard(metadataLock_); + DeviceID hashDeviceId; + GetHashDeviceId(deviceId, hashDeviceId, true); + if (metadataMap_.find(hashDeviceId) != metadataMap_.end()) { + metadata = metadataMap_[hashDeviceId]; + outValue = metadata.dbCreateTime; + return; + } + outValue = 0; + LOGI("Metadata::GetDbCreateTime, not found dev = %s dbCreateTime", STR_MASK(deviceId)); +} + +int Metadata::SetDbCreateTime(const DeviceID &deviceId, uint64_t inValue, bool isNeedHash) +{ + MetaDataValue metadata; + std::lock_guard lockGuard(metadataLock_); + DeviceID hashDeviceId; + GetHashDeviceId(deviceId, hashDeviceId, isNeedHash); + if (metadataMap_.find(hashDeviceId) != metadataMap_.end()) { + metadata = metadataMap_[hashDeviceId]; + if (metadata.dbCreateTime != 0 && metadata.dbCreateTime != inValue) { + metadata.clearDeviceDataMark = REMOVE_DEVICE_DATA_MARK; + LOGI("Metadata::SetDbCreateTime,set cleardata mark,dev=%s,dbCreateTime=%" PRIu64, + STR_MASK(deviceId), inValue); + } + if (metadata.dbCreateTime == 0) { + LOGI("Metadata::SetDbCreateTime,update dev=%s,dbCreateTime=%" PRIu64, STR_MASK(deviceId), inValue); + } + } + metadata.dbCreateTime = inValue; + return SaveMetaDataValue(deviceId, metadata); +} + +int Metadata::ResetMetaDataAfterRemoveData(const DeviceID &deviceId) +{ + MetaDataValue metadata; + std::lock_guard lockGuard(metadataLock_); + DeviceID hashDeviceId; + GetHashDeviceId(deviceId, hashDeviceId, true); + if (metadataMap_.find(hashDeviceId) != metadataMap_.end()) { + metadata = metadataMap_[hashDeviceId]; + metadata.clearDeviceDataMark = 0; + return SaveMetaDataValue(deviceId, metadata); + } + return -E_NOT_FOUND; +} + +void Metadata::GetRemoveDataMark(const DeviceID &deviceId, uint64_t &outValue) +{ + MetaDataValue metadata; + std::lock_guard lockGuard(metadataLock_); + DeviceID hashDeviceId; + GetHashDeviceId(deviceId, hashDeviceId, true); + if (metadataMap_.find(hashDeviceId) != metadataMap_.end()) { + metadata = metadataMap_[hashDeviceId]; + outValue = metadata.clearDeviceDataMark; + return; + } + outValue = 0; +} + +int Metadata::DeleteMetaDataByPrefixKey(const Key &keyPrefix) const +{ + if (naturalStoragePtr_ == nullptr) { + return -E_INVALID_DB; + } + return naturalStoragePtr_->DeleteMetaDataByPrefixKey(keyPrefix); +} + +uint64_t Metadata::GetQueryLastTimestamp(const DeviceID &deviceId, const std::string &queryId) const +{ + std::vector key; + std::vector value; + std::string hashqueryId = DBConstant::SUBSCRIBE_QUERY_PREFIX + DBCommon::TransferHashString(queryId); + DBCommon::StringToVector(hashqueryId, key); + int errCode = GetMetadataFromDb(key, value); + std::lock_guard lockGuard(metadataLock_); + if (errCode == -E_NOT_FOUND) { + auto iter = queryIdMap_.find(deviceId); + if (iter != queryIdMap_.end()) { + if (iter->second.find(hashqueryId) == iter->second.end()) { + iter->second.insert(hashqueryId); + return INT64_MAX; + } + return 0; + } else { + queryIdMap_[deviceId] = { hashqueryId }; + return INT64_MAX; + } + } + auto iter = queryIdMap_.find(deviceId); + // while value is found in db, it can be found in db later when db is not closed + // so no need to record the hashqueryId in map + if (errCode == E_OK && iter != queryIdMap_.end()) { + iter->second.erase(hashqueryId); + } + return StringToLong(value); +} + +void Metadata::RemoveQueryFromRecordSet(const DeviceID &deviceId, const std::string &queryId) +{ + std::lock_guard lockGuard(metadataLock_); + std::string hashqueryId = DBConstant::SUBSCRIBE_QUERY_PREFIX + DBCommon::TransferHashString(queryId); + auto iter = queryIdMap_.find(deviceId); + if (iter != queryIdMap_.end() && iter->second.find(hashqueryId) != iter->second.end()) { + iter->second.erase(hashqueryId); + } +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/meta_data.h b/mock/distributeddb/syncer/src/meta_data.h new file mode 100644 index 00000000..7b2a0051 --- /dev/null +++ b/mock/distributeddb/syncer/src/meta_data.h @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 META_DATA_H +#define META_DATA_H + +#include +#include +#include +#include + +#include "db_types.h" +#include "ikvdb_sync_interface.h" +#include "query_sync_water_mark_helper.h" + +namespace DistributedDB { +struct MetaDataValue { + TimeOffset timeOffset = 0; + uint64_t lastUpdateTime = 0; + uint64_t localWaterMark = 0; + uint64_t peerWaterMark = 0; + Timestamp dbCreateTime = 0; + uint64_t clearDeviceDataMark = 0; // Default 0 for not remove device data. +}; + +class Metadata { +public: + Metadata(); + virtual ~Metadata(); + + int Initialize(ISyncInterface *storage); + + int SaveTimeOffset(const DeviceID &deviceId, TimeOffset inValue); + + void GetTimeOffset(const DeviceID &deviceId, TimeOffset &outValue); + + void GetLocalWaterMark(const DeviceID &deviceId, uint64_t &outValue); + + int SaveLocalWaterMark(const DeviceID &deviceId, uint64_t inValue); + + void GetPeerWaterMark(const DeviceID &deviceId, uint64_t &outValue); + + int SavePeerWaterMark(const DeviceID &deviceId, uint64_t inValue, bool isNeedHash); + + int SaveLocalTimeOffset(TimeOffset timeOffset); + + TimeOffset GetLocalTimeOffset() const; + + int EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash); + + int EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash, const std::string &tableName); + + void SetLastLocalTime(Timestamp lastLocalTime); + + Timestamp GetLastLocalTime() const; + + int SetSendQueryWaterMark(const std::string &queryIdentify, + const std::string &deviceId, const WaterMark &waterMark); + + // the querySync's sendWatermark will increase by the device watermark + // if the sendWatermark less than device watermark + int GetSendQueryWaterMark(const std::string &queryIdentify, + const std::string &deviceId, WaterMark &waterMark, bool isAutoLift = true); + + int SetRecvQueryWaterMark(const std::string &queryIdentify, + const std::string &deviceId, const WaterMark &waterMark); + + // the querySync's recvWatermark will increase by the device watermark + // if the watermark less than device watermark + int GetRecvQueryWaterMark(const std::string &queryIdentify, + const std::string &deviceId, WaterMark &waterMark); + + virtual int SetLastQueryTime(const std::string &queryIdentify, const std::string &deviceId, + const Timestamp ×tamp); + + virtual int GetLastQueryTime(const std::string &queryIdentify, const std::string &deviceId, Timestamp ×tamp); + + int SetSendDeleteSyncWaterMark(const std::string &deviceId, const WaterMark &waterMark); + + // the deleteSync's sendWatermark will increase by the device watermark + // if the sendWatermark less than device watermark + int GetSendDeleteSyncWaterMark(const std::string &deviceId, WaterMark &waterMark, bool isAutoLift = true); + + int SetRecvDeleteSyncWaterMark(const std::string &deviceId, const WaterMark &waterMark); + + // the deleteSync's recvWatermark will increase by the device watermark + // if the recvWatermark less than device watermark + int GetRecvDeleteSyncWaterMark(const std::string &deviceId, WaterMark &waterMark); + + void GetDbCreateTime(const DeviceID &deviceId, uint64_t &outValue); + + int SetDbCreateTime(const DeviceID &deviceId, uint64_t inValue, bool isNeedHash); + + int ResetMetaDataAfterRemoveData(const DeviceID &deviceId); + + void GetRemoveDataMark(const DeviceID &deviceId, uint64_t &outValue); + + // always get value from db, value updated from storage trigger + uint64_t GetQueryLastTimestamp(const DeviceID &deviceId, const std::string &queryId) const; + + void RemoveQueryFromRecordSet(const DeviceID &deviceId, const std::string &queryId); +private: + + int SaveMetaDataValue(const DeviceID &deviceId, const MetaDataValue &inValue); + + // sync module need hash devices id + void GetMetaDataValue(const DeviceID &deviceId, MetaDataValue &outValue, bool isNeedHash); + + int SerializeMetaData(const MetaDataValue &inValue, std::vector &outValue); + + int DeSerializeMetaData(const std::vector &inValue, MetaDataValue &outValue) const; + + int GetMetadataFromDb(const std::vector &key, std::vector &outValue) const; + + int SetMetadataToDb(const std::vector &key, const std::vector &inValue); + + int DeleteMetaDataFromDB(const std::vector &keys) const; + int DeleteMetaDataByPrefixKey(const Key &keyPrefix) const; + + void PutMetadataToMap(const DeviceID &deviceId, const MetaDataValue &value); + + void GetMetadataFromMap(const DeviceID &deviceId, MetaDataValue &outValue); + + int64_t StringToLong(const std::vector &value) const; + + int GetAllMetadataKey(std::vector> &keys); + + int LoadAllMetadata(); + + uint64_t GetRandTimeOffset() const; + + void GetHashDeviceId(const DeviceID &deviceId, DeviceID &hashDeviceId, bool isNeedHash); + + // this function will read data from db by metaData's key + // and then serialize it and put to map + int LoadDeviceIdDataToMap(const Key &key); + + // reset the waterMark to zero + int ResetRecvQueryWaterMark(const DeviceID &deviceId, const std::string &tableName = ""); + + // store localTimeOffset in ram; if change, should add a lock first, change here and metadata, + // then release lock + std::atomic localTimeOffset_; + std::mutex localTimeOffsetLock_; + ISyncInterface *naturalStoragePtr_; + + // if changed, it should be locked from save-to-db to change-in-memory.save to db must be first, + // if save to db fail, it will not be changed in memory. + std::map metadataMap_; + mutable std::mutex metadataLock_; + std::map deviceIdToHashDeviceIdMap_; + + // store localTimeOffset in ram, used to make timestamp increase + mutable std::mutex lastLocalTimeLock_; + Timestamp lastLocalTime_; + + QuerySyncWaterMarkHelper querySyncWaterMarkHelper_; + + // set value: SUBSCRIBE_QUERY_PREFIX + DBCommon::TransferHashString(queryId) + // queryId is not in set while key is not found from db first time, and return lastTimestamp = INT64_MAX + // if query is in set return 0 while not found from db, means already sync before, don't trigger again + mutable std::map> queryIdMap_; +}; +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/syncer/src/multi_ver_data_sync.cpp b/mock/distributeddb/syncer/src/multi_ver_data_sync.cpp new file mode 100644 index 00000000..9bc8c84f --- /dev/null +++ b/mock/distributeddb/syncer/src/multi_ver_data_sync.cpp @@ -0,0 +1,691 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_data_sync.h" + +#include "parcel.h" +#include "log_print.h" +#include "sync_types.h" +#include "message_transform.h" +#include "performance_analysis.h" +#include "db_constant.h" + +namespace DistributedDB { +// Class MultiVerRequestPacket +uint32_t MultiVerRequestPacket::CalculateLen() const +{ + uint64_t len = Parcel::GetIntLen(); + len = Parcel::GetEightByteAlign(len); + len += Parcel::GetMultiVerCommitLen(commit_); + if (len > INT32_MAX) { + return 0; + } + return len; +} + +void MultiVerRequestPacket::SetCommit(MultiVerCommitNode &commit) +{ + commit_ = std::move(commit); +} + +void MultiVerRequestPacket::GetCommit(MultiVerCommitNode &commit) const +{ + commit = commit_; +} + +void MultiVerRequestPacket::SetErrCode(int32_t errCode) +{ + errCode_ = errCode; +} + +int32_t MultiVerRequestPacket::GetErrCode() const +{ + return errCode_; +} + +// Class MultiVerAckPacket +uint32_t MultiVerAckPacket::CalculateLen() const +{ + uint64_t len = Parcel::GetIntLen(); + len = Parcel::GetEightByteAlign(len); + for (const auto &iter : entries_) { + len += Parcel::GetVectorCharLen(iter); + if (len > INT32_MAX) { + return 0; + } + } + return len; +} + +void MultiVerAckPacket::SetData(std::vector> &data) +{ + entries_ = std::move(data); +} + +void MultiVerAckPacket::GetData(std::vector> &data) const +{ + data = entries_; +} + +void MultiVerAckPacket::SetErrorCode(int32_t errCode) +{ + errorCode_ = errCode; +} + +void MultiVerAckPacket::GetErrorCode(int32_t &errCode) const +{ + errCode = errorCode_; +} + +// Class MultiVerDataSync +MultiVerDataSync::~MultiVerDataSync() +{ + storagePtr_ = nullptr; + communicateHandle_ = nullptr; +} + +int MultiVerDataSync::Serialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + if ((buffer == nullptr) || !(IsPacketValid(inMsg, TYPE_RESPONSE) || IsPacketValid(inMsg, TYPE_REQUEST))) { + return -E_INVALID_ARGS; + } + + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return RequestPacketSerialization(buffer, length, inMsg); + case TYPE_RESPONSE: + return AckPacketSerialization(buffer, length, inMsg); + default: + return -E_MESSAGE_TYPE_ERROR; + } +} + +int MultiVerDataSync::DeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + if ((buffer == nullptr) || !(IsPacketValid(inMsg, TYPE_RESPONSE) || IsPacketValid(inMsg, TYPE_REQUEST))) { + return -E_MESSAGE_ID_ERROR; + } + + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return RequestPacketDeSerialization(buffer, length, inMsg); + case TYPE_RESPONSE: + return AckPacketDeSerialization(buffer, length, inMsg); + default: + return -E_MESSAGE_TYPE_ERROR; + } +} + +uint32_t MultiVerDataSync::CalculateLen(const Message *inMsg) +{ + if (!(IsPacketValid(inMsg, TYPE_RESPONSE) || IsPacketValid(inMsg, TYPE_REQUEST))) { + return 0; + } + + uint32_t len = 0; + int errCode = E_OK; + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + errCode = RequestPacketCalculateLen(inMsg, len); + break; + case TYPE_RESPONSE: + errCode = AckPacketCalculateLen(inMsg, len); + break; + default: + return 0; + } + if (errCode != E_OK) { + return 0; + } + return len; +} + +int MultiVerDataSync::RegisterTransformFunc() +{ + TransformFunc func; + func.computeFunc = std::bind(&MultiVerDataSync::CalculateLen, std::placeholders::_1); + func.serializeFunc = std::bind(&MultiVerDataSync::Serialization, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + func.deserializeFunc = std::bind(&MultiVerDataSync::DeSerialization, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + return MessageTransform::RegTransformFunction(MULTI_VER_DATA_SYNC_MESSAGE, func); +} + +int MultiVerDataSync::Initialize(MultiVerKvDBSyncInterface *storagePtr, ICommunicator *communicateHandle) +{ + if ((storagePtr == nullptr) || (communicateHandle == nullptr)) { + return -E_INVALID_ARGS; + } + storagePtr_ = storagePtr; + communicateHandle_ = communicateHandle; + return E_OK; +} + +void MultiVerDataSync::TimeOutCallback(MultiVerSyncTaskContext *context, const Message *message) const +{ + return; +} + +int MultiVerDataSync::SyncStart(MultiVerSyncTaskContext *context) +{ + if (context == nullptr) { + return -E_INVALID_ARGS; + } + LOGD("MultiVerDataSync::SyncStart dst=%s{private}, begin", context->GetDeviceId().c_str()); + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_DATA_GET_VALID_COMMIT); + } + MultiVerCommitNode commit; + int errCode = GetValidCommit(context, commit); + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_DATA_GET_VALID_COMMIT); + } + if (errCode != E_OK) { + // sync don't need start + SendFinishedRequest(context); + return errCode; + } + + errCode = SendRequestPacket(context, commit); + LOGD("MultiVerDataSync::SyncStart dst=%s{private}, end", context->GetDeviceId().c_str()); + return errCode; +} + +int MultiVerDataSync::RequestRecvCallback(const MultiVerSyncTaskContext *context, const Message *message) +{ + if (message == nullptr || context == nullptr) { + return -E_INVALID_ARGS; + } + + if (!IsPacketValid(message, TYPE_REQUEST)) { + return -E_INVALID_ARGS; + } + + const MultiVerRequestPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + if (packet->GetErrCode() == -E_LAST_SYNC_FRAME) { + return -E_LAST_SYNC_FRAME; + } + MultiVerCommitNode commit; + packet->GetCommit(commit); + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_GET_COMMIT_DATA); + } + std::vector dataEntries; + int errCode = GetCommitData(commit, dataEntries); + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_GET_COMMIT_DATA); + } + if (errCode != E_OK) { + LOGE("MultiVerDataSync::RequestRecvCallback : GetCommitData ERR, errno = %d", errCode); + } + + errCode = SendAckPacket(context, dataEntries, errCode, message); + for (auto &iter : dataEntries) { + ReleaseKvEntry(iter); + iter = nullptr; + } + LOGD("MultiVerDataSync::RequestRecvCallback : SendAckPacket, errno = %d, dst = %s{private}", + errCode, context->GetDeviceId().c_str()); + return errCode; +} + +int MultiVerDataSync::AckRecvCallback(MultiVerSyncTaskContext *context, const Message *message) +{ + if (message == nullptr) { + return -E_INVALID_ARGS; + } + if (!IsPacketValid(message, TYPE_RESPONSE) || (context == nullptr)) { + return -E_INVALID_ARGS; + } + + const MultiVerAckPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + int32_t errCode = E_OK; + packet->GetErrorCode(errCode); + if (errCode != E_OK) { + return errCode; + } + std::vector> dataEntries; + std::vector entries; + std::vector valueHashes; + MultiVerKvEntry *entry = nullptr; + + packet->GetData(dataEntries); + for (auto &iter : dataEntries) { + MultiVerKvEntry *item = CreateKvEntry(iter); + entries.push_back(item); + } + context->ReleaseEntries(); + context->SetEntries(entries); + context->SetEntriesIndex(0); + context->SetEntriesSize(static_cast(entries.size())); + LOGD("MultiVerDataSync::AckRecvCallback src=%s{private}, entries num = %zu", + context->GetDeviceId().c_str(), entries.size()); + + if (entries.size() > 0) { + entry = entries[0]; + errCode = entry->GetValueHash(valueHashes); + if (errCode != E_OK) { + return errCode; + } + } + context->SetValueSliceHashNodes(valueHashes); + context->SetValueSlicesIndex(0); + context->SetValueSlicesSize(valueHashes.size()); + LOGD("MultiVerDataSync::AckRecvCallback src=%s{private}, ValueSlicesSize num = %zu", + context->GetDeviceId().c_str(), valueHashes.size()); + return errCode; +} + +int MultiVerDataSync::PutCommitData(const MultiVerCommitNode &commit, const std::vector &entries, + const std::string &deviceName) +{ + return storagePtr_->PutCommitData(commit, entries, deviceName); +} + +int MultiVerDataSync::MergeSyncCommit(const MultiVerCommitNode &commit, const std::vector &commits) +{ + return storagePtr_->MergeSyncCommit(commit, commits); +} + +void MultiVerDataSync::ReleaseKvEntry(const MultiVerKvEntry *entry) +{ + return storagePtr_->ReleaseKvEntry(entry); +} + +void MultiVerDataSync::SendFinishedRequest(const MultiVerSyncTaskContext *context) +{ + if (context == nullptr) { + return; + } + MultiVerRequestPacket *packet = new (std::nothrow) MultiVerRequestPacket(); + if (packet == nullptr) { + LOGE("MultiVerRequestPacket::SendRequestPacket : new packet error"); + return; + } + packet->SetErrCode(-E_LAST_SYNC_FRAME); + Message *message = new (std::nothrow) Message(MULTI_VER_DATA_SYNC_MESSAGE); + if (message == nullptr) { + delete packet; + packet = nullptr; + LOGE("MultiVerDataSync::SendRequestPacket : new message error"); + return; + } + message->SetMessageType(TYPE_REQUEST); + message->SetTarget(context->GetDeviceId()); + int errCode = message->SetExternalObject(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + delete message; + message = nullptr; + LOGE("[MultiVerDataSync][SendFinishedRequest] : SetExternalObject failed errCode:%d", errCode); + return; + } + message->SetSessionId(context->GetRequestSessionId()); + message->SetSequenceId(context->GetSequenceId()); + + errCode = Send(message->GetTarget(), message); + if (errCode != E_OK) { + delete message; + message = nullptr; + LOGE("[MultiVerDataSync][SendFinishedRequest] SendFinishedRequest failed, err %d", errCode); + } + LOGI("[MultiVerDataSync][SendFinishedRequest] SendFinishedRequest dst=%s{private}", context->GetDeviceId().c_str()); +} + +int MultiVerDataSync::RequestPacketCalculateLen(const Message *inMsg, uint32_t &len) +{ + if ((inMsg == nullptr) || !IsPacketValid(inMsg, TYPE_REQUEST)) { + return -E_INVALID_ARGS; + } + const MultiVerRequestPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + + len = packet->CalculateLen(); + return E_OK; +} + +int MultiVerDataSync::RequestPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + if ((buffer == nullptr) || !IsPacketValid(inMsg, TYPE_REQUEST)) { + return -E_INVALID_ARGS; + } + const MultiVerRequestPacket *packet = inMsg->GetObject(); + if ((packet == nullptr) || (length != packet->CalculateLen())) { + return -E_INVALID_ARGS; + } + + MultiVerCommitNode commit; + packet->GetCommit(commit); + int32_t ackCode = packet->GetErrCode(); + + Parcel parcel(buffer, length); + int errCode = parcel.WriteInt(ackCode); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + parcel.EightByteAlign(); + + // commitMap Serialization + errCode = parcel.WriteMultiVerCommit(commit); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + + return errCode; +} + +int MultiVerDataSync::RequestPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + if ((buffer == nullptr) || !IsPacketValid(inMsg, TYPE_REQUEST)) { + return -E_INVALID_ARGS; + } + + MultiVerCommitNode commit; + Parcel parcel(const_cast(buffer), length); + int32_t pktErrCode; + uint64_t packLen = parcel.ReadInt(pktErrCode); + if (parcel.IsError()) { + return -E_INVALID_ARGS; + } + parcel.EightByteAlign(); + packLen = Parcel::GetEightByteAlign(packLen); + // commit DeSerialization + packLen += parcel.ReadMultiVerCommit(commit); + if (packLen != length || parcel.IsError()) { + return -E_INVALID_ARGS; + } + MultiVerRequestPacket *packet = new (std::nothrow) MultiVerRequestPacket(); + if (packet == nullptr) { + LOGE("MultiVerDataSync::RequestPacketDeSerialization : new packet error"); + return -E_OUT_OF_MEMORY; + } + packet->SetCommit(commit); + packet->SetErrCode(pktErrCode); + int errCode = inMsg->SetExternalObject<>(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + } + return errCode; +} + +int MultiVerDataSync::AckPacketCalculateLen(const Message *inMsg, uint32_t &len) +{ + if (!IsPacketValid(inMsg, TYPE_RESPONSE)) { + return -E_INVALID_ARGS; + } + + const MultiVerAckPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + len = packet->CalculateLen(); + return E_OK; +} + +int MultiVerDataSync::AckPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + if ((buffer == nullptr) || !IsPacketValid(inMsg, TYPE_RESPONSE)) { + return -E_INVALID_ARGS; + } + const MultiVerAckPacket *packet = inMsg->GetObject(); + if ((packet == nullptr) || (length != packet->CalculateLen())) { + return -E_INVALID_ARGS; + } + + Parcel parcel(buffer, length); + std::vector> entries; + + packet->GetData(entries); + int32_t errCode = E_OK; + packet->GetErrorCode(errCode); + // errCode Serialization + errCode = parcel.WriteInt(errCode); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + parcel.EightByteAlign(); + + // commits vector Serialization + for (const auto &iter : entries) { + errCode = parcel.WriteVectorChar(iter); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + } + + return errCode; +} + +int MultiVerDataSync::AckPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + if ((buffer == nullptr) || !IsPacketValid(inMsg, TYPE_RESPONSE)) { + return -E_INVALID_ARGS; + } + + Parcel parcel(const_cast(buffer), length); + int32_t pktErrCode; + + // errCode DeSerialization + uint32_t packLen = parcel.ReadInt(pktErrCode); + if (parcel.IsError()) { + return -E_INVALID_ARGS; + } + parcel.EightByteAlign(); + packLen = Parcel::GetEightByteAlign(packLen); + + // commits vector DeSerialization + std::vector> entries; + while (packLen < length) { + std::vector data; + packLen += parcel.ReadVectorChar(data); + // A valid dataItem got, Save to storage + entries.push_back(data); + if (parcel.IsError()) { + return -E_INVALID_ARGS; + } + } + MultiVerAckPacket *packet = new (std::nothrow) MultiVerAckPacket(); + if (packet == nullptr) { + LOGE("MultiVerDataSync::AckPacketDeSerialization : new packet error"); + return -E_OUT_OF_MEMORY; + } + packet->SetData(entries); + packet->SetErrorCode(pktErrCode); + int errCode = inMsg->SetExternalObject<>(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + } + return errCode; +} + +bool MultiVerDataSync::IsPacketValid(const Message *inMsg, uint16_t messageType) +{ + if ((inMsg == nullptr) || (inMsg->GetMessageId() != MULTI_VER_DATA_SYNC_MESSAGE)) { + return false; + } + if (messageType != inMsg->GetMessageType()) { + return false; + } + return true; +} + +int MultiVerDataSync::GetValidCommit(MultiVerSyncTaskContext *context, MultiVerCommitNode &commit) +{ + int commitsSize = context->GetCommitsSize(); + if (commitsSize > DBConstant::MAX_COMMIT_SIZE) { + LOGE("MultiVerDataSync::GetValidCommit failed, to large!"); + return -E_LENGTH_ERROR; + } + int index = context->GetCommitIndex(); + if (context->GetRetryStatus() == SyncTaskContext::NEED_RETRY) { + context->SetRetryStatus(SyncTaskContext::NO_NEED_RETRY); + index--; + } + index = (index < 0) ? 0 : index; + LOGD("MultiVerDataSync::GetValidCommit begin, dst=%s{private}, index = %d", context->GetDeviceId().c_str(), index); + while (index < commitsSize) { + MultiVerCommitNode commitItem; + context->GetCommit(index, commitItem); + LOGD("MultiVerDataSync::GetValidCommit , dst=%s{private}, index = %d, commitsSize = %d", + context->GetDeviceId().c_str(), index, commitsSize); + + index++; + context->SetCommitIndex(index); + if (IsCommitExisted(commitItem)) { + continue; + } + commit = commitItem; + LOGD("MultiVerDataSync::GetValidCommit ok, dst=%s{private}, commit index = %d", + context->GetDeviceId().c_str(), index); + return E_OK; + } + LOGD("MultiVerDataSync::GetValidCommit not found, dst=%s{private}", context->GetDeviceId().c_str()); + return -E_NOT_FOUND; +} + +bool MultiVerDataSync::IsCommitExisted(const MultiVerCommitNode &commit) +{ + return storagePtr_->IsCommitExisted(commit); +} + +int MultiVerDataSync::Send(const DeviceID &deviceId, const Message *inMsg) +{ + SendConfig conf = {false, false, SEND_TIME_OUT, {}}; + int errCode = communicateHandle_->SendMessage(deviceId, inMsg, conf); + if (errCode != E_OK) { + LOGE("MultiVerDataSync::Send ERR! ERR = %d", errCode); + } + return errCode; +} + +int MultiVerDataSync::SendRequestPacket(const MultiVerSyncTaskContext *context, MultiVerCommitNode &commit) +{ + MultiVerRequestPacket *packet = new (std::nothrow) MultiVerRequestPacket(); + if (packet == nullptr) { + LOGE("MultiVerRequestPacket::SendRequestPacket : new packet error"); + return -E_OUT_OF_MEMORY; + } + packet->SetCommit(commit); + Message *message = new (std::nothrow) Message(MULTI_VER_DATA_SYNC_MESSAGE); + if (message == nullptr) { + delete packet; + packet = nullptr; + LOGE("MultiVerDataSync::SendRequestPacket : new message error"); + return -E_OUT_OF_MEMORY; + } + message->SetMessageType(TYPE_REQUEST); + message->SetTarget(context->GetDeviceId()); + int errCode = message->SetExternalObject(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + delete message; + message = nullptr; + LOGE("[MultiVerDataSync][SendRequestPacket] : SetExternalObject failed errCode:%d", errCode); + return errCode; + } + message->SetSessionId(context->GetRequestSessionId()); + message->SetSequenceId(context->GetSequenceId()); + + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_DATA_ENTRY_SEND_REQUEST_TO_ACK_RECV); + } + errCode = Send(message->GetTarget(), message); + if (errCode != E_OK) { + delete message; + message = nullptr; + } + LOGD("MultiVerDataSync::SendRequestPacket end"); + return errCode; +} + +int MultiVerDataSync::SendAckPacket(const MultiVerSyncTaskContext *context, + const std::vector &dataItems, int retCode, const Message *message) +{ + if (message == nullptr) { + LOGE("MultiVerDataSync::SendAckPacket : message is nullptr"); + return -E_INVALID_ARGS; + } + + MultiVerAckPacket *packet = new (std::nothrow) MultiVerAckPacket(); + if (packet == nullptr) { + LOGE("MultiVerDataSync::SendAckPack et : packet is nullptr"); + return -E_OUT_OF_MEMORY; + } + Message *ackMessage = new (std::nothrow) Message(MULTI_VER_DATA_SYNC_MESSAGE); + if (ackMessage == nullptr) { + delete packet; + packet = nullptr; + LOGE("MultiVerDataSync::SendAckPacket : new message error"); + return -E_OUT_OF_MEMORY; + } + + std::vector> entries; + for (const auto &iter : dataItems) { + std::vector item; + iter->GetSerialData(item); + entries.push_back(item); + } + packet->SetData(entries); + packet->SetErrorCode(static_cast(retCode)); + + ackMessage->SetMessageType(TYPE_RESPONSE); + ackMessage->SetTarget(context->GetDeviceId()); + int errCode = ackMessage->SetExternalObject(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + delete ackMessage; + ackMessage = nullptr; + LOGE("[MultiVerDataSync][SendAckPacket] : SetExternalObject failed errCode:%d", errCode); + return errCode; + } + ackMessage->SetSequenceId(message->GetSequenceId()); + ackMessage->SetSessionId(message->GetSessionId()); + errCode = Send(ackMessage->GetTarget(), ackMessage); + if (errCode != E_OK) { + delete ackMessage; + ackMessage = nullptr; + } + LOGD("MultiVerDataSync::SendAckPacket end, dst=%s{private}, errCode = %d", context->GetDeviceId().c_str(), errCode); + return errCode; +} + +int MultiVerDataSync::GetCommitData(const MultiVerCommitNode &commit, std::vector &entries) +{ + return storagePtr_->GetCommitData(commit, entries); +} + +MultiVerKvEntry *MultiVerDataSync::CreateKvEntry(const std::vector &entry) +{ + return storagePtr_->CreateKvEntry(entry); +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/multi_ver_data_sync.h b/mock/distributeddb/syncer/src/multi_ver_data_sync.h new file mode 100644 index 00000000..889be32c --- /dev/null +++ b/mock/distributeddb/syncer/src/multi_ver_data_sync.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_DATA_SYNC_H +#define MULTI_VER_DATA_SYNC_H + +#ifndef OMIT_MULTI_VER +#include + +#include "icommunicator.h" +#include "multi_ver_kvdb_sync_interface.h" +#include "multi_ver_sync_task_context.h" +#include "sync_task_context.h" + +namespace DistributedDB { +class MultiVerRequestPacket { +public: + MultiVerRequestPacket() : errCode_(E_OK) {}; + ~MultiVerRequestPacket() {}; + + uint32_t CalculateLen() const; + + void SetCommit(MultiVerCommitNode &commit); + + void GetCommit(MultiVerCommitNode &commit) const; + + void SetErrCode(int32_t errCode); + + int32_t GetErrCode() const; +private: + MultiVerCommitNode commit_; + int32_t errCode_ = E_OK; +}; + +class MultiVerAckPacket { +public: + MultiVerAckPacket() : errorCode_(0) {}; + ~MultiVerAckPacket() {}; + + uint32_t CalculateLen() const; + + void SetData(std::vector> &data); + + void GetData(std::vector> &data) const; + + void SetErrorCode(int32_t errCode); + + void GetErrorCode(int32_t &errCode) const; +private: + std::vector> entries_; + int32_t errorCode_; +}; + +class MultiVerDataSync { +public: + MultiVerDataSync() : storagePtr_(nullptr), communicateHandle_(nullptr) {}; + ~MultiVerDataSync(); + DISABLE_COPY_ASSIGN_MOVE(MultiVerDataSync); + + static int RegisterTransformFunc(); + + int Initialize(MultiVerKvDBSyncInterface *storagePtr, ICommunicator *communicateHandle); + + static int Serialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int DeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static uint32_t CalculateLen(const Message *inMsg); + + void TimeOutCallback(MultiVerSyncTaskContext *context, const Message *message) const; + + int SyncStart(MultiVerSyncTaskContext *context); + + int RequestRecvCallback(const MultiVerSyncTaskContext *context, const Message *message); + + int AckRecvCallback(MultiVerSyncTaskContext *context, const Message *message); + + int PutCommitData(const MultiVerCommitNode &commit, const std::vector &entries, + const std::string &deviceName); + + int MergeSyncCommit(const MultiVerCommitNode &commit, const std::vector &commits); + + void ReleaseKvEntry(const MultiVerKvEntry *entry); + + void SendFinishedRequest(const MultiVerSyncTaskContext *context); + +private: + static int RequestPacketCalculateLen(const Message *inMsg, uint32_t &len); + + static int RequestPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int RequestPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static int AckPacketCalculateLen(const Message *inMsg, uint32_t &len); + + static int AckPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int AckPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static bool IsPacketValid(const Message *inMsg, uint16_t messageType); + + int GetValidCommit(MultiVerSyncTaskContext *context, MultiVerCommitNode &commit); + + bool IsCommitExisted(const MultiVerCommitNode &); + + int Send(const DeviceID &deviceId, const Message *inMsg); + + int SendRequestPacket(const MultiVerSyncTaskContext *context, MultiVerCommitNode &commit); + + int SendAckPacket(const MultiVerSyncTaskContext *context, const std::vector &dataItems, + int retCode, const Message *message); + + int GetCommitData(const MultiVerCommitNode &commit, std::vector &entries); + + MultiVerKvEntry *CreateKvEntry(const std::vector &entry); + + MultiVerKvDBSyncInterface *storagePtr_; + ICommunicator *communicateHandle_; +}; +} + +#endif +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/multi_ver_sync_engine.cpp b/mock/distributeddb/syncer/src/multi_ver_sync_engine.cpp new file mode 100644 index 00000000..54bbe224 --- /dev/null +++ b/mock/distributeddb/syncer/src/multi_ver_sync_engine.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_sync_engine.h" +#include "multi_ver_sync_task_context.h" + +namespace DistributedDB { +ISyncTaskContext *MultiVerSyncEngine::CreateSyncTaskContext() +{ + return new (std::nothrow) MultiVerSyncTaskContext; +} + +DEFINE_OBJECT_TAG_FACILITIES(MultiVerSyncEngine); +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/multi_ver_sync_engine.h b/mock/distributeddb/syncer/src/multi_ver_sync_engine.h new file mode 100644 index 00000000..0c23ae95 --- /dev/null +++ b/mock/distributeddb/syncer/src/multi_ver_sync_engine.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_SYNC_ENGINE_H +#define MULTI_VER_SYNC_ENGINE_H + +#ifndef OMIT_MULTI_VER +#include "sync_engine.h" + +namespace DistributedDB { +class MultiVerSyncEngine final : public SyncEngine { +public: + MultiVerSyncEngine() {}; + + DISABLE_COPY_ASSIGN_MOVE(MultiVerSyncEngine); + +protected: + ~MultiVerSyncEngine() override {}; + + // Create a context + ISyncTaskContext *CreateSyncTaskContext() override; + +private: + DECLARE_OBJECT_TAG(MultiVerSyncEngine); +}; +} // namespace DistributedDB + +#endif // MULTI_VER_SYNC_ENGINE_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/multi_ver_sync_state_machine.cpp b/mock/distributeddb/syncer/src/multi_ver_sync_state_machine.cpp new file mode 100644 index 00000000..c63868a7 --- /dev/null +++ b/mock/distributeddb/syncer/src/multi_ver_sync_state_machine.cpp @@ -0,0 +1,616 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_sync_state_machine.h" + +#include +#include +#include + +#include "message_transform.h" +#include "log_print.h" +#include "sync_types.h" +#include "db_common.h" +#include "ref_object.h" +#include "performance_analysis.h" + +namespace DistributedDB { +namespace { +void ChangeEntriesTimestamp(std::vector &entries, TimeOffset outOffset, TimeOffset timefixOffset) +{ + for (MultiVerKvEntry *entry : entries) { + if (entry == nullptr) { + continue; + } + Timestamp timestamp; + entry->GetTimestamp(timestamp); + timestamp = timestamp - static_cast(outOffset + timefixOffset); + entry->SetTimestamp(timestamp); + } +} +} +std::vector MultiVerSyncStateMachine::stateSwitchTables_; +MultiVerSyncStateMachine::MultiVerSyncStateMachine() + : context_(nullptr), + multiVerStorage_(nullptr), + timeSync_(nullptr), + commitHistorySync_(nullptr), + multiVerDataSync_(nullptr), + valueSliceSync_(nullptr) +{ +} + +MultiVerSyncStateMachine::~MultiVerSyncStateMachine() +{ + Clear(); +} + +int MultiVerSyncStateMachine::Initialize(ISyncTaskContext *context, ISyncInterface *syncInterface, + std::shared_ptr &metadata, ICommunicator *communicator) +{ + if (context == nullptr || syncInterface == nullptr || metadata == nullptr || communicator == nullptr) { + return -E_INVALID_ARGS; + } + int errCode = SyncStateMachine::Initialize(context, syncInterface, metadata, communicator); + if (errCode != E_OK) { + return errCode; + } + + timeSync_ = std::make_unique(); + commitHistorySync_ = std::make_unique(); + multiVerDataSync_ = std::make_unique(); + valueSliceSync_ = std::make_unique(); + + errCode = timeSync_->Initialize(communicator, metadata, syncInterface, context->GetDeviceId()); + if (errCode != E_OK) { + LOGE("timeSync_->Initialize failed err %d", errCode); + goto ERROR_OUT; + } + LOGD("timeSync_->Initialize OK"); + + // init functions below will never fail + multiVerStorage_ = static_cast(syncInterface); + commitHistorySync_->Initialize(multiVerStorage_, communicator); + multiVerDataSync_->Initialize(multiVerStorage_, communicator); + valueSliceSync_->Initialize(multiVerStorage_, communicator); + + context_ = static_cast(context); + currentState_ = IDLE; + (void)timeSync_->SyncStart(); + return E_OK; + +ERROR_OUT: + Clear(); + return errCode; +} + +void MultiVerSyncStateMachine::SyncStep() +{ + RefObject::IncObjRef(context_); + RefObject::IncObjRef(communicator_); + int errCode = RuntimeContext::GetInstance()->ScheduleTask( + std::bind(&MultiVerSyncStateMachine::SyncStepInnerLocked, this)); + if (errCode != E_OK) { + LOGE("[MultiVerSyncStateMachine] Schedule SyncStep failed"); + RefObject::DecObjRef(communicator_); + RefObject::DecObjRef(context_); + } +} + +void MultiVerSyncStateMachine::StepToIdle() +{ + currentState_ = IDLE; + StopWatchDog(); + context_->Clear(); + PerformanceAnalysis::GetInstance()->TimeRecordEnd(); + LOGD("[MultiVerSyncStateMachine][%s] step to idle", STR_MASK(context_->GetDeviceId())); +} + +int MultiVerSyncStateMachine::MessageCallbackCheck(const Message *inMsg) +{ + RefObject::AutoLock lock(context_); + if (context_->IsKilled()) { + return -E_OBJ_IS_KILLED; + } + if (!IsPacketValid(inMsg)) { + return -E_INVALID_ARGS; + } + if ((inMsg->GetMessageType() == TYPE_RESPONSE) && (inMsg->GetMessageId() != TIME_SYNC_MESSAGE)) { + context_->IncSequenceId(); + int errCode = ResetWatchDog(); + if (errCode != E_OK) { + LOGW("[MultiVerSyncStateMachine][MessageCallback] ResetWatchDog failed , err %d", errCode); + } + } + return E_OK; +} + +int MultiVerSyncStateMachine::ReceiveMessageCallback(Message *inMsg) +{ + if (inMsg == nullptr) { + return -E_INVALID_ARGS; + } + if (inMsg->IsFeedbackError()) { + LOGE("[MultiVerSyncStateMachine] Feedback Message with errorNo=%u.", inMsg->GetErrorNo()); + return -static_cast(inMsg->GetErrorNo()); + } + if (inMsg->GetMessageId() == TIME_SYNC_MESSAGE) { + return TimeSyncPacketRecvCallback(context_, inMsg); + } + std::lock_guard lock(stateMachineLock_); + int errCode = MessageCallbackCheck(inMsg); + if (errCode != E_OK) { + return errCode; + } + switch (inMsg->GetMessageId()) { + case COMMIT_HISTORY_SYNC_MESSAGE: + errCode = CommitHistorySyncPktRecvCallback(context_, inMsg); + if ((errCode != -E_NOT_FOUND) && (inMsg->GetMessageType() == TYPE_REQUEST) && (errCode != -E_NOT_PERMIT)) { + SyncResponseBegin(inMsg->GetSessionId()); + } + break; + case MULTI_VER_DATA_SYNC_MESSAGE: + errCode = MultiVerDataPktRecvCallback(context_, inMsg); + break; + case VALUE_SLICE_SYNC_MESSAGE: + errCode = ValueSlicePktRecvCallback(context_, inMsg); + break; + default: + errCode = -E_NOT_SUPPORT; + break; + } + if (errCode == -E_LAST_SYNC_FRAME) { + SyncResponseEnd(inMsg->GetSessionId()); + return errCode; + } + if (errCode != E_OK && inMsg->GetMessageType() == TYPE_RESPONSE) { + Abort(); + } + return errCode; +} + +void MultiVerSyncStateMachine::StepToTimeout(TimerId timerId) +{ + { + std::lock_guard lock(stateMachineLock_); + TimerId timer = syncContext_->GetTimerId(); + if (timer != timerId) { + return; + } + currentState_ = SYNC_TIME_OUT; + } + Abort(); +} + +int MultiVerSyncStateMachine::CommitHistorySyncStepInner(void) +{ + int errCode = commitHistorySync_->SyncStart(context_); + if (errCode != E_OK) { + LOGE("[MultiVerSyncStateMachine][CommitHistorySyncStep] failed, errCode %d", errCode); + } + return errCode; +} + +int MultiVerSyncStateMachine::MultiVerDataSyncStepInner(void) +{ + return multiVerDataSync_->SyncStart(context_); +} + +int MultiVerSyncStateMachine::ValueSliceSyncStepInner(void) +{ + return valueSliceSync_->SyncStart(context_); +} + +void MultiVerSyncStateMachine::SyncStepInnerLocked() +{ + if (context_->IncUsedCount() != E_OK) { + goto SYNC_STEP_OUT; + } + + LOGD("[MultiVerSyncStateMachine] SyncStep dst=%s, state = %d", STR_MASK(context_->GetDeviceId()), currentState_); + int errCode; + { + std::lock_guard lock(stateMachineLock_); + switch (currentState_) { + case COMMIT_HISTORY_SYNC: + errCode = CommitHistorySyncStepInner(); + if (errCode != E_OK) { + Abort(); + } + break; + case MULTI_VER_DATA_ENTRY_SYNC: + errCode = MultiVerDataSyncStepInner(); + if (errCode == -E_NOT_FOUND) { + Finish(); + goto SYNC_STEP_SAFE_OUT; + } + break; + case MULTI_VER_VALUE_SLICE_SYNC: + errCode = ValueSliceSyncStepInner(); + if (errCode == -E_NOT_FOUND) { + int err = OneCommitSyncFinish(); + if (err != E_OK) { + valueSliceSync_->SendFinishedRequest(context_); + Abort(); + goto SYNC_STEP_SAFE_OUT; + } + currentState_ = MULTI_VER_DATA_ENTRY_SYNC; + SyncStep(); + goto SYNC_STEP_SAFE_OUT; + } + break; + default: + break; + } + } + +SYNC_STEP_SAFE_OUT: + context_->SafeExit(); + +SYNC_STEP_OUT: + RefObject::DecObjRef(communicator_); + RefObject::DecObjRef(context_); +} + +void MultiVerSyncStateMachine::SyncStepInner() +{ +} + +int MultiVerSyncStateMachine::StartSyncInner() +{ + LOGI("[MultiVerSyncStateMachine] StartSync"); + currentState_ = COMMIT_HISTORY_SYNC; + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->TimeRecordStart(); + } + int errCode = StartWatchDog(); + if (errCode != E_OK) { + LOGE("[MultiVerSyncStateMachine][StartSync] WatchDog start failed! err:%d", errCode); + return errCode; + } + SyncStep(); + return E_OK; +} + +void MultiVerSyncStateMachine::AbortInner() +{ + context_->Clear(); + StepToIdle(); + ExecNextTask(); +} + +const std::vector &MultiVerSyncStateMachine::GetStateSwitchTables() const +{ + return stateSwitchTables_; +} + +int MultiVerSyncStateMachine::PrepareNextSyncTask() +{ + return StartSyncInner(); +} + +void MultiVerSyncStateMachine::SendSaveDataNotifyPacket(uint32_t sessionId, uint32_t sequenceId, uint32_t inMsgId) +{ + (void)sessionId; + (void)sequenceId; + (void)inMsgId; +} + +void MultiVerSyncStateMachine::CommErrAbort() +{ + std::lock_guard lock(stateMachineLock_); + Abort(); + RefObject::DecObjRef(context_); +} + +int MultiVerSyncStateMachine::TimeSyncPacketRecvCallback(const MultiVerSyncTaskContext *context, const Message *inMsg) +{ + int errCode; + if ((context == nullptr) || (inMsg == nullptr) || (inMsg->GetMessageId() != TIME_SYNC_MESSAGE)) { + return -E_INVALID_ARGS; + } + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + errCode = timeSync_->RequestRecv(inMsg); + return errCode; + case TYPE_RESPONSE: + errCode = timeSync_->AckRecv(inMsg); + if (errCode != E_OK) { + LOGE("[MultiVerSyncStateMachine] TimeSyncPacketRecvCallback AckRecv failed err %d", errCode); + } + return errCode; + default: + return -E_INVALID_ARGS; + } +} + +int MultiVerSyncStateMachine::CommitHistorySyncPktRecvCallback(MultiVerSyncTaskContext *context, const Message *inMsg) +{ + if ((context == nullptr) || (inMsg == nullptr) || (inMsg->GetMessageId() != COMMIT_HISTORY_SYNC_MESSAGE)) { + return -E_INVALID_ARGS; + } + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + int errCode; + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_SEND_LOCAL_DATA_CHANGED_TO_COMMIT_REQUEST_RECV); + } + return commitHistorySync_->RequestRecvCallback(context, inMsg); + case TYPE_RESPONSE: + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_COMMIT_SEND_REQUEST_TO_ACK_RECV); + } + errCode = commitHistorySync_->AckRecvCallback(context, inMsg); + if (errCode != E_OK) { + return errCode; + } + currentState_ = MULTI_VER_DATA_ENTRY_SYNC; + SyncStep(); + return errCode; + default: + return -E_INVALID_ARGS; + } +} + +int MultiVerSyncStateMachine::MultiVerDataPktRecvCallback(MultiVerSyncTaskContext *context, const Message *inMsg) +{ + if ((context == nullptr) || (inMsg == nullptr) || (inMsg->GetMessageId() != MULTI_VER_DATA_SYNC_MESSAGE)) { + return -E_INVALID_ARGS; + } + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + int errCode; + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return multiVerDataSync_->RequestRecvCallback(context, inMsg); + case TYPE_RESPONSE: + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_DATA_ENTRY_SEND_REQUEST_TO_ACK_RECV); + } + errCode = multiVerDataSync_->AckRecvCallback(context, inMsg); + if (errCode != E_OK) { + multiVerDataSync_->SendFinishedRequest(context); + return errCode; + } + currentState_ = MULTI_VER_VALUE_SLICE_SYNC; + SyncStep(); + return errCode; + default: + return -E_INVALID_ARGS; + } +} + +int MultiVerSyncStateMachine::ValueSlicePktRecvCallback(MultiVerSyncTaskContext *context, const Message *inMsg) +{ + if ((context == nullptr) || (inMsg == nullptr) || (inMsg->GetMessageId() != VALUE_SLICE_SYNC_MESSAGE)) { + return -E_INVALID_ARGS; + } + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + int errCode; + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return valueSliceSync_->RequestRecvCallback(context, inMsg); + case TYPE_RESPONSE: + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_VALUE_SLICE_SEND_REQUEST_TO_ACK_RECV); + } + errCode = valueSliceSync_->AckRecvCallback(context, inMsg); + if (errCode != E_OK) { + valueSliceSync_->SendFinishedRequest(context); + return errCode; + } + currentState_ = MULTI_VER_VALUE_SLICE_SYNC; + SyncStep(); + return errCode; + default: + return -E_INVALID_ARGS; + } +} + +void MultiVerSyncStateMachine::Finish() +{ + MultiVerCommitNode commit; + std::vector commits; + int commitsSize = context_->GetCommitsSize(); + if (commitsSize > 0) { + context_->GetCommit(commitsSize - 1, commit); + context_->GetCommits(commits); + LOGD("MultiVerSyncStateMachine::Finish merge src=%s", STR_MASK(context_->GetDeviceId())); + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_MERGE); + } + int errCode = multiVerDataSync_->MergeSyncCommit(commit, commits); + LOGD("MultiVerSyncStateMachine::Finish merge src=%s, MergeSyncCommit errCode:%d", + STR_MASK(context_->GetDeviceId()), errCode); + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_MERGE); + } + } + RefObject::AutoLock lock(context_); + context_->SetOperationStatus(SyncOperation::OP_FINISHED_ALL); + StepToIdle(); + ExecNextTask(); +} + +int MultiVerSyncStateMachine::OneCommitSyncFinish() +{ + MultiVerCommitNode commit; + std::vector entries; + std::string deviceName; + TimeOffset outOffset = 0; + int errCode = E_OK; + int commitIndex = context_->GetCommitIndex(); + + LOGD("MultiVerSyncStateMachine::OneCommitSyncFinish src=%s, commitIndex = %d,", STR_MASK(context_->GetDeviceId()), + commitIndex); + if (commitIndex > 0) { + context_->GetCommit(commitIndex - 1, commit); + deviceName = context_->GetDeviceId(); + context_->GetEntries(entries); + LOGD("MultiVerSyncStateMachine::OneCommitSyncFinish src=%s, entries size = %lu", + STR_MASK(context_->GetDeviceId()), entries.size()); + errCode = timeSync_->GetTimeOffset(outOffset, TIME_SYNC_WAIT_TIME); + if (errCode != E_OK) { + LOGI("MultiVerSyncStateMachine::OneCommitSyncFinish GetTimeOffset fail errCode:%d", errCode); + return errCode; + } + Timestamp currentLocalTime = context_->GetCurrentLocalTime(); + commit.timestamp -= static_cast(outOffset); + + // Due to time sync error, commit timestamp may bigger than currentLocalTime, we need to fix the timestamp + TimeOffset timefixOffset = (commit.timestamp < currentLocalTime) ? 0 : (commit.timestamp - + static_cast(currentLocalTime)); + LOGD("MultiVerSyncStateMachine::OneCommitSyncFinish src=%s, timefixOffset = %" PRId64, + STR_MASK(context_->GetDeviceId()), timefixOffset); + commit.timestamp -= static_cast(timefixOffset); + ChangeEntriesTimestamp(entries, outOffset, timefixOffset); + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_PUT_COMMIT_DATA); + } + errCode = multiVerDataSync_->PutCommitData(commit, entries, deviceName); + LOGD("MultiVerSyncStateMachine::OneCommitSyncFinish PutCommitData src=%s, errCode = %d", + STR_MASK(context_->GetDeviceId()), errCode); + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_PUT_COMMIT_DATA); + } + if (errCode == E_OK) { + context_->ReleaseEntries(); + } + } + DBCommon::PrintHexVector(commit.commitId, __LINE__); + return errCode; +} + +bool MultiVerSyncStateMachine::IsPacketValid(const Message *inMsg) const +{ + if (inMsg == nullptr) { + return false; + } + + if ((inMsg->GetMessageId() < TIME_SYNC_MESSAGE) || (inMsg->GetMessageId() > VALUE_SLICE_SYNC_MESSAGE) || + (inMsg->GetMessageId() == DATA_SYNC_MESSAGE)) { + LOGE("[MultiVerSyncStateMachine] Message is invalid, id = %d", inMsg->GetMessageId()); + return false; + } + if (inMsg->GetMessageId() == TIME_SYNC_MESSAGE) { + return true; + } + if (inMsg->GetMessageType() == TYPE_RESPONSE) { + if ((inMsg->GetSequenceId() != context_->GetSequenceId()) || + (inMsg->GetSessionId() != context_->GetRequestSessionId())) { + LOGE("[MultiVerSyncStateMachine] Message is invalid, inMsg SequenceId = %d, context seq = %d," + "msg session id = %d, context session = %d", inMsg->GetSequenceId(), context_->GetSequenceId(), + inMsg->GetSessionId(), context_->GetRequestSessionId()); + return false; + } + } + return true; +} + +void MultiVerSyncStateMachine::Clear() +{ + commitHistorySync_ = nullptr; + multiVerDataSync_ = nullptr; + timeSync_ = nullptr; + valueSliceSync_ = nullptr; + multiVerStorage_ = nullptr; + context_ = nullptr; +} + +void MultiVerSyncStateMachine::SyncResponseBegin(uint32_t sessionId) +{ + { + std::lock_guard lock(responseInfosLock_); + auto iter = std::find_if(responseInfos_.begin(), responseInfos_.end(), [sessionId](const ResponseInfo &info) { + return info.sessionId == sessionId; + }); + if (iter != responseInfos_.end()) { + LOGE("[MultiVerSyncStateMachine][SyncResponseEnd] sessionId existed! exit."); + return; + } + TimerAction timeOutCallback = + std::bind(&MultiVerSyncStateMachine::SyncResponseTimeout, this, std::placeholders::_1); + // To make sure context_ alive in timeout callback, we should IncObjRef for the context_. + RefObject::IncObjRef(context_); + TimerId timerId = 0; + int errCode = RuntimeContext::GetInstance()->SetTimer( + RESPONSE_TIME_OUT, timeOutCallback, + [this]() { + int ret = RuntimeContext::GetInstance()->ScheduleTask([this](){ RefObject::DecObjRef(context_); }); + if (ret != E_OK) { + LOGE("[MultiVerSyncStateMachine][SyncResponseEnd] timer finalizer ScheduleTask, errCode %d", ret); + } + }, + timerId); + if (errCode != E_OK) { + LOGE("[MultiVerSyncStateMachine][ResponseSessionBegin] SetTimer failed err %d", errCode); + RefObject::DecObjRef(context_); + return; + } + ResponseInfo info{sessionId, timerId}; + responseInfos_.push_back(info); + LOGI("[MultiVerSyncStateMachine][SyncResponseBegin] begin"); + } + multiVerStorage_->NotifyStartSyncOperation(); +} + +void MultiVerSyncStateMachine::SyncResponseEnd(uint32_t sessionId) +{ + { + std::lock_guard lock(responseInfosLock_); + auto iter = std::find_if(responseInfos_.begin(), responseInfos_.end(), [sessionId](const ResponseInfo &info) { + return info.sessionId == sessionId; + }); + if (iter == responseInfos_.end()) { + LOGW("[MultiVerSyncStateMachine][SyncResponseEnd] Can't find sync response %d", sessionId); + return; + } + RuntimeContext::GetInstance()->RemoveTimer(iter->timerId); + responseInfos_.erase(iter); + LOGI("[MultiVerSyncStateMachine][SyncResponseBegin] end response"); + } + multiVerStorage_->NotifyFinishSyncOperation(); +} + +int MultiVerSyncStateMachine::SyncResponseTimeout(TimerId timerId) +{ + uint32_t sessionId; + { + std::lock_guard lock(responseInfosLock_); + auto iter = std::find_if(responseInfos_.begin(), responseInfos_.end(), [timerId](const ResponseInfo &info) { + return info.timerId == timerId; + }); + if (iter == responseInfos_.end()) { + LOGW("[MultiVerSyncStateMachine][SyncResponseTimeout] Can't find sync response timerId %" PRIu64, timerId); + return E_OK; + } + sessionId = iter->sessionId; + } + SyncResponseEnd(sessionId); + return E_OK; +} + +bool MultiVerSyncStateMachine::IsNeedTriggerQueryAutoSync(Message *inMsg, QuerySyncObject &query) +{ + (void) inMsg; + (void) query; + return false; +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/multi_ver_sync_state_machine.h b/mock/distributeddb/syncer/src/multi_ver_sync_state_machine.h new file mode 100644 index 00000000..ea02f5d4 --- /dev/null +++ b/mock/distributeddb/syncer/src/multi_ver_sync_state_machine.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_SYNC_STATE_MACHINE_H +#define MULTI_VER_SYNC_STATE_MACHINE_H + +#ifndef OMIT_MULTI_VER +#include + +#include "commit_history_sync.h" +#include "db_types.h" +#include "meta_data.h" +#include "multi_ver_data_sync.h" +#include "multi_ver_sync_task_context.h" +#include "sync_state_machine.h" +#include "time_sync.h" +#include "value_slice_sync.h" + +namespace DistributedDB { +class MultiVerSyncStateMachine final : public SyncStateMachine { +public: + struct ResponseInfo { + uint32_t sessionId = 0; + TimerId timerId = 0; + }; + + MultiVerSyncStateMachine(); + ~MultiVerSyncStateMachine() override; + + // Init the MultiVerSyncStateMachine + int Initialize(ISyncTaskContext *context, ISyncInterface *syncInterface, std::shared_ptr &metadata, + ICommunicator *communicator) override; + + // send Message to the StateMachine + int ReceiveMessageCallback(Message *inMsg) override; + + // Called by CommErrHandler, used to abort sync when handle err + void CommErrAbort() override; + + DISABLE_COPY_ASSIGN_MOVE(MultiVerSyncStateMachine); + +protected: + // Step the MultiVerSyncStateMachine + void SyncStep() override; + + // SyncOperation is timeout, step to timeout state + void StepToTimeout(TimerId timerId) override; + + void SyncStepInnerLocked() override; + + void SyncStepInner() override; + + void AbortInner() override; + + int StartSyncInner() override; + + const std::vector &GetStateSwitchTables() const override; + + // Do some init for run a next sync task + int PrepareNextSyncTask() override; + + // Called by StartSaveDataNotifyTimer, used to send a save data notify packet + void SendSaveDataNotifyPacket(uint32_t sessionId, uint32_t sequenceId, uint32_t inMsgId) override; + + bool IsNeedTriggerQueryAutoSync(Message *inMsg, QuerySyncObject &query) override; + +private: + enum State { + IDLE, + TIME_SYNC, + COMMIT_HISTORY_SYNC, + MULTI_VER_DATA_ENTRY_SYNC, + MULTI_VER_VALUE_SLICE_SYNC, + SYNC_TIME_OUT, + INNER_ERR + }; + + void StepToIdle(); + + int MessageCallbackCheck(const Message *inMsg); + + int CommitHistorySyncStepInner(void); + + int MultiVerDataSyncStepInner(void); + + int ValueSliceSyncStepInner(void); + + int TimeSyncPacketRecvCallback(const MultiVerSyncTaskContext *context, const Message *inMsg); + + int CommitHistorySyncPktRecvCallback(MultiVerSyncTaskContext *context, const Message *inMsg); + + int MultiVerDataPktRecvCallback(MultiVerSyncTaskContext *context, const Message *inMsg); + + int ValueSlicePktRecvCallback(MultiVerSyncTaskContext *context, const Message *inMsg); + + void Finish(); + + int OneCommitSyncFinish(); + + bool IsPacketValid(const Message *inMsg) const; + + void Clear(); + + // Mark sync response is begin now, we should disable real delete + void SyncResponseBegin(uint32_t sessionId); + + // Mark sync response is finished, we should enable real delete + void SyncResponseEnd(uint32_t sessionId); + + // Mark sync response may has an err, has not received finish ack, we should enable real delete + int SyncResponseTimeout(TimerId timerId); + + static const int RESPONSE_TIME_OUT = 30 * 1000; // 30s + + static std::vector stateSwitchTables_; + MultiVerSyncTaskContext *context_; + MultiVerKvDBSyncInterface *multiVerStorage_; + std::mutex responseInfosLock_; + std::list responseInfos_; + std::unique_ptr timeSync_; + std::unique_ptr commitHistorySync_; + std::unique_ptr multiVerDataSync_; + std::unique_ptr valueSliceSync_; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_SYNC_STATE_MACHINE_H +#endif diff --git a/mock/distributeddb/syncer/src/multi_ver_sync_target.h b/mock/distributeddb/syncer/src/multi_ver_sync_target.h new file mode 100644 index 00000000..aea80481 --- /dev/null +++ b/mock/distributeddb/syncer/src/multi_ver_sync_target.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_SYNC_TASK_INFO_H +#define MULTI_VER_SYNC_TASK_INFO_H + +#include "sync_target.h" + +namespace DistributedDB { +class MultiVerSyncTarget final : public SyncTarget { +}; +} // namespace DistributedDB + +#endif // MULTI_VER_SYNC_TASK_INFO_H diff --git a/mock/distributeddb/syncer/src/multi_ver_sync_task_context.cpp b/mock/distributeddb/syncer/src/multi_ver_sync_task_context.cpp new file mode 100644 index 00000000..77e522b1 --- /dev/null +++ b/mock/distributeddb/syncer/src/multi_ver_sync_task_context.cpp @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "db_common.h" +#include "db_dfx_adapter.h" +#include "log_print.h" +#include "multi_ver_sync_state_machine.h" +#include "multi_ver_sync_target.h" +#include "multi_ver_sync_task_context.h" + +namespace DistributedDB { +DEFINE_OBJECT_TAG_FACILITIES(MultiVerSyncTaskContext) + +MultiVerSyncTaskContext::~MultiVerSyncTaskContext() +{ +} + +int MultiVerSyncTaskContext::Initialize(const std::string &deviceId, ISyncInterface *syncInterface, + std::shared_ptr &metadata, ICommunicator *communicator) +{ + if (deviceId.empty() || (syncInterface == nullptr) || (communicator == nullptr)) { + return -E_INVALID_ARGS; + } + syncInterface_ = syncInterface; + communicator_ = communicator; + deviceId_ = deviceId; + taskExecStatus_ = INIT; + isAutoSync_ = true; + timeHelper_ = std::make_unique(); + int errCode = timeHelper_->Initialize(syncInterface, metadata); + if (errCode != E_OK) { + LOGE("[MultiVerSyncTaskContext] timeHelper Initialize failed, err %d.", errCode); + return errCode; + } + + stateMachine_ = new (std::nothrow) MultiVerSyncStateMachine; + if (stateMachine_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + + errCode = stateMachine_->Initialize(this, syncInterface, metadata, communicator); + TimerAction timeOutCallback = std::bind(&SyncStateMachine::TimeoutCallback, + static_cast(stateMachine_), + std::placeholders::_1); + SetTimeoutCallback(timeOutCallback); + OnKill([this]() { this->KillWait(); }); + { + std::lock_guard lock(synTaskContextSetLock_); + synTaskContextSet_.insert(this); + } + std::vector label = syncInterface_->GetIdentifier(); + label.resize(3); // only show 3 bytes + syncActionName_ = DBDfxAdapter::SYNC_ACTION + "_" + + DBCommon::VectorToHexString(label) + "_" + deviceId_.c_str(); + return errCode; +} + +int MultiVerSyncTaskContext::AddSyncOperation(SyncOperation *operation) +{ + if (operation == nullptr) { + return -E_INVALID_ARGS; + } + + if (operation->IsAutoSync() && !IsTargetQueueEmpty()) { + LOGI("[MultiVerSyncTaskContext] Exist operation in queue, skip it!"); + operation->SetStatus(deviceId_, SyncOperation::OP_FINISHED_ALL); + return E_OK; + } + + MultiVerSyncTarget *target = new (std::nothrow) MultiVerSyncTarget; + if (target == nullptr) { + return -E_OUT_OF_MEMORY; + } + target->SetSyncOperation(operation); + target->SetTaskType(ISyncTarget::REQUEST); + AddSyncTarget(target); + return E_OK; +} + +int MultiVerSyncTaskContext::GetCommitIndex() const +{ + return commitsIndex_; +} + +void MultiVerSyncTaskContext::SetCommitIndex(int index) +{ + commitsIndex_ = index; +} + +int MultiVerSyncTaskContext::GetEntriesIndex() const +{ + return entriesIndex_; +} + +void MultiVerSyncTaskContext::SetEntriesIndex(int index) +{ + entriesIndex_ = index; +} + +int MultiVerSyncTaskContext::GetValueSlicesIndex() const +{ + return valueSlicesIndex_; +} + +void MultiVerSyncTaskContext::SetValueSlicesIndex(int index) +{ + valueSlicesIndex_ = index; +} + +void MultiVerSyncTaskContext::GetCommits(std::vector &commits) +{ + commits = commits_; +} + +void MultiVerSyncTaskContext::SetCommits(const std::vector &commits) +{ + commits_ = commits; +} + +void MultiVerSyncTaskContext::GetCommit(int index, MultiVerCommitNode &commit) const +{ + commit = commits_[index]; +} + +void MultiVerSyncTaskContext::SetCommit(int index, const MultiVerCommitNode &commit) +{ + commits_[index] = commit; +} + +void MultiVerSyncTaskContext::SetEntries(const std::vector &entries) +{ + entries_ = entries; +} + +void MultiVerSyncTaskContext::ReleaseEntries(void) +{ + for (auto &item : entries_) { + if (syncInterface_ != nullptr) { + static_cast(syncInterface_)->ReleaseKvEntry(item); + } + item = nullptr; + } + entries_.clear(); + entries_.shrink_to_fit(); +} + +void MultiVerSyncTaskContext::GetEntries(std::vector &entries) const +{ + entries = entries_; +} + +void MultiVerSyncTaskContext::GetEntry(int index, MultiVerKvEntry *&entry) +{ + entry = entries_[index]; +} + +void MultiVerSyncTaskContext::SetCommitsSize(int commitsSize) +{ + commitsSize_ = commitsSize; +} + +int MultiVerSyncTaskContext::GetCommitsSize() const +{ + return commitsSize_; +} + +void MultiVerSyncTaskContext::SetEntriesSize(int entriesSize) +{ + entriesSize_ = entriesSize; +} + +int MultiVerSyncTaskContext::GetEntriesSize() const +{ + return entriesSize_; +} + +void MultiVerSyncTaskContext::SetValueSlicesSize(int valueSlicesSize) +{ + valueSlicesSize_ = valueSlicesSize; +} + +int MultiVerSyncTaskContext::GetValueSlicesSize() const +{ + return valueSlicesSize_; +} + +void MultiVerSyncTaskContext::GetValueSliceHashNode(int index, ValueSliceHash &hashNode) const +{ + hashNode = valueSliceHashNodes_[index]; +} + +void MultiVerSyncTaskContext::SetValueSliceHashNodes(const std::vector &valueSliceHashNodes) +{ + valueSliceHashNodes_ = valueSliceHashNodes; +} + +void MultiVerSyncTaskContext::GetValueSliceHashNodes(std::vector &valueSliceHashNodes) const +{ + valueSliceHashNodes = valueSliceHashNodes_; +} + +void MultiVerSyncTaskContext::Clear() +{ + commits_.clear(); + commits_.shrink_to_fit(); + ReleaseEntries(); + valueSliceHashNodes_.clear(); + valueSliceHashNodes_.shrink_to_fit(); + commitsIndex_ = 0; + commitsSize_ = 0; + entriesIndex_ = 0; + entriesSize_ = 0; + valueSlicesIndex_ = 0; + valueSlicesSize_ = 0; + retryTime_ = 0; + isNeedRetry_ = NO_NEED_RETRY; + StopTimer(); + sequenceId_ = 1; // minimum valid ID : 1 + syncId_ = 0; +} + +void MultiVerSyncTaskContext::CopyTargetData(const ISyncTarget *target, const TaskParam &taskParam) +{ + SyncTaskContext::CopyTargetData(target, taskParam); +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/multi_ver_sync_task_context.h b/mock/distributeddb/syncer/src/multi_ver_sync_task_context.h new file mode 100644 index 00000000..054e9759 --- /dev/null +++ b/mock/distributeddb/syncer/src/multi_ver_sync_task_context.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_SYNC_TASK_CONTEXT_H +#define MULTI_VER_SYNC_TASK_CONTEXT_H + +#ifndef OMIT_MULTI_VER +#include "multi_ver_kvdb_sync_interface.h" +#include "sync_task_context.h" + +namespace DistributedDB { +class MultiVerSyncTaskContext final : public SyncTaskContext { +public: + MultiVerSyncTaskContext() {}; + + DISABLE_COPY_ASSIGN_MOVE(MultiVerSyncTaskContext); + + // Init the MultiVerSyncTaskContext + int Initialize(const std::string &deviceId, ISyncInterface *syncInterface, std::shared_ptr &metadata, + ICommunicator *communicator) override; + + // Add a sync task target with the operation to the queue + int AddSyncOperation(SyncOperation *operation) override; + + int GetCommitIndex() const; + + void SetCommitIndex(int index); + + int GetEntriesIndex() const; + + void SetEntriesIndex(int index); + + int GetValueSlicesIndex() const; + + void SetValueSlicesIndex(int index); + + void GetCommits(std::vector &commits); + + void SetCommits(const std::vector &commits); + + void GetCommit(int index, MultiVerCommitNode &commit) const; + + void SetCommit(int index, const MultiVerCommitNode &commit); + + void SetEntries(const std::vector &entries); + + void ReleaseEntries(void); + + void GetEntries(std::vector &entries) const; + + void GetEntry(int index, MultiVerKvEntry *&entry); + + void SetCommitsSize(int commitsSize); + + int GetCommitsSize() const; + + void SetEntriesSize(int entriesSize); + + int GetEntriesSize() const; + + void SetValueSlicesSize(int valueSlicesSize); + + int GetValueSlicesSize() const; + + void GetValueSliceHashNode(int index, ValueSliceHash &hashNode) const; + + void SetValueSliceHashNodes(const std::vector &valueSliceHashNodes); + + void GetValueSliceHashNodes(std::vector &valueSliceHashNodes) const; + + void Clear() override; + +protected: + ~MultiVerSyncTaskContext() override; + + void CopyTargetData(const ISyncTarget *target, const TaskParam &taskParam) override; + +private: + DECLARE_OBJECT_TAG(MultiVerSyncTaskContext); + + std::vector commits_; + std::vector entries_; + std::vector valueSliceHashNodes_; + int commitsIndex_ = 0; + int commitsSize_ = 0; + int entriesIndex_ = 0; + int entriesSize_ = 0; + int valueSlicesIndex_ = 0; + int valueSlicesSize_ = 0; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_SYNC_TASK_CONTEXT_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/multi_ver_syncer.cpp b/mock/distributeddb/syncer/src/multi_ver_syncer.cpp new file mode 100644 index 00000000..96018b9c --- /dev/null +++ b/mock/distributeddb/syncer/src/multi_ver_syncer.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "multi_ver_syncer.h" + +#include +#include +#include + +#include "multi_ver_sync_engine.h" +#include "multi_ver_kvdb_sync_interface.h" +#include "log_print.h" + +namespace DistributedDB { +MultiVerSyncer::MultiVerSyncer() + : autoSyncEnable_(true) +{ +} + +MultiVerSyncer::~MultiVerSyncer() +{ +} + +void MultiVerSyncer::EnableAutoSync(bool enable) +{ + LOGD("[Syncer] EnableAutoSync enable = %d", enable); + if (autoSyncEnable_ == enable) { + return; + } + + autoSyncEnable_ = enable; + if (!enable || (syncEngine_ == nullptr)) { + return; + } + + std::vector devices; + GetOnlineDevices(devices); + if (devices.empty()) { + return; + } + + int errCode = Sync(devices, SyncModeType::AUTO_PULL, nullptr, nullptr, false); + if (errCode != E_OK) { + LOGE("[Syncer] sync start by EnableAutoSync failed err %d", errCode); + } +} + +int MultiVerSyncer::EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash) +{ + return EraseDeviceWaterMark(deviceId, isNeedHash, ""); +} + +int MultiVerSyncer::EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash, + const std::string &tableName) +{ + return -E_NOT_SUPPORT; +} + +void MultiVerSyncer::LocalDataChanged(int notifyEvent) +{ + if (!initialized_) { + LOGE("[Syncer] Syncer has not Init"); + return; + } + + if (!autoSyncEnable_) { + return; + } + + syncEngine_->BroadCastDataChanged(); +} + +void MultiVerSyncer::RemoteDataChanged(const std::string &device) +{ + LOGD("[MultiVerSyncer] Remote data changed or device online dev %s{private}", device.c_str()); + if (autoSyncEnable_) { + std::vector devices {device}; + int errCode = Sync(devices, SyncModeType::AUTO_PULL, nullptr, nullptr, false); + if (errCode != E_OK) { + LOGE("[MultiVerSyncer] sync start by RemoteDataChanged failed err %d", errCode); + } + } +} + +void MultiVerSyncer::RemoteDeviceOffline(const std::string &device) +{ + (void) device; +} + +ISyncEngine *MultiVerSyncer::CreateSyncEngine() +{ + return new (std::nothrow) MultiVerSyncEngine(); +} + +void MultiVerSyncer::AddSyncOperation(SyncOperation *operation) +{ + MultiVerKvDBSyncInterface *syncInterface = static_cast(syncInterface_); + syncInterface->NotifyStartSyncOperation(); + GenericSyncer::AddSyncOperation(operation); +} + +void MultiVerSyncer::SyncOperationKillCallbackInner(int syncId) +{ + if (syncInterface_ != nullptr) { + LOGI("[MultiVerSyncer] Operation on kill id = %d", syncId); + MultiVerKvDBSyncInterface *syncInterface = static_cast(syncInterface_); + syncInterface->NotifyFinishSyncOperation(); + } + GenericSyncer::SyncOperationKillCallbackInner(syncId); +} + +int MultiVerSyncer::SetStaleDataWipePolicy(WipePolicy policy) +{ + (void) policy; + return -E_NOT_SUPPORT; +} +} // namespace DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/multi_ver_syncer.h b/mock/distributeddb/syncer/src/multi_ver_syncer.h new file mode 100644 index 00000000..37b43128 --- /dev/null +++ b/mock/distributeddb/syncer/src/multi_ver_syncer.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_SYNCER_H +#define MULTI_VER_SYNCER_H + +#ifndef OMIT_MULTI_VER +#include "generic_syncer.h" +#include "macro_utils.h" + +namespace DistributedDB { +class MultiVerSyncer final : public GenericSyncer { +public: + MultiVerSyncer(); + ~MultiVerSyncer() override; + + // Enable auto sync function + void EnableAutoSync(bool enable) override; + + // delete specified device's watermark + int EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash) override; + + // delete specified device's and table's watermark + int EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash, + const std::string &tableName) override; + + // Local data changed callback + void LocalDataChanged(int notifyEvent) override; + + // Remote data changed callback + void RemoteDataChanged(const std::string &device) override; + + void RemoteDeviceOffline(const std::string &device) override; + + // Set stale data wipe policy + int SetStaleDataWipePolicy(WipePolicy policy) override; + +protected: + // Create a sync engine, if has memory error, will return nullptr. + ISyncEngine *CreateSyncEngine() override; + + // Add a Sync Operation, after call this function, the operation will be start + void AddSyncOperation(SyncOperation *operation) override; + + // Used to set to the SyncOperation Onkill + void SyncOperationKillCallbackInner(int syncId) override; + +private: + bool autoSyncEnable_; +}; +} // namespace DistributedDB + +#endif // MULTI_VER_SYNCER_H +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/query_sync_water_mark_helper.cpp b/mock/distributeddb/syncer/src/query_sync_water_mark_helper.cpp new file mode 100644 index 00000000..4fbeb8a0 --- /dev/null +++ b/mock/distributeddb/syncer/src/query_sync_water_mark_helper.cpp @@ -0,0 +1,595 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "query_sync_water_mark_helper.h" + +#include +#include +#include "platform_specific.h" +#include "parcel.h" +#include "db_errno.h" +#include "db_common.h" +#include "log_print.h" + +namespace DistributedDB { +namespace { + const int MAX_CACHE_ITEMS = 200; + const uint32_t MAX_STORE_ITEMS = 100000; + // WaterMark Version + constexpr uint32_t QUERY_WATERMARK_VERSION_CURRENT = SOFTWARE_VERSION_RELEASE_6_0; + constexpr uint32_t DELETE_WATERMARK_VERSION_CURRENT = SOFTWARE_VERSION_RELEASE_3_0; + // Prefix Key in db + const std::string QUERY_SYNC_PREFIX_KEY = "querySync"; + const std::string DELETE_SYNC_PREFIX_KEY = "deleteSync"; +} + +QuerySyncWaterMarkHelper::QuerySyncWaterMarkHelper() + : storage_(nullptr) +{} + +QuerySyncWaterMarkHelper::~QuerySyncWaterMarkHelper() +{ + storage_ = nullptr; + deviceIdToHashQuerySyncIdMap_.clear(); + deleteSyncCache_.clear(); + deviceIdToHashDeleteSyncIdMap_.clear(); +} + +int LruMap::Put(const std::string &key, const QueryWaterMark &inValue) +{ + std::lock_guard autoLock(lruLock_); + cache_[key] = inValue; + return Elimination(key, inValue); +} + +int LruMap::Get(const std::string &key, QueryWaterMark &outValue) +{ + std::lock_guard autoLock(lruLock_); + if (cache_.find(key) == cache_.end()) { + return -E_NOT_FOUND; + } + outValue = cache_[key]; + return Elimination(key, outValue); +} + +void LruMap::RemoveWithPrefixKey(const std::string &prefixKey) +{ + std::lock_guard autoLock(lruLock_); + auto iterator = eliminationChain_.begin(); + while (iterator != eliminationChain_.end()) { + const std::string &key = (*iterator).first; + if (key.find(prefixKey) == 0) { + (void)cache_.erase(key); + iterator = eliminationChain_.erase(iterator); + } else { + iterator++; + } + } +} + +// move the node to last and remove the first node until the size less than limit +int LruMap::Elimination(const std::string &key, const QueryWaterMark &inQueryWaterMark) +{ + auto iterator = eliminationChain_.begin(); + while (iterator != eliminationChain_.end()) { + if ((*iterator).first == key) { + eliminationChain_.erase(iterator); + break; + } + iterator++; + } + std::pair entry = {key, inQueryWaterMark}; + eliminationChain_.push_back(entry); + while (eliminationChain_.size() > MAX_CACHE_ITEMS) { + std::pair &pair = eliminationChain_.front(); + cache_.erase(pair.first); + eliminationChain_.pop_front(); + } + return E_OK; +} + +int QuerySyncWaterMarkHelper::GetMetadataFromDb(const std::vector &key, std::vector &outValue) +{ + if (storage_ == nullptr) { + return -E_INVALID_DB; + } + return storage_->GetMetaData(key, outValue); +} + +int QuerySyncWaterMarkHelper::SetMetadataToDb(const std::vector &key, const std::vector &inValue) +{ + if (storage_ == nullptr) { + return -E_INVALID_DB; + } + return storage_->PutMetaData(key, inValue); +} + +int QuerySyncWaterMarkHelper::DeleteMetaDataFromDB(const std::vector &keys) const +{ + if (storage_ == nullptr) { + return -E_INVALID_DB; + } + return storage_->DeleteMetaData(keys); +} + +int QuerySyncWaterMarkHelper::Initialize(ISyncInterface *storage) +{ + storage_ = storage; + return E_OK; +} + +int QuerySyncWaterMarkHelper::LoadDeleteSyncDataToCache(const Key &deleteWaterMarkKey) +{ + std::vector value; + int errCode = GetMetadataFromDb(deleteWaterMarkKey, value); + if (errCode != E_OK) { + return errCode; + } + DeleteWaterMark deleteWaterMark; + std::string dbKey(deleteWaterMarkKey.begin(), deleteWaterMarkKey.end()); + errCode = DeSerializeDeleteWaterMark(value, deleteWaterMark); + if (errCode != E_OK) { + return errCode; + } + std::lock_guard autoLock(deleteSyncLock_); + deleteSyncCache_[dbKey] = deleteWaterMark; + return errCode; +} + +int QuerySyncWaterMarkHelper::GetQueryWaterMarkInCacheAndDb(const std::string &cacheKey, + QueryWaterMark &queryWaterMark) +{ + // first get from cache_ + int errCode = querySyncCache_.Get(cacheKey, queryWaterMark); + bool addToCache = false; + if (errCode == -E_NOT_FOUND) { + // second get from db + errCode = GetQueryWaterMarkFromDB(cacheKey, queryWaterMark); + addToCache = true; + } + if (errCode == -E_NOT_FOUND) { + // third generate one and save to db + errCode = PutQueryWaterMarkToDB(cacheKey, queryWaterMark); + } + // something error return + if (errCode != E_OK) { + LOGE("[Meta]GetQueryWaterMark Fail code = %d", errCode); + return errCode; + } + // remember add to cache_ + if (addToCache) { + querySyncCache_.Put(cacheKey, queryWaterMark); + } + return errCode; +} + +int QuerySyncWaterMarkHelper::GetQueryWaterMark(const std::string &queryIdentify, const std::string &deviceId, + QueryWaterMark &queryWaterMark) +{ + std::string cacheKey; + GetHashQuerySyncDeviceId(deviceId, queryIdentify, cacheKey); + std::lock_guard autoLock(queryWaterMarkLock_); + return GetQueryWaterMarkInCacheAndDb(cacheKey, queryWaterMark); +} + +int QuerySyncWaterMarkHelper::SetRecvQueryWaterMark(const std::string &queryIdentify, + const std::string &deviceId, const WaterMark &waterMark) +{ + std::string cacheKey; + GetHashQuerySyncDeviceId(deviceId, queryIdentify, cacheKey); + std::lock_guard autoLock(queryWaterMarkLock_); + return SetRecvQueryWaterMarkWithoutLock(cacheKey, waterMark); +} + +int QuerySyncWaterMarkHelper::SetLastQueryTime(const std::string &queryIdentify, + const std::string &deviceId, const Timestamp ×tamp) +{ + std::string cacheKey; + GetHashQuerySyncDeviceId(deviceId, queryIdentify, cacheKey); + std::lock_guard autoLock(queryWaterMarkLock_); + QueryWaterMark queryWaterMark; + int errCode = GetQueryWaterMarkInCacheAndDb(cacheKey, queryWaterMark); + if (errCode != E_OK) { + return errCode; + } + queryWaterMark.lastQueryTime = timestamp; + return UpdateCacheAndSave(cacheKey, queryWaterMark); +} + +int QuerySyncWaterMarkHelper::SetRecvQueryWaterMarkWithoutLock(const std::string &cacheKey, + const WaterMark &waterMark) +{ + QueryWaterMark queryWaterMark; + int errCode = GetQueryWaterMarkInCacheAndDb(cacheKey, queryWaterMark); + if (errCode != E_OK) { + return errCode; + } + queryWaterMark.recvWaterMark = waterMark; + return UpdateCacheAndSave(cacheKey, queryWaterMark); +} + +int QuerySyncWaterMarkHelper::SetSendQueryWaterMark(const std::string &queryIdentify, + const std::string &deviceId, const WaterMark &waterMark) +{ + std::string cacheKey; + GetHashQuerySyncDeviceId(deviceId, queryIdentify, cacheKey); + QueryWaterMark queryWaterMark; + std::lock_guard autoLock(queryWaterMarkLock_); + int errCode = GetQueryWaterMarkInCacheAndDb(cacheKey, queryWaterMark); + if (errCode != E_OK) { + return errCode; + } + queryWaterMark.sendWaterMark = waterMark; + return UpdateCacheAndSave(cacheKey, queryWaterMark); +} + +int QuerySyncWaterMarkHelper::UpdateCacheAndSave(const std::string &cacheKey, + QueryWaterMark &queryWaterMark) +{ + // update lastUsedTime + int errCode = OS::GetCurrentSysTimeInMicrosecond(queryWaterMark.lastUsedTime); + if (errCode != E_OK) { + return errCode; + } + // save db first + errCode = SaveQueryWaterMarkToDB(cacheKey, queryWaterMark); + if (errCode != E_OK) { + return errCode; + } + querySyncCache_.Put(cacheKey, queryWaterMark); + return errCode; +} + +int QuerySyncWaterMarkHelper::PutQueryWaterMarkToDB(const DeviceID &dbKeyString, QueryWaterMark &queryWaterMark) +{ + int errCode = OS::GetCurrentSysTimeInMicrosecond(queryWaterMark.lastUsedTime); + if (errCode != E_OK) { + return errCode; + } + queryWaterMark.version = QUERY_WATERMARK_VERSION_CURRENT; + return SaveQueryWaterMarkToDB(dbKeyString, queryWaterMark); +} + +int QuerySyncWaterMarkHelper::SaveQueryWaterMarkToDB(const DeviceID &dbKeyString, const QueryWaterMark &queryWaterMark) +{ + // serialize value + Value dbValue; + int errCode = SerializeQueryWaterMark(queryWaterMark, dbValue); + if (errCode != E_OK) { + return errCode; + } + // serialize key + Key dbKey; + DBCommon::StringToVector(dbKeyString, dbKey); + // save + errCode = SetMetadataToDb(dbKey, dbValue); + if (errCode != E_OK) { + LOGE("QuerySyncWaterMarkHelper::SaveQueryWaterMarkToDB failed errCode:%d", errCode); + } + return errCode; +} + +int QuerySyncWaterMarkHelper::GetQueryWaterMarkFromDB(const DeviceID &dbKeyString, QueryWaterMark &queryWaterMark) +{ + // serialize key + Key dbKey; + DBCommon::StringToVector(dbKeyString, dbKey); + // search in db + Value dbValue; + int errCode = GetMetadataFromDb(dbKey, dbValue); + if (errCode != E_OK) { + return errCode; + } + return DeSerializeQueryWaterMark(dbValue, queryWaterMark); +} + +int QuerySyncWaterMarkHelper::SerializeQueryWaterMark(const QueryWaterMark &queryWaterMark, Value &outValue) +{ + uint64_t length = CalculateQueryWaterMarkSize(queryWaterMark); + outValue.resize(length); + Parcel parcel(outValue.data(), outValue.size()); + parcel.WriteUInt32(queryWaterMark.version); + parcel.EightByteAlign(); + parcel.WriteUInt64(queryWaterMark.sendWaterMark); + parcel.WriteUInt64(queryWaterMark.recvWaterMark); + parcel.WriteUInt64(queryWaterMark.lastUsedTime); + parcel.WriteString(queryWaterMark.sql); + parcel.WriteUInt64(queryWaterMark.lastQueryTime); + if (parcel.IsError()) { + LOGE("[Meta] Parcel error when serialize queryWaterMark"); + return -E_PARSE_FAIL; + } + return E_OK; +} + +int QuerySyncWaterMarkHelper::DeSerializeQueryWaterMark(const Value &dbQueryWaterMark, QueryWaterMark &queryWaterMark) +{ + Parcel parcel(const_cast(dbQueryWaterMark.data()), dbQueryWaterMark.size()); + parcel.ReadUInt32(queryWaterMark.version); + parcel.EightByteAlign(); + parcel.ReadUInt64(queryWaterMark.sendWaterMark); + parcel.ReadUInt64(queryWaterMark.recvWaterMark); + parcel.ReadUInt64(queryWaterMark.lastUsedTime); + parcel.ReadString(queryWaterMark.sql); + if (queryWaterMark.version >= SOFTWARE_VERSION_RELEASE_6_0) { + parcel.ReadUInt64(queryWaterMark.lastQueryTime); + } + if (parcel.IsError()) { + LOGE("[Meta] Parcel error when deserialize queryWaterMark"); + return -E_PARSE_FAIL; + } + return E_OK; +} + +uint64_t QuerySyncWaterMarkHelper::CalculateQueryWaterMarkSize(const QueryWaterMark &queryWaterMark) +{ + uint64_t length = Parcel::GetUInt32Len(); // version + length = Parcel::GetEightByteAlign(length); + length += Parcel::GetUInt64Len(); // sendWaterMark + length += Parcel::GetUInt64Len(); // recvWaterMark + length += Parcel::GetUInt64Len(); // lastUsedTime + length += Parcel::GetStringLen(queryWaterMark.sql); + length += Parcel::GetUInt64Len(); // lastQueryTime + return length; +} + +void QuerySyncWaterMarkHelper::GetHashQuerySyncDeviceId(const DeviceID &deviceId, + const DeviceID &queryId, DeviceID &hashQuerySyncId) +{ + std::lock_guard autoLock(queryWaterMarkLock_); + if (deviceIdToHashQuerySyncIdMap_[deviceId].count(queryId) == 0) { + // do not modify this + hashQuerySyncId = QUERY_SYNC_PREFIX_KEY + DBCommon::TransferHashString(deviceId) + queryId; + deviceIdToHashQuerySyncIdMap_[deviceId][queryId] = hashQuerySyncId; + } else { + hashQuerySyncId = deviceIdToHashQuerySyncIdMap_[deviceId][queryId]; + } +} + +int QuerySyncWaterMarkHelper::GetDeleteSyncWaterMark(const std::string &deviceId, DeleteWaterMark &deleteWaterMark) +{ + std::string hashId; + GetHashDeleteSyncDeviceId(deviceId, hashId); + return GetDeleteWaterMarkFromCache(hashId, deleteWaterMark); +} + +int QuerySyncWaterMarkHelper::SetSendDeleteSyncWaterMark(const DeviceID &deviceId, const WaterMark &waterMark) +{ + std::string hashId; + GetHashDeleteSyncDeviceId(deviceId, hashId); + DeleteWaterMark deleteWaterMark; + GetDeleteWaterMarkFromCache(hashId, deleteWaterMark); + deleteWaterMark.sendWaterMark = waterMark; + std::lock_guard autoLock(deleteSyncLock_); + return UpdateDeleteSyncCacheAndSave(hashId, deleteWaterMark); +} + +int QuerySyncWaterMarkHelper::SetRecvDeleteSyncWaterMark(const DeviceID &deviceId, const WaterMark &waterMark) +{ + std::string hashId; + GetHashDeleteSyncDeviceId(deviceId, hashId); + DeleteWaterMark deleteWaterMark; + GetDeleteWaterMarkFromCache(hashId, deleteWaterMark); + deleteWaterMark.recvWaterMark = waterMark; + std::lock_guard autoLock(deleteSyncLock_); + return UpdateDeleteSyncCacheAndSave(hashId, deleteWaterMark); +} + +int QuerySyncWaterMarkHelper::UpdateDeleteSyncCacheAndSave(const std::string &dbKey, + const DeleteWaterMark &deleteWaterMark) +{ + // save db first + int errCode = SaveDeleteWaterMarkToDB(dbKey, deleteWaterMark); + if (errCode != E_OK) { + return errCode; + } + // modify cache + deleteSyncCache_[dbKey] = deleteWaterMark; + return errCode; +} + +int QuerySyncWaterMarkHelper::GetDeleteWaterMarkFromCache(const DeviceID &hashDeviceId, + DeleteWaterMark &deleteWaterMark) +{ + // lock prevent different thread visit deleteSyncCache_ + std::lock_guard autoLock(deleteSyncLock_); + // if not found + if (deleteSyncCache_.find(hashDeviceId) == deleteSyncCache_.end()) { + DeleteWaterMark waterMark; + waterMark.version = DELETE_WATERMARK_VERSION_CURRENT; + int errCode = GetDeleteWaterMarkFromDB(hashDeviceId, waterMark); + if (errCode == -E_NOT_FOUND) { + deleteWaterMark.sendWaterMark = 0; + deleteWaterMark.recvWaterMark = 0; + errCode = E_OK; + } + if (errCode != E_OK) { + LOGE("[Meta]GetDeleteWaterMark Fail code = %d", errCode); + return errCode; + } + deleteSyncCache_.insert(std::pair(hashDeviceId, waterMark)); + } + deleteWaterMark = deleteSyncCache_[hashDeviceId]; + return E_OK; +} + +int QuerySyncWaterMarkHelper::GetDeleteWaterMarkFromDB(const DeviceID &hashDeviceId, + DeleteWaterMark &deleteWaterMark) +{ + Key dbKey; + DBCommon::StringToVector(hashDeviceId, dbKey); + // search in db + Value dbValue; + int errCode = GetMetadataFromDb(dbKey, dbValue); + if (errCode != E_OK) { + return errCode; + } + // serialize value + return DeSerializeDeleteWaterMark(dbValue, deleteWaterMark); +} + +int QuerySyncWaterMarkHelper::SaveDeleteWaterMarkToDB(const DeviceID &hashDeviceId, + const DeleteWaterMark &deleteWaterMark) +{ + // serialize value + Value dbValue; + int errCode = SerializeDeleteWaterMark(deleteWaterMark, dbValue); + if (errCode != E_OK) { + return errCode; + } + Key dbKey; + DBCommon::StringToVector(hashDeviceId, dbKey); + // save + errCode = SetMetadataToDb(dbKey, dbValue); + if (errCode != E_OK) { + LOGE("QuerySyncWaterMarkHelper::SaveDeleteWaterMarkToDB failed errCode:%d", errCode); + } + return errCode; +} + +void QuerySyncWaterMarkHelper::GetHashDeleteSyncDeviceId(const DeviceID &deviceId, DeviceID &hashDeleteSyncId) +{ + std::lock_guard autoLock(deleteSyncLock_); + if (deviceIdToHashDeleteSyncIdMap_.count(deviceId) == 0) { + hashDeleteSyncId = DELETE_SYNC_PREFIX_KEY + DBCommon::TransferHashString(deviceId); + deviceIdToHashDeleteSyncIdMap_.insert(std::pair(deviceId, hashDeleteSyncId)); + } else { + hashDeleteSyncId = deviceIdToHashDeleteSyncIdMap_[deviceId]; + } +} + +int QuerySyncWaterMarkHelper::SerializeDeleteWaterMark(const DeleteWaterMark &deleteWaterMark, + std::vector &outValue) +{ + uint64_t length = CalculateDeleteWaterMarkSize(); + outValue.resize(length); + Parcel parcel(outValue.data(), outValue.size()); + parcel.WriteUInt32(deleteWaterMark.version); + parcel.EightByteAlign(); + parcel.WriteUInt64(deleteWaterMark.sendWaterMark); + parcel.WriteUInt64(deleteWaterMark.recvWaterMark); + if (parcel.IsError()) { + LOGE("[Meta] Parcel error when serialize deleteWaterMark."); + return -E_PARSE_FAIL; + } + return E_OK; +} + +int QuerySyncWaterMarkHelper::DeSerializeDeleteWaterMark(const std::vector &inValue, + DeleteWaterMark &deleteWaterMark) +{ + Parcel parcel(const_cast(inValue.data()), inValue.size()); + parcel.ReadUInt32(deleteWaterMark.version); + parcel.EightByteAlign(); + parcel.ReadUInt64(deleteWaterMark.sendWaterMark); + parcel.ReadUInt64(deleteWaterMark.recvWaterMark); + if (parcel.IsError()) { + LOGE("[Meta] Parcel error when deserialize deleteWaterMark."); + return -E_PARSE_FAIL; + } + return E_OK; +} + +uint64_t QuerySyncWaterMarkHelper::CalculateDeleteWaterMarkSize() +{ + uint64_t length = Parcel::GetUInt32Len(); // version + length = Parcel::GetEightByteAlign(length); + length += Parcel::GetUInt64Len(); // sendWaterMark + length += Parcel::GetUInt64Len(); // recvWaterMark + return length; +} + +std::string QuerySyncWaterMarkHelper::GetQuerySyncPrefixKey() +{ + return QUERY_SYNC_PREFIX_KEY; +} + +std::string QuerySyncWaterMarkHelper::GetDeleteSyncPrefixKey() +{ + return DELETE_SYNC_PREFIX_KEY; +} + +int QuerySyncWaterMarkHelper::RemoveLeastUsedQuerySyncItems(const std::vector &querySyncIds) +{ + if (querySyncIds.size() < MAX_STORE_ITEMS) { + return E_OK; + } + std::vector> allItems; + std::map> idMap; + std::vector> waitToRemove; + for (const auto &id : querySyncIds) { + Value value; + int errCode = GetMetadataFromDb(id, value); + if (errCode != E_OK) { + waitToRemove.push_back(id); + continue; // may be this failure cause by wrong data + } + QueryWaterMark queryWaterMark; + std::string queryKey(id.begin(), id.end()); + errCode = DeSerializeQueryWaterMark(value, queryWaterMark); + if (errCode != E_OK) { + waitToRemove.push_back(id); + continue; // may be this failure cause by wrong data + } + idMap.insert({queryKey, id}); + allItems.emplace_back(queryKey, queryWaterMark.lastUsedTime); + } + // we only remove broken data below + // 1. common data size less then 10w + // 2. allItems.size() - MAX_STORE_ITEMS - waitToRemove.size() < 0 + // so we only let allItems.size() < MAX_STORE_ITEMS + waitToRemove.size() + if (allItems.size() < MAX_STORE_ITEMS + waitToRemove.size()) { + // remove in db + return DeleteMetaDataFromDB(waitToRemove); + } + uint32_t removeCount = allItems.size() - MAX_STORE_ITEMS - waitToRemove.size(); + // quick select the k_th least used + std::nth_element(allItems.begin(), allItems.begin() + removeCount, allItems.end(), + [](std::pair &w1, std::pair &w2) { + return w1.second < w2.second; + }); + for (uint32_t i = 0; i < removeCount; ++i) { + waitToRemove.push_back(idMap[allItems[i].first]); + } + // remove in db + return DeleteMetaDataFromDB(waitToRemove); +} + +int QuerySyncWaterMarkHelper::ResetRecvQueryWaterMark(const DeviceID &deviceId, const std::string &tableName) +{ + // lock prevent other thread modify queryWaterMark at this moment + { + std::lock_guard autoLock(queryWaterMarkLock_); + std::string prefixKeyStr = QUERY_SYNC_PREFIX_KEY + DBCommon::TransferHashString(deviceId); + if (!tableName.empty()) { + std::string hashTableName = DBCommon::TransferHashString(tableName); + std::string hexTableName = DBCommon::TransferStringToHex(hashTableName); + prefixKeyStr += hexTableName; + } + + // remove in db + Key prefixKey; + DBCommon::StringToVector(prefixKeyStr, prefixKey); + int errCode = storage_->DeleteMetaDataByPrefixKey(prefixKey); + if (errCode != E_OK) { + LOGE("[META]ResetRecvQueryWaterMark fail errCode:%d", errCode); + return errCode; + } + // clean cache + querySyncCache_.RemoveWithPrefixKey(prefixKeyStr); + } + return E_OK; +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/query_sync_water_mark_helper.h b/mock/distributeddb/syncer/src/query_sync_water_mark_helper.h new file mode 100644 index 00000000..f0e6a8b4 --- /dev/null +++ b/mock/distributeddb/syncer/src/query_sync_water_mark_helper.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 QUERY_SYNC_WATER_MARK_HELPER_H +#define QUERY_SYNC_WATER_MARK_HELPER_H + +#include +#include +#include +#include +#include +#include "db_types.h" +#include "ikvdb_sync_interface.h" + +namespace DistributedDB { +struct QueryWaterMark { + uint32_t version = 0; // start with 103 + WaterMark sendWaterMark = 0; + WaterMark recvWaterMark = 0; + Timestamp lastUsedTime = 0; // use for delete data + std::string sql; // for analyze sql from logs + Timestamp lastQueryTime = 0; // use for miss query scene add in 106 +}; + +struct DeleteWaterMark { + uint32_t version = 0; + WaterMark sendWaterMark = 0; + WaterMark recvWaterMark = 0; +}; + +// LRU map +class LruMap final { +public: + LruMap() = default; + ~LruMap() = default; + + DISABLE_COPY_ASSIGN_MOVE(LruMap); + + int Get(const std::string &key, QueryWaterMark &outValue); + int Put(const std::string &key, const QueryWaterMark &inValue); + void RemoveWithPrefixKey(const std::string &prefixKey); +private: + int Elimination(const std::string &key, const QueryWaterMark &inQueryWaterMark); + + std::mutex lruLock_; + std::map cache_; + std::deque> eliminationChain_; +}; + +class QuerySyncWaterMarkHelper { +public: + QuerySyncWaterMarkHelper(); + ~QuerySyncWaterMarkHelper(); + + DISABLE_COPY_ASSIGN_MOVE(QuerySyncWaterMarkHelper); + + int Initialize(ISyncInterface *storage); + + int GetQueryWaterMark(const std::string &queryIdentify, const std::string &deviceId, + QueryWaterMark &queryWaterMark); + + int SetSendQueryWaterMark(const std::string &queryIdentify, + const std::string &deviceId, const WaterMark &waterMark); + + int SetRecvQueryWaterMark(const std::string &queryIdentify, + const std::string &deviceId, const WaterMark &waterMark); + + int SetLastQueryTime(const std::string &queryIdentify, + const std::string &deviceId, const Timestamp ×tamp); + + int GetDeleteSyncWaterMark(const std::string &deviceId, DeleteWaterMark &deleteWaterMark); + + int SetSendDeleteSyncWaterMark(const std::string &deviceId, const WaterMark &waterMark); + + int SetRecvDeleteSyncWaterMark(const std::string &deviceId, const WaterMark &waterMark); + + // this function will read deleteWaterMark from db by it's deleteWaterMarkKey + // and then serialize it and put to cache + int LoadDeleteSyncDataToCache(const Key &deleteWaterMarkKey); + + // this function will remove data in db + int RemoveLeastUsedQuerySyncItems(const std::vector &querySyncIds); + + // reset the waterMark to zero + int ResetRecvQueryWaterMark(const DeviceID &deviceId, const std::string &tableName); + + static std::string GetQuerySyncPrefixKey(); + + static std::string GetDeleteSyncPrefixKey(); + +private: + + int GetMetadataFromDb(const std::vector &key, std::vector &outValue); + + int SetMetadataToDb(const std::vector &key, const std::vector &inValue); + + int DeleteMetaDataFromDB(const std::vector &keys) const; + + int SaveQueryWaterMarkToDB(const DeviceID &dbKeyString, const QueryWaterMark &queryWaterMark); + + int GetQueryWaterMarkFromDB(const DeviceID &dbKeyString, QueryWaterMark &queryWaterMark); + + int SetRecvQueryWaterMarkWithoutLock(const std::string &cacheKey, + const WaterMark &waterMark); + + // search the queryWaterMark from db or cache_ + // and ensure it exit in cache_ + int GetQueryWaterMarkInCacheAndDb(const std::string &cacheKey, QueryWaterMark &queryWaterMark); + + // only first create queryWaterMark will call this function + // it will create a queryWaterMark and save to db + int PutQueryWaterMarkToDB(const DeviceID &dbKeyString, QueryWaterMark &queryWaterMark); + + // get the querySync hashId in cache_ or generate one and then put it in to cache_ + // the hashId is made up of "QUERY_SYNC_PREFIX_KEY" + hash(deviceId) + queryId + void GetHashQuerySyncDeviceId(const DeviceID &deviceId, + const DeviceID &queryId, DeviceID &hashQuerySyncId); + + // put queryWaterMark to lru cache_ and then save to db + int UpdateCacheAndSave(const std::string &cacheKey, QueryWaterMark &queryWaterMark); + + // search the deleteWaterMark from db or cache_ + // and ensure it exit in cache_ + int GetDeleteWaterMarkFromCache(const DeviceID &hashDeviceId, DeleteWaterMark &deleteWaterMark); + + // get the deleteSync hashId in cache_ or generate one and then put it in to cache_ + // the hashId is made up of "DELETE_SYNC_PREFIX_KEY" + hash(deviceId) + void GetHashDeleteSyncDeviceId(const DeviceID &deviceId, DeviceID &hashDeleteSyncId); + + int SaveDeleteWaterMarkToDB(const DeviceID &hashDeviceId, const DeleteWaterMark &deleteWaterMark); + + int GetDeleteWaterMarkFromDB(const DeviceID &hashDeviceId, DeleteWaterMark &deleteWaterMark); + + // put queryWaterMark to lru cache_ and then save to db + int UpdateDeleteSyncCacheAndSave(const std::string &dbKey, const DeleteWaterMark &deleteWaterMark); + + static int SerializeQueryWaterMark(const QueryWaterMark &queryWaterMark, std::vector &outValue); + + static int DeSerializeQueryWaterMark(const std::vector &dbQueryWaterMark, QueryWaterMark &queryWaterMark); + + static uint64_t CalculateQueryWaterMarkSize(const QueryWaterMark &queryWaterMark); + + static int SerializeDeleteWaterMark(const DeleteWaterMark &deleteWaterMark, std::vector &outValue); + + static int DeSerializeDeleteWaterMark(const std::vector &inValue, DeleteWaterMark &deleteWaterMark); + + static uint64_t CalculateDeleteWaterMarkSize(); + + // store or visit queryWaterMark should add a lock + // because it will change the eliminationChain + // and the queryWaterMark use a LRU Map to store in ram + std::mutex queryWaterMarkLock_; + LruMap querySyncCache_; + std::map> deviceIdToHashQuerySyncIdMap_; + + // also store deleteKeyWaterMark should add a lock + std::mutex deleteSyncLock_; + std::map deleteSyncCache_; + std::map deviceIdToHashDeleteSyncIdMap_; + + ISyncInterface *storage_; +}; +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/syncer/src/single_ver_data_message_schedule.cpp b/mock/distributeddb/syncer/src/single_ver_data_message_schedule.cpp new file mode 100644 index 00000000..b958ee8f --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_data_message_schedule.cpp @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "single_ver_data_message_schedule.h" + +#include "db_common.h" +#include "log_print.h" +#include "version.h" +#include "single_ver_data_sync.h" + +namespace DistributedDB { +SingleVerDataMessageSchedule::~SingleVerDataMessageSchedule() +{ + LOGD("~SingleVerDataMessageSchedule"); + ClearMsg(); +} + +void SingleVerDataMessageSchedule::Initialize(const std::string &label, const std::string &deviceId) +{ + label_ = label; + deviceId_ = deviceId; +} + +void SingleVerDataMessageSchedule::PutMsg(Message *inMsg) +{ + if (inMsg == nullptr) { + return; + } + std::lock_guard lock(queueLock_); + msgQueue_.push(inMsg); + isNeedReload_ = true; +} + +bool SingleVerDataMessageSchedule::IsNeedReloadQueue() +{ + std::lock_guard lock(queueLock_); + return isNeedReload_; +} + +Message *SingleVerDataMessageSchedule::MoveNextMsg(SingleVerSyncTaskContext *context, bool &isNeedHandle, + bool &isNeedContinue) +{ + uint32_t remoteVersion = context->GetRemoteSoftwareVersion(); + if (remoteVersion < SOFTWARE_VERSION_RELEASE_3_0) { + // just get last msg when remote version is < 103 or >=103 but just open db now + return GetLastMsgFromQueue(); + } + { + std::lock_guard lock(workingLock_); + if (isWorking_) { + isNeedContinue = false; + return nullptr; + } + isWorking_ = true; + } + ResetTimer(context); + UpdateMsgMap(); + Message *msg = GetMsgFromMap(isNeedHandle); + isNeedContinue = true; + if (msg == nullptr) { + StopTimer(); + std::lock_guard lock(workingLock_); + isWorking_ = false; + return nullptr; + } + return msg; +} + +void SingleVerDataMessageSchedule::ScheduleInfoHandle(bool isNeedHandleStatus, bool isNeedClearMap, + const Message *inMsg) +{ + if (isNeedHandleStatus) { + const DataRequestPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + LOGE("[DataMsgSchedule] packet is nullptr"); + return; + } + uint64_t curPacketId = packet->GetPacketId(); + { + std::lock_guard lock(lock_); + finishedPacketId_ = curPacketId; + if (isNeedClearMap) { + ClearMsgMapWithNoLock(); + expectedSequenceId_ = 1; + } else { + LOGI("[DataMsgSchedule] DealMsg seqId=%" PRIu32 " finishedPacketId=%" PRIu64 " ok,label=%s,dev=%s", + expectedSequenceId_, finishedPacketId_, label_.c_str(), STR_MASK(deviceId_)); + expectedSequenceId_++; + } + } + } + std::lock_guard lock(workingLock_); + isWorking_ = false; +} + +void SingleVerDataMessageSchedule::ClearMsg() +{ + StopTimer(); + ClearMsgQueue(); + ClearMsgMap(); +} + +void SingleVerDataMessageSchedule::UpdateMsgMap() +{ + std::queue msgTmpQueue; + { + std::lock_guard lock(queueLock_); + while (!msgQueue_.empty()) { + msgTmpQueue.push(msgQueue_.front()); + msgQueue_.pop(); + } + isNeedReload_ = false; + } + UpdateMsgMapInner(msgTmpQueue); +} + +void SingleVerDataMessageSchedule::UpdateMsgMapInner(std::queue &msgTmpQueue) +{ + // update msg map + std::lock_guard lock(lock_); + while (!msgTmpQueue.empty()) { + Message *msg = msgTmpQueue.front(); + msgTmpQueue.pop(); + // insert new msg into map and delete old msg + int errCode = UpdateMsgMapIfNeed(msg); + if (errCode != E_OK) { + delete msg; + } + } +} + +Message *SingleVerDataMessageSchedule::GetMsgFromMap(bool &isNeedHandle) +{ + isNeedHandle = true; + std::lock_guard lock(lock_); + while (!messageMap_.empty()) { + auto iter = messageMap_.begin(); + Message *msg = iter->second; + messageMap_.erase(iter); + const DataRequestPacket *packet = msg->GetObject(); + if (packet == nullptr) { + LOGE("[DataMsgSchedule] expected error"); + delete msg; + continue; + } + uint32_t sequenceId = msg->GetSequenceId(); + uint64_t packetId = packet->GetPacketId(); + if (sequenceId < expectedSequenceId_) { + uint64_t revisePacketId = finishedPacketId_ - (expectedSequenceId_ - 1 - sequenceId); + LOGI("[DataMsgSchedule] drop msg because seqId less than exSeqId"); + if (packetId < revisePacketId) { + delete msg; + continue; + } + // means already handle the msg, and just send E_OK ack in dataSync + isNeedHandle = false; + return msg; + } + if (sequenceId == expectedSequenceId_) { + if (packetId < finishedPacketId_) { + LOGI("[DataMsgSchedule] drop msg because packetId less than finishedPacketId"); + delete msg; + continue; + } + // if packetId == finishedPacketId_ need handle + // it will happened while watermark/need_abilitySync when last ack is missing + return msg; + } + // sequenceId > expectedSequenceId_, not need handle, put into map again + messageMap_[sequenceId] = msg; + return nullptr; + } + return nullptr; +} + +Message *SingleVerDataMessageSchedule::GetLastMsgFromQueue() +{ + std::lock_guard lock(queueLock_); + isNeedReload_ = false; + while (!msgQueue_.empty()) { + Message *msg = msgQueue_.front(); + msgQueue_.pop(); + if (msgQueue_.empty()) { // means last msg + return msg; + } + delete msg; + } + return nullptr; +} + +void SingleVerDataMessageSchedule::ClearMsgMap() +{ + std::lock_guard lock(lock_); + ClearMsgMapWithNoLock(); +} + +void SingleVerDataMessageSchedule::ClearMsgMapWithNoLock() +{ + LOGD("[DataMsgSchedule] begin to ClearMsgMapWithNoLock"); + for (auto &iter : messageMap_) { + delete iter.second; + iter.second = nullptr; + } + messageMap_.clear(); +} + +void SingleVerDataMessageSchedule::ClearMsgQueue() +{ + std::lock_guard lock(queueLock_); + while (!msgQueue_.empty()) { + Message *msg = msgQueue_.front(); + msgQueue_.pop(); + delete msg; + } +} + +void SingleVerDataMessageSchedule::StartTimer(SingleVerSyncTaskContext *context) +{ + std::lock_guard lock(lock_); + TimerId timerId = 0; + RefObject::IncObjRef(context); + TimerAction timeOutCallback = std::bind(&SingleVerDataMessageSchedule::TimeOut, this, std::placeholders::_1); + int errCode = RuntimeContext::GetInstance()->SetTimer(IDLE_TIME_OUT, timeOutCallback, + [context]() { + int errCode = RuntimeContext::GetInstance()->ScheduleTask([context]() { + RefObject::DecObjRef(context); + }); + if (errCode != E_OK) { + LOGE("[DataMsgSchedule] timer finalizer ScheduleTask,errCode=%d", errCode); + } + }, timerId); + if (errCode != E_OK) { + RefObject::DecObjRef(context); + LOGE("[DataMsgSchedule] timer ScheduleTask, errCode=%d", errCode); + return; + } + timerId_ = timerId; + LOGD("[DataMsgSchedule] StartTimer,TimerId=%" PRIu64, timerId_); +} + +void SingleVerDataMessageSchedule::StopTimer() +{ + TimerId timerId; + { + std::lock_guard lock(lock_); + LOGD("[DataMsgSchedule] StopTimer,remove TimerId=%" PRIu64, timerId_); + if (timerId_ == 0) { + return; + } + timerId = timerId_; + timerId_ = 0; + } + RuntimeContext::GetInstance()->RemoveTimer(timerId); +} + +void SingleVerDataMessageSchedule::ResetTimer(SingleVerSyncTaskContext *context) +{ + StopTimer(); + StartTimer(context); +} + +int SingleVerDataMessageSchedule::TimeOut(TimerId timerId) +{ + if (IsNeedReloadQueue()) { + LOGI("[DataMsgSchedule] new msg exists, no need to timeout handle"); + return E_OK; + } + { + std::lock_guard lock(workingLock_); + if (isWorking_) { + LOGI("[DataMsgSchedule] other thread is handle msg, no need to timeout handle"); + return E_OK; + } + } + { + std::lock_guard lock(lock_); + LOGI("[DataMsgSchedule] timeout handling, stop timerId_[%" PRIu64 "]", timerId); + if (timerId == timerId_) { + ClearMsgMapWithNoLock(); + timerId_ = 0; + } + } + RuntimeContext::GetInstance()->RemoveTimer(timerId); + return E_OK; +} + +int SingleVerDataMessageSchedule::UpdateMsgMapIfNeed(Message *msg) +{ + if (msg == nullptr) { + return -E_INVALID_ARGS; + } + const DataRequestPacket *packet = msg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + uint32_t sessionId = msg->GetSessionId(); + uint32_t sequenceId = msg->GetSequenceId(); + uint64_t packetId = packet->GetPacketId(); + if (prevSessionId_ != 0 && sessionId == prevSessionId_) { + LOGD("[DataMsgSchedule] recv prev sessionId msg, drop msg, label=%s, dev=%s", label_.c_str(), + STR_MASK(deviceId_)); + return -E_INVALID_ARGS; + } + if (sessionId != currentSessionId_) { + // make sure all msg sessionId is same in msgMap + ClearMsgMapWithNoLock(); + prevSessionId_ = currentSessionId_; + currentSessionId_ = sessionId; + finishedPacketId_ = 0; + expectedSequenceId_ = 1; + } + if (messageMap_.count(sequenceId) > 0) { + const auto *cachePacket = messageMap_[sequenceId]->GetObject(); + if (cachePacket != nullptr) { + if (packetId != 0 && packetId < cachePacket->GetPacketId()) { + LOGD("[DataMsgSchedule] drop msg packetId=%" PRIu64 ", cachePacketId=%" PRIu64 ", label=%s, dev=%s", + packetId, cachePacket->GetPacketId(), label_.c_str(), STR_MASK(deviceId_)); + return -E_INVALID_ARGS; + } + } + delete messageMap_[sequenceId]; + messageMap_[sequenceId] = nullptr; + } + messageMap_[sequenceId] = msg; + LOGD("[DataMsgSchedule] put into msgMap seqId=%" PRIu32 ", packetId=%" PRIu64 ", label=%s, dev=%s", sequenceId, + packetId, label_.c_str(), STR_MASK(deviceId_)); + return E_OK; +} +} \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_data_message_schedule.h b/mock/distributeddb/syncer/src/single_ver_data_message_schedule.h new file mode 100644 index 00000000..bc9a0937 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_data_message_schedule.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 SINGLE_VER_DATA_MESSAGE_SCHEDULE_H +#define SINGLE_VER_DATA_MESSAGE_SCHEDULE_H +#include +#include +#include +#include + +#include "message.h" +#include "runtime_context.h" +#include "single_ver_sync_task_context.h" + +namespace DistributedDB { +class SingleVerDataMessageSchedule { +public: + SingleVerDataMessageSchedule() = default; + ~SingleVerDataMessageSchedule(); + void Initialize(const std::string &label, const std::string &deviceId); + void PutMsg(Message *inMsg); + bool IsNeedReloadQueue(); + Message *MoveNextMsg(SingleVerSyncTaskContext *context, bool &isNeedHandle, bool &isNeedContinue); + void ScheduleInfoHandle(bool isNeedHandleStatus, bool isNeedClearMap, const Message *inMsg); + void ClearMsg(); +private: + void UpdateMsgMap(); + void UpdateMsgMapInner(std::queue &msgTmpQueue); + int UpdateMsgMapIfNeed(Message *msg); + Message *GetMsgFromMap(bool &isNeedHandle); + Message *GetLastMsgFromQueue(); + void ClearMsgMap(); + void ClearMsgMapWithNoLock(); + void ClearMsgQueue(); + void StartTimer(SingleVerSyncTaskContext *context); + void StopTimer(); + void ResetTimer(SingleVerSyncTaskContext *context); + // when timeout queue size is 0 because thread can move queue msg to map if isNeedReload which is + // activated when queue has new msg is true + // so only need clear map msg + int TimeOut(TimerId timerId); + + static constexpr int IDLE_TIME_OUT = 5 * 60 * 1000; // 5min + std::mutex queueLock_; + std::queue msgQueue_; + bool isNeedReload_ = false; + // only one thread is deal msg + std::mutex workingLock_; + bool isWorking_ = false; + // first:sequenceId second:Message*, deal msg from low sequenceId to high sequenceId + std::mutex lock_; + std::map messageMap_; + uint32_t prevSessionId_ = 0; // drop the msg if msg sessionId is prev sessionId. + uint32_t currentSessionId_ = 0; + uint64_t finishedPacketId_ = 0; // next msg packetId should larger than it + uint32_t expectedSequenceId_ = 0; // handle next msg which sequenceId is equal to it + TimerId timerId_ = 0; + + std::string label_; + std::string deviceId_; +}; +} +#endif // SINGLE_VER_DATA_MESSAGE_SCHEDULE_H \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_data_packet.cpp b/mock/distributeddb/syncer/src/single_ver_data_packet.cpp new file mode 100644 index 00000000..ea073388 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_data_packet.cpp @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_data_packet.h" +#include "icommunicator.h" +#include "single_ver_kvdb_sync_interface.h" +#include "query_sync_object.h" +#include "generic_single_ver_kv_entry.h" +#include "sync_types.h" +#include "version.h" +#include "parcel.h" + + +namespace DistributedDB { +DataRequestPacket::~DataRequestPacket() +{ + for (auto &entry : data_) { + delete entry; + entry = nullptr; + } +} + +void DataRequestPacket::SetData(std::vector &data) +{ + data_ = std::move(data); +} + +const std::vector &DataRequestPacket::GetData() const +{ + return data_; +} + +void DataRequestPacket::SetCompressData(std::vector &compressData) +{ + compressData_ = std::move(compressData); +} + +const std::vector &DataRequestPacket::GetCompressData() const +{ + return compressData_; +} + +void DataRequestPacket::SetEndWaterMark(WaterMark waterMark) +{ + endWaterMark_ = waterMark; +} + +WaterMark DataRequestPacket::GetEndWaterMark() const +{ + return endWaterMark_; +} + +void DataRequestPacket::SetLocalWaterMark(WaterMark waterMark) +{ + localWaterMark_ = waterMark; +} + +WaterMark DataRequestPacket::GetLocalWaterMark() const +{ + return localWaterMark_; +} + +void DataRequestPacket::SetPeerWaterMark(WaterMark waterMark) +{ + peerWaterMark_ = waterMark; +} + +WaterMark DataRequestPacket::GetPeerWaterMark() const +{ + return peerWaterMark_; +} + +void DataRequestPacket::SetSendCode(int32_t errCode) +{ + sendCode_ = errCode; +} + +int32_t DataRequestPacket::GetSendCode() const +{ + return sendCode_; +} + +void DataRequestPacket::SetMode(int32_t mode) +{ + mode_ = mode; +} + +int32_t DataRequestPacket::GetMode() const +{ + return mode_; +} + +void DataRequestPacket::SetSessionId(uint32_t sessionId) +{ + sessionId_ = sessionId; +} + +uint32_t DataRequestPacket::GetSessionId() const +{ + return sessionId_; +} + +void DataRequestPacket::SetVersion(uint32_t version) +{ + version_ = version; +} + +uint32_t DataRequestPacket::GetVersion() const +{ + return version_; +} + +void DataRequestPacket::SetReserved(std::vector &reserved) +{ + reserved_ = std::move(reserved); +} + +void DataRequestPacket::SetReserved(std::vector &&reserved) +{ + reserved_ = reserved; +} + +std::vector DataRequestPacket::GetReserved() const +{ + return reserved_; +} + +uint64_t DataRequestPacket::GetPacketId() const +{ + uint64_t packetId = 0; + std::vector DataRequestReserve = GetReserved(); + if (DataRequestReserve.size() > REQUEST_PACKET_RESERVED_INDEX_PACKETID) { + return DataRequestReserve[REQUEST_PACKET_RESERVED_INDEX_PACKETID]; + } else { + return packetId; + } +} + +uint32_t DataRequestPacket::CalculateLen(uint32_t messageId) const +{ + uint64_t totalLen = GenericSingleVerKvEntry::CalculateLens( + IsCompressData() ? std::vector {} : data_, version_); // for data + totalLen += Parcel::GetUInt64Len(); // endWaterMark + totalLen += Parcel::GetUInt64Len(); // localWaterMark + totalLen += Parcel::GetUInt64Len(); // peerWaterMark + totalLen += Parcel::GetIntLen(); // sendCode + totalLen += Parcel::GetIntLen(); // mode + totalLen += Parcel::GetUInt32Len(); // sessionId + totalLen += Parcel::GetUInt32Len(); // version + totalLen += Parcel::GetVectorLen(reserved_); // reserved + + if (version_ > SOFTWARE_VERSION_RELEASE_2_0) { + totalLen += Parcel::GetUInt32Len(); // flag bit0 used for isLastSequence + } + totalLen = Parcel::GetEightByteAlign(totalLen); // 8-byte align + if (totalLen > INT32_MAX) { + return 0; + } + if (messageId == QUERY_SYNC_MESSAGE) { + // deleted watermark + totalLen += Parcel::GetUInt64Len(); + // query id + totalLen += Parcel::GetStringLen(queryId_); + // add for queryObject + totalLen += query_.CalculateParcelLen(SOFTWARE_VERSION_CURRENT); + } + if (IsCompressData()) { + totalLen += GenericSingleVerKvEntry::CalculateCompressedLens(compressData_); // add forcompressData_ + } + if (totalLen > INT32_MAX) { + return 0; + } + return totalLen; +} + +void DataRequestPacket::SetFlag(uint32_t flag) +{ + flag_ = flag; +} + +uint32_t DataRequestPacket::GetFlag() const +{ + return flag_; +} + +bool DataRequestPacket::IsLastSequence() const +{ + return ((flag_ & IS_LAST_SEQUENCE) == IS_LAST_SEQUENCE); +} + +void DataRequestPacket::SetLastSequence() +{ + flag_ = flag_ | IS_LAST_SEQUENCE; +} + +bool DataRequestPacket::IsNeedUpdateWaterMark() const +{ + return !((flag_ & IS_UPDATE_WATER) == IS_UPDATE_WATER); +} + +void DataRequestPacket::SetUpdateWaterMark() +{ + flag_ = flag_ | IS_UPDATE_WATER; +} + +void DataRequestPacket::SetCompressDataMark() +{ + flag_ = flag_ | IS_COMPRESS_DATA; +} + +bool DataRequestPacket::IsCompressData() const +{ + return ((flag_ & IS_COMPRESS_DATA) == IS_COMPRESS_DATA); +} + +void DataRequestPacket::SetCompressAlgo(CompressAlgorithm algo) +{ + algo_ = algo; +} + +CompressAlgorithm DataRequestPacket::GetCompressAlgo() const +{ + return algo_; +} + +void DataRequestPacket::SetBasicInfo(int sendCode, uint32_t version, int32_t mode) +{ + SetSendCode(sendCode); + SetVersion(version); + SetMode(mode); +} + +void DataRequestPacket::SetWaterMark(WaterMark localMark, WaterMark peerMark, WaterMark deletedWatermark) +{ + localWaterMark_ = localMark; + peerWaterMark_ = peerMark; + deletedWatermark_ = deletedWatermark; +} + +void DataRequestPacket::SetQuery(const QuerySyncObject &query) +{ + query_ = query; +} + +QuerySyncObject DataRequestPacket::GetQuery() const +{ + return query_; +} + +void DataRequestPacket::SetQueryId(const std::string &queryId) +{ + queryId_ = queryId; +} + +std::string DataRequestPacket::GetQueryId() const +{ + return queryId_; +} + +void DataRequestPacket::SetDeletedWaterMark(WaterMark watermark) +{ + deletedWatermark_ = watermark; +} + +WaterMark DataRequestPacket::GetDeletedWaterMark() const +{ + return deletedWatermark_; +} + +void DataAckPacket::SetData(uint64_t data) +{ + data_ = data; +} + +uint64_t DataAckPacket::GetData() const +{ + return data_; +} + +void DataAckPacket::SetRecvCode(int32_t errorCode) +{ + recvCode_ = errorCode; +} + +int32_t DataAckPacket::GetRecvCode() const +{ + return recvCode_; +} + +void DataAckPacket::SetVersion(uint32_t version) +{ + version_ = version; +} + +uint32_t DataAckPacket::GetVersion() const +{ + return version_; +} + +void DataAckPacket::SetReserved(std::vector &reserved) +{ + reserved_ = std::move(reserved); +} + +std::vector DataAckPacket::GetReserved() const +{ + return reserved_; +} + +uint64_t DataAckPacket::GetPacketId() const +{ + uint64_t packetId = 0; + std::vector DataAckReserve = GetReserved(); + if (DataAckReserve.size() > ACK_PACKET_RESERVED_INDEX_PACKETID) { + packetId = DataAckReserve[ACK_PACKET_RESERVED_INDEX_PACKETID]; + } + // while remote db is close and open again, it may not carry packetId + // so the second index is deletewatermark if it is the query Sync, should drop the deletewatermark here + if (packetId > MAX_PACKETID) { + return 0; + } + return packetId; +} + +bool DataAckPacket::IsPacketIdValid(uint64_t packetId) +{ + return (packetId > 0); +} + +uint32_t DataAckPacket::CalculateLen() const +{ + uint64_t len = Parcel::GetUInt64Len(); // ackWaterMark + len += Parcel::GetIntLen(); // recvCode + len += Parcel::GetUInt32Len(); // version + len += Parcel::GetVectorLen(reserved_); // reserved + + len = Parcel::GetEightByteAlign(len); + if (len > INT32_MAX) { + return 0; + } + return len; +} + +void ControlRequestPacket::SetPacketHead(int sendCode, uint32_t version, int32_t controlCmd, uint32_t flag) +{ + sendCode_ = sendCode; + version_ = version; + controlCmdType_ = static_cast(controlCmd); + flag_ = flag; +} + +int32_t ControlRequestPacket::GetSendCode() const +{ + return sendCode_; +} + +uint32_t ControlRequestPacket::GetVersion() const +{ + return version_; +} + +uint32_t ControlRequestPacket::GetcontrolCmdType() const +{ + return controlCmdType_; +} + +uint32_t ControlRequestPacket::GetFlag() const +{ + return flag_; +} + +void ControlRequestPacket::SetQuery(const QuerySyncObject &query) +{ + (void)query; +} + +uint32_t ControlRequestPacket::CalculateLen() const +{ + uint64_t len = Parcel::GetUInt32Len(); // version_ + len += Parcel::GetIntLen(); // sendCode_ + len += Parcel::GetUInt32Len(); // controlCmdType_ + len += Parcel::GetUInt32Len(); // flag + + len = Parcel::GetEightByteAlign(len); + if (len > INT32_MAX) { + return 0; + } + return len; +} + +void SubscribeRequest::SetQuery(const QuerySyncObject &query) +{ + query_ = query; +} + +QuerySyncObject SubscribeRequest::GetQuery() const +{ + return query_; +} + +uint32_t SubscribeRequest::CalculateLen() const +{ + uint64_t totalLen = ControlRequestPacket::CalculateLen(); + if (totalLen == 0) { + LOGE("[SubscribeRequest] cal packet len failed"); + return 0; + } + // add for queryObject + totalLen += query_.CalculateParcelLen(SOFTWARE_VERSION_CURRENT); + if (totalLen > INT32_MAX) { + return 0; + } + return totalLen; +} + +bool SubscribeRequest::IsAutoSubscribe() const +{ + return ((GetFlag() & IS_AUTO_SUBSCRIBE) == IS_AUTO_SUBSCRIBE); +} + +void ControlAckPacket::SetPacketHead(int recvCode, uint32_t version, int32_t controlCmd, uint32_t flag) +{ + recvCode_ = recvCode; + version_ = version; + controlCmdType_ = static_cast(controlCmd); + flag_ = flag; +} + +int32_t ControlAckPacket::GetRecvCode() const +{ + return recvCode_; +} + +uint32_t ControlAckPacket::GetVersion() const +{ + return version_; +} + +uint32_t ControlAckPacket::GetcontrolCmdType() const +{ + return controlCmdType_; +} + +uint32_t ControlAckPacket::GetFlag() const +{ + return flag_; +} + +uint32_t ControlAckPacket::CalculateLen() const +{ + uint64_t len = Parcel::GetUInt32Len(); // version_ + len += Parcel::GetIntLen(); // recvCode_ + len += Parcel::GetUInt32Len(); // controlCmdType_ + len += Parcel::GetUInt32Len(); // flag + len = Parcel::GetEightByteAlign(len); + if (len > INT32_MAX) { + return 0; + } + return len; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/syncer/src/single_ver_data_packet.h b/mock/distributeddb/syncer/src/single_ver_data_packet.h new file mode 100644 index 00000000..7731a71a --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_data_packet.h @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_DATA_PACKET_NEW_H +#define SINGLE_VER_DATA_PACKET_NEW_H + +#include "icommunicator.h" +#include "parcel.h" +#include "query_sync_object.h" +#include "single_ver_kvdb_sync_interface.h" +#include "sync_types.h" +#include "version.h" + +namespace DistributedDB { +using SendDataItem = SingleVerKvEntry *; + +class DataRequestPacket { +public: + DataRequestPacket() {}; + virtual ~DataRequestPacket(); + + void SetData(std::vector &data); + + const std::vector &GetData() const; + const std::vector &GetCompressedData() const; + + void SetCompressData(std::vector &compressData); + + const std::vector &GetCompressData() const; + + void SetEndWaterMark(WaterMark waterMark); + + WaterMark GetEndWaterMark() const; + + void SetLocalWaterMark(WaterMark waterMark); + + WaterMark GetLocalWaterMark() const; + + void SetPeerWaterMark(WaterMark waterMark); + + WaterMark GetPeerWaterMark() const; + + void SetSendCode(int32_t errCode); + + int32_t GetSendCode() const; + + void SetMode(int32_t mode); + + int32_t GetMode() const; + + void SetSessionId(uint32_t sessionId); + + uint32_t GetSessionId() const; + + void SetVersion(uint32_t version); + + uint32_t GetVersion() const; + + uint32_t CalculateLen(uint32_t messageId) const; + + void SetReserved(std::vector &reserved); + void SetReserved(std::vector &&reserved); + + std::vector GetReserved() const; + + uint64_t GetPacketId() const; + + void SetFlag(uint32_t flag); + + uint32_t GetFlag() const; + + bool IsLastSequence() const; + + void SetLastSequence(); + + bool IsNeedUpdateWaterMark() const; + + void SetUpdateWaterMark(); + + void SetBasicInfo(int sendCode, uint32_t version, int32_t mode); + + void SetWaterMark(WaterMark localMark, WaterMark peerMark, WaterMark deletedWatermark); + + void SetQuery(const QuerySyncObject &query); + QuerySyncObject GetQuery() const; + + void SetQueryId(const std::string &queryId); + std::string GetQueryId() const; + + void SetDeletedWaterMark(WaterMark watermark); + WaterMark GetDeletedWaterMark() const; + + void SetCompressDataMark(); + bool IsCompressData() const; + + void SetCompressAlgo(CompressAlgorithm algo); + CompressAlgorithm GetCompressAlgo() const; + +protected: + std::vector data_; + WaterMark endWaterMark_ = 0; + WaterMark localWaterMark_ = 0; + WaterMark peerWaterMark_ = 0; + int32_t sendCode_ = 0; + int32_t mode_ = SyncModeType::INVALID_MODE; + uint32_t sessionId_ = 0; + uint32_t version_ = SOFTWARE_VERSION_CURRENT; + std::vector reserved_; + uint32_t flag_ = 0; // bit 0 used for isLastSequence + // add for query sync mode + QuerySyncObject query_; + std::string queryId_; + WaterMark deletedWatermark_ = 0; + std::vector compressData_; // if compressData size is above 0, means use compressData and ignore data_ + CompressAlgorithm algo_ = CompressAlgorithm::NONE; // used for param while serialize compress data + static const uint32_t IS_LAST_SEQUENCE = 0x1; // bit 0 used for isLastSequence, 1: is last, 0: not last + static const uint32_t IS_UPDATE_WATER = 0x2; // bit 1 used for update watermark, 0: update, 1: not update + static const uint32_t IS_COMPRESS_DATA = 0x4; // bit 3 used for compress data, 0: raw data, 1: compress data +}; + +class DataAckPacket { +public: + DataAckPacket() {}; + virtual ~DataAckPacket() {}; + + void SetData(uint64_t data); + + uint64_t GetData() const; + + void SetRecvCode(int32_t errorCode); + + int32_t GetRecvCode() const; + + void SetVersion(uint32_t version); + + uint32_t GetVersion() const; + + void SetReserved(std::vector &reserved); + + std::vector GetReserved() const; + + uint64_t GetPacketId() const; + + static bool IsPacketIdValid(uint64_t packetId); + + uint32_t CalculateLen() const; + +private: + /* + * data_ is waterMark when revCode_ == LOCAL_WATER_MARK_NOT_INIT || revCode_ == E_OK; + * data_ is timer in milliSeconds when revCode_ == -E_SAVE_DATA_NOTIFY && data_ != 0. + */ + uint64_t data_ = 0; + int32_t recvCode_ = 0; + uint32_t version_ = SOFTWARE_VERSION_CURRENT; + std::vector reserved_; +}; + +class ControlRequestPacket { +public: + ControlRequestPacket() {}; + virtual ~ControlRequestPacket() {}; + void SetPacketHead(int sendCode, uint32_t version, int32_t controlCmd, uint32_t flag); + + int32_t GetSendCode() const; + uint32_t GetVersion() const; + uint32_t GetcontrolCmdType() const; + uint32_t GetFlag() const; + virtual void SetQuery(const QuerySyncObject &query); + virtual uint32_t CalculateLen() const; +private: + uint32_t version_ = SOFTWARE_VERSION_CURRENT; + int32_t sendCode_ = 0; + uint32_t controlCmdType_ = 0; + uint32_t flag_ = 0; +}; + +class SubscribeRequest : public ControlRequestPacket { +public: + SubscribeRequest() {}; + ~SubscribeRequest() override {}; + QuerySyncObject GetQuery() const; + bool IsAutoSubscribe() const; + void SetQuery(const QuerySyncObject &query) override; + uint32_t CalculateLen() const override; + static const uint32_t IS_AUTO_SUBSCRIBE = 0x1; +private: + QuerySyncObject query_; +}; + +class ControlAckPacket { +public: + ControlAckPacket() {}; + virtual ~ControlAckPacket() {}; + void SetPacketHead(int recvCode, uint32_t version, int32_t controlCmd, uint32_t flag); + int32_t GetRecvCode() const; + uint32_t GetVersion() const; + uint32_t GetcontrolCmdType() const; + uint32_t GetFlag() const; + uint32_t CalculateLen() const; + +private: + uint32_t version_ = SOFTWARE_VERSION_CURRENT; + int32_t recvCode_ = 0; + uint32_t controlCmdType_ = 0; + uint32_t flag_ = 0; +}; +} // namespace DistributedDB + +#endif // SINGLE_VER_DATA_SYNC_NEW_H \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_data_sync.cpp b/mock/distributeddb/syncer/src/single_ver_data_sync.cpp new file mode 100644 index 00000000..8796f514 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_data_sync.cpp @@ -0,0 +1,2031 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_data_sync.h" + +#include "db_common.h" +#include "db_types.h" +#include "generic_single_ver_kv_entry.h" +#include "intercepted_data_impl.h" +#include "log_print.h" +#include "message_transform.h" +#include "performance_analysis.h" +#include "single_ver_data_sync_utils.h" +#include "single_ver_sync_state_machine.h" +#include "subscribe_manager.h" +#ifdef RELATIONAL_STORE +#include "relational_db_sync_interface.h" +#endif + +namespace DistributedDB { +SingleVerDataSync::SingleVerDataSync() + : mtuSize_(0), + storage_(nullptr), + communicateHandle_(nullptr), + metadata_(nullptr) +{ +} + +SingleVerDataSync::~SingleVerDataSync() +{ + storage_ = nullptr; + communicateHandle_ = nullptr; + metadata_ = nullptr; +} + +int SingleVerDataSync::Initialize(ISyncInterface *inStorage, ICommunicator *inCommunicateHandle, + std::shared_ptr &inMetadata, const std::string &deviceId) +{ + if ((inStorage == nullptr) || (inCommunicateHandle == nullptr) || (inMetadata == nullptr)) { + return -E_INVALID_ARGS; + } + storage_ = static_cast(inStorage); + communicateHandle_ = inCommunicateHandle; + metadata_ = inMetadata; + mtuSize_ = DBConstant::MIN_MTU_SIZE; // default size is 1K, it will update when need sync data. + std::vector label = inStorage->GetIdentifier(); + label.resize(3); // only show 3 Bytes enough + label_ = DBCommon::VectorToHexString(label); + deviceId_ = deviceId; + msgSchedule_.Initialize(label_, deviceId_); + return E_OK; +} + +int SingleVerDataSync::SyncStart(int mode, SingleVerSyncTaskContext *context) +{ + std::lock_guard lock(lock_); + if (sessionId_ != 0) { // auto sync timeout resend + return ReSendData(context); + } + ResetSyncStatus(mode, context); + LOGI("[DataSync] SendStart,mode=%d,label=%s,device=%s", mode_, label_.c_str(), STR_MASK(deviceId_)); + int tmpMode = SyncOperation::TransferSyncMode(mode); + int errCode = E_OK; + if (tmpMode == SyncModeType::PUSH) { + errCode = PushStart(context); + } else if (tmpMode == SyncModeType::PUSH_AND_PULL) { + errCode = PushPullStart(context); + } else if (tmpMode == SyncModeType::PULL) { + errCode = PullRequestStart(context); + } else { + errCode = PullResponseStart(context); + } + if (context->IsSkipTimeoutError(errCode)) { + // if E_TIMEOUT occurred, means send message pressure is high, put into resend map and wait for resend. + // just return to avoid higher pressure for send. + return E_OK; + } + if (errCode != E_OK) { + LOGE("[DataSync] SendStart errCode=%d", errCode); + return errCode; + } + if (tmpMode == SyncModeType::PUSH_AND_PULL && context->GetTaskErrCode() == -E_EKEYREVOKED) { + LOGE("wait for recv finished for push and pull mode"); + return -E_EKEYREVOKED; + } + return InnerSyncStart(context); +} + +int SingleVerDataSync::InnerSyncStart(SingleVerSyncTaskContext *context) +{ + while (true) { + if (windowSize_ <= 0 || isAllDataHasSent_) { + LOGD("[DataSync] InnerDataSync winSize=%d,isAllSent=%d,label=%s,device=%s", windowSize_, isAllDataHasSent_, + label_.c_str(), STR_MASK(deviceId_)); + return E_OK; + } + int mode = SyncOperation::TransferSyncMode(mode_); + if (mode == SyncModeType::PULL) { + LOGE("[DataSync] unexpected error"); + return -E_INVALID_ARGS; + } + int errCode; + context->IncSequenceId(); + if (mode == SyncModeType::PUSH || mode == SyncModeType::PUSH_AND_PULL) { + errCode = PushStart(context); + } else { + errCode = PullResponseStart(context); + } + if ((mode == SyncModeType::PUSH_AND_PULL) && errCode == -E_EKEYREVOKED) { + LOGE("[DataSync] wait for recv finished,label=%s,device=%s", label_.c_str(), STR_MASK(deviceId_)); + isAllDataHasSent_ = true; + return -E_EKEYREVOKED; + } + if (context->IsSkipTimeoutError(errCode)) { + // if E_TIMEOUT occurred, means send message pressure is high, put into resend map and wait for resend. + // just return to avoid higher pressure for send. + return E_OK; + } + if (errCode != E_OK) { + LOGE("[DataSync] InnerSend errCode=%d", errCode); + return errCode; + } + } + return E_OK; +} + +void SingleVerDataSync::InnerClearSyncStatus() +{ + sessionId_ = 0; + reSendMap_.clear(); + windowSize_ = 0; + maxSequenceIdHasSent_ = 0; + isAllDataHasSent_ = false; +} + +int SingleVerDataSync::TryContinueSync(SingleVerSyncTaskContext *context, const Message *message) +{ + if (message == nullptr) { + LOGE("[DataSync] AckRecv message nullptr"); + return -E_INVALID_ARGS; + } + const DataAckPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + uint64_t packetId = packet->GetPacketId(); // above 102 version data request reserve[0] store packetId value + uint32_t sessionId = message->GetSessionId(); + uint32_t sequenceId = message->GetSequenceId(); + + std::lock_guard lock(lock_); + LOGI("[DataSync] recv ack seqId=%" PRIu32 ",packetId=%" PRIu64 ",winSize=%d,label=%s,dev=%s", sequenceId, packetId, + windowSize_, label_.c_str(), STR_MASK(deviceId_)); + if (sessionId != sessionId_) { + LOGI("[DataSync] ignore ack,sessionId is different"); + return E_OK; + } + Timestamp lastQueryTime = 0; + if (reSendMap_.count(sequenceId) != 0) { + lastQueryTime = reSendMap_[sequenceId].end; + reSendMap_.erase(sequenceId); + windowSize_++; + } else { + LOGI("[DataSync] ack seqId not in map"); + return E_OK; + } + if (context->IsQuerySync() && storage_->GetInterfaceType() == ISyncInterface::SYNC_RELATION) { + Timestamp dbLastQueryTime = 0; + int errCode = metadata_->GetLastQueryTime(context->GetQuerySyncId(), context->GetDeviceId(), dbLastQueryTime); + if (errCode == E_OK && dbLastQueryTime < lastQueryTime) { + errCode = metadata_->SetLastQueryTime(context->GetQuerySyncId(), context->GetDeviceId(), lastQueryTime); + } + if (errCode != E_OK) { + return errCode; + } + } + if (!isAllDataHasSent_) { + return InnerSyncStart(context); + } else if (reSendMap_.size() == 0) { + context->SetOperationStatus(SyncOperation::OP_SEND_FINISHED); + InnerClearSyncStatus(); + return -E_FINISHED; + } + return E_OK; +} + +void SingleVerDataSync::ClearSyncStatus() +{ + std::lock_guard lock(lock_); + InnerClearSyncStatus(); +} + +int SingleVerDataSync::ReSendData(SingleVerSyncTaskContext *context) +{ + if (reSendMap_.empty()) { + LOGI("[DataSync] ReSend map empty"); + return -E_INTERNAL_ERROR; + } + uint32_t sequenceId = reSendMap_.begin()->first; + ReSendInfo reSendInfo = reSendMap_.begin()->second; + LOGI("[DataSync] ReSend mode=%d,start=%" PRIu64 ",end=%" PRIu64 ",delStart=%" PRIu64 ",delEnd=%" PRIu64 "," + "seqId=%" PRIu32 ",packetId=%" PRIu64 ",windowsize=%d,label=%s,deviceId=%s", mode_, reSendInfo.start, + reSendInfo.end, reSendInfo.deleteBeginTime, reSendInfo.deleteEndTime, sequenceId, reSendInfo.packetId, + windowSize_, label_.c_str(), STR_MASK(deviceId_)); + DataSyncReSendInfo dataReSendInfo = {sessionId_, sequenceId, reSendInfo.start, reSendInfo.end, + reSendInfo.deleteBeginTime, reSendInfo.deleteEndTime, reSendInfo.packetId}; + return ReSend(context, dataReSendInfo); +} + +std::string SingleVerDataSync::GetLocalDeviceName() +{ + std::string deviceInfo; + if (communicateHandle_ != nullptr) { + communicateHandle_->GetLocalIdentity(deviceInfo); + } + return deviceInfo; +} + +int SingleVerDataSync::Send(SingleVerSyncTaskContext *context, const Message *message, const CommErrHandler &handler, + uint32_t packetLen) +{ + bool startFeedDogRet = false; + if (packetLen > mtuSize_ && mtuSize_ > NOTIFY_MIN_MTU_SIZE) { + uint32_t time = static_cast(static_cast(packetLen) * + static_cast(context->GetTimeoutTime()) / mtuSize_); // no overflow + startFeedDogRet = context->StartFeedDogForSync(time, SyncDirectionFlag::SEND); + } + SendConfig sendConfig; + SetSendConfigParam(storage_->GetDbProperties(), context->GetDeviceId(), false, SEND_TIME_OUT, sendConfig); + int errCode = communicateHandle_->SendMessage(context->GetDeviceId(), message, sendConfig, handler); + if (errCode != E_OK) { + LOGE("[DataSync][Send] send message failed, errCode=%d", errCode); + if (startFeedDogRet) { + context->StopFeedDogForSync(SyncDirectionFlag::SEND); + } + } + return errCode; +} + +int SingleVerDataSync::GetData(SingleVerSyncTaskContext *context, std::vector &outData, size_t packetSize) +{ + int errCode; + UpdateMtuSize(); + if (context->GetRetryStatus() == SyncTaskContext::NEED_RETRY) { + context->SetRetryStatus(SyncTaskContext::NO_NEED_RETRY); + LOGI("[DataSync][GetData] resend data"); + errCode = GetUnsyncData(context, outData, packetSize); + } else { + ContinueToken token; + context->GetContinueToken(token); + if (token == nullptr) { + errCode = GetUnsyncData(context, outData, packetSize); + } else { + LOGD("[DataSync][GetData] get data from token"); + // if there is data to send, read out data, and update local watermark, send data + errCode = GetNextUnsyncData(context, outData, packetSize); + } + } + if (errCode == -E_UNFINISHED) { + LOGD("[DataSync][GetData] not finished."); + } + if (SingleVerDataSyncUtils::IsGetDataSuccessfully(errCode)) { + std::string localHashName = DBCommon::TransferHashString(GetLocalDeviceName()); + SingleVerDataSyncUtils::TransDbDataItemToSendDataItem(localHashName, outData); + } + return errCode; +} + +int SingleVerDataSync::GetDataWithPerformanceRecord(SingleVerSyncTaskContext *context, SyncEntry &syncOutData) +{ + uint32_t version = std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT); + size_t packetSize = (version > SOFTWARE_VERSION_RELEASE_2_0) ? + DBConstant::MAX_HPMODE_PACK_ITEM_SIZE : DBConstant::MAX_NORMAL_PACK_ITEM_SIZE; + bool needCompressOnSync = false; + uint8_t compressionRate = DBConstant::DEFAULT_COMPTRESS_RATE; + (void)storage_->GetCompressionOption(needCompressOnSync, compressionRate); + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(PT_TEST_RECORDS::RECORD_READ_DATA); + } + int errCode = GetData(context, syncOutData.entries, packetSize); + if (performance != nullptr) { + performance->StepTimeRecordEnd(PT_TEST_RECORDS::RECORD_READ_DATA); + } + if (!SingleVerDataSyncUtils::IsGetDataSuccessfully(errCode)) { + context->SetTaskErrCode(errCode); + return errCode; + } + + int innerCode = InterceptData(syncOutData); + if (innerCode != E_OK) { + context->SetTaskErrCode(innerCode); + return innerCode; + } + + CompressAlgorithm remoteAlgo = context->ChooseCompressAlgo(); + if (needCompressOnSync && remoteAlgo != CompressAlgorithm::NONE) { + int compressCode = GenericSingleVerKvEntry::Compress(syncOutData.entries, syncOutData.compressedEntries, + { remoteAlgo, version }); + if (compressCode != E_OK) { + return compressCode; + } + } + return errCode; +} + +int SingleVerDataSync::GetUnsyncData(SingleVerSyncTaskContext *context, std::vector &outData, + size_t packetSize) +{ + int errCode; + WaterMark startMark = 0; + SyncType curType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + GetLocalWaterMark(curType, context->GetQuerySyncId(), context, startMark); + WaterMark endMark = MAX_TIMESTAMP; + if ((endMark == 0) || (startMark > endMark)) { + return E_OK; + } + ContinueToken token = nullptr; + context->GetContinueToken(token); + if (token != nullptr) { + storage_->ReleaseContinueToken(token); + } + DataSizeSpecInfo syncDataSizeInfo = GetDataSizeSpecInfo(packetSize); + if (curType != SyncType::QUERY_SYNC_TYPE) { + errCode = storage_->GetSyncData(startMark, endMark, outData, token, syncDataSizeInfo); + } else { + WaterMark deletedStartMark = 0; + GetLocalDeleteSyncWaterMark(context, deletedStartMark); + Timestamp lastQueryTimestamp = 0; + errCode = metadata_->GetLastQueryTime(context->GetQuerySyncId(), + context->GetDeviceId(), lastQueryTimestamp); + if (errCode == E_OK) { + QuerySyncObject queryObj = context->GetQuery(); + errCode = storage_->GetSyncData(queryObj, + SyncTimeRange{ startMark, deletedStartMark, endMark, endMark, lastQueryTimestamp}, + syncDataSizeInfo, token, outData); + } + } + context->SetContinueToken(token); + if (!SingleVerDataSyncUtils::IsGetDataSuccessfully(errCode)) { + LOGE("[DataSync][GetUnsyncData] get unsync data failed,errCode=%d", errCode); + } + return errCode; +} + +int SingleVerDataSync::GetNextUnsyncData(SingleVerSyncTaskContext *context, std::vector &outData, + size_t packetSize) +{ + ContinueToken token; + context->GetContinueToken(token); + DataSizeSpecInfo syncDataSizeInfo = GetDataSizeSpecInfo(packetSize); + int errCode = storage_->GetSyncDataNext(outData, token, syncDataSizeInfo); + context->SetContinueToken(token); + if (!SingleVerDataSyncUtils::IsGetDataSuccessfully(errCode)) { + LOGE("[DataSync][GetNextUnsyncData] get next unsync data failed, errCode=%d", errCode); + } + return errCode; +} + +int SingleVerDataSync::SaveData(const SingleVerSyncTaskContext *context, const std::vector &inData, + SyncType curType, const QuerySyncObject &query) +{ + if (inData.empty()) { + return E_OK; + } + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(PT_TEST_RECORDS::RECORD_SAVE_DATA); + } + + const std::string localHashName = DBCommon::TransferHashString(GetLocalDeviceName()); + SingleVerDataSyncUtils::TransSendDataItemToLocal(context, localHashName, inData); + int errCode = E_OK; + // query only support prefix key and don't have query in packet in 104 version + errCode = storage_->PutSyncDataWithQuery(query, inData, context->GetDeviceId()); + if (performance != nullptr) { + performance->StepTimeRecordEnd(PT_TEST_RECORDS::RECORD_SAVE_DATA); + } + if (errCode != E_OK) { + LOGE("[DataSync][SaveData] save sync data failed,errCode=%d", errCode); + } + return errCode; +} + +void SingleVerDataSync::ResetSyncStatus(int inMode, SingleVerSyncTaskContext *context) +{ + mode_ = inMode; + maxSequenceIdHasSent_ = 0; + isAllDataHasSent_ = false; + context->ReSetSequenceId(); + reSendMap_.clear(); + if (context->GetRemoteSoftwareVersion() < SOFTWARE_VERSION_RELEASE_3_0) { + windowSize_ = LOW_VERSION_WINDOW_SIZE; + } else { + windowSize_ = HIGH_VERSION_WINDOW_SIZE; + } + int mode = SyncOperation::TransferSyncMode(inMode); + if (mode == SyncModeType::PUSH || mode == SyncModeType::PUSH_AND_PULL || mode == SyncModeType::PULL) { + sessionId_ = context->GetRequestSessionId(); + } else { + sessionId_ = context->GetResponseSessionId(); + } +} + +SyncTimeRange SingleVerDataSync::GetSyncDataTimeRange(SyncType syncType, SingleVerSyncTaskContext *context, + const std::vector &inData, UpdateWaterMark &isUpdate) +{ + WaterMark localMark = 0; + WaterMark deleteMark = 0; + GetLocalWaterMark(syncType, context->GetQuerySyncId(), context, localMark); + GetLocalDeleteSyncWaterMark(context, deleteMark); + return SingleVerDataSyncUtils::GetSyncDataTimeRange(syncType, localMark, deleteMark, inData, isUpdate); +} + +int SingleVerDataSync::SaveLocalWaterMark(SyncType syncType, const SingleVerSyncTaskContext *context, + SyncTimeRange dataTimeRange, bool isCheckBeforUpdate) const +{ + WaterMark localMark = 0; + int errCode = E_OK; + const std::string &deviceId = context->GetDeviceId(); + std::string queryId = context->GetQuerySyncId(); + if (syncType != SyncType::QUERY_SYNC_TYPE) { + if (isCheckBeforUpdate) { + GetLocalWaterMark(syncType, queryId, context, localMark); + if (localMark >= dataTimeRange.endTime) { + return E_OK; + } + } + errCode = metadata_->SaveLocalWaterMark(deviceId, dataTimeRange.endTime); + } else { + bool isNeedUpdateMark = true; + bool isNeedUpdateDeleteMark = true; + if (isCheckBeforUpdate) { + WaterMark deleteDataWaterMark = 0; + GetLocalWaterMark(syncType, queryId, context, localMark); + GetLocalDeleteSyncWaterMark(context, deleteDataWaterMark); + if (localMark >= dataTimeRange.endTime) { + isNeedUpdateMark = false; + } + if (deleteDataWaterMark >= dataTimeRange.deleteEndTime) { + isNeedUpdateDeleteMark = false; + } + } + if (isNeedUpdateMark) { + LOGD("label=%s,dev=%s,endTime=%" PRIu64, label_.c_str(), STR_MASK(GetDeviceId()), dataTimeRange.endTime); + errCode = metadata_->SetSendQueryWaterMark(queryId, deviceId, dataTimeRange.endTime); + if (errCode != E_OK) { + LOGE("[DataSync][SaveLocalWaterMark] save query metadata watermark failed,errCode=%d", errCode); + return errCode; + } + } + if (isNeedUpdateDeleteMark) { + LOGD("label=%s,dev=%s,deleteEndTime=%" PRIu64, label_.c_str(), STR_MASK(GetDeviceId()), + dataTimeRange.deleteEndTime); + errCode = metadata_->SetSendDeleteSyncWaterMark(context->GetDeleteSyncId(), dataTimeRange.deleteEndTime); + } + } + if (errCode != E_OK) { + LOGE("[DataSync][SaveLocalWaterMark] save metadata local watermark failed,errCode=%d", errCode); + } + return errCode; +} + +void SingleVerDataSync::GetPeerWaterMark(SyncType syncType, const std::string &queryIdentify, + const DeviceID &deviceId, WaterMark &waterMark) const +{ + if (syncType != SyncType::QUERY_SYNC_TYPE) { + metadata_->GetPeerWaterMark(deviceId, waterMark); + return; + } + metadata_->GetRecvQueryWaterMark(queryIdentify, deviceId, waterMark); +} + +void SingleVerDataSync::GetPeerDeleteSyncWaterMark(const DeviceID &deviceId, WaterMark &waterMark) +{ + metadata_->GetRecvDeleteSyncWaterMark(deviceId, waterMark); +} + +void SingleVerDataSync::GetLocalDeleteSyncWaterMark(const SingleVerSyncTaskContext *context, + WaterMark &waterMark) const +{ + metadata_->GetSendDeleteSyncWaterMark(context->GetDeleteSyncId(), waterMark, context->IsAutoLiftWaterMark()); +} + +void SingleVerDataSync::GetLocalWaterMark(SyncType syncType, const std::string &queryIdentify, + const SingleVerSyncTaskContext *context, WaterMark &waterMark) const +{ + if (syncType != SyncType::QUERY_SYNC_TYPE) { + metadata_->GetLocalWaterMark(context->GetDeviceId(), waterMark); + return; + } + metadata_->GetSendQueryWaterMark(queryIdentify, context->GetDeviceId(), + waterMark, context->IsAutoLiftWaterMark()); +} + +int SingleVerDataSync::RemoveDeviceDataHandle(SingleVerSyncTaskContext *context, const Message *message, + WaterMark maxSendDataTime) +{ + bool isNeedClearRemoteData = false; + std::lock_guard autoLock(removeDeviceDataLock_); + if (context->GetRemoteSoftwareVersion() > SOFTWARE_VERSION_RELEASE_3_0) { + uint64_t clearDeviceDataMark = 0; + metadata_->GetRemoveDataMark(context->GetDeviceId(), clearDeviceDataMark); + isNeedClearRemoteData = (clearDeviceDataMark == REMOVE_DEVICE_DATA_MARK); + } else { + const DataRequestPacket *packet = message->GetObject(); + if (packet == nullptr) { + LOGE("[RemoveDeviceDataHandle] get packet object failed"); + return -E_INVALID_ARGS; + } + SyncType curType = SyncOperation::GetSyncType(packet->GetMode()); + WaterMark packetLocalMark = packet->GetLocalWaterMark(); + WaterMark peerMark = 0; + GetPeerWaterMark(curType, context->GetQuerySyncId(), context->GetDeviceId(), peerMark); + isNeedClearRemoteData = ((packetLocalMark == 0) && (peerMark != 0)); + } + if (!isNeedClearRemoteData) { + return E_OK; + } + int errCode = E_OK; + if (context->IsNeedClearRemoteStaleData()) { + // need to clear remote device history data + errCode = storage_->RemoveDeviceData(context->GetDeviceId(), true); + if (errCode != E_OK) { + (void)SendDataAck(context, message, errCode, maxSendDataTime); + return errCode; + } + if (context->GetRemoteSoftwareVersion() == SOFTWARE_VERSION_EARLIEST) { + // avoid repeat clear in ack + metadata_->SaveLocalWaterMark(context->GetDeviceId(), 0); + } + } + if (context->GetRemoteSoftwareVersion() > SOFTWARE_VERSION_RELEASE_3_0) { + errCode = metadata_->ResetMetaDataAfterRemoveData(context->GetDeviceId()); + if (errCode != E_OK) { + (void)SendDataAck(context, message, errCode, maxSendDataTime); + return errCode; + } + } + return E_OK; +} + +int SingleVerDataSync::DealRemoveDeviceDataByAck(SingleVerSyncTaskContext *context, WaterMark ackWaterMark, + const std::vector &reserved) +{ + bool isNeedClearRemoteData = false; + std::lock_guard autoLock(removeDeviceDataLock_); + SyncType curType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + if (context->GetRemoteSoftwareVersion() > SOFTWARE_VERSION_RELEASE_3_0) { + uint64_t clearDeviceDataMark = 0; + metadata_->GetRemoveDataMark(context->GetDeviceId(), clearDeviceDataMark); + isNeedClearRemoteData = (clearDeviceDataMark != 0); + } else if (reserved.empty()) { + WaterMark localMark = 0; + GetLocalWaterMark(curType, context->GetQuery().GetIdentify(), context, localMark); + isNeedClearRemoteData = ((localMark != 0) && (ackWaterMark == 0)); + } else { + WaterMark peerMark = 0; + GetPeerWaterMark(curType, context->GetQuerySyncId(), + context->GetDeviceId(), peerMark); + isNeedClearRemoteData = ((reserved[ACK_PACKET_RESERVED_INDEX_LOCAL_WATER_MARK] == 0) && (peerMark != 0)); + } + if (!isNeedClearRemoteData) { + return E_OK; + } + // need to clear remote historydata + LOGI("[DataSync][WaterMarkException] AckRecv reserved not empty,rebuilted,clear historydata,label=%s,dev=%s", + label_.c_str(), STR_MASK(GetDeviceId())); + int errCode = storage_->RemoveDeviceData(context->GetDeviceId(), true); + if (errCode != E_OK) { + return errCode; + } + if (context->GetRemoteSoftwareVersion() > SOFTWARE_VERSION_RELEASE_3_0) { + errCode = metadata_->ResetMetaDataAfterRemoveData(context->GetDeviceId()); + } + return errCode; +} + +void SingleVerDataSync::SetSessionEndTimestamp(Timestamp end) +{ + sessionEndTimestamp_ = end; +} + +Timestamp SingleVerDataSync::GetSessionEndTimestamp() const +{ + return sessionEndTimestamp_; +} + +void SingleVerDataSync::UpdateSendInfo(SyncTimeRange dataTimeRange, SingleVerSyncTaskContext *context) +{ + ReSendInfo reSendInfo; + reSendInfo.start = dataTimeRange.beginTime; + reSendInfo.end = dataTimeRange.endTime; + reSendInfo.deleteBeginTime = dataTimeRange.deleteBeginTime; + reSendInfo.deleteEndTime = dataTimeRange.deleteEndTime; + reSendInfo.packetId = context->GetPacketId(); + maxSequenceIdHasSent_++; + reSendMap_[maxSequenceIdHasSent_] = reSendInfo; + windowSize_--; + ContinueToken token; + context->GetContinueToken(token); + if (token == nullptr) { + isAllDataHasSent_ = true; + } + LOGI("[DataSync] mode=%d,start=%" PRIu64 ",end=%" PRIu64 ",deleteStart=%" PRIu64 ",deleteEnd=%" PRIu64 "," + "seqId=%" PRIu32 ",packetId=%" PRIu64 ",window_size=%d,isAllSend=%d,label=%s,device=%s", mode_, + reSendInfo.start, reSendInfo.end, reSendInfo.deleteBeginTime, reSendInfo.deleteEndTime, maxSequenceIdHasSent_, + reSendInfo.packetId, windowSize_, isAllDataHasSent_, label_.c_str(), STR_MASK(deviceId_)); +} + +void SingleVerDataSync::FillDataRequestPacket(DataRequestPacket *packet, SingleVerSyncTaskContext *context, + SyncEntry &syncData, int sendCode, int mode) +{ + SyncType curType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + uint32_t version = std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT); + WaterMark localMark = 0; + WaterMark peerMark = 0; + WaterMark deleteMark = 0; + bool needCompressOnSync = false; + uint8_t compressionRate = DBConstant::DEFAULT_COMPTRESS_RATE; + (void)storage_->GetCompressionOption(needCompressOnSync, compressionRate); + std::string id = context->GetQuerySyncId(); + GetLocalWaterMark(curType, id, context, localMark); + GetPeerWaterMark(curType, id, context->GetDeviceId(), peerMark); + GetLocalDeleteSyncWaterMark(context, deleteMark); + if (((mode != SyncModeType::RESPONSE_PULL && sendCode == E_OK)) || + (mode == SyncModeType::RESPONSE_PULL && sendCode == SEND_FINISHED)) { + packet->SetLastSequence(); + } + int tmpMode = mode; + if (mode == SyncModeType::RESPONSE_PULL) { + tmpMode = (curType == SyncType::QUERY_SYNC_TYPE) ? SyncModeType::QUERY_PUSH : SyncModeType::PUSH; + } + packet->SetData(syncData.entries); + packet->SetCompressData(syncData.compressedEntries); + packet->SetBasicInfo(sendCode, version, tmpMode); + packet->SetWaterMark(localMark, peerMark, deleteMark); + if (SyncOperation::TransferSyncMode(mode) == SyncModeType::PUSH_AND_PULL) { + packet->SetEndWaterMark(context->GetEndMark()); + packet->SetSessionId(context->GetRequestSessionId()); + } + packet->SetQuery(context->GetQuery()); + packet->SetQueryId(context->GetQuerySyncId()); + CompressAlgorithm curAlgo = context->ChooseCompressAlgo(); + if (needCompressOnSync && curAlgo != CompressAlgorithm::NONE) { + packet->SetCompressDataMark(); + packet->SetCompressAlgo(curAlgo); + } + SingleVerDataSyncUtils::SetPacketId(packet, context, version); + if (curType == SyncType::QUERY_SYNC_TYPE && (context->GetQuery().HasLimit() || + context->GetQuery().HasOrderBy())) { + packet->SetUpdateWaterMark(); + } + LOGD("[DataSync] curType=%d,local=%" PRIu64 ",del=%" PRIu64 ",end=%" PRIu64 ",label=%s,dev=%s,queryId=%s," + "isCompress=%d", static_cast(curType), localMark, deleteMark, context->GetEndMark(), label_.c_str(), + STR_MASK(GetDeviceId()), STR_MASK(context->GetQuery().GetIdentify()), packet->IsCompressData()); +} + +int SingleVerDataSync::RequestStart(SingleVerSyncTaskContext *context, int mode) +{ + if (!SingleVerDataSyncUtils::QuerySyncCheck(context)) { + context->SetTaskErrCode(-E_NOT_SUPPORT); + return -E_NOT_SUPPORT; + } + int errCode = RemoveDeviceDataIfNeed(context); + if (errCode != E_OK) { + context->SetTaskErrCode(errCode); + return errCode; + } + SyncEntry syncData; + // get data + errCode = GetDataWithPerformanceRecord(context, syncData); + SingleVerDataSyncUtils::TranslateErrCodeIfNeed(mode, context->GetRemoteSoftwareVersion(), errCode); + + if (!SingleVerDataSyncUtils::IsGetDataSuccessfully(errCode)) { + LOGE("[DataSync][PushStart] get data failed, errCode=%d", errCode); + return errCode; + } + + DataRequestPacket *packet = new (std::nothrow) DataRequestPacket; + if (packet == nullptr) { + LOGE("[DataSync][PushStart] new DataRequestPacket error"); + return -E_OUT_OF_MEMORY; + } + SyncType curType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + UpdateWaterMark isUpdateWaterMark; + SyncTimeRange dataTime = GetSyncDataTimeRange(curType, context, syncData.entries, isUpdateWaterMark); + if (errCode == E_OK) { + SetSessionEndTimestamp(std::max(dataTime.endTime, dataTime.deleteEndTime)); + } + FillDataRequestPacket(packet, context, syncData, errCode, mode); + errCode = SendDataPacket(curType, packet, context); + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordEnd(PT_TEST_RECORDS::RECORD_MACHINE_START_TO_PUSH_SEND); + } + if (errCode == E_OK || errCode == -E_TIMEOUT) { + UpdateSendInfo(dataTime, context); + } + if (errCode == E_OK) { + if (curType == SyncType::QUERY_SYNC_TYPE && (context->GetQuery().HasLimit() || + context->GetQuery().HasOrderBy())) { + LOGI("[DataSync][RequestStart] query contain limit/offset/orderby, no need to update watermark."); + return E_OK; + } + SyncTimeRange tmpDataTime = SingleVerDataSyncUtils::ReviseLocalMark(curType, dataTime, isUpdateWaterMark); + SaveLocalWaterMark(curType, context, tmpDataTime); + } + return errCode; +} + +int SingleVerDataSync::PushStart(SingleVerSyncTaskContext *context) +{ + if (context == nullptr) { + return -E_INVALID_ARGS; + } + SyncType curType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + return RequestStart(context, + (curType == SyncType::QUERY_SYNC_TYPE) ? SyncModeType::QUERY_PUSH : SyncModeType::PUSH); +} + +int SingleVerDataSync::PushPullStart(SingleVerSyncTaskContext *context) +{ + if (context == nullptr) { + return -E_INVALID_ARGS; + } + return RequestStart(context, context->GetMode()); +} + +int SingleVerDataSync::PullRequestStart(SingleVerSyncTaskContext *context) +{ + if (context == nullptr) { + return -E_INVALID_ARGS; + } + if (!SingleVerDataSyncUtils::QuerySyncCheck(context)) { + context->SetTaskErrCode(-E_NOT_SUPPORT); + return -E_NOT_SUPPORT; + } + int errCode = RemoveDeviceDataIfNeed(context); + if (errCode != E_OK) { + context->SetTaskErrCode(errCode); + return errCode; + } + DataRequestPacket *packet = new (std::nothrow) DataRequestPacket; + if (packet == nullptr) { + LOGE("[DataSync][PullRequest]new DataRequestPacket error"); + return -E_OUT_OF_MEMORY; + } + SyncType syncType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + WaterMark peerMark = 0; + WaterMark localMark = 0; + WaterMark deleteMark = 0; + GetPeerWaterMark(syncType, context->GetQuerySyncId(), + context->GetDeviceId(), peerMark); + GetLocalWaterMark(syncType, context->GetQuerySyncId(), context, localMark); + GetLocalDeleteSyncWaterMark(context, deleteMark); + uint32_t version = std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT); + WaterMark endMark = context->GetEndMark(); + SyncTimeRange dataTime = {localMark, deleteMark, localMark, deleteMark}; + + packet->SetBasicInfo(E_OK, version, context->GetMode()); + packet->SetWaterMark(localMark, peerMark, deleteMark); + packet->SetEndWaterMark(endMark); + packet->SetSessionId(context->GetRequestSessionId()); + packet->SetQuery(context->GetQuery()); + packet->SetQueryId(context->GetQuerySyncId()); + packet->SetLastSequence(); + SingleVerDataSyncUtils::SetPacketId(packet, context, version); + + LOGD("[DataSync][Pull] curType=%d,local=%" PRIu64 ",del=%" PRIu64 ",end=%" PRIu64 ",peer=%" PRIu64 ",label=%s," + "dev=%s", static_cast(syncType), localMark, deleteMark, endMark, peerMark, label_.c_str(), + STR_MASK(GetDeviceId())); + UpdateSendInfo(dataTime, context); + return SendDataPacket(syncType, packet, context); +} + +int SingleVerDataSync::PullResponseStart(SingleVerSyncTaskContext *context) +{ + if (context == nullptr) { + return -E_INVALID_ARGS; + } + SyncEntry syncData; + // get data + int errCode = GetDataWithPerformanceRecord(context, syncData); + if (!SingleVerDataSyncUtils::IsGetDataSuccessfully(errCode)) { + if (context->GetRemoteSoftwareVersion() > SOFTWARE_VERSION_RELEASE_2_0) { + SendPullResponseDataPkt(errCode, syncData, context); + } + return errCode; + } + // if send finished + int ackCode = E_OK; + ContinueToken token = nullptr; + context->GetContinueToken(token); + if (errCode == E_OK && token == nullptr) { + LOGD("[DataSync][PullResponse] send last frame end"); + ackCode = SEND_FINISHED; + } + SyncType curType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + UpdateWaterMark isUpdateWaterMark; + SyncTimeRange dataTime = GetSyncDataTimeRange(curType, context, syncData.entries, isUpdateWaterMark); + if (errCode == E_OK) { + SetSessionEndTimestamp(std::max(dataTime.endTime, dataTime.deleteEndTime)); + } + errCode = SendPullResponseDataPkt(ackCode, syncData, context); + if (errCode == E_OK || errCode == -E_TIMEOUT) { + UpdateSendInfo(dataTime, context); + } + if (errCode == E_OK) { + if (curType == SyncType::QUERY_SYNC_TYPE && (context->GetQuery().HasLimit() || + context->GetQuery().HasOrderBy())) { + LOGI("[DataSync][PullResponseStart] query contain limit/offset/orderby, no need to update watermark."); + return E_OK; + } + SyncTimeRange tmpDataTime = SingleVerDataSyncUtils::ReviseLocalMark(curType, dataTime, isUpdateWaterMark); + SaveLocalWaterMark(curType, context, tmpDataTime); + } + return errCode; +} + +void SingleVerDataSync::UpdateQueryPeerWaterMark(SyncType syncType, const std::string &queryId, SyncTimeRange &dataTime, + const SingleVerSyncTaskContext *context, UpdateWaterMark isUpdateWaterMark) +{ + WaterMark tmpPeerWatermark = dataTime.endTime; + WaterMark tmpPeerDeletedWatermark = dataTime.deleteEndTime; + if (isUpdateWaterMark.normalUpdateMark) { + tmpPeerWatermark++; + } + if (isUpdateWaterMark.deleteUpdateMark) { + tmpPeerDeletedWatermark++; + } + UpdatePeerWaterMark(syncType, queryId, context, tmpPeerWatermark, tmpPeerDeletedWatermark); +} + +void SingleVerDataSync::UpdatePeerWaterMark(SyncType syncType, const std::string &queryId, + const SingleVerSyncTaskContext *context, WaterMark peerWatermark, WaterMark peerDeletedWatermark) +{ + if (peerWatermark == 0 && peerDeletedWatermark == 0) { + return; + } + int errCode = E_OK; + if (syncType != SyncType::QUERY_SYNC_TYPE) { + errCode = metadata_->SavePeerWaterMark(context->GetDeviceId(), peerWatermark, true); + } else { + if (peerWatermark != 0) { + LOGD("label=%s,dev=%s,endTime=%" PRIu64, label_.c_str(), STR_MASK(GetDeviceId()), peerWatermark); + errCode = metadata_->SetRecvQueryWaterMark(queryId, context->GetDeviceId(), peerWatermark); + if (errCode != E_OK) { + LOGE("[DataSync][UpdatePeerWaterMark] save query peer water mark failed,errCode=%d", errCode); + } + } + if (peerDeletedWatermark != 0) { + LOGD("label=%s,dev=%s,peerDeletedTime=%" PRIu64, + label_.c_str(), STR_MASK(GetDeviceId()), peerDeletedWatermark); + errCode = metadata_->SetRecvDeleteSyncWaterMark(context->GetDeleteSyncId(), peerDeletedWatermark); + } + } + if (errCode != E_OK) { + LOGE("[DataSync][UpdatePeerWaterMark] save peer water mark failed,errCode=%d", errCode); + } +} + +int SingleVerDataSync::DoAbilitySyncIfNeed(SingleVerSyncTaskContext *context, const Message *message, bool isControlMsg) +{ + uint16_t remoteCommunicatorVersion = 0; + if (communicateHandle_->GetRemoteCommunicatorVersion(context->GetDeviceId(), remoteCommunicatorVersion) == + -E_NOT_FOUND) { + LOGE("[DataSync][DoAbilitySyncIfNeed] get remote communicator version failed"); + return -E_VERSION_NOT_SUPPORT; + } + // If remote is not the first version, we need check SOFTWARE_VERSION_BASE + if (remoteCommunicatorVersion == 0) { + LOGI("[DataSync] set remote version 0"); + context->SetRemoteSoftwareVersion(SOFTWARE_VERSION_EARLIEST); + return E_OK; + } else { + LOGI("[DataSync][DoAbilitySyncIfNeed] need do ability sync"); + if (isControlMsg) { + SendControlAck(context, message, -E_NEED_ABILITY_SYNC, 0); + } else { + SendDataAck(context, message, -E_NEED_ABILITY_SYNC, 0); + } + return -E_WAIT_NEXT_MESSAGE; + } +} + +int SingleVerDataSync::DataRequestRecvPre(SingleVerSyncTaskContext *context, const Message *message) +{ + if (context == nullptr || message == nullptr) { + return -E_INVALID_ARGS; + } + auto *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + if (context->GetRemoteSoftwareVersion() <= SOFTWARE_VERSION_BASE) { + return DoAbilitySyncIfNeed(context, message); + } + int32_t sendCode = packet->GetSendCode(); + if (sendCode == -E_VERSION_NOT_SUPPORT) { + LOGE("[DataSync] Version mismatch: ver=%u, current=%u", packet->GetVersion(), SOFTWARE_VERSION_CURRENT); + (void)SendDataAck(context, message, -E_VERSION_NOT_SUPPORT, 0); + return -E_WAIT_NEXT_MESSAGE; + } + // only deal with pull response packet errCode + if (sendCode != E_OK && sendCode != SEND_FINISHED && + message->GetSessionId() == context->GetRequestSessionId()) { + LOGE("[DataSync][DataRequestRecvPre] remote pullResponse getData sendCode=%d", sendCode); + return sendCode; + } + int errCode = RunPermissionCheck(context, message, packet); + if (errCode != E_OK) { + return errCode; + } + if (std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT) > SOFTWARE_VERSION_RELEASE_2_0) { + errCode = CheckSchemaStrategy(context, message); + } + if (errCode == E_OK) { + errCode = SingleVerDataSyncUtils::RequestQueryCheck(packet, storage_); + } + if (errCode != E_OK) { + (void)SendDataAck(context, message, errCode, 0); + } + return errCode; +} + +int SingleVerDataSync::DataRequestRecv(SingleVerSyncTaskContext *context, const Message *message, + WaterMark &pullEndWatermark) +{ + int errCode = DataRequestRecvPre(context, message); + if (errCode != E_OK) { + return errCode; + } + const DataRequestPacket *packet = message->GetObject(); + const std::vector &data = packet->GetData(); + SyncType curType = SyncOperation::GetSyncType(packet->GetMode()); + LOGI("[DataSync][DataRequestRecv] curType=%d, remote ver=%" PRIu32 ", size=%zu, errCode=%d, queryId=%s," + " Label=%s, dev=%s", static_cast(curType), packet->GetVersion(), data.size(), packet->GetSendCode(), + STR_MASK(packet->GetQueryId()), label_.c_str(), STR_MASK(GetDeviceId())); + context->SetReceiveWaterMarkErr(false); + UpdateWaterMark isUpdateWaterMark; + SyncTimeRange dataTime = SingleVerDataSyncUtils::GetRecvDataTimeRange(curType, data, isUpdateWaterMark); + errCode = RemoveDeviceDataHandle(context, message, dataTime.endTime); + if (errCode != E_OK) { + return errCode; + } + if (WaterMarkErrHandle(curType, context, message)) { + return E_OK; + } + GetPullEndWatermark(context, packet, pullEndWatermark); + // save data first + errCode = SaveData(context, data, curType, packet->GetQuery()); + if (errCode != E_OK) { + (void)SendDataAck(context, message, errCode, dataTime.endTime); + return errCode; + } + if (pullEndWatermark > 0 && !storage_->IsReadable()) { // pull mode + pullEndWatermark = 0; + errCode = SendDataAck(context, message, -E_EKEYREVOKED, dataTime.endTime); + } else { + // if data is empty, we don't know the max timestap of this packet. + errCode = SendDataAck(context, message, !data.empty() ? E_OK : WATER_MARK_INVALID, dataTime.endTime); + } + RemotePushFinished(packet->GetSendCode(), packet->GetMode(), message->GetSessionId(), + context->GetRequestSessionId()); + if (curType != SyncType::QUERY_SYNC_TYPE && isUpdateWaterMark.normalUpdateMark) { + UpdatePeerWaterMark(curType, "", context, dataTime.endTime + 1, 0); + } else if (curType == SyncType::QUERY_SYNC_TYPE && packet->IsNeedUpdateWaterMark()) { + UpdateQueryPeerWaterMark(curType, packet->GetQueryId(), dataTime, context, isUpdateWaterMark); + } + if (errCode != E_OK) { + return errCode; + } + if (packet->GetSendCode() == SEND_FINISHED) { + return -E_RECV_FINISHED; + } + return errCode; +} + +int SingleVerDataSync::SendDataPacket(SyncType syncType, const DataRequestPacket *packet, + SingleVerSyncTaskContext *context) +{ + Message *message = new (std::nothrow) Message(SingleVerDataSyncUtils::GetMessageId(syncType)); + if (message == nullptr) { + LOGE("[DataSync][SendDataPacket] new message error"); + delete packet; + packet = nullptr; + return -E_OUT_OF_MEMORY; + } + uint32_t packetLen = packet->CalculateLen(SingleVerDataSyncUtils::GetMessageId(syncType)); + int errCode = message->SetExternalObject(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + delete message; + message = nullptr; + LOGE("[DataSync][SendDataPacket] set external object failed errCode=%d", errCode); + return errCode; + } + SingleVerDataSyncUtils::SetMessageHeadInfo(*message, TYPE_REQUEST, context->GetDeviceId(), + context->GetSequenceId(), context->GetRequestSessionId()); + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(PT_TEST_RECORDS::RECORD_DATA_SEND_REQUEST_TO_ACK_RECV); + } + CommErrHandler handler = std::bind(&SyncTaskContext::CommErrHandlerFunc, std::placeholders::_1, + context, message->GetSessionId()); + errCode = Send(context, message, handler, packetLen); + if (errCode != E_OK) { + delete message; + message = nullptr; + } + + return errCode; +} + +int SingleVerDataSync::SendPullResponseDataPkt(int ackCode, SyncEntry &syncOutData, + SingleVerSyncTaskContext *context) +{ + DataRequestPacket *packet = new (std::nothrow) DataRequestPacket; + if (packet == nullptr) { + LOGE("[DataSync][SendPullResponseDataPkt] new data request packet error"); + return -E_OUT_OF_MEMORY; + } + SyncType syncType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + FillDataRequestPacket(packet, context, syncOutData, ackCode, SyncModeType::RESPONSE_PULL); + uint32_t packetLen = packet->CalculateLen(SingleVerDataSyncUtils::GetMessageId(syncType)); + Message *message = new (std::nothrow) Message(SingleVerDataSyncUtils::GetMessageId(syncType)); + if (message == nullptr) { + LOGE("[DataSync][SendPullResponseDataPkt] new message error"); + delete packet; + packet = nullptr; + return -E_OUT_OF_MEMORY; + } + int errCode = message->SetExternalObject(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + delete message; + message = nullptr; + LOGE("[SendPullResponseDataPkt] set external object failed, errCode=%d", errCode); + return errCode; + } + SingleVerDataSyncUtils::SetMessageHeadInfo(*message, TYPE_REQUEST, context->GetDeviceId(), + context->GetSequenceId(), context->GetResponseSessionId()); + SendResetWatchDogPacket(context, packetLen); + errCode = Send(context, message, nullptr, packetLen); + if (errCode != E_OK) { + delete message; + message = nullptr; + } + return errCode; +} + +void SingleVerDataSync::SendFinishedDataAck(SingleVerSyncTaskContext *context, const Message *message) +{ + SendDataAck(context, message, E_OK, 0); +} + +int SingleVerDataSync::SendDataAck(SingleVerSyncTaskContext *context, const Message *message, int32_t recvCode, + WaterMark maxSendDataTime) +{ + const DataRequestPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + Message *ackMessage = new (std::nothrow) Message(message->GetMessageId()); + if (ackMessage == nullptr) { + LOGE("[DataSync][SendDataAck] new message error"); + return -E_OUT_OF_MEMORY; + } + DataAckPacket ack; + SetAckPacket(ack, context, packet, recvCode, maxSendDataTime); + int errCode = ackMessage->SetCopiedObject(ack); + if (errCode != E_OK) { + delete ackMessage; + ackMessage = nullptr; + LOGE("[DataSync][SendDataAck] set copied object failed, errcode=%d", errCode); + return errCode; + } + SingleVerDataSyncUtils::SetMessageHeadInfo(*ackMessage, TYPE_RESPONSE, context->GetDeviceId(), + message->GetSequenceId(), message->GetSessionId()); + + errCode = Send(context, ackMessage, nullptr, 0); + if (errCode != E_OK) { + delete ackMessage; + ackMessage = nullptr; + } + return errCode; +} + +bool SingleVerDataSync::AckPacketIdCheck(const Message *message) +{ + if (message == nullptr) { + LOGE("[DataSync] AckRecv message nullptr"); + return false; + } + if (message->GetMessageType() == TYPE_NOTIFY || message->IsFeedbackError()) { + return true; + } + const DataAckPacket *packet = message->GetObject(); + if (packet == nullptr) { + return false; + } + uint64_t packetId = packet->GetPacketId(); // above 102 version data request reserve[0] store packetId value + std::lock_guard lock(lock_); + uint32_t sequenceId = message->GetSequenceId(); + if (reSendMap_.count(sequenceId) != 0) { + uint64_t originalPacketId = reSendMap_[sequenceId].packetId; + if (DataAckPacket::IsPacketIdValid(packetId) && packetId != originalPacketId) { + LOGE("[DataSync] packetId[%" PRIu64 "] is not match with original[%" PRIu64 "]", packetId, + originalPacketId); + return false; + } + } + return true; +} + +int SingleVerDataSync::AckRecv(SingleVerSyncTaskContext *context, const Message *message) +{ + int errCode = SingleVerDataSyncUtils::AckMsgErrnoCheck(context, message); + if (errCode != E_OK) { + return errCode; + } + const DataAckPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + int32_t recvCode = packet->GetRecvCode(); + LOGD("[DataSync][AckRecv] ver=%u,recvCode=%d,myversion=%u,label=%s,dev=%s", packet->GetVersion(), recvCode, + SOFTWARE_VERSION_CURRENT, label_.c_str(), STR_MASK(GetDeviceId())); + if (recvCode == -E_VERSION_NOT_SUPPORT) { + LOGE("[DataSync][AckRecv] Version mismatch"); + return -E_VERSION_NOT_SUPPORT; + } + + if (recvCode == -E_NEED_ABILITY_SYNC || recvCode == -E_NOT_PERMIT) { + // we should ReleaseContinueToken, avoid crash + LOGI("[DataSync][AckRecv] Data sync abort,recvCode =%d,label =%s,dev=%s", recvCode, label_.c_str(), + STR_MASK(GetDeviceId())); + context->ReleaseContinueToken(); + return recvCode; + } + uint64_t data = packet->GetData(); + if (recvCode == LOCAL_WATER_MARK_NOT_INIT) { + return DealWaterMarkException(context, data, packet->GetReserved()); + } + + if (recvCode == -E_SAVE_DATA_NOTIFY && data != 0) { + // data only use low 32bit + context->StartFeedDogForSync(static_cast(data), SyncDirectionFlag::RECEIVE); + LOGI("[DataSync][AckRecv] notify ResetWatchDog=%" PRIu64 ",label=%s,dev=%s", data, label_.c_str(), + STR_MASK(GetDeviceId())); + } + + if (recvCode != E_OK && recvCode != WATER_MARK_INVALID) { + LOGW("[DataSync][AckRecv] Received a uncatched recvCode=%d,label=%s,dev=%s", recvCode, + label_.c_str(), STR_MASK(GetDeviceId())); + return recvCode; + } + + // Judge if send finished + ContinueToken token; + context->GetContinueToken(token); + if (((message->GetSessionId() == context->GetResponseSessionId()) || + (message->GetSessionId() == context->GetRequestSessionId())) && (token == nullptr)) { + return -E_NO_DATA_SEND; + } + + // send next data + return -E_SEND_DATA; +} + +void SingleVerDataSync::SendSaveDataNotifyPacket(SingleVerSyncTaskContext *context, uint32_t pktVersion, + uint32_t sessionId, uint32_t sequenceId, uint32_t inMsgId) +{ + if (inMsgId != DATA_SYNC_MESSAGE && inMsgId != QUERY_SYNC_MESSAGE) { + LOGE("[SingleVerDataSync] messageId not available."); + return; + } + Message *ackMessage = new (std::nothrow) Message(inMsgId); + if (ackMessage == nullptr) { + LOGE("[DataSync][SaveDataNotify] new message failed"); + return; + } + + DataAckPacket ack; + ack.SetRecvCode(-E_SAVE_DATA_NOTIFY); + ack.SetVersion(pktVersion); + int errCode = ackMessage->SetCopiedObject(ack); + if (errCode != E_OK) { + delete ackMessage; + ackMessage = nullptr; + LOGE("[DataSync][SendSaveDataNotifyPacket] set copied object failed,errcode=%d", errCode); + return; + } + SingleVerDataSyncUtils::SetMessageHeadInfo(*ackMessage, TYPE_NOTIFY, context->GetDeviceId(), sequenceId, sessionId); + + errCode = Send(context, ackMessage, nullptr, 0); + if (errCode != E_OK) { + delete ackMessage; + ackMessage = nullptr; + } + LOGD("[DataSync][SaveDataNotify] Send SaveDataNotify packet Finished,errcode=%d,label=%s,dev=%s", + errCode, label_.c_str(), STR_MASK(GetDeviceId())); +} + +void SingleVerDataSync::GetPullEndWatermark(const SingleVerSyncTaskContext *context, const DataRequestPacket *packet, + WaterMark &pullEndWatermark) const +{ + if (packet == nullptr) { + return; + } + int mode = SyncOperation::TransferSyncMode(packet->GetMode()); + if ((mode == SyncModeType::PULL) || (mode == SyncModeType::PUSH_AND_PULL)) { + WaterMark endMark = packet->GetEndWaterMark(); + TimeOffset offset; + metadata_->GetTimeOffset(context->GetDeviceId(), offset); + pullEndWatermark = endMark - static_cast(offset); + LOGD("[DataSync][PullEndWatermark] packetEndMark=%" PRIu64 ",offset=%" PRId64 ",endWaterMark=%" PRIu64 "," + "label=%s,dev=%s", endMark, offset, pullEndWatermark, label_.c_str(), STR_MASK(GetDeviceId())); + } +} + +int SingleVerDataSync::DealWaterMarkException(SingleVerSyncTaskContext *context, WaterMark ackWaterMark, + const std::vector &reserved) +{ + WaterMark deletedWaterMark = 0; + SyncType curType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + if (curType == SyncType::QUERY_SYNC_TYPE) { + if (reserved.size() <= ACK_PACKET_RESERVED_INDEX_DELETE_WATER_MARK) { + LOGE("[DataSync] get packet reserve size failed"); + return -E_INVALID_ARGS; + } + deletedWaterMark = reserved[ACK_PACKET_RESERVED_INDEX_DELETE_WATER_MARK]; + } + LOGI("[DataSync][WaterMarkException] AckRecv water error, mark=%" PRIu64 ",deleteMark=%" PRIu64 "," + "label=%s,dev=%s", ackWaterMark, deletedWaterMark, label_.c_str(), STR_MASK(GetDeviceId())); + int errCode = SaveLocalWaterMark(curType, context, + {0, 0, ackWaterMark, deletedWaterMark}); + if (errCode != E_OK) { + return errCode; + } + context->SetRetryStatus(SyncTaskContext::NEED_RETRY); + context->IncNegotiationCount(); + SingleVerDataSyncUtils::PushAndPullKeyRevokHandle(context); + if (!context->IsNeedClearRemoteStaleData()) { + return -E_RE_SEND_DATA; + } + errCode = DealRemoveDeviceDataByAck(context, ackWaterMark, reserved); + if (errCode != E_OK) { + return errCode; + } + return -E_RE_SEND_DATA; +} + +int SingleVerDataSync::RunPermissionCheck(SingleVerSyncTaskContext *context, const Message *message, + const DataRequestPacket *packet) +{ + int mode = SyncOperation::TransferSyncMode(packet->GetMode()); + int errCode = SingleVerDataSyncUtils::RunPermissionCheck(context, storage_, label_, mode); + if (errCode != E_OK) { + if (context->GetRemoteSoftwareVersion() > SOFTWARE_VERSION_EARLIEST) { // ver 101 can't handle this errCode + (void)SendDataAck(context, message, -E_NOT_PERMIT, 0); + } + return -E_NOT_PERMIT; + } + const std::vector &data = packet->GetData(); + WaterMark maxSendDataTime = SingleVerDataSyncUtils::GetMaxSendDataTime(data); + uint32_t version = std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT); + if (version > SOFTWARE_VERSION_RELEASE_2_0 && (mode != SyncModeType::PULL) && + !context->GetReceivcPermitCheck()) { + bool permitReceive = SingleVerDataSyncUtils::CheckPermitReceiveData(context, communicateHandle_); + if (permitReceive) { + context->SetReceivcPermitCheck(true); + } else { + (void)SendDataAck(context, message, -E_SECURITY_OPTION_CHECK_ERROR, maxSendDataTime); + return -E_SECURITY_OPTION_CHECK_ERROR; + } + } + return errCode; +} + +// used in pull response +void SingleVerDataSync::SendResetWatchDogPacket(SingleVerSyncTaskContext *context, uint32_t packetLen) +{ + // When mtu less than 30k, we send data with bluetooth + // In order not to block the bluetooth channel, we don't send notify packet here + if (mtuSize_ >= packetLen || mtuSize_ < NOTIFY_MIN_MTU_SIZE) { + return; + } + uint64_t data = static_cast(packetLen) * static_cast(context->GetTimeoutTime()) / mtuSize_; + + Message *ackMessage = new (std::nothrow) Message(DATA_SYNC_MESSAGE); + if (ackMessage == nullptr) { + LOGE("[DataSync][ResetWatchDog] new message failed"); + return; + } + + DataAckPacket ack; + ack.SetData(data); + ack.SetRecvCode(-E_SAVE_DATA_NOTIFY); + ack.SetVersion(std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT)); + int errCode = ackMessage->SetCopiedObject(ack); + if (errCode != E_OK) { + delete ackMessage; + ackMessage = nullptr; + LOGE("[DataSync][ResetWatchDog] set copied object failed, errcode=%d", errCode); + return; + } + SingleVerDataSyncUtils::SetMessageHeadInfo(*ackMessage, TYPE_NOTIFY, context->GetDeviceId(), + context->GetSequenceId(), context->GetResponseSessionId()); + + errCode = Send(context, ackMessage, nullptr, 0); + if (errCode != E_OK) { + delete ackMessage; + ackMessage = nullptr; + LOGE("[DataSync][ResetWatchDog] Send packet failed,errcode=%d,label=%s,dev=%s", errCode, label_.c_str(), + STR_MASK(GetDeviceId())); + } else { + LOGI("[DataSync][ResetWatchDog] data = %" PRIu64 ",label=%s,dev=%s", data, label_.c_str(), + STR_MASK(GetDeviceId())); + } +} + +int32_t SingleVerDataSync::ReSend(SingleVerSyncTaskContext *context, DataSyncReSendInfo reSendInfo) +{ + if (context == nullptr) { + return -E_INVALID_ARGS; + } + SyncEntry syncData; + int errCode = GetReSendData(syncData, context, reSendInfo); + if (!SingleVerDataSyncUtils::IsGetDataSuccessfully(errCode)) { + return errCode; + } + SyncType curType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + DataRequestPacket *packet = new (std::nothrow) DataRequestPacket; + if (packet == nullptr) { + LOGE("[DataSync][ReSend] new DataRequestPacket error"); + return -E_OUT_OF_MEMORY; + } + FillRequestReSendPacket(context, packet, reSendInfo, syncData, errCode); + errCode = SendReSendPacket(packet, context, reSendInfo.sessionId, reSendInfo.sequenceId); + if (errCode == E_OK && SyncOperation::TransferSyncMode(context->GetMode()) != SyncModeType::PULL) { + // resend.end may not update in localwatermark while E_TIMEOUT occurred in send message last time. + SyncTimeRange dataTime {reSendInfo.start, reSendInfo.deleteDataStart, reSendInfo.end, reSendInfo.deleteDataEnd}; + if (reSendInfo.deleteDataEnd > reSendInfo.deleteDataStart && curType == SyncType::QUERY_SYNC_TYPE) { + dataTime.deleteEndTime += 1; + } + if (reSendInfo.end > reSendInfo.start) { + dataTime.endTime += 1; + } + SaveLocalWaterMark(curType, context, dataTime, true); + } + return errCode; +} + +int SingleVerDataSync::SendReSendPacket(const DataRequestPacket *packet, SingleVerSyncTaskContext *context, + uint32_t sessionId, uint32_t sequenceId) +{ + SyncType syncType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + Message *message = new (std::nothrow) Message(SingleVerDataSyncUtils::GetMessageId(syncType)); + if (message == nullptr) { + LOGE("[DataSync][SendDataPacket] new message error"); + delete packet; + packet = nullptr; + return -E_OUT_OF_MEMORY; + } + uint32_t packetLen = packet->CalculateLen(SingleVerDataSyncUtils::GetMessageId(syncType)); + int errCode = message->SetExternalObject(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + delete message; + message = nullptr; + LOGE("[DataSync][SendReSendPacket] SetExternalObject failed errCode=%d", errCode); + return errCode; + } + SingleVerDataSyncUtils::SetMessageHeadInfo(*message, TYPE_REQUEST, context->GetDeviceId(), sequenceId, sessionId); + CommErrHandler handler = std::bind(&SyncTaskContext::CommErrHandlerFunc, std::placeholders::_1, + context, message->GetSessionId()); + errCode = Send(context, message, handler, packetLen); + if (errCode != E_OK) { + delete message; + message = nullptr; + } + return errCode; +} + +int SingleVerDataSync::CheckPermitSendData(int inMode, SingleVerSyncTaskContext *context) +{ + uint32_t version = std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT); + int mode = SyncOperation::TransferSyncMode(inMode); + // for pull mode it just need to get data, no need to send data. + if (version <= SOFTWARE_VERSION_RELEASE_2_0 || mode == SyncModeType::PULL) { + return E_OK; + } + if (context->GetSendPermitCheck()) { + return E_OK; + } + bool isPermitSync = true; + std::string deviceId = context->GetDeviceId(); + SecurityOption remoteSecOption = context->GetRemoteSeccurityOption(); + if (mode == SyncModeType::PUSH || mode == SyncModeType::PUSH_AND_PULL || mode == SyncModeType::RESPONSE_PULL) { + isPermitSync = SingleVerDataSyncUtils::IsPermitRemoteDeviceRecvData(deviceId, remoteSecOption, storage_); + } + LOGI("[DataSync][PermitSendData] mode=%d,dev=%s,label=%d,flag=%d,PermitSync=%d", mode, STR_MASK(deviceId_), + remoteSecOption.securityLabel, remoteSecOption.securityFlag, isPermitSync); + if (isPermitSync) { + context->SetSendPermitCheck(true); + return E_OK; + } + if (mode == SyncModeType::PUSH || mode == SyncModeType::PUSH_AND_PULL) { + context->SetTaskErrCode(-E_SECURITY_OPTION_CHECK_ERROR); + return -E_SECURITY_OPTION_CHECK_ERROR; + } + if (mode == SyncModeType::RESPONSE_PULL) { + SyncEntry syncData; + SendPullResponseDataPkt(-E_SECURITY_OPTION_CHECK_ERROR, syncData, context); + return -E_SECURITY_OPTION_CHECK_ERROR; + } + if (mode == SyncModeType::SUBSCRIBE_QUERY) { + return -E_SECURITY_OPTION_CHECK_ERROR; + } + return E_OK; +} + +std::string SingleVerDataSync::GetLabel() const +{ + return label_; +} + +std::string SingleVerDataSync::GetDeviceId() const +{ + return deviceId_; +} + +bool SingleVerDataSync::WaterMarkErrHandle(SyncType syncType, SingleVerSyncTaskContext *context, const Message *message) +{ + const DataRequestPacket *packet = message->GetObject(); + if (packet == nullptr) { + LOGE("[WaterMarkErrHandle] get packet object failed"); + return -E_INVALID_ARGS; + } + WaterMark packetLocalMark = packet->GetLocalWaterMark(); + WaterMark packetDeletedMark = packet->GetDeletedWaterMark(); + WaterMark peerMark = 0; + WaterMark deletedMark = 0; + GetPeerWaterMark(syncType, packet->GetQueryId(), context->GetDeviceId(), peerMark); + if (syncType == SyncType::QUERY_SYNC_TYPE) { + GetPeerDeleteSyncWaterMark(context->GetDeleteSyncId(), deletedMark); + } + if (syncType != SyncType::QUERY_SYNC_TYPE && packetLocalMark > peerMark) { + LOGI("[DataSync][DataRequestRecv] packetLocalMark=%" PRIu64 ",current=%" PRIu64, packetLocalMark, peerMark); + context->SetReceiveWaterMarkErr(true); + SendDataAck(context, message, LOCAL_WATER_MARK_NOT_INIT, 0); + return true; + } + if (syncType == SyncType::QUERY_SYNC_TYPE && (packetLocalMark > peerMark || packetDeletedMark > deletedMark)) { + LOGI("[DataSync][DataRequestRecv] packetDeletedMark=%" PRIu64 ",deletedMark=%" PRIu64 "," + "packetLocalMark=%" PRIu64 ",peerMark=%" PRIu64, packetDeletedMark, deletedMark, packetLocalMark, + peerMark); + context->SetReceiveWaterMarkErr(true); + SendDataAck(context, message, LOCAL_WATER_MARK_NOT_INIT, 0); + return true; + } + return false; +} + +int SingleVerDataSync::CheckSchemaStrategy(SingleVerSyncTaskContext *context, const Message *message) +{ + auto *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + auto query = packet->GetQuery(); + SyncStrategy localStrategy = context->GetSyncStrategy(query); + if (!context->GetIsSchemaSync()) { + LOGE("[DataSync][CheckSchemaStrategy] isSchemaSync=%d check failed", context->GetIsSchemaSync()); + (void)SendDataAck(context, message, -E_NEED_ABILITY_SYNC, 0); + return -E_NEED_ABILITY_SYNC; + } + if (!localStrategy.permitSync) { + LOGE("[DataSync][CheckSchemaStrategy] Strategy permitSync=%d check failed", localStrategy.permitSync); + (void)SendDataAck(context, message, -E_SCHEMA_MISMATCH, 0); + return -E_SCHEMA_MISMATCH; + } + return E_OK; +} + +void SingleVerDataSync::RemotePushFinished(int sendCode, int inMode, uint32_t msgSessionId, uint32_t contextSessionId) +{ + int mode = SyncOperation::TransferSyncMode(inMode); + if ((mode != SyncModeType::PUSH) && (mode != SyncModeType::PUSH_AND_PULL) && (mode != SyncModeType::QUERY_PUSH) && + (mode != SyncModeType::QUERY_PUSH_PULL)) { + return; + } + + if ((sendCode == E_OK) && (msgSessionId != 0) && (msgSessionId != contextSessionId)) { + storage_->NotifyRemotePushFinished(deviceId_); + } +} + +void SingleVerDataSync::SetAckPacket(DataAckPacket &ackPacket, SingleVerSyncTaskContext *context, + const DataRequestPacket *packet, int32_t recvCode, WaterMark maxSendDataTime) +{ + SyncType curType = SyncOperation::GetSyncType(packet->GetMode()); + WaterMark localMark = 0; + GetLocalWaterMark(curType, packet->GetQueryId(), context, localMark); + ackPacket.SetRecvCode(recvCode); + // send ack packet + if ((recvCode == E_OK) && (maxSendDataTime != 0)) { + ackPacket.SetData(maxSendDataTime + 1); // + 1 to next start + } else if (recvCode != WATER_MARK_INVALID) { + WaterMark mark = 0; + GetPeerWaterMark(curType, packet->GetQueryId(), context->GetDeviceId(), mark); + ackPacket.SetData(mark); + } + std::vector reserved {localMark}; + uint32_t version = std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT); + uint64_t packetId = 0; + if (version > SOFTWARE_VERSION_RELEASE_2_0) { + packetId = packet->GetPacketId(); // above 102 version data request reserve[0] store packetId value + } + if (version > SOFTWARE_VERSION_RELEASE_2_0 && packetId > 0) { + reserved.push_back(packetId); + } + // while recv is not E_OK, data is peerMark, reserve[2] is deletedPeerMark value + if (curType == SyncType::QUERY_SYNC_TYPE && recvCode != WATER_MARK_INVALID) { + WaterMark deletedPeerMark; + GetPeerDeleteSyncWaterMark(context->GetDeleteSyncId(), deletedPeerMark); + reserved.push_back(deletedPeerMark); // query sync mode, reserve[2] store deletedPeerMark value + } + ackPacket.SetReserved(reserved); + ackPacket.SetVersion(version); +} + +int SingleVerDataSync::GetReSendData(SyncEntry &syncData, SingleVerSyncTaskContext *context, + DataSyncReSendInfo reSendInfo) +{ + int mode = SyncOperation::TransferSyncMode(context->GetMode()); + if (mode == SyncModeType::PULL) { + return E_OK; + } + ContinueToken token = nullptr; + uint32_t version = std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT); + size_t packetSize = (version > SOFTWARE_VERSION_RELEASE_2_0) ? + DBConstant::MAX_HPMODE_PACK_ITEM_SIZE : DBConstant::MAX_NORMAL_PACK_ITEM_SIZE; + DataSizeSpecInfo reSendDataSizeInfo = GetDataSizeSpecInfo(packetSize); + SyncType curType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + int errCode; + if (curType != SyncType::QUERY_SYNC_TYPE) { + errCode = storage_->GetSyncData(reSendInfo.start, reSendInfo.end + 1, syncData.entries, token, + reSendDataSizeInfo); + } else { + QuerySyncObject queryObj = context->GetQuery(); + errCode = storage_->GetSyncData(queryObj, SyncTimeRange { reSendInfo.start, reSendInfo.deleteDataStart, + reSendInfo.end + 1, reSendInfo.deleteDataEnd + 1 }, reSendDataSizeInfo, token, syncData.entries); + } + if (token != nullptr) { + storage_->ReleaseContinueToken(token); + } + if (errCode == -E_BUSY || errCode == -E_EKEYREVOKED) { + context->SetTaskErrCode(errCode); + return errCode; + } + if (!SingleVerDataSyncUtils::IsGetDataSuccessfully(errCode)) { + return errCode; + } + + int innerCode = InterceptData(syncData); + if (innerCode != E_OK) { + context->SetTaskErrCode(innerCode); + return innerCode; + } + + bool needCompressOnSync = false; + uint8_t compressionRate = DBConstant::DEFAULT_COMPTRESS_RATE; + (void)storage_->GetCompressionOption(needCompressOnSync, compressionRate); + CompressAlgorithm remoteAlgo = context->ChooseCompressAlgo(); + if (needCompressOnSync && remoteAlgo != CompressAlgorithm::NONE) { + int compressCode = GenericSingleVerKvEntry::Compress(syncData.entries, syncData.compressedEntries, + { remoteAlgo, version }); + if (compressCode != E_OK) { + return compressCode; + } + } + return errCode; +} + +int SingleVerDataSync::RemoveDeviceDataIfNeed(SingleVerSyncTaskContext *context) +{ + if (context->GetRemoteSoftwareVersion() <= SOFTWARE_VERSION_RELEASE_3_0) { + return E_OK; + } + uint64_t clearRemoteDataMark = 0; + std::lock_guard autoLock(removeDeviceDataLock_); + metadata_->GetRemoveDataMark(context->GetDeviceId(), clearRemoteDataMark); + if (clearRemoteDataMark == 0) { + return E_OK; + } + int errCode = E_OK; + if (context->IsNeedClearRemoteStaleData() && clearRemoteDataMark == REMOVE_DEVICE_DATA_MARK) { + errCode = storage_->RemoveDeviceData(context->GetDeviceId(), true); + if (errCode != E_OK) { + LOGE("clear remote %s data failed,errCode=%d", STR_MASK(GetDeviceId()), errCode); + return errCode; + } + } + if (clearRemoteDataMark == REMOVE_DEVICE_DATA_MARK) { + errCode = metadata_->ResetMetaDataAfterRemoveData(context->GetDeviceId()); + if (errCode != E_OK) { + LOGE("set %s removeDataWaterMark to false failed,errCode=%d", STR_MASK(GetDeviceId()), errCode); + return errCode; + } + } + return E_OK; +} + +void SingleVerDataSync::UpdateMtuSize() +{ + mtuSize_ = communicateHandle_->GetCommunicatorMtuSize(deviceId_) * 9 / 10; // get the 9/10 of the size +} + +void SingleVerDataSync::FillRequestReSendPacket(const SingleVerSyncTaskContext *context, DataRequestPacket *packet, + DataSyncReSendInfo reSendInfo, SyncEntry &syncData, int sendCode) +{ + SyncType curType = (context->IsQuerySync()) ? SyncType::QUERY_SYNC_TYPE : SyncType::MANUAL_FULL_SYNC_TYPE; + WaterMark peerMark = 0; + GetPeerWaterMark(curType, context->GetQuerySyncId(), context->GetDeviceId(), + peerMark); + uint32_t version = std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT); + // transfer reSend mode, RESPONSE_PULL transfer to push or query push + // PUSH_AND_PULL mode which sequenceId lager than first transfer to push or query push + int reSendMode = SingleVerDataSyncUtils::GetReSendMode(context->GetMode(), reSendInfo.sequenceId, curType); + if (GetSessionEndTimestamp() == std::max(reSendInfo.end, reSendInfo.deleteDataEnd) || + SyncOperation::TransferSyncMode(context->GetMode()) == SyncModeType::PULL) { + LOGI("[DataSync][ReSend] set lastid,label=%s,dev=%s", label_.c_str(), STR_MASK(GetDeviceId())); + packet->SetLastSequence(); + } + if (sendCode == E_OK && GetSessionEndTimestamp() == std::max(reSendInfo.end, reSendInfo.deleteDataEnd) && + context->GetMode() == SyncModeType::RESPONSE_PULL) { + sendCode = SEND_FINISHED; + } + packet->SetData(syncData.entries); + packet->SetCompressData(syncData.compressedEntries); + packet->SetBasicInfo(sendCode, version, reSendMode); + packet->SetWaterMark(reSendInfo.start, peerMark, reSendInfo.deleteDataStart); + if (SyncOperation::TransferSyncMode(reSendMode) != SyncModeType::PUSH) { + packet->SetEndWaterMark(context->GetEndMark()); + packet->SetQuery(context->GetQuery()); + } + packet->SetQueryId(context->GetQuerySyncId()); + if (version > SOFTWARE_VERSION_RELEASE_2_0) { + std::vector reserved {reSendInfo.packetId}; + packet->SetReserved(reserved); + } + bool needCompressOnSync = false; + uint8_t compressionRate = DBConstant::DEFAULT_COMPTRESS_RATE; + (void)storage_->GetCompressionOption(needCompressOnSync, compressionRate); + CompressAlgorithm curAlgo = context->ChooseCompressAlgo(); + if (needCompressOnSync && curAlgo != CompressAlgorithm::NONE) { + packet->SetCompressDataMark(); + packet->SetCompressAlgo(curAlgo); + } +} + +DataSizeSpecInfo SingleVerDataSync::GetDataSizeSpecInfo(size_t packetSize) +{ + bool needCompressOnSync = false; + uint8_t compressionRate = DBConstant::DEFAULT_COMPTRESS_RATE; + (void)storage_->GetCompressionOption(needCompressOnSync, compressionRate); + uint32_t blockSize = std::min(static_cast(DBConstant::MAX_SYNC_BLOCK_SIZE), + mtuSize_ * 100 / compressionRate); // compressionRate max is 100 + return {blockSize, packetSize}; +} + +int SingleVerDataSync::InterceptData(SyncEntry &syncEntry) +{ + if (storage_ == nullptr) { + LOGE("Invalid DB. Can not intercept data."); + return -E_INVALID_DB; + } + + // GetLocalDeviceName get local device ID. + // GetDeviceId get remote device ID. + // If intercept data fail, entries will be released. + return storage_->InterceptData(syncEntry.entries, GetLocalDeviceName(), GetDeviceId()); +} + +int SingleVerDataSync::ControlCmdStart(SingleVerSyncTaskContext *context) +{ + if (context == nullptr) { + return -E_INVALID_ARGS; + } + std::shared_ptr subManager = context->GetSubscribeManager(); + if (subManager == nullptr) { + return -E_INVALID_ARGS; + } + int errCode = ControlCmdStartCheck(context); + if (errCode != E_OK) { + return errCode; + } + ControlRequestPacket* packet = new (std::nothrow) SubscribeRequest(); + if (packet == nullptr) { + LOGE("[DataSync][ControlCmdStart] new SubscribeRequest error"); + return -E_OUT_OF_MEMORY; + } + if (context->GetMode() == SyncModeType::SUBSCRIBE_QUERY) { + errCode = subManager->ReserveLocalSubscribeQuery(context->GetDeviceId(), context->GetQuery()); + if (errCode != E_OK) { + LOGE("[DataSync][ControlCmdStart] reserve local subscribe query failed,err=%d", errCode); + delete packet; + packet = nullptr; + return errCode; + } + } + SingleVerDataSyncUtils::FillControlRequestPacket(packet, context); + errCode = SendControlPacket(packet, context); + if (errCode != E_OK && context->GetMode() == SyncModeType::SUBSCRIBE_QUERY) { + subManager->DeleteLocalSubscribeQuery(context->GetDeviceId(), context->GetQuery()); + } + return errCode; +} + +int SingleVerDataSync::ControlCmdRequestRecv(SingleVerSyncTaskContext *context, const Message *message) +{ + const ControlRequestPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + LOGI("[SingleVerDataSync] recv control cmd message,label=%s,dev=%s,controlType=%u", label_.c_str(), + STR_MASK(GetDeviceId()), packet->GetcontrolCmdType()); + int errCode = ControlCmdRequestRecvPre(context, message); + if (errCode != E_OK) { + return errCode; + } + if (packet->GetcontrolCmdType() == ControlCmdType::SUBSCRIBE_QUERY_CMD) { + errCode = SubscribeRequestRecv(context, message); + } else if (packet->GetcontrolCmdType() == ControlCmdType::UNSUBSCRIBE_QUERY_CMD) { + errCode = UnsubscribeRequestRecv(context, message); + } + return errCode; +} + +int SingleVerDataSync::ControlCmdAckRecv(SingleVerSyncTaskContext *context, const Message *message) +{ + std::shared_ptr subManager = context->GetSubscribeManager(); + if (subManager == nullptr) { + return -E_INVALID_ARGS; + } + int errCode = SingleVerDataSyncUtils::AckMsgErrnoCheck(context, message); + if (errCode != E_OK) { + SingleVerDataSyncUtils::ControlAckErrorHandle(context, subManager); + return errCode; + } + const ControlAckPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + int32_t recvCode = packet->GetRecvCode(); + uint32_t cmdType = packet->GetcontrolCmdType(); + if (recvCode != E_OK) { + LOGE("[DataSync][AckRecv] control sync abort,recvCode=%d,label=%s,dev=%s,type=%u", recvCode, label_.c_str(), + STR_MASK(GetDeviceId()), cmdType); + // for unsubscribe no need to do something + SingleVerDataSyncUtils::ControlAckErrorHandle(context, subManager); + return recvCode; + } + if (cmdType == ControlCmdType::SUBSCRIBE_QUERY_CMD) { + errCode = subManager->ActiveLocalSubscribeQuery(context->GetDeviceId(), context->GetQuery()); + } else if (cmdType == ControlCmdType::UNSUBSCRIBE_QUERY_CMD) { + subManager->RemoveLocalSubscribeQuery(context->GetDeviceId(), context->GetQuery()); + } + if (errCode != E_OK) { + LOGE("[DataSync] ack handle failed,label =%s,dev=%s,type=%u", label_.c_str(), STR_MASK(GetDeviceId()), cmdType); + return errCode; + } + return -E_NO_DATA_SEND; // means control msg send finished +} + +int SingleVerDataSync::ControlCmdStartCheck(SingleVerSyncTaskContext *context) +{ + if ((context->GetMode() != SyncModeType::SUBSCRIBE_QUERY) && + (context->GetMode() != SyncModeType::UNSUBSCRIBE_QUERY)) { + LOGE("[ControlCmdStartCheck] not support controlCmd"); + return -E_INVALID_ARGS; + } + if (context->GetMode() == SyncModeType::SUBSCRIBE_QUERY && + context->GetQuery().HasInKeys() && + context->GetRemoteDbAbility().GetAbilityItem(SyncConfig::INKEYS_QUERY) != SUPPORT_MARK) { + return -E_NOT_SUPPORT; + } + if ((context->GetMode() != SyncModeType::SUBSCRIBE_QUERY) || context->GetReceivcPermitCheck()) { + return E_OK; + } + bool permitReceive = SingleVerDataSyncUtils::CheckPermitReceiveData(context, communicateHandle_); + if (permitReceive) { + context->SetReceivcPermitCheck(true); + } else { + return -E_SECURITY_OPTION_CHECK_ERROR; + } + return E_OK; +} + +int SingleVerDataSync::SendControlPacket(const ControlRequestPacket *packet, SingleVerSyncTaskContext *context) +{ + Message *message = new (std::nothrow) Message(CONTROL_SYNC_MESSAGE); + if (message == nullptr) { + LOGE("[DataSync][SendControlPacket] new message error"); + delete packet; + packet = nullptr; + return -E_OUT_OF_MEMORY; + } + uint32_t packetLen = packet->CalculateLen(); + int errCode = message->SetExternalObject(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + delete message; + message = nullptr; + LOGE("[DataSync][SendControlPacket] set external object failed errCode=%d", errCode); + return errCode; + } + SingleVerDataSyncUtils::SetMessageHeadInfo(*message, TYPE_REQUEST, context->GetDeviceId(), + context->GetSequenceId(), context->GetRequestSessionId()); + CommErrHandler handler = std::bind(&SyncTaskContext::CommErrHandlerFunc, std::placeholders::_1, + context, message->GetSessionId()); + errCode = Send(context, message, handler, packetLen); + if (errCode != E_OK) { + delete message; + message = nullptr; + } + return errCode; +} + +int SingleVerDataSync::SendControlAck(SingleVerSyncTaskContext *context, const Message *message, int32_t recvCode, + uint32_t controlCmdType, const CommErrHandler &handler) +{ + Message *ackMessage = new (std::nothrow) Message(message->GetMessageId()); + if (ackMessage == nullptr) { + LOGE("[DataSync][SendControlAck] new message error"); + return -E_OUT_OF_MEMORY; + } + uint32_t version = std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT); + ControlAckPacket ack; + ack.SetPacketHead(recvCode, version, static_cast(controlCmdType), 0); + int errCode = ackMessage->SetCopiedObject(ack); + if (errCode != E_OK) { + delete ackMessage; + ackMessage = nullptr; + LOGE("[DataSync][SendControlAck] set copied object failed, errcode=%d", errCode); + return errCode; + } + SingleVerDataSyncUtils::SetMessageHeadInfo(*ackMessage, TYPE_RESPONSE, context->GetDeviceId(), + message->GetSequenceId(), message->GetSessionId()); + errCode = Send(context, ackMessage, handler, 0); + if (errCode != E_OK) { + delete ackMessage; + ackMessage = nullptr; + } + return errCode; +} + +int SingleVerDataSync::ControlCmdRequestRecvPre(SingleVerSyncTaskContext *context, const Message *message) +{ + if (context == nullptr || message == nullptr) { + return -E_INVALID_ARGS; + } + const ControlRequestPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + uint32_t controlCmdType = packet->GetcontrolCmdType(); + if (context->GetRemoteSoftwareVersion() <= SOFTWARE_VERSION_BASE) { + return DoAbilitySyncIfNeed(context, message, true); + } + if (controlCmdType >= ControlCmdType::INVALID_CONTROL_CMD) { + SendControlAck(context, message, -E_NOT_SUPPORT, controlCmdType); + return -E_WAIT_NEXT_MESSAGE; + } + return E_OK; +} + +int SingleVerDataSync::SubscribeRequestRecvPre(SingleVerSyncTaskContext *context, const SubscribeRequest *packet, + const Message *message) +{ + uint32_t controlCmdType = packet->GetcontrolCmdType(); + if (controlCmdType != ControlCmdType::SUBSCRIBE_QUERY_CMD) { + return E_OK; + } + QuerySyncObject syncQuery = packet->GetQuery(); + int errCode; + if (!packet->IsAutoSubscribe()) { + errCode = storage_->CheckAndInitQueryCondition(syncQuery); + if (errCode != E_OK) { + LOGE("[SingleVerDataSync] check sync query failed,errCode=%d", errCode); + SendControlAck(context, message, errCode, controlCmdType); + return -E_WAIT_NEXT_MESSAGE; + } + } + int mode = SingleVerDataSyncUtils::GetModeByControlCmdType( + static_cast(packet->GetcontrolCmdType())); + if (mode >= SyncModeType::INVALID_MODE) { + LOGE("[SingleVerDataSync] invalid mode"); + SendControlAck(context, message, -E_INVALID_ARGS, controlCmdType); + return -E_WAIT_NEXT_MESSAGE; + } + errCode = CheckPermitSendData(mode, context); + if (errCode != E_OK) { + LOGE("[SingleVerDataSync] check sync query failed,errCode=%d", errCode); + SendControlAck(context, message, errCode, controlCmdType); + } + return errCode; +} + +int SingleVerDataSync::SubscribeRequestRecv(SingleVerSyncTaskContext *context, const Message *message) +{ + const SubscribeRequest *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + int errCode = SubscribeRequestRecvPre(context, packet, message); + if (errCode != E_OK) { + return errCode; + } + uint32_t controlCmdType = packet->GetcontrolCmdType(); + std::shared_ptr subscribeManager = context->GetSubscribeManager(); + if (subscribeManager == nullptr) { + LOGE("[SingleVerDataSync] subscribeManager check failed"); + SendControlAck(context, message, -E_NOT_REGISTER, controlCmdType); + return -E_INVALID_ARGS; + } + errCode = storage_->AddSubscribe(packet->GetQuery().GetIdentify(), packet->GetQuery(), packet->IsAutoSubscribe()); + if (errCode != E_OK) { + LOGE("[SingleVerDataSync] add trigger failed,err=%d,label=%s,dev=%s", errCode, label_.c_str(), + STR_MASK(GetDeviceId())); + SendControlAck(context, message, errCode, controlCmdType); + return errCode; + } + errCode = subscribeManager->ReserveRemoteSubscribeQuery(context->GetDeviceId(), packet->GetQuery()); + if (errCode != E_OK) { + LOGE("[SingleVerDataSync] add remote subscribe query failed,err=%d,label=%s,dev=%s", errCode, label_.c_str(), + STR_MASK(GetDeviceId())); + storage_->RemoveSubscribe(packet->GetQuery().GetIdentify()); + SendControlAck(context, message, errCode, controlCmdType); + return errCode; + } + errCode = SendControlAck(context, message, E_OK, controlCmdType); + if (errCode != E_OK) { + storage_->RemoveSubscribe(packet->GetQuery().GetIdentify()); + subscribeManager->DeleteRemoteSubscribeQuery(context->GetDeviceId(), packet->GetQuery()); + LOGE("[SingleVerDataSync] send control msg failed,err=%d,label=%s,dev=%s", errCode, label_.c_str(), + STR_MASK(GetDeviceId())); + return errCode; + } + subscribeManager->ActiveRemoteSubscribeQuery(context->GetDeviceId(), packet->GetQuery()); + return errCode; +} + +int SingleVerDataSync::UnsubscribeRequestRecv(SingleVerSyncTaskContext *context, const Message *message) +{ + const SubscribeRequest *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + uint32_t controlCmdType = packet->GetcontrolCmdType(); + std::shared_ptr subscribeManager = context->GetSubscribeManager(); + if (subscribeManager == nullptr) { + LOGE("[SingleVerDataSync] subscribeManager check failed"); + SendControlAck(context, message, -E_NOT_REGISTER, controlCmdType); + return -E_INVALID_ARGS; + } + int errCode; + if (subscribeManager->IsRemoteContainSubscribe(context->GetDeviceId(), packet->GetQuery())) { + errCode = storage_->RemoveSubscribe(packet->GetQuery().GetIdentify()); + if (errCode != E_OK) { + LOGE("[SingleVerDataSync] remove trigger failed,err=%d,label=%s,dev=%s", errCode, label_.c_str(), + STR_MASK(GetDeviceId())); + SendControlAck(context, message, errCode, controlCmdType); + return errCode; + } + } + errCode = SendControlAck(context, message, E_OK, controlCmdType); + if (errCode != E_OK) { + LOGE("[SingleVerDataSync] send control msg failed,err=%d,label=%s,dev=%s", errCode, label_.c_str(), + STR_MASK(GetDeviceId())); + return errCode; + } + subscribeManager->RemoveRemoteSubscribeQuery(context->GetDeviceId(), packet->GetQuery()); + metadata_->RemoveQueryFromRecordSet(context->GetDeviceId(), packet->GetQuery().GetIdentify()); + return errCode; +} + +void SingleVerDataSync::PutDataMsg(Message *message) +{ + return msgSchedule_.PutMsg(message); +} + +Message *SingleVerDataSync::MoveNextDataMsg(SingleVerSyncTaskContext *context, bool &isNeedHandle, + bool &isNeedContinue) +{ + return msgSchedule_.MoveNextMsg(context, isNeedHandle, isNeedContinue); +} + +bool SingleVerDataSync::IsNeedReloadQueue() +{ + return msgSchedule_.IsNeedReloadQueue(); +} + +void SingleVerDataSync::ScheduleInfoHandle(bool isNeedHandleStatus, bool isNeedClearMap, const Message *message) +{ + msgSchedule_.ScheduleInfoHandle(isNeedHandleStatus, isNeedClearMap, message); +} + +void SingleVerDataSync::ClearDataMsg() +{ + msgSchedule_.ClearMsg(); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/syncer/src/single_ver_data_sync.h b/mock/distributeddb/syncer/src/single_ver_data_sync.h new file mode 100644 index 00000000..610537fa --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_data_sync.h @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_DATA_SYNC_NEW_H +#define SINGLE_VER_DATA_SYNC_NEW_H + +#include "icommunicator.h" +#include "isync_interface.h" +#include "meta_data.h" +#include "parcel.h" +#include "single_ver_data_message_schedule.h" +#include "single_ver_data_packet.h" +#include "single_ver_kvdb_sync_interface.h" +#include "single_ver_sync_task_context.h" +#include "sync_generic_interface.h" +#include "sync_types.h" +#include "version.h" + +namespace DistributedDB { +using SendDataItem = SingleVerKvEntry *; +struct ReSendInfo { + Timestamp start = 0; + Timestamp end = 0; + Timestamp deleteBeginTime = 0; + Timestamp deleteEndTime = 0; + // packetId is used for matched ackpacket packetId which saved in ackPacket.reserve + // if equaled, means need to handle the ack, or drop. it is always increased + uint64_t packetId = 0; +}; + +struct DataSyncReSendInfo { + uint32_t sessionId = 0; + uint32_t sequenceId = 0; + Timestamp start = 0; // means normal or sync data localwatermark + Timestamp end = 0; + Timestamp deleteDataStart = 0; // means delete data localwatermark + Timestamp deleteDataEnd = 0; + uint64_t packetId = 0; +}; + +struct SyncEntry { + std::vector entries; + std::vector compressedEntries; +}; + +class SingleVerDataSync { +public: + SingleVerDataSync(); + virtual ~SingleVerDataSync(); + + DISABLE_COPY_ASSIGN_MOVE(SingleVerDataSync); + + int Initialize(ISyncInterface *inStorage, ICommunicator *inCommunicateHandle, + std::shared_ptr &inMetadata, const std::string &deviceId); + + int SyncStart(int mode, SingleVerSyncTaskContext *context); + + int TryContinueSync(SingleVerSyncTaskContext *context, const Message *message); + + void ClearSyncStatus(); + + int PushStart(SingleVerSyncTaskContext *context); + + int PushPullStart(SingleVerSyncTaskContext *context); + + int PullRequestStart(SingleVerSyncTaskContext *context); + + int PullResponseStart(SingleVerSyncTaskContext *context); + + int DataRequestRecv(SingleVerSyncTaskContext *context, const Message *message, WaterMark &pullEndWatermark); + + bool AckPacketIdCheck(const Message *message); + + int AckRecv(SingleVerSyncTaskContext *context, const Message *message); + + void SendSaveDataNotifyPacket(SingleVerSyncTaskContext *context, uint32_t pktVersion, uint32_t sessionId, + uint32_t sequenceId, uint32_t inMsgId); + + virtual int SendDataAck(SingleVerSyncTaskContext *context, const Message *message, int32_t recvCode, + WaterMark maxSendDataTime); + + int CheckPermitSendData(int inMode, SingleVerSyncTaskContext *context); + + std::string GetLabel() const; + + std::string GetDeviceId() const; + + bool WaterMarkErrHandle(SyncType syncType, SingleVerSyncTaskContext *context, const Message *message); + + int ControlCmdStart(SingleVerSyncTaskContext *context); + + int ControlCmdRequestRecv(SingleVerSyncTaskContext *context, const Message *message); + + int ControlCmdAckRecv(SingleVerSyncTaskContext *context, const Message *message); + + void PutDataMsg(Message *message); + + Message *MoveNextDataMsg(SingleVerSyncTaskContext *context, bool &isNeedHandle, bool &isNeedContinue); + + bool IsNeedReloadQueue(); + + void SendFinishedDataAck(SingleVerSyncTaskContext *context, const Message *message); + + void ScheduleInfoHandle(bool isNeedHandleStatus, bool isNeedClearMap, const Message *message); + + void ClearDataMsg(); + +protected: + static const int SEND_FINISHED = 0xff; + static const int LOCAL_WATER_MARK_NOT_INIT = 0xaa; + static const int PEER_WATER_MARK_NOT_INIT = 0x55; + static const int WATER_MARK_INVALID = 0xbb; + static const int MTU_SIZE = 28311552; // 27MB + + void ResetSyncStatus(int inMode, SingleVerSyncTaskContext *context); + + int InnerSyncStart(SingleVerSyncTaskContext *context); + + void InnerClearSyncStatus(); + + int ReSendData(SingleVerSyncTaskContext *context); + + int32_t ReSend(SingleVerSyncTaskContext *context, DataSyncReSendInfo reSendInfo); + + void SetSessionEndTimestamp(Timestamp end); + + Timestamp GetSessionEndTimestamp() const; + + void FillDataRequestPacket(DataRequestPacket *packet, SingleVerSyncTaskContext *context, + SyncEntry &syncData, int sendCode, int mode); + + int RequestStart(SingleVerSyncTaskContext *context, int mode); + + SyncTimeRange GetSyncDataTimeRange(SyncType syncType, SingleVerSyncTaskContext *context, + const std::vector &inData, UpdateWaterMark &isUpdate); + + int GetData(SingleVerSyncTaskContext *context, std::vector &outData, size_t packetSize); + + int GetDataWithPerformanceRecord(SingleVerSyncTaskContext *context, SyncEntry &syncOutData); + + int Send(SingleVerSyncTaskContext *context, const Message *message, const CommErrHandler &handler, + uint32_t packetLen); + + int GetUnsyncData(SingleVerSyncTaskContext *context, std::vector &outData, size_t packetSize); + + int GetNextUnsyncData(SingleVerSyncTaskContext *context, std::vector &outData, size_t packetSize); + + int SaveData(const SingleVerSyncTaskContext *context, const std::vector &inData, SyncType curType, + const QuerySyncObject &query); + + int SaveLocalWaterMark(SyncType syncType, const SingleVerSyncTaskContext *context, + SyncTimeRange dataTimeRange, bool isCheckBeforUpdate = false) const; + + void GetLocalWaterMark(SyncType syncType, const std::string &queryIdentify, const SingleVerSyncTaskContext *context, + WaterMark &watermark) const; + + void GetPeerWaterMark(SyncType syncType, const std::string &queryIdentify, const DeviceID &deviceId, + WaterMark &watermark) const; + + void GetPeerDeleteSyncWaterMark(const DeviceID &deviceId, WaterMark &waterMark); + + void GetLocalDeleteSyncWaterMark(const SingleVerSyncTaskContext *context, WaterMark &waterMark) const; + + int RemoveDeviceDataHandle(SingleVerSyncTaskContext *context, const Message *message, WaterMark maxSendDataTime); + + int DealRemoveDeviceDataByAck(SingleVerSyncTaskContext *context, WaterMark ackWaterMark, + const std::vector &reserved); + + int SendDataPacket(SyncType syncType, const DataRequestPacket *packet, SingleVerSyncTaskContext *context); + + void UpdateQueryPeerWaterMark(SyncType syncType, const std::string &queryId, SyncTimeRange &dataTime, + const SingleVerSyncTaskContext *context, UpdateWaterMark isUpdateWaterMark); + + void UpdatePeerWaterMark(SyncType syncType, const std::string &queryId, const SingleVerSyncTaskContext *context, + WaterMark peerWatermark, WaterMark peerDeletedWatermark); + + std::string GetLocalDeviceName(); + + int DoAbilitySyncIfNeed(SingleVerSyncTaskContext *context, const Message *message, bool isControlMsg = false); + + int DataRequestRecvPre(SingleVerSyncTaskContext *context, const Message *message); + + void GetPullEndWatermark(const SingleVerSyncTaskContext *context, const DataRequestPacket *packet, + WaterMark &pullEndWatermark) const; + + int DealWaterMarkException(SingleVerSyncTaskContext *context, WaterMark ackWaterMark, + const std::vector &reserved); + + int RunPermissionCheck(SingleVerSyncTaskContext *context, const Message *message, + const DataRequestPacket *packet); + + void SendResetWatchDogPacket(SingleVerSyncTaskContext *context, uint32_t packetLen); + + int SendReSendPacket(const DataRequestPacket *packet, SingleVerSyncTaskContext *context, + uint32_t sessionId, uint32_t sequenceId); + + int SendPullResponseDataPkt(int ackCode, SyncEntry &syncOutData, SingleVerSyncTaskContext *context); + + int CheckSchemaStrategy(SingleVerSyncTaskContext *context, const Message *message); + + void RemotePushFinished(int sendCode, int inMode, uint32_t msgSessionId, uint32_t contextSessionId); + + void SetAckPacket(DataAckPacket &ackPacket, SingleVerSyncTaskContext *context, const DataRequestPacket *packet, + int32_t recvCode, WaterMark maxSendDataTime); + + int GetReSendData(SyncEntry &syncData, SingleVerSyncTaskContext *context, + DataSyncReSendInfo reSendInfo); + + virtual int RemoveDeviceDataIfNeed(SingleVerSyncTaskContext *context); + + virtual void UpdateSendInfo(SyncTimeRange dataTimeRange, SingleVerSyncTaskContext *context); + + void FillRequestReSendPacket(const SingleVerSyncTaskContext *context, DataRequestPacket *packet, + DataSyncReSendInfo reSendInfo, SyncEntry &syncData, int sendCode); + + void UpdateMtuSize(); + + DataSizeSpecInfo GetDataSizeSpecInfo(size_t packetSize); + + int InterceptData(SyncEntry &syncEntry); + + int ControlCmdStartCheck(SingleVerSyncTaskContext *context); + + int SendControlPacket(const ControlRequestPacket *packet, SingleVerSyncTaskContext *context); + + int ControlCmdRequestRecvPre(SingleVerSyncTaskContext *context, const Message *message); + int SubscribeRequestRecvPre(SingleVerSyncTaskContext *context, const SubscribeRequest *packet, + const Message *message); + int SubscribeRequestRecv(SingleVerSyncTaskContext *context, const Message *message); + int UnsubscribeRequestRecv(SingleVerSyncTaskContext *context, const Message *message); + int SendControlAck(SingleVerSyncTaskContext *context, const Message *message, int32_t recvCode, + uint32_t controlCmdType, const CommErrHandler &handler = nullptr); + + uint32_t mtuSize_; + SyncGenericInterface* storage_; + ICommunicator* communicateHandle_; + std::shared_ptr metadata_; + std::string label_; + std::string deviceId_; + + SingleVerDataMessageSchedule msgSchedule_; + + static const int HIGH_VERSION_WINDOW_SIZE = 3; + static const int LOW_VERSION_WINDOW_SIZE = 1; + // below param is about sliding sync info, is different from every sync task + std::mutex lock_; + int mode_ = 0; // sync mode, may diff from context mode if trigger pull_response while push finish + uint32_t sessionId_ = 0; + // sequenceId as key + std::map reSendMap_; + // remaining sending window + int32_t windowSize_ = 0; + // max sequenceId has been sent + uint32_t maxSequenceIdHasSent_ = 0; + bool isAllDataHasSent_ = false; + // in a sync session, the last data timestamp + Timestamp sessionEndTimestamp_ = 0; + + std::mutex removeDeviceDataLock_; +}; +} // namespace DistributedDB + +#endif // SINGLE_VER_DATA_SYNC_NEW_H diff --git a/mock/distributeddb/syncer/src/single_ver_data_sync_utils.cpp b/mock/distributeddb/syncer/src/single_ver_data_sync_utils.cpp new file mode 100644 index 00000000..a2bc1d62 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_data_sync_utils.cpp @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "single_ver_data_sync_utils.h" + +#include +#include "db_common.h" +#include "version.h" +#include "log_print.h" +#include "message.h" +namespace DistributedDB { +bool SingleVerDataSyncUtils::QuerySyncCheck(const SingleVerSyncTaskContext *context) +{ + if (!context->IsQuerySync()) { + return true; + } + uint32_t version = std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT); + // for 101 version, no need to do abilitySync, just send request to remote + if (version <= SOFTWARE_VERSION_RELEASE_1_0) { + return true; + } + if (version < SOFTWARE_VERSION_RELEASE_4_0) { + LOGE("[SingleVerDataSync] not support query sync when remote ver lower than 104"); + return false; + } + if (version < SOFTWARE_VERSION_RELEASE_5_0 && !(context->GetQuery().IsQueryOnlyByKey())) { + LOGE("[SingleVerDataSync] remote version only support prefix key"); + return false; + } + if (context->GetQuery().HasInKeys() && + context->GetRemoteDbAbility().GetAbilityItem(SyncConfig::INKEYS_QUERY) != SUPPORT_MARK) { + return false; + } + return true; +} + +int SingleVerDataSyncUtils::AckMsgErrnoCheck(const SingleVerSyncTaskContext *context, const Message *message) +{ + if (context == nullptr || message == nullptr) { + return -E_INVALID_ARGS; + } + if (message->IsFeedbackError()) { + LOGE("[DataSync][AckMsgErrnoCheck] message errNo=%d", message->GetErrorNo()); + return -static_cast(message->GetErrorNo()); + } + return E_OK; +} + +int SingleVerDataSyncUtils::RequestQueryCheck(const DataRequestPacket *packet, SyncGenericInterface *storage) +{ + if (storage == nullptr || packet == nullptr) { + return -E_INVALID_ARGS; + } + if (SyncOperation::GetSyncType(packet->GetMode()) != SyncType::QUERY_SYNC_TYPE) { + return E_OK; + } + QuerySyncObject syncQuery = packet->GetQuery(); + int errCode = storage->CheckAndInitQueryCondition(syncQuery); + if (errCode != E_OK) { + LOGE("[SingleVerDataSync] check sync query failed,errCode=%d", errCode); + return errCode; + } + return E_OK; +} + +bool SingleVerDataSyncUtils::IsPermitLocalDeviceRecvData(const std::string &deviceId, + const SecurityOption &remoteSecOption) +{ + return RuntimeContext::GetInstance()->CheckDeviceSecurityAbility(deviceId, remoteSecOption); +} + +bool SingleVerDataSyncUtils::IsPermitRemoteDeviceRecvData(const std::string &deviceId, + const SecurityOption &remoteSecOption, SyncGenericInterface *storage) +{ + if (storage == nullptr) { + return -E_INVALID_ARGS; + } + SecurityOption localSecOption; + if (remoteSecOption.securityLabel == NOT_SURPPORT_SEC_CLASSIFICATION) { + return true; + } + int errCode = storage->GetSecurityOption(localSecOption); + if (errCode == -E_NOT_SUPPORT) { + return true; + } + return RuntimeContext::GetInstance()->CheckDeviceSecurityAbility(deviceId, localSecOption); +} + +void SingleVerDataSyncUtils::TransDbDataItemToSendDataItem(const std::string &localHashName, + std::vector &outData) +{ + for (size_t i = 0; i < outData.size(); i++) { + if (outData[i] == nullptr) { + continue; + } + outData[i]->SetOrigDevice(outData[i]->GetOrigDevice().empty() ? localHashName : outData[i]->GetOrigDevice()); + if (i == 0 || i == (outData.size() - 1)) { + LOGD("[DataSync][TransToSendItem] printData packet=%zu,timestamp=%" PRIu64 ",flag=%" PRIu64, i, + outData[i]->GetTimestamp(), outData[i]->GetFlag()); + } + } +} + +std::string SingleVerDataSyncUtils::TransferForeignOrigDevName(const std::string &deviceName, + const std::string &localHashName) +{ + if (localHashName == deviceName) { + return ""; + } + return deviceName; +} + +void SingleVerDataSyncUtils::TransSendDataItemToLocal(const SingleVerSyncTaskContext *context, + const std::string &localHashName, const std::vector &data) +{ + TimeOffset offset = context->GetTimeOffset(); + Timestamp currentLocalTime = context->GetCurrentLocalTime(); + for (auto &item : data) { + if (item == nullptr) { + continue; + } + item->SetOrigDevice(TransferForeignOrigDevName(item->GetOrigDevice(), localHashName)); + Timestamp tempTimestamp = item->GetTimestamp(); + Timestamp tempWriteTimestamp = item->GetWriteTimestamp(); + item->SetTimestamp(tempTimestamp - static_cast(offset)); + if (tempWriteTimestamp != 0) { + item->SetWriteTimestamp(tempWriteTimestamp - static_cast(offset)); + } + + if (item->GetTimestamp() > currentLocalTime) { + item->SetTimestamp(currentLocalTime); + } + if (item->GetWriteTimestamp() > currentLocalTime) { + item->SetWriteTimestamp(currentLocalTime); + } + } +} + +void SingleVerDataSyncUtils::TranslateErrCodeIfNeed(int mode, uint32_t version, int &errCode) +{ + // once get data occur E_EKEYREVOKED error, should also send request to remote dev to pull data. + if (SyncOperation::TransferSyncMode(mode) == SyncModeType::PUSH_AND_PULL && + version > SOFTWARE_VERSION_RELEASE_2_0 && errCode == -E_EKEYREVOKED) { + errCode = E_OK; + } +} + +int SingleVerDataSyncUtils::RunPermissionCheck(SingleVerSyncTaskContext *context, const SyncGenericInterface* storage, + const std::string &label, int mode) +{ + std::string appId = storage->GetDbProperties().GetStringProp(KvDBProperties::APP_ID, ""); + std::string userId = storage->GetDbProperties().GetStringProp(KvDBProperties::USER_ID, ""); + std::string storeId = storage->GetDbProperties().GetStringProp(KvDBProperties::STORE_ID, ""); + uint8_t flag; + switch (mode) { + case SyncModeType::PUSH: + flag = CHECK_FLAG_RECEIVE; + break; + case SyncModeType::PULL: + flag = CHECK_FLAG_SEND; + break; + case SyncModeType::PUSH_AND_PULL: + flag = CHECK_FLAG_SEND | CHECK_FLAG_RECEIVE; + break; + default: + flag = CHECK_FLAG_RECEIVE; + break; + } + int errCode = E_OK; + if (storage->GetInterfaceType() != ISyncInterface::SYNC_RELATION) { + errCode = RuntimeContext::GetInstance()->RunPermissionCheck(userId, appId, storeId, context->GetDeviceId(), + flag); + } + if (errCode != E_OK) { + LOGE("[DataSync][RunPermissionCheck] check failed flag=%" PRIu8 ",Label=%s,dev=%s", flag, label.c_str(), + STR_MASK(context->GetDeviceId())); + } + return errCode; +} + +bool SingleVerDataSyncUtils::CheckPermitReceiveData(const SingleVerSyncTaskContext *context, + const ICommunicator *communicator) +{ + SecurityOption remoteSecOption = context->GetRemoteSeccurityOption(); + std::string localDeviceId; + if (communicator == nullptr || remoteSecOption.securityLabel == NOT_SURPPORT_SEC_CLASSIFICATION) { + return true; + } + communicator->GetLocalIdentity(localDeviceId); + bool isPermitSync = SingleVerDataSyncUtils::IsPermitLocalDeviceRecvData(localDeviceId, remoteSecOption); + if (isPermitSync) { + return isPermitSync; + } + LOGE("[DataSync][PermitReceiveData] check failed: permitReceive=%d, localDev=%s, seclabel=%d, secflag=%d", + isPermitSync, STR_MASK(localDeviceId), remoteSecOption.securityLabel, remoteSecOption.securityFlag); + return isPermitSync; +} + +void SingleVerDataSyncUtils::SetPacketId(DataRequestPacket *packet, SingleVerSyncTaskContext *context, uint32_t version) +{ + if (version > SOFTWARE_VERSION_RELEASE_2_0) { + context->IncPacketId(); // begin from 1 + std::vector reserved {context->GetPacketId()}; + packet->SetReserved(reserved); + } +} + +int SingleVerDataSyncUtils::GetMessageId(SyncType syncType) +{ + if (syncType == SyncType::QUERY_SYNC_TYPE) { + return QUERY_SYNC_MESSAGE; + } + return DATA_SYNC_MESSAGE; +} + +void SingleVerDataSyncUtils::PushAndPullKeyRevokHandle(SingleVerSyncTaskContext *context) +{ + // for push_and_pull mode it may be EKEYREVOKED error before receive watermarkexception + // should clear errCode and restart pushpull request. + int mode = SyncOperation::TransferSyncMode(context->GetMode()); + if (context->GetRemoteSoftwareVersion() > SOFTWARE_VERSION_RELEASE_2_0 && mode == SyncModeType::PUSH_AND_PULL && + context->GetTaskErrCode() == -E_EKEYREVOKED) { + context->SetTaskErrCode(E_OK); + } +} + +int SingleVerDataSyncUtils::GetReSendMode(int mode, uint32_t sequenceId, SyncType syncType) +{ + int curMode = SyncOperation::TransferSyncMode(mode); + if (curMode == SyncModeType::PUSH || curMode == SyncModeType::PULL) { + return mode; + } + if (curMode == SyncModeType::RESPONSE_PULL) { + return (syncType == SyncType::QUERY_SYNC_TYPE) ? SyncModeType::QUERY_PUSH : SyncModeType::PUSH; + } + // set push_and_pull mode when resend first sequenceId to inform remote to run RESPONSE_PULL task + // for sequenceId which is larger than first, only need to send data, means to set push or query_push mode + if (sequenceId == 1) { + return (syncType == SyncType::QUERY_SYNC_TYPE) ? SyncModeType::QUERY_PUSH_PULL : SyncModeType::PUSH_AND_PULL; + } + return (syncType == SyncType::QUERY_SYNC_TYPE) ? SyncModeType::QUERY_PUSH : SyncModeType::PUSH; +} + +void SingleVerDataSyncUtils::FillControlRequestPacket(ControlRequestPacket *packet, SingleVerSyncTaskContext *context) +{ + uint32_t version = std::min(context->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT); + uint32_t flag = 0; + if (context->GetMode() == SyncModeType::SUBSCRIBE_QUERY && context->IsAutoSubscribe()) { + flag = flag | SubscribeRequest::IS_AUTO_SUBSCRIBE; + } + packet->SetPacketHead(E_OK, version, GetControlCmdType(context->GetMode()), flag); + packet->SetQuery(context->GetQuery()); +} + +ControlCmdType SingleVerDataSyncUtils::GetControlCmdType(int mode) +{ + if (mode == SyncModeType::SUBSCRIBE_QUERY) { + return ControlCmdType::SUBSCRIBE_QUERY_CMD; + } else if (mode == SyncModeType::UNSUBSCRIBE_QUERY) { + return ControlCmdType::UNSUBSCRIBE_QUERY_CMD; + } + return ControlCmdType::INVALID_CONTROL_CMD; +} + +int SingleVerDataSyncUtils::GetModeByControlCmdType(ControlCmdType controlCmd) +{ + if (controlCmd == ControlCmdType::SUBSCRIBE_QUERY_CMD) { + return SyncModeType::SUBSCRIBE_QUERY; + } else if (controlCmd == ControlCmdType::UNSUBSCRIBE_QUERY_CMD) { + return SyncModeType::UNSUBSCRIBE_QUERY; + } + return SyncModeType::INVALID_MODE; +} + +bool SingleVerDataSyncUtils::IsNeedTriggerQueryAutoSync(Message *inMsg, QuerySyncObject &query) +{ + if (inMsg == nullptr) { + return false; + } + if (inMsg->GetMessageId() != CONTROL_SYNC_MESSAGE) { + return false; + } + const ControlRequestPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return false; + } + uint32_t controlCmdType = packet->GetcontrolCmdType(); + if (controlCmdType == ControlCmdType::SUBSCRIBE_QUERY_CMD && inMsg->GetMessageType() == TYPE_REQUEST) { + const SubscribeRequest *subPacket = inMsg->GetObject(); + if (packet == nullptr) { + return false; + } + query = subPacket->GetQuery(); + LOGI("[SingleVerDataSync] receive sub scribe query cmd,begin to trigger query auto sync"); + return true; + } + return false; +} + +void SingleVerDataSyncUtils::ControlAckErrorHandle(const SingleVerSyncTaskContext *context, + const std::shared_ptr &subManager) +{ + if (context->GetMode() == SyncModeType::SUBSCRIBE_QUERY) { + // reserve before need clear + subManager->DeleteLocalSubscribeQuery(context->GetDeviceId(), context->GetQuery()); + } +} + +void SingleVerDataSyncUtils::SetMessageHeadInfo(Message &message, uint16_t inMsgType, const std::string &inTarget, + uint32_t inSequenceId, uint32_t inSessionId) +{ + message.SetMessageType(inMsgType); + message.SetTarget(inTarget); + message.SetSequenceId(inSequenceId); + message.SetSessionId(inSessionId); +} + +bool SingleVerDataSyncUtils::IsGetDataSuccessfully(int errCode) +{ + return (errCode == E_OK || errCode == -E_UNFINISHED); +} + +Timestamp SingleVerDataSyncUtils::GetMaxSendDataTime(const std::vector &inData) +{ + Timestamp stamp = 0; + for (size_t i = 0; i < inData.size(); i++) { + if (inData[i] == nullptr) { + continue; + } + Timestamp tempStamp = inData[i]->GetTimestamp(); + if (stamp < tempStamp) { + stamp = tempStamp; + } + } + return stamp; +} + +SyncTimeRange SingleVerDataSyncUtils::GetFullSyncDataTimeRange(const std::vector &inData, + WaterMark localMark, UpdateWaterMark &isUpdate) +{ + Timestamp maxTimestamp = localMark; + Timestamp minTimestamp = localMark; + for (size_t i = 0; i < inData.size(); i++) { + if (inData[i] == nullptr) { + continue; + } + Timestamp tempStamp = inData[i]->GetTimestamp(); + if (maxTimestamp < tempStamp) { + maxTimestamp = tempStamp; + } + if (minTimestamp > tempStamp) { + minTimestamp = tempStamp; + } + isUpdate.normalUpdateMark = true; + } + return {minTimestamp, 0, maxTimestamp, 0}; +} + +SyncTimeRange SingleVerDataSyncUtils::GetQuerySyncDataTimeRange(const std::vector &inData, + WaterMark localMark, WaterMark deleteLocalMark, UpdateWaterMark &isUpdate) +{ + SyncTimeRange dataTimeRange = {localMark, deleteLocalMark, localMark, deleteLocalMark}; + for (size_t i = 0; i < inData.size(); i++) { + if (inData[i] == nullptr) { + continue; + } + Timestamp tempStamp = inData[i]->GetTimestamp(); + if ((inData[i]->GetFlag() & DataItem::DELETE_FLAG) == 0) { // query data + if (dataTimeRange.endTime < tempStamp) { + dataTimeRange.endTime = tempStamp; + } + if (dataTimeRange.beginTime > tempStamp) { + dataTimeRange.beginTime = tempStamp; + } + isUpdate.normalUpdateMark = true; + } + if ((inData[i]->GetFlag() & DataItem::DELETE_FLAG) != 0) { // delete data + if (dataTimeRange.deleteEndTime < tempStamp) { + dataTimeRange.deleteEndTime = tempStamp; + } + if (dataTimeRange.deleteBeginTime > tempStamp) { + dataTimeRange.deleteBeginTime = tempStamp; + } + isUpdate.deleteUpdateMark = true; + } + } + return dataTimeRange; +} + +SyncTimeRange SingleVerDataSyncUtils::ReviseLocalMark(SyncType syncType, SyncTimeRange &dataTimeRange, + UpdateWaterMark updateMark) +{ + SyncTimeRange tmpDataTime = dataTimeRange; + if (updateMark.deleteUpdateMark && syncType == SyncType::QUERY_SYNC_TYPE) { + tmpDataTime.deleteEndTime += 1; + } + if (updateMark.normalUpdateMark) { + tmpDataTime.endTime += 1; + } + return tmpDataTime; +} + +SyncTimeRange SingleVerDataSyncUtils::GetRecvDataTimeRange(SyncType syncType, + const std::vector &data, UpdateWaterMark &isUpdate) +{ + if (syncType != SyncType::QUERY_SYNC_TYPE) { + return SingleVerDataSyncUtils::GetFullSyncDataTimeRange(data, 0, isUpdate); + } + return SingleVerDataSyncUtils::GetQuerySyncDataTimeRange(data, 0, 0, isUpdate); +} + +SyncTimeRange SingleVerDataSyncUtils::GetSyncDataTimeRange(SyncType syncType, WaterMark localMark, WaterMark deleteMark, + const std::vector &inData, UpdateWaterMark &isUpdate) +{ + if (syncType != SyncType::QUERY_SYNC_TYPE) { + return SingleVerDataSyncUtils::GetFullSyncDataTimeRange(inData, localMark, isUpdate); + } + return SingleVerDataSyncUtils::GetQuerySyncDataTimeRange(inData, localMark, deleteMark, isUpdate); +} +} \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_data_sync_utils.h b/mock/distributeddb/syncer/src/single_ver_data_sync_utils.h new file mode 100644 index 00000000..f075ffaf --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_data_sync_utils.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 SINGLE_VER_DATA_SYNC_UTIL_H +#define SINGLE_VER_DATA_SYNC_UTIL_H +#include "single_ver_data_packet.h" +#include "single_ver_sync_task_context.h" +#include "message.h" +namespace DistributedDB { +class SingleVerDataSyncUtils { +public: + static bool QuerySyncCheck(const SingleVerSyncTaskContext *context); + + static int AckMsgErrnoCheck(const SingleVerSyncTaskContext *context, const Message *message); + + static int RequestQueryCheck(const DataRequestPacket *packet, SyncGenericInterface *storage); + + static bool IsPermitLocalDeviceRecvData(const std::string &deviceId, const SecurityOption &remoteSecOption); + + static bool IsPermitRemoteDeviceRecvData(const std::string &deviceId, const SecurityOption &remoteSecOption, + SyncGenericInterface *storage); + + static void TransDbDataItemToSendDataItem(const std::string &localHashName, + std::vector &outData); + + static std::string TransferForeignOrigDevName(const std::string &deviceName, const std::string &localHashName); + + static void TransSendDataItemToLocal(const SingleVerSyncTaskContext *context, + const std::string &localHashName, const std::vector &data); + + static void TranslateErrCodeIfNeed(int mode, uint32_t version, int &errCode); + + static int RunPermissionCheck(SingleVerSyncTaskContext *context, const SyncGenericInterface* storage, + const std::string &label, int mode); + + static bool CheckPermitReceiveData(const SingleVerSyncTaskContext *context, const ICommunicator *communicator); + + static void SetPacketId(DataRequestPacket *packet, SingleVerSyncTaskContext *context, uint32_t version); + + static int GetMessageId(SyncType syncType); + + static void PushAndPullKeyRevokHandle(SingleVerSyncTaskContext *context); + + static int GetReSendMode(int mode, uint32_t sequenceId, SyncType syncType); + + static void FillControlRequestPacket(ControlRequestPacket *packet, SingleVerSyncTaskContext *context); + + static ControlCmdType GetControlCmdType(int mode); + + static int GetModeByControlCmdType(ControlCmdType controlCmd); + + static bool IsNeedTriggerQueryAutoSync(Message *inMsg, QuerySyncObject &query); + + static void ControlAckErrorHandle(const SingleVerSyncTaskContext *context, + const std::shared_ptr &subManager); + + static void SetMessageHeadInfo(Message &message, uint16_t inMsgType, + const std::string &inTarget, uint32_t inSequenceId, uint32_t inSessionId); + + static bool IsGetDataSuccessfully(int errCode); + + static Timestamp GetMaxSendDataTime(const std::vector &inData); + + static SyncTimeRange GetFullSyncDataTimeRange(const std::vector &inData, WaterMark localMark, + UpdateWaterMark &isUpdate); + + static SyncTimeRange GetQuerySyncDataTimeRange(const std::vector &inData, WaterMark localMark, + WaterMark deleteLocalMark, UpdateWaterMark &isUpdate); + + static SyncTimeRange ReviseLocalMark(SyncType syncType, SyncTimeRange &dataTimeRange, UpdateWaterMark updateMark); + + static SyncTimeRange GetRecvDataTimeRange(SyncType syncType, + const std::vector &data, UpdateWaterMark &isUpdate); + + static SyncTimeRange GetSyncDataTimeRange(SyncType syncType, WaterMark localMark, WaterMark deleteMark, + const std::vector &inData, UpdateWaterMark &isUpdate); +}; +} +#endif // SINGLE_VER_DATA_SYNC_UTIL_H \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_kv_sync_task_context.cpp b/mock/distributeddb/syncer/src/single_ver_kv_sync_task_context.cpp new file mode 100644 index 00000000..fb1f8ba5 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_kv_sync_task_context.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "single_ver_kv_sync_task_context.h" + +namespace DistributedDB { +SingleVerKvSyncTaskContext::SingleVerKvSyncTaskContext() + : SingleVerSyncTaskContext(), syncStrategy_{} +{} + +SingleVerKvSyncTaskContext::~SingleVerKvSyncTaskContext() +{ +} + +std::string SingleVerKvSyncTaskContext::GetQuerySyncId() const +{ + return query_.GetIdentify(); +} + +std::string SingleVerKvSyncTaskContext::GetDeleteSyncId() const +{ + return GetDeviceId(); +} + +void SingleVerKvSyncTaskContext::SetSyncStrategy(const SyncStrategy &strategy) +{ + syncStrategy_.permitSync = strategy.permitSync; + syncStrategy_.convertOnSend = strategy.convertOnSend; + syncStrategy_.convertOnReceive = strategy.convertOnReceive; + syncStrategy_.checkOnReceive = strategy.checkOnReceive; +} + +SyncStrategy SingleVerKvSyncTaskContext::GetSyncStrategy(QuerySyncObject &querySyncObject) const +{ + (void) querySyncObject; + return syncStrategy_; +} +} \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_kv_sync_task_context.h b/mock/distributeddb/syncer/src/single_ver_kv_sync_task_context.h new file mode 100644 index 00000000..5d8b615d --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_kv_sync_task_context.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 SINGLE_VER_KV_SYNC_TASK_CONTEXT_H +#define SINGLE_VER_KV_SYNC_TASK_CONTEXT_H + +#include "single_ver_sync_task_context.h" + +namespace DistributedDB { +class SingleVerKvSyncTaskContext : public SingleVerSyncTaskContext { +public: + + explicit SingleVerKvSyncTaskContext(); + + DISABLE_COPY_ASSIGN_MOVE(SingleVerKvSyncTaskContext); + + std::string GetQuerySyncId() const override; + std::string GetDeleteSyncId() const override; + + void SetSyncStrategy(const SyncStrategy &strategy); + SyncStrategy GetSyncStrategy(QuerySyncObject &querySyncObject) const override; +protected: + ~SingleVerKvSyncTaskContext() override; + + SyncStrategy syncStrategy_; +}; +} +#endif // SINGLE_VER_KV_SYNC_TASK_CONTEXT_H \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_kv_syncer.cpp b/mock/distributeddb/syncer/src/single_ver_kv_syncer.cpp new file mode 100644 index 00000000..c9abb08a --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_kv_syncer.cpp @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_kv_syncer.h" + +#include +#include +#include + +#include "db_common.h" +#include "ikvdb_sync_interface.h" +#include "log_print.h" +#include "meta_data.h" +#include "single_ver_sync_engine.h" +#include "sqlite_single_ver_natural_store.h" + +namespace DistributedDB { +SingleVerKVSyncer::SingleVerKVSyncer() + : autoSyncEnable_(false), triggerSyncTask_(true) +{ +} + +SingleVerKVSyncer::~SingleVerKVSyncer() +{ +} + +void SingleVerKVSyncer::EnableAutoSync(bool enable) +{ + LOGI("[Syncer] EnableAutoSync enable = %d, Label=%s", enable, label_.c_str()); + if (autoSyncEnable_ == enable) { + return; + } + + autoSyncEnable_ = enable; + if (!enable) { + return; + } + + if (!initialized_) { + LOGE("[Syncer] Syncer has not Init"); + return; + } + + std::vector devices; + GetOnlineDevices(devices); + if (devices.empty()) { + LOGI("[Syncer] EnableAutoSync no online devices"); + return; + } + int errCode = Sync(devices, SyncModeType::AUTO_PUSH, nullptr, nullptr, false); + if (errCode != E_OK) { + LOGE("[Syncer] sync start by EnableAutoSync failed err %d", errCode); + } +} + +// Local data changed callback +void SingleVerKVSyncer::LocalDataChanged(int notifyEvent) +{ + if (!initialized_) { + LOGE("[Syncer] Syncer has not Init"); + return; + } + + if (notifyEvent != SQLITE_GENERAL_FINISH_MIGRATE_EVENT && + notifyEvent != SQLITE_GENERAL_NS_PUT_EVENT) { + LOGD("[Syncer] ignore event:%d", notifyEvent); + return; + } + if (!triggerSyncTask_) { + LOGI("[Syncer] some sync task is scheduling"); + return; + } + triggerSyncTask_ = false; + RefObject::IncObjRef(syncEngine_); + // To avoid many task were produced and waiting in the queue. For example, put value in a loop. + // It will consume thread pool resources, so other task will delay until these task finish. + // In extreme situation, 10 thread run the localDataChanged task and 1 task waiting in queue. + int errCode = RuntimeContext::GetInstance()->ScheduleTask([this] { + triggerSyncTask_ = true; + std::vector devices; + GetOnlineDevices(devices); + if (devices.empty()) { + LOGI("[Syncer] LocalDataChanged no online devices, Label=%s", label_.c_str()); + RefObject::DecObjRef(syncEngine_); + return; + } + if (!TryFullSync(devices)) { + TriggerSubQuerySync(devices); + } + RefObject::DecObjRef(syncEngine_); + }); + // if task schedule failed, but triggerSyncTask_ is not set to true, other thread may skip the schedule time + // when task schedule failed, it means unormal status, it is unable to schedule next time probably + // so it is ok if other thread skip the schedule if last task schedule failed + if (errCode != E_OK) { + triggerSyncTask_ = true; + LOGE("[TriggerSync] LocalDataChanged retCode:%d", errCode); + RefObject::DecObjRef(syncEngine_); + } + return; +} + +// remote device online callback +void SingleVerKVSyncer::RemoteDataChanged(const std::string &device) +{ + LOGI("[SingleVerKVSyncer] device online dev %s", STR_MASK(device)); + if (!initialized_) { + LOGE("[Syncer] Syncer has not Init"); + return; + } + std::string userId = syncInterface_->GetDbProperties().GetStringProp(KvDBProperties::USER_ID, ""); + std::string appId = syncInterface_->GetDbProperties().GetStringProp(KvDBProperties::APP_ID, ""); + std::string storeId = syncInterface_->GetDbProperties().GetStringProp(KvDBProperties::STORE_ID, ""); + RuntimeContext::GetInstance()->NotifyDatabaseStatusChange(userId, appId, storeId, device, true); + SingleVerSyncer::RemoteDataChanged(device); + if (autoSyncEnable_) { + RefObject::IncObjRef(syncEngine_); + int retCode = RuntimeContext::GetInstance()->ScheduleTask([this, device] { + std::vector devices; + devices.push_back(device); + int errCode = Sync(devices, SyncModeType::AUTO_PUSH, nullptr, nullptr, false); + if (errCode != E_OK) { + LOGE("[SingleVerKVSyncer] sync start by RemoteDataChanged failed err %d", errCode); + } + RefObject::DecObjRef(syncEngine_); + }); + if (retCode != E_OK) { + LOGE("[AutoLaunch] RemoteDataChanged triggler sync retCode:%d", retCode); + RefObject::DecObjRef(syncEngine_); + } + } + // db online again ,trigger subscribe + // if remote device online, subscribequery num is 0 + std::vector syncQueries; + static_cast(syncEngine_)->GetLocalSubscribeQueries(device, syncQueries); + if (syncQueries.size() == 0) { + LOGI("no need to trigger auto subscribe"); + return; + } + LOGI("[SingleVerKVSyncer] trigger local subscribe sync, queryNums=%zu", syncQueries.size()); + for (const auto &query : syncQueries) { + TriggerSubscribe(device, query); + } + static_cast(syncEngine_)->PutUnfiniedSubQueries(device, syncQueries); +} + +void SingleVerKVSyncer::QueryAutoSync(const InternalSyncParma ¶m) +{ + if (!initialized_) { + LOGE("[Syncer] Syncer has not Init"); + return; + } + LOGI("[SingleVerKVSyncer] trigger query syncmode=%u,dev=%s", param.mode, GetSyncDevicesStr(param.devices).c_str()); + RefObject::IncObjRef(syncEngine_); + int retCode = RuntimeContext::GetInstance()->ScheduleTask([this, param] { + int errCode = Sync(param); + if (errCode != E_OK) { + LOGE("[SingleVerKVSyncer] sync start by QueryAutoSync failed err %d", errCode); + } + RefObject::DecObjRef(syncEngine_); + }); + if (retCode != E_OK) { + LOGE("[SingleVerKVSyncer] QueryAutoSync triggler sync retCode:%d", retCode); + RefObject::DecObjRef(syncEngine_); + } +} + +int SingleVerKVSyncer::SyncConditionCheck(QuerySyncObject &query, int mode, bool isQuerySync, + const std::vector &devices) const +{ + if (!isQuerySync) { + return E_OK; + } + int errCode = static_cast(syncInterface_)->CheckAndInitQueryCondition(query); + if (errCode != E_OK) { + LOGE("[SingleVerKVSyncer] QuerySyncObject check failed"); + return errCode; + } + if (mode != SUBSCRIBE_QUERY) { + return E_OK; + } + if (query.HasLimit() || query.HasOrderBy()) { + LOGE("[SingleVerKVSyncer] subscribe query not support limit,offset or orderby"); + return -E_NOT_SUPPORT; + } + if (devices.size() > MAX_DEVICES_NUM) { + LOGE("[SingleVerKVSyncer] devices is overlimit"); + return -E_MAX_LIMITS; + } + return syncEngine_->SubscribeLimitCheck(devices, query); +} + +void SingleVerKVSyncer::TriggerSubscribe(const std::string &device, const QuerySyncObject &query) +{ + if (!initialized_) { + LOGE("[Syncer] Syncer has not Init"); + return; + } + RefObject::IncObjRef(syncEngine_); + int retCode = RuntimeContext::GetInstance()->ScheduleTask([this, device, query] { + std::vector devices; + devices.push_back(device); + SyncParma param; + param.devices = devices; + param.mode = SyncModeType::AUTO_SUBSCRIBE_QUERY; + param.onComplete = nullptr; + param.onFinalize = nullptr; + param.wait = false; + param.isQuerySync = true; + param.syncQuery = query; + int errCode = Sync(param); + if (errCode != E_OK) { + LOGE("[SingleVerKVSyncer] subscribe start by RemoteDataChanged failed err %d", errCode); + } + RefObject::DecObjRef(syncEngine_); + }); + if (retCode != E_OK) { + LOGE("[Syncer] triggler query subscribe start failed err %d", retCode); + RefObject::DecObjRef(syncEngine_); + } +} + +bool SingleVerKVSyncer::TryFullSync(const std::vector &devices) +{ + if (!initialized_) { + LOGE("[Syncer] Syncer has not Init"); + return true; + } + if (!autoSyncEnable_) { + LOGD("[Syncer] autoSync no enable"); + return false; + } + int errCode = Sync(devices, SyncModeType::AUTO_PUSH, nullptr, nullptr, false); + if (errCode != E_OK) { + LOGE("[Syncer] sync start by RemoteDataChanged failed err %d", errCode); + return false; + } + return true; +} + +void SingleVerKVSyncer::TriggerSubQuerySync(const std::vector &devices) +{ + if (!initialized_) { + LOGE("[Syncer] Syncer has not Init"); + return; + } + int errCode; + for (auto &device : devices) { + std::vector queries; + static_cast(syncEngine_)->GetRemoteSubscribeQueries(device, queries); + for (auto &query : queries) { + std::string queryId = query.GetIdentify(); + uint64_t lastTimestamp = metadata_->GetQueryLastTimestamp(device, queryId); + WaterMark queryWaterMark = 0; + errCode = metadata_->GetSendQueryWaterMark(queryId, device, queryWaterMark, false); + if (errCode != E_OK) { + LOGE("[Syncer] get queryId=%s,dev=%s watermark failed", STR_MASK(queryId), STR_MASK(device)); + continue; + } + if (lastTimestamp < queryWaterMark || lastTimestamp == 0) { + continue; + } + LOGD("[Syncer] lastTime=%" PRIu64 " vs WaterMark=%" PRIu64 ",trigger queryId=%s,dev=%s", lastTimestamp, + queryWaterMark, STR_MASK(queryId), STR_MASK(device)); + InternalSyncParma param; + std::vector targetDevices; + targetDevices.push_back(device); + param.devices = targetDevices; + param.mode = SyncModeType::AUTO_PUSH; + param.isQuerySync = true; + param.syncQuery = query; + QueryAutoSync(param); + } + } +} + +SyncerBasicInfo SingleVerKVSyncer::DumpSyncerBasicInfo() +{ + SyncerBasicInfo basicInfo = GenericSyncer::DumpSyncerBasicInfo(); + basicInfo.isAutoSync = autoSyncEnable_; + return basicInfo; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/syncer/src/single_ver_kv_syncer.h b/mock/distributeddb/syncer/src/single_ver_kv_syncer.h new file mode 100644 index 00000000..c291acf2 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_kv_syncer.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_SYNCER_H +#define KV_SYNCER_H + +#include "single_ver_syncer.h" + +namespace DistributedDB { +class SingleVerKVSyncer final : public SingleVerSyncer { +public: + SingleVerKVSyncer(); + ~SingleVerKVSyncer() override; + + // Enable auto sync function + void EnableAutoSync(bool enable) override; + + // Local data changed callback + void LocalDataChanged(int notifyEvent) override; + + // Remote data changed callback + void RemoteDataChanged(const std::string &device) override; + + void QueryAutoSync(const InternalSyncParma ¶m) override; + + void TriggerSubscribe(const std::string &device, const QuerySyncObject &query); + + SyncerBasicInfo DumpSyncerBasicInfo() override; + +protected: + int SyncConditionCheck(QuerySyncObject &query, int mode, bool isQuerySync, + const std::vector &devices) const override; + +private: + // if trigger full sync, no need to trigger query sync again + bool TryFullSync(const std::vector &devices); + void TriggerSubQuerySync(const std::vector &devices); + bool autoSyncEnable_; + std::atomic triggerSyncTask_; +}; +} // namespace DistributedDB + +#endif // KV_SYNCER_H diff --git a/mock/distributeddb/syncer/src/single_ver_relational_sync_task_context.cpp b/mock/distributeddb/syncer/src/single_ver_relational_sync_task_context.cpp new file mode 100644 index 00000000..ef556385 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_relational_sync_task_context.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "single_ver_relational_sync_task_context.h" +#include "db_common.h" + +#ifdef RELATIONAL_STORE +namespace DistributedDB { +SingleVerRelationalSyncTaskContext::SingleVerRelationalSyncTaskContext() + : SingleVerSyncTaskContext() +{} + +SingleVerRelationalSyncTaskContext::~SingleVerRelationalSyncTaskContext() +{ +} + +std::string SingleVerRelationalSyncTaskContext::GetQuerySyncId() const +{ + return querySyncId_; +} + +std::string SingleVerRelationalSyncTaskContext::GetDeleteSyncId() const +{ + return deleteSyncId_; +} + +void SingleVerRelationalSyncTaskContext::Clear() +{ + querySyncId_.clear(); + deleteSyncId_.clear(); + SingleVerSyncTaskContext::Clear(); +} + +void SingleVerRelationalSyncTaskContext::CopyTargetData(const ISyncTarget *target, const TaskParam &TaskParam) +{ + SingleVerSyncTaskContext::CopyTargetData(target, TaskParam); + std::string hashTableName = DBCommon::TransferHashString(query_.GetRelationTableName()); + std::string hexTableName = DBCommon::TransferStringToHex(hashTableName); + querySyncId_ = hexTableName + query_.GetIdentify(); // save as deviceId + hexTableName + queryId + deleteSyncId_ = GetDeviceId() + hexTableName; // save as deviceId + hexTableName +} + +void SingleVerRelationalSyncTaskContext::SetRelationalSyncStrategy(RelationalSyncStrategy strategy) +{ + std::lock_guard autoLock(syncStrategyMutex_); + relationalSyncStrategy_ = strategy; +} + +SyncStrategy SingleVerRelationalSyncTaskContext::GetSyncStrategy(QuerySyncObject &querySyncObject) const +{ + std::lock_guard autoLock(syncStrategyMutex_); + auto it = relationalSyncStrategy_.find(querySyncObject.GetRelationTableName()); + if (it == relationalSyncStrategy_.end()) { + return {}; + } + return it->second; +} + +void SingleVerRelationalSyncTaskContext::SetIsNeedResetAbilitySync(bool isNeedReset) +{ + isNeedResetAbilitySync_ = isNeedReset; + if (isNeedResetAbilitySync_) { + SetIsSchemaSync(false); + } +} + +void SingleVerRelationalSyncTaskContext::SchemaChange() +{ + SetIsNeedResetAbilitySync(true); + std::lock_guard autoLock(syncStrategyMutex_); + relationalSyncStrategy_ = {}; +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_relational_sync_task_context.h b/mock/distributeddb/syncer/src/single_ver_relational_sync_task_context.h new file mode 100644 index 00000000..0146c42b --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_relational_sync_task_context.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 SINGLE_VER_RELATIONAL_SYNC_TASK_CONTEXT_H +#define SINGLE_VER_RELATIONAL_SYNC_TASK_CONTEXT_H + +#ifdef RELATIONAL_STORE +#include "single_ver_sync_task_context.h" + +namespace DistributedDB { +class SingleVerRelationalSyncTaskContext : public SingleVerSyncTaskContext { +public: + + explicit SingleVerRelationalSyncTaskContext(); + + DISABLE_COPY_ASSIGN_MOVE(SingleVerRelationalSyncTaskContext); + + void Clear() override; + std::string GetQuerySyncId() const override; + std::string GetDeleteSyncId() const override; + + void SetRelationalSyncStrategy(RelationalSyncStrategy strategy); + SyncStrategy GetSyncStrategy(QuerySyncObject &querySyncObject) const override; + + void SetIsNeedResetAbilitySync(bool isNeedReset) override; + + void SchemaChange() override; +protected: + ~SingleVerRelationalSyncTaskContext() override; + void CopyTargetData(const ISyncTarget *target, const TaskParam &taskParam) override; + + std::string querySyncId_; + std::string deleteSyncId_; + + // for relational syncStrategy + mutable std::mutex syncStrategyMutex_; + RelationalSyncStrategy relationalSyncStrategy_; +}; +} +#endif // RELATIONAL_STORE +#endif // SINGLE_VER_RELATIONAL_SYNC_TASK_CONTEXT_H \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_relational_syncer.cpp b/mock/distributeddb/syncer/src/single_ver_relational_syncer.cpp new file mode 100644 index 00000000..264ab16c --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_relational_syncer.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_relational_syncer.h" +#ifdef RELATIONAL_STORE +#include "db_common.h" +#include "relational_db_sync_interface.h" +#include "single_ver_sync_engine.h" + +namespace DistributedDB { +int SingleVerRelationalSyncer::Initialize(ISyncInterface *syncInterface, bool isNeedActive) +{ + int errCode = SingleVerSyncer::Initialize(syncInterface, isNeedActive); + if (errCode != E_OK) { + return errCode; + } + auto callback = std::bind(&SingleVerRelationalSyncer::SchemaChangeCallback, this); + return static_cast(syncInterface)-> + RegisterSchemaChangedCallback(callback); +} + +int SingleVerRelationalSyncer::Sync(const SyncParma ¶m, uint64_t connectionId) +{ + if (param.mode == SYNC_MODE_PUSH_PULL) { + return -E_NOT_SUPPORT; + } + if (param.syncQuery.GetRelationTableName().empty()) { + return -E_NOT_SUPPORT; + } + return GenericSyncer::Sync(param, connectionId); +} + +int SingleVerRelationalSyncer::PrepareSync(const SyncParma ¶m, uint32_t syncId, uint64_t connectionId) +{ + const auto &syncInterface = static_cast(syncInterface_); + std::vector tablesQuery; + if (param.isQuerySync) { + tablesQuery.push_back(param.syncQuery); + } else { + tablesQuery = syncInterface->GetTablesQuery(); + } + std::set subSyncIdSet; + int errCode = GenerateEachSyncTask(param, syncId, tablesQuery, connectionId, subSyncIdSet); + if (errCode != E_OK) { + DoRollBack(subSyncIdSet); + return errCode; + } + if (param.wait) { + DoOnComplete(param, syncId); + } + return E_OK; +} + +int SingleVerRelationalSyncer::GenerateEachSyncTask(const SyncParma ¶m, uint32_t syncId, + const std::vector &tablesQuery, uint64_t connectionId, std::set &subSyncIdSet) +{ + SyncParma subParam = param; + subParam.isQuerySync = true; + int errCode = E_OK; + for (const QuerySyncObject &table : tablesQuery) { + uint32_t subSyncId = GenerateSyncId(); + std::string hashTableName = DBCommon::TransferHashString(table.GetRelationTableName()); + LOGI("[SingleVerRelationalSyncer] SubSyncId %u create by SyncId %d, hashTableName = %s", + subSyncId, syncId, STR_MASK(DBCommon::TransferStringToHex(hashTableName))); + subParam.syncQuery = table; + subParam.onComplete = std::bind(&SingleVerRelationalSyncer::DoOnSubSyncComplete, this, subSyncId, + syncId, param, std::placeholders::_1); + { + std::lock_guard lockGuard(syncMapLock_); + fullSyncIdMap_[syncId].insert(subSyncId); + } + errCode = GenericSyncer::PrepareSync(subParam, subSyncId, connectionId); + if (errCode != E_OK) { + LOGW("[SingleVerRelationalSyncer] PrepareSync failed errCode:%d", errCode); + std::lock_guard lockGuard(syncMapLock_); + fullSyncIdMap_[syncId].erase(subSyncId); + break; + } + subSyncIdSet.insert(subSyncId); + } + return errCode; +} + +void SingleVerRelationalSyncer::DoOnSubSyncComplete(const uint32_t subSyncId, const uint32_t syncId, + const SyncParma ¶m, const std::map &devicesMap) +{ + bool allFinish = true; + { + std::lock_guard lockGuard(syncMapLock_); + fullSyncIdMap_[syncId].erase(subSyncId); + allFinish = fullSyncIdMap_[syncId].empty(); + for (const auto &item : devicesMap) { + resMap_[syncId][item.first][param.syncQuery.GetRelationTableName()] = static_cast(item.second); + } + } + // block sync do callback in sync function + if (allFinish && !param.wait) { + DoOnComplete(param, syncId); + } +} + +void SingleVerRelationalSyncer::DoRollBack(std::set &subSyncIdSet) +{ + for (const auto &removeId : subSyncIdSet) { + int retCode = RemoveSyncOperation(static_cast(removeId)); + if (retCode != E_OK) { + LOGW("[SingleVerRelationalSyncer] RemoveSyncOperation failed errCode:%d, syncId:%d", retCode, removeId); + } + } +} + +void SingleVerRelationalSyncer::DoOnComplete(const SyncParma ¶m, uint32_t syncId) +{ + if (!param.relationOnComplete) { + return; + } + std::map> syncRes; + std::map> tmpMap; + { + std::lock_guard lockGuard(syncMapLock_); + tmpMap = resMap_[syncId]; + } + for (const auto &devicesRes : tmpMap) { + for (const auto &tableRes : devicesRes.second) { + syncRes[devicesRes.first].push_back( + {tableRes.first, static_cast(tableRes.second)}); + } + } + param.relationOnComplete(syncRes); + { + std::lock_guard lockGuard(syncMapLock_); + resMap_.erase(syncId); + fullSyncIdMap_.erase(syncId); + } +} + +void SingleVerRelationalSyncer::EnableAutoSync(bool enable) +{ + (void)enable; +} + +void SingleVerRelationalSyncer::LocalDataChanged(int notifyEvent) +{ + (void)notifyEvent; +} + +void SingleVerRelationalSyncer::SchemaChangeCallback() +{ + if (syncEngine_ != nullptr) { + syncEngine_->SchemaChange(); + } +} + +int SingleVerRelationalSyncer::SyncConditionCheck(QuerySyncObject &query, int mode, bool isQuerySync, + const std::vector &devices) const +{ + if (!isQuerySync) { + return E_OK; + } + int errCode = static_cast(syncInterface_)->CheckAndInitQueryCondition(query); + if (errCode != E_OK) { + LOGE("[SingleVerRelationalSyncer] QuerySyncObject check failed"); + return errCode; + } + if (mode == SUBSCRIBE_QUERY) { + return -E_NOT_SUPPORT; + } + return E_OK; +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_relational_syncer.h b/mock/distributeddb/syncer/src/single_ver_relational_syncer.h new file mode 100644 index 00000000..5bb7caee --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_relational_syncer.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RELATIONAL_SYNCER_H +#define RELATIONAL_SYNCER_H +#ifdef RELATIONAL_STORE +#include "single_ver_syncer.h" +namespace DistributedDB { +class SingleVerRelationalSyncer final : public SingleVerSyncer { +public: + SingleVerRelationalSyncer() = default; + ~SingleVerRelationalSyncer() override = default; + + int Initialize(ISyncInterface *syncInterface, bool isNeedActive) override; + + // Sync function. use SyncParma to reduce parameter. + int Sync(const SyncParma ¶m, uint64_t connectionId) override; + + void EnableAutoSync(bool enable) override; + + void LocalDataChanged(int notifyEvent) override; + +protected: + + int PrepareSync(const SyncParma ¶m, uint32_t syncId, uint64_t connectionId) override; + + int SyncConditionCheck(QuerySyncObject &query, int mode, bool isQuerySync, + const std::vector &devices) const override; + +private: + + int GenerateEachSyncTask(const SyncParma ¶m, uint32_t syncId, + const std::vector &tablesQuery, uint64_t connectionId, std::set &subSyncIdSet); + + void DoRollBack(std::set &subSyncIdSet); + + void DoOnComplete(const SyncParma ¶m, uint32_t syncId); + void DoOnSubSyncComplete(const uint32_t subSyncId, const uint32_t syncId, + const SyncParma ¶m, const std::map &devicesMap); + + void SchemaChangeCallback(); + + mutable std::mutex syncMapLock_; + std::map> fullSyncIdMap_; + std::map>> resMap_; +}; +} +#endif +#endif // RELATIONAL_SYNCER_H \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_serialize_manager.cpp b/mock/distributeddb/syncer/src/single_ver_serialize_manager.cpp new file mode 100644 index 00000000..a6fc97b0 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_serialize_manager.cpp @@ -0,0 +1,713 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_serialize_manager.h" +#include "icommunicator.h" +#include "log_print.h" +#include "message_transform.h" +#include "parcel.h" +#include "generic_single_ver_kv_entry.h" +#include "sync_types.h" +#include "version.h" +#include "db_common.h" + +namespace DistributedDB { +int SingleVerSerializeManager::Serialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + if ((buffer == nullptr) || !(IsPacketValid(inMsg))) { + return -E_MESSAGE_ID_ERROR; + } + if (inMsg->GetMessageId() == CONTROL_SYNC_MESSAGE) { + return ControlSerialization(buffer, length, inMsg); + } + return DataSerialization(buffer, length, inMsg); +} + +int SingleVerSerializeManager::DataSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return DataPacketSerialization(buffer, length, inMsg); + case TYPE_RESPONSE: + case TYPE_NOTIFY: + return AckPacketSerialization(buffer, length, inMsg); + default: + return -E_MESSAGE_TYPE_ERROR; + } +} + +int SingleVerSerializeManager::ControlSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return ControlPacketSerialization(buffer, length, inMsg); + case TYPE_RESPONSE: + return AckControlPacketSerialization(buffer, length, inMsg); + default: + return -E_MESSAGE_TYPE_ERROR; + } +} + +int SingleVerSerializeManager::DeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + if ((buffer == nullptr) || !(IsPacketValid(inMsg))) { + return -E_MESSAGE_ID_ERROR; + } + if (inMsg->GetMessageId() == CONTROL_SYNC_MESSAGE) { + return ControlDeSerialization(buffer, length, inMsg); + } + return DataDeSerialization(buffer, length, inMsg); +} + +int SingleVerSerializeManager::DataDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return DataPacketDeSerialization(buffer, length, inMsg); + case TYPE_RESPONSE: + case TYPE_NOTIFY: + return AckPacketDeSerialization(buffer, length, inMsg); + default: + return -E_MESSAGE_TYPE_ERROR; + } +} + +int SingleVerSerializeManager::ControlDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return ControlPacketDeSerialization(buffer, length, inMsg); + case TYPE_RESPONSE: + return AckControlPacketDeSerialization(buffer, length, inMsg); + default: + return -E_MESSAGE_TYPE_ERROR; + } +} + +uint32_t SingleVerSerializeManager::CalculateLen(const Message *inMsg) +{ + if (!(IsPacketValid(inMsg))) { + return 0; + } + if (inMsg->GetMessageId() == CONTROL_SYNC_MESSAGE) { + return CalculateControlLen(inMsg); + } + return CalculateDataLen(inMsg); +} + +uint32_t SingleVerSerializeManager::CalculateDataLen(const Message *inMsg) +{ + uint32_t len = 0; + int errCode; + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + errCode = DataPacketCalculateLen(inMsg, len); + if (errCode != E_OK) { + LOGE("[CalculateDataLen] calculate data request packet len failed, errCode=%d", errCode); + return 0; + } + return len; + case TYPE_RESPONSE: + case TYPE_NOTIFY: + errCode = AckPacketCalculateLen(inMsg, len); + if (errCode != E_OK) { + LOGE("[CalculateDataLen] calculate data notify packet len failed errCode=%d", errCode); + return 0; + } + return len; + default: + return 0; + } +} + +uint32_t SingleVerSerializeManager::CalculateControlLen(const Message *inMsg) +{ + uint32_t len = 0; + int errCode; + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + errCode = ControlPacketCalculateLen(inMsg, len); + if (errCode != E_OK) { + LOGE("[CalculateControlLen] calculate control request packet len failed, errCode=%d", errCode); + return 0; + } + return len; + case TYPE_RESPONSE: + case TYPE_NOTIFY: + errCode = AckControlPacketCalculateLen(inMsg, len); + if (errCode != E_OK) { + LOGE("[CalculateControlLen] calculate control request packet len failed, errCode=%d", errCode); + return 0; + } + return len; + default: + return 0; + } +} + +int SingleVerSerializeManager::RegisterTransformFunc() +{ + TransformFunc func; + func.computeFunc = std::bind(&SingleVerSerializeManager::CalculateLen, std::placeholders::_1); + func.serializeFunc = std::bind(&SingleVerSerializeManager::Serialization, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + func.deserializeFunc = std::bind(&SingleVerSerializeManager::DeSerialization, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + + int errCode = MessageTransform::RegTransformFunction(QUERY_SYNC_MESSAGE, func); + if (errCode != E_OK) { + return errCode; + } + errCode = MessageTransform::RegTransformFunction(DATA_SYNC_MESSAGE, func); + if (errCode != E_OK) { + return errCode; + } + return MessageTransform::RegTransformFunction(CONTROL_SYNC_MESSAGE, func); +} + +int SingleVerSerializeManager::DataPacketSyncerPartSerialization(Parcel &parcel, const DataRequestPacket *packet) +{ + parcel.WriteUInt64(packet->GetEndWaterMark()); + parcel.WriteUInt64(packet->GetLocalWaterMark()); + parcel.WriteUInt64(packet->GetPeerWaterMark()); + parcel.WriteInt(packet->GetSendCode()); + parcel.WriteInt(packet->GetMode()); + parcel.WriteUInt32(packet->GetSessionId()); + parcel.WriteVector(packet->GetReserved()); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + if (packet->GetVersion() > SOFTWARE_VERSION_RELEASE_2_0) { + parcel.WriteUInt32(packet->GetFlag()); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + } + parcel.EightByteAlign(); + return E_OK; +} + +int SingleVerSerializeManager::DataPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + auto packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + Parcel parcel(buffer, length); + + // version + int errCode = parcel.WriteUInt32(packet->GetVersion()); + if (errCode != E_OK) { + LOGE("[DataPacketSerialization] Serialize version failed"); + return errCode; + } + // sendDataItems + errCode = GenericSingleVerKvEntry::SerializeDatas( + (packet->IsCompressData() ? std::vector {} : packet->GetData()), parcel, packet->GetVersion()); + if (errCode != E_OK) { + LOGE("[DataPacketSerialization] Serialize Data failed"); + return errCode; + } + + // data sync + errCode = DataPacketSyncerPartSerialization(parcel, packet); + if (errCode != E_OK) { + LOGE("[DataPacketSerialization] Serialize Data failed"); + return errCode; + } + if (inMsg->GetMessageId() == QUERY_SYNC_MESSAGE) { + errCode = DataPacketQuerySyncSerialization(parcel, packet); // for query sync + if (errCode != E_OK) { + return errCode; + } + } + if (packet->IsCompressData()) { + // serialize compress data + errCode = GenericSingleVerKvEntry::SerializeCompressedDatas(packet->GetData(), packet->GetCompressData(), + parcel, packet->GetVersion(), packet->GetCompressAlgo()); + if (errCode != E_OK) { + LOGE("[DataPacketSerialization] Serialize compress Data failed"); + return errCode; + } + } + return E_OK; +} + +int SingleVerSerializeManager::DataPacketQuerySyncSerialization(Parcel &parcel, const DataRequestPacket *packet) +{ + // deleted record send watermark + int errCode = parcel.WriteUInt64(packet->GetDeletedWaterMark()); + if (errCode != E_OK) { + LOGE("[QuerySerialization] Serialize deleted record send watermark failed!"); + return errCode; + } + + // query identify + QuerySyncObject queryObj = packet->GetQuery(); + errCode = parcel.WriteString(packet->GetQueryId()); + if (errCode != E_OK) { + LOGE("[QuerySerialization] Serialize query id failed!"); + return errCode; + } + if ((packet->GetVersion() > SOFTWARE_VERSION_RELEASE_4_0) || packet->GetMode() != QUERY_PUSH) { + // need to check. + errCode = queryObj.SerializeData(parcel, SOFTWARE_VERSION_CURRENT); + } + return errCode; +} + +int SingleVerSerializeManager::DataPacketCalculateLen(const Message *inMsg, uint32_t &len) +{ + const DataRequestPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + + len = packet->CalculateLen(inMsg->GetMessageId()); + return E_OK; +} + +int SingleVerSerializeManager::AckPacketCalculateLen(const Message *inMsg, uint32_t &len) +{ + const DataAckPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + + len = packet->CalculateLen(); + return E_OK; +} + +bool SingleVerSerializeManager::IsPacketValid(const Message *inMsg) +{ + if (inMsg == nullptr) { + return false; + } + + int msgType = inMsg->GetMessageType(); + if (msgType != TYPE_REQUEST && msgType != TYPE_RESPONSE && msgType != TYPE_NOTIFY) { + LOGE("[DataSync][IsPacketValid] Message type ERROR! message type=%d", msgType); + return false; + } + return true; +} + +int SingleVerSerializeManager::AckPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + const DataAckPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + + Parcel parcel(buffer, length); + parcel.WriteUInt32(packet->GetVersion()); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + // now V1 compatible for softWareVersion :{101, 102} + return AckPacketSyncerPartSerializationV1(parcel, packet); +} + +int SingleVerSerializeManager::AckPacketSyncerPartSerializationV1(Parcel &parcel, const DataAckPacket *packet) +{ + parcel.WriteUInt64(packet->GetData()); + parcel.WriteInt(packet->GetRecvCode()); + parcel.WriteVector(packet->GetReserved()); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + parcel.EightByteAlign(); + return E_OK; +} + +int SingleVerSerializeManager::DataPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + std::vector dataItems; + uint32_t version; + Parcel parcel(const_cast(buffer), length); + uint32_t packLen = parcel.ReadUInt32(version); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + + if (version > SOFTWARE_VERSION_CURRENT) { + return -E_VERSION_NOT_SUPPORT; + } + + packLen += static_cast(GenericSingleVerKvEntry::DeSerializeDatas(dataItems, parcel)); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + + auto packet = new (std::nothrow) DataRequestPacket(); + if (packet == nullptr) { + return -E_OUT_OF_MEMORY; + } + + packet->SetVersion(version); + packet->SetData(dataItems); + int errCode = DataPacketSyncerPartDeSerialization(parcel, packet, packLen, length, version); + if (errCode != E_OK) { + goto ERROR; + } + if (inMsg->GetMessageId() == QUERY_SYNC_MESSAGE) { + errCode = DataPacketQuerySyncDeSerialization(parcel, packet); + if (errCode != E_OK) { + goto ERROR; + } + } + if (packet->IsCompressData()) { + errCode = DataPacketCompressDataDeSerialization(parcel, packet); + if (errCode != E_OK) { + goto ERROR; + } + } + + errCode = inMsg->SetExternalObject<>(packet); + if (errCode != E_OK) { + goto ERROR; + } + return errCode; + +ERROR: + delete packet; + packet = nullptr; + return errCode; +} + +int SingleVerSerializeManager::DataPacketQuerySyncDeSerialization(Parcel &parcel, DataRequestPacket *packet) +{ + WaterMark deletedWatermark = 0; + parcel.ReadUInt64(deletedWatermark); + std::string queryId; + parcel.ReadString(queryId); + if (parcel.IsError()) { + return -E_PARSE_FAIL; + } + // query identify + QuerySyncObject querySyncObj; + int errCode = E_OK; + // for version 105, query is always sent. + if ((packet->GetVersion() > SOFTWARE_VERSION_RELEASE_4_0) || packet->GetMode() != QUERY_PUSH) { + // need to check. + errCode = QuerySyncObject::DeSerializeData(parcel, querySyncObj); + } + if (errCode != E_OK) { + LOGI("[SingleVerSerializeManager] DeSerializeData object failed."); + return errCode; + } + packet->SetDeletedWaterMark(deletedWatermark); + packet->SetQueryId(queryId); + if ((packet->GetVersion() > SOFTWARE_VERSION_RELEASE_4_0) || packet->GetMode() != QUERY_PUSH) { + packet->SetQuery(querySyncObj); + } + return E_OK; +} + +int SingleVerSerializeManager::DataPacketCompressDataDeSerialization(Parcel &parcel, DataRequestPacket *packet) +{ + std::vector originalData; + int errCode = GenericSingleVerKvEntry::DeSerializeCompressedDatas(originalData, parcel); + if (errCode != E_OK) { + LOGE("[SingleVerSerializeManager] DeSerializeComptressData failed, errCode=%d", errCode); + return errCode; + } + packet->SetData(originalData); + return E_OK; +} + +int SingleVerSerializeManager::DataPacketSyncerPartDeSerialization(Parcel &parcel, DataRequestPacket *packet, + uint32_t packLen, uint32_t length, uint32_t version) +{ + WaterMark waterMark; + WaterMark localWaterMark; + WaterMark peerWaterMark; + int32_t sendCode; + int32_t mode; + uint32_t sessionId; + uint32_t flag = 0; + std::vector reserved; + + packLen += parcel.ReadUInt64(waterMark); + packLen += parcel.ReadUInt64(localWaterMark); + packLen += parcel.ReadUInt64(peerWaterMark); + packLen += parcel.ReadInt(sendCode); + packLen += parcel.ReadInt(mode); + packLen += parcel.ReadUInt32(sessionId); + packLen += parcel.ReadVector(reserved); + if (version > SOFTWARE_VERSION_RELEASE_2_0) { + packLen += parcel.ReadUInt32(flag); + packet->SetFlag(flag); + } + packLen = Parcel::GetEightByteAlign(packLen); + if (parcel.IsError()) { + LOGE("[DataSync][DataPacketDeSerialization] deserialize failed! input len=%" PRIu32 ",packLen=%" PRIu32, + length, packLen); + return -E_LENGTH_ERROR; + } + parcel.EightByteAlign(); + packet->SetEndWaterMark(waterMark); + packet->SetLocalWaterMark(localWaterMark); + packet->SetPeerWaterMark(peerWaterMark); + packet->SetSendCode(sendCode); + packet->SetMode(mode); + packet->SetSessionId(sessionId); + packet->SetReserved(reserved); + return E_OK; +} + +int SingleVerSerializeManager::AckPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + DataAckPacket packet; + Parcel parcel(const_cast(buffer), length); + uint32_t version; + + parcel.ReadUInt32(version); + if (parcel.IsError()) { + return -E_INVALID_ARGS; + } + if (version > SOFTWARE_VERSION_CURRENT) { + packet.SetVersion(version); + packet.SetRecvCode(-E_VERSION_NOT_SUPPORT); + return inMsg->SetCopiedObject<>(packet); + } + packet.SetVersion(version); + // now V1 compatible for softWareVersion :{101, 102} + int errCode = AckPacketSyncerPartDeSerializationV1(parcel, packet); + if (errCode != E_OK) { + return errCode; + } + + return inMsg->SetCopiedObject<>(packet); +} + +int SingleVerSerializeManager::AckPacketSyncerPartDeSerializationV1(Parcel &parcel, DataAckPacket &packet) +{ + WaterMark mark; + int32_t errCode; + std::vector reserved; + + parcel.ReadUInt64(mark); + parcel.ReadInt(errCode); + parcel.ReadVector(reserved); + if (parcel.IsError()) { + LOGE("[AckPacketSyncerPartDeSerializationV1] DeSerialization failed"); + return -E_INVALID_ARGS; + } + packet.SetData(mark); + packet.SetRecvCode(errCode); + packet.SetReserved(reserved); + return E_OK; +} + +int SingleVerSerializeManager::ControlPacketCalculateLen(const Message *inMsg, uint32_t &len) +{ + auto packet = inMsg->GetObject(); + if (packet == nullptr || packet->GetcontrolCmdType() >= INVALID_CONTROL_CMD) { + LOGE("[ControlPacketSerialization] invalid control cmd"); + return -E_INVALID_ARGS; + } + if (packet->GetcontrolCmdType() == SUBSCRIBE_QUERY_CMD || packet->GetcontrolCmdType() == UNSUBSCRIBE_QUERY_CMD) { + return SingleVerSerializeManager::SubscribeCalculateLen(inMsg, len); + } + return E_OK; +} + +int SingleVerSerializeManager::ControlPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + auto packet = inMsg->GetObject(); + if (packet == nullptr || packet->GetcontrolCmdType() >= INVALID_CONTROL_CMD) { + LOGE("[ControlPacketSerialization] invalid control cmd"); + return -E_INVALID_ARGS; + } + if (packet->GetcontrolCmdType() == SUBSCRIBE_QUERY_CMD || packet->GetcontrolCmdType() == UNSUBSCRIBE_QUERY_CMD) { + return SingleVerSerializeManager::SubscribeSerialization(buffer, length, inMsg); + } + return E_OK; +} + +int SingleVerSerializeManager::ControlPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + Parcel parcel(const_cast(buffer), length); + ControlRequestPacket packet; + int errCode = ControlRequestDeSerialization(parcel, packet); + if (errCode != E_OK) { + return errCode; + } + if (packet.GetcontrolCmdType() == SUBSCRIBE_QUERY_CMD || packet.GetcontrolCmdType() == UNSUBSCRIBE_QUERY_CMD) { + errCode = SubscribeDeSerialization(parcel, inMsg, packet); + } + return errCode; +} + +int SingleVerSerializeManager::AckControlPacketCalculateLen(const Message *inMsg, uint32_t &len) +{ + auto packet = inMsg->GetObject(); + if (packet == nullptr) { + LOGE("[AckControlPacketCalculateLen] invalid control cmd"); + return -E_INVALID_ARGS; + } + len = packet->CalculateLen(); + return E_OK; +} + +int SingleVerSerializeManager::AckControlPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + auto packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + Parcel parcel(buffer, length); + parcel.WriteUInt32(packet->GetVersion()); + parcel.WriteInt(packet->GetRecvCode()); + parcel.WriteUInt32(packet->GetcontrolCmdType()); + parcel.WriteUInt32(packet->GetFlag()); + if (parcel.IsError()) { + LOGE("[AckControlPacketSerialization] Serialization failed"); + return -E_INVALID_ARGS; + } + parcel.EightByteAlign(); + return E_OK; +} + +int SingleVerSerializeManager::AckControlPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + auto packet = new (std::nothrow) ControlAckPacket(); + if (packet == nullptr) { + return -E_OUT_OF_MEMORY; + } + Parcel parcel(const_cast(buffer), length); + int32_t recvCode = 0; + uint32_t version = 0; + uint32_t controlCmdType = 0; + uint32_t flag = 0; + parcel.ReadUInt32(version); + parcel.ReadInt(recvCode); + parcel.ReadUInt32(controlCmdType); + parcel.ReadUInt32(flag); + int errCode; + if (parcel.IsError()) { + LOGE("[AckControlPacketDeSerialization] DeSerialization failed"); + errCode = -E_INVALID_ARGS; + goto ERROR; + } + packet->SetPacketHead(recvCode, version, static_cast(controlCmdType), flag); + errCode = inMsg->SetExternalObject<>(packet); + if (errCode != E_OK) { + goto ERROR; + } + return errCode; +ERROR: + delete packet; + packet = nullptr; + return errCode; +} + +int SingleVerSerializeManager::ControlRequestSerialization(Parcel &parcel, const Message *inMsg) +{ + auto packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + parcel.WriteUInt32(packet->GetVersion()); + parcel.WriteInt(packet->GetSendCode()); + parcel.WriteUInt32(packet->GetcontrolCmdType()); + parcel.WriteUInt32(packet->GetFlag()); + if (parcel.IsError()) { + LOGE("[ControlRequestSerialization] Serialization failed"); + return -E_INVALID_ARGS; + } + parcel.EightByteAlign(); + return E_OK; +} + +int SingleVerSerializeManager::ControlRequestDeSerialization(Parcel &parcel, ControlRequestPacket &packet) +{ + uint32_t version = 0; + int32_t sendCode = 0; + uint32_t controlCmdType = 0; + uint32_t flag = 0; + parcel.ReadUInt32(version); + if (version > SOFTWARE_VERSION_CURRENT) { + return -E_VERSION_NOT_SUPPORT; + } + parcel.ReadInt(sendCode); + parcel.ReadUInt32(controlCmdType); + parcel.ReadUInt32(flag); + if (parcel.IsError()) { + LOGE("[ControlRequestDeSerialization] deserialize failed!"); + return -E_LENGTH_ERROR; + } + packet.SetPacketHead(sendCode, version, static_cast(controlCmdType), flag); + return E_OK; +} + +int SingleVerSerializeManager::SubscribeCalculateLen(const Message *inMsg, uint32_t &len) +{ + auto packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + len = packet->CalculateLen(); + return E_OK; +} + +int SingleVerSerializeManager::SubscribeSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + auto packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + Parcel parcel(buffer, length); + int errCode = ControlRequestSerialization(parcel, inMsg); + if (errCode != E_OK) { + LOGE("[SubscribeSerialization] ControlRequestPacket Serialization failed, errCode=%d", errCode); + return errCode; + } + QuerySyncObject queryObj = packet->GetQuery(); + errCode = queryObj.SerializeData(parcel, SOFTWARE_VERSION_CURRENT); + if (errCode != E_OK) { + LOGE("[SubscribeSerialization] query object Serialization failed, errCode=%d", errCode); + return errCode; + } + return E_OK; +} + +int SingleVerSerializeManager::SubscribeDeSerialization(Parcel &parcel, Message *inMsg, + ControlRequestPacket &controlPacket) +{ + auto packet = new (std::nothrow) SubscribeRequest(); + if (packet == nullptr) { + return -E_OUT_OF_MEMORY; + } + QuerySyncObject querySyncObj; + int errCode = QuerySyncObject::DeSerializeData(parcel, querySyncObj); + if (errCode != E_OK) { + goto ERROR; + } + packet->SetPacketHead(controlPacket.GetSendCode(), controlPacket.GetVersion(), + static_cast(controlPacket.GetcontrolCmdType()), controlPacket.GetFlag()); + packet->SetQuery(querySyncObj); + errCode = inMsg->SetExternalObject<>(packet); + if (errCode != E_OK) { + goto ERROR; + } + return errCode; +ERROR: + delete packet; + packet = nullptr; + return errCode; +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_serialize_manager.h b/mock/distributeddb/syncer/src/single_ver_serialize_manager.h new file mode 100644 index 00000000..162a23da --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_serialize_manager.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_SERIALIZE_MANAGER_NEW_H +#define SINGLE_VER_SERIALIZE_MANAGER_NEW_H + +#include "icommunicator.h" +#include "parcel.h" +#include "single_ver_data_packet.h" + +namespace DistributedDB { +class SingleVerSerializeManager { +public: + SingleVerSerializeManager(); + virtual ~SingleVerSerializeManager(); + + DISABLE_COPY_ASSIGN_MOVE(SingleVerSerializeManager); + + static int Serialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int DeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static uint32_t CalculateLen(const Message *inMsg); + + static int RegisterTransformFunc(); +private: + static bool IsPacketValid(const Message *inMsg); + + static int DataSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + static int ControlSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int DataDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + static int ControlDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static uint32_t CalculateDataLen(const Message *inMsg); + static uint32_t CalculateControlLen(const Message *inMsg); + + static int DataPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + static int DataPacketSyncerPartSerialization(Parcel &parcel, const DataRequestPacket *packet); + static int DataPacketQuerySyncSerialization(Parcel &parcel, const DataRequestPacket *packet); + static int DataPacketCalculateLen(const Message *inMsg, uint32_t &len); + + static int DataPacketQuerySyncDeSerialization(Parcel &parcel, DataRequestPacket *packet); + static int DataPacketCompressDataDeSerialization(Parcel &parcel, DataRequestPacket *packet); + static int DataPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + static int DataPacketSyncerPartDeSerialization(Parcel &parcel, DataRequestPacket *packet, uint32_t packLen, + uint32_t length, uint32_t version); + + static int AckPacketCalculateLen(const Message *inMsg, uint32_t &len); + static int AckPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + static int AckPacketSyncerPartSerializationV1(Parcel &parcel, const DataAckPacket *packet); + + static int AckPacketSyncerPartDeSerializationV1(Parcel &parcel, DataAckPacket &packet); + static int AckPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static int ControlPacketCalculateLen(const Message *inMsg, uint32_t &len); + static int ControlPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + static int ControlPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static int AckControlPacketCalculateLen(const Message *inMsg, uint32_t &len); + static int AckControlPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + static int AckControlPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static int ControlRequestSerialization(Parcel &parcel, const Message *inMsg); + static int ControlRequestDeSerialization(Parcel &parcel, ControlRequestPacket &packet); + static int SubscribeCalculateLen(const Message *inMsg, uint32_t &len); + static int SubscribeSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + static int SubscribeDeSerialization(Parcel &parcel, Message *inMsg, ControlRequestPacket &controlPacket); +}; +} // namespace DistributedDB + +#endif // SINGLE_VER_SERIALIZE_MANAGER_NEW_H \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_sync_engine.cpp b/mock/distributeddb/syncer/src/single_ver_sync_engine.cpp new file mode 100644 index 00000000..3f85cb9e --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_sync_engine.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_sync_engine.h" +#include "db_common.h" +#include "single_ver_sync_task_context.h" +#include "single_ver_kv_sync_task_context.h" +#include "single_ver_relational_sync_task_context.h" +#include "log_print.h" + +namespace DistributedDB { +ISyncTaskContext *SingleVerSyncEngine::CreateSyncTaskContext() +{ + SingleVerSyncTaskContext *context = nullptr; + switch (syncInterface_->GetInterfaceType()) { + case ISyncInterface::SYNC_SVD: + context = new (std::nothrow) SingleVerKvSyncTaskContext(); + break; +#ifdef RELATIONAL_STORE + case ISyncInterface::SYNC_RELATION: + context = new (std::nothrow) SingleVerRelationalSyncTaskContext(); + break; +#endif + default: + break; + } + + if (context == nullptr) { + LOGE("[SingleVerSyncEngine][CreateSyncTaskContext] create failed, may be out of memory"); + return nullptr; + } + context->SetSyncRetry(GetSyncRetry()); + context->EnableClearRemoteStaleData(needClearRemoteStaleData_); + context->SetSubscribeManager(subManager_); + return context; +} + +void SingleVerSyncEngine::EnableClearRemoteStaleData(bool enable) +{ + LOGI("[SingleVerSyncEngine][EnableClearRemoteStaleData] enabled %d", enable); + needClearRemoteStaleData_ = enable; + std::unique_lock lock(contextMapLock_); + for (auto &iter : syncTaskContextMap_) { + auto context = static_cast(iter.second); + if (context != nullptr) { + context->EnableClearRemoteStaleData(enable); + } + } +} + +int SingleVerSyncEngine::StartAutoSubscribeTimer() +{ + std::lock_guard lockGuard(timerLock_); + if (subscribeTimerId_ > 0) { + LOGI("[SingleSyncEngine] subscribeTimerId is already set"); + return -E_INTERNAL_ERROR; + } + TimerId timerId = 0; + TimerAction timeOutCallback = std::bind(&SingleVerSyncEngine::SubscribeTimeOut, this, std::placeholders::_1); + int errCode = RuntimeContext::GetInstance()->SetTimer(SUBSCRIBE_TRIGGER_TIME_OUT, timeOutCallback, nullptr, + timerId); + if (errCode != E_OK) { + return errCode; + } + subscribeTimerId_ = timerId; + LOGI("[SingleSyncEngine] start auto subscribe timerId=%" PRIu64 " finished", timerId); + return errCode; +} + +void SingleVerSyncEngine::StopAutoSubscribeTimer() +{ + std::lock_guard lockGuard(timerLock_); + if (subscribeTimerId_ == 0) { + return; + } + LOGI("[SingleSyncEngine] stop auto subscribe timerId=%" PRIu64 " finished", subscribeTimerId_); + RuntimeContext::GetInstance()->RemoveTimer(subscribeTimerId_); + subscribeTimerId_ = 0; +} + +int SingleVerSyncEngine::SubscribeTimeOut(TimerId id) +{ + if (!queryAutoSyncCallback_) { + return E_OK; + } + std::lock_guard lockGuard(timerLock_); + std::map> allSyncQueries; + GetAllUnFinishSubQueries(allSyncQueries); + LOGI("[SingleVerSyncEngine] SubscribeTimeOut,size=%zu", allSyncQueries.size()); + if (allSyncQueries.size() == 0) { + LOGI("no need to trigger auto subscribe"); + return E_OK; + } + for (auto &item : allSyncQueries) { + for (auto &query : item.second) { + InternalSyncParma param; + GetSubscribeSyncParam(item.first, query, param); + queryAutoSyncCallback_(param); + } + } + return E_OK; +} + +void SingleVerSyncEngine::SetIsNeedResetAbilitySync(const std::string &deviceId, bool isNeedReset) +{ + ISyncTaskContext *context = GetSyncTaskContextAndInc(deviceId); + if (context != nullptr) { + context->SetIsNeedResetAbilitySync(isNeedReset); + RefObject::DecObjRef(context); + } +} +DEFINE_OBJECT_TAG_FACILITIES(SingleVerSyncEngine); +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_sync_engine.h b/mock/distributeddb/syncer/src/single_ver_sync_engine.h new file mode 100644 index 00000000..2fc8498a --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_sync_engine.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_SYNC_ENGINE_H +#define SINGLE_VER_SYNC_ENGINE_H + +#include "sync_engine.h" + +namespace DistributedDB { +class SingleVerSyncEngine final : public SyncEngine { +public: + SingleVerSyncEngine() : needClearRemoteStaleData_(false) {}; + + // If set true, remote stale data will be clear when remote db rebuilt. + void EnableClearRemoteStaleData(bool enable); + + // used by SingleVerKVSyncer when db online + int StartAutoSubscribeTimer() override; + + // used by SingleVerKVSyncer when remote/local db closed + void StopAutoSubscribeTimer() override; + + int SubscribeTimeOut(TimerId id); + + void SetIsNeedResetAbilitySync(const std::string &deviceId, bool isNeedReset); + + DISABLE_COPY_ASSIGN_MOVE(SingleVerSyncEngine); +protected: + ~SingleVerSyncEngine() override {}; + + // Create a context + ISyncTaskContext *CreateSyncTaskContext() override; + +private: + DECLARE_OBJECT_TAG(SingleVerSyncEngine); +#ifndef RUNNING_ON_TESTCASE + static constexpr int SUBSCRIBE_TRIGGER_TIME_OUT = 30 * 60 * 1000; // 30min +#else + static constexpr int SUBSCRIBE_TRIGGER_TIME_OUT = 5 * 60 * 1000; // 5min for test +#endif + + bool needClearRemoteStaleData_; + + // for subscribe timeout callback + std::mutex timerLock_; + TimerId subscribeTimerId_ = 0; +}; +} // namespace DistributedDB + +#endif // SINGLE_VER_SYNC_ENGINE_H \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_sync_state_machine.cpp b/mock/distributeddb/syncer/src/single_ver_sync_state_machine.cpp new file mode 100644 index 00000000..2756785b --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_sync_state_machine.cpp @@ -0,0 +1,1217 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_sync_state_machine.h" + +#include +#include +#include + +#include "db_errno.h" +#include "log_print.h" +#include "sync_operation.h" +#include "message_transform.h" +#include "sync_types.h" +#include "db_common.h" +#include "runtime_context.h" +#include "performance_analysis.h" +#include "single_ver_sync_target.h" +#include "single_ver_data_sync.h" +#include "single_ver_data_sync_utils.h" + +namespace DistributedDB { +using Event = SingleVerSyncStateMachine::Event; +using State = SingleVerSyncStateMachine::State; +namespace { + // used for state switch table + const int CURRENT_STATE_INDEX = 0; + const int EVENT_INDEX = 1; + const int OUTPUT_STATE_INDEX = 2; + + // drop v1 and v2 table by one optimize, dataSend mode in all version go with slide window mode. + // State switch table v3, has three columns, CurrentState, Event, and OutSate + const std::vector> STATE_SWITCH_TABLE_V3 = { + {State::IDLE, Event::START_SYNC_EVENT, State::TIME_SYNC}, + + // In TIME_SYNC state + {State::TIME_SYNC, Event::TIME_SYNC_FINISHED_EVENT, State::ABILITY_SYNC}, + {State::TIME_SYNC, Event::TIME_OUT_EVENT, State::SYNC_TIME_OUT}, + {State::TIME_SYNC, Event::INNER_ERR_EVENT, State::INNER_ERR}, + + // In ABILITY_SYNC state, compare version num and schema + {State::ABILITY_SYNC, Event::VERSION_NOT_SUPPOR_EVENT, State::INNER_ERR}, + {State::ABILITY_SYNC, Event::ABILITY_SYNC_FINISHED_EVENT, State::START_INITIACTIVE_DATA_SYNC}, + {State::ABILITY_SYNC, Event::TIME_OUT_EVENT, State::SYNC_TIME_OUT}, + {State::ABILITY_SYNC, Event::INNER_ERR_EVENT, State::INNER_ERR}, + {State::ABILITY_SYNC, Event::CONTROL_CMD_EVENT, State::SYNC_CONTROL_CMD}, + + // In START_INITIACTIVE_DATA_SYNC state, send a sync request, and send first packt of data sync + {State::START_INITIACTIVE_DATA_SYNC, Event::NEED_ABILITY_SYNC_EVENT, State::ABILITY_SYNC}, + {State::START_INITIACTIVE_DATA_SYNC, Event::TIME_OUT_EVENT, State::SYNC_TIME_OUT}, + {State::START_INITIACTIVE_DATA_SYNC, Event::INNER_ERR_EVENT, State::INNER_ERR}, + {State::START_INITIACTIVE_DATA_SYNC, Event::SEND_FINISHED_EVENT, State::START_PASSIVE_DATA_SYNC}, + {State::START_INITIACTIVE_DATA_SYNC, Event::RE_SEND_DATA_EVENT, State::START_INITIACTIVE_DATA_SYNC}, + + // In START_PASSIVE_DATA_SYNC state, do response pull request, and send first packt of data sync + {State::START_PASSIVE_DATA_SYNC, Event::SEND_FINISHED_EVENT, State::START_PASSIVE_DATA_SYNC}, + {State::START_PASSIVE_DATA_SYNC, Event::RESPONSE_TASK_FINISHED_EVENT, State::WAIT_FOR_RECEIVE_DATA_FINISH}, + {State::START_PASSIVE_DATA_SYNC, Event::TIME_OUT_EVENT, State::SYNC_TIME_OUT}, + {State::START_PASSIVE_DATA_SYNC, Event::INNER_ERR_EVENT, State::INNER_ERR}, + {State::START_PASSIVE_DATA_SYNC, Event::NEED_ABILITY_SYNC_EVENT, State::ABILITY_SYNC}, + {State::START_PASSIVE_DATA_SYNC, Event::RE_SEND_DATA_EVENT, State::START_PASSIVE_DATA_SYNC}, + + // In WAIT_FOR_RECEIVE_DATA_FINISH, + {State::WAIT_FOR_RECEIVE_DATA_FINISH, Event::RECV_FINISHED_EVENT, State::SYNC_TASK_FINISHED}, + {State::WAIT_FOR_RECEIVE_DATA_FINISH, Event::START_PULL_RESPONSE_EVENT, State::START_PASSIVE_DATA_SYNC}, + {State::WAIT_FOR_RECEIVE_DATA_FINISH, Event::TIME_OUT_EVENT, State::SYNC_TIME_OUT}, + {State::WAIT_FOR_RECEIVE_DATA_FINISH, Event::INNER_ERR_EVENT, State::INNER_ERR}, + {State::WAIT_FOR_RECEIVE_DATA_FINISH, Event::NEED_ABILITY_SYNC_EVENT, State::ABILITY_SYNC}, + + {State::SYNC_CONTROL_CMD, Event::SEND_FINISHED_EVENT, State::SYNC_TASK_FINISHED}, + {State::SYNC_CONTROL_CMD, Event::TIME_OUT_EVENT, State::SYNC_TIME_OUT}, + {State::SYNC_CONTROL_CMD, Event::INNER_ERR_EVENT, State::INNER_ERR}, + {State::SYNC_CONTROL_CMD, Event::NEED_ABILITY_SYNC_EVENT, State::ABILITY_SYNC}, + + // In SYNC_TASK_FINISHED, + {State::SYNC_TASK_FINISHED, Event::ALL_TASK_FINISHED_EVENT, State::IDLE}, + {State::SYNC_TASK_FINISHED, Event::START_SYNC_EVENT, State::TIME_SYNC}, + + // SYNC_TIME_OUT and INNE_ERR state, just do some exception resolve + {State::SYNC_TIME_OUT, Event::ANY_EVENT, State::SYNC_TASK_FINISHED}, + {State::INNER_ERR, Event::ANY_EVENT, State::SYNC_TASK_FINISHED}, + }; +} + +std::mutex SingleVerSyncStateMachine::stateSwitchTableLock_; +std::vector SingleVerSyncStateMachine::stateSwitchTables_; +bool SingleVerSyncStateMachine::isStateSwitchTableInited_ = false; + +SingleVerSyncStateMachine::SingleVerSyncStateMachine() + : context_(nullptr), + syncInterface_(nullptr), + timeSync_(nullptr), + abilitySync_(nullptr), + dataSync_(nullptr), + currentRemoteVersionId_(0) +{ +} + +SingleVerSyncStateMachine::~SingleVerSyncStateMachine() +{ + LOGD("~SingleVerSyncStateMachine"); + Clear(); +} + +int SingleVerSyncStateMachine::Initialize(ISyncTaskContext *context, ISyncInterface *syncInterface, + std::shared_ptr &metaData, ICommunicator *communicator) +{ + if ((context == nullptr) || (syncInterface == nullptr) || (metaData == nullptr) || (communicator == nullptr)) { + return -E_INVALID_ARGS; + } + + int errCode = SyncStateMachine::Initialize(context, syncInterface, metaData, communicator); + if (errCode != E_OK) { + return errCode; + } + + timeSync_ = std::make_unique(); + dataSync_ = std::make_shared(); + abilitySync_ = std::make_unique(); + + errCode = timeSync_->Initialize(communicator, metaData, syncInterface, context->GetDeviceId()); + if (errCode != E_OK) { + goto ERROR_OUT; + } + errCode = dataSync_->Initialize(syncInterface, communicator, metaData, context->GetDeviceId()); + if (errCode != E_OK) { + goto ERROR_OUT; + } + errCode = abilitySync_->Initialize(communicator, syncInterface, metaData, context->GetDeviceId()); + if (errCode != E_OK) { + goto ERROR_OUT; + } + + currentState_ = IDLE; + context_ = static_cast(context); + syncInterface_ = static_cast(syncInterface); + + InitStateSwitchTables(); + InitStateMapping(); + return E_OK; + +ERROR_OUT: + Clear(); + return errCode; +} + +void SingleVerSyncStateMachine::SyncStep() +{ + RefObject::IncObjRef(context_); + RefObject::IncObjRef(communicator_); + int errCode = RuntimeContext::GetInstance()->ScheduleTask( + std::bind(&SingleVerSyncStateMachine::SyncStepInnerLocked, this)); + if (errCode != E_OK) { + LOGE("[StateMachine][SyncStep] Schedule SyncStep failed"); + RefObject::DecObjRef(communicator_); + RefObject::DecObjRef(context_); + } +} + +int SingleVerSyncStateMachine::ReceiveMessageCallback(Message *inMsg) +{ + int errCode = MessageCallbackPre(inMsg); + if (errCode != E_OK) { + return errCode; + } + switch (inMsg->GetMessageId()) { + case TIME_SYNC_MESSAGE: + errCode = TimeMarkSyncRecv(inMsg); + break; + case ABILITY_SYNC_MESSAGE: + errCode = AbilitySyncRecv(inMsg); + break; + case DATA_SYNC_MESSAGE: + case QUERY_SYNC_MESSAGE: + errCode = DataPktRecv(inMsg); + break; + case CONTROL_SYNC_MESSAGE: + errCode = ControlPktRecv(inMsg); + break; + default: + errCode = -E_NOT_SUPPORT; + } + return errCode; +} + +void SingleVerSyncStateMachine::SyncStepInnerLocked() +{ + if (context_->IncUsedCount() != E_OK) { + goto SYNC_STEP_OUT; + } + { + std::lock_guard lock(stateMachineLock_); + SyncStepInner(); + } + context_->SafeExit(); + +SYNC_STEP_OUT: + RefObject::DecObjRef(communicator_); + RefObject::DecObjRef(context_); +} + +void SingleVerSyncStateMachine::SyncStepInner() +{ + Event event = INNER_ERR_EVENT; + do { + auto iter = stateMapping_.find(currentState_); + if (iter != stateMapping_.end()) { + event = static_cast(iter->second()); + } else { + LOGE("[StateMachine][SyncStepInner] can not find state=%d,label=%s,dev=%s", currentState_, + dataSync_->GetLabel().c_str(), STR_MASK(context_->GetDeviceId())); + break; + } + } while (event != Event::WAIT_ACK_EVENT && SwitchMachineState(event) == E_OK && currentState_ != IDLE); +} + +void SingleVerSyncStateMachine::SetCurStateErrStatus() +{ + currentState_ = State::INNER_ERR; +} + +int SingleVerSyncStateMachine::StartSyncInner() +{ + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(PT_TEST_RECORDS::RECORD_MACHINE_START_TO_PUSH_SEND); + } + int errCode = PrepareNextSyncTask(); + if (errCode == E_OK) { + SwitchStateAndStep(Event::START_SYNC_EVENT); + } + return errCode; +} + +void SingleVerSyncStateMachine::AbortInner() +{ + LOGE("[StateMachine][AbortInner] error occurred,abort,label=%s,dev=%s", dataSync_->GetLabel().c_str(), + STR_MASK(context_->GetDeviceId())); + if (context_->IsKilled()) { + dataSync_->ClearDataMsg(); + } + dataSync_->ClearSyncStatus(); + ContinueToken token; + context_->GetContinueToken(token); + if (token != nullptr) { + syncInterface_->ReleaseContinueToken(token); + } + context_->SetContinueToken(nullptr); + context_->Clear(); +} + +const std::vector &SingleVerSyncStateMachine::GetStateSwitchTables() const +{ + return stateSwitchTables_; +} + +int SingleVerSyncStateMachine::PrepareNextSyncTask() +{ + int errCode = StartWatchDog(); + if (errCode != E_OK) { + LOGE("[StateMachine][PrepareNextSyncTask] WatchDog start failed,err=%d", errCode); + return errCode; + } + + if (currentState_ != State::IDLE && currentState_ != State::SYNC_TASK_FINISHED) { + LOGW("[StateMachine][PrepareNextSyncTask] PreSync may get an err, state=%" PRIu8 ",dev=%s", + currentState_, STR_MASK(context_->GetDeviceId())); + currentState_ = State::IDLE; + } + return E_OK; +} + +void SingleVerSyncStateMachine::SendSaveDataNotifyPacket(uint32_t sessionId, uint32_t sequenceId, uint32_t inMsgId) +{ + dataSync_->SendSaveDataNotifyPacket(context_, + std::min(context_->GetRemoteSoftwareVersion(), SOFTWARE_VERSION_CURRENT), sessionId, sequenceId, inMsgId); +} + +void SingleVerSyncStateMachine::CommErrAbort() +{ + std::lock_guard lock(stateMachineLock_); + if (SwitchMachineState(Event::INNER_ERR_EVENT) == E_OK) { + SyncStep(); + } +} + +void SingleVerSyncStateMachine::InitStateSwitchTables() +{ + if (isStateSwitchTableInited_) { + return; + } + + std::lock_guard lock(stateSwitchTableLock_); + if (isStateSwitchTableInited_) { + return; + } + + InitStateSwitchTable(SINGLE_VER_SYNC_PROCTOL_V3, STATE_SWITCH_TABLE_V3); + std::sort(stateSwitchTables_.begin(), stateSwitchTables_.end(), + [](const auto &tableA, const auto &tableB) { + return tableA.version > tableB.version; + }); // descending + isStateSwitchTableInited_ = true; +} + +void SingleVerSyncStateMachine::InitStateSwitchTable(uint32_t version, + const std::vector> &switchTable) +{ + StateSwitchTable table; + table.version = version; + for (const auto &stateSwitch : switchTable) { + if (stateSwitch.size() <= OUTPUT_STATE_INDEX) { + LOGE("[StateMachine][InitSwitchTable] stateSwitch size err,size=%zu", stateSwitch.size()); + return; + } + if (table.switchTable.count(stateSwitch[CURRENT_STATE_INDEX]) == 0) { + EventToState eventToState; // new EventToState + eventToState[stateSwitch[EVENT_INDEX]] = stateSwitch[OUTPUT_STATE_INDEX]; + table.switchTable[stateSwitch[CURRENT_STATE_INDEX]] = eventToState; + } else { // key stateSwitch[CURRENT_STATE_INDEX] already has EventToState + EventToState &eventToState = table.switchTable[stateSwitch[CURRENT_STATE_INDEX]]; + eventToState[stateSwitch[EVENT_INDEX]] = stateSwitch[OUTPUT_STATE_INDEX]; + } + } + stateSwitchTables_.push_back(table); +} + +void SingleVerSyncStateMachine::InitStateMapping() +{ + stateMapping_[TIME_SYNC] = std::bind(&SingleVerSyncStateMachine::DoTimeSync, this); + stateMapping_[ABILITY_SYNC] = std::bind(&SingleVerSyncStateMachine::DoAbilitySync, this); + stateMapping_[WAIT_FOR_RECEIVE_DATA_FINISH] = std::bind(&SingleVerSyncStateMachine::DoWaitForDataRecv, this); + stateMapping_[SYNC_TASK_FINISHED] = std::bind(&SingleVerSyncStateMachine::DoSyncTaskFinished, this); + stateMapping_[SYNC_TIME_OUT] = std::bind(&SingleVerSyncStateMachine::DoTimeout, this); + stateMapping_[INNER_ERR] = std::bind(&SingleVerSyncStateMachine::DoInnerErr, this); + stateMapping_[START_INITIACTIVE_DATA_SYNC] = + std::bind(&SingleVerSyncStateMachine::DoInitiactiveDataSyncWithSlidingWindow, this); + stateMapping_[START_PASSIVE_DATA_SYNC] = + std::bind(&SingleVerSyncStateMachine::DoPassiveDataSyncWithSlidingWindow, this); + stateMapping_[SYNC_CONTROL_CMD] = std::bind(&SingleVerSyncStateMachine::DoInitiactiveControlSync, this); +} + +Event SingleVerSyncStateMachine::DoInitiactiveDataSyncWithSlidingWindow() +{ + LOGD("[StateMachine][activeDataSync] mode=%d,label=%s,dev=%s", context_->GetMode(), + dataSync_->GetLabel().c_str(), STR_MASK(context_->GetDeviceId())); + int errCode = E_OK; + switch (context_->GetMode()) { + case SyncModeType::PUSH: + case SyncModeType::QUERY_PUSH: + context_->SetOperationStatus(SyncOperation::OP_RECV_FINISHED); + errCode = dataSync_->SyncStart(context_->GetMode(), context_); + break; + case SyncModeType::PULL: + case SyncModeType::QUERY_PULL: + context_->SetOperationStatus(SyncOperation::OP_SEND_FINISHED); + errCode = dataSync_->SyncStart(context_->GetMode(), context_); + break; + case SyncModeType::PUSH_AND_PULL: + case SyncModeType::QUERY_PUSH_PULL: + errCode = dataSync_->SyncStart(context_->GetMode(), context_); + break; + case SyncModeType::RESPONSE_PULL: + errCode = dataSync_->SyncStart(context_->GetMode(), context_); + break; + default: + errCode = -E_NOT_SUPPORT; + break; + } + if (errCode == E_OK) { + return Event::WAIT_ACK_EVENT; + } + // once E_EKEYREVOKED error occurred, PUSH_AND_PULL mode should wait for ack to pull remote data. + if (SyncOperation::TransferSyncMode(context_->GetMode()) == SyncModeType::PUSH_AND_PULL && + errCode == -E_EKEYREVOKED) { + return Event::WAIT_ACK_EVENT; + } + + // when response task step dataSync again while request task is running, ignore the errCode + bool ignoreInnerErr = (context_->GetResponseSessionId() != 0 && context_->GetRequestSessionId() != 0); + Event event = TransformErrCodeToEvent(errCode); + return (ignoreInnerErr && event == INNER_ERR_EVENT) ? SEND_FINISHED_EVENT : event; +} + +Event SingleVerSyncStateMachine::DoPassiveDataSyncWithSlidingWindow() +{ + { + RefObject::AutoLock lock(context_); + if (context_->GetRspTargetQueueSize() != 0) { + PreStartPullResponse(); + } else { + return RESPONSE_TASK_FINISHED_EVENT; + } + } + int errCode = dataSync_->SyncStart(SyncModeType::RESPONSE_PULL, context_); + if (errCode != E_OK) { + LOGW("[SingleVerSyncStateMachine][DoPassiveDataSyncWithSlidingWindow] response pull send failed[%d]", errCode); + return RESPONSE_TASK_FINISHED_EVENT; + } + return Event::WAIT_ACK_EVENT; +} + +Event SingleVerSyncStateMachine::DoInitiactiveControlSync() +{ + LOGD("[StateMachine][activeControlSync] mode=%d,label=%s,dev=%s", context_->GetMode(), + dataSync_->GetLabel().c_str(), STR_MASK(context_->GetDeviceId())); + context_->SetOperationStatus(SyncOperation::OP_RECV_FINISHED); + int errCode = dataSync_->ControlCmdStart(context_); + if (errCode == E_OK) { + return Event::WAIT_ACK_EVENT; + } + context_->SetTaskErrCode(errCode); + return TransformErrCodeToEvent(errCode); +} + +int SingleVerSyncStateMachine::HandleControlAckRecv(const Message *inMsg) +{ + std::lock_guard lock(stateMachineLock_); + if (IsNeedResetWatchdog(inMsg)) { + (void)ResetWatchDog(); + } + int errCode = dataSync_->ControlCmdAckRecv(context_, inMsg); + ControlAckRecvErrCodeHandle(errCode); + SwitchStateAndStep(TransformErrCodeToEvent(errCode)); + return E_OK; +} + +Event SingleVerSyncStateMachine::DoWaitForDataRecv() const +{ + if (context_->GetRspTargetQueueSize() != 0) { + return START_PULL_RESPONSE_EVENT; + } + if (context_->GetOperationStatus() == SyncOperation::OP_FINISHED_ALL) { + return RECV_FINISHED_EVENT; + } + if (SyncOperation::TransferSyncMode(context_->GetMode()) == SyncModeType::PUSH_AND_PULL && + context_->GetOperationStatus() == SyncOperation::OP_EKEYREVOKED_FAILURE && + context_->GetRemoteSoftwareVersion() > SOFTWARE_VERSION_RELEASE_2_0) { + return RECV_FINISHED_EVENT; + } + return Event::WAIT_ACK_EVENT; +} + +Event SingleVerSyncStateMachine::DoTimeSync() +{ + if (timeSync_->IsNeedSync()) { + CommErrHandler handler = nullptr; + // Auto sync need do retry don't use errHandler to return. + if (!context_->IsAutoSync()) { + handler = std::bind(&SyncTaskContext::CommErrHandlerFunc, std::placeholders::_1, + context_, context_->GetRequestSessionId()); + } + int errCode = timeSync_->SyncStart(handler, context_->GetRequestSessionId()); + if (errCode == E_OK) { + return Event::WAIT_ACK_EVENT; + } + context_->SetTaskErrCode(errCode); + return TransformErrCodeToEvent(errCode); + } + + return Event::TIME_SYNC_FINISHED_EVENT; +} + +Event SingleVerSyncStateMachine::DoAbilitySync() +{ + uint16_t remoteCommunicatorVersion = 0; + int errCode = communicator_->GetRemoteCommunicatorVersion(context_->GetDeviceId(), remoteCommunicatorVersion); + if (errCode != E_OK) { + LOGE("[StateMachine][DoAbilitySync] Get RemoteCommunicatorVersion errCode=%d", errCode); + return Event::INNER_ERR_EVENT; + } + // Fistr version, not support AbilitySync + if (remoteCommunicatorVersion == 0) { + context_->SetRemoteSoftwareVersion(SOFTWARE_VERSION_EARLIEST); + return GetEventAfterTimeSync(context_->GetMode()); + } + if (context_->GetIsNeedResetAbilitySync()) { + abilitySync_->SetAbilitySyncFinishedStatus(false); + context_->SetIsNeedResetAbilitySync(false); + } + if (abilitySync_->GetAbilitySyncFinishedStatus()) { + return GetEventAfterTimeSync(context_->GetMode()); + } + + CommErrHandler handler = std::bind(&SyncTaskContext::CommErrHandlerFunc, std::placeholders::_1, + context_, context_->GetRequestSessionId()); + LOGI("[StateMachine][AbilitySync] start abilitySync,label=%s,dev=%s", dataSync_->GetLabel().c_str(), + STR_MASK(context_->GetDeviceId())); + errCode = abilitySync_->SyncStart(context_->GetRequestSessionId(), context_->GetSequenceId(), + remoteCommunicatorVersion, handler); + if (errCode != E_OK) { + LOGE("[StateMachine][DoAbilitySync] ability sync start failed,errCode=%d", errCode); + return TransformErrCodeToEvent(errCode); + } + return Event::WAIT_ACK_EVENT; +} + +Event SingleVerSyncStateMachine::GetEventAfterTimeSync(int mode) const +{ + if (mode == SyncModeType::SUBSCRIBE_QUERY || mode == SyncModeType::UNSUBSCRIBE_QUERY) { + return Event::CONTROL_CMD_EVENT; + } + return Event::ABILITY_SYNC_FINISHED_EVENT; +} + +Event SingleVerSyncStateMachine::DoSyncTaskFinished() +{ + StopWatchDog(); + dataSync_->ClearSyncStatus(); + RefObject::AutoLock lock(syncContext_); + int errCode = ExecNextTask(); + if (errCode == E_OK) { + return Event::START_SYNC_EVENT; + } + return TransformErrCodeToEvent(errCode); +} + +Event SingleVerSyncStateMachine::DoTimeout() +{ + RefObject::AutoLock lock(context_); + if (context_->GetMode() == SyncModeType::SUBSCRIBE_QUERY) { + std::shared_ptr subManager = context_->GetSubscribeManager(); + if (subManager != nullptr) { + subManager->DeleteLocalSubscribeQuery(context_->GetDeviceId(), context_->GetQuery()); + } + } + context_->Abort(SyncOperation::OP_TIMEOUT); + context_->Clear(); + AbortInner(); + return Event::ANY_EVENT; +} + +Event SingleVerSyncStateMachine::DoInnerErr() +{ + RefObject::AutoLock lock(context_); + if (!context_->IsCommNormal()) { + if (context_->GetMode() == SyncModeType::SUBSCRIBE_QUERY) { + std::shared_ptr subManager = context_->GetSubscribeManager(); + if (subManager != nullptr) { + subManager->DeleteLocalSubscribeQuery(context_->GetDeviceId(), context_->GetQuery()); + } + } + context_->Abort(SyncOperation::OP_COMM_ABNORMAL); + } else { + int status = GetSyncOperationStatus(context_->GetTaskErrCode()); + context_->Abort(status); + } + context_->Clear(); + AbortInner(); + return Event::ANY_EVENT; +} + +int SingleVerSyncStateMachine::AbilitySyncRecv(const Message *inMsg) +{ + if (inMsg->GetMessageType() == TYPE_REQUEST) { + return abilitySync_->RequestRecv(inMsg, context_); + } + + if (inMsg->GetMessageType() == TYPE_RESPONSE && AbilityMsgSessionIdCheck(inMsg)) { + int errCode = abilitySync_->AckRecv(inMsg, context_); + std::lock_guard lock(stateMachineLock_); + (void)ResetWatchDog(); + if (errCode != E_OK) { + LOGE("[StateMachine][AbilitySyncRecv] handle ackRecv failed,errCode=%d", errCode); + SwitchStateAndStep(TransformErrCodeToEvent(errCode)); + } else if (context_->GetRemoteSoftwareVersion() <= SOFTWARE_VERSION_RELEASE_2_0) { + abilitySync_->SetAbilitySyncFinishedStatus(true); + LOGI("[StateMachine][AbilitySyncRecv] ability Sync Finished,label=%s,dev=%s", + dataSync_->GetLabel().c_str(), STR_MASK(context_->GetDeviceId())); + currentRemoteVersionId_ = context_->GetRemoteSoftwareVersionId(); + JumpStatusAfterAbilitySync(context_->GetMode()); + } + return E_OK; + } + if (inMsg->GetMessageType() == TYPE_NOTIFY) { + const AbilitySyncAckPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + int ackCode = packet->GetAckCode(); + if (ackCode != AbilitySync::CHECK_SUCCESS && ackCode != AbilitySync::LAST_NOTIFY) { + LOGE("[StateMachine][AbilitySyncRecv] ackCode check failed,ackCode=%d", ackCode); + std::lock_guard lock(stateMachineLock_); + SwitchStateAndStep(Event::INNER_ERR_EVENT); + return E_OK; + } + if (ackCode == AbilitySync::LAST_NOTIFY && AbilityMsgSessionIdCheck(inMsg)) { + abilitySync_->SetAbilitySyncFinishedStatus(true); + // while recv last notify means ability sync finished,it is better to reset watchDog to avoid timeout. + LOGI("[StateMachine][AbilitySyncRecv] ability sync finished,label=%s,dev=%s", + dataSync_->GetLabel().c_str(), STR_MASK(context_->GetDeviceId())); + currentRemoteVersionId_ = context_->GetRemoteSoftwareVersionId(); + (static_cast(context_))->SetIsSchemaSync(true); + std::lock_guard lock(stateMachineLock_); + (void)ResetWatchDog(); + JumpStatusAfterAbilitySync(context_->GetMode()); + } else if (ackCode != AbilitySync::LAST_NOTIFY) { + abilitySync_->AckNotifyRecv(inMsg, context_); + } + return E_OK; + } + + LOGE("[StateMachine][AbilitySyncRecv] msg type invalid"); + return -E_NOT_SUPPORT; +} + +int SingleVerSyncStateMachine::HandleDataRequestRecv(const Message *inMsg) +{ + TimeOffset offset = 0; + uint32_t timeout = communicator_->GetTimeout(context_->GetDeviceId()); + // If message is data sync request, we should check timeoffset. + int errCode = timeSync_->GetTimeOffset(offset, timeout); + if (errCode != E_OK) { + LOGE("[StateMachine][HandleDataRequestRecv] GetTimeOffset err! errCode=%d", errCode); + return errCode; + } + context_->SetTimeOffset(offset); + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(PT_TEST_RECORDS::RECORD_DATA_REQUEST_RECV_TO_SEND_ACK); + } + DecRefCountOfFeedDogTimer(SyncDirectionFlag::RECEIVE); + { + std::lock_guard lockWatchDog(stateMachineLock_); + if (IsNeedResetWatchdog(inMsg)) { + (void)ResetWatchDog(); + } + } + + // RequestRecv will save data, it may cost a long time. + // So we need to send save data notify to keep remote alive. + bool isNeedStop = StartSaveDataNotify(inMsg->GetSessionId(), inMsg->GetSequenceId(), inMsg->GetMessageId()); + WaterMark pullEndWaterkark = 0; + errCode = dataSync_->DataRequestRecv(context_, inMsg, pullEndWaterkark); + if (performance != nullptr) { + performance->StepTimeRecordEnd(PT_TEST_RECORDS::RECORD_DATA_REQUEST_RECV_TO_SEND_ACK); + } + if (isNeedStop) { + StopSaveDataNotify(); + } + // only higher than 102 version receive this errCode here. + // while both RequestSessionId is not equal,but get this errCode;slwr would seem to handle first secquencid. + // so while receive the same secquencid after abiitysync it wouldn't handle. + if (errCode == -E_NEED_ABILITY_SYNC) { + return errCode; + } + std::lock_guard lock(stateMachineLock_); + DataRecvErrCodeHandle(inMsg->GetSessionId(), errCode); + if (pullEndWaterkark > 0) { + AddPullResponseTarget(inMsg, pullEndWaterkark); + } + return E_OK; +} + +void SingleVerSyncStateMachine::HandleDataAckRecvWithSlidingWindow(int errCode, const Message *inMsg, + bool ignoreInnerErr) +{ + if (errCode == -E_RE_SEND_DATA) { // LOCAL_WATER_MARK_NOT_INIT + dataSync_->ClearSyncStatus(); + } + if (errCode == -E_NO_DATA_SEND || errCode == -E_SEND_DATA) { + int ret = dataSync_->TryContinueSync(context_, inMsg); + if (ret == -E_FINISHED) { + SwitchStateAndStep(Event::SEND_FINISHED_EVENT); + return; + } else if (ret == E_OK) { // do nothing and waiting for all ack receive + return; + } + errCode = ret; + } + ResponsePullError(errCode, ignoreInnerErr); +} + +void SingleVerSyncStateMachine::NeedAbilitySyncHandle() +{ + // if the remote device version num is overdue, + // mean the version num has been reset when syncing data, + // there should not clear the new version cache again. + if (currentRemoteVersionId_ == context_->GetRemoteSoftwareVersionId()) { + LOGI("[StateMachine] set remote version 0, currentRemoteVersionId_ = %" PRIu64, currentRemoteVersionId_); + context_->SetRemoteSoftwareVersion(0); + } else { + currentRemoteVersionId_ = context_->GetRemoteSoftwareVersionId(); + } + abilitySync_->SetAbilitySyncFinishedStatus(false); + dataSync_->ClearSyncStatus(); +} + +int SingleVerSyncStateMachine::HandleDataAckRecv(const Message *inMsg) +{ + if (inMsg->GetMessageType() == TYPE_RESPONSE) { + DecRefCountOfFeedDogTimer(SyncDirectionFlag::SEND); + } + std::lock_guard lock(stateMachineLock_); + // Unfortunately we use stateMachineLock_ in many sync process + // So a bad ack will check before the lock and wait + // And then another process is running, it will get the lock.After this process, the ack became invalid. + // If we don't check ack again, it will be delivered to dataSyncer. + if (!IsPacketValid(inMsg)) { + return -E_INVALID_ARGS; + } + if (IsNeedResetWatchdog(inMsg)) { + (void)ResetWatchDog(); + } + if (context_->GetRemoteSoftwareVersion() > SOFTWARE_VERSION_RELEASE_2_0 && !dataSync_->AckPacketIdCheck(inMsg)) { + // packetId not match but sequence id matched scene, means resend map has be rebuilt + // this is old ack, shoulb be dropped and wait for the same packetId sequence. + return E_OK; + } + // AckRecv will save meta data, it may cost a long time. if another thread is saving data + // So we need to send save data notify to keep remote alive. + // eg. remote do pull sync + bool isNeedStop = StartSaveDataNotify(inMsg->GetSessionId(), inMsg->GetSequenceId(), inMsg->GetMessageId()); + int errCode = dataSync_->AckRecv(context_, inMsg); + if (isNeedStop) { + StopSaveDataNotify(); + } + if (errCode == -E_NEED_ABILITY_SYNC || errCode == -E_RE_SEND_DATA) { + StopFeedDogForSync(SyncDirectionFlag::SEND); + } else if (errCode == -E_SAVE_DATA_NOTIFY) { + return errCode; + } + // when this msg is from response task while request task is running, ignore the errCode + bool ignoreInnerErr = inMsg->GetSessionId() == context_->GetResponseSessionId() && + context_->GetRequestSessionId() != 0; + DataAckRecvErrCodeHandle(errCode, !ignoreInnerErr); + HandleDataAckRecvWithSlidingWindow(errCode, inMsg, ignoreInnerErr); + return errCode; +} + +int SingleVerSyncStateMachine::DataPktRecv(Message *inMsg) +{ + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + int errCode = E_OK; + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + ScheduleMsgAndHandle(inMsg); + errCode = -E_NOT_NEED_DELETE_MSG; + break; + case TYPE_RESPONSE: + case TYPE_NOTIFY: + if (performance != nullptr) { + performance->StepTimeRecordEnd(PT_TEST_RECORDS::RECORD_DATA_SEND_REQUEST_TO_ACK_RECV); + performance->StepTimeRecordStart(PT_TEST_RECORDS::RECORD_ACK_RECV_TO_USER_CALL_BACK); + } + errCode = HandleDataAckRecv(inMsg); + break; + default: + errCode = -E_INVALID_ARGS; + break; + } + return errCode; +} + +void SingleVerSyncStateMachine::ScheduleMsgAndHandle(Message *inMsg) +{ + dataSync_->PutDataMsg(inMsg); + while (true) { + bool isNeedHandle = true; + bool isNeedContinue = true; + Message *msg = dataSync_->MoveNextDataMsg(context_, isNeedHandle, isNeedContinue); + if (!isNeedContinue) { + break; + } + if (msg == nullptr) { + if (dataSync_->IsNeedReloadQueue()) { + continue; + } + break; + } + bool isNeedClearMap = false; + if (isNeedHandle) { + int errCode = HandleDataRequestRecv(msg); + if (context_->IsReceiveWaterMarkErr() || errCode == -E_NEED_ABILITY_SYNC) { + isNeedClearMap = true; + } + if (errCode == -E_TIMEOUT) { + isNeedHandle = false; + } + } else { + dataSync_->SendFinishedDataAck(context_, msg); + } + if (context_->GetRemoteSoftwareVersion() < SOFTWARE_VERSION_RELEASE_3_0) { + // for lower version, no need to handle map schedule info, just reset schedule working status + isNeedHandle = false; + } + dataSync_->ScheduleInfoHandle(isNeedHandle, isNeedClearMap, msg); + delete msg; + } +} + +int SingleVerSyncStateMachine::ControlPktRecv(Message *inMsg) +{ + int errCode = E_OK; + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + errCode = dataSync_->ControlCmdRequestRecv(context_, inMsg); + break; + case TYPE_RESPONSE: + errCode = HandleControlAckRecv(inMsg); + break; + default: + errCode = -E_INVALID_ARGS; + break; + } + return errCode; +} + +void SingleVerSyncStateMachine::StepToTimeout(TimerId timerId) +{ + std::lock_guard lock(stateMachineLock_); + TimerId timer = syncContext_->GetTimerId(); + if (timer != timerId) { + return; + } + SwitchStateAndStep(Event::TIME_OUT_EVENT); +} + +int SingleVerSyncStateMachine::GetSyncOperationStatus(int errCode) const +{ + static const std::map statusMap = { + { -E_SCHEMA_MISMATCH, SyncOperation::OP_SCHEMA_INCOMPATIBLE }, + { -E_EKEYREVOKED, SyncOperation::OP_EKEYREVOKED_FAILURE }, + { -E_SECURITY_OPTION_CHECK_ERROR, SyncOperation::OP_SECURITY_OPTION_CHECK_FAILURE }, + { -E_BUSY, SyncOperation::OP_BUSY_FAILURE }, + { -E_NOT_PERMIT, SyncOperation::OP_PERMISSION_CHECK_FAILED }, + { -E_TIMEOUT, SyncOperation::OP_TIMEOUT }, + { -E_INVALID_QUERY_FORMAT, SyncOperation::OP_QUERY_FORMAT_FAILURE }, + { -E_INVALID_QUERY_FIELD, SyncOperation::OP_QUERY_FIELD_FAILURE }, + { -E_FEEDBACK_UNKNOWN_MESSAGE, SyncOperation::OP_NOT_SUPPORT }, + { -E_FEEDBACK_COMMUNICATOR_NOT_FOUND, SyncOperation::OP_COMM_ABNORMAL }, + { -E_NOT_SUPPORT, SyncOperation::OP_NOT_SUPPORT }, + { -E_INTERCEPT_DATA_FAIL, SyncOperation::OP_INTERCEPT_DATA_FAIL }, + { -E_MAX_LIMITS, SyncOperation::OP_MAX_LIMITS }, + { -E_DISTRIBUTED_SCHEMA_CHANGED, SyncOperation::OP_SCHEMA_CHANGED }, + { -E_NOT_REGISTER, SyncOperation::OP_NOT_SUPPORT }, + }; + auto iter = statusMap.find(errCode); + if (iter != statusMap.end()) { + return iter->second; + } + return SyncOperation::OP_FAILED; +} + +int SingleVerSyncStateMachine::TimeMarkSyncRecv(const Message *inMsg) +{ + LOGD("[StateMachine][TimeMarkSyncRecv] type=%d,label=%s,dev=%s", inMsg->GetMessageType(), + dataSync_->GetLabel().c_str(), STR_MASK(context_->GetDeviceId())); + { + std::lock_guard lock(stateMachineLock_); + (void)ResetWatchDog(); + } + if (inMsg->GetMessageType() == TYPE_REQUEST) { + return timeSync_->RequestRecv(inMsg); + } else if (inMsg->GetMessageType() == TYPE_RESPONSE) { + int errCode = timeSync_->AckRecv(inMsg, context_->GetRequestSessionId()); + if (errCode != E_OK) { + LOGE("[StateMachine][TimeMarkSyncRecv] AckRecv failed errCode=%d", errCode); + if (inMsg->GetSessionId() != 0 && inMsg->GetSessionId() == context_->GetRequestSessionId()) { + context_->SetTaskErrCode(errCode); + CommErrAbort(); + } + return errCode; + } + std::lock_guard lock(stateMachineLock_); + SwitchStateAndStep(TIME_SYNC_FINISHED_EVENT); + return E_OK; + } else { + return -E_INVALID_ARGS; + } +} + +void SingleVerSyncStateMachine::Clear() +{ + dataSync_ = nullptr; + timeSync_ = nullptr; + abilitySync_ = nullptr; + context_ = nullptr; + syncInterface_ = nullptr; +} + +bool SingleVerSyncStateMachine::IsPacketValid(const Message *inMsg) const +{ + if (inMsg == nullptr) { + return false; + } + + if ((inMsg->GetMessageId() < TIME_SYNC_MESSAGE) || (inMsg->GetMessageId() >= UNKNOW_MESSAGE)) { + LOGE("[StateMachine][IsPacketValid] Message is invalid, id=%d", inMsg->GetMessageId()); + return false; + } + // filter invalid ack at first + bool isResponseType = (inMsg->GetMessageType() == TYPE_RESPONSE); + if (isResponseType && (inMsg->GetMessageId() == CONTROL_SYNC_MESSAGE) && + (inMsg->GetSessionId() != context_->GetRequestSessionId())) { + LOGE("[StateMachine][IsPacketValid] Control Message is invalid, label=%s, dev=%s", + dataSync_->GetLabel().c_str(), STR_MASK(context_->GetDeviceId())); + return false; + } + if (isResponseType && (inMsg->GetMessageId() != TIME_SYNC_MESSAGE) && + (inMsg->GetSessionId() != context_->GetRequestSessionId()) && + (inMsg->GetSessionId() != context_->GetResponseSessionId())) { + LOGE("[StateMachine][IsPacketValid] Data Message is invalid, label=%s, dev=%s", + dataSync_->GetLabel().c_str(), STR_MASK(context_->GetDeviceId())); + return false; + } + + // timeSync and abilitySync don't need to check sequenceId + if (inMsg->GetMessageId() == TIME_SYNC_MESSAGE || inMsg->GetMessageId() == ABILITY_SYNC_MESSAGE || + inMsg->GetMessageId() == CONTROL_SYNC_MESSAGE) { + return true; + } + // sequenceId will be checked in dataSync + return true; +} + +void SingleVerSyncStateMachine::PreStartPullResponse() +{ + SingleVerSyncTarget target; + context_->PopResponseTarget(target); + context_->SetEndMark(target.GetEndWaterMark()); + context_->SetResponseSessionId(target.GetResponseSessionId()); + context_->SetMode(SyncModeType::RESPONSE_PULL); + context_->ReSetSequenceId(); + context_->SetQuerySync(target.IsQuerySync()); + context_->SetQuery(target.GetQuery()); +} + +bool SingleVerSyncStateMachine::CheckIsStartPullResponse() const +{ + // Other state will step to do pull response, only this statem we need to step the statemachine + if (currentState_ == WAIT_FOR_RECEIVE_DATA_FINISH) { + return true; + } + return false; +} + +int SingleVerSyncStateMachine::MessageCallbackPre(const Message *inMsg) +{ + RefObject::AutoLock lock(context_); + if (context_->IsKilled()) { + return -E_OBJ_IS_KILLED; + } + + if (!IsPacketValid(inMsg)) { + return -E_INVALID_ARGS; + } + return E_OK; +} + +void SingleVerSyncStateMachine::AddPullResponseTarget(const Message *inMsg, WaterMark pullEndWatermark) +{ + int messageType = static_cast(inMsg->GetMessageId()); + uint32_t sessionId = inMsg->GetSessionId(); + if (pullEndWatermark == 0) { + LOGE("[StateMachine][AddPullResponseTarget] pullEndWatermark is 0!"); + return; + } + if (context_->GetResponseSessionId() == sessionId || context_->FindResponseSyncTarget(sessionId)) { + LOGI("[StateMachine][AddPullResponseTarget] task is already running"); + return; + } + const DataRequestPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + LOGE("[AddPullResponseTarget] get packet object failed"); + return; + } + SingleVerSyncTarget *targetTmp = new (std::nothrow) SingleVerSyncTarget; + if (targetTmp == nullptr) { + LOGE("[StateMachine][AddPullResponseTarget] add failed, may oom"); + return; + } + targetTmp->SetTaskType(ISyncTarget::RESPONSE); + if (messageType == QUERY_SYNC_MESSAGE) { + targetTmp->SetQuery(packet->GetQuery()); + targetTmp->SetQuerySync(true); + } + targetTmp->SetMode(SyncModeType::RESPONSE_PULL); + targetTmp->SetEndWaterMark(pullEndWatermark); + targetTmp->SetResponseSessionId(sessionId); + if (context_->AddSyncTarget(targetTmp) != E_OK) { + delete targetTmp; + return; + } + if (CheckIsStartPullResponse()) { + SwitchStateAndStep(TransformErrCodeToEvent(-E_NEED_PULL_REPONSE)); + } +} + +Event SingleVerSyncStateMachine::TransformErrCodeToEvent(int errCode) +{ + switch (errCode) { + case -E_TIMEOUT: + return TransforTimeOutErrCodeToEvent(); + case -VERSION_NOT_SUPPOR_EVENT: + return Event::VERSION_NOT_SUPPOR_EVENT; + case -E_SEND_DATA: + return Event::SEND_DATA_EVENT; + case -E_NO_DATA_SEND: + return Event::SEND_FINISHED_EVENT; + case -E_RECV_FINISHED: + return Event::RECV_FINISHED_EVENT; + case -E_NEED_ABILITY_SYNC: + return Event::NEED_ABILITY_SYNC_EVENT; + case -E_NO_SYNC_TASK: + return Event::ALL_TASK_FINISHED_EVENT; + case -E_NEED_PULL_REPONSE: + return Event::START_PULL_RESPONSE_EVENT; + case -E_RE_SEND_DATA: + return Event::RE_SEND_DATA_EVENT; + default: + return Event::INNER_ERR_EVENT; + } +} + +bool SingleVerSyncStateMachine::IsNeedResetWatchdog(const Message *inMsg) const +{ + if (inMsg == nullptr) { + return false; + } + + if (IsNeedErrCodeHandle(inMsg->GetSessionId())) { + return true; + } + + int msgType = inMsg->GetMessageType(); + if (msgType == TYPE_RESPONSE || msgType == TYPE_NOTIFY) { + if (inMsg->GetSessionId() == context_->GetResponseSessionId()) { + // Pull response ack also should reset watchdog + return true; + } + } + + return false; +} + +Event SingleVerSyncStateMachine::TransforTimeOutErrCodeToEvent() +{ + if (syncContext_->IsSyncTaskNeedRetry() && (syncContext_->GetRetryTime() < syncContext_->GetSyncRetryTimes())) { + return Event::WAIT_TIME_OUT_EVENT; + } else { + return Event::TIME_OUT_EVENT; + } +} + +bool SingleVerSyncStateMachine::IsNeedErrCodeHandle(uint32_t sessionId) const +{ + // omit to set sessionId so version_3 should skip to compare sessionid. + if (sessionId == context_->GetRequestSessionId() || + context_->GetRemoteSoftwareVersion() == SOFTWARE_VERSION_RELEASE_2_0) { + return true; + } + return false; +} + +void SingleVerSyncStateMachine::PushPullDataRequestEvokeErrHandle() +{ + // the pushpull sync task should wait for send finished after remote dev get data occur E_EKEYREVOKED error. + if (context_->GetRemoteSoftwareVersion() > SOFTWARE_VERSION_RELEASE_2_0 && + SyncOperation::TransferSyncMode(context_->GetMode()) == SyncModeType::PUSH_AND_PULL) { + LOGI("data request errCode = %d, wait for send finished", -E_EKEYREVOKED); + context_->SetTaskErrCode(-E_EKEYREVOKED); + context_->SetOperationStatus(SyncOperation::OP_RECV_FINISHED); + SwitchStateAndStep(Event::RECV_FINISHED_EVENT); + } else { + context_->SetTaskErrCode(-E_EKEYREVOKED); + SwitchStateAndStep(Event::INNER_ERR_EVENT); + } +} + +void SingleVerSyncStateMachine::DataRecvErrCodeHandle(uint32_t sessionId, int errCode) +{ + if (IsNeedErrCodeHandle(sessionId)) { + switch (errCode) { + case E_OK: + break; + case -E_RECV_FINISHED: + context_->SetOperationStatus(SyncOperation::OP_RECV_FINISHED); + SwitchStateAndStep(Event::RECV_FINISHED_EVENT); + break; + case -E_EKEYREVOKED: + PushPullDataRequestEvokeErrHandle(); + break; + case -E_BUSY: + case -E_DISTRIBUTED_SCHEMA_CHANGED: + case -E_DISTRIBUTED_SCHEMA_NOT_FOUND: + case -E_FEEDBACK_COMMUNICATOR_NOT_FOUND: + case -E_FEEDBACK_UNKNOWN_MESSAGE: + case -E_INTERCEPT_DATA_FAIL: + case -E_INVALID_QUERY_FIELD: + case -E_INVALID_QUERY_FORMAT: + case -E_MAX_LIMITS: + case -E_NOT_REGISTER: + case -E_NOT_SUPPORT: + case -E_SECURITY_OPTION_CHECK_ERROR: + context_->SetTaskErrCode(errCode); + SwitchStateAndStep(Event::INNER_ERR_EVENT); + break; + default: + SwitchStateAndStep(Event::INNER_ERR_EVENT); + break; + } + } +} + +bool SingleVerSyncStateMachine::AbilityMsgSessionIdCheck(const Message *inMsg) +{ + if (inMsg != nullptr && inMsg->GetSessionId() == context_->GetRequestSessionId()) { + return true; + } + LOGE("[AbilitySync] session check failed,dev=%s", STR_MASK(context_->GetDeviceId())); + return false; +} + +SyncType SingleVerSyncStateMachine::GetSyncType(uint32_t messageId) const +{ + if (messageId == QUERY_SYNC_MESSAGE) { + return SyncType::QUERY_SYNC_TYPE; + } + return SyncType::MANUAL_FULL_SYNC_TYPE; +} + +void SingleVerSyncStateMachine::DataAckRecvErrCodeHandle(int errCode, bool handleError) +{ + switch (errCode) { + case -E_NEED_ABILITY_SYNC: + NeedAbilitySyncHandle(); + break; + case -E_NOT_PERMIT: + if (handleError) { + context_->SetOperationStatus(SyncOperation::OP_PERMISSION_CHECK_FAILED); + } + break; + case -E_BUSY: + case -E_DISTRIBUTED_SCHEMA_CHANGED: + case -E_DISTRIBUTED_SCHEMA_NOT_FOUND: + case -E_EKEYREVOKED: + case -E_FEEDBACK_COMMUNICATOR_NOT_FOUND: + case -E_FEEDBACK_UNKNOWN_MESSAGE: + case -E_INTERCEPT_DATA_FAIL: + case -E_INVALID_QUERY_FIELD: + case -E_INVALID_QUERY_FORMAT: + case -E_MAX_LIMITS: + case -E_NOT_REGISTER: + case -E_NOT_SUPPORT: + case -E_SECURITY_OPTION_CHECK_ERROR: + if (handleError) { + context_->SetTaskErrCode(errCode); + } + break; + default: + break; + } +} + +bool SingleVerSyncStateMachine::IsNeedTriggerQueryAutoSync(Message *inMsg, QuerySyncObject &query) +{ + return SingleVerDataSyncUtils::IsNeedTriggerQueryAutoSync(inMsg, query); +} + +void SingleVerSyncStateMachine::JumpStatusAfterAbilitySync(int mode) +{ + if ((mode == SyncModeType::SUBSCRIBE_QUERY) || (mode == SyncModeType::UNSUBSCRIBE_QUERY)) { + SwitchStateAndStep(CONTROL_CMD_EVENT); + } else { + SwitchStateAndStep(ABILITY_SYNC_FINISHED_EVENT); + } +} + +void SingleVerSyncStateMachine::ControlAckRecvErrCodeHandle(int errCode) +{ + switch (errCode) { + case -E_NEED_ABILITY_SYNC: + NeedAbilitySyncHandle(); + break; + case -E_NO_DATA_SEND: + context_->SetOperationStatus(SyncOperation::OP_SEND_FINISHED); + break; + case -E_NOT_PERMIT: + context_->SetOperationStatus(SyncOperation::OP_PERMISSION_CHECK_FAILED); + break; + // other errCode use default + default: + context_->SetTaskErrCode(errCode); + break; + } +} + +void SingleVerSyncStateMachine::GetLocalWaterMark(const DeviceID &deviceId, uint64_t &outValue) +{ + metadata_->GetLocalWaterMark(deviceId, outValue); +} + +int SingleVerSyncStateMachine::GetSendQueryWaterMark(const std::string &queryId, const DeviceID &deviceId, + bool isAutoLift, uint64_t &outValue) +{ + return metadata_->GetSendQueryWaterMark(queryId, deviceId, outValue, isAutoLift); +} + +void SingleVerSyncStateMachine::ResponsePullError(int errCode, bool ignoreInnerErr) +{ + Event event = TransformErrCodeToEvent(errCode); + SwitchStateAndStep((ignoreInnerErr && event == INNER_ERR_EVENT) ? RESPONSE_TASK_FINISHED_EVENT : event); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/syncer/src/single_ver_sync_state_machine.h b/mock/distributeddb/syncer/src/single_ver_sync_state_machine.h new file mode 100644 index 00000000..c847b5a8 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_sync_state_machine.h @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_SYNC_STATE_MACHINE_H +#define SINGLE_VER_SYNC_STATE_MACHINE_H + +#include +#include + +#include "ability_sync.h" +#include "message.h" +#include "meta_data.h" +#include "semaphore_utils.h" +#include "single_ver_data_sync.h" +#include "single_ver_sync_task_context.h" +#include "sync_state_machine.h" +#include "sync_target.h" + +#include "time_sync.h" +#include "time_helper.h" + +namespace DistributedDB { +using StateMappingHandler = std::function; +class SingleVerSyncStateMachine : public SyncStateMachine { +public: + enum State { + IDLE = 0, + TIME_SYNC, + ABILITY_SYNC, + WAIT_FOR_RECEIVE_DATA_FINISH, // all data send finished, wait for data revice if has pull request + SYNC_TASK_FINISHED, // current sync task finihsed, try to schedule next sync task + SYNC_TIME_OUT, + INNER_ERR, + START_INITIACTIVE_DATA_SYNC, // used to do sync started by local device, use sliding window + START_PASSIVE_DATA_SYNC, // used to do pull response, use sliding window + SYNC_CONTROL_CMD // used to send control cmd. + }; + + enum Event { + START_SYNC_EVENT = 1, + TIME_SYNC_FINISHED_EVENT, + ABILITY_SYNC_FINISHED_EVENT, + VERSION_NOT_SUPPOR_EVENT, + SEND_DATA_EVENT, + SEND_FINISHED_EVENT, + RECV_FINISHED_EVENT, + NEED_ABILITY_SYNC_EVENT, + RESPONSE_TASK_FINISHED_EVENT, + START_PULL_RESPONSE_EVENT, + WAIT_ACK_EVENT, + ALL_TASK_FINISHED_EVENT, + TIME_OUT_EVENT, + INNER_ERR_EVENT, + WAIT_TIME_OUT_EVENT, + RE_SEND_DATA_EVENT, + CONTROL_CMD_EVENT, + ANY_EVENT + }; + SingleVerSyncStateMachine(); + ~SingleVerSyncStateMachine() override; + + // Init the SingleVerSyncStateMachine + int Initialize(ISyncTaskContext *context, ISyncInterface *syncInterface, std::shared_ptr &metadata, + ICommunicator *communicator) override; + + // send Message to the StateMachine + int ReceiveMessageCallback(Message *inMsg) override; + + // Called by CommErrHandler, used to abort sync when handle err + void CommErrAbort() override; + + int HandleDataRequestRecv(const Message *inMsg); + + bool IsNeedErrCodeHandle(uint32_t sessionId) const; + + void PushPullDataRequestEvokeErrHandle(); + + void DataRecvErrCodeHandle(uint32_t sessionId, int errCode); + + bool IsNeedTriggerQueryAutoSync(Message *inMsg, QuerySyncObject &query) override; + + void GetLocalWaterMark(const DeviceID &deviceId, uint64_t &outValue); + + int GetSendQueryWaterMark(const std::string &queryId, const DeviceID &deviceId, bool isAutoLift, + uint64_t &outValue); + +protected: + // Step the SingleVerSyncStateMachine + void SyncStep() override; + + // SyncOperation is timeout, step to timeout state + void StepToTimeout(TimerId timerId) override; + + void SyncStepInnerLocked() override; + + // Do state machine step with no lock, for inner use + void SyncStepInner() override; + + int StartSyncInner() override; + + void AbortInner() override; + + void SetCurStateErrStatus() override; + + // Used to get instance class' stateSwitchTables + const std::vector &GetStateSwitchTables() const override; + + // Do some init for run a next sync task + int PrepareNextSyncTask() override; + + // Called by StartSaveDataNotifyTimer, used to send a save data notify packet + void SendSaveDataNotifyPacket(uint32_t sessionId, uint32_t sequenceId, uint32_t inMsgId) override; + + int TimeMarkSyncRecv(const Message *inMsg); + + void DataAckRecvErrCodeHandle(int errCode, bool handleError); + +private: + // Used to init sync state machine switchbables + static void InitStateSwitchTables(); + + // To generate the statemachine switchtable with the given version + static void InitStateSwitchTable(uint32_t version, const std::vector> &switchTable); + + void InitStateMapping(); + + // Do TimeSync, for first sync + Event DoTimeSync(); + + // Do AbilitySync, for first sync + Event DoAbilitySync(); + + // Waiting for pull data revice finish, if coming a pull request, should goto START_PASSIVE_DATA_SYNC state + Event DoWaitForDataRecv() const; + + // Sync task finihsed, should do some data clear and exec next task. + Event DoSyncTaskFinished(); + + // Do something when sync timeout. + Event DoTimeout(); + + // Do something when sync get some err. + Event DoInnerErr(); + + Event DoInitiactiveDataSyncWithSlidingWindow(); + + Event DoPassiveDataSyncWithSlidingWindow(); + + Event DoInitiactiveControlSync(); + + Event GetEventAfterTimeSync(int mode) const; + + int HandleControlAckRecv(const Message *inMsg); + + int GetSyncOperationStatus(int errCode) const; + + int AbilitySyncRecv(const Message *inMsg); + + int DataPktRecv(Message *inMsg); + + void ScheduleMsgAndHandle(Message *inMsg); + + int ControlPktRecv(Message *inMsg); + + void NeedAbilitySyncHandle(); + + int HandleDataAckRecv(const Message *inMsg); + + void HandleDataAckRecvWithSlidingWindow(int errCode, const Message *inMsg, bool ignoreInnerErr); + + void ResponsePullError(int errCode, bool ignoreInnerErr); + + void Clear(); + + bool IsPacketValid(const Message *inMsg) const; + + void PreStartPullResponse(); + + bool CheckIsStartPullResponse() const; + + int MessageCallbackPre(const Message *inMsg); + + void AddPullResponseTarget(const Message *inMsg, WaterMark pullEndWatermark); + + Event TransformErrCodeToEvent(int errCode); + + bool IsNeedResetWatchdog(const Message *inMsg) const; + + Event TransforTimeOutErrCodeToEvent(); + + bool AbilityMsgSessionIdCheck(const Message *inMsg); + + SyncType GetSyncType(uint32_t messageId) const; + + void JumpStatusAfterAbilitySync(int mode); + + void ControlAckRecvErrCodeHandle(int errCode); + + DISABLE_COPY_ASSIGN_MOVE(SingleVerSyncStateMachine); + + static std::mutex stateSwitchTableLock_; + static bool isStateSwitchTableInited_; + static std::vector stateSwitchTables_; + SingleVerSyncTaskContext *context_; + SingleVerKvDBSyncInterface *syncInterface_; + std::unique_ptr timeSync_; + std::unique_ptr abilitySync_; + std::shared_ptr dataSync_; + uint64_t currentRemoteVersionId_; + std::map stateMapping_; +}; +} // namespace DistributedDB + +#endif // SINGLE_VER_SYNC_STATE_MACHINE_H diff --git a/mock/distributeddb/syncer/src/single_ver_sync_target.cpp b/mock/distributeddb/syncer/src/single_ver_sync_target.cpp new file mode 100644 index 00000000..2771ec16 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_sync_target.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_sync_target.h" + +#include + +#include "db_errno.h" +#include "sync_operation.h" +#include "log_print.h" + +namespace DistributedDB { +SingleVerSyncTarget::SingleVerSyncTarget() + : endWaterMark_(0), + query_(QuerySyncObject()) +{ +} + +SingleVerSyncTarget::~SingleVerSyncTarget() +{ +} + +void SingleVerSyncTarget::SetSyncOperation(SyncOperation *operation) +{ + SyncTarget::SetSyncOperation(operation); + if ((operation != nullptr) && !operation->IsKilled()) { + query_ = operation->GetQuery(); + isQuerySync_ = operation->IsQuerySync(); + } +} + +void SingleVerSyncTarget::SetEndWaterMark(WaterMark waterMark) +{ + endWaterMark_ = waterMark; +} + +WaterMark SingleVerSyncTarget::GetEndWaterMark() const +{ + return endWaterMark_; +} + +void SingleVerSyncTarget::SetResponseSessionId(uint32_t responseSessionId) +{ + responseSessionId_ = responseSessionId; +} + +uint32_t SingleVerSyncTarget::GetResponseSessionId() const +{ + return responseSessionId_; +} + +void SingleVerSyncTarget::SetQuery(const QuerySyncObject &query) +{ + query_ = query; +} + +QuerySyncObject SingleVerSyncTarget::GetQuery() const +{ + return query_; +} + +void SingleVerSyncTarget::SetQuerySync(bool isQuerySync) +{ + isQuerySync_ = isQuerySync; +} + +bool SingleVerSyncTarget::IsQuerySync() const +{ + return isQuerySync_; +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/single_ver_sync_target.h b/mock/distributeddb/syncer/src/single_ver_sync_target.h new file mode 100644 index 00000000..d0c405c6 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_sync_target.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_SYNC_TARGET_H +#define SINGLE_VER_SYNC_TARGET_H + +#include "db_types.h" +#include "query_sync_object.h" +#include "sync_target.h" + +namespace DistributedDB { +class SingleVerSyncTarget final : public SyncTarget { +public: + SingleVerSyncTarget(); + ~SingleVerSyncTarget() override; + + // Set a syncOperation + void SetSyncOperation(SyncOperation *operation) override; + + // Set the end water mark of this task + void SetEndWaterMark(WaterMark waterMark); + + // Get the end water mark of this task + WaterMark GetEndWaterMark() const; + + // For pull response sync + void SetResponseSessionId(uint32_t responseSessionId); + uint32_t GetResponseSessionId() const override; + + // For query sync + void SetQuery(const QuerySyncObject &query); + QuerySyncObject GetQuery() const; + void SetQuerySync(bool isQuerySync); + bool IsQuerySync() const; +private: + WaterMark endWaterMark_; + uint32_t responseSessionId_ = 0; + QuerySyncObject query_; + bool isQuerySync_ = false; +}; +} // namespace DistributedDB + +#endif // SINGLE_VER_SYNC_TARGET_H diff --git a/mock/distributeddb/syncer/src/single_ver_sync_task_context.cpp b/mock/distributeddb/syncer/src/single_ver_sync_task_context.cpp new file mode 100644 index 00000000..a14dd993 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_sync_task_context.cpp @@ -0,0 +1,564 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_sync_task_context.h" + +#include +#include "db_common.h" +#include "db_dfx_adapter.h" +#include "db_errno.h" +#include "log_print.h" +#include "isyncer.h" +#include "single_ver_sync_state_machine.h" +#include "single_ver_sync_target.h" +#include "sync_types.h" + +namespace DistributedDB { +SingleVerSyncTaskContext::SingleVerSyncTaskContext() + : SyncTaskContext(), + token_(nullptr), + endMark_(0), + needClearRemoteStaleData_(false) +{} + +SingleVerSyncTaskContext::~SingleVerSyncTaskContext() +{ + token_ = nullptr; + subManager_ = nullptr; +} + +int SingleVerSyncTaskContext::Initialize(const std::string &deviceId, + ISyncInterface *syncInterface, std::shared_ptr &metadata, ICommunicator *communicator) +{ + if (deviceId.empty() || syncInterface == nullptr || metadata == nullptr || + communicator == nullptr) { + return -E_INVALID_ARGS; + } + stateMachine_ = new (std::nothrow) SingleVerSyncStateMachine; + if (stateMachine_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + deviceId_ = deviceId; + std::vector dbIdentifier = syncInterface->GetIdentifier(); + dbIdentifier.resize(3); // only show 3 bytes + syncActionName_ = DBDfxAdapter::SYNC_ACTION + "_" + + DBCommon::VectorToHexString(dbIdentifier) + "_" + deviceId_.c_str(); + TimerAction timeOutCallback; + int errCode = stateMachine_->Initialize(this, syncInterface, metadata, communicator); + if (errCode != E_OK) { + LOGE("[SingleVerSyncTaskContext] stateMachine Initialize failed, err %d.", errCode); + goto ERROR_OUT; + } + + timeHelper_ = std::make_unique(); + errCode = timeHelper_->Initialize(syncInterface, metadata); + if (errCode != E_OK) { + LOGE("[SingleVerSyncTaskContext] timeHelper Initialize failed, err %d.", errCode); + goto ERROR_OUT; + } + timeOutCallback = std::bind(&SyncStateMachine::TimeoutCallback, + static_cast(stateMachine_), + std::placeholders::_1); + SetTimeoutCallback(timeOutCallback); + + syncInterface_ = syncInterface; + communicator_ = communicator; + taskExecStatus_ = INIT; + OnKill([this]() { this->KillWait(); }); + { + std::lock_guard lock(synTaskContextSetLock_); + synTaskContextSet_.insert(this); + } + return errCode; + +ERROR_OUT: + delete stateMachine_; + stateMachine_ = nullptr; + return errCode; +} + +int SingleVerSyncTaskContext::AddSyncOperation(SyncOperation *operation) +{ + if (operation == nullptr) { + return -E_INVALID_ARGS; + } + + // If auto sync, just update the end watermark + if (operation->IsAutoSync()) { + std::lock_guard lock(targetQueueLock_); + bool isQuerySync = operation->IsQuerySync(); + std::string queryId = operation->GetQueryId(); + auto iter = std::find_if(requestTargetQueue_.begin(), requestTargetQueue_.end(), + [isQuerySync, queryId](const ISyncTarget *target) { + if (target == nullptr) { + return false; + } + if (isQuerySync) { + SyncOperation *tmpOperation = nullptr; + target->GetSyncOperation(tmpOperation); + return (tmpOperation != nullptr && tmpOperation->GetQueryId() == queryId) && target->IsAutoSync(); + } + return target->IsAutoSync(); + }); + if (iter != requestTargetQueue_.end()) { + static_cast(*iter)->SetEndWaterMark(timeHelper_->GetTime()); + operation->SetStatus(deviceId_, SyncOperation::OP_FINISHED_ALL); + return E_OK; + } + } + + auto *newTarget = new (std::nothrow) SingleVerSyncTarget; + if (newTarget == nullptr) { + return -E_OUT_OF_MEMORY; + } + newTarget->SetSyncOperation(operation); + Timestamp timstamp = timeHelper_->GetTime(); + newTarget->SetEndWaterMark(timstamp); + newTarget->SetTaskType(ISyncTarget::REQUEST); + AddSyncTarget(newTarget); + return E_OK; +} + +void SingleVerSyncTaskContext::SetEndMark(WaterMark endMark) +{ + endMark_ = endMark; +} + +WaterMark SingleVerSyncTaskContext::GetEndMark() const +{ + return endMark_; +} + +void SingleVerSyncTaskContext::GetContinueToken(ContinueToken &outToken) const +{ + outToken = token_; +} + +void SingleVerSyncTaskContext::SetContinueToken(ContinueToken token) +{ + token_ = token; + return; +} + +void SingleVerSyncTaskContext::ReleaseContinueToken() +{ + if (token_ != nullptr) { + static_cast(syncInterface_)->ReleaseContinueToken(token_); + token_ = nullptr; + } +} + +int SingleVerSyncTaskContext::PopResponseTarget(SingleVerSyncTarget &target) +{ + std::lock_guard lock(targetQueueLock_); + LOGD("[SingleVerSyncTaskContext] GetFrontExtWaterMarak size = %zu", responseTargetQueue_.size()); + if (!responseTargetQueue_.empty()) { + ISyncTarget *tmpTarget = responseTargetQueue_.front(); + responseTargetQueue_.pop_front(); + target = *(static_cast(tmpTarget)); + delete tmpTarget; + tmpTarget = nullptr; + return E_OK; + } + return -E_LENGTH_ERROR; +} + +int SingleVerSyncTaskContext::GetRspTargetQueueSize() const +{ + std::lock_guard lock(targetQueueLock_); + return responseTargetQueue_.size(); +} + +void SingleVerSyncTaskContext::SetResponseSessionId(uint32_t responseSessionId) +{ + responseSessionId_ = responseSessionId; +} + +uint32_t SingleVerSyncTaskContext::GetResponseSessionId() const +{ + return responseSessionId_; +} + +void SingleVerSyncTaskContext::CopyTargetData(const ISyncTarget *target, const TaskParam &taskParam) +{ + const SingleVerSyncTarget *targetTmp = static_cast(target); + SyncTaskContext::CopyTargetData(target, taskParam); + mode_ = targetTmp->GetMode(); + endMark_ = targetTmp->GetEndWaterMark(); + if (mode_ == SyncModeType::RESPONSE_PULL) { + responseSessionId_ = targetTmp->GetResponseSessionId(); + } + query_ = targetTmp->GetQuery(); + isQuerySync_ = targetTmp->IsQuerySync(); +} + +void SingleVerSyncTaskContext::Clear() +{ + retryTime_ = 0; + ClearSyncOperation(); + SyncTaskContext::Clear(); + SetMode(SyncModeType::INVALID_MODE); + syncId_ = 0; + isAutoSync_ = false; + SetOperationStatus(SyncOperation::OP_WAITING); + SetEndMark(0); + SetResponseSessionId(0); + query_ = QuerySyncObject(); + isQuerySync_ = false; +} + +void SingleVerSyncTaskContext::Abort(int status) +{ + { + std::lock_guard lock(operationLock_); + if (syncOperation_ != nullptr) { + syncOperation_->SetStatus(deviceId_, status); + if ((status >= SyncOperation::OP_FINISHED_ALL)) { + UnlockObj(); + if (syncOperation_->CheckIsAllFinished()) { + syncOperation_->Finished(); + } + LockObj(); + } + } + } + StopFeedDogForSync(SyncDirectionFlag::SEND); + StopFeedDogForSync(SyncDirectionFlag::RECEIVE); + Clear(); +} + +void SingleVerSyncTaskContext::ClearAllSyncTask() +{ + // clear request queue sync task and responsequeue first. + std::list targetQueue; + { + std::lock_guard lock(targetQueueLock_); + LOGI("[SingleVerSyncTaskContext] request taskcount=%zu, responsecount=%zu", requestTargetQueue_.size(), + responseTargetQueue_.size()); + while (!requestTargetQueue_.empty()) { + ISyncTarget *tmpTarget = nullptr; + tmpTarget = requestTargetQueue_.front(); + requestTargetQueue_.pop_front(); + targetQueue.push_back(tmpTarget); + } + while (!responseTargetQueue_.empty()) { + ISyncTarget *tmpTarget = nullptr; + tmpTarget = responseTargetQueue_.front(); + responseTargetQueue_.pop_front(); + delete tmpTarget; + tmpTarget = nullptr; + } + } + while (!targetQueue.empty()) { + ISyncTarget *target = nullptr; + target = targetQueue.front(); + targetQueue.pop_front(); + SyncOperation *tmpOperation = nullptr; + target->GetSyncOperation(tmpOperation); + if (tmpOperation == nullptr) { + LOGE("[ClearAllSyncTask] tmpOperation is nullptr"); + continue; // not exit this scene + } + LOGI("[SingleVerSyncTaskContext] killing syncId=%d,dev=%s", tmpOperation->GetSyncId(), STR_MASK(deviceId_)); + if (target->IsAutoSync()) { + tmpOperation->SetStatus(deviceId_, SyncOperation::OP_FINISHED_ALL); + } else { + tmpOperation->SetStatus(deviceId_, SyncOperation::OP_COMM_ABNORMAL); + } + if (tmpOperation->CheckIsAllFinished()) { + tmpOperation->Finished(); + } + delete target; + target = nullptr; + } + if (GetTaskExecStatus() == SyncTaskContext::RUNNING) { + // clear syncing task. + isCommNormal_ = false; + stateMachine_->CommErrAbort(); + } + // reset last push status for sync merge + ResetLastPushTaskStatus(); +} + +void SingleVerSyncTaskContext::EnableClearRemoteStaleData(bool enable) +{ + needClearRemoteStaleData_ = enable; +} + +bool SingleVerSyncTaskContext::IsNeedClearRemoteStaleData() const +{ + return needClearRemoteStaleData_; +} + +bool SingleVerSyncTaskContext::StartFeedDogForSync(uint32_t time, SyncDirectionFlag flag) +{ + return stateMachine_->StartFeedDogForSync(time, flag); +} + +void SingleVerSyncTaskContext::StopFeedDogForSync(SyncDirectionFlag flag) +{ + stateMachine_->StopFeedDogForSync(flag); +} + +int SingleVerSyncTaskContext::HandleDataRequestRecv(const Message *msg) +{ + return static_cast(stateMachine_)->HandleDataRequestRecv(msg); +} + +bool SingleVerSyncTaskContext::IsReceiveWaterMarkErr() const +{ + return isReceiveWaterMarkErr_; +} + +void SingleVerSyncTaskContext::SetReceiveWaterMarkErr(bool isErr) +{ + isReceiveWaterMarkErr_ = isErr; +} + +void SingleVerSyncTaskContext::SetRemoteSeccurityOption(SecurityOption secOption) +{ + remoteSecOption_ = secOption; +} + +SecurityOption SingleVerSyncTaskContext::GetRemoteSeccurityOption() const +{ + return remoteSecOption_; +} + +void SingleVerSyncTaskContext::SetReceivcPermitCheck(bool isChecked) +{ + isReceivcPermitChecked_ = isChecked; +} + +bool SingleVerSyncTaskContext::GetReceivcPermitCheck() const +{ + return isReceivcPermitChecked_; +} + +void SingleVerSyncTaskContext::SetSendPermitCheck(bool isChecked) +{ + isSendPermitChecked_ = isChecked; +} + +bool SingleVerSyncTaskContext::GetSendPermitCheck() const +{ + return isSendPermitChecked_; +} + +void SingleVerSyncTaskContext::SetIsSchemaSync(bool isSchemaSync) +{ + isSchemaSync_ = isSchemaSync; +} + +bool SingleVerSyncTaskContext::GetIsSchemaSync() const +{ + return isSchemaSync_; +} + +bool SingleVerSyncTaskContext::IsSkipTimeoutError(int errCode) const +{ + if (errCode == -E_TIMEOUT && IsSyncTaskNeedRetry() && (GetRetryTime() < GetSyncRetryTimes())) { + LOGE("[SingleVerSyncTaskContext] send message timeout error occurred"); + return true; + } else { + return false; + } +} + +bool SingleVerSyncTaskContext::FindResponseSyncTarget(uint32_t responseSessionId) const +{ + std::lock_guard lock(targetQueueLock_); + auto iter = std::find_if(responseTargetQueue_.begin(), responseTargetQueue_.end(), + [responseSessionId](const ISyncTarget *target) { + return target->GetResponseSessionId() == responseSessionId; + }); + if (iter == responseTargetQueue_.end()) { + return false; + } + return true; +} + +void SingleVerSyncTaskContext::SetQuery(const QuerySyncObject &query) +{ + query_ = query; +} + +const QuerySyncObject &SingleVerSyncTaskContext::GetQuery() const +{ + return query_; +} + +void SingleVerSyncTaskContext::SetQuerySync(bool isQuerySync) +{ + isQuerySync_ = isQuerySync; +} + +bool SingleVerSyncTaskContext::IsQuerySync() const +{ + return isQuerySync_; +} + +std::set SingleVerSyncTaskContext::GetRemoteCompressAlgo() const +{ + std::set compressAlgoSet; + for (const auto &algo : SyncConfig::COMPRESSALGOMAP) { + if (remoteDbAbility_.GetAbilityItem(algo.second) == SUPPORT_MARK) { + compressAlgoSet.insert(static_cast(algo.first)); + } + } + return compressAlgoSet; +} + +std::string SingleVerSyncTaskContext::GetRemoteCompressAlgoStr() const +{ + static std::map algoMap = {{CompressAlgorithm::ZLIB, "zlib"}}; + std::set remoteCompressAlgoSet = GetRemoteCompressAlgo(); + if (remoteCompressAlgoSet.size() == 0) { + return "none"; + } + std::string currentAlgoStr; + for (const auto &algo : remoteCompressAlgoSet) { + auto iter = algoMap.find(algo); + if (iter != algoMap.end()) { + currentAlgoStr += algoMap[algo] + ","; + } + } + return currentAlgoStr.substr(0, currentAlgoStr.length() - 1); +} + +void SingleVerSyncTaskContext::SetDbAbility(DbAbility &remoteDbAbility) +{ + remoteDbAbility_ = remoteDbAbility; + LOGI("[SingleVerSyncTaskContext] set dev=%s compressAlgo=%s, IsSupAllPredicateQuery=%u," + "IsSupSubscribeQuery=%u, inKeys=%u", + STR_MASK(GetDeviceId()), GetRemoteCompressAlgoStr().c_str(), + remoteDbAbility.GetAbilityItem(SyncConfig::ALLPREDICATEQUERY), + remoteDbAbility.GetAbilityItem(SyncConfig::SUBSCRIBEQUERY), + remoteDbAbility.GetAbilityItem(SyncConfig::INKEYS_QUERY)); +} + +CompressAlgorithm SingleVerSyncTaskContext::ChooseCompressAlgo() const +{ + std::set remoteAlgo = GetRemoteCompressAlgo(); + if (remoteAlgo.size() == 0) { + return CompressAlgorithm::NONE; + } + std::set localAlgorithmSet; + (void)(static_cast(syncInterface_))->GetCompressionAlgo(localAlgorithmSet); + std::set algoIntersection; + set_intersection(remoteAlgo.begin(), remoteAlgo.end(), localAlgorithmSet.begin(), localAlgorithmSet.end(), + inserter(algoIntersection, algoIntersection.begin())); + if (algoIntersection.size() == 0) { + return CompressAlgorithm::NONE; + } + return *(algoIntersection.begin()); +} + +const DbAbility& SingleVerSyncTaskContext::GetRemoteDbAbility() const +{ + return remoteDbAbility_; +} + +void SingleVerSyncTaskContext::SetSubscribeManager(std::shared_ptr &subManager) +{ + subManager_ = subManager; +} + +std::shared_ptr SingleVerSyncTaskContext::GetSubscribeManager() const +{ + return subManager_; +} +DEFINE_OBJECT_TAG_FACILITIES(SingleVerSyncTaskContext) + +bool SingleVerSyncTaskContext::IsCurrentSyncTaskCanBeSkipped() const +{ + if (mode_ == SyncModeType::PUSH) { + if (lastFullSyncTaskStatus_ != SyncOperation::OP_FINISHED_ALL) { + return false; + } + } else if (mode_ == SyncModeType::QUERY_PUSH) { + if (syncOperation_ == nullptr) { + return true; + } + auto it = lastQuerySyncTaskStatusMap_.find(syncOperation_->GetQueryId()); + if (it == lastQuerySyncTaskStatusMap_.end()) { + // no last query_push and push + if (lastFullSyncTaskStatus_ != SyncOperation::OP_FINISHED_ALL) { + LOGD("no prev query push or successful prev push"); + return false; + } + } else { + if (it->second != SyncOperation::OP_FINISHED_ALL) { + LOGD("last query push status = %d.", it->second); + return false; + } + } + } else { + return false; + } + + Timestamp maxTimestampInDb; + syncInterface_->GetMaxTimestamp(maxTimestampInDb); + uint64_t localWaterMark = 0; + int errCode = GetCorrectedSendWaterMarkForCurrentTask(localWaterMark); + if (errCode != E_OK) { + LOGE("GetLocalWaterMark in state machine failed: %d", errCode); + return false; + } + if (localWaterMark > maxTimestampInDb) { + LOGI("skip current push task, deviceId_ = %s, localWaterMark = %" PRIu64 ", maxTimestampInDb = %" PRIu64, + STR_MASK(deviceId_), localWaterMark, maxTimestampInDb); + return true; + } + return false; +} + +void SingleVerSyncTaskContext::SaveLastPushTaskExecStatus(int finalStatus) +{ + if (IsTargetQueueEmpty()) { + LOGD("sync que is empty, reset last push status"); + ResetLastPushTaskStatus(); + return; + } + if (mode_ == SyncModeType::PUSH || mode_ == SyncModeType::PUSH_AND_PULL || mode_ == SyncModeType::RESPONSE_PULL) { + lastFullSyncTaskStatus_ = finalStatus; + } else if (mode_ == SyncModeType::QUERY_PUSH || mode_ == SyncModeType::QUERY_PUSH_PULL) { + lastQuerySyncTaskStatusMap_[syncOperation_->GetQueryId()] = finalStatus; + } +} + +int SingleVerSyncTaskContext::GetCorrectedSendWaterMarkForCurrentTask(uint64_t &waterMark) const +{ + if (syncOperation_->IsQuerySync()) { + LOGD("Is QuerySync"); + int errCode = static_cast(stateMachine_)->GetSendQueryWaterMark( + syncOperation_->GetQueryId(), deviceId_, + lastFullSyncTaskStatus_ == SyncOperation::OP_FINISHED_ALL, waterMark); + if (errCode != E_OK) { + return errCode; + } + } else { + LOGD("Not QuerySync"); + static_cast(stateMachine_)->GetLocalWaterMark(deviceId_, waterMark); + } + return E_OK; +} + +void SingleVerSyncTaskContext::ResetLastPushTaskStatus() +{ + lastFullSyncTaskStatus_ = SyncOperation::OP_WAITING; + lastQuerySyncTaskStatusMap_.clear(); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/syncer/src/single_ver_sync_task_context.h b/mock/distributeddb/syncer/src/single_ver_sync_task_context.h new file mode 100644 index 00000000..f0619a41 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_sync_task_context.h @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_SYNC_TASK_CONTEXT_H +#define SINGLE_VER_SYNC_TASK_CONTEXT_H + +#include +#include +#include +#include + +#include "db_ability.h" +#include "query_sync_object.h" +#include "schema_negotiate.h" +#include "single_ver_kvdb_sync_interface.h" +#include "single_ver_sync_target.h" +#include "subscribe_manager.h" +#include "sync_target.h" +#include "sync_task_context.h" +#include "time_helper.h" + + +namespace DistributedDB { +class SingleVerSyncTaskContext : public SyncTaskContext { +public: + + explicit SingleVerSyncTaskContext(); + + DISABLE_COPY_ASSIGN_MOVE(SingleVerSyncTaskContext); + + // Init SingleVerSyncTaskContext + int Initialize(const std::string &deviceId, ISyncInterface *syncInterface, std::shared_ptr &metadata, + ICommunicator *communicator) override; + + // Add a sync task target with the operation to the queue + int AddSyncOperation(SyncOperation *operation) override; + + bool IsCurrentSyncTaskCanBeSkipped() const override; + + // Set the end water mark of this task + void SetEndMark(WaterMark endMark); + + // Get the end water mark of this task + WaterMark GetEndMark() const; + + void GetContinueToken(ContinueToken &outToken) const; + + void SetContinueToken(ContinueToken token); + + void ReleaseContinueToken(); + + int PopResponseTarget(SingleVerSyncTarget &target); + + int GetRspTargetQueueSize() const; + + // responseSessionId used for mark the pull response task + void SetResponseSessionId(uint32_t responseSessionId); + + // responseSessionId used for mark the pull response task + uint32_t GetResponseSessionId() const; + + void Clear() override; + + void Abort(int status) override; + + void ClearAllSyncTask() override; + + // If set true, remote stale data will be clear when remote db rebuiled. + void EnableClearRemoteStaleData(bool enable); + + // Check if need to clear remote device stale data in syncing, when the remote db rebuilt. + bool IsNeedClearRemoteStaleData() const; + + // start a timer to ResetWatchDog when sync data one (key,value) size bigger than mtu + bool StartFeedDogForSync(uint32_t time, SyncDirectionFlag flag); + + // stop timer to ResetWatchDog when sync data one (key,value) size bigger than mtu + void StopFeedDogForSync(SyncDirectionFlag flag); + + virtual int HandleDataRequestRecv(const Message *msg); + + // is receive warterMark err + bool IsReceiveWaterMarkErr() const; + + // set receive warterMark err + void SetReceiveWaterMarkErr(bool isErr); + + void SetRemoteSeccurityOption(SecurityOption secOption); + + SecurityOption GetRemoteSeccurityOption() const; + + void SetReceivcPermitCheck(bool isChecked); + + bool GetReceivcPermitCheck() const; + + void SetSendPermitCheck(bool isChecked); + + bool GetSendPermitCheck() const; + + virtual SyncStrategy GetSyncStrategy(QuerySyncObject &querySyncObject) const = 0; + + void SetIsSchemaSync(bool isSchemaSync); + + bool GetIsSchemaSync() const; + + bool IsSkipTimeoutError(int errCode) const; + + bool FindResponseSyncTarget(uint32_t responseSessionId) const; + + // For query sync + void SetQuery(const QuerySyncObject &query); + const QuerySyncObject &GetQuery() const; + void SetQuerySync(bool isQuerySync); + bool IsQuerySync() const; + std::set GetRemoteCompressAlgo() const; + std::string GetRemoteCompressAlgoStr() const; + void SetDbAbility(DbAbility &remoteDbAbility); + CompressAlgorithm ChooseCompressAlgo() const; + const DbAbility& GetRemoteDbAbility() const; + + void SetSubscribeManager(std::shared_ptr &subManager); + std::shared_ptr GetSubscribeManager() const; + + void SaveLastPushTaskExecStatus(int finalStatus) override; + void ResetLastPushTaskStatus() override; + + virtual std::string GetQuerySyncId() const = 0; + virtual std::string GetDeleteSyncId() const = 0; +protected: + ~SingleVerSyncTaskContext() override; + void CopyTargetData(const ISyncTarget *target, const TaskParam &taskParam) override; + + // For querySync + QuerySyncObject query_; + bool isQuerySync_ = false; +private: + int GetCorrectedSendWaterMarkForCurrentTask(uint64_t &waterMark) const; + + constexpr static int64_t REDUNDACE_WATER_MARK = 1 * 1000LL * 1000LL * 10LL; // 1s + + DECLARE_OBJECT_TAG(SingleVerSyncTaskContext); + + ContinueToken token_; + WaterMark endMark_; + uint32_t responseSessionId_ = 0; + + bool needClearRemoteStaleData_; + SecurityOption remoteSecOption_ = {0, 0}; // remote targe can handle secOption data or not. + bool isReceivcPermitChecked_ = false; + bool isSendPermitChecked_ = false; + std::atomic isSchemaSync_ = false; + + // is receive waterMark err, peerWaterMark bigger than remote localWaterMark + bool isReceiveWaterMarkErr_ = false; + + // For db ability + DbAbility remoteDbAbility_; + + // For subscribe manager + std::shared_ptr subManager_; + + // for merge sync task + int lastFullSyncTaskStatus_ = SyncOperation::Status::OP_WAITING; + // + std::unordered_map lastQuerySyncTaskStatusMap_; +}; +} // namespace DistributedDB + +#endif // SYNC_TASK_CONTEXT_H diff --git a/mock/distributeddb/syncer/src/single_ver_syncer.cpp b/mock/distributeddb/syncer/src/single_ver_syncer.cpp new file mode 100644 index 00000000..c923405f --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_syncer.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "single_ver_syncer.h" + +#include "db_common.h" +#include "single_ver_sync_engine.h" + +namespace DistributedDB { +void SingleVerSyncer::RemoteDataChanged(const std::string &device) +{ + LOGI("[SingleVerSyncer] device online dev %s", STR_MASK(device)); + // while remote db is online again, need to do abilitySync + static_cast(syncEngine_)->SetIsNeedResetAbilitySync(device, true); +} + +void SingleVerSyncer::RemoteDeviceOffline(const std::string &device) +{ + LOGI("[SingleVerRelationalSyncer] device offline dev %s", STR_MASK(device)); + std::string userId = syncInterface_->GetDbProperties().GetStringProp(KvDBProperties::USER_ID, ""); + std::string appId = syncInterface_->GetDbProperties().GetStringProp(KvDBProperties::APP_ID, ""); + std::string storeId = syncInterface_->GetDbProperties().GetStringProp(KvDBProperties::STORE_ID, ""); + RuntimeContext::GetInstance()->NotifyDatabaseStatusChange(userId, appId, storeId, device, false); + RefObject::IncObjRef(syncEngine_); + static_cast(syncEngine_)->OfflineHandleByDevice(device); + RefObject::DecObjRef(syncEngine_); +} + +int SingleVerSyncer::EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash) +{ + return EraseDeviceWaterMark(deviceId, isNeedHash, ""); +} + +int SingleVerSyncer::EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash, + const std::string &tableName) +{ + if (metadata_ == nullptr) { + return -E_NOT_INIT; + } + return metadata_->EraseDeviceWaterMark(deviceId, isNeedHash, tableName); +} + +int SingleVerSyncer::SetStaleDataWipePolicy(WipePolicy policy) +{ + std::lock_guard lock(syncerLock_); + if (closing_) { + LOGE("[Syncer] Syncer is closing, return!"); + return -E_BUSY; + } + if (syncEngine_ == nullptr) { + return -E_NOT_INIT; + } + int errCode = E_OK; + switch (policy) { + case RETAIN_STALE_DATA: + static_cast(syncEngine_)->EnableClearRemoteStaleData(false); + break; + case WIPE_STALE_DATA: + static_cast(syncEngine_)->EnableClearRemoteStaleData(true); + break; + default: + errCode = -E_NOT_SUPPORT; + break; + } + return errCode; +} + +ISyncEngine *SingleVerSyncer::CreateSyncEngine() +{ + return new (std::nothrow) SingleVerSyncEngine(); +} +} diff --git a/mock/distributeddb/syncer/src/single_ver_syncer.h b/mock/distributeddb/syncer/src/single_ver_syncer.h new file mode 100644 index 00000000..9dc5e0e1 --- /dev/null +++ b/mock/distributeddb/syncer/src/single_ver_syncer.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNCER_H +#define SYNCER_H + +#include "generic_syncer.h" + +namespace DistributedDB { +class SingleVerSyncer : public GenericSyncer { +public: + void RemoteDataChanged(const std::string &device) override; + + void RemoteDeviceOffline(const std::string &device) override; + + // Set stale data wipe policy + int SetStaleDataWipePolicy(WipePolicy policy) override; + + // delete specified device's watermark + int EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash) override; + + // delete specified device's and table's watermark + int EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash, + const std::string &tableName) override; + +protected: + // Create a sync engine, if has memory error, will return nullptr. + ISyncEngine *CreateSyncEngine() override; +}; +} +#endif // SYNCER_H diff --git a/mock/distributeddb/syncer/src/subscribe_manager.cpp b/mock/distributeddb/syncer/src/subscribe_manager.cpp new file mode 100644 index 00000000..572cd915 --- /dev/null +++ b/mock/distributeddb/syncer/src/subscribe_manager.cpp @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "subscribe_manager.h" +#include "db_common.h" +#include "sync_types.h" + +namespace DistributedDB { +void SubscribeManager::ClearRemoteSubscribeQuery(const std::string &device) +{ + std::unique_lock lockGuard(remoteSubscribedMapLock_); + ClearSubscribeQuery(device, remoteSubscribedMap_, remoteSubscribedTotalMap_); +} + +void SubscribeManager::ClearAllRemoteQuery() +{ + std::unique_lock lockGuard(remoteSubscribedMapLock_); + remoteSubscribedMap_.clear(); + remoteSubscribedTotalMap_.clear(); +} + +void SubscribeManager::ClearLocalSubscribeQuery(const std::string &device) +{ + std::unique_lock lockGuard(localSubscribeMapLock_); + unFinishedLocalAutoSubMap_.erase(device); + ClearSubscribeQuery(device, localSubscribeMap_, localSubscribeTotalMap_); +} + +int SubscribeManager::ReserveRemoteSubscribeQuery(const std::string &device, const QuerySyncObject &query) +{ + std::unique_lock lockGuard(remoteSubscribedMapLock_); + int errCode = ReserveSubscribeQuery(device, query, remoteSubscribedMap_, remoteSubscribedTotalMap_); + LOGI("[SubscribeManager] dev=%s,queryId=%s remote reserve err=%d", STR_MASK(device), STR_MASK(query.GetIdentify()), + errCode); + return errCode; +} + +int SubscribeManager::ActiveRemoteSubscribeQuery(const std::string &device, const QuerySyncObject &query) +{ + std::unique_lock lockGuard(remoteSubscribedMapLock_); + std::string queryId = query.GetIdentify(); + int errCode = ActiveSubscribeQuery(device, queryId, remoteSubscribedMap_, remoteSubscribedTotalMap_); + LOGI("[SubscribeManager] dev=%s,queryId=%s remote active err=%d", STR_MASK(device), STR_MASK(queryId), errCode); + return errCode; +} + +int SubscribeManager::ReserveLocalSubscribeQuery(const std::string &device, const QuerySyncObject &query) +{ + std::unique_lock lockGuard(localSubscribeMapLock_); + int errCode = ReserveSubscribeQuery(device, query, localSubscribeMap_, localSubscribeTotalMap_); + LOGI("[SubscribeManager] dev=%s,queryId=%s local reserve err=%d", STR_MASK(device), STR_MASK(query.GetIdentify()), + errCode); + return errCode; +} + +int SubscribeManager::ActiveLocalSubscribeQuery(const std::string &device, const QuerySyncObject &query) +{ + std::unique_lock lockGuard(localSubscribeMapLock_); + std::string queryId = query.GetIdentify(); + int errCode = ActiveSubscribeQuery(device, queryId, localSubscribeMap_, localSubscribeTotalMap_); + LOGI("[SubscribeManager] dev=%s,queryId=%s local active err=%d", STR_MASK(device), STR_MASK(queryId), errCode); + if (errCode != E_OK) { + return errCode; + } + if (unFinishedLocalAutoSubMap_.find(device) != unFinishedLocalAutoSubMap_.end() && + unFinishedLocalAutoSubMap_[device].find(queryId) != unFinishedLocalAutoSubMap_[device].end()) { + unFinishedLocalAutoSubMap_[device].erase(queryId); + } + return errCode; +} + +void SubscribeManager::DeleteLocalSubscribeQuery(const std::string &device, const QuerySyncObject &query) +{ + std::unique_lock lockGuard(localSubscribeMapLock_); + std::string queryId = query.GetIdentify(); + DeleteSubscribeQuery(device, queryId, localSubscribeMap_, localSubscribeTotalMap_); +} + +void SubscribeManager::DeleteRemoteSubscribeQuery(const std::string &device, const QuerySyncObject &query) +{ + std::unique_lock lockGuard(remoteSubscribedMapLock_); + std::string queryId = query.GetIdentify(); + DeleteSubscribeQuery(device, queryId, remoteSubscribedMap_, remoteSubscribedTotalMap_); +} + +void SubscribeManager::PutLocalUnFiniedSubQueries(const std::string &device, + std::vector &subscribeQueries) +{ + LOGI("[SubscribeManager] put local unfinished subscribe queries, nums=%zu", subscribeQueries.size()); + std::unique_lock lockGuard(localSubscribeMapLock_); + if (subscribeQueries.size() == 0) { + unFinishedLocalAutoSubMap_.erase(device); + return; + } + unFinishedLocalAutoSubMap_[device].clear(); + auto iter = unFinishedLocalAutoSubMap_.find(device); + for (const auto &query : subscribeQueries) { + iter->second.insert(query.GetIdentify()); + } +} + +void SubscribeManager::GetAllUnFinishSubQueries( + std::map> &allSyncQueries) const +{ + std::shared_lock lock(localSubscribeMapLock_); + for (auto &item : unFinishedLocalAutoSubMap_) { + if (item.second.size() == 0) { + continue; + } + allSyncQueries[item.first] = {}; + auto iter = allSyncQueries.find(item.first); + for (const auto &queryId : item.second) { + auto iterTmp = localSubscribeTotalMap_.find(queryId); + if (iterTmp == localSubscribeTotalMap_.end()) { + LOGI("[SubscribeManager] queryId=%s not in localTotalMap", STR_MASK(queryId)); + continue; + } + iter->second.push_back(iterTmp->second.first); + } + } +} + +void SubscribeManager::RemoveRemoteSubscribeQuery(const std::string &device, const QuerySyncObject &query) +{ + std::unique_lock lockGuard(remoteSubscribedMapLock_); + std::string queryId = query.GetIdentify(); + RemoveSubscribeQuery(device, queryId, remoteSubscribedMap_, remoteSubscribedTotalMap_); +} + +void SubscribeManager::RemoveLocalSubscribeQuery(const std::string &device, const QuerySyncObject &query) +{ + std::unique_lock lockGuard(localSubscribeMapLock_); + std::string queryId = query.GetIdentify(); + RemoveSubscribeQuery(device, queryId, localSubscribeMap_, localSubscribeTotalMap_); + if (unFinishedLocalAutoSubMap_.find(device) != unFinishedLocalAutoSubMap_.end() && + unFinishedLocalAutoSubMap_[device].find(queryId) != unFinishedLocalAutoSubMap_[device].end()) { + unFinishedLocalAutoSubMap_[device].erase(queryId); + LOGI("[SubscribeManager] dev=%s,queryId=%s delete from UnFinishedMap", STR_MASK(device), STR_MASK(queryId)); + if (unFinishedLocalAutoSubMap_[device].size() == 0) { + LOGI("[SubscribeManager] dev=%s delete from unFinish map", STR_MASK(device)); + unFinishedLocalAutoSubMap_.erase(device); + } + } +} + +void SubscribeManager::GetLocalSubscribeQueries(const std::string &device, + std::vector &subscribeQueries) const +{ + std::shared_lock lock(localSubscribeMapLock_); + GetSubscribeQueries(device, localSubscribeMap_, localSubscribeTotalMap_, subscribeQueries); +} + +void SubscribeManager::GetRemoteSubscribeQueries(const std::string &device, + std::vector &subscribeQueries) const +{ + std::shared_lock lockGuard(remoteSubscribedMapLock_); + GetSubscribeQueries(device, remoteSubscribedMap_, remoteSubscribedTotalMap_, subscribeQueries); +} + +bool SubscribeManager::IsRemoteContainSubscribe(const std::string &device, const QuerySyncObject &query) const +{ + std::shared_lock lockGuard(remoteSubscribedMapLock_); + auto iter = remoteSubscribedMap_.find(device); + if (iter == remoteSubscribedMap_.end()) { + LOGD("[SubscribeManager] dev=%s not in remoteSubscribedMap", STR_MASK(device)); + return false; + } + std::string queryId = query.GetIdentify(); + auto subIter = iter->second.find(queryId); + if (subIter == iter->second.end()) { + LOGE("[SubscribeManager] queryId=%s not in RemoteTotalMap", STR_MASK(queryId)); + return false; + } + return true; +} + +void SubscribeManager::GetRemoteSubscribeQueryIds(const std::string &device, + std::vector &subscribeQueryIds) const +{ + std::shared_lock lockGuard(remoteSubscribedMapLock_); + auto iter = remoteSubscribedMap_.find(device); + if (iter == remoteSubscribedMap_.end()) { + LOGI("[SubscribeManager] dev=%s not in remoteSubscribedMap", STR_MASK(device)); + return; + } + for (const auto &queryInfo : iter->second) { + if (remoteSubscribedTotalMap_.find(queryInfo.first) == remoteSubscribedTotalMap_.end()) { + LOGE("[SubscribeManager] queryId=%s not in RemoteTotalMap", STR_MASK(queryInfo.first)); + continue; + } + subscribeQueryIds.push_back(queryInfo.first); + } +} + +int SubscribeManager::LocalSubscribeLimitCheck(const std::vector &devices, QuerySyncObject &query) const +{ + std::shared_lock lock(localSubscribeMapLock_); + size_t devNum = localSubscribeMap_.size(); + for (const auto &device : devices) { + if (localSubscribeMap_.find(device) != localSubscribeMap_.end()) { + continue; + } + devNum++; + if (devNum > MAX_DEVICES_NUM) { + LOGE("[SubscribeManager] local subscribe devices is over limit"); + return -E_MAX_LIMITS; + } + } + std::string queryId = query.GetIdentify(); + auto allIter = localSubscribeTotalMap_.find(queryId); + if (allIter == localSubscribeTotalMap_.end() && localSubscribeTotalMap_.size() >= MAX_SUBSCRIBE_NUM_PER_DB) { + LOGE("[SubscribeManager] all local subscribe sums is over limit"); + return -E_MAX_LIMITS; + } + return E_OK; +} + +void SubscribeManager::ClearSubscribeQuery(const std::string &device, SubscribeMap &subscribeMap, + SubscribedTotalMap &subscribedTotalMap) +{ + if (subscribeMap.find(device) == subscribeMap.end()) { + LOGI("[SubscribeManager] dev=%s not in SubscribedMap", STR_MASK(device)); + return; + } + for (const auto &queryInfo : subscribeMap[device]) { + if (subscribedTotalMap.find(queryInfo.first) != subscribedTotalMap.end()) { + if (subscribedTotalMap[queryInfo.first].second > 0) { + subscribedTotalMap[queryInfo.first].second--; + } + if (subscribedTotalMap[queryInfo.first].second == 0) { + LOGI("[SubscribeManager] queryId=%s delete from TotalMap", STR_MASK(queryInfo.first)); + subscribedTotalMap.erase(queryInfo.first); + } + } + } + subscribeMap.erase(device); + LOGI("[SubscribeManager] clear dev=%s remote subscribe queies finished", STR_MASK(device)); +} + +int SubscribeManager::ReserveSubscribeQuery(const std::string &device, const QuerySyncObject &query, + SubscribeMap &subscribeMap, SubscribedTotalMap &subscribedTotalMap) +{ + std::string queryId = query.GetIdentify(); + auto iter = subscribeMap.find(device); + auto allIter = subscribedTotalMap.find(queryId); + // limit check + if (allIter == subscribedTotalMap.end() && subscribedTotalMap.size() >= MAX_SUBSCRIBE_NUM_PER_DB) { + LOGE("[SubscribeManager] all subscribe sums is over limit"); + return -E_MAX_LIMITS; + } + if (iter == subscribeMap.end() && subscribeMap.size() >= MAX_DEVICES_NUM) { + LOGE("[SubscribeManager] subscribe devices is over limit"); + return -E_MAX_LIMITS; + } + if (iter != subscribeMap.end() && iter->second.find(queryId) == iter->second.end() && + iter->second.size() >= MAX_SUBSCRIBE_NUM_PER_DEV) { + LOGE("[SubscribeManager] subscribe sums is over limit"); + return -E_MAX_LIMITS; + } + if (iter != subscribeMap.end() && iter->second.find(queryId) != iter->second.end() && + iter->second[queryId] == SubscribeStatus::ACTIVE) { + LOGE("[SubscribeManager] dev=%s,queryId=%s already active in map", STR_MASK(device), STR_MASK(queryId)); + return E_OK; + } + + if (iter == subscribeMap.end()) { + subscribeMap[device] = std::map {}; + } + bool isNeedInc = false; + if (subscribeMap[device].find(queryId) == subscribeMap[device].end()) { + subscribeMap[device][queryId] = SubscribeStatus::NOT_ACTIVE; + isNeedInc = true; + } + if (allIter == subscribedTotalMap.end()) { + subscribedTotalMap[queryId] = {query, 1}; + } else if (isNeedInc) { + subscribedTotalMap[queryId].second++; + } + return E_OK; +} + +int SubscribeManager::ActiveSubscribeQuery(const std::string &device, const std::string &queryId, + SubscribeMap &subscribeMap, SubscribedTotalMap &subscribedTotalMap) +{ + if (subscribedTotalMap.find(queryId) == subscribedTotalMap.end()) { + LOGE("[SubscribeManager] can not find queryId=%s in SubscribeTotalMap", STR_MASK(queryId)); + return -E_INTERNAL_ERROR; + } + if (subscribeMap.find(device) == subscribeMap.end()) { + LOGE("[SubscribeManager] can not find dev=%s in localSubscribeMap", STR_MASK(device)); + return -E_INTERNAL_ERROR; + } + if (subscribeMap[device].find(queryId) == subscribeMap[device].end()) { + LOGE("[SubscribeManager] can not find dev=%s,queryId=%s in map", STR_MASK(device), STR_MASK(queryId)); + return -E_INTERNAL_ERROR; + } + subscribeMap[device][queryId] = SubscribeStatus::ACTIVE; + return E_OK; +} + +void SubscribeManager::DeleteSubscribeQuery(const std::string &device, const std::string &queryId, + SubscribeMap &subscribeMap, SubscribedTotalMap &subscribedTotalMap) +{ + if (subscribeMap.find(device) == subscribeMap.end()) { + LOGE("[SubscribeManager] can not find dev=%s in map", STR_MASK(device)); + return; + } + if (subscribeMap[device].find(queryId) == subscribeMap[device].end()) { + LOGE("[SubscribeManager] can not find dev=%s,queryId=%s in map", STR_MASK(device), STR_MASK(queryId)); + return; + } + SubscribeStatus queryStatus = subscribeMap[device][queryId]; + // not permit to delete the query when something wrong this time,because it is subscribed successfully last time + if (queryStatus == SubscribeStatus::ACTIVE) { + LOGE("[SubscribeManager] dev=%s,queryId=%s is active, no need to del", STR_MASK(device), STR_MASK(queryId)); + return; + } + subscribeMap[device].erase(queryId); + auto iter = subscribedTotalMap.find(queryId); + if (iter == subscribedTotalMap.end()) { + LOGE("[SubscribeManager] can not find queryId=%s in SubscribeTotalMap", STR_MASK(queryId)); + return; + } + iter->second.second--; + if (iter->second.second <= 0) { + LOGI("[SubscribeManager] del queryId=%s from SubscribeTotalMap", STR_MASK(queryId)); + subscribedTotalMap.erase(queryId); + } + LOGI("[SubscribeManager] dev=%s,queryId=%s remove from SubscribeMap success", STR_MASK(device), STR_MASK(queryId)); +} + +void SubscribeManager::RemoveSubscribeQuery(const std::string &device, const std::string &queryId, + SubscribeMap &subscribeMap, SubscribedTotalMap &subscribedTotalMap) +{ + auto iter = subscribeMap.find(device); + if (iter == subscribeMap.end()) { + LOGE("[SubscribeManager] dev=%s not in SubscribedMap", STR_MASK(device)); + return; + } + if (iter->second.find(queryId) == subscribeMap[device].end()) { + LOGI("[SubscribeManager] dev=%s,queryId=%s not in SubscribedMap", STR_MASK(device), STR_MASK(queryId)); + return; + } + iter->second.erase(queryId); + auto allIter = subscribedTotalMap.find(queryId); + if (allIter == subscribedTotalMap.end()) { + LOGI("[SubscribeManager] queryId=%s not in TotalMap", STR_MASK(queryId)); + return; + } + allIter->second.second--; + if (allIter->second.second <= 0) { + subscribedTotalMap.erase(queryId); + LOGI("[SubscribeManager] queryId=%s delete from TotalMap", STR_MASK(queryId)); + } + LOGI("[SubscribeManager] dev=%s,queryId=%s remove from SubscribedMap success", STR_MASK(device), STR_MASK(queryId)); +} + +void SubscribeManager::GetSubscribeQueries(const std::string &device, const SubscribeMap &subscribeMap, + const SubscribedTotalMap &subscribedTotalMap, std::vector &subscribeQueries) const +{ + auto iter = subscribeMap.find(device); + if (iter == subscribeMap.end()) { + LOGD("[SubscribeManager] dev=%s not in localSubscribeMap", STR_MASK(device)); + return; + } + for (const auto &queryInfo : iter->second) { + auto iterTmp = subscribedTotalMap.find(queryInfo.first); + if (iterTmp == subscribedTotalMap.end()) { + LOGE("[SubscribeManager] queryId=%s not in localTotalMap", STR_MASK(queryInfo.first)); + continue; + } + subscribeQueries.push_back(iterTmp->second.first); + } +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/subscribe_manager.h b/mock/distributeddb/syncer/src/subscribe_manager.h new file mode 100644 index 00000000..6b3bb1d3 --- /dev/null +++ b/mock/distributeddb/syncer/src/subscribe_manager.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SINGLE_VER_SUBSCRIBE_MANAGER_H +#define SINGLE_VER_SUBSCRIBE_MANAGER_H + +#include +#include +#include "query_sync_object.h" + +namespace DistributedDB { +enum class SubscribeStatus { + NOT_ACTIVE = 0, + ACTIVE = 1, +}; + +using SubscribeMap = std::map>; +using SubscribedTotalMap = std::map>; + +class SubscribeManager { +public: + SubscribeManager() = default; + ~SubscribeManager() {}; + + DISABLE_COPY_ASSIGN_MOVE(SubscribeManager); + + // clear remoteSubscribeMap_[device] list when remote db is closed or dev offline. + void ClearRemoteSubscribeQuery(const std::string &device); + + // clear localSubscribeMap_[device] list when device is offline. + void ClearLocalSubscribeQuery(const std::string &device); + + void ClearAllRemoteQuery(); + + // add query when receive subscribe command + int ReserveRemoteSubscribeQuery(const std::string &device, const QuerySyncObject &query); + + // active query to ACTIVE when send ack ok + int ActiveRemoteSubscribeQuery(const std::string &device, const QuerySyncObject &query); + + // reserve query when user call SubscribeRemoteQuery, status set to NOT_ACTIVE + int ReserveLocalSubscribeQuery(const std::string &device, const QuerySyncObject &query); + + // active query to ACTIVE when receive ack ok + int ActiveLocalSubscribeQuery(const std::string &device, const QuerySyncObject &query); + + // delete local subscribe query when recv wrong errCode, only not_active status allowed to del + void DeleteLocalSubscribeQuery(const std::string &device, const QuerySyncObject &query); + + // delete remote subscribe query when send msg failed, only not_active status allowed to del + void DeleteRemoteSubscribeQuery(const std::string &device, const QuerySyncObject &query); + + // put subscribe queries into unfinished map when remote db online + void PutLocalUnFiniedSubQueries(const std::string &device, std::vector &subscribeQueries); + + // get all device unFinished subscribe queries which triggered by auto subscribe and need retry subscribe + void GetAllUnFinishSubQueries(std::map> &allSyncQueries) const; + + // remove query when receive unsubscribe command + void RemoveRemoteSubscribeQuery(const std::string &device, const QuerySyncObject &query); + + // remove query when user call UnSubscribeRemoteQuery + void RemoveLocalSubscribeQuery(const std::string &device, const QuerySyncObject &query); + + // get device active subscribeQueries from localSubscribeMap_ + void GetLocalSubscribeQueries(const std::string &device, std::vector &subscribeQueries) const; + + // get device remote queryId from remoteSubscribedMap_ while data change + void GetRemoteSubscribeQueryIds(const std::string &device, std::vector &subscribeQueryIds) const; + // get device remote subscribeQueries from remoteSubscribedMap_ while data change + void GetRemoteSubscribeQueries(const std::string &device, std::vector &subscribeQueries) const; + + bool IsRemoteContainSubscribe(const std::string &device, const QuerySyncObject &query) const; + + int LocalSubscribeLimitCheck(const std::vector &devices, QuerySyncObject &query) const; +private: + void ClearSubscribeQuery(const std::string &device, SubscribeMap &subscribeMap, + SubscribedTotalMap &subscribedTotalMap); + + int ReserveSubscribeQuery(const std::string &device, const QuerySyncObject &query, SubscribeMap &subscribeMap, + SubscribedTotalMap &subscribedTotalMap); + + int ActiveSubscribeQuery(const std::string &device, const std::string &queryId, SubscribeMap &subscribeMap, + SubscribedTotalMap &subscribedTotalMap); + + void DeleteSubscribeQuery(const std::string &device, const std::string &queryId, SubscribeMap &subscribeMap, + SubscribedTotalMap &subscribedTotalMap); + + void RemoveSubscribeQuery(const std::string &device, const std::string &queryId, SubscribeMap &subscribeMap, + SubscribedTotalMap &subscribedTotalMap); + + void GetSubscribeQueries(const std::string &device, const SubscribeMap &subscribeMap, + const SubscribedTotalMap &subscribedTotalMap, std::vector &subscribeQueries) const; + + mutable std::shared_mutex localSubscribeMapLock_; + // subscribe sponsor, key: device, value: pair map + // status 0: active, 1: not active + SubscribeMap localSubscribeMap_; + + // used retry subscribe in db open scene, key: device value: set + std::map> unFinishedLocalAutoSubMap_; + + // subscribe sponsor total query info, key:queryId, value: + // while use_num is 0, delete item from the map + SubscribedTotalMap localSubscribeTotalMap_; + + mutable std::shared_mutex remoteSubscribedMapLock_; + // subscribed, key: device, value: pair map + // status 0: active, 1: not active + SubscribeMap remoteSubscribedMap_; + + // subscribed total query info, key:queryId, value: + // while use_num is 0, delete item from the map + SubscribedTotalMap remoteSubscribedTotalMap_; +}; +} // namespace DistributedDB + +#endif // SINGLE_VER_SUBSCRIBE_MANAGER_H \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/sync_config.cpp b/mock/distributeddb/syncer/src/sync_config.cpp new file mode 100644 index 00000000..8b3c6b89 --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_config.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "sync_config.h" +namespace DistributedDB { +const AbilityItem SyncConfig::DATABASE_COMPRESSION_ZLIB = {0, 1}; +const AbilityItem SyncConfig::ALLPREDICATEQUERY = {1, 1}; // 0b10 {1: start at second bit, 1: 1 bit len} +const AbilityItem SyncConfig::SUBSCRIBEQUERY = {2, 1}; // 0b100 +const AbilityItem SyncConfig::INKEYS_QUERY = {3, 1}; // 0b1000 + +const std::vector SyncConfig::ABILITYBITS = { + DATABASE_COMPRESSION_ZLIB, + ALLPREDICATEQUERY, + SUBSCRIBEQUERY, + INKEYS_QUERY}; + +const std::map SyncConfig::COMPRESSALGOMAP = { + {static_cast(CompressAlgorithm::ZLIB), DATABASE_COMPRESSION_ZLIB}, +}; +} // DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/sync_config.h b/mock/distributeddb/syncer/src/sync_config.h new file mode 100644 index 00000000..dc2c252d --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_config.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNC_CONFIG_H +#define SYNC_CONFIG_H + +#include +#include +#include +#include "macro_utils.h" +#include "parcel.h" +#include "types_export.h" + +// db ability config +namespace DistributedDB { +// offset, used_bits_num, used_bits_num < 64 +using AbilityItem = std::pair; +// format: {offset, used_bits_num} +/* +if need to add new ability, just add append to the last ability +current ability format: +|first bit|second bit|third bit| +|DATABASE_COMPRESSION_ZLIB|ALLPREDICATEQUERY|SUBSCRIBEQUERY| +*/ +class SyncConfig final { +public: + static const AbilityItem DATABASE_COMPRESSION_ZLIB; + static const AbilityItem ALLPREDICATEQUERY; + static const AbilityItem SUBSCRIBEQUERY; + static const AbilityItem INKEYS_QUERY; + static const std::vector ABILITYBITS; + static const std::map COMPRESSALGOMAP; +}; +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/sync_engine.cpp b/mock/distributeddb/syncer/src/sync_engine.cpp new file mode 100644 index 00000000..15688bed --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_engine.cpp @@ -0,0 +1,1067 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sync_engine.h" + +#include +#include +#include + +#include "ability_sync.h" +#include "db_common.h" +#include "db_dump_helper.h" +#include "db_errno.h" +#include "device_manager.h" +#include "hash.h" +#include "isync_state_machine.h" +#include "log_print.h" +#include "runtime_context.h" +#include "single_ver_serialize_manager.h" +#include "subscribe_manager.h" +#include "time_sync.h" + +#ifndef OMIT_MULTI_VER +#include "commit_history_sync.h" +#include "multi_ver_data_sync.h" +#include "value_slice_sync.h" +#endif + +namespace DistributedDB { +int SyncEngine::queueCacheSize_ = 0; +int SyncEngine::maxQueueCacheSize_ = DEFAULT_CACHE_SIZE; +unsigned int SyncEngine::discardMsgNum_ = 0; +std::mutex SyncEngine::queueLock_; + +SyncEngine::SyncEngine() + : syncInterface_(nullptr), + communicator_(nullptr), + deviceManager_(nullptr), + metadata_(nullptr), + timeChangedListener_(nullptr), + execTaskCount_(0), + isSyncRetry_(false), + communicatorProxy_(nullptr), + isActive_(false) +{ +} + +SyncEngine::~SyncEngine() +{ + LOGD("[SyncEngine] ~SyncEngine!"); + ClearInnerResource(); + equalIdentifierMap_.clear(); + subManager_ = nullptr; + LOGD("[SyncEngine] ~SyncEngine ok!"); +} + +int SyncEngine::Initialize(ISyncInterface *syncInterface, std::shared_ptr &metadata, + const std::function &onRemoteDataChanged, const std::function &offlineChanged, + const std::function &queryAutoSyncCallback) +{ + if ((syncInterface == nullptr) || (metadata == nullptr)) { + return -E_INVALID_ARGS; + } + int errCode = StartAutoSubscribeTimer(); + if (errCode != OK) { + return errCode; + } + syncInterface_ = syncInterface; + errCode = InitComunicator(syncInterface); + if (errCode != E_OK) { + LOGE("[SyncEngine] Init Communicator failed"); + // There need to set nullptr. other wise, syncInterface will be + // DecRef in th destroy-method. + StopAutoSubscribeTimer(); + syncInterface_ = nullptr; + return errCode; + } + onRemoteDataChanged_ = onRemoteDataChanged; + offlineChanged_ = offlineChanged; + queryAutoSyncCallback_ = queryAutoSyncCallback; + errCode = InitDeviceManager(onRemoteDataChanged, offlineChanged); + if (errCode != E_OK) { + // reset ptr if initialize device manager failed + syncInterface_ = nullptr; + StopAutoSubscribeTimer(); + return errCode; + } + if (subManager_ == nullptr) { + subManager_ = std::make_shared(); + } + metadata_ = metadata; + errCode = InitTimeChangedListener(); + if (errCode != E_OK) { + syncInterface_ = nullptr; + StopAutoSubscribeTimer(); + return errCode; + } + isActive_ = true; + LOGI("[SyncEngine] Engine init ok"); + return E_OK; +} + +int SyncEngine::Close() +{ + LOGI("[SyncEngine] SyncEngine[%s] close enter!", label_.c_str()); + isActive_ = false; + UnRegCommunicatorsCallback(); + StopAutoSubscribeTimer(); + + std::unique_lock closeLock(execTaskCountLock_); + bool isTimeout = execTaskCv_.wait_for(closeLock, std::chrono::milliseconds(DBConstant::MIN_TIMEOUT), + [this]() { return execTaskCount_ == 0; }); + if (!isTimeout) { + LOGD("SyncEngine Close with executing task!"); + } + // Clear SyncContexts + { + std::unique_lock lock(contextMapLock_); + for (auto &iter : syncTaskContextMap_) { + ISyncTaskContext *tempContext = iter.second; + lock.unlock(); + RefObject::KillAndDecObjRef(tempContext); + tempContext = nullptr; + lock.lock(); + iter.second = nullptr; + } + syncTaskContextMap_.clear(); + } + + ReleaseCommunicators(); + std::lock_guard msgLock(queueLock_); + while (!msgQueue_.empty()) { + Message *inMsg = msgQueue_.front(); + msgQueue_.pop_front(); + if (inMsg != nullptr) { + queueCacheSize_ -= GetMsgSize(inMsg); + delete inMsg; + inMsg = nullptr; + } + } + // close db, rekey or import scene, need clear all remote query info + // local query info will destroy with syncEngine destruct + if (subManager_ != nullptr) { + subManager_->ClearAllRemoteQuery(); + } + ClearInnerResource(); + LOGI("[SyncEngine] SyncEngine closed!"); + return E_OK; +} + +int SyncEngine::AddSyncOperation(SyncOperation *operation) +{ + if (operation == nullptr) { + LOGE("[SyncEngine] operation is nullptr"); + return -E_INVALID_ARGS; + } + + std::vector devices = operation->GetDevices(); + for (const auto &deviceId : devices) { + if (deviceId.size() == 0) { + operation->SetStatus(deviceId, SyncOperation::OP_INVALID_ARGS); + continue; + } + operation->SetStatus(deviceId, SyncOperation::OP_WAITING); + int errCode = AddSyncOperForContext(deviceId, operation); + if (errCode != E_OK) { + operation->SetStatus(deviceId, SyncOperation::OP_FAILED); + } + } + return E_OK; +} + +void SyncEngine::RemoveSyncOperation(int syncId) +{ + std::lock_guard lock(contextMapLock_); + for (auto &iter : syncTaskContextMap_) { + ISyncTaskContext *context = iter.second; + if (context != nullptr) { + context->RemoveSyncOperation(syncId); + } + } +} + +void SyncEngine::BroadCastDataChanged() const +{ + if (deviceManager_ != nullptr) { + (void)deviceManager_->SendBroadCast(LOCAL_DATA_CHANGED); + } +} + +void SyncEngine::RegConnectCallback() +{ + if (communicator_ == nullptr) { + LOGE("[SyncEngine][RegConnCB] communicator is not set!"); + return; + } + LOGD("[SyncEngine] RegOnConnectCallback"); + int errCode = communicator_->RegOnConnectCallback( + std::bind(&DeviceManager::OnDeviceConnectCallback, deviceManager_, + std::placeholders::_1, std::placeholders::_2), nullptr); + if (errCode != E_OK) { + LOGE("[SyncEngine][RegConnCB] register failed, auto sync can not use! err %d", errCode); + return; + } + communicator_->Activate(); +} + +void SyncEngine::GetOnlineDevices(std::vector &devices) const +{ + devices.clear(); + if (deviceManager_ != nullptr) { + deviceManager_->GetOnlineDevices(devices); + } +} + +int SyncEngine::InitDeviceManager(const std::function &onRemoteDataChanged, + const std::function &offlineChanged) +{ + deviceManager_ = new (std::nothrow) DeviceManager(); + if (deviceManager_ == nullptr) { + LOGE("[SyncEngine] deviceManager alloc failed!"); + return -E_OUT_OF_MEMORY; + } + + int errCode = deviceManager_->Initialize(communicatorProxy_, onRemoteDataChanged, offlineChanged); + if (errCode != E_OK) { + LOGE("[SyncEngine] deviceManager init failed! err %d", errCode); + delete deviceManager_; + deviceManager_ = nullptr; + return errCode; + } + return E_OK; +} + +int SyncEngine::InitComunicator(const ISyncInterface *syncInterface) +{ + ICommunicatorAggregator *communicatorAggregator = nullptr; + int errCode = RuntimeContext::GetInstance()->GetCommunicatorAggregator(communicatorAggregator); + if (communicatorAggregator == nullptr) { + LOGE("[SyncEngine] Get ICommunicatorAggregator error when init the sync engine err = %d", errCode); + return errCode; + } + std::vector label = syncInterface->GetIdentifier(); + bool isSyncDualTupleMode = syncInterface->GetDbProperties().GetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, + false); + if (isSyncDualTupleMode) { + std::vector dualTuplelabel = syncInterface->GetDualTupleIdentifier(); + LOGI("[SyncEngine] dual tuple mode, original identifier=%.6s, target identifier=%.6s", VEC_TO_STR(label), + VEC_TO_STR(dualTuplelabel)); + communicator_ = communicatorAggregator->AllocCommunicator(dualTuplelabel, errCode); + } else { + communicator_ = communicatorAggregator->AllocCommunicator(label, errCode); + } + if (communicator_ == nullptr) { + LOGE("[SyncEngine] AllocCommunicator error when init the sync engine! err = %d", errCode); + return errCode; + } + + errCode = communicator_->RegOnMessageCallback( + std::bind(&SyncEngine::MessageReciveCallback, this, std::placeholders::_1, std::placeholders::_2), + []() {}); + if (errCode != E_OK) { + LOGE("[SyncEngine] SyncRequestCallback register failed! err = %d", errCode); + communicatorAggregator->ReleaseCommunicator(communicator_); + communicator_ = nullptr; + return errCode; + } + + communicatorProxy_ = new (std::nothrow) CommunicatorProxy(); + if (communicatorProxy_ == nullptr) { + communicatorAggregator->ReleaseCommunicator(communicator_); + communicator_ = nullptr; + return -E_OUT_OF_MEMORY; + } + + communicatorProxy_->SetMainCommunicator(communicator_); + label.resize(3); // only show 3 Bytes enough + label_ = DBCommon::VectorToHexString(label); + LOGD("[SyncEngine] RegOnConnectCallback"); + return errCode; +} + +int SyncEngine::AddSyncOperForContext(const std::string &deviceId, SyncOperation *operation) +{ + int errCode = E_OK; + ISyncTaskContext *context = nullptr; + { + std::lock_guard lock(contextMapLock_); + context = FindSyncTaskContext(deviceId); + if (context == nullptr) { + if (!IsKilled()) { + context = GetSyncTaskContext(deviceId, errCode); + } + if (context == nullptr) { + return errCode; + } + } + if (context->IsKilled()) { + return -E_OBJ_IS_KILLED; + } + // IncRef for SyncEngine to make sure context is valid, to avoid a big lock + RefObject::IncObjRef(context); + } + + errCode = context->AddSyncOperation(operation); + RefObject::DecObjRef(context); + return errCode; +} + +void SyncEngine::MessageReciveCallbackTask(ISyncTaskContext *context, const ICommunicator *communicator, + Message *inMsg) +{ + std::string deviceId = context->GetDeviceId(); + + if (inMsg->GetMessageId() != LOCAL_DATA_CHANGED) { + int errCode = context->ReceiveMessageCallback(inMsg); + if (errCode == -E_NOT_NEED_DELETE_MSG) { + goto MSG_CALLBACK_OUT_NOT_DEL; + } + // add auto sync here while recv subscribe request + QuerySyncObject syncObject; + if (errCode == E_OK && context->IsNeedTriggerQueryAutoSync(inMsg, syncObject)) { + InternalSyncParma param; + GetQueryAutoSyncParam(deviceId, syncObject, param); + queryAutoSyncCallback_(param); + } + } + + delete inMsg; + inMsg = nullptr; +MSG_CALLBACK_OUT_NOT_DEL: + ScheduleTaskOut(context, communicator); +} + +void SyncEngine::RemoteDataChangedTask(ISyncTaskContext *context, const ICommunicator *communicator, Message *inMsg) +{ + do { + std::string deviceId = context->GetDeviceId(); + if (onRemoteDataChanged_ && deviceManager_->IsDeviceOnline(deviceId)) { + onRemoteDataChanged_(deviceId); + } else { + LOGE("[SyncEngine] onRemoteDataChanged is null!"); + } + } while (false); + delete inMsg; + inMsg = nullptr; + ScheduleTaskOut(context, communicator); +} + +void SyncEngine::ScheduleTaskOut(ISyncTaskContext *context, const ICommunicator *communicator) +{ + (void)DealMsgUtilQueueEmpty(); + DecExecTaskCount(); + RefObject::DecObjRef(communicator); + RefObject::DecObjRef(context); +} + +int SyncEngine::DealMsgUtilQueueEmpty() +{ + if (!isActive_) { + return -E_BUSY; // db is closing just return + } + int errCode = E_OK; + Message *inMsg = nullptr; + { + std::lock_guard lock(queueLock_); + if (msgQueue_.empty()) { + return errCode; + } + inMsg = msgQueue_.front(); + msgQueue_.pop_front(); + queueCacheSize_ -= GetMsgSize(inMsg); + } + + IncExecTaskCount(); + // it will deal with the first message in queue, we should increase object reference counts and sure that resources + // could be prevented from destroying by other threads. + do { + ISyncTaskContext *nextContext = GetConextForMsg(inMsg->GetTarget(), errCode); + if (errCode != E_OK) { + break; + } + errCode = ScheduleDealMsg(nextContext, inMsg); + if (errCode != E_OK) { + RefObject::DecObjRef(nextContext); + } + } while (false); + if (errCode != E_OK) { + delete inMsg; + inMsg = nullptr; + DecExecTaskCount(); + } + return errCode; +} + +ISyncTaskContext *SyncEngine::GetConextForMsg(const std::string &targetDev, int &errCode) +{ + ISyncTaskContext *context = nullptr; + { + std::lock_guard lock(contextMapLock_); + context = FindSyncTaskContext(targetDev); + if (context != nullptr) { + if (context->IsKilled()) { + errCode = -E_OBJ_IS_KILLED; + return nullptr; + } + } else { + if (IsKilled()) { + errCode = -E_OBJ_IS_KILLED; + return nullptr; + } + context = GetSyncTaskContext(targetDev, errCode); + if (context == nullptr) { + return nullptr; + } + } + // IncRef for context to make sure context is valid, when task run another thread + RefObject::IncObjRef(context); + } + return context; +} + +int SyncEngine::ScheduleDealMsg(ISyncTaskContext *context, Message *inMsg) +{ + if (inMsg == nullptr) { + LOGE("[SyncEngine] MessageReciveCallback inMsg is null!"); + DecExecTaskCount(); + return E_OK; + } + RefObject::IncObjRef(communicatorProxy_); + int errCode = E_OK; + // deal remote local data changed message + if (inMsg->GetMessageId() == LOCAL_DATA_CHANGED) { + RemoteDataChangedTask(context, communicatorProxy_, inMsg); + } else { + errCode = RuntimeContext::GetInstance()->ScheduleTask(std::bind(&SyncEngine::MessageReciveCallbackTask, + this, context, communicatorProxy_, inMsg)); + } + + if (errCode != E_OK) { + LOGE("[SyncEngine] MessageReciveCallbackTask Schedule failed err %d", errCode); + RefObject::DecObjRef(communicatorProxy_); + } + return errCode; +} + +void SyncEngine::MessageReciveCallback(const std::string &targetDev, Message *inMsg) +{ + IncExecTaskCount(); + int errCode = MessageReciveCallbackInner(targetDev, inMsg); + if (errCode != E_OK) { + delete inMsg; + inMsg = nullptr; + DecExecTaskCount(); + LOGE("[SyncEngine] MessageReciveCallback failed!"); + } +} + +int SyncEngine::MessageReciveCallbackInner(const std::string &targetDev, Message *inMsg) +{ + if (targetDev.empty() || inMsg == nullptr) { + LOGE("[SyncEngine][MessageReciveCallback] from a invalid device or inMsg is null "); + return -E_INVALID_ARGS; + } + if (!isActive_) { + LOGE("[SyncEngine] engine is closing, ignore msg"); + return -E_BUSY; + } + int msgSize = 0; + if (!IsSkipCalculateLen(inMsg)) { + msgSize = GetMsgSize(inMsg); + if (msgSize <= 0) { + LOGE("[SyncEngine] GetMsgSize makes a mistake"); + return -E_NOT_SUPPORT; + } + } + + { + std::lock_guard lock(queueLock_); + if ((queueCacheSize_ + msgSize) > maxQueueCacheSize_) { + LOGE("[SyncEngine] The size of message queue is beyond maximum"); + discardMsgNum_++; + return -E_BUSY; + } + + if (execTaskCount_ > MAX_EXEC_NUM) { + PutMsgIntoQueue(targetDev, inMsg, msgSize); + // task dont exec here + DecExecTaskCount(); + return E_OK; + } + } + + int errCode = E_OK; + ISyncTaskContext *nextContext = GetConextForMsg(targetDev, errCode); + if (errCode != E_OK) { + return errCode; + } + LOGD("[SyncEngine] MessageReciveCallback MSG ID = %d", inMsg->GetMessageId()); + return ScheduleDealMsg(nextContext, inMsg); +} + +void SyncEngine::PutMsgIntoQueue(const std::string &targetDev, Message *inMsg, int msgSize) +{ + if (inMsg->GetMessageId() == LOCAL_DATA_CHANGED) { + auto iter = std::find_if(msgQueue_.begin(), msgQueue_.end(), + [&targetDev](const Message *msg) { + return targetDev == msg->GetTarget() && msg->GetMessageId() == LOCAL_DATA_CHANGED; + }); + if (iter != msgQueue_.end()) { + delete inMsg; + inMsg = nullptr; + return; + } + } + inMsg->SetTarget(targetDev); + msgQueue_.push_back(inMsg); + queueCacheSize_ += msgSize; + LOGE("[SyncEngine] The quantity of executing threads is beyond maximum. msgQueueSize = %zu", msgQueue_.size()); +} + +int SyncEngine::GetMsgSize(const Message *inMsg) const +{ + switch (inMsg->GetMessageId()) { + case TIME_SYNC_MESSAGE: + return TimeSync::CalculateLen(inMsg); + case ABILITY_SYNC_MESSAGE: + return AbilitySync::CalculateLen(inMsg); + case DATA_SYNC_MESSAGE: + case QUERY_SYNC_MESSAGE: + case CONTROL_SYNC_MESSAGE: + return SingleVerSerializeManager::CalculateLen(inMsg); +#ifndef OMIT_MULTI_VER + case COMMIT_HISTORY_SYNC_MESSAGE: + return CommitHistorySync::CalculateLen(inMsg); + case MULTI_VER_DATA_SYNC_MESSAGE: + return MultiVerDataSync::CalculateLen(inMsg); + case VALUE_SLICE_SYNC_MESSAGE: + return ValueSliceSync::CalculateLen(inMsg); +#endif + case LOCAL_DATA_CHANGED: + return DeviceManager::CalculateLen(); + default: + LOGE("[SyncEngine] GetMsgSize not support msgId:%u", inMsg->GetMessageId()); + return -E_NOT_SUPPORT; + } +} + +ISyncTaskContext *SyncEngine::FindSyncTaskContext(const std::string &deviceId) +{ + auto iter = syncTaskContextMap_.find(deviceId); + if (iter != syncTaskContextMap_.end()) { + ISyncTaskContext *context = iter->second; + return context; + } + return nullptr; +} + +ISyncTaskContext *SyncEngine::GetSyncTaskContextAndInc(const std::string &deviceId) +{ + ISyncTaskContext *context = nullptr; + std::lock_guard lock(contextMapLock_); + context = FindSyncTaskContext(deviceId); + if (context == nullptr) { + LOGI("[SyncEngine] dev=%s, context is null, no need to clear sync operation", STR_MASK(deviceId)); + return nullptr; + } + if (context->IsKilled()) { + LOGI("[SyncEngine] context is killing"); + return nullptr; + } + RefObject::IncObjRef(context); + return context; +} + +ISyncTaskContext *SyncEngine::GetSyncTaskContext(const std::string &deviceId, int &errCode) +{ + ISyncTaskContext *context = CreateSyncTaskContext(); + if (context == nullptr) { + errCode = -E_OUT_OF_MEMORY; + LOGE("[SyncEngine] SyncTaskContext alloc failed, may be no memory available!"); + return nullptr; + } + errCode = context->Initialize(deviceId, syncInterface_, metadata_, communicatorProxy_); + if (errCode != E_OK) { + LOGE("[SyncEngine] context init failed err %d, dev %s", errCode, STR_MASK(deviceId)); + RefObject::DecObjRef(context); + context = nullptr; + return nullptr; + } + syncTaskContextMap_.insert(std::pair(deviceId, context)); + // IncRef for SyncEngine to make sure SyncEngine is valid when context access + RefObject::IncObjRef(this); + context->OnLastRef([this, deviceId]() { + LOGD("[SyncEngine] SyncTaskContext for id %s finalized", STR_MASK(deviceId)); + RefObject::DecObjRef(this); + }); + context->RegOnSyncTask(std::bind(&SyncEngine::ExecSyncTask, this, context)); + return context; +} + +int SyncEngine::ExecSyncTask(ISyncTaskContext *context) +{ + if (IsKilled()) { + return -E_OBJ_IS_KILLED; + } + + AutoLock lockGuard(context); + int status = context->GetTaskExecStatus(); + if ((status == SyncTaskContext::RUNNING) || context->IsKilled()) { + return -E_NOT_SUPPORT; + } + context->SetTaskExecStatus(ISyncTaskContext::RUNNING); + if (!context->IsTargetQueueEmpty()) { + context->MoveToNextTarget(); + int checkErrCode = E_OK; + // rdb don't support PermissionCheck + if (syncInterface_->GetInterfaceType() != ISyncInterface::SYNC_RELATION) { + checkErrCode = RunPermissionCheck(context->GetDeviceId(), + GetPermissionCheckFlag(context->IsAutoSync(), context->GetMode())); + } + if (checkErrCode != E_OK) { + context->SetOperationStatus(SyncOperation::OP_PERMISSION_CHECK_FAILED); + context->SetTaskExecStatus(ISyncTaskContext::FINISHED); + return checkErrCode; + } + context->UnlockObj(); + int errCode = context->StartStateMachine(); + context->LockObj(); + if (errCode != E_OK) { + LOGE("[SyncEngine] machine StartSync failed"); + context->SetOperationStatus(SyncOperation::OP_FAILED); + return errCode; + } + } else { + LOGD("[SyncEngine] ExecSyncTask finished"); + context->SetTaskExecStatus(ISyncTaskContext::FINISHED); + } + return E_OK; +} + +int SyncEngine::GetQueueCacheSize() const +{ + return queueCacheSize_; +} + +unsigned int SyncEngine::GetDiscardMsgNum() const +{ + return discardMsgNum_; +} + +unsigned int SyncEngine::GetMaxExecNum() const +{ + return MAX_EXEC_NUM; +} + +void SyncEngine::SetMaxQueueCacheSize(int value) +{ + maxQueueCacheSize_ = value; +} + +uint8_t SyncEngine::GetPermissionCheckFlag(bool isAutoSync, int syncMode) +{ + uint8_t flag = 0; + int mode = SyncOperation::TransferSyncMode(syncMode); + if (mode == SyncModeType::PUSH || mode == SyncModeType::RESPONSE_PULL) { + flag = CHECK_FLAG_SEND; + } else if (mode == SyncModeType::PULL) { + flag = CHECK_FLAG_RECEIVE; + } else if (mode == SyncModeType::PUSH_AND_PULL) { + flag = CHECK_FLAG_SEND | CHECK_FLAG_RECEIVE; + } + if (isAutoSync) { + flag = flag | CHECK_FLAG_AUTOSYNC; + } + if (mode != SyncModeType::RESPONSE_PULL) { + // it means this sync is started by local + flag = flag | CHECK_FLAG_SPONSOR; + } + return flag; +} + +int SyncEngine::RunPermissionCheck(const std::string &deviceId, uint8_t flag) const +{ + std::string appId = syncInterface_->GetDbProperties().GetStringProp(KvDBProperties::APP_ID, ""); + std::string userId = syncInterface_->GetDbProperties().GetStringProp(KvDBProperties::USER_ID, ""); + std::string storeId = syncInterface_->GetDbProperties().GetStringProp(KvDBProperties::STORE_ID, ""); + int errCode = RuntimeContext::GetInstance()->RunPermissionCheck(userId, appId, storeId, deviceId, flag); + if (errCode != E_OK) { + LOGE("[SyncEngine] RunPermissionCheck not pass errCode:%d, flag:%d, %s Label=%s", + errCode, flag, STR_MASK(deviceId), label_.c_str()); + } + return errCode; +} + +std::string SyncEngine::GetLabel() const +{ + return label_; +} + +bool SyncEngine::GetSyncRetry() const +{ + return isSyncRetry_; +} + +void SyncEngine::SetSyncRetry(bool isRetry) +{ + if (isSyncRetry_ == isRetry) { + LOGI("sync retry is equal, syncTry=%d, no need to set.", isRetry); + return; + } + isSyncRetry_ = isRetry; + LOGI("[SyncEngine] SetSyncRetry:%d ok", isRetry); + std::lock_guard lock(contextMapLock_); + for (auto &iter : syncTaskContextMap_) { + ISyncTaskContext *context = iter.second; + if (context != nullptr) { + context->SetSyncRetry(isRetry); + } + } +} + +int SyncEngine::SetEqualIdentifier(const std::string &identifier, const std::vector &targets) +{ + if (!isActive_) { + LOGI("[SyncEngine] engine is closed, just put into map"); + return E_OK; + } + ICommunicator *communicator = nullptr; + { + std::lock_guard lock(equalCommunicatorsLock_); + if (equalCommunicators_.count(identifier) != 0) { + communicator = equalCommunicators_[identifier]; + } else { + int errCode = E_OK; + communicator = AllocCommunicator(identifier, errCode); + if (communicator == nullptr) { + return errCode; + } + equalCommunicators_[identifier] = communicator; + } + } + std::string targetDevices; + for (const auto &dev : targets) { + targetDevices += DBCommon::StringMasking(dev) + ","; + } + LOGI("[SyncEngine] set equal identifier=%s, original=%s, targetDevices=%s", + DBCommon::TransferStringToHex(identifier).c_str(), label_.c_str(), + targetDevices.substr(0, targetDevices.size() - 1).c_str()); + communicatorProxy_->SetEqualCommunicator(communicator, identifier, targets); + communicator->Activate(); + return E_OK; +} + +void SyncEngine::SetEqualIdentifier() +{ + std::map> equalIdentifier; // key: equalIdentifier value: devices + for (auto &item : equalIdentifierMap_) { + if (equalIdentifier.find(item.second) == equalIdentifier.end()) { + equalIdentifier[item.second] = {item.first}; + } else { + equalIdentifier[item.second].push_back(item.first); + } + } + for (auto &item : equalIdentifier) { + SetEqualIdentifier(item.first, item.second); + } +} + +void SyncEngine::SetEqualIdentifierMap(const std::string &identifier, const std::vector &targets) +{ + for (auto iter = equalIdentifierMap_.begin(); iter != equalIdentifierMap_.end();) { + if (identifier == iter->second) { + iter = equalIdentifierMap_.erase(iter); + continue; + } + iter++; + } + for (auto &device : targets) { + equalIdentifierMap_[device] = identifier; + } +} + +void SyncEngine::OfflineHandleByDevice(const std::string &deviceId) +{ + if (communicatorProxy_ == nullptr) { + return; + } + // db closed or device is offline + // clear remote subscribe and trigger + std::vector remoteQueryId; + subManager_->GetRemoteSubscribeQueryIds(deviceId, remoteQueryId); + subManager_->ClearRemoteSubscribeQuery(deviceId); + static_cast(syncInterface_)->RemoveSubscribe(remoteQueryId); + // get context and Inc context if context is not nullprt + ISyncTaskContext *context = GetSyncTaskContextAndInc(deviceId); + if (context != nullptr) { + context->SetIsNeedResetAbilitySync(true); + } + if (communicatorProxy_->IsDeviceOnline(deviceId)) { + LOGI("[SyncEngine] target dev=%s is online, no need to clear task.", STR_MASK(deviceId)); + RefObject::DecObjRef(context); + return; + } + // means device is offline, clear local subscribe + subManager_->ClearLocalSubscribeQuery(deviceId); + // clear sync task + if (context != nullptr) { + context->ClearAllSyncTask(); + RefObject::DecObjRef(context); + } +} + +void SyncEngine::GetLocalSubscribeQueries(const std::string &device, std::vector &subscribeQueries) +{ + subManager_->GetLocalSubscribeQueries(device, subscribeQueries); +} + +void SyncEngine::GetRemoteSubscribeQueryIds(const std::string &device, std::vector &subscribeQueryIds) +{ + subManager_->GetRemoteSubscribeQueryIds(device, subscribeQueryIds); +} + +void SyncEngine::GetRemoteSubscribeQueries(const std::string &device, std::vector &subscribeQueries) +{ + subManager_->GetRemoteSubscribeQueries(device, subscribeQueries); +} + +void SyncEngine::PutUnfiniedSubQueries(const std::string &device, std::vector &subscribeQueries) +{ + subManager_->PutLocalUnFiniedSubQueries(device, subscribeQueries); +} + +void SyncEngine::GetAllUnFinishSubQueries(std::map> &allSyncQueries) +{ + subManager_->GetAllUnFinishSubQueries(allSyncQueries); +} + +ICommunicator *SyncEngine::AllocCommunicator(const std::string &identifier, int &errCode) +{ + ICommunicatorAggregator *communicatorAggregator = nullptr; + errCode = RuntimeContext::GetInstance()->GetCommunicatorAggregator(communicatorAggregator); + if (communicatorAggregator == nullptr) { + LOGE("[SyncEngine] Get ICommunicatorAggregator error when SetEqualIdentifier err = %d", errCode); + return nullptr; + } + std::vector identifierVect(identifier.begin(), identifier.end()); + auto communicator = communicatorAggregator->AllocCommunicator(identifierVect, errCode); + if (communicator == nullptr) { + LOGE("[SyncEngine] AllocCommunicator error when SetEqualIdentifier! err = %d", errCode); + return communicator; + } + + errCode = communicator->RegOnMessageCallback( + std::bind(&SyncEngine::MessageReciveCallback, this, std::placeholders::_1, std::placeholders::_2), + []() {}); + if (errCode != E_OK) { + LOGE("[SyncEngine] SyncRequestCallback register failed in SetEqualIdentifier! err = %d", errCode); + communicatorAggregator->ReleaseCommunicator(communicator); + return nullptr; + } + + errCode = communicator->RegOnConnectCallback( + std::bind(&DeviceManager::OnDeviceConnectCallback, deviceManager_, + std::placeholders::_1, std::placeholders::_2), nullptr); + if (errCode != E_OK) { + LOGE("[SyncEngine][RegConnCB] register failed in SetEqualIdentifier! err %d", errCode); + communicator->RegOnMessageCallback(nullptr, nullptr); + communicatorAggregator->ReleaseCommunicator(communicator); + return nullptr; + } + + return communicator; +} + +void SyncEngine::UnRegCommunicatorsCallback() +{ + if (communicator_ != nullptr) { + communicator_->RegOnMessageCallback(nullptr, nullptr); + communicator_->RegOnConnectCallback(nullptr, nullptr); + communicator_->RegOnSendableCallback(nullptr, nullptr); + } + std::lock_guard lock(equalCommunicatorsLock_); + for (const auto &iter : equalCommunicators_) { + iter.second->RegOnMessageCallback(nullptr, nullptr); + iter.second->RegOnConnectCallback(nullptr, nullptr); + iter.second->RegOnSendableCallback(nullptr, nullptr); + } +} + +void SyncEngine::ReleaseCommunicators() +{ + RefObject::KillAndDecObjRef(communicatorProxy_); + communicatorProxy_ = nullptr; + ICommunicatorAggregator *communicatorAggregator = nullptr; + int errCode = RuntimeContext::GetInstance()->GetCommunicatorAggregator(communicatorAggregator); + if (communicatorAggregator == nullptr) { + LOGF("[SyncEngine] ICommunicatorAggregator get failed when fialize SyncEngine err %d", errCode); + return; + } + + if (communicator_ != nullptr) { + communicatorAggregator->ReleaseCommunicator(communicator_); + communicator_ = nullptr; + } + + std::lock_guard lock(equalCommunicatorsLock_); + for (auto &iter : equalCommunicators_) { + communicatorAggregator->ReleaseCommunicator(iter.second); + } + equalCommunicators_.clear(); +} + +bool SyncEngine::IsSkipCalculateLen(const Message *inMsg) +{ + if (inMsg->IsFeedbackError()) { + LOGE("[SyncEngine] Feedback Message with errorNo=%u.", inMsg->GetErrorNo()); + return true; + } + return false; +} + +void SyncEngine::GetSubscribeSyncParam(const std::string &device, const QuerySyncObject &query, + InternalSyncParma &outParam) +{ + outParam.devices = { device }; + outParam.mode = SyncModeType::AUTO_SUBSCRIBE_QUERY; + outParam.isQuerySync = true; + outParam.syncQuery = query; +} + +void SyncEngine::GetQueryAutoSyncParam(const std::string &device, const QuerySyncObject &query, + InternalSyncParma &outParam) +{ + outParam.devices = { device }; + outParam.mode = SyncModeType::AUTO_PUSH; + outParam.isQuerySync = true; + outParam.syncQuery = query; +} + +int SyncEngine::StartAutoSubscribeTimer() +{ + return E_OK; +} + +void SyncEngine::StopAutoSubscribeTimer() +{ +} + +int SyncEngine::InitTimeChangedListener() +{ + int errCode = E_OK; + timeChangedListener_ = RuntimeContext::GetInstance()->RegisterTimeChangedLister( + [this](void *changedOffset) { + if (changedOffset == nullptr) { + return; + } + TimeOffset changedTimeOffset = *(reinterpret_cast(changedOffset)) * + static_cast(TimeHelper::TO_100_NS); + TimeOffset orgOffset = this->metadata_->GetLocalTimeOffset() - changedTimeOffset; + Timestamp currentSysTime = TimeHelper::GetSysCurrentTime(); + Timestamp maxItemTime = 0; + this->syncInterface_->GetMaxTimestamp(maxItemTime); + if ((currentSysTime + static_cast(orgOffset)) <= maxItemTime) { + orgOffset = static_cast(maxItemTime - currentSysTime + TimeHelper::MS_TO_100_NS); // 1ms + } + this->metadata_->SaveLocalTimeOffset(orgOffset); + }, errCode); + if (timeChangedListener_ == nullptr) { + LOGE("[SyncEngine] Init RegisterTimeChangedLister failed"); + return errCode; + } + return E_OK; +} + +int SyncEngine::SubscribeLimitCheck(const std::vector &devices, QuerySyncObject &query) const +{ + return subManager_->LocalSubscribeLimitCheck(devices, query); +} + + +void SyncEngine::ClearInnerResource() +{ + if (timeChangedListener_ != nullptr) { + timeChangedListener_->Drop(true); + timeChangedListener_ = nullptr; + } + if (syncInterface_ != nullptr) { + syncInterface_->DecRefCount(); + syncInterface_ = nullptr; + } + if (deviceManager_ != nullptr) { + delete deviceManager_; + deviceManager_ = nullptr; + } + communicator_ = nullptr; + metadata_ = nullptr; + onRemoteDataChanged_ = nullptr; + offlineChanged_ = nullptr; + queryAutoSyncCallback_ = nullptr; +} + +bool SyncEngine::IsEngineActive() const +{ + return isActive_; +} + +void SyncEngine::SchemaChange() +{ + std::lock_guard lock(contextMapLock_); + for (auto &entry : syncTaskContextMap_) { + auto context = entry.second; + if (context->IsKilled()) { + continue; + } + // IncRef for SyncEngine to make sure context is valid, to avoid a big lock + context->SchemaChange(); + } +} + +void SyncEngine::IncExecTaskCount() +{ + std::lock_guard incLock(execTaskCountLock_); + execTaskCount_++; +} + +void SyncEngine::DecExecTaskCount() +{ + { + std::lock_guard decLock(execTaskCountLock_); + execTaskCount_--; + } + execTaskCv_.notify_all(); +} + +void SyncEngine::Dump(int fd) +{ + std::string communicatorLabel; + if (communicatorProxy_ != nullptr) { + communicatorProxy_->GetLocalIdentity(communicatorLabel); + } + DBDumpHelper::Dump(fd, "\tcommunicator label = %s, equalIdentify Info [\n", communicatorLabel.c_str()); + if (communicatorProxy_ != nullptr) { + communicatorProxy_->GetLocalIdentity(communicatorLabel); + communicatorProxy_->Dump(fd); + } + DBDumpHelper::Dump(fd, "\t]\n\tcontext info [\n"); + // dump context info + std::lock_guard autoLock(contextMapLock_); + for (const auto &entry : syncTaskContextMap_) { + entry.second->Dump(fd); + } + DBDumpHelper::Dump(fd, "\t]\n\n"); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/syncer/src/sync_engine.h b/mock/distributeddb/syncer/src/sync_engine.h new file mode 100644 index 00000000..950faa0b --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_engine.h @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNC_ENGINE_H +#define SYNC_ENGINE_H + +#include +#include +#include + +#include "communicator_proxy.h" +#include "device_manager.h" +#include "isync_engine.h" +#include "isync_task_context.h" +#include "subscribe_manager.h" +#include "task_pool.h" + +namespace DistributedDB { +constexpr uint16_t NEW_SEND_TASK = 1; + +class SyncEngine : public ISyncEngine { +public: + SyncEngine(); + ~SyncEngine() override; + + // Do some init things + int Initialize(ISyncInterface *syncInterface, std::shared_ptr &metadata, + const std::function &onRemoteDataChanged, + const std::function &offlineChanged, + const std::function &queryAutoSyncCallback) override; + + // Do some things, when db close. + int Close() override; + + // Alloc and Add sync SyncTarget + // return E_OK if operator success. + int AddSyncOperation(SyncOperation *operation) override; + + // Clear the SyncTarget matched the syncId. + void RemoveSyncOperation(int syncId) override; + + // notify other devices data has changed + void BroadCastDataChanged() const override; + + // Get Online devices + void GetOnlineDevices(std::vector &devices) const override; + + // Register the device connect callback, this function must be called after Engine inited + void RegConnectCallback() override; + + // Get the queue cache memory size + int GetQueueCacheSize() const; + + // Get the number of message which is discarded + unsigned int GetDiscardMsgNum() const; + + // Get the maximum of executing message number + unsigned int GetMaxExecNum() const; + + // Set the maximum of queue cache memory size + void SetMaxQueueCacheSize(int value); + + std::string GetLabel() const override; + + bool GetSyncRetry() const; + void SetSyncRetry(bool isRetry) override; + + // Set an equal identifier for this database, After this called, send msg to the target will use this identifier + int SetEqualIdentifier(const std::string &identifier, const std::vector &targets) override; + + void SetEqualIdentifier() override; + + void SetEqualIdentifierMap(const std::string &identifier, const std::vector &targets) override; + + void OfflineHandleByDevice(const std::string &deviceId); + + void GetLocalSubscribeQueries(const std::string &device, std::vector &subscribeQueries); + + // subscribeQueries item is queryId + void GetRemoteSubscribeQueryIds(const std::string &device, std::vector &subscribeQueryIds); + + void GetRemoteSubscribeQueries(const std::string &device, std::vector &subscribeQueries); + + void PutUnfiniedSubQueries(const std::string &device, std::vector &subscribeQueries); + + void GetAllUnFinishSubQueries(std::map> &allSyncQueries); + + // used by SingleVerSyncer when db online + int StartAutoSubscribeTimer() override; + + // used by SingleVerSyncer when remote/local db closed + void StopAutoSubscribeTimer() override; + + int SubscribeLimitCheck(const std::vector &devices, QuerySyncObject &query) const override; + + bool IsEngineActive() const override; + + void SchemaChange() override; + + void Dump(int fd) override; + +protected: + // Create a context + virtual ISyncTaskContext *CreateSyncTaskContext() = 0; + + // Find SyncTaskContext from the map + ISyncTaskContext *FindSyncTaskContext(const std::string &deviceId); + ISyncTaskContext *GetSyncTaskContextAndInc(const std::string &deviceId); + void GetQueryAutoSyncParam(const std::string &device, const QuerySyncObject &query, InternalSyncParma &outParam); + void GetSubscribeSyncParam(const std::string &device, const QuerySyncObject &query, InternalSyncParma &outParam); + + ISyncInterface *syncInterface_; + // Used to store all send sync task infos (such as pull sync response, and push sync request) + std::map syncTaskContextMap_; + std::mutex contextMapLock_; + std::shared_ptr subManager_; + std::function queryAutoSyncCallback_; + +private: + + // Init DeviceManager set callback + int InitDeviceManager(const std::function &onRemoteDataChanged, + const std::function &offlineChanged); + + int InitTimeChangedListener(); + + ISyncTaskContext *GetSyncTaskContext(const std::string &deviceId, int &errCode); + + // Init Comunicator, register callbacks + int InitComunicator(const ISyncInterface *syncInterface); + + // Add the sync task info to the map. + int AddSyncOperForContext(const std::string &deviceId, SyncOperation *operation); + + // Sync Request CallbackTask run at a sub thread. + void MessageReciveCallbackTask(ISyncTaskContext *context, const ICommunicator *communicator, Message *inMsg); + + void RemoteDataChangedTask(ISyncTaskContext *context, const ICommunicator *communicator, Message *inMsg); + + void ScheduleTaskOut(ISyncTaskContext *context, const ICommunicator *communicator); + + // wrapper of MessageReciveCallbackTask + void MessageReciveCallback(const std::string &targetDev, Message *inMsg); + + // Sync Request Callback + int MessageReciveCallbackInner(const std::string &targetDev, Message *inMsg); + + // Exec the given SyncTarget. and callback onComplete. + int ExecSyncTask(ISyncTaskContext *context); + + // Anti-DOS attack + void PutMsgIntoQueue(const std::string &targetDev, Message *inMsg, int msgSize); + + // Get message size + int GetMsgSize(const Message *inMsg) const; + + // Do not run MessageReceiveCallbackTask until msgQueue is empty + int DealMsgUtilQueueEmpty(); + + // Handle message in order. + int ScheduleDealMsg(ISyncTaskContext *context, Message *inMsg); + + // Schedule Sync Task + void ScheduleSyncTask(ISyncTaskContext *context); + + ISyncTaskContext *GetConextForMsg(const std::string &targetDev, int &errCode); + + int RunPermissionCheck(const std::string &deviceId, uint8_t flag) const; + + ICommunicator *AllocCommunicator(const std::string &identifier, int &errCode); + + void UnRegCommunicatorsCallback(); + + void ReleaseCommunicators(); + + bool IsSkipCalculateLen(const Message *inMsg); + + void ClearInnerResource(); + + static uint8_t GetPermissionCheckFlag(bool isAutoSync, int syncMode); + + void IncExecTaskCount(); + + void DecExecTaskCount(); + + ICommunicator *communicator_; + DeviceManager *deviceManager_; + std::function onRemoteDataChanged_; + std::function offlineChanged_; + std::shared_ptr metadata_; + std::deque msgQueue_; + NotificationChain::Listener *timeChangedListener_; + uint32_t execTaskCount_; + std::string label_; + bool isSyncRetry_; + CommunicatorProxy *communicatorProxy_; + std::mutex equalCommunicatorsLock_; + std::map equalCommunicators_; + + static int queueCacheSize_; + static int maxQueueCacheSize_; + static unsigned int discardMsgNum_; + static const unsigned int MAX_EXEC_NUM = 7; // Set the maximum of threads as 6 < 7 + static constexpr int DEFAULT_CACHE_SIZE = 160 * 1024 * 1024; // Initial the default cache size of queue as 160MB + static std::mutex queueLock_; + std::atomic isActive_; + + // key: device value: equalIdentifier + std::map equalIdentifierMap_; + std::mutex execTaskCountLock_; + std::condition_variable execTaskCv_; +}; +} // namespace DistributedDB + +#endif // SYNC_ENGINE_H diff --git a/mock/distributeddb/syncer/src/sync_operation.cpp b/mock/distributeddb/syncer/src/sync_operation.cpp new file mode 100644 index 00000000..48c7fc7e --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_operation.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sync_operation.h" +#include "db_errno.h" +#include "log_print.h" +#include "performance_analysis.h" + +namespace DistributedDB { +SyncOperation::SyncOperation(uint32_t syncId, const std::vector &devices, + int mode, const UserCallback &userCallback, bool isBlockSync) + : devices_(devices), + syncId_(syncId), + mode_(mode), + userCallback_(userCallback), + isBlockSync_(isBlockSync), + isAutoSync_(false), + isFinished_(false), + semaphore_(nullptr), + query_(QuerySyncObject()), + isQuerySync_(false), + isAutoSubscribe_(false) +{ +} + +SyncOperation::~SyncOperation() +{ + LOGD("SyncOperation::~SyncOperation()"); + Finalize(); +} + +int SyncOperation::Initialize() +{ + LOGD("[SyncOperation] Init SyncOperation id:%d.", syncId_); + AutoLock lockGuard(this); + for (const std::string &deviceId : devices_) { + statuses_.insert(std::pair(deviceId, OP_WAITING)); + } + + if (mode_ == AUTO_PUSH) { + mode_ = PUSH; + isAutoSync_ = true; + } else if (mode_ == AUTO_PULL) { + mode_ = PULL; + isAutoSync_ = true; + } else if (mode_ == AUTO_SUBSCRIBE_QUERY) { + mode_ = SUBSCRIBE_QUERY; + isAutoSubscribe_ = true; + } + if (isBlockSync_) { + semaphore_ = std::make_unique(0); + } + + return E_OK; +} + +void SyncOperation::SetOnSyncFinalize(const OnSyncFinalize &callback) +{ + onFinalize_ = callback; +} + +void SyncOperation::SetOnSyncFinished(const OnSyncFinished &callback) +{ + onFinished_ = callback; +} + +void SyncOperation::SetStatus(const std::string &deviceId, int status) +{ + LOGD("[SyncOperation] SetStatus dev %s{private} status %d", deviceId.c_str(), status); + AutoLock lockGuard(this); + if (IsKilled()) { + LOGE("[SyncOperation] SetStatus failed, the SyncOperation has been killed!"); + return; + } + if (isFinished_) { + LOGI("[SyncOperation] SetStatus already finished"); + return; + } + + auto iter = statuses_.find(deviceId); + if (iter != statuses_.end()) { + if (iter->second >= OP_FINISHED_ALL) { + return; + } + iter->second = status; + return; + } +} + +void SyncOperation::SetUnfinishedDevStatus(int status) +{ + LOGD("[SyncOperation] SetUnfinishedDevStatus status %d", status); + AutoLock lockGuard(this); + if (IsKilled()) { + LOGE("[SyncOperation] SetUnfinishedDevStatus failed, the SyncOperation has been killed!"); + return; + } + if (isFinished_) { + LOGI("[SyncOperation] SetUnfinishedDevStatus already finished"); + return; + } + for (auto &item : statuses_) { + if (item.second >= OP_FINISHED_ALL) { + continue; + } + item.second = status; + } +} + +int SyncOperation::GetStatus(const std::string &deviceId) const +{ + AutoLock lockGuard(this); + auto iter = statuses_.find(deviceId); + if (iter != statuses_.end()) { + return iter->second; + } + return -E_INVALID_ARGS; +} + +uint32_t SyncOperation::GetSyncId() const +{ + return syncId_; +} + +int SyncOperation::GetMode() const +{ + return mode_; +} + +void SyncOperation::Finished() +{ + std::map tmpStatus; + { + AutoLock lockGuard(this); + if (IsKilled() || isFinished_) { + return; + } + isFinished_ = true; + tmpStatus = statuses_; + } + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordEnd(PT_TEST_RECORDS::RECORD_ACK_RECV_TO_USER_CALL_BACK); + } + if (userCallback_) { + LOGI("[SyncOperation] Sync %d finished call onComplete.", syncId_); + if (IsBlockSync()) { + userCallback_(tmpStatus); + } else { + RefObject::IncObjRef(this); + int errCode = RuntimeContext::GetInstance()->ScheduleQueuedTask(identifier_, [this, tmpStatus] { + userCallback_(tmpStatus); + RefObject::DecObjRef(this); + }); + if (errCode != E_OK) { + LOGE("[Finished] SyncOperation Finished userCallback_ retCode:%d", errCode); + RefObject::DecObjRef(this); + } + } + } + if (onFinished_) { + LOGD("[SyncOperation] Sync %d finished call onFinished.", syncId_); + onFinished_(syncId_); + } +} + +const std::vector &SyncOperation::GetDevices() const +{ + return devices_; +} + +void SyncOperation::WaitIfNeed() +{ + if (isBlockSync_ && (semaphore_ != nullptr)) { + LOGD("[SyncOperation] Wait."); + semaphore_->WaitSemaphore(); + } +} + +void SyncOperation::NotifyIfNeed() +{ + if (isBlockSync_ && (semaphore_ != nullptr)) { + LOGD("[SyncOperation] Notify."); + semaphore_->SendSemaphore(); + } +} + +bool SyncOperation::IsAutoSync() const +{ + return isAutoSync_; +} + +bool SyncOperation::IsBlockSync() const +{ + return isBlockSync_; +} + +bool SyncOperation::IsAutoControlCmd() const +{ + return isAutoSubscribe_; +} + +bool SyncOperation::CheckIsAllFinished() const +{ + AutoLock lockGuard(this); + for (const auto &iter : statuses_) { + if (iter.second < OP_FINISHED_ALL) { + return false; + } + } + return true; +} + +void SyncOperation::Finalize() +{ + if ((syncId_ > 0) && onFinalize_) { + LOGD("[SyncOperation] Callback SyncOperation onFinalize."); + onFinalize_(); + } +} + +void SyncOperation::SetQuery(const QuerySyncObject &query) +{ + query_ = query; + isQuerySync_ = true; + if (mode_ != SyncModeType::SUBSCRIBE_QUERY && mode_ != SyncModeType::UNSUBSCRIBE_QUERY) { + mode_ += QUERY_SYNC_MODE_BASE; + } +} + +QuerySyncObject SyncOperation::GetQuery() const +{ + return query_; +} + +bool SyncOperation::IsQuerySync() const +{ + return isQuerySync_; +} + +void SyncOperation::SetIdentifier(const std::vector &identifier) +{ + identifier_.assign(identifier.begin(), identifier.end()); +} + +SyncType SyncOperation::GetSyncType(int mode) +{ + static const std::map syncTypeMap = { + {SyncModeType::PUSH, SyncType::MANUAL_FULL_SYNC_TYPE}, + {SyncModeType::PULL, SyncType::MANUAL_FULL_SYNC_TYPE}, + {SyncModeType::PUSH_AND_PULL, SyncType::MANUAL_FULL_SYNC_TYPE}, + {SyncModeType::RESPONSE_PULL, SyncType::MANUAL_FULL_SYNC_TYPE}, + {SyncModeType::AUTO_PULL, SyncType::AUTO_SYNC_TYPE}, + {SyncModeType::AUTO_PUSH, SyncType::AUTO_SYNC_TYPE}, + {SyncModeType::QUERY_PUSH, SyncType::QUERY_SYNC_TYPE}, + {SyncModeType::QUERY_PULL, SyncType::QUERY_SYNC_TYPE}, + {SyncModeType::QUERY_PUSH_PULL, SyncType::QUERY_SYNC_TYPE}, + }; + auto iter = syncTypeMap.find(mode); + if (iter != syncTypeMap.end()) { + return iter->second; + } + return SyncType::INVALID_SYNC_TYPE; +} + +int SyncOperation::TransferSyncMode(int mode) +{ + // AUTO_PUSH and AUTO_PULL mode is used before sync, RESPONSE_PULL is regarded as push or query push mode. + // so for the three mode, it is no need to transferred. + if (mode >= SyncModeType::QUERY_PUSH && mode <= SyncModeType::QUERY_PUSH_PULL) { + return (mode - QUERY_SYNC_MODE_BASE); + } + return mode; +} + +std::string SyncOperation::GetQueryId() const +{ + return query_.GetIdentify(); +} + +const std::map &SyncOperation::DBStatusTransMap() +{ + static const std::map transMap = { + { static_cast(OP_FINISHED_ALL), OK }, + { static_cast(OP_TIMEOUT), TIME_OUT }, + { static_cast(OP_PERMISSION_CHECK_FAILED), PERMISSION_CHECK_FORBID_SYNC }, + { static_cast(OP_COMM_ABNORMAL), COMM_FAILURE }, + { static_cast(OP_SECURITY_OPTION_CHECK_FAILURE), SECURITY_OPTION_CHECK_ERROR }, + { static_cast(OP_EKEYREVOKED_FAILURE), EKEYREVOKED_ERROR }, + { static_cast(OP_SCHEMA_INCOMPATIBLE), SCHEMA_MISMATCH }, + { static_cast(OP_BUSY_FAILURE), BUSY }, + { static_cast(OP_QUERY_FORMAT_FAILURE), INVALID_QUERY_FORMAT }, + { static_cast(OP_QUERY_FIELD_FAILURE), INVALID_QUERY_FIELD }, + { static_cast(OP_NOT_SUPPORT), NOT_SUPPORT }, + { static_cast(OP_INTERCEPT_DATA_FAIL), INTERCEPT_DATA_FAIL }, + { static_cast(OP_MAX_LIMITS), OVER_MAX_LIMITS }, + { static_cast(OP_SCHEMA_CHANGED), DISTRIBUTED_SCHEMA_CHANGED }, + { static_cast(OP_INVALID_ARGS), INVALID_ARGS }, + { static_cast(OP_USER_CHANGED), USER_CHANGED}, + }; + return transMap; +} +DEFINE_OBJECT_TAG_FACILITIES(SyncOperation) +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/sync_operation.h b/mock/distributeddb/syncer/src/sync_operation.h new file mode 100644 index 00000000..229c8320 --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_operation.h @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNC_OPERATION_H +#define SYNC_OPERATION_H + +#include +#include +#include +#include +#include + +#include "ikvdb_sync_interface.h" +#include "notification_chain.h" +#include "query_sync_object.h" +#include "ref_object.h" +#include "runtime_context.h" +#include "semaphore_utils.h" +#include "sync_types.h" + +namespace DistributedDB { +class SyncOperation : public RefObject { +public: + enum Status { + OP_WAITING = 0, + OP_SYNCING, + OP_SEND_FINISHED, + OP_RECV_FINISHED, + OP_FINISHED_ALL, // status >= OP_FINISHED_ALL is final status. + OP_FAILED, + OP_TIMEOUT, + OP_PERMISSION_CHECK_FAILED, + OP_COMM_ABNORMAL, + OP_SECURITY_OPTION_CHECK_FAILURE, // remote device's SecurityOption not equal to local + OP_EKEYREVOKED_FAILURE, // EKEYREVOKED error + OP_BUSY_FAILURE, + OP_SCHEMA_INCOMPATIBLE, + OP_QUERY_FORMAT_FAILURE, + OP_QUERY_FIELD_FAILURE, + OP_NOT_SUPPORT, + OP_INTERCEPT_DATA_FAIL, + OP_MAX_LIMITS, + OP_SCHEMA_CHANGED, + OP_INVALID_ARGS, + OP_USER_CHANGED + }; + + using UserCallback = std::function)>; + using OnSyncFinished = std::function; + using OnSyncFinalize = std::function; + + SyncOperation(uint32_t syncId, const std::vector &devices, int mode, + const UserCallback &userCallback, bool isBlockSync); + + DISABLE_COPY_ASSIGN_MOVE(SyncOperation); + + // Init the status for callback + int Initialize(); + + // Set the OnSyncFinalize callback + void SetOnSyncFinalize(const OnSyncFinalize &callback); + + // Set the OnSyncFinished callback, it will be called either success or failed. + void SetOnSyncFinished(const OnSyncFinished &callback); + + // Set the sync status, running or finished + void SetStatus(const std::string &deviceId, int status); + + // Set the unfinished devices sync status, running or finished + void SetUnfinishedDevStatus(int status); + + // Set the identifier, used in SyncOperation::Finished + void SetIdentifier(const std::vector &identifier); + + // Get the sync status, running or finished + int GetStatus(const std::string &deviceId) const; + + // Get the sync id. + uint32_t GetSyncId() const; + + // Get the sync mode + int GetMode() const; + + // Used to call the onFinished and caller's on complete + void Finished(); + + // Get the deviceId of this sync status + const std::vector &GetDevices() const; + + // Wait if it's a block sync + void WaitIfNeed(); + + // Notify if it's a block sync + void NotifyIfNeed(); + + // Return if this sync is auto sync + bool IsAutoSync() const; + + // Return if this sync is block sync + bool IsBlockSync() const; + + // Return if this sync is AUTO_SUBSCRIBE_QUERY + bool IsAutoControlCmd() const; + + // Check if All devices sync finished. + bool CheckIsAllFinished() const; + + // For query sync + void SetQuery(const QuerySyncObject &query); + QuerySyncObject GetQuery() const; + bool IsQuerySync() const; + std::string GetQueryId() const; + static SyncType GetSyncType(int mode); + static int TransferSyncMode(int mode); + + static const std::map &DBStatusTransMap(); + +protected: + virtual ~SyncOperation(); + +private: + DECLARE_OBJECT_TAG(SyncOperation); + + // called by destruction + void Finalize(); + + // Transfer sync mode from interface to inner + void TransferQuerySyncMode(); + + // The device list + const std::vector devices_; + + // The Syncid + uint32_t syncId_; + + // The sync mode_ see SyncMode + int mode_; + + // The callback caller registered + UserCallback userCallback_; + + // The callback caller registered, when sync timeout, call + OnSyncFinished onFinished_; + + // The callback caller registered, will be called when destruction. + OnSyncFinalize onFinalize_; + + // The device id we sync with + std::map statuses_; + + // Is this operation is a block sync + bool isBlockSync_; + + // Is this operation is an auto sync + bool isAutoSync_; + + // Is this operation has finished + bool isFinished_; + + // Used for block sync + std::unique_ptr semaphore_; + + QuerySyncObject query_; + bool isQuerySync_; + + bool isAutoSubscribe_; + + // record identifier used to call ScheduleQueuedTask in SyncOperation::Finished + std::string identifier_; +}; +} // namespace DistributedDB + +#endif // SYNC_OPERATION_H diff --git a/mock/distributeddb/syncer/src/sync_state_machine.cpp b/mock/distributeddb/syncer/src/sync_state_machine.cpp new file mode 100644 index 00000000..5fae3682 --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_state_machine.cpp @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sync_state_machine.h" + +#include + +#include "log_print.h" +#include "version.h" + +namespace DistributedDB { +SyncStateMachine::SyncStateMachine() + : syncContext_(nullptr), + storageInterface_(nullptr), + communicator_(nullptr), + metadata_(nullptr), + currentState_(0), + watchDogStarted_(false), + currentSyncProctolVersion_(SINGLE_VER_SYNC_PROCTOL_V3), + saveDataNotifyTimerId_(0), + saveDataNotifyCount_(0) +{ +} + +SyncStateMachine::~SyncStateMachine() +{ + syncContext_ = nullptr; + storageInterface_ = nullptr; + watchDogStarted_ = false; + metadata_ = nullptr; + if (communicator_ != nullptr) { + RefObject::DecObjRef(communicator_); + communicator_ = nullptr; + } +} + +int SyncStateMachine::Initialize(ISyncTaskContext *context, ISyncInterface *syncInterface, + std::shared_ptr &metadata, ICommunicator *communicator) +{ + if ((context == nullptr) || (syncInterface == nullptr) || (metadata == nullptr) || (communicator == nullptr)) { + return -E_INVALID_ARGS; + } + syncContext_ = context; + storageInterface_ = syncInterface; + metadata_ = metadata; + RefObject::IncObjRef(communicator); + communicator_ = communicator; + return E_OK; +} + +int SyncStateMachine::StartSync() +{ + int errCode = syncContext_->IncUsedCount(); + if (errCode != E_OK) { + return errCode; + } + std::lock_guard lock(stateMachineLock_); + errCode = StartSyncInner(); + syncContext_->SafeExit(); + return errCode; +} + +int SyncStateMachine::TimeoutCallback(TimerId timerId) +{ + RefObject::AutoLock lock(syncContext_); + if (syncContext_->IsKilled()) { + return -E_OBJ_IS_KILLED; + } + TimerId timer = syncContext_->GetTimerId(); + if (timer != timerId) { + return -E_UNEXPECTED_DATA; + } + + int retryTime = syncContext_->GetRetryTime(); + if (retryTime >= syncContext_->GetSyncRetryTimes() || !syncContext_->IsSyncTaskNeedRetry()) { + LOGI("[SyncStateMachine][Timeout] TimeoutCallback retryTime:%d", retryTime); + syncContext_->UnlockObj(); + StepToTimeout(timerId); + syncContext_->LockObj(); + return E_OK; + } + retryTime++; + syncContext_->SetRetryTime(retryTime); + // the sequenceid will be managed by dataSync slide windows. + syncContext_->SetRetryStatus(SyncTaskContext::NEED_RETRY); + int timeoutTime = syncContext_->GetSyncRetryTimeout(retryTime); + syncContext_->ModifyTimer(timeoutTime); + LOGI("[SyncStateMachine][Timeout] Schedule task, timeoutTime = %d, retryTime = %d", timeoutTime, retryTime); + SyncStep(); + return E_OK; +} + +void SyncStateMachine::Abort() +{ + RefObject::IncObjRef(syncContext_); + int errCode = RuntimeContext::GetInstance()->ScheduleTask([this]() { + { + std::lock_guard lock(this->stateMachineLock_); + this->AbortInner(); + StopWatchDog(); + currentState_ = 0; + } + RefObject::DecObjRef(this->syncContext_); + }); + if (errCode != E_OK) { + LOGE("[SyncStateMachine][Abort] Abort failed, errCode %d", errCode); + RefObject::DecObjRef(syncContext_); + } +} + +int SyncStateMachine::SwitchMachineState(uint8_t event) +{ + const std::vector &tables = GetStateSwitchTables(); + auto tableIter = std::find_if(tables.begin(), tables.end(), + [this](const StateSwitchTable &table) { + return table.version <= currentSyncProctolVersion_; + }); + if (tableIter == tables.end()) { + LOGE("[SyncStateMachine][SwitchState] Can't find a compatible version by version %u", + currentSyncProctolVersion_); + return -E_NOT_FOUND; + } + + const std::map &table = (*tableIter).switchTable; + auto eventToStateIter = table.find(currentState_); + if (eventToStateIter == table.end()) { + LOGE("[SyncStateMachine][SwitchState] tableVer:%d, Can't find EventToState with currentSate %u", + (*tableIter).version, currentState_); + SetCurStateErrStatus(); + return E_OK; + } + + const EventToState &eventToState = eventToStateIter->second; + auto stateIter = eventToState.find(event); + if (stateIter == eventToState.end()) { + LOGD("[SyncStateMachine][SwitchState] tableVer:%d, Can't find event %u int currentSate %u ignore", + (*tableIter).version, event, currentState_); + return -E_NOT_FOUND; + } + + currentState_ = stateIter->second; + LOGD("[SyncStateMachine][SwitchState] tableVer:%d, from state %u move to state %u with event %u dev %s{private}", + (*tableIter).version, eventToStateIter->first, currentState_, event, syncContext_->GetDeviceId().c_str()); + return E_OK; +} + +void SyncStateMachine::SwitchStateAndStep(uint8_t event) +{ + if (SwitchMachineState(event) == E_OK) { + SyncStepInner(); + } +} + +int SyncStateMachine::ExecNextTask() +{ + while (!syncContext_->IsTargetQueueEmpty()) { + syncContext_->MoveToNextTarget(); + if (syncContext_->IsCurrentSyncTaskCanBeSkipped()) { + syncContext_->SetOperationStatus(SyncOperation::OP_FINISHED_ALL); + continue; + } + int errCode = PrepareNextSyncTask(); + if (errCode != E_OK) { + LOGE("[SyncStateMachine] PrepareSync failed"); + syncContext_->SetOperationStatus(SyncOperation::OP_FAILED); + } + return errCode; + } + // no task left + syncContext_->SetTaskExecStatus(ISyncTaskContext::FINISHED); + syncContext_->Clear(); + LOGD("[SyncStateMachine] All sync task finished!"); + return -E_NO_SYNC_TASK; +} + +int SyncStateMachine::StartWatchDog() +{ + int errCode = syncContext_->StartTimer(); + if (errCode == E_OK) { + watchDogStarted_ = true; + } + return errCode; +} + +int SyncStateMachine::ResetWatchDog() +{ + if (!watchDogStarted_) { + return E_OK; + } + LOGD("[SyncStateMachine][WatchDog] ResetWatchDog."); + syncContext_->StopTimer(); + syncContext_->SetRetryTime(0); + return syncContext_->StartTimer(); +} + +void SyncStateMachine::StopWatchDog() +{ + watchDogStarted_ = false; + LOGD("[SyncStateMachine][WatchDog] StopWatchDog."); + syncContext_->StopTimer(); +} + +bool SyncStateMachine::StartSaveDataNotify(uint32_t sessionId, uint32_t sequenceId, uint32_t inMsgId) +{ + std::lock_guard lockGuard(saveDataNotifyLock_); + if (saveDataNotifyTimerId_ > 0) { + saveDataNotifyCount_ = 0; + LOGW("[SyncStateMachine][SaveDataNotify] timer has been started!"); + return false; + } + + // Incref to make sure context still alive before timer stopped. + RefObject::IncObjRef(syncContext_); + int errCode = RuntimeContext::GetInstance()->SetTimer( + SAVE_DATA_NOTIFY_INTERVAL, + [this, sessionId, sequenceId, inMsgId](TimerId timerId) { + RefObject::IncObjRef(syncContext_); + int ret = RuntimeContext::GetInstance()->ScheduleTask([this, sessionId, sequenceId, inMsgId]() { + DoSaveDataNotify(sessionId, sequenceId, inMsgId); + RefObject::DecObjRef(syncContext_); + }); + if (ret != E_OK) { + LOGE("[SyncStateMachine] [DoSaveDataNotify] ScheduleTask failed errCode %d", ret); + RefObject::DecObjRef(syncContext_); + } + return ret; + }, + [this]() { RefObject::DecObjRef(syncContext_); }, + saveDataNotifyTimerId_); + if (errCode != E_OK) { + LOGW("[SyncStateMachine][SaveDataNotify] start timer failed err %d !", errCode); + return false; + } + return true; +} + +void SyncStateMachine::StopSaveDataNotify() +{ + std::lock_guard lockGuard(saveDataNotifyLock_); + StopSaveDataNotifyNoLock(); +} + +void SyncStateMachine::StopSaveDataNotifyNoLock() +{ + if (saveDataNotifyTimerId_ == 0) { + LOGI("[SyncStateMachine][SaveDataNotify] timer is not started!"); + return; + } + RuntimeContext::GetInstance()->RemoveTimer(saveDataNotifyTimerId_); + saveDataNotifyTimerId_ = 0; + saveDataNotifyCount_ = 0; +} + +bool SyncStateMachine::StartFeedDogForSync(uint32_t time, SyncDirectionFlag flag) +{ + if (flag != SyncDirectionFlag::SEND && flag != SyncDirectionFlag::RECEIVE) { + LOGE("[SyncStateMachine][feedDog] start wrong flag:%d", flag); + return false; + } + + uint8_t cnt = GetFeedDogTimeout(time / SAVE_DATA_NOTIFY_INTERVAL); + LOGI("[SyncStateMachine][feedDog] start cnt:%d, flag:%d", cnt, flag); + + std::lock_guard lockGuard(feedDogLock_[flag]); + watchDogController_[flag].refCount++; + LOGD("af incr refCount = %d", watchDogController_[flag].refCount); + + if (watchDogController_[flag].feedDogTimerId > 0) { + // update the upperLimit, if the new cnt is bigger then last upperLimit + if (cnt > watchDogController_[flag].feedDogUpperLimit) { + LOGD("update feedDogUpperLimit = %d", cnt); + watchDogController_[flag].feedDogUpperLimit = cnt; + } + watchDogController_[flag].feedDogCnt = 0u; + LOGW("[SyncStateMachine][feedDog] timer has been started!, flag:%d", flag); + return false; + } + + // Incref to make sure context still alive before timer stopped. + RefObject::IncObjRef(syncContext_); + watchDogController_[flag].feedDogUpperLimit = cnt; + int errCode = RuntimeContext::GetInstance()->SetTimer( + SAVE_DATA_NOTIFY_INTERVAL, + [this, flag](TimerId timerId) { + RefObject::IncObjRef(syncContext_); + int ret = RuntimeContext::GetInstance()->ScheduleTask([this, flag]() { + DoFeedDogForSync(flag); + RefObject::DecObjRef(syncContext_); + }); + if (ret != E_OK) { + LOGE("[SyncStateMachine] [DoFeedDogForSync] ScheduleTask failed errCode %d", ret); + RefObject::DecObjRef(syncContext_); + } + return ret; + }, + [this]() { RefObject::DecObjRef(syncContext_); }, + watchDogController_[flag].feedDogTimerId); + if (errCode != E_OK) { + LOGW("[SyncStateMachine][feedDog] start timer failed err %d !", errCode); + return false; + } + return true; +} + +uint8_t SyncStateMachine::GetFeedDogTimeout(int timeoutCount) const +{ + if (timeoutCount > UINT8_MAX) { + return UINT8_MAX; + } + return timeoutCount; +} + +void SyncStateMachine::StopFeedDogForSync(SyncDirectionFlag flag) +{ + if (flag != SyncDirectionFlag::SEND && flag != SyncDirectionFlag::RECEIVE) { + LOGE("[SyncStateMachine][feedDog] stop wrong flag:%d", flag); + return; + } + std::lock_guard lockGuard(feedDogLock_[flag]); + StopFeedDogForSyncNoLock(flag); +} + +void SyncStateMachine::StopFeedDogForSyncNoLock(SyncDirectionFlag flag) +{ + if (flag != SyncDirectionFlag::SEND && flag != SyncDirectionFlag::RECEIVE) { + LOGE("[SyncStateMachine][feedDog] stop wrong flag:%d", flag); + return; + } + if (watchDogController_[flag].feedDogTimerId == 0) { + return; + } + LOGI("[SyncStateMachine][feedDog] stop flag:%d", flag); + RuntimeContext::GetInstance()->RemoveTimer(watchDogController_[flag].feedDogTimerId); + watchDogController_[flag].feedDogTimerId = 0; + watchDogController_[flag].feedDogCnt = 0; + watchDogController_[flag].refCount = 0; +} + +void SyncStateMachine::SetCurStateErrStatus() +{ +} + +void SyncStateMachine::DecRefCountOfFeedDogTimer(SyncDirectionFlag flag) +{ + std::lock_guard lockGuard(feedDogLock_[flag]); + if (watchDogController_[flag].feedDogTimerId == 0) { + return; + } + if (--watchDogController_[flag].refCount <= 0) { + LOGD("stop feed dog timer, refcount = %d", watchDogController_[flag].refCount); + StopFeedDogForSyncNoLock(flag); + } + LOGD("af dec refcount = %d", watchDogController_[flag].refCount); +} + +void SyncStateMachine::DoSaveDataNotify(uint32_t sessionId, uint32_t sequenceId, uint32_t inMsgId) +{ + { + std::lock_guard lock(stateMachineLock_); + (void)ResetWatchDog(); + } + std::lock_guard innerLock(saveDataNotifyLock_); + if (saveDataNotifyCount_ >= MAXT_SAVE_DATA_NOTIFY_COUNT) { + StopSaveDataNotifyNoLock(); + return; + } + SendSaveDataNotifyPacket(sessionId, sequenceId, inMsgId); + saveDataNotifyCount_++; + return; +} + +void SyncStateMachine::DoFeedDogForSync(SyncDirectionFlag flag) +{ + { + std::lock_guard lock(stateMachineLock_); + (void)ResetWatchDog(); + } + std::lock_guard innerLock(feedDogLock_[flag]); + if (watchDogController_[flag].feedDogCnt >= watchDogController_[flag].feedDogUpperLimit) { + StopFeedDogForSyncNoLock(flag); + return; + } + watchDogController_[flag].feedDogCnt++; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/syncer/src/sync_state_machine.h b/mock/distributeddb/syncer/src/sync_state_machine.h new file mode 100644 index 00000000..1e633b9b --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_state_machine.h @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNC_STATE_MACHINE_H +#define SYNC_STATE_MACHINE_H + +#include + +#include "isync_interface.h" +#include "isync_state_machine.h" + +namespace DistributedDB { +// the 1st uint8_t is event, the 2nd uint8_t is out state +using EventToState = std::map; + +// The StateSwitchTable with the SyncProctolVersion +struct StateSwitchTable { + uint32_t version = 0; + std::map switchTable; // the 1st uint8_t is current state +}; + +struct WatchDogController { + TimerId feedDogTimerId = 0; + uint8_t feedDogCnt = 0; + uint8_t feedDogUpperLimit = 0; + /* this variable will +1 when call StartFeedDogForSync, -1 when recv one ack, + when it become <= 0, we stop the watch dog. */ + int refCount = 0; +}; + +class SyncStateMachine : public ISyncStateMachine { +public: + SyncStateMachine(); + ~SyncStateMachine() override; + + // Init the SingleVerSyncStateMachine + int Initialize(ISyncTaskContext *context, ISyncInterface *syncInterface, std::shared_ptr &metadata, + ICommunicator *communicator) override; + + // start a sync step + int StartSync() override; + + // call when timeout + int TimeoutCallback(TimerId timerId) override; + + // Force stop the state machine + void Abort() override; + + // start a timer to ResetWatchDog when sync data one (key,value) size bigger than mtu + bool StartFeedDogForSync(uint32_t time, SyncDirectionFlag flag) override; + + uint8_t GetFeedDogTimeout(int timeoutCount) const; + + // stop timer to ResetWatchDog when sync data one (key,value) size bigger than mtu + void StopFeedDogForSync(SyncDirectionFlag flag) override; +protected: + + // SyncOperation is timeout, step to timeout state + virtual void StepToTimeout(TimerId timerId) = 0; + + // Step the SingleVerSyncStateMachine + virtual void SyncStep() = 0; + + // Called by SyncStep, Sub class should realize this function to do machine step + virtual void SyncStepInnerLocked() = 0; + + // Do state machine step with no lock, for inner use + virtual void SyncStepInner() = 0; + + // Called by StartSync, Sub class should realize this function to start statemachine + virtual int StartSyncInner() = 0; + + // Called by Abort, Sub class should realize this function to force abort statemachine + virtual void AbortInner() = 0; + + // while currentstate could not be found, should called, Sub class should realize this function. + virtual void SetCurStateErrStatus(); + + // Used to get instance class' stateSwitchTables + virtual const std::vector &GetStateSwitchTables() const = 0; + + // Called by ExecNextTask, Sub class should realize this function to do some thing for run next sync task + virtual int PrepareNextSyncTask() = 0; + + // Called by StartSaveDataNotifyTimer, Sub class should realize this function to send a heartbeet packet + virtual void SendSaveDataNotifyPacket(uint32_t sessionId, uint32_t sequenceId, uint32_t inMsgId) = 0; + + // Used to parse state table to switch machine state, this function must be called in stateMachineLock + int SwitchMachineState(uint8_t event); + + // Do state switch with the event, and do syncstep + virtual void SwitchStateAndStep(uint8_t event); + + // To Exec next sync task in context targetQueue + int ExecNextTask(); + + // Start a watchdog used for manual sync, when begin a manual sync + int StartWatchDog(); + + // Reset the watchdog used for manual sync + int ResetWatchDog(); + + // stop a watchdog used for manual sync, call when sync finished, + void StopWatchDog(); + + // Start a timer to send data notify packet to keep remote device not timeout + bool StartSaveDataNotify(uint32_t sessionId, uint32_t sequenceId, uint32_t inMsgId); + + // Stop send save data notify + void StopSaveDataNotify(); + + // Stop send save data notify without lock + void StopSaveDataNotifyNoLock(); + + // stop a timer to ResetWatchDog when sync data bigger than mtu without lock + void StopFeedDogForSyncNoLock(SyncDirectionFlag flag); + + void DecRefCountOfFeedDogTimer(SyncDirectionFlag flag); + + virtual void DoSaveDataNotify(uint32_t sessionId, uint32_t sequenceId, uint32_t inMsgId); + + void DoFeedDogForSync(SyncDirectionFlag flag); + + DISABLE_COPY_ASSIGN_MOVE(SyncStateMachine); + + ISyncTaskContext *syncContext_; + ISyncInterface *storageInterface_; + ICommunicator *communicator_; + std::shared_ptr metadata_; + std::mutex stateMachineLock_; + uint8_t currentState_; + bool watchDogStarted_; + uint32_t currentSyncProctolVersion_; + + // For save data notify + static const int SAVE_DATA_NOTIFY_INTERVAL = 2000; // 2s for save data notify + static const int MAXT_SAVE_DATA_NOTIFY_COUNT = 15; // only notify 15 times + static const int SYNC_DIRECTION_NUM = 2; // send receive + std::mutex saveDataNotifyLock_; + TimerId saveDataNotifyTimerId_; + uint8_t saveDataNotifyCount_; + + // used for one (key,value) bigger than mtu size, in this case, send packet need more longger time + std::mutex feedDogLock_[SYNC_DIRECTION_NUM]; + WatchDogController watchDogController_[SYNC_DIRECTION_NUM] = {{0}, {0}}; +}; +} // namespace DistributedDB +#endif // SYNC_STATE_MACHINE_H diff --git a/mock/distributeddb/syncer/src/sync_target.cpp b/mock/distributeddb/syncer/src/sync_target.cpp new file mode 100644 index 00000000..68343ba1 --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_target.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sync_target.h" + +#include "db_errno.h" +#include "sync_operation.h" +#include "log_print.h" + +namespace DistributedDB { +SyncTarget::~SyncTarget() +{ + operation_ = nullptr; +} + +int SyncTarget::GetSyncId() const +{ + if (operation_ == nullptr) { + return 0; + } + return operation_->GetSyncId(); +} + +void SyncTarget::SetTaskType(int taskType) +{ + taskType_ = taskType; +} + +int SyncTarget::GetTaskType() const +{ + return taskType_; +} + +void SyncTarget::SetMode(int mode) +{ + mode_ = mode; +} + +int SyncTarget::GetMode() const +{ + return mode_; +} + +void SyncTarget::SetSyncOperation(SyncOperation *operation) +{ + if ((operation != nullptr) && !operation->IsKilled()) { + operation_ = operation; + mode_ = operation->GetMode(); + taskType_ = REQUEST; + } + operation_ = operation; +} + +void SyncTarget::GetSyncOperation(SyncOperation *&operation) const +{ + if (operation_ == nullptr) { + LOGD("GetSyncOperation is nullptr"); + } + operation = operation_; +} + +bool SyncTarget::IsAutoSync() const +{ + if (operation_ == nullptr) { + return false; + } + return operation_->IsAutoSync(); +} + +uint32_t SyncTarget::GetResponseSessionId() const +{ + return 0; +} +} // namespace DistributedDB + diff --git a/mock/distributeddb/syncer/src/sync_target.h b/mock/distributeddb/syncer/src/sync_target.h new file mode 100644 index 00000000..11df7813 --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_target.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNC_TARGET_H +#define SYNC_TARGET_H + +#include "isync_target.h" + +namespace DistributedDB { +class SyncTarget : public ISyncTarget { +public: + SyncTarget() : operation_(nullptr), taskType_(0), mode_(0) {}; + ~SyncTarget() override; + + // Get the Sync Id of this task + int GetSyncId() const override; + + // Set the type of this task request or response + void SetTaskType(int taskType) override; + + // Get the type of this task request or response + int GetTaskType() const override; + + // Set the mode of this task request or response + void SetMode(int mode) override; + + // Get the mode of this task request or response + int GetMode() const override; + + // Set a SyncOperation + void SetSyncOperation(SyncOperation *operation) override; + + // Get a SyncOperation + void GetSyncOperation(SyncOperation *&operation) const override; + + // Is this target is an auto sync + bool IsAutoSync() const override; + + uint32_t GetResponseSessionId() const override; + +protected: + SyncOperation *operation_; + int taskType_; // sync task or response task; + int mode_; +}; +} // namespace DistributedDB + +#endif // SYNC_TARGET_H diff --git a/mock/distributeddb/syncer/src/sync_task_context.cpp b/mock/distributeddb/syncer/src/sync_task_context.cpp new file mode 100644 index 00000000..c59ae5ab --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_task_context.cpp @@ -0,0 +1,730 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "sync_task_context.h" + +#include +#include + +#include "db_constant.h" +#include "db_dump_helper.h" +#include "db_dfx_adapter.h" +#include "db_errno.h" +#include "hash.h" +#include "isync_state_machine.h" +#include "log_print.h" +#include "time_helper.h" + +namespace DistributedDB { +std::mutex SyncTaskContext::synTaskContextSetLock_; +std::set SyncTaskContext::synTaskContextSet_; + +namespace { + const int NEGOTIATION_LIMIT = 2; +} + +SyncTaskContext::SyncTaskContext() + : syncOperation_(nullptr), + syncId_(0), + mode_(0), + isAutoSync_(false), + status_(0), + taskExecStatus_(0), + syncInterface_(nullptr), + communicator_(nullptr), + stateMachine_(nullptr), + requestSessionId_(0), + lastRequestSessionId_(0), + timeHelper_(nullptr), + remoteSoftwareVersion_(0), + remoteSoftwareVersionId_(0), + isCommNormal_(true), + taskErrCode_(E_OK), + syncTaskRetryStatus_(false), + isSyncRetry_(false), + negotiationCount_(0), + isAutoSubscribe_(false), + isNeedResetAbilitySync_(false) +{ +} + +SyncTaskContext::~SyncTaskContext() +{ + if (stateMachine_ != nullptr) { + delete stateMachine_; + stateMachine_ = nullptr; + } + ClearSyncOperation(); + ClearSyncTarget(); + syncInterface_ = nullptr; + communicator_ = nullptr; +} + +int SyncTaskContext::AddSyncTarget(ISyncTarget *target) +{ + if (target == nullptr) { + return -E_INVALID_ARGS; + } + int targetMode = target->GetMode(); + { + std::lock_guard lock(targetQueueLock_); + if (target->GetTaskType() == ISyncTarget::REQUEST) { + requestTargetQueue_.push_back(target); + } else if (target->GetTaskType() == ISyncTarget::RESPONSE) { + responseTargetQueue_.push_back(target); + } else { + return -E_INVALID_ARGS; + } + } + CancelCurrentSyncRetryIfNeed(targetMode); + if (taskExecStatus_ == RUNNING) { + return E_OK; + } + if (onSyncTaskAdd_) { + RefObject::IncObjRef(this); + int errCode = RuntimeContext::GetInstance()->ScheduleTask([this]() { + onSyncTaskAdd_(); + RefObject::DecObjRef(this); + }); + if (errCode != E_OK) { + RefObject::DecObjRef(this); + } + } + return E_OK; +} + +void SyncTaskContext::SetOperationStatus(int status) +{ + std::lock_guard lock(operationLock_); + if (syncOperation_ == nullptr) { + LOGD("[SyncTaskContext][SetStatus] syncOperation is null"); + return; + } + int finalStatus = status; + + int operationStatus = syncOperation_->GetStatus(deviceId_); + if (status == SyncOperation::OP_SEND_FINISHED && operationStatus == SyncOperation::OP_RECV_FINISHED) { + if (GetTaskErrCode() == -E_EKEYREVOKED) { + finalStatus = SyncOperation::OP_EKEYREVOKED_FAILURE; + } else { + finalStatus = SyncOperation::OP_FINISHED_ALL; + } + } else if (status == SyncOperation::OP_RECV_FINISHED && operationStatus == SyncOperation::OP_SEND_FINISHED) { + if (GetTaskErrCode() == -E_EKEYREVOKED) { + finalStatus = SyncOperation::OP_EKEYREVOKED_FAILURE; + } else { + finalStatus = SyncOperation::OP_FINISHED_ALL; + } + } + syncOperation_->SetStatus(deviceId_, finalStatus); + if (finalStatus >= SyncOperation::OP_FINISHED_ALL) { + SaveLastPushTaskExecStatus(finalStatus); + } + if (syncOperation_->CheckIsAllFinished()) { + syncOperation_->Finished(); + } +} + +void SyncTaskContext::SaveLastPushTaskExecStatus(int finalStatus) +{ + (void)finalStatus; +} + +void SyncTaskContext::Clear() +{ + StopTimer(); + retryTime_ = 0; + sequenceId_ = 1; + syncId_ = 0; + isAutoSync_ = false; + requestSessionId_ = 0; + isNeedRetry_ = NO_NEED_RETRY; + mode_ = SyncModeType::INVALID_MODE; + status_ = SyncOperation::OP_WAITING; + taskErrCode_ = E_OK; + packetId_ = 0; + isAutoSubscribe_ = false; +} + +int SyncTaskContext::RemoveSyncOperation(int syncId) +{ + std::lock_guard lock(targetQueueLock_); + auto iter = std::find_if(requestTargetQueue_.begin(), requestTargetQueue_.end(), + [syncId](const ISyncTarget *target) { + if (target == nullptr) { + return false; + } + return target->GetSyncId() == syncId; + }); + if (iter != requestTargetQueue_.end()) { + if (*iter != nullptr) { + delete *iter; + *iter = nullptr; + } + requestTargetQueue_.erase(iter); + return E_OK; + } + return -E_INVALID_ARGS; +} + +void SyncTaskContext::ClearSyncTarget() +{ + std::lock_guard lock(targetQueueLock_); + for (auto &requestTarget : requestTargetQueue_) { + if (requestTarget != nullptr) { + delete requestTarget; + requestTarget = nullptr; + } + } + requestTargetQueue_.clear(); + + for (auto &responseTarget : responseTargetQueue_) { + if (responseTarget != nullptr) { + delete responseTarget; + responseTarget = nullptr; + } + } + responseTargetQueue_.clear(); +} + +bool SyncTaskContext::IsTargetQueueEmpty() const +{ + std::lock_guard lock(targetQueueLock_); + return requestTargetQueue_.empty() && responseTargetQueue_.empty(); +} + +int SyncTaskContext::GetOperationStatus() const +{ + std::lock_guard lock(operationLock_); + if (syncOperation_ == nullptr) { + return SyncOperation::OP_FINISHED_ALL; + } + return syncOperation_->GetStatus(deviceId_); +} + +void SyncTaskContext::SetMode(int mode) +{ + mode_ = mode; +} + +int SyncTaskContext::GetMode() const +{ + return mode_; +} + +void SyncTaskContext::MoveToNextTarget() +{ + ClearSyncOperation(); + TaskParam param; + // call other system api without lock + param.timeout = communicator_->GetTimeout(deviceId_); + std::lock_guard lock(targetQueueLock_); + while (!requestTargetQueue_.empty() || !responseTargetQueue_.empty()) { + ISyncTarget *tmpTarget = nullptr; + if (!requestTargetQueue_.empty()) { + tmpTarget = requestTargetQueue_.front(); + requestTargetQueue_.pop_front(); + } else { + tmpTarget = responseTargetQueue_.front(); + responseTargetQueue_.pop_front(); + } + if (tmpTarget == nullptr) { + LOGE("[SyncTaskContext][MoveToNextTarget] currentTarget is null skip!"); + continue; + } + SyncOperation *tmpOperation = nullptr; + tmpTarget->GetSyncOperation(tmpOperation); + if ((tmpOperation != nullptr) && tmpOperation->IsKilled()) { + // if killed skip this syncOperation_. + delete tmpTarget; + tmpTarget = nullptr; + continue; + } + CopyTargetData(tmpTarget, param); + delete tmpTarget; + tmpTarget = nullptr; + break; + } +} + +uint32_t SyncTaskContext::GetSyncId() const +{ + return syncId_; +} + +// Get the current task deviceId. +std::string SyncTaskContext::GetDeviceId() const +{ + return deviceId_; +} + +void SyncTaskContext::SetTaskExecStatus(int status) +{ + taskExecStatus_ = status; +} + +int SyncTaskContext::GetTaskExecStatus() const +{ + return taskExecStatus_; +} + +bool SyncTaskContext::IsAutoSync() const +{ + return isAutoSync_; +} + +int SyncTaskContext::StartTimer() +{ + std::lock_guard lockGuard(timerLock_); + if (timerId_ > 0) { + return -E_UNEXPECTED_DATA; + } + TimerId timerId = 0; + RefObject::IncObjRef(this); + TimerAction timeOutCallback = std::bind(&SyncTaskContext::TimeOut, this, std::placeholders::_1); + int errCode = RuntimeContext::GetInstance()->SetTimer(timeout_, timeOutCallback, + [this]() { + int ret = RuntimeContext::GetInstance()->ScheduleTask([this](){ RefObject::DecObjRef(this); }); + if (ret != E_OK) { + LOGE("[SyncTaskContext] timer finalizer ScheduleTask, errCode %d", ret); + } + }, timerId); + if (errCode != E_OK) { + RefObject::DecObjRef(this); + return errCode; + } + timerId_ = timerId; + return errCode; +} + +void SyncTaskContext::StopTimer() +{ + TimerId timerId; + { + std::lock_guard lockGuard(timerLock_); + timerId = timerId_; + if (timerId_ == 0) { + return; + } + timerId_ = 0; + } + RuntimeContext::GetInstance()->RemoveTimer(timerId); +} + +int SyncTaskContext::ModifyTimer(int milliSeconds) +{ + std::lock_guard lockGuard(timerLock_); + if (timerId_ == 0) { + return -E_UNEXPECTED_DATA; + } + return RuntimeContext::GetInstance()->ModifyTimer(timerId_, milliSeconds); +} + +void SyncTaskContext::SetRetryTime(int retryTime) +{ + retryTime_ = retryTime; +} + +int SyncTaskContext::GetRetryTime() const +{ + return retryTime_; +} + +void SyncTaskContext::SetRetryStatus(int isNeedRetry) +{ + isNeedRetry_ = isNeedRetry; +} + +int SyncTaskContext::GetRetryStatus() const +{ + return isNeedRetry_; +} + +TimerId SyncTaskContext::GetTimerId() const +{ + return timerId_; +} + +uint32_t SyncTaskContext::GetRequestSessionId() const +{ + return requestSessionId_; +} + +void SyncTaskContext::IncSequenceId() +{ + sequenceId_++; +} + +uint32_t SyncTaskContext::GetSequenceId() const +{ + return sequenceId_; +} + +void SyncTaskContext::ReSetSequenceId() +{ + sequenceId_ = 1; +} + +void SyncTaskContext::IncPacketId() +{ + packetId_++; +} + +uint64_t SyncTaskContext::GetPacketId() const +{ + return packetId_; +} + +int SyncTaskContext::GetTimeoutTime() const +{ + return timeout_; +} + +void SyncTaskContext::SetTimeoutCallback(const TimerAction &timeOutCallback) +{ + timeOutCallback_ = timeOutCallback; +} + +void SyncTaskContext::SetTimeOffset(TimeOffset offset) +{ + timeOffset_ = offset; +} + +TimeOffset SyncTaskContext::GetTimeOffset() const +{ + return timeOffset_; +} + +int SyncTaskContext::StartStateMachine() +{ + return stateMachine_->StartSync(); +} + +int SyncTaskContext::ReceiveMessageCallback(Message *inMsg) +{ + int errCode = E_OK; + if (IncUsedCount() == E_OK) { + errCode = stateMachine_->ReceiveMessageCallback(inMsg); + SafeExit(); + } + return errCode; +} + +void SyncTaskContext::RegOnSyncTask(const std::function &callback) +{ + onSyncTaskAdd_ = callback; +} + +int SyncTaskContext::IncUsedCount() +{ + AutoLock lock(this); + if (IsKilled()) { + LOGI("[SyncTaskContext] IncUsedCount isKilled"); + return -E_OBJ_IS_KILLED; + } + usedCount_++; + return E_OK; +} + +void SyncTaskContext::SafeExit() +{ + AutoLock lock(this); + usedCount_--; + if (usedCount_ < 1) { + safeKill_.notify_one(); + } +} + +Timestamp SyncTaskContext::GetCurrentLocalTime() const +{ + if (timeHelper_ == nullptr) { + return TimeHelper::INVALID_TIMESTAMP; + } + return timeHelper_->GetTime(); +} + +void SyncTaskContext::Abort(int status) +{ + (void)status; + Clear(); +} + +void SyncTaskContext::CommErrHandlerFunc(int errCode, ISyncTaskContext *context, int32_t sessionId) +{ + { + std::lock_guard lock(synTaskContextSetLock_); + if (synTaskContextSet_.count(context) == 0) { + LOGI("[SyncTaskContext][CommErrHandle] context has been killed"); + return; + } + + // IncObjRef to maker sure context not been killed. after the lock_guard + RefObject::IncObjRef(context); + } + + static_cast(context)->CommErrHandlerFuncInner(errCode, static_cast(sessionId)); + RefObject::DecObjRef(context); +} + +void SyncTaskContext::SetRemoteSoftwareVersion(uint32_t version) +{ + std::lock_guard lock(remoteSoftwareVersionLock_); + remoteSoftwareVersion_ = version; + remoteSoftwareVersionId_++; +} + +uint32_t SyncTaskContext::GetRemoteSoftwareVersion() const +{ + std::lock_guard lock(remoteSoftwareVersionLock_); + return remoteSoftwareVersion_; +} + +uint64_t SyncTaskContext::GetRemoteSoftwareVersionId() const +{ + std::lock_guard lock(remoteSoftwareVersionLock_); + return remoteSoftwareVersionId_; +} + +bool SyncTaskContext::IsCommNormal() const +{ + return isCommNormal_; +} + +void SyncTaskContext::CommErrHandlerFuncInner(int errCode, uint32_t sessionId) +{ + { + RefObject::AutoLock lock(this); + if (sessionId != requestSessionId_) { + return; + } + + if (errCode == E_OK) { + // when communicator sent message failed, the state machine will get the error and exit this sync task + // it seems unnecessary to change isCommNormal_ value, so just return here + return; + } + + isCommNormal_ = false; + } + LOGE("[SyncTaskContext][CommErr] errCode %d", errCode); + stateMachine_->CommErrAbort(); +} + +int SyncTaskContext::TimeOut(TimerId id) +{ + int errCode = E_OK; + if (!timeOutCallback_) { + return errCode; + } + if (IncUsedCount() == E_OK) { + errCode = timeOutCallback_(id); + SafeExit(); + } + return errCode; +} + +void SyncTaskContext::CopyTargetData(const ISyncTarget *target, const TaskParam &taskParam) +{ + mode_ = target->GetMode(); + status_ = SyncOperation::OP_SYNCING; + isNeedRetry_ = SyncTaskContext::NO_NEED_RETRY; + taskErrCode_ = E_OK; + packetId_ = 0; + isCommNormal_ = true; // reset comm status here + syncTaskRetryStatus_ = isSyncRetry_; + timeout_ = static_cast(taskParam.timeout); + negotiationCount_ = 0; + target->GetSyncOperation(syncOperation_); + ReSetSequenceId(); + + if (syncOperation_ != nullptr) { + // IncRef for syncOperation_ to make sure syncOperation_ is valid, when setStatus + RefObject::IncObjRef(syncOperation_); + syncId_ = syncOperation_->GetSyncId(); + isAutoSync_ = syncOperation_->IsAutoSync(); + isAutoSubscribe_ = syncOperation_->IsAutoControlCmd(); + if (isAutoSync_ || mode_ == SUBSCRIBE_QUERY || mode_ == UNSUBSCRIBE_QUERY) { + syncTaskRetryStatus_ = true; + } + requestSessionId_ = Hash::Hash32Func(deviceId_ + std::to_string(syncId_) + + std::to_string(TimeHelper::GetSysCurrentTime())); + if (lastRequestSessionId_ == requestSessionId_) { + // Hash32Func max is 0x7fffffff and UINT32_MAX is 0xffffffff + requestSessionId_++; + LOGI("last sessionId is equal to this sessionId!"); + } + LOGI("[SyncTaskContext][copyTarget] mode=%d,syncId=%d,isAutoSync=%d,isRetry=%d,dev=%s{private}", + mode_, syncId_, isAutoSync_, syncTaskRetryStatus_, deviceId_.c_str()); + lastRequestSessionId_ = requestSessionId_; + DBDfxAdapter::StartAsyncTrace(syncActionName_, static_cast(syncId_)); + } else { + isAutoSync_ = false; + LOGI("[SyncTaskContext][copyTarget] for response data dev %s{private},isRetry=%d", deviceId_.c_str(), + syncTaskRetryStatus_); + } +} + +void SyncTaskContext::KillWait() +{ + StopTimer(); + stateMachine_->Abort(); + LOGW("[SyncTaskContext] Try to kill a context, now wait."); + bool noDeadLock = WaitLockedUntil( + safeKill_, + [this]() { + if (usedCount_ < 1) { + return true; + } + return false; + }, + KILL_WAIT_SECONDS); + if (!noDeadLock) { + LOGE("[SyncTaskContext] Dead lock may happen, we stop waiting the task exit."); + } else { + LOGW("[SyncTaskContext] Wait the task exit ok."); + } + std::lock_guard lock(synTaskContextSetLock_); + synTaskContextSet_.erase(this); +} + +void SyncTaskContext::ClearSyncOperation() +{ + std::lock_guard lock(operationLock_); + if (syncOperation_ != nullptr) { + DBDfxAdapter::FinishAsyncTrace(syncActionName_, static_cast(syncId_)); + RefObject::DecObjRef(syncOperation_); + syncOperation_ = nullptr; + } +} + +void SyncTaskContext::CancelCurrentSyncRetryIfNeed(int newTargetMode) +{ + AutoLock(this); + if (!isAutoSync_) { + return; + } + int mode = SyncOperation::TransferSyncMode(newTargetMode); + if (newTargetMode == mode_ || mode == SyncModeType::PUSH_AND_PULL) { + SetRetryTime(AUTO_RETRY_TIMES); + ModifyTimer(timeout_); + } +} + +int SyncTaskContext::GetTaskErrCode() const +{ + return taskErrCode_; +} + +void SyncTaskContext::SetTaskErrCode(int errCode) +{ + taskErrCode_ = errCode; +} + +bool SyncTaskContext::IsSyncTaskNeedRetry() const +{ + return syncTaskRetryStatus_; +} + +void SyncTaskContext::SetSyncRetry(bool isRetry) +{ + isSyncRetry_ = isRetry; +} + +int SyncTaskContext::GetSyncRetryTimes() const +{ + if (IsAutoSync() || mode_ == SUBSCRIBE_QUERY || mode_ == UNSUBSCRIBE_QUERY) { + return AUTO_RETRY_TIMES; + } + return MANUAL_RETRY_TIMES; +} + +int SyncTaskContext::GetSyncRetryTimeout(int retryTime) const +{ + int timeoutTime = GetTimeoutTime(); + if (IsAutoSync()) { + // set the new timeout value with 2 raised to the power of retryTime. + return timeoutTime * static_cast(pow(2, retryTime)); + } + return timeoutTime; +} + +void SyncTaskContext::ClearAllSyncTask() +{ +} + +bool SyncTaskContext::IsAutoLiftWaterMark() const +{ + return negotiationCount_ < NEGOTIATION_LIMIT; +} + +void SyncTaskContext::IncNegotiationCount() +{ + negotiationCount_++; +} + +bool SyncTaskContext::IsNeedTriggerQueryAutoSync(Message *inMsg, QuerySyncObject &query) +{ + return stateMachine_->IsNeedTriggerQueryAutoSync(inMsg, query); +} + +bool SyncTaskContext::IsAutoSubscribe() const +{ + return isAutoSubscribe_; +} + +bool SyncTaskContext::GetIsNeedResetAbilitySync() const +{ + return isNeedResetAbilitySync_; +} + +void SyncTaskContext::SetIsNeedResetAbilitySync(bool isNeedReset) +{ + isNeedResetAbilitySync_ = isNeedReset; +} + +bool SyncTaskContext::IsCurrentSyncTaskCanBeSkipped() const +{ + return false; +} + +void SyncTaskContext::ResetLastPushTaskStatus() +{ +} + +void SyncTaskContext::SchemaChange() +{ + SetIsNeedResetAbilitySync(true); +} + +void SyncTaskContext::Dump(int fd) +{ + size_t totalSyncTaskCount = 0u; + size_t autoSyncTaskCount = 0u; + size_t reponseTaskCount = 0u; + { + std::lock_guard lock(targetQueueLock_); + totalSyncTaskCount = requestTargetQueue_.size() + responseTargetQueue_.size(); + for (const auto &target : requestTargetQueue_) { + if (target->IsAutoSync()) { + autoSyncTaskCount++; + } + } + reponseTaskCount = responseTargetQueue_.size(); + } + DBDumpHelper::Dump(fd, "\t\ttarget = %s, total sync task count = %zu, auto sync task count = %zu," + " response task count = %zu\n", + deviceId_.c_str(), totalSyncTaskCount, autoSyncTaskCount, reponseTaskCount); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/syncer/src/sync_task_context.h b/mock/distributeddb/syncer/src/sync_task_context.h new file mode 100644 index 00000000..c7fffc87 --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_task_context.h @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNC_TASK_CONTEXT_H +#define SYNC_TASK_CONTEXT_H + +#include +#include + +#include "icommunicator.h" +#include "ikvdb_sync_interface.h" +#include "isync_task_context.h" +#include "meta_data.h" +#include "runtime_context.h" +#include "semaphore_utils.h" +#include "sync_operation.h" +#include "sync_target.h" +#include "time_helper.h" + +namespace DistributedDB { +enum SyncDirectionFlag { + SEND = 0, + RECEIVE = 1, +}; +struct TaskParam { + uint32_t timeout = 0; +}; +class ISyncStateMachine; + +class SyncTaskContext : public ISyncTaskContext { +public: + SyncTaskContext(); + + // Add a sync task target to the queue + int AddSyncTarget(ISyncTarget *target) override; + + // Set the status of this task + void SetOperationStatus(int status) override; + + // Clear context data + void Clear() override; + + // remove a sync target by syncId + int RemoveSyncOperation(int syncId) override; + + // If the requestTargetQueue is empty + bool IsTargetQueueEmpty() const override; + + // Get the status of this task + int GetOperationStatus() const override; + + // Set the mode of this task + void SetMode(int mode) override; + + // Get the mode of this task + int GetMode() const override; + + // Move to next target to sync + void MoveToNextTarget() override; + + // Get the current task syncId + uint32_t GetSyncId() const override; + + // Get the current task deviceId. + std::string GetDeviceId() const override; + + // Set the sync task queue exec status + void SetTaskExecStatus(int status) override; + + // Get the sync task queue exec status + int GetTaskExecStatus() const override; + + // Return if now is doing auto sync + bool IsAutoSync() const override; + + // Set a Timer used for timeout + int StartTimer() override; + + // delete timer + void StopTimer() override; + + // modify timer + int ModifyTimer(int milliSeconds) override; + + // Set a RetryTime for the sync task + void SetRetryTime(int retryTime) override; + + // Get a RetryTime for the sync task + int GetRetryTime() const override; + + // Set Retry status for the sync task + void SetRetryStatus(int isNeedRetry) override; + + // Get Retry status for the sync task + int GetRetryStatus() const override; + + TimerId GetTimerId() const override; + + // Inc the current message sequenceId + void IncSequenceId() override; + + // Get the current initiactive sync session id + uint32_t GetRequestSessionId() const override; + + // Get the current message sequence id + uint32_t GetSequenceId() const override; + + void ReSetSequenceId() override; + + void IncPacketId(); + + uint64_t GetPacketId() const; + + // Get the current watch timeout time + int GetTimeoutTime() const override; + + void SetTimeoutCallback(const TimerAction &timeOutCallback) override; + + // Start the sync state machine + int StartStateMachine() override; + + // Set the timeoffset with the remote device + void SetTimeOffset(TimeOffset offset) override; + + // Get the timeoffset with the remote device + TimeOffset GetTimeOffset() const override; + + // Used for sync message callback + int ReceiveMessageCallback(Message *inMsg) override; + + // used to register a callback, called when new SyncTarget added + void RegOnSyncTask(const std::function &callback) override; + + // When schedule a new task, should call this function to inc usedcount + int IncUsedCount() override; + + // When schedule task exit, should call this function to dec usedcount + void SafeExit() override; + + // Get current local time from TimeHelper + Timestamp GetCurrentLocalTime() const override; + + // Set the remount software version num + void SetRemoteSoftwareVersion(uint32_t version) override; + + // Get the remount software version num + uint32_t GetRemoteSoftwareVersion() const override; + + // Get the remount software version id, when called GetRemoteSoftwareVersion this id will be increase. + // Used to check if the version num is is overdue + uint64_t GetRemoteSoftwareVersionId() const override; + + // Judge if the communicator is normal + bool IsCommNormal() const override; + + // If ability sync request set version, need call this function. + // Should be called with ObjLock + virtual void Abort(int status); + + // Used in send msg, as execution is asynchronous, should use this function to handle result. + static void CommErrHandlerFunc(int errCode, ISyncTaskContext *context, int32_t sessionId); + + int GetTaskErrCode() const override; + + void SetTaskErrCode(int errCode) override; + + bool IsSyncTaskNeedRetry() const override; + + void SetSyncRetry(bool isRetry) override; + + int GetSyncRetryTimes() const override; + + int GetSyncRetryTimeout(int retryTime) const override; + + void ClearAllSyncTask() override; + + bool IsAutoLiftWaterMark() const override; + + void IncNegotiationCount() override; + + // check if need trigger query auto sync and get query from inMsg + bool IsNeedTriggerQueryAutoSync(Message *inMsg, QuerySyncObject &query) override; + + bool IsAutoSubscribe() const override; + + bool IsCurrentSyncTaskCanBeSkipped() const override; + + virtual void ResetLastPushTaskStatus(); + + bool GetIsNeedResetAbilitySync() const; + + void SetIsNeedResetAbilitySync(bool isNeedReset) override; + + void SchemaChange() override; + + void Dump(int fd) override; + +protected: + const static int KILL_WAIT_SECONDS = INT32_MAX; + + ~SyncTaskContext() override; + + virtual int TimeOut(TimerId id); + + virtual void CopyTargetData(const ISyncTarget *target, const TaskParam &taskParam); + + void CommErrHandlerFuncInner(int errCode, uint32_t sessionId); + + void KillWait(); + + void ClearSyncOperation(); + + void ClearSyncTarget(); + + void CancelCurrentSyncRetryIfNeed(int newTargetMode); + + virtual void SaveLastPushTaskExecStatus(int finalStatus); + + mutable std::mutex targetQueueLock_; + std::list requestTargetQueue_; + std::list responseTargetQueue_; + SyncOperation *syncOperation_; + mutable std::mutex operationLock_; + uint32_t syncId_; + int mode_; + bool isAutoSync_; + int status_; + int taskExecStatus_; + std::string deviceId_; + std::string syncActionName_; + ISyncInterface *syncInterface_; + ICommunicator *communicator_; + ISyncStateMachine *stateMachine_; + TimeOffset timeOffset_ = 0; + int retryTime_ = 0; + int isNeedRetry_ = SyncTaskContext::NO_NEED_RETRY; + uint32_t requestSessionId_ = 0; + uint32_t lastRequestSessionId_ = 0; + uint32_t sequenceId_ = 1; + std::function onSyncTaskAdd_; + + // for safe exit + std::condition_variable safeKill_; + int usedCount_ = 0; + + // for timeout callback + std::mutex timerLock_; + TimerId timerId_ = 0; + int timeout_ = 1000; // 1000ms + TimerAction timeOutCallback_; + std::unique_ptr timeHelper_; + + // for version sync + mutable std::mutex remoteSoftwareVersionLock_; + uint32_t remoteSoftwareVersion_; + uint64_t remoteSoftwareVersionId_; // Check if the remoteSoftwareVersion_ is is overdue + + bool isCommNormal_; + int taskErrCode_; + uint64_t packetId_ = 0; // used for assignment to reSendMap_.ReSendInfo.packetId in 103 version or above + bool syncTaskRetryStatus_; + bool isSyncRetry_; + uint32_t negotiationCount_; + bool isAutoSubscribe_; + // syncFinished_ need to set false if isNeedResetSyncFinished_ is true when start do abilitySync interface + std::atomic isNeedResetAbilitySync_; + + // For global ISyncTaskContext Set, used by CommErrCallback. + static std::mutex synTaskContextSetLock_; + static std::set synTaskContextSet_; +}; +} // namespace DistributedDB + +#endif // SYNC_TASK_CONTEXT_H diff --git a/mock/distributeddb/syncer/src/sync_types.h b/mock/distributeddb/syncer/src/sync_types.h new file mode 100644 index 00000000..3af5663d --- /dev/null +++ b/mock/distributeddb/syncer/src/sync_types.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNC_TYPES_H +#define SYNC_TYPES_H + +#include +#include "query_sync_object.h" +#include "sync_config.h" + +namespace DistributedDB { +enum MessageId { + TIME_SYNC_MESSAGE = 1, + DATA_SYNC_MESSAGE, + COMMIT_HISTORY_SYNC_MESSAGE, + MULTI_VER_DATA_SYNC_MESSAGE, + VALUE_SLICE_SYNC_MESSAGE, + LOCAL_DATA_CHANGED, + ABILITY_SYNC_MESSAGE, + QUERY_SYNC_MESSAGE, + CONTROL_SYNC_MESSAGE, + UNKNOW_MESSAGE, +}; + +enum SyncModeType { + PUSH, + PULL, + PUSH_AND_PULL, + AUTO_PUSH, + AUTO_PULL, + RESPONSE_PULL, + QUERY_PUSH, + QUERY_PULL, + QUERY_PUSH_PULL, + SUBSCRIBE_QUERY, + UNSUBSCRIBE_QUERY, + AUTO_SUBSCRIBE_QUERY, + INVALID_MODE +}; + +enum class SyncType { + MANUAL_FULL_SYNC_TYPE = 1, + AUTO_SYNC_TYPE, + QUERY_SYNC_TYPE, + INVALID_SYNC_TYPE, +}; + +enum ControlCmdType { + SUBSCRIBE_QUERY_CMD, + UNSUBSCRIBE_QUERY_CMD, + INVALID_CONTROL_CMD, +}; + +struct UpdateWaterMark { + bool normalUpdateMark = false; + bool deleteUpdateMark = false; +}; + +struct InternalSyncParma { + std::vector devices; + int mode = 0; + bool isQuerySync = false; + QuerySyncObject syncQuery; +}; + +constexpr int NOT_SURPPORT_SEC_CLASSIFICATION = 0xff; +constexpr uint8_t QUERY_SYNC_MODE_BASE = SyncModeType::QUERY_PUSH; +constexpr int AUTO_RETRY_TIMES = 3; +constexpr int MANUAL_RETRY_TIMES = 1; +constexpr int TIME_SYNC_WAIT_TIME = 5000; // 5s +constexpr uint64_t MAX_PACKETID = 10000000000; // max packetId +constexpr int NOTIFY_MIN_MTU_SIZE = 30 * 1024; // 30k + +constexpr int MAX_SUBSCRIBE_NUM_PER_DEV = 4; +constexpr int MAX_SUBSCRIBE_NUM_PER_DB = 8; +constexpr size_t MAX_DEVICES_NUM = 32; + +// index 0 for packetId in data request +// if ack reserve size is 1, reserve is {localWaterMark} +// if ack reserve size is above 2, reserve is {localWaterMark, packetId, deletedWaterMark...} +constexpr uint32_t REQUEST_PACKET_RESERVED_INDEX_PACKETID = 0; +constexpr uint32_t ACK_PACKET_RESERVED_INDEX_PACKETID = 1; // index 1 for packetId +constexpr uint32_t ACK_PACKET_RESERVED_INDEX_LOCAL_WATER_MARK = 0; // index 0 for localWaterMark +constexpr uint32_t ACK_PACKET_RESERVED_INDEX_DELETE_WATER_MARK = 2; // index 2 for deleteDataWaterMark +constexpr uint64_t MAX_TIMESTAMP = INT64_MAX; +constexpr uint8_t REMOVE_DEVICE_DATA_MARK = 1; +constexpr uint8_t SUPPORT_MARK = 1; // used for set is support one ability +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/syncer_factory.cpp b/mock/distributeddb/syncer/src/syncer_factory.cpp new file mode 100644 index 00000000..b0c8d9d4 --- /dev/null +++ b/mock/distributeddb/syncer/src/syncer_factory.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "syncer_factory.h" + +#include "ikvdb_sync_interface.h" +#include "multi_ver_syncer.h" +#include "single_ver_kv_syncer.h" +#ifdef RELATIONAL_STORE +#include "single_ver_relational_syncer.h" +#endif + +namespace DistributedDB { +std::shared_ptr SyncerFactory::GetSyncer(int type) +{ + if (type == ISyncInterface::SYNC_SVD) { + std::shared_ptr singleVerSyncer(std::make_shared()); + return singleVerSyncer; +#ifndef OMIT_MULTI_VER + } else if (type == ISyncInterface::SYNC_MVD) { + std::shared_ptr multiVerSyncer(std::make_shared()); + return multiVerSyncer; +#endif +#ifdef RELATIONAL_STORE + } else if (type == ISyncInterface::SYNC_RELATION) { + std::shared_ptr relationalSyncer(std::make_shared()); + return relationalSyncer; +#endif + } else { + return nullptr; + } +} +} // namespace DistributedDB diff --git a/mock/distributeddb/syncer/src/syncer_factory.h b/mock/distributeddb/syncer/src/syncer_factory.h new file mode 100644 index 00000000..20e1c45e --- /dev/null +++ b/mock/distributeddb/syncer/src/syncer_factory.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 SYNCER_FACTORY_H +#define SYNCER_FACTORY_H + +#include + +#include "isyncer.h" + +namespace DistributedDB { +class SyncerFactory final { +public: + // Product a ISyncer for the given type + // type can be : IKvDBSyncInterface::SYNC_SVD + // IKvDBSyncInterface::SYNC_MVD + static std::shared_ptr GetSyncer(int type); +}; +} // namespace DistributedDB + +#endif // SYNCER_FACTORY_H diff --git a/mock/distributeddb/syncer/src/syncer_proxy.cpp b/mock/distributeddb/syncer/src/syncer_proxy.cpp new file mode 100644 index 00000000..1e28076b --- /dev/null +++ b/mock/distributeddb/syncer/src/syncer_proxy.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "syncer_proxy.h" + +#include "syncer_factory.h" +#include "db_errno.h" +#include "log_print.h" + +namespace DistributedDB { +SyncerProxy::SyncerProxy() + : syncer_(nullptr) +{ +} + +int SyncerProxy::Initialize(ISyncInterface *syncInterface, bool isNeedActive) +{ + if (syncInterface == nullptr) { + return -E_INVALID_ARGS; + } + + int interfaceType = syncInterface->GetInterfaceType(); + { + std::lock_guard lock(syncerLock_); + if (syncer_ == nullptr) { + syncer_ = SyncerFactory::GetSyncer(interfaceType); + } + } + if (syncer_ == nullptr) { + LOGF("syncer create failed! invalid interface type %d", interfaceType); + return -E_OUT_OF_MEMORY; + } + + return syncer_->Initialize(syncInterface, isNeedActive); +} + +int SyncerProxy::Close(bool isClosedOperation) +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->Close(isClosedOperation); +} + +int SyncerProxy::Sync(const std::vector &devices, int mode, + const std::function &)> &onComplete, + const std::function &onFinalize, bool wait) +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->Sync(devices, mode, onComplete, onFinalize, wait); +} + +int SyncerProxy::Sync(const SyncParma &parma, uint64_t connectionId) +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->Sync(parma, connectionId); +} + +int SyncerProxy::RemoveSyncOperation(int syncId) +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->RemoveSyncOperation(syncId); +} + +int SyncerProxy::StopSync(uint64_t connectionId) +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->StopSync(connectionId); +} + +uint64_t SyncerProxy::GetTimestamp() +{ + if (syncer_ == nullptr) { + return SyncerFactory::GetSyncer(ISyncInterface::SYNC_SVD)->GetTimestamp(); + } + return syncer_->GetTimestamp(); +} + +void SyncerProxy::EnableAutoSync(bool enable) +{ + if (syncer_ == nullptr) { + return; + } + syncer_->EnableAutoSync(enable); +} + +int SyncerProxy::EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash) +{ + return syncer_->EraseDeviceWaterMark(deviceId, isNeedHash, ""); +} + +int SyncerProxy::EraseDeviceWaterMark(const std::string &deviceId, bool isNeedHash, + const std::string &tableName) +{ + if (syncer_ == nullptr) { + LOGE("[SyncerProxy] Syncer no init, unknown rule to erase waterMark!"); + return -E_NOT_INIT; + } + return syncer_->EraseDeviceWaterMark(deviceId, isNeedHash, tableName); +} + +void SyncerProxy::LocalDataChanged(int notifyEvent) +{ + if (syncer_ == nullptr) { + return; + } + syncer_->LocalDataChanged(notifyEvent); +} + +int SyncerProxy::GetQueuedSyncSize(int *queuedSyncSize) const +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->GetQueuedSyncSize(queuedSyncSize); +} + +int SyncerProxy::SetQueuedSyncLimit(const int *queuedSyncLimit) +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->SetQueuedSyncLimit(queuedSyncLimit); +} + +int SyncerProxy::GetQueuedSyncLimit(int *queuedSyncLimit) const +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->GetQueuedSyncLimit(queuedSyncLimit); +} + +int SyncerProxy::DisableManualSync(void) +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->DisableManualSync(); +} + +int SyncerProxy::EnableManualSync(void) +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->EnableManualSync(); +} + +int SyncerProxy::GetLocalIdentity(std::string &outTarget) const +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->GetLocalIdentity(outTarget); +} + +int SyncerProxy::SetStaleDataWipePolicy(WipePolicy policy) +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->SetStaleDataWipePolicy(policy); +} + +int SyncerProxy::SetSyncRetry(bool isRetry) +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->SetSyncRetry(isRetry); +} + +int SyncerProxy::SetEqualIdentifier(const std::string &identifier, const std::vector &targets) +{ + if (syncer_ == nullptr) { + return -E_NOT_INIT; + } + return syncer_->SetEqualIdentifier(identifier, targets); +} + +void SyncerProxy::Dump(int fd) +{ + if (syncer_ == nullptr) { + return; + } + return syncer_->Dump(fd); +} + +SyncerBasicInfo SyncerProxy::DumpSyncerBasicInfo() +{ + if (syncer_ == nullptr) { + return SyncerBasicInfo { false, false, false }; + } + return syncer_->DumpSyncerBasicInfo(); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/syncer/src/time_helper.cpp b/mock/distributeddb/syncer/src/time_helper.cpp new file mode 100644 index 00000000..0ea1f4f2 --- /dev/null +++ b/mock/distributeddb/syncer/src/time_helper.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "time_helper.h" + +#include "db_errno.h" +#include "log_print.h" +#include "platform_specific.h" + +namespace DistributedDB { +std::mutex TimeHelper::systemTimeLock_; +Timestamp TimeHelper::lastSystemTimeUs_ = 0; +Timestamp TimeHelper::currentIncCount_ = 0; + +Timestamp TimeHelper::GetSysCurrentTime() +{ + uint64_t curTime = 0; + std::lock_guard lock(systemTimeLock_); + int errCode = OS::GetCurrentSysTimeInMicrosecond(curTime); + if (errCode != E_OK) { + return INVALID_TIMESTAMP; + } + + // If GetSysCurrentTime in 1us, we need increase the currentIncCount_ + if (curTime == lastSystemTimeUs_) { + // if the currentIncCount_ has been increased MAX_INC_COUNT, keep the currentIncCount_ + if (currentIncCount_ < MAX_INC_COUNT) { + currentIncCount_++; + } + } else { + lastSystemTimeUs_ = curTime; + currentIncCount_ = 0; + } + return (curTime * TO_100_NS) + currentIncCount_; // Currently Timestamp is uint64_t +} + +TimeHelper::TimeHelper() + : storage_(nullptr), + metadata_(nullptr) +{ +} + +TimeHelper::~TimeHelper() +{ + metadata_ = nullptr; + storage_ = nullptr; +} + +int TimeHelper::Initialize(const ISyncInterface *inStorage, std::shared_ptr &inMetadata) +{ + if ((inStorage == nullptr) || (inMetadata == nullptr)) { + return -E_INVALID_ARGS; + } + metadata_ = inMetadata; + storage_ = inStorage; + Timestamp currentSysTime = GetSysCurrentTime(); + TimeOffset localTimeOffset = GetLocalTimeOffset(); + Timestamp maxItemTime = GetMaxDataItemTime(); + if (currentSysTime > MAX_VALID_TIME || localTimeOffset > MAX_VALID_TIME || maxItemTime > MAX_VALID_TIME) { + return -E_INVALID_TIME; + } + if ((currentSysTime + localTimeOffset) <= maxItemTime) { + localTimeOffset = static_cast(maxItemTime - currentSysTime + MS_TO_100_NS); // 1ms + int errCode = SaveLocalTimeOffset(localTimeOffset); + if (errCode != E_OK) { + LOGE("[TimeHelper] save local time offset failed,err=%d", errCode); + return errCode; + } + } + metadata_->SetLastLocalTime(currentSysTime + static_cast(localTimeOffset)); + return E_OK; +} + +Timestamp TimeHelper::GetTime() +{ + Timestamp currentSysTime = GetSysCurrentTime(); + TimeOffset localTimeOffset = GetLocalTimeOffset(); + Timestamp currentLocalTime = currentSysTime + localTimeOffset; + Timestamp lastLocalTime = metadata_->GetLastLocalTime(); + if (currentLocalTime <= lastLocalTime || currentLocalTime > MAX_VALID_TIME) { + lastLocalTime++; + currentLocalTime = lastLocalTime; + metadata_->SetLastLocalTime(lastLocalTime); + } else { + metadata_->SetLastLocalTime(currentLocalTime); + } + return currentLocalTime; +} + +Timestamp TimeHelper::GetMaxDataItemTime() +{ + Timestamp timestamp = 0; + storage_->GetMaxTimestamp(timestamp); + return timestamp; +} + +TimeOffset TimeHelper::GetLocalTimeOffset() const +{ + return metadata_->GetLocalTimeOffset(); +} + +int TimeHelper::SaveLocalTimeOffset(TimeOffset offset) +{ + return metadata_->SaveLocalTimeOffset(offset); +} + +void TimeHelper::SetSendConfig(const std::string &dstTarget, bool nonBlock, uint32_t timeout, SendConfig &sendConf) +{ + SetSendConfigParam(storage_->GetDbProperties(), dstTarget, false, SEND_TIME_OUT, sendConf); +} +} // namespace DistributedDB diff --git a/mock/distributeddb/syncer/src/time_helper.h b/mock/distributeddb/syncer/src/time_helper.h new file mode 100644 index 00000000..d19d00bd --- /dev/null +++ b/mock/distributeddb/syncer/src/time_helper.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 TIME_HELPER_H +#define TIME_HELPER_H + +#include + +#include "icommunicator.h" +#include "meta_data.h" +#include "runtime_context.h" + +namespace DistributedDB { +class TimeHelper { +public: + constexpr static int64_t BASE_OFFSET = 10000LL * 365LL * 24LL * 3600LL * 1000LL * 1000LL * 10L; // 10000 year 100ns + + constexpr static int64_t MAX_VALID_TIME = BASE_OFFSET * 2; // 20000 year 100ns + + static const uint64_t TO_100_NS = 10; // 1us to 100ns + + static const uint64_t MS_TO_100_NS = 10000; // 1ms to 100ns + + static const Timestamp INVALID_TIMESTAMP = 0; + + // Get current system time + static Timestamp GetSysCurrentTime(); + + TimeHelper(); + ~TimeHelper(); + + // Init the TimeHelper + int Initialize(const ISyncInterface *inStorage, std::shared_ptr &inMetadata); + + // Get Timestamp when write data into db, export interface; + Timestamp GetTime(); + + // Get max data time from db + Timestamp GetMaxDataItemTime(); + + // Get local time offset + TimeOffset GetLocalTimeOffset() const; + + // Get local time + int SaveLocalTimeOffset(TimeOffset offset); + + void SetSendConfig(const std::string &dstTarget, bool nonBlock, uint32_t timeout, SendConfig &sendConf); + +private: + static std::mutex systemTimeLock_; + static Timestamp lastSystemTimeUs_; + static Timestamp currentIncCount_; + static const uint64_t MAX_INC_COUNT = 9; // last bit from 0-9 + const ISyncInterface *storage_; + std::shared_ptr metadata_; +}; +} // namespace DistributedDB + +#endif // TIME_HELPER_H diff --git a/mock/distributeddb/syncer/src/time_sync.cpp b/mock/distributeddb/syncer/src/time_sync.cpp new file mode 100644 index 00000000..a7a40178 --- /dev/null +++ b/mock/distributeddb/syncer/src/time_sync.cpp @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "time_sync.h" + +#include "parcel.h" +#include "log_print.h" +#include "sync_types.h" +#include "message_transform.h" +#include "version.h" +#include "isync_task_context.h" + +namespace DistributedDB { +std::mutex TimeSync::timeSyncSetLock_; +std::set TimeSync::timeSyncSet_; +namespace { + constexpr uint64_t TIME_SYNC_INTERVAL = 24 * 60 * 60 * 1000; // 24h + constexpr int TRIP_DIV_HALF = 2; + constexpr int64_t MAX_TIME_OFFSET_NOISE = 1 * 1000 * 10000; // 1s for 100ns +} + +// Class TimeSyncPacket +TimeSyncPacket::TimeSyncPacket() + : sourceTimeBegin_(0), + sourceTimeEnd_(0), + targetTimeBegin_(0), + targetTimeEnd_(0), + version_(TIME_SYNC_VERSION_V1) +{ +} + +TimeSyncPacket::~TimeSyncPacket() +{ +} + +void TimeSyncPacket::SetSourceTimeBegin(Timestamp sourceTimeBegin) +{ + sourceTimeBegin_ = sourceTimeBegin; +} + +Timestamp TimeSyncPacket::GetSourceTimeBegin() const +{ + return sourceTimeBegin_; +} + +void TimeSyncPacket::SetSourceTimeEnd(Timestamp sourceTimeEnd) +{ + sourceTimeEnd_ = sourceTimeEnd; +} + +Timestamp TimeSyncPacket::GetSourceTimeEnd() const +{ + return sourceTimeEnd_; +} + +void TimeSyncPacket::SetTargetTimeBegin(Timestamp targetTimeBegin) +{ + targetTimeBegin_ = targetTimeBegin; +} + +Timestamp TimeSyncPacket::GetTargetTimeBegin() const +{ + return targetTimeBegin_; +} + +void TimeSyncPacket::SetTargetTimeEnd(Timestamp targetTimeEnd) +{ + targetTimeEnd_ = targetTimeEnd; +} + +Timestamp TimeSyncPacket::GetTargetTimeEnd() const +{ + return targetTimeEnd_; +} + +void TimeSyncPacket::SetVersion(uint32_t version) +{ + version_ = version; +} + +uint32_t TimeSyncPacket::GetVersion() const +{ + return version_; +} + +uint32_t TimeSyncPacket::CalculateLen() +{ + uint32_t len = Parcel::GetUInt32Len(); + len += Parcel::GetUInt64Len(); + len += Parcel::GetUInt64Len(); + len += Parcel::GetUInt64Len(); + len += Parcel::GetUInt64Len(); + len = Parcel::GetEightByteAlign(len); + return len; +} + +// Class TimeSync +TimeSync::TimeSync() + : communicateHandle_(nullptr), + metadata_(nullptr), + timeHelper_(nullptr), + retryTime_(0), + driverTimerId_(0), + isSynced_(false), + isAckReceived_(false), + timeChangedListener_(nullptr), + timeDriverLockCount_(0), + isOnline_(true) +{ +} + +TimeSync::~TimeSync() +{ + Finalize(); + driverTimerId_ = 0; + + if (timeChangedListener_ != nullptr) { + timeChangedListener_->Drop(true); + timeChangedListener_ = nullptr; + } + timeHelper_ = nullptr; + communicateHandle_ = nullptr; + metadata_ = nullptr; + + std::lock_guard lock(timeSyncSetLock_); + timeSyncSet_.erase(this); +} + +int TimeSync::RegisterTransformFunc() +{ + TransformFunc func; + func.computeFunc = std::bind(&TimeSync::CalculateLen, std::placeholders::_1); + func.serializeFunc = std::bind(&TimeSync::Serialization, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + func.deserializeFunc = std::bind(&TimeSync::DeSerialization, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + return MessageTransform::RegTransformFunction(TIME_SYNC_MESSAGE, func); +} + +int TimeSync::Initialize(ICommunicator *communicator, std::shared_ptr &metadata, + const ISyncInterface *storage, const DeviceID &deviceId) +{ + if ((communicator == nullptr) || (storage == nullptr) || (metadata == nullptr)) { + return -E_INVALID_ARGS; + } + { + std::lock_guard lock(timeSyncSetLock_); + timeSyncSet_.insert(this); + } + communicateHandle_ = communicator; + metadata_ = metadata; + deviceId_ = deviceId; + timeHelper_ = std::make_unique(); + + int errCode = timeHelper_->Initialize(storage, metadata_); + if (errCode != E_OK) { + timeHelper_ = nullptr; + LOGE("[TimeSync] timeHelper Init failed, err %d.", errCode); + return errCode; + } + + driverCallback_ = std::bind(&TimeSync::TimeSyncDriver, this, std::placeholders::_1); + errCode = RuntimeContext::GetInstance()->SetTimer(TIME_SYNC_INTERVAL, driverCallback_, nullptr, driverTimerId_); + if (errCode != E_OK) { + return errCode; + } + return errCode; +} + +void TimeSync::Finalize() +{ + // Stop the timer + LOGD("[TimeSync] Finalize enter!"); + RuntimeContext *runtimeContext = RuntimeContext::GetInstance(); + std::unique_lock lock(timeDriverLock_); + runtimeContext->RemoveTimer(driverTimerId_, true); + timeDriverCond_.wait(lock, [this](){ return this->timeDriverLockCount_ == 0; }); + LOGD("[TimeSync] Finalized!"); +} + +int TimeSync::SyncStart(const CommErrHandler &handler, uint32_t sessionId) +{ + isOnline_ = true; + TimeSyncPacket packet; + Timestamp startTime = timeHelper_->GetTime(); + packet.SetSourceTimeBegin(startTime); + // send timeSync request + LOGD("[TimeSync] startTime = %" PRIu64 ", dev = %s{private}", startTime, deviceId_.c_str()); + + Message *message = new (std::nothrow) Message(TIME_SYNC_MESSAGE); + if (message == nullptr) { + return -E_OUT_OF_MEMORY; + } + message->SetSessionId(sessionId); + message->SetMessageType(TYPE_REQUEST); + message->SetPriority(Priority::NORMAL); + int errCode = message->SetCopiedObject<>(packet); + if (errCode != E_OK) { + delete message; + message = nullptr; + return errCode; + } + + errCode = SendPacket(deviceId_, message, handler); + if (errCode != E_OK) { + delete message; + message = nullptr; + } + return errCode; +} + +uint32_t TimeSync::CalculateLen(const Message *inMsg) +{ + if (!(IsPacketValid(inMsg, TYPE_RESPONSE) || IsPacketValid(inMsg, TYPE_REQUEST))) { + return 0; + } + + const TimeSyncPacket *packet = const_cast(inMsg->GetObject()); + if (packet == nullptr) { + return 0; + } + + return TimeSyncPacket::CalculateLen(); +} + +int TimeSync::Serialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + if ((buffer == nullptr) || !(IsPacketValid(inMsg, TYPE_RESPONSE) || IsPacketValid(inMsg, TYPE_REQUEST))) { + return -E_INVALID_ARGS; + } + const TimeSyncPacket *packet = inMsg->GetObject(); + if ((packet == nullptr) || (length != TimeSyncPacket::CalculateLen())) { + return -E_INVALID_ARGS; + } + + Parcel parcel(buffer, length); + Timestamp srcBegin = packet->GetSourceTimeBegin(); + Timestamp srcEnd = packet->GetSourceTimeEnd(); + Timestamp targetBegin = packet->GetTargetTimeBegin(); + Timestamp targetEnd = packet->GetTargetTimeEnd(); + + int errCode = parcel.WriteUInt32(TIME_SYNC_VERSION_V1); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + errCode = parcel.WriteUInt64(srcBegin); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + errCode = parcel.WriteUInt64(srcEnd); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + errCode = parcel.WriteUInt64(targetBegin); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + errCode = parcel.WriteUInt64(targetEnd); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + parcel.EightByteAlign(); + + return errCode; +} + +int TimeSync::DeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + if ((buffer == nullptr) || !(IsPacketValid(inMsg, TYPE_RESPONSE) || IsPacketValid(inMsg, TYPE_REQUEST))) { + return -E_INVALID_ARGS; + } + TimeSyncPacket packet; + Parcel parcel(const_cast(buffer), length); + Timestamp srcBegin; + Timestamp srcEnd; + Timestamp targetBegin; + Timestamp targetEnd; + + uint32_t version = 0; + parcel.ReadUInt32(version); + if (parcel.IsError()) { + return -E_INVALID_ARGS; + } + if (version > TIME_SYNC_VERSION_V1) { + packet.SetVersion(version); + return inMsg->SetCopiedObject<>(packet); + } + parcel.ReadUInt64(srcBegin); + parcel.ReadUInt64(srcEnd); + parcel.ReadUInt64(targetBegin); + parcel.ReadUInt64(targetEnd); + if (parcel.IsError()) { + return -E_INVALID_ARGS; + } + packet.SetSourceTimeBegin(srcBegin); + packet.SetSourceTimeEnd(srcEnd); + packet.SetTargetTimeBegin(targetBegin); + packet.SetTargetTimeEnd(targetEnd); + + return inMsg->SetCopiedObject<>(packet); +} + +int TimeSync::AckRecv(const Message *message, uint32_t targetSessionId) +{ + // only check when sessionId is not 0, because old version timesync sessionId is 0. + if (message != nullptr && message->GetSessionId() != 0 && + message->GetErrorNo() == E_FEEDBACK_COMMUNICATOR_NOT_FOUND && message->GetSessionId() == targetSessionId) { + LOGE("[AbilitySync][AckMsgCheck] Remote db is closed"); + return -E_FEEDBACK_COMMUNICATOR_NOT_FOUND; + } + if (!IsPacketValid(message, TYPE_RESPONSE)) { + return -E_INVALID_ARGS; + } + const TimeSyncPacket *packet = message->GetObject(); + if (packet == nullptr) { + LOGE("[TimeSync] AckRecv packet is null"); + return -E_INVALID_ARGS; + } + + TimeSyncPacket packetData = TimeSyncPacket(*packet); + Timestamp sourceTimeEnd = timeHelper_->GetTime(); + packetData.SetSourceTimeEnd(sourceTimeEnd); + if (packetData.GetSourceTimeBegin() > packetData.GetSourceTimeEnd() || + packetData.GetTargetTimeBegin() > packetData.GetTargetTimeEnd() || + packetData.GetSourceTimeEnd() > TimeHelper::MAX_VALID_TIME || + packetData.GetTargetTimeEnd() > TimeHelper::MAX_VALID_TIME) { + LOGD("[TimeSync][AckRecv] Time valid check failed."); + return -E_INVALID_TIME; + } + // calculate timeoffset of two devices + TimeOffset offset = CalculateTimeOffset(packetData); + LOGD("TimeSync::AckRecv, dev = %s{private}, sEnd = %" PRIu64 ", tEnd = %" PRIu64 ", sBegin = %" PRIu64 + ", tBegin = %" PRIu64 ", offset = %" PRId64, + deviceId_.c_str(), + packetData.GetSourceTimeEnd(), + packetData.GetTargetTimeEnd(), + packetData.GetSourceTimeBegin(), + packetData.GetTargetTimeBegin(), + offset); + + // save timeoffset into metadata, maybe a block action + int errCode = SaveTimeOffset(deviceId_, offset); + isSynced_ = true; + { + std::lock_guard lock(cvLock_); + isAckReceived_ = true; + } + conditionVar_.notify_all(); + ResetTimer(); + return errCode; +} + +int TimeSync::RequestRecv(const Message *message) +{ + if (!IsPacketValid(message, TYPE_REQUEST)) { + return -E_INVALID_ARGS; + } + Timestamp targetTimeBegin = timeHelper_->GetTime(); + + const TimeSyncPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + + // build timeSync ack packet + TimeSyncPacket ackPacket = TimeSyncPacket(*packet); + ackPacket.SetTargetTimeBegin(targetTimeBegin); + Timestamp targetTimeEnd = timeHelper_->GetTime(); + ackPacket.SetTargetTimeEnd(targetTimeEnd); + LOGD("TimeSync::RequestRecv, dev = %s{private}, sTimeEnd = %" PRIu64 ", tTimeEnd = %" PRIu64 ", sbegin = %" PRIu64 + ", tbegin = %" PRIu64, deviceId_.c_str(), ackPacket.GetSourceTimeEnd(), ackPacket.GetTargetTimeEnd(), + ackPacket.GetSourceTimeBegin(), ackPacket.GetTargetTimeBegin()); + if (ackPacket.GetSourceTimeBegin() > TimeHelper::MAX_VALID_TIME) { + LOGD("[TimeSync][RequestRecv] Time valid check failed."); + return -E_INVALID_TIME; + } + + TimeOffset timeoffsetIgnoreRtt = static_cast(ackPacket.GetSourceTimeBegin() - targetTimeBegin); + TimeOffset metadataTimeoffset; + metadata_->GetTimeOffset(deviceId_, metadataTimeoffset); + + // 2 is half of INT64_MAX + if ((std::abs(metadataTimeoffset) >= INT64_MAX / 2) || (std::abs(timeoffsetIgnoreRtt) >= INT64_MAX / 2) || + (std::abs(metadataTimeoffset - timeoffsetIgnoreRtt) > MAX_TIME_OFFSET_NOISE)) { + LOGI("[TimeSync][RequestRecv] timeoffSet invalid, should do time sync"); + isSynced_ = false; + } + + Message *ackMessage = new (std::nothrow) Message(TIME_SYNC_MESSAGE); + if (ackMessage == nullptr) { + return -E_OUT_OF_MEMORY; + } + ackMessage->SetSessionId(message->GetSessionId()); + ackMessage->SetPriority(Priority::NORMAL); + ackMessage->SetMessageType(TYPE_RESPONSE); + ackMessage->SetTarget(deviceId_); + int errCode = ackMessage->SetCopiedObject<>(ackPacket); + if (errCode != E_OK) { + delete ackMessage; + ackMessage = nullptr; + return errCode; + } + + errCode = SendPacket(deviceId_, ackMessage); + if (errCode != E_OK) { + delete ackMessage; + ackMessage = nullptr; + } + return errCode; +} + +int TimeSync::SaveTimeOffset(const DeviceID &deviceID, TimeOffset timeOffset) +{ + return metadata_->SaveTimeOffset(deviceID, timeOffset); +} + +TimeOffset TimeSync::CalculateTimeOffset(const TimeSyncPacket &timeSyncInfo) +{ + TimeOffset roundTrip = static_cast((timeSyncInfo.GetSourceTimeEnd() - + timeSyncInfo.GetSourceTimeBegin()) - (timeSyncInfo.GetTargetTimeEnd() - timeSyncInfo.GetTargetTimeBegin())); + TimeOffset offset1 = static_cast(timeSyncInfo.GetTargetTimeBegin() - + timeSyncInfo.GetSourceTimeBegin() - (roundTrip / TRIP_DIV_HALF)); + TimeOffset offset2 = static_cast(timeSyncInfo.GetTargetTimeEnd() + (roundTrip / TRIP_DIV_HALF) - + timeSyncInfo.GetSourceTimeEnd()); + TimeOffset offset = (offset1 / TRIP_DIV_HALF) + (offset2 / TRIP_DIV_HALF); + LOGD("TimeSync::CalculateTimeOffset roundTrip= %" PRId64 ", offset1 = %" PRId64 ", offset2 = %" PRId64 + ", offset = %" PRId64, roundTrip, offset1, offset2, offset); + return offset; +} + +bool TimeSync::IsPacketValid(const Message *inMsg, uint16_t messageType) +{ + if (inMsg == nullptr) { + return false; + } + if (inMsg->GetMessageId() != TIME_SYNC_MESSAGE) { + LOGD("message Id = %d", inMsg->GetMessageId()); + return false; + } + if (messageType != inMsg->GetMessageType()) { + LOGD("input Type = %hu, inMsg type = %hu", messageType, inMsg->GetMessageType()); + return false; + } + return true; +} + +int TimeSync::SendPacket(const DeviceID &deviceId, const Message *message, const CommErrHandler &handler) +{ + SendConfig conf; + timeHelper_->SetSendConfig(deviceId, false, SEND_TIME_OUT, conf); + int errCode = communicateHandle_->SendMessage(deviceId, message, conf, handler); + if (errCode != E_OK) { + LOGE("[TimeSync] SendPacket failed, err %d", errCode); + } + return errCode; +} + +int TimeSync::TimeSyncDriver(TimerId timerId) +{ + if (timerId != driverTimerId_) { + return -E_INTERNAL_ERROR; + } + if (!isOnline_) { + return E_OK; + } + std::lock_guard lock(timeDriverLock_); + int errCode = RuntimeContext::GetInstance()->ScheduleTask([this]() { + CommErrHandler handler = std::bind(&TimeSync::CommErrHandlerFunc, std::placeholders::_1, this); + (void)this->SyncStart(handler); + std::lock_guard innerLock(this->timeDriverLock_); + this->timeDriverLockCount_--; + this->timeDriverCond_.notify_all(); + }); + if (errCode != E_OK) { + LOGE("[TimeSync][TimerSyncDriver] ScheduleTask failed err %d", errCode); + return errCode; + } + timeDriverLockCount_++; + return E_OK; +} + +int TimeSync::GetTimeOffset(TimeOffset &outOffset, uint32_t timeout, uint32_t sessionId) +{ + if (!isSynced_) { + { + std::lock_guard lock(cvLock_); + isAckReceived_ = false; + } + CommErrHandler handler = std::bind(&TimeSync::CommErrHandlerFunc, std::placeholders::_1, this); + int errCode = SyncStart(handler, sessionId); + LOGD("TimeSync::GetTimeOffset start, current time = %" PRIu64 ", errCode = %d, timeout = %" PRIu32 " ms", + TimeHelper::GetSysCurrentTime(), errCode, timeout); + std::unique_lock lock(cvLock_); + if (errCode != E_OK || !conditionVar_.wait_for(lock, std::chrono::milliseconds(timeout), + [this](){ return this->isAckReceived_ == true; })) { + LOGD("TimeSync::GetTimeOffset, retryTime_ = %d", retryTime_); + retryTime_++; + if (retryTime_ < MAX_RETRY_TIME) { + lock.unlock(); + LOGI("TimeSync::GetTimeOffset timeout, try again"); + return GetTimeOffset(outOffset, timeout); + } + retryTime_ = 0; + return -E_TIMEOUT; + } + } + retryTime_ = 0; + metadata_->GetTimeOffset(deviceId_, outOffset); + return E_OK; +} + +bool TimeSync::IsNeedSync() const +{ + return !isSynced_; +} + +void TimeSync::SetOnline(bool isOnline) +{ + isOnline_ = isOnline; +} + +void TimeSync::CommErrHandlerFunc(int errCode, TimeSync *timeSync) +{ + LOGD("[TimeSync][CommErrHandle] errCode:%d", errCode); + std::lock_guard lock(timeSyncSetLock_); + if (timeSyncSet_.count(timeSync) == 0) { + LOGI("[TimeSync][CommErrHandle] timeSync has been killed"); + return; + } + if (timeSync == nullptr) { + LOGI("[TimeSync][CommErrHandle] timeSync is nullptr"); + return; + } + if (errCode != E_OK) { + timeSync->SetOnline(false); + } else { + timeSync->SetOnline(true); + } +} + +void TimeSync::ResetTimer() +{ + std::lock_guard lock(timeDriverLock_); + RuntimeContext::GetInstance()->RemoveTimer(driverTimerId_, true); + int errCode = RuntimeContext::GetInstance()->SetTimer( + TIME_SYNC_INTERVAL, driverCallback_, nullptr, driverTimerId_); + if (errCode != E_OK) { + LOGW("[TimeSync] Reset TimeSync timer failed err :%d", errCode); + } +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/syncer/src/time_sync.h b/mock/distributeddb/syncer/src/time_sync.h new file mode 100644 index 00000000..5aaa8a11 --- /dev/null +++ b/mock/distributeddb/syncer/src/time_sync.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 TIME_SYNC_H +#define TIME_SYNC_H + +#include "icommunicator.h" +#include "meta_data.h" +#include "sync_task_context.h" +#include "time_helper.h" + +namespace DistributedDB { +class TimeSyncPacket { +public: + TimeSyncPacket(); + ~TimeSyncPacket(); + + void SetSourceTimeBegin(Timestamp sourceTimeBegin); + + Timestamp GetSourceTimeBegin() const; + + void SetSourceTimeEnd(Timestamp sourceTimeEnd); + + Timestamp GetSourceTimeEnd() const; + + void SetTargetTimeBegin(Timestamp targetTimeBegin); + + Timestamp GetTargetTimeBegin() const; + + void SetTargetTimeEnd(Timestamp targetTimeEnd); + + Timestamp GetTargetTimeEnd() const; + + void SetVersion(uint32_t version); + + uint32_t GetVersion() const; + + static uint32_t CalculateLen(); +private: + Timestamp sourceTimeBegin_; // start point time on peer + Timestamp sourceTimeEnd_; // end point time on local + Timestamp targetTimeBegin_; // start point time on peer + Timestamp targetTimeEnd_; // end point time on peer + uint32_t version_; +}; + +class TimeSync { +public: + TimeSync(); + ~TimeSync(); + + DISABLE_COPY_ASSIGN_MOVE(TimeSync); + + static int RegisterTransformFunc(); + + static uint32_t CalculateLen(const Message *inMsg); + + static int Serialization(uint8_t *buffer, uint32_t length, const Message *inMsg); // register to communicator + + static int DeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); // register to communicator + + int Initialize(ICommunicator *communicator, std::shared_ptr &metadata, + const ISyncInterface *storage, const DeviceID &deviceId); + + int SyncStart(const CommErrHandler &handler = nullptr, uint32_t sessionId = 0); // send timesync request + + int AckRecv(const Message *message, uint32_t targetSessionId = 0); + + int RequestRecv(const Message *message); + + // Get timeoffset from metadata + int GetTimeOffset(TimeOffset &outOffset, uint32_t timeout, uint32_t sessionId = 0); + + bool IsNeedSync() const; + + void SetOnline(bool isOnline); + + // Used in send msg, as execution is asynchronous, should use this function to handle result. + static void CommErrHandlerFunc(int errCode, TimeSync *timeSync); + +private: + static const int MAX_RETRY_TIME = 1; + + static TimeOffset CalculateTimeOffset(const TimeSyncPacket &timeSyncInfo); + + static bool IsPacketValid(const Message *inMsg, uint16_t messageType); + + void Finalize(); + + int SaveTimeOffset(const DeviceID &deviceID, TimeOffset timeOffset); + + int SendPacket(const DeviceID &deviceId, const Message *message, const CommErrHandler &handler = nullptr); + + int TimeSyncDriver(TimerId timerId); + + void ResetTimer(); + + ICommunicator *communicateHandle_; + std::shared_ptr metadata_; + std::unique_ptr timeHelper_; + DeviceID deviceId_; + int retryTime_; + TimerId driverTimerId_; + TimerAction driverCallback_; + bool isSynced_; + bool isAckReceived_; + std::condition_variable conditionVar_; + std::mutex cvLock_; + NotificationChain::Listener *timeChangedListener_; + std::condition_variable timeDriverCond_; + std::mutex timeDriverLock_; + int timeDriverLockCount_; + bool isOnline_; + static std::mutex timeSyncSetLock_; + static std::set timeSyncSet_; +}; +} // namespace DistributedDB + +#endif // TIME_SYNC_H diff --git a/mock/distributeddb/syncer/src/value_slice_sync.cpp b/mock/distributeddb/syncer/src/value_slice_sync.cpp new file mode 100644 index 00000000..6b19fa33 --- /dev/null +++ b/mock/distributeddb/syncer/src/value_slice_sync.cpp @@ -0,0 +1,631 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_MULTI_VER +#include "value_slice_sync.h" + +#include "parcel.h" +#include "log_print.h" +#include "sync_types.h" +#include "message_transform.h" +#include "performance_analysis.h" +#include "db_constant.h" + +namespace DistributedDB { +const int ValueSliceSync::MAX_VALUE_NODE_SIZE = 100000; + +// Class ValueSliceHashPacket +uint32_t ValueSliceHashPacket::CalculateLen() const +{ + uint64_t len = Parcel::GetIntLen(); + len = Parcel::GetEightByteAlign(len); + len += Parcel::GetVectorCharLen(valueSliceHash_); + if (len > INT32_MAX) { + return 0; + } + return len; +} + +void ValueSliceHashPacket::SetValueSliceHash(ValueSliceHash &hash) +{ + valueSliceHash_ = std::move(hash); +} + +void ValueSliceHashPacket::GetValueSliceHash(ValueSliceHash &hash) const +{ + hash = valueSliceHash_; +} + +void ValueSliceHashPacket::SetErrCode(int32_t errCode) +{ + errCode_ = errCode; +} + +int32_t ValueSliceHashPacket::GetErrCode() const +{ + return errCode_; +} + +// Class ValueSlicePacket +uint32_t ValueSlicePacket::CalculateLen() const +{ + uint64_t len = Parcel::GetIntLen(); + len = Parcel::GetEightByteAlign(len); + len += Parcel::GetVectorCharLen(valueSlice_); + if (len > INT32_MAX) { + return 0; + } + return len; +} + +void ValueSlicePacket::SetData(const ValueSlice &data) +{ + valueSlice_ = std::move(data); +} + +void ValueSlicePacket::GetData(ValueSlice &data) const +{ + data = valueSlice_; +} + +void ValueSlicePacket::SetErrorCode(int32_t errCode) +{ + errorCode_ = errCode; +} + +void ValueSlicePacket::GetErrorCode(int32_t &errCode) const +{ + errCode = errorCode_; +} + +// Class ValueSliceSync +ValueSliceSync::~ValueSliceSync() +{ + storagePtr_ = nullptr; + communicateHandle_ = nullptr; +} + +int ValueSliceSync::Serialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + if ((buffer == nullptr) || !(IsPacketValid(inMsg, TYPE_RESPONSE) || IsPacketValid(inMsg, TYPE_REQUEST))) { + return -E_INVALID_ARGS; + } + + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return RequestPacketSerialization(buffer, length, inMsg); + case TYPE_RESPONSE: + return AckPacketSerialization(buffer, length, inMsg); + default: + return -E_MESSAGE_TYPE_ERROR; + } +} + +int ValueSliceSync::DeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + if ((buffer == nullptr) || !(IsPacketValid(inMsg, TYPE_RESPONSE) || IsPacketValid(inMsg, TYPE_REQUEST))) { + return -E_INVALID_ARGS; + } + + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + return RequestPacketDeSerialization(buffer, length, inMsg); + case TYPE_RESPONSE: + return AckPacketDeSerialization(buffer, length, inMsg); + default: + return -E_MESSAGE_TYPE_ERROR; + } +} + +uint32_t ValueSliceSync::CalculateLen(const Message *inMsg) +{ + if (!(IsPacketValid(inMsg, TYPE_RESPONSE) || IsPacketValid(inMsg, TYPE_REQUEST))) { + return 0; + } + + uint32_t len = 0; + int errCode = E_OK; + switch (inMsg->GetMessageType()) { + case TYPE_REQUEST: + errCode = RequestPacketCalculateLen(inMsg, len); + break; + case TYPE_RESPONSE: + errCode = AckPacketCalculateLen(inMsg, len); + break; + default: + return 0; + } + if (errCode != E_OK) { + return 0; + } + return len; +} + +int ValueSliceSync::RegisterTransformFunc() +{ + TransformFunc func; + func.computeFunc = std::bind(&ValueSliceSync::CalculateLen, std::placeholders::_1); + func.serializeFunc = std::bind(&ValueSliceSync::Serialization, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + func.deserializeFunc = std::bind(&ValueSliceSync::DeSerialization, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + return MessageTransform::RegTransformFunction(VALUE_SLICE_SYNC_MESSAGE, func); +} + +int ValueSliceSync::Initialize(MultiVerKvDBSyncInterface *storagePtr, ICommunicator *communicateHandle) +{ + if ((storagePtr == nullptr) || (communicateHandle == nullptr)) { + return -E_INVALID_ARGS; + } + storagePtr_ = storagePtr; + communicateHandle_ = communicateHandle; + return E_OK; +} + +int ValueSliceSync::SyncStart(MultiVerSyncTaskContext *context) +{ + if (context == nullptr) { + return -E_INVALID_ARGS; + } + int entriesIndex = context->GetEntriesIndex(); + int entriesSize = context->GetEntriesSize(); + if (entriesSize > DBConstant::MAX_ENTRIES_SIZE) { + LOGE("ValueSliceSync::entriesSize too large %d", entriesSize); + return -E_INVALID_ARGS; + } + while (entriesIndex < entriesSize) { + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_GET_VALUE_SLICE_NODE); + } + ValueSliceHash valueSliceHashNode; + int errCode = GetValidValueSliceHashNode(context, valueSliceHashNode); + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_GET_VALUE_SLICE_NODE); + } + LOGD("ValueSliceSync::SyncStart begin errCode = %d", errCode); + if (errCode == E_OK) { + errCode = SendRequestPacket(context, valueSliceHashNode); + LOGD("ValueSliceSync::SyncStart send request packet dst=%s{private}, errCode = %d", + context->GetDeviceId().c_str(), errCode); + return errCode; + } + // move to next entry + MultiVerKvEntry *entry = nullptr; + std::vector valueHashes; + entriesIndex++; + if (entriesIndex < entriesSize) { + LOGD("ValueSliceSync::SyncStart begin entriesIndex = %d, entriesSize = %d", entriesIndex, entriesSize); + context->SetEntriesIndex(entriesIndex); + context->GetEntry(entriesIndex, entry); + errCode = entry->GetValueHash(valueHashes); + if (errCode != E_OK) { + LOGE("ValueSliceSync::entry->GetValueHash %d", errCode); + return errCode; + } + context->SetValueSliceHashNodes(valueHashes); + context->SetValueSlicesIndex(0); + context->SetValueSlicesSize(static_cast(valueHashes.size())); + } else { + // all entries are received, move to next commit + return -E_NOT_FOUND; + } + } + return -E_NOT_FOUND; +} + +int ValueSliceSync::RequestRecvCallback(const MultiVerSyncTaskContext *context, const Message *message) +{ + if (!IsPacketValid(message, TYPE_REQUEST) || context == nullptr) { + return -E_INVALID_ARGS; + } + + const ValueSliceHashPacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + ValueSliceHash valueSliceHashNode; + packet->GetValueSliceHash(valueSliceHashNode); + if ((packet->GetErrCode() == -E_LAST_SYNC_FRAME) && valueSliceHashNode.empty()) { + return -E_LAST_SYNC_FRAME; + } + ValueSlice valueSlice; + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_READ_VALUE_SLICE); + } + int errCode = GetValueSlice(valueSliceHashNode, valueSlice); + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_READ_VALUE_SLICE); + } + if (errCode != E_OK) { + LOGE("ValueSliceSync::RequestRecvCallback : GetValueSlice ERR, errno = %d", errCode); + } + errCode = SendAckPacket(context, valueSlice, errCode, message); + LOGD("ValueSliceSync::RequestRecvCallback : SendAckPacket, errno = %d, dst = %s{private}", errCode, + context->GetDeviceId().c_str()); + if (packet->GetErrCode() == -E_LAST_SYNC_FRAME) { + return -E_LAST_SYNC_FRAME; + } + return errCode; +} + +int ValueSliceSync::AckRecvCallback(const MultiVerSyncTaskContext *context, const Message *message) +{ + if (!IsPacketValid(message, TYPE_RESPONSE) || (context == nullptr)) { + return -E_INVALID_ARGS; + } + + const ValueSlicePacket *packet = message->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + int errCode = E_OK; + packet->GetErrorCode(errCode); + ValueSlice valueSlice; + packet->GetData(valueSlice); + if (errCode != E_OK) { + return errCode; + } + int index = context->GetValueSlicesIndex(); + ValueSliceHash hashValue; + context->GetValueSliceHashNode(index, hashValue); + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_SAVE_VALUE_SLICE); + } + errCode = PutValueSlice(hashValue, valueSlice); + if (performance != nullptr) { + performance->StepTimeRecordEnd(MV_TEST_RECORDS::RECORD_SAVE_VALUE_SLICE); + } + LOGD("ValueSliceSync::AckRecvCallback PutValueSlice finished, src=%s{private}, errCode = %d", + context->GetDeviceId().c_str(), errCode); + if (errCode != E_OK) { + return errCode; + } + return errCode; +} + +void ValueSliceSync::SendFinishedRequest(const MultiVerSyncTaskContext *context) +{ + if (context == nullptr) { + return; + } + + ValueSliceHashPacket *packet = new (std::nothrow) ValueSliceHashPacket(); + if (packet == nullptr) { + return; + } + + packet->SetErrCode(-E_LAST_SYNC_FRAME); + Message *message = new (std::nothrow) Message(VALUE_SLICE_SYNC_MESSAGE); + if (message == nullptr) { + delete packet; + packet = nullptr; + return; + } + + int errCode = message->SetExternalObject(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + delete message; + message = nullptr; + return; + } + + message->SetMessageType(TYPE_REQUEST); + message->SetTarget(context->GetDeviceId()); + message->SetSessionId(context->GetRequestSessionId()); + message->SetSequenceId(context->GetSequenceId()); + errCode = Send(message->GetTarget(), message); + if (errCode != E_OK) { + delete message; + message = nullptr; + LOGE("[ValueSliceSync][SendRequestPacket] SendRequestPacket failed, err %d", errCode); + } + LOGI("[ValueSliceSync][SendRequestPacket] SendRequestPacket dst=%s{private}", context->GetDeviceId().c_str()); +} + +int ValueSliceSync::RequestPacketCalculateLen(const Message *inMsg, uint32_t &len) +{ + const ValueSliceHashPacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + + len = packet->CalculateLen(); + return E_OK; +} + +int ValueSliceSync::RequestPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + const ValueSliceHashPacket *packet = inMsg->GetObject(); + if ((packet == nullptr) || (length != packet->CalculateLen())) { + return -E_INVALID_ARGS; + } + + Parcel parcel(buffer, length); + ValueSliceHash valueSliceHash; + packet->GetValueSliceHash(valueSliceHash); + int32_t ackCode = packet->GetErrCode(); + // errCode Serialization + int32_t errCode = parcel.WriteInt(ackCode); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + parcel.EightByteAlign(); + // commitMap Serialization + errCode = parcel.WriteVectorChar(valueSliceHash); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + + return errCode; +} + +int ValueSliceSync::RequestPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + Parcel parcel(const_cast(buffer), length); + + int ackCode = 0; + // errCode DeSerialization + uint32_t packLen = parcel.ReadInt(ackCode); + parcel.EightByteAlign(); + packLen = Parcel::GetEightByteAlign(packLen); + + ValueSliceHash valueSliceHash; + // commit DeSerialization + packLen += parcel.ReadVectorChar(valueSliceHash); + if (packLen != length || parcel.IsError()) { + return -E_INVALID_ARGS; + } + ValueSliceHashPacket *packet = new (std::nothrow) ValueSliceHashPacket(); + if (packet == nullptr) { + LOGE("ValueSliceSync::AckPacketDeSerialization : new packet error"); + return -E_OUT_OF_MEMORY; + } + + packet->SetValueSliceHash(valueSliceHash); + int errCode = inMsg->SetExternalObject<>(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + } + return errCode; +} + +int ValueSliceSync::AckPacketCalculateLen(const Message *inMsg, uint32_t &len) +{ + const ValueSlicePacket *packet = inMsg->GetObject(); + if (packet == nullptr) { + return -E_INVALID_ARGS; + } + len = packet->CalculateLen(); + return E_OK; +} + +int ValueSliceSync::AckPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg) +{ + if ((buffer == nullptr) || !IsPacketValid(inMsg, TYPE_RESPONSE)) { + return -E_INVALID_ARGS; + } + const ValueSlicePacket *packet = inMsg->GetObject(); + if ((packet == nullptr) || (length != packet->CalculateLen())) { + return -E_INVALID_ARGS; + } + + Parcel parcel(buffer, length); + ValueSlice valueSlice; + packet->GetData(valueSlice); + int32_t ackCode = 0; + packet->GetErrorCode(ackCode); + // errCode Serialization + int32_t errCode = parcel.WriteInt(ackCode); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + parcel.EightByteAlign(); + + // commits vector Serialization + errCode = parcel.WriteVectorChar(valueSlice); + if (errCode != E_OK) { + return -E_SECUREC_ERROR; + } + + return errCode; +} + +int ValueSliceSync::AckPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg) +{ + Parcel parcel(const_cast(buffer), length); + int32_t ackCode = 0; + uint32_t packLen = 0; + ValueSlice valueSlice; + + // errCode DeSerialization + packLen += parcel.ReadInt(ackCode); + parcel.EightByteAlign(); + packLen = Parcel::GetEightByteAlign(packLen); + // valueSlice DeSerialization + packLen += parcel.ReadVectorChar(valueSlice); + if (packLen != length || parcel.IsError()) { + LOGE("ValueSliceSync::AckPacketSerialization data error, packLen = %" PRIu32 ", length = %" PRIu32, + packLen, length); + return -E_INVALID_ARGS; + } + ValueSlicePacket *packet = new (std::nothrow) ValueSlicePacket(); + if (packet == nullptr) { + LOGE("ValueSliceSync::AckPacketDeSerialization : new packet error"); + return -E_OUT_OF_MEMORY; + } + packet->SetData(valueSlice); + packet->SetErrorCode(ackCode); + int errCode = inMsg->SetExternalObject<>(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + } + return errCode; +} + +bool ValueSliceSync::IsPacketValid(const Message *inMsg, uint16_t messageType) +{ + if ((inMsg == nullptr) || (inMsg->GetMessageId() != VALUE_SLICE_SYNC_MESSAGE)) { + return false; + } + if (messageType != inMsg->GetMessageType()) { + return false; + } + return true; +} + +int ValueSliceSync::GetValidValueSliceHashNode(MultiVerSyncTaskContext *context, ValueSliceHash &valueHashNode) +{ + int index = context->GetValueSlicesIndex(); + int valueNodesSize = context->GetValueSlicesSize(); + if (valueNodesSize > MAX_VALUE_NODE_SIZE) { + LOGD("ValueSliceSync::GetValidValueSliceHashNode failed, too large!"); + return -E_LENGTH_ERROR; + } + LOGD("ValueSliceSync::GetValidValueSliceHashNode ValueSlicesSize = %d", valueNodesSize); + if (context->GetRetryStatus() == SyncTaskContext::NEED_RETRY) { + context->SetRetryStatus(SyncTaskContext::NO_NEED_RETRY); + index--; + } + std::vector valueSliceHashNodes; + context->GetValueSliceHashNodes(valueSliceHashNodes); + index = (index < 0) ? 0 : index; + while (index < valueNodesSize) { + if (IsValueSliceExisted(valueSliceHashNodes[index])) { + index++; + context->SetValueSlicesIndex(index); + continue; + } + valueHashNode = valueSliceHashNodes[index]; + return E_OK; + } + return -E_NOT_FOUND; +} + +int ValueSliceSync::Send(const DeviceID &deviceId, const Message *inMsg) +{ + SendConfig conf = {false, false, SEND_TIME_OUT, {}}; + int errCode = communicateHandle_->SendMessage(deviceId, inMsg, conf); + if (errCode != E_OK) { + LOGE("ValueSliceSync::Send ERR! err = %d", errCode); + } + return errCode; +} + +int ValueSliceSync::SendRequestPacket(const MultiVerSyncTaskContext *context, ValueSliceHash &valueSliceHash) +{ + ValueSliceHashPacket *packet = new (std::nothrow) ValueSliceHashPacket(); + if (packet == nullptr) { + LOGE("ValueSliceSync::SendRequestPacket : new packet error"); + return -E_OUT_OF_MEMORY; + } + + packet->SetValueSliceHash(valueSliceHash); + Message *message = new (std::nothrow) Message(VALUE_SLICE_SYNC_MESSAGE); + if (message == nullptr) { + delete packet; + packet = nullptr; + LOGE("ValueSliceSync::SendRequestPacket : new message error"); + return -E_OUT_OF_MEMORY; + } + + int errCode = message->SetExternalObject<>(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + delete message; + message = nullptr; + return errCode; + } + + message->SetMessageType(TYPE_REQUEST); + message->SetTarget(context->GetDeviceId()); + message->SetSessionId(context->GetRequestSessionId()); + message->SetSequenceId(context->GetSequenceId()); + PerformanceAnalysis *performance = PerformanceAnalysis::GetInstance(); + if (performance != nullptr) { + performance->StepTimeRecordStart(MV_TEST_RECORDS::RECORD_VALUE_SLICE_SEND_REQUEST_TO_ACK_RECV); + } + errCode = Send(message->GetTarget(), message); + if (errCode != E_OK) { + delete message; + message = nullptr; + } + return errCode; +} + +int ValueSliceSync::SendAckPacket(const MultiVerSyncTaskContext *context, const ValueSlice &value, + int ackCode, const Message *message) +{ + ValueSlicePacket *packet = new (std::nothrow) ValueSlicePacket(); + if (packet == nullptr) { + LOGE("ValueSliceSync::SendAckPacket : packet is nullptr"); + return -E_OUT_OF_MEMORY; + } + + Message *ackMessage = new (std::nothrow) Message(VALUE_SLICE_SYNC_MESSAGE); + if (ackMessage == nullptr) { + delete packet; + packet = nullptr; + LOGE("ValueSliceSync::SendAckPacket : new message error"); + return -E_OUT_OF_MEMORY; + } + + packet->SetData(value); + packet->SetErrorCode(static_cast(ackCode)); + int errCode = ackMessage->SetExternalObject<>(packet); + if (errCode != E_OK) { + delete packet; + packet = nullptr; + delete ackMessage; + ackMessage = nullptr; + return errCode; + } + + ackMessage->SetMessageType(TYPE_RESPONSE); + ackMessage->SetTarget(context->GetDeviceId()); + ackMessage->SetSequenceId(message->GetSequenceId()); + ackMessage->SetSessionId(message->GetSessionId()); + errCode = Send(ackMessage->GetTarget(), ackMessage); + if (errCode != E_OK) { + delete ackMessage; + ackMessage = nullptr; + } + + return errCode; +} + +bool ValueSliceSync::IsValueSliceExisted(const ValueSliceHash &value) +{ + return storagePtr_->IsValueSliceExisted(value); +} + +int ValueSliceSync::GetValueSlice(const ValueSliceHash &hashValue, ValueSlice &sliceValue) +{ + return storagePtr_->GetValueSlice(hashValue, sliceValue); +} + +int ValueSliceSync::PutValueSlice(const ValueSliceHash &hashValue, const ValueSlice &sliceValue) +{ + return storagePtr_->PutValueSlice(hashValue, sliceValue); +} +} // namespace DistributedDB +#endif diff --git a/mock/distributeddb/syncer/src/value_slice_sync.h b/mock/distributeddb/syncer/src/value_slice_sync.h new file mode 100644 index 00000000..e8ff764f --- /dev/null +++ b/mock/distributeddb/syncer/src/value_slice_sync.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 VALUE_SLICE_SYNC_H +#define VALUE_SLICE_SYNC_H + +#ifndef OMIT_MULTI_VER +#include + +#include "icommunicator.h" +#include "multi_ver_kvdb_sync_interface.h" +#include "multi_ver_sync_task_context.h" + +namespace DistributedDB { +class ValueSliceHashPacket { +public: + ValueSliceHashPacket() : errCode_(E_OK) {}; + ~ValueSliceHashPacket() {}; + + uint32_t CalculateLen() const; + + void SetValueSliceHash(ValueSliceHash &hash); + + void GetValueSliceHash(ValueSliceHash &hash) const; + + void SetErrCode(int32_t errCode); + + int32_t GetErrCode() const; +private: + ValueSliceHash valueSliceHash_; + int32_t errCode_; +}; + +class ValueSlicePacket { +public: + ValueSlicePacket() : errorCode_(0) {}; + ~ValueSlicePacket() {}; + + uint32_t CalculateLen() const; + + void SetData(const ValueSlice &data); + + void GetData(ValueSlice &data) const; + + void SetErrorCode(int32_t errCode); + + void GetErrorCode(int32_t &errCode) const; +private: + ValueSlice valueSlice_; + int32_t errorCode_; +}; + +class ValueSliceSync { +public: + ValueSliceSync() : storagePtr_(nullptr), communicateHandle_(nullptr) {}; + ~ValueSliceSync(); + DISABLE_COPY_ASSIGN_MOVE(ValueSliceSync); + + static int RegisterTransformFunc(); + + int Initialize(MultiVerKvDBSyncInterface *storagePtr, ICommunicator *communicateHandle); + + static int Serialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int DeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static uint32_t CalculateLen(const Message *inMsg); + + int SyncStart(MultiVerSyncTaskContext *context); + + int RequestRecvCallback(const MultiVerSyncTaskContext *context, const Message *message); + + int AckRecvCallback(const MultiVerSyncTaskContext *context, const Message *message); + + void SendFinishedRequest(const MultiVerSyncTaskContext *context); + +private: + static int RequestPacketCalculateLen(const Message *inMsg, uint32_t &len); + + static int RequestPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int RequestPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static int AckPacketCalculateLen(const Message *inMsg, uint32_t &len); + + static int AckPacketSerialization(uint8_t *buffer, uint32_t length, const Message *inMsg); + + static int AckPacketDeSerialization(const uint8_t *buffer, uint32_t length, Message *inMsg); + + static bool IsPacketValid(const Message *inMsg, uint16_t messageType); + + int GetValidValueSliceHashNode(MultiVerSyncTaskContext *context, ValueSliceHash &valueHashNode); + + int Send(const DeviceID &deviceId, const Message *inMsg); + + int SendRequestPacket(const MultiVerSyncTaskContext *context, ValueSliceHash &valueSliceHash); + + int SendAckPacket(const MultiVerSyncTaskContext *context, const ValueSlice &value, int ackCode, + const Message *message); + + bool IsValueSliceExisted(const ValueSliceHash &value); + + int GetValueSlice(const ValueSliceHash &hashValue, ValueSlice &sliceValue); + + int PutValueSlice(const ValueSliceHash &hashValue, const ValueSlice &sliceValue); + + static const int MAX_VALUE_NODE_SIZE; + MultiVerKvDBSyncInterface *storagePtr_; + ICommunicator *communicateHandle_; +}; +} + +#endif +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/BUILD.gn b/mock/distributeddb/test/BUILD.gn new file mode 100644 index 00000000..3ecedfed --- /dev/null +++ b/mock/distributeddb/test/BUILD.gn @@ -0,0 +1,772 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# 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. +import("//build/test.gni") + +module_output_path = "distributeddatamgr/distributeddb" + +############################################################################### +config("module_private_config") { + visibility = [ ":*" ] + + include_dirs = [ + "./unittest/common/common", + "./unittest/common/syncer", + "./unittest/common/storage", + "./unittest/common/interfaces", + "../include", + "../interfaces/include", + "../interfaces/include/relational", + "../interfaces/src", + "../interfaces/src/relational", + "../storage/include", + "../storage/src", + "../storage/src/multiver", + "../storage/src/operation", + "../storage/src/sqlite", + "../storage/src/sqlite/relational", + "../storage/src/upgrader", + "../common/include", + "../common/include/relational", + "../common/src", + "../communicator/include", + "../communicator/src", + "../syncer/include", + "../syncer/src", + "//third_party/openssl/include/", + ] + + defines = [ + "SQLITE_ENABLE_SNAPSHOT", + "_LARGEFILE64_SOURCE", + "_FILE_OFFSET_BITS=64", + "SQLITE_HAS_CODEC", + "SQLITE_ENABLE_JSON1", + "USING_HILOG_LOGGER", + "USE_SQLITE_SYMBOLS", + "USING_DB_JSON_EXTRACT_AUTOMATICALLY", + "LOW_LEVEL_MEM_DEV", + "JSONCPP_USE_BUILDER", + "OMIT_FLATBUFFER", + "RELATIONAL_STORE", + "SQLITE_DISTRIBUTE_RELATIONAL", + "USE_DFX_ABILITY", + "TRACE_SQLITE_EXECUTE", + ] +} + +############################################################################### +ohos_source_set("src_file") { + testonly = true + + sources = [ + "../common/src/auto_launch.cpp", + "../common/src/data_compression.cpp", + "../common/src/data_value.cpp", + "../common/src/db_common.cpp", + "../common/src/db_constant.cpp", + "../common/src/db_dfx_adapter.cpp", + "../common/src/db_dump_helper.cpp", + "../common/src/evloop/src/event_impl.cpp", + "../common/src/evloop/src/event_loop_epoll.cpp", + "../common/src/evloop/src/event_loop_impl.cpp", + "../common/src/evloop/src/event_loop_select.cpp", + "../common/src/evloop/src/ievent.cpp", + "../common/src/evloop/src/ievent_loop.cpp", + "../common/src/flatbuffer_schema.cpp", + "../common/src/hash.cpp", + "../common/src/json_object.cpp", + "../common/src/lock_status_observer.cpp", + "../common/src/log_print.cpp", + "../common/src/notification_chain.cpp", + "../common/src/param_check_utils.cpp", + "../common/src/parcel.cpp", + "../common/src/performance_analysis.cpp", + "../common/src/platform_specific.cpp", + "../common/src/query.cpp", + "../common/src/query_expression.cpp", + "../common/src/ref_object.cpp", + "../common/src/relational/relational_schema_object.cpp", + "../common/src/runtime_context.cpp", + "../common/src/runtime_context_impl.cpp", + "../common/src/schema_constant.cpp", + "../common/src/schema_negotiate.cpp", + "../common/src/schema_object.cpp", + "../common/src/schema_utils.cpp", + "../common/src/semaphore_utils.cpp", + "../common/src/task_pool.cpp", + "../common/src/task_pool_impl.cpp", + "../common/src/task_queue.cpp", + "../common/src/time_tick_monitor.cpp", + "../common/src/types_export.cpp", + "../common/src/user_change_monitor.cpp", + "../common/src/value_object.cpp", + "../common/src/zlib_compression.cpp", + "../communicator/src/combine_status.cpp", + "../communicator/src/communicator.cpp", + "../communicator/src/communicator_aggregator.cpp", + "../communicator/src/communicator_linker.cpp", + "../communicator/src/frame_combiner.cpp", + "../communicator/src/frame_retainer.cpp", + "../communicator/src/header_converter.cpp", + "../communicator/src/message_transform.cpp", + "../communicator/src/network_adapter.cpp", + "../communicator/src/protocol_proto.cpp", + "../communicator/src/send_task_scheduler.cpp", + "../communicator/src/serial_buffer.cpp", + "../interfaces/src/intercepted_data_impl.cpp", + "../interfaces/src/kv_store_changed_data_impl.cpp", + "../interfaces/src/kv_store_delegate_impl.cpp", + "../interfaces/src/kv_store_delegate_manager.cpp", + "../interfaces/src/kv_store_errno.cpp", + "../interfaces/src/kv_store_nb_conflict_data_impl.cpp", + "../interfaces/src/kv_store_nb_delegate_impl.cpp", + "../interfaces/src/kv_store_result_set_impl.cpp", + "../interfaces/src/kv_store_snapshot_delegate_impl.cpp", + "../interfaces/src/relational/relational_store_changed_data_impl.cpp", + "../interfaces/src/relational/relational_store_delegate_impl.cpp", + "../interfaces/src/relational/relational_store_manager.cpp", + "../interfaces/src/relational/relational_store_sqlite_ext.cpp", + "../interfaces/src/relational/runtime_config.cpp", + "../storage/src/data_transformer.cpp", + "../storage/src/db_properties.cpp", + "../storage/src/default_factory.cpp", + "../storage/src/generic_kvdb.cpp", + "../storage/src/generic_kvdb_connection.cpp", + "../storage/src/generic_single_ver_kv_entry.cpp", + "../storage/src/iconnection.cpp", + "../storage/src/ikvdb_factory.cpp", + "../storage/src/kvdb_commit_notify_filterable_data.cpp", + "../storage/src/kvdb_manager.cpp", + "../storage/src/kvdb_observer_handle.cpp", + "../storage/src/kvdb_properties.cpp", + "../storage/src/kvdb_utils.cpp", + "../storage/src/kvdb_windowed_result_set.cpp", + "../storage/src/multiver/generic_multi_ver_kv_entry.cpp", + "../storage/src/multiver/multi_ver_commit.cpp", + "../storage/src/multiver/multi_ver_kvdata_storage.cpp", + "../storage/src/multiver/multi_ver_natural_store.cpp", + "../storage/src/multiver/multi_ver_natural_store_commit_notify_data.cpp", + "../storage/src/multiver/multi_ver_natural_store_commit_storage.cpp", + "../storage/src/multiver/multi_ver_natural_store_connection.cpp", + "../storage/src/multiver/multi_ver_natural_store_snapshot.cpp", + "../storage/src/multiver/multi_ver_natural_store_transfer_data.cpp", + "../storage/src/multiver/multi_ver_storage_engine.cpp", + "../storage/src/multiver/multi_ver_storage_executor.cpp", + "../storage/src/multiver/multi_ver_vacuum.cpp", + "../storage/src/multiver/multi_ver_vacuum_executor_impl.cpp", + "../storage/src/multiver/multi_ver_value_object.cpp", + "../storage/src/operation/database_oper.cpp", + "../storage/src/operation/local_database_oper.cpp", + "../storage/src/operation/multi_ver_database_oper.cpp", + "../storage/src/operation/single_ver_database_oper.cpp", + "../storage/src/package_file.cpp", + "../storage/src/relational_store_connection.cpp", + "../storage/src/relational_store_instance.cpp", + "../storage/src/relational_sync_able_storage.cpp", + "../storage/src/relationaldb_properties.cpp", + "../storage/src/result_entries_window.cpp", + "../storage/src/single_ver_natural_store_commit_notify_data.cpp", + "../storage/src/sqlite/query_object.cpp", + "../storage/src/sqlite/query_sync_object.cpp", + "../storage/src/sqlite/relational/sqlite_relational_store.cpp", + "../storage/src/sqlite/relational/sqlite_relational_store_connection.cpp", + "../storage/src/sqlite/relational/sqlite_single_relational_storage_engine.cpp", + "../storage/src/sqlite/sqlite_local_kvdb.cpp", + "../storage/src/sqlite/sqlite_local_kvdb_connection.cpp", + "../storage/src/sqlite/sqlite_local_kvdb_snapshot.cpp", + "../storage/src/sqlite/sqlite_local_storage_engine.cpp", + "../storage/src/sqlite/sqlite_local_storage_executor.cpp", + "../storage/src/sqlite/sqlite_multi_ver_data_storage.cpp", + "../storage/src/sqlite/sqlite_multi_ver_transaction.cpp", + "../storage/src/sqlite/sqlite_query_helper.cpp", + "../storage/src/sqlite/sqlite_single_ver_continue_token.cpp", + "../storage/src/sqlite/sqlite_single_ver_database_upgrader.cpp", + "../storage/src/sqlite/sqlite_single_ver_forward_cursor.cpp", + "../storage/src/sqlite/sqlite_single_ver_natural_store.cpp", + "../storage/src/sqlite/sqlite_single_ver_natural_store_connection.cpp", + "../storage/src/sqlite/sqlite_single_ver_relational_continue_token.cpp", + "../storage/src/sqlite/sqlite_single_ver_relational_storage_executor.cpp", + "../storage/src/sqlite/sqlite_single_ver_result_set.cpp", + "../storage/src/sqlite/sqlite_single_ver_schema_database_upgrader.cpp", + "../storage/src/sqlite/sqlite_single_ver_storage_engine.cpp", + "../storage/src/sqlite/sqlite_single_ver_storage_executor.cpp", + "../storage/src/sqlite/sqlite_single_ver_storage_executor_cache.cpp", + "../storage/src/sqlite/sqlite_single_ver_storage_executor_subscribe.cpp", + "../storage/src/sqlite/sqlite_storage_engine.cpp", + "../storage/src/sqlite/sqlite_storage_executor.cpp", + "../storage/src/sqlite/sqlite_utils.cpp", + "../storage/src/storage_engine.cpp", + "../storage/src/storage_engine_manager.cpp", + "../storage/src/storage_executor.cpp", + "../storage/src/sync_able_engine.cpp", + "../storage/src/sync_able_kvdb.cpp", + "../storage/src/sync_able_kvdb_connection.cpp", + "../storage/src/upgrader/single_ver_database_upgrader.cpp", + "../storage/src/upgrader/single_ver_schema_database_upgrader.cpp", + "../syncer/src/ability_sync.cpp", + "../syncer/src/commit_history_sync.cpp", + "../syncer/src/communicator_proxy.cpp", + "../syncer/src/db_ability.cpp", + "../syncer/src/device_manager.cpp", + "../syncer/src/generic_syncer.cpp", + "../syncer/src/meta_data.cpp", + "../syncer/src/multi_ver_data_sync.cpp", + "../syncer/src/multi_ver_sync_engine.cpp", + "../syncer/src/multi_ver_sync_state_machine.cpp", + "../syncer/src/multi_ver_sync_task_context.cpp", + "../syncer/src/multi_ver_syncer.cpp", + "../syncer/src/query_sync_water_mark_helper.cpp", + "../syncer/src/single_ver_data_message_schedule.cpp", + "../syncer/src/single_ver_data_packet.cpp", + "../syncer/src/single_ver_data_sync.cpp", + "../syncer/src/single_ver_data_sync_utils.cpp", + "../syncer/src/single_ver_kv_sync_task_context.cpp", + "../syncer/src/single_ver_kv_syncer.cpp", + "../syncer/src/single_ver_relational_sync_task_context.cpp", + "../syncer/src/single_ver_relational_syncer.cpp", + "../syncer/src/single_ver_serialize_manager.cpp", + "../syncer/src/single_ver_sync_engine.cpp", + "../syncer/src/single_ver_sync_state_machine.cpp", + "../syncer/src/single_ver_sync_target.cpp", + "../syncer/src/single_ver_sync_task_context.cpp", + "../syncer/src/single_ver_syncer.cpp", + "../syncer/src/subscribe_manager.cpp", + "../syncer/src/sync_config.cpp", + "../syncer/src/sync_engine.cpp", + "../syncer/src/sync_operation.cpp", + "../syncer/src/sync_state_machine.cpp", + "../syncer/src/sync_target.cpp", + "../syncer/src/sync_task_context.cpp", + "../syncer/src/syncer_factory.cpp", + "../syncer/src/syncer_proxy.cpp", + "../syncer/src/time_helper.cpp", + "../syncer/src/time_sync.cpp", + "../syncer/src/value_slice_sync.cpp", + "unittest/common/common/distributeddb_data_generate_unit_test.cpp", + "unittest/common/common/distributeddb_tools_unit_test.cpp", + "unittest/common/interfaces/process_system_api_adapter_impl.cpp", + "unittest/common/syncer/generic_virtual_device.cpp", + "unittest/common/syncer/kv_virtual_device.cpp", + "unittest/common/syncer/relational_virtual_device.cpp", + "unittest/common/syncer/virtual_communicator.cpp", + "unittest/common/syncer/virtual_communicator_aggregator.cpp", + "unittest/common/syncer/virtual_multi_ver_sync_db_interface.cpp", + "unittest/common/syncer/virtual_single_ver_sync_db_Interface.cpp", + ] + + configs = [ ":module_private_config" ] + + deps = [ + "//third_party/googletest:gtest_main", + "//third_party/sqlite:sqlite", + "//third_party/zlib:libz", + "//utils/native/base:utils", + ] + + configs += [ "//third_party/jsoncpp:jsoncpp_config" ] + ldflags = [ "-Wl,--exclude-libs,ALL" ] + deps += [ + "//third_party/jsoncpp:jsoncpp", + "//third_party/openssl:libcrypto_shared", + ] + external_deps = [ + "hisysevent_native:libhisysevent", + "hitrace_native:hitrace_meter", + "hiviewdfx_hilog_native:libhilog", + ] + part_name = "distributeddatamgr" +} + +template("distributeddb_unittest") { + ohos_unittest(target_name) { + forward_variables_from(invoker, "*") + module_out_path = module_output_path + if (!defined(deps)) { + deps = [] + } + if (!defined(external_deps)) { + external_deps = [] + } + configs = [ ":module_private_config" ] + deps += [ + ":src_file", + "//third_party/googletest:gmock_main", + "//third_party/googletest:gtest_main", + "//third_party/sqlite:sqlite", + "//third_party/zlib:libz", + "//utils/native/base:utils", + ] + configs += [ "//third_party/jsoncpp:jsoncpp_config" ] + ldflags = [ "-Wl,--exclude-libs,ALL" ] + deps += [ + "//third_party/jsoncpp:jsoncpp", + "//third_party/openssl:libcrypto_shared", + ] + external_deps = [ + "hisysevent_native:libhisysevent", + "hitrace_native:hitrace_meter", + "hiviewdfx_hilog_native:libhilog", + ] + } +} + +distributeddb_unittest("DistributedDBSchemalTest") { + sources = [ "unittest/common/common/distributeddb_schema_unit_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesDatabaseTest") { + sources = [ + "unittest/common/interfaces/distributeddb_interfaces_database_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBInterfacesDataOperationTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_data_operation_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesEncryptDatabaseTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_encrypt_database_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesEncryptDelegateTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_encrypt_delegate_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesImportAndExportTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_import_and_export_test.cpp" ] +} + +distributeddb_unittest("DistributedDBStorageDataOperationTest") { + sources = [ + "unittest/common/storage/distributeddb_storage_data_operation_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBStorageRegisterConflictTest") { + sources = [ + "unittest/common/storage/distributeddb_storage_register_conflict_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBInterfacesTransactionTest") { + sources = [ + "unittest/common/interfaces/distributeddb_interfaces_transaction_test.cpp", + "unittest/common/interfaces/distributeddb_interfaces_transaction_testcase.cpp", + ] +} + +distributeddb_unittest("DistributedDBStorageTransactionDataTest") { + sources = [ + "unittest/common/storage/distributeddb_storage_transaction_data_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBStorageTransactionRecordTest") { + sources = [ + "unittest/common/storage/distributeddb_storage_transaction_record_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBNotificationChainTest") { + sources = + [ "unittest/common/common/distributeddb_notification_chain_test.cpp" ] +} + +distributeddb_unittest("DistributedDBStorageCommitStorageTest") { + sources = [ + "unittest/common/storage/distributeddb_storage_commit_storage_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBInterfacesDataOperationSyncDBTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_data_operation_syncdb_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesRegisterSyncDBTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_register_syncdb_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesTransactionSyncDBTest") { + sources = [ + "unittest/common/interfaces/distributeddb_interfaces_transaction_syncdb_test.cpp", + "unittest/common/interfaces/distributeddb_interfaces_transaction_testcase.cpp", + ] +} + +distributeddb_unittest("DistributedDBSingleVerP2PSyncTest") { + sources = + [ "unittest/common/syncer/distributeddb_single_ver_p2p_sync_test.cpp" ] +} + +distributeddb_unittest("DistributedDBSingleVerMsgScheduleTest") { + sources = [ + "unittest/common/syncer/distributeddb_single_ver_msg_schedule_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBInterfacesNBDelegateTest") { + sources = [ + "unittest/common/interfaces/distributeddb_interfaces_nb_delegate_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBCommonTest") { + sources = [ "unittest/common/common/distributeddb_common_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesNBDelegateLocalBatchTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_nb_delegate_local_batch_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesTransactionOptimizationTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_transaction_optimization_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesQueryDBTest") { + sources = + [ "unittest/common/interfaces/distributeddb_interfaces_query_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesNBDelegateSchemaPutTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_nb_delegate_schema_put_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesNBTransactionTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_nb_transaction_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesNBPublishTest") { + sources = [ + "unittest/common/interfaces/distributeddb_interfaces_nb_publish_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBInterfacesNBUnpublishTest") { + sources = [ + "unittest/common/interfaces/distributeddb_interfaces_nb_unpublish_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBInterfacesSpaceManagementTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_space_management_test.cpp" ] +} + +distributeddb_unittest("DistributedDBStorageRegisterObserverTest") { + sources = [ + "unittest/common/storage/distributeddb_storage_register_observer_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBCommunicatorTest") { + sources = [ + "unittest/common/communicator/adapter_stub.cpp", + "unittest/common/communicator/distributeddb_communicator_common.cpp", + "unittest/common/communicator/distributeddb_communicator_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBCommunicatorSendReceiveTest") { + sources = [ + "unittest/common/communicator/adapter_stub.cpp", + "unittest/common/communicator/distributeddb_communicator_common.cpp", + "unittest/common/communicator/distributeddb_communicator_send_receive_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBCommunicatorDeepTest") { + sources = [ + "unittest/common/communicator/adapter_stub.cpp", + "unittest/common/communicator/distributeddb_communicator_common.cpp", + "unittest/common/communicator/distributeddb_communicator_deep_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBSyncerDeviceManagerTest") { + sources = + [ "unittest/common/syncer/distributeddb_syncer_device_manager_test.cpp" ] +} + +distributeddb_unittest("DistributedDBMultiVerP2PSyncTest") { + sources = + [ "unittest/common/syncer/distributeddb_multi_ver_p2p_sync_test.cpp" ] +} + +distributeddb_unittest("DistributedDBStorageSQLiteSingleVerNaturalStoreTest") { + sources = [ + "unittest/common/storage/distributeddb_storage_single_ver_natural_store_testcase.cpp", + "unittest/common/storage/distributeddb_storage_sqlite_single_ver_natural_store_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBStorageMemorySingleVerNaturalStoreTest") { + sources = [ + "unittest/common/storage/distributeddb_storage_memory_single_ver_naturall_store_test.cpp", + "unittest/common/storage/distributeddb_storage_single_ver_natural_store_testcase.cpp", + ] +} + +distributeddb_unittest("DistributedDBEventLoopTimerTest") { + sources = [ "unittest/common/common/evloop_timer_unit_test.cpp" ] +} + +distributeddb_unittest("DistributedDBTimeSyncTest") { + sources = [ + "unittest/common/syncer/distributeddb_time_sync_test.cpp", + "unittest/common/syncer/virtual_time_sync_communicator.cpp", + ] +} + +distributeddb_unittest("DistributedDBDeviceIdentifierTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_device_identifier_test.cpp" ] +} + +distributeddb_unittest("DistributedDBSingleVersionResultSetTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_single_version_result_set_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesDatabaseCorruptTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_database_corrupt_test.cpp" ] +} + +distributeddb_unittest("DistributedDBFilePackageTest") { + sources = [ "unittest/common/storage/distributeddb_file_package_test.cpp" ] +} + +distributeddb_unittest("DistributedDBMultiVerVacuumTest") { + sources = [ + "unittest/common/storage/distributeddb_multi_ver_vacuum_test.cpp", + "unittest/common/storage/multi_ver_vacuum_executor_stub.cpp", + ] +} + +distributeddb_unittest("DistributedDBParcelTest") { + sources = [ "unittest/common/common/distributeddb_parcel_unit_test.cpp" ] +} + +distributeddb_unittest("DistributedDBAbilitySyncTest") { + sources = [ "unittest/common/syncer/distributeddb_ability_sync_test.cpp" ] +} + +distributeddb_unittest("DistributedDBSchemaObjectTest") { + sources = [ "unittest/common/common/distributeddb_schema_object_test.cpp" ] +} + +distributeddb_unittest("DistributedDBStorageSingleVerUpgradeTest") { + sources = [ + "unittest/common/storage/distributeddb_storage_single_ver_upgrade_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBSqliteRegisterTest") { + sources = [ "unittest\common\storage\distributeddb_sqlite_register_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesAutoLaunchTest") { + sources = [ + "unittest/common/interfaces/distributeddb_interfaces_auto_launch_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBInterfacesIndexUnitTest") { + sources = [ + "unittest\common\interfaces\distributeddb_interfaces_index_unit_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBAutoLaunchUnitTest") { + sources = [ "unittest/common/common/distributeddb_auto_launch_test.cpp" ] +} + +distributeddb_unittest("DistributedDBDataCompressionTest") { + sources = [ "unittest/common/common/distributeddb_data_compression_test.cpp" ] +} + +############################################################################### +distributeddb_unittest("DistributedDBJsonPrecheckUnitTest") { + sources = + [ "unittest/common/common/distributeddb_json_precheck_unit_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesNBResultsetPerfTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_resultset_performance.cpp" ] +} + +distributeddb_unittest("DistributedDBStorageResultAndJsonOptimizeTest") { + sources = [ "unittest/common/storage/distributeddb_storage_resultset_and_json_optimize.cpp" ] +} + +distributeddb_unittest("DistributedDBStorageIndexOptimizeTest") { + sources = [ + "unittest/common/storage/distributeddb_storage_index_optimize_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBSingleVerP2PSyncCheckTest") { + sources = [ + "unittest/common/syncer/distributeddb_single_ver_p2p_sync_check_test.cpp", + ] +} + +distributeddb_unittest("RuntimeContextProcessSystemApiAdapterImplTest") { + sources = [ "unittest/common/interfaces/runtime_context_process_system_api_adapter_impl_test.cpp" ] +} + +distributeddb_unittest("DistributedDBInterfacesSchemaDatabaseUpgradeTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_schema_database_upgrade_test.cpp" ] +} + +distributeddb_unittest("DistributedDBStorageQuerySyncTest") { + sources = + [ "unittest/common/storage/distributeddb_storage_query_sync_test.cpp" ] +} + +distributeddb_unittest("DistributedDBSingleVerP2PQuerySyncTest") { + sources = [ + "unittest/common/syncer/distributeddb_single_ver_p2p_query_sync_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBCommunicatorProxyTest") { + sources = + [ "unittest/common/syncer/distributeddb_communicator_proxy_test.cpp" ] +} + +distributeddb_unittest("DistributedDBSingleVerP2PSubscribeSyncTest") { + sources = [ "unittest/common/syncer/distributeddb_single_ver_p2p_subsribe_sync_test.cpp" ] +} + +distributeddb_unittest("DistributedDBMockSyncModuleTest") { + sources = [ "unittest/common/syncer/distributeddb_mock_sync_module_test.cpp" ] +} + +distributeddb_unittest("DistributedInterfacesRelationalTest") { + sources = [ + "unittest/common/interfaces/distributeddb_interfaces_relational_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBRelationalSchemaObjectTest") { + sources = [ + "unittest/common/common/distributeddb_relational_schema_object_test.cpp", + ] +} + +distributeddb_unittest("DistributedDBInterfacesRelationalSyncTest") { + sources = [ "unittest/common/interfaces/distributeddb_interfaces_relational_sync_test.cpp" ] +} + +distributeddb_unittest("DistributedDBRelationalGetDataTest") { + sources = + [ "unittest/common/storage/distributeddb_relational_get_data_test.cpp" ] +} + +distributeddb_unittest("DistributedDBSingleVerMultiUserTest") { + sources = + [ "unittest/common/syncer/distributeddb_single_ver_multi_user_test.cpp" ] +} + +############################################################################### +group("unittest") { + testonly = true + deps = [ "//third_party/googletest:gmock" ] + + deps += [ + ":DistributedDBAbilitySyncTest", + ":DistributedDBAutoLaunchUnitTest", + ":DistributedDBCommonTest", + ":DistributedDBCommunicatorDeepTest", + ":DistributedDBCommunicatorProxyTest", + ":DistributedDBCommunicatorSendReceiveTest", + ":DistributedDBCommunicatorTest", + ":DistributedDBDeviceIdentifierTest", + ":DistributedDBEventLoopTimerTest", + ":DistributedDBFilePackageTest", + ":DistributedDBInterfacesAutoLaunchTest", + ":DistributedDBInterfacesDataOperationSyncDBTest", + ":DistributedDBInterfacesDataOperationTest", + ":DistributedDBInterfacesDatabaseCorruptTest", + ":DistributedDBInterfacesDatabaseTest", + ":DistributedDBInterfacesEncryptDatabaseTest", + ":DistributedDBInterfacesEncryptDelegateTest", + ":DistributedDBInterfacesImportAndExportTest", + ":DistributedDBInterfacesIndexUnitTest", + ":DistributedDBInterfacesNBDelegateLocalBatchTest", + ":DistributedDBInterfacesNBDelegateSchemaPutTest", + ":DistributedDBInterfacesNBDelegateTest", + ":DistributedDBInterfacesNBPublishTest", + ":DistributedDBInterfacesNBResultsetPerfTest", + ":DistributedDBInterfacesNBTransactionTest", + ":DistributedDBInterfacesNBUnpublishTest", + ":DistributedDBInterfacesQueryDBTest", + ":DistributedDBInterfacesRegisterSyncDBTest", + ":DistributedDBInterfacesRelationalSyncTest", + ":DistributedDBInterfacesSchemaDatabaseUpgradeTest", + ":DistributedDBInterfacesSpaceManagementTest", + ":DistributedDBInterfacesTransactionOptimizationTest", + ":DistributedDBInterfacesTransactionSyncDBTest", + ":DistributedDBInterfacesTransactionTest", + ":DistributedDBJsonPrecheckUnitTest", + ":DistributedDBMockSyncModuleTest", + ":DistributedDBMultiVerP2PSyncTest", + ":DistributedDBMultiVerVacuumTest", + ":DistributedDBNotificationChainTest", + ":DistributedDBParcelTest", + ":DistributedDBRelationalGetDataTest", + ":DistributedDBRelationalSchemaObjectTest", + ":DistributedDBSchemaObjectTest", + ":DistributedDBSchemalTest", + ":DistributedDBSingleVerMsgScheduleTest", + ":DistributedDBSingleVerMultiUserTest", + ":DistributedDBSingleVerP2PQuerySyncTest", + ":DistributedDBSingleVerP2PSubscribeSyncTest", + ":DistributedDBSingleVerP2PSyncCheckTest", + ":DistributedDBSingleVerP2PSyncTest", + ":DistributedDBSingleVersionResultSetTest", + ":DistributedDBSqliteRegisterTest", + ":DistributedDBStorageCommitStorageTest", + ":DistributedDBStorageDataOperationTest", + ":DistributedDBStorageIndexOptimizeTest", + ":DistributedDBStorageMemorySingleVerNaturalStoreTest", + ":DistributedDBStorageQuerySyncTest", + ":DistributedDBStorageRegisterConflictTest", + ":DistributedDBStorageRegisterObserverTest", + ":DistributedDBStorageResultAndJsonOptimizeTest", + ":DistributedDBStorageSQLiteSingleVerNaturalStoreTest", + ":DistributedDBStorageSingleVerUpgradeTest", + ":DistributedDBStorageTransactionDataTest", + ":DistributedDBStorageTransactionRecordTest", + ":DistributedDBSyncerDeviceManagerTest", + ":DistributedDBTimeSyncTest", + ":DistributedInterfacesRelationalTest", + ":RuntimeContextProcessSystemApiAdapterImplTest", + ] +} + +############################################################################### + +group("distributeddatamgr_fuzztest") { + testonly = true + deps = [] + deps += [ + "fuzztest/delegate_fuzzer:fuzztest", + "fuzztest/fileoper_fuzzer:fuzztest", + "fuzztest/importfile_fuzzer:fuzztest", + "fuzztest/iprocesscommunicator_fuzzer:fuzztest", + "fuzztest/kvstoreresultset_fuzzer:fuzztest", + "fuzztest/nbdelegate_fuzzer:fuzztest", + "fuzztest/parseckeck_fuzzer:fuzztest", + "fuzztest/query_fuzzer:fuzztest", + "fuzztest/rekey_fuzzer:fuzztest", + ] +} +############################################################################### diff --git a/mock/distributeddb/test/fuzztest/common/distributeddb_tools_test.cpp b/mock/distributeddb/test/fuzztest/common/distributeddb_tools_test.cpp new file mode 100644 index 00000000..78456936 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/common/distributeddb_tools_test.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "distributeddb_tools_test.h" +#include +#include +#include +#include +#include +#include +#include + +#include "db_common.h" +#include "db_constant.h" +#include "generic_single_ver_kv_entry.h" +#include "platform_specific.h" +#include "single_ver_data_packet.h" +#include "value_hash_calc.h" + +using namespace DistributedDB; +namespace DistributedDBTest { +int DistributedDBToolsTest::GetCurrentDir(std::string &dir) +{ + static const int maxFileLength = 1024; + dir = ""; + char buffer[maxFileLength] = {0}; + int length = readlink("/proc/self/exe", buffer, maxFileLength); + if (length < 0 || length >= maxFileLength) { + LOGE("read directory err length:%d", length); + return -E_LENGTH_ERROR; + } + LOGD("DIR = %s", buffer); + dir = buffer; + if (std::string::npos == dir.rfind("/") && std::string::npos == dir.rfind("\\")) { + LOGE("current patch format err"); + return -E_INVALID_PATH; + } + + if (dir.rfind("/") != std::string::npos) { + dir.erase(dir.rfind("/") + 1); + } + return E_OK; +} + +void DistributedDBToolsTest::TestDirInit(std::string& dir) +{ + if (GetCurrentDir(dir) != E_OK) { + dir = "/"; + } + + dir.append("testDbDir"); + DIR *dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + if (OS::MakeDBDirectory(dir) != 0) { + LOGI("MakeDirectory err!"); + dir = "/"; + return; + } + } else { + closedir(dirTmp); + } +} + +int DistributedDBToolsTest::RemoveTestDbFiles(const std::string& dir) +{ + bool isExisted = OS::CheckPathExistence(dir); + if (!isExisted) { + return E_OK; + } + + int nFile = 0; + std::string dirName; + struct dirent *direntPtr = nullptr; + DIR *dirPtr = opendir(dir.c_str()); + if (dirPtr == nullptr) { + LOGE("opendir error!"); + return -E_INVALID_PATH; + } + while (true) { + direntPtr = readdir(dirPtr); + // condition to exit the loop + if (direntPtr == nullptr) { + break; + } + // only remove all *.db files + std::string str(direntPtr->d_name); + if (str == "." || str == "..") { + continue; + } + dirName.clear(); + dirName.append(dir).append("/").append(str); + if (direntPtr->d_type == DT_DIR) { + RemoveTestDbFiles(dirName); + rmdir(dirName.c_str()); + } else if (remove(dirName.c_str()) != 0) { + LOGI("remove file: %s failed!", dirName.c_str()); + continue; + } + nFile++; + } + closedir(dirPtr); + LOGI("Total %d test db files are removed!", nFile); + return 0; +} + +void DistributedDBToolsTest::GetRandomKeyValue(std::vector &value, uint32_t defaultSize) +{ + uint32_t randSize = 0; + if (defaultSize == 0) { + uint8_t simSize = 0; + RAND_bytes(&simSize, 1); + randSize = (simSize == 0) ? 1 : simSize; + } else { + randSize = defaultSize; + } + + value.resize(randSize); + RAND_bytes(value.data(), randSize); +} + +KvStoreObserverTest::KvStoreObserverTest() : callCount_(0), isCleared_(false) +{} + +void KvStoreObserverTest::OnChange(const KvStoreChangedData& data) +{ + callCount_++; + inserted_ = data.GetEntriesInserted(); + updated_ = data.GetEntriesUpdated(); + deleted_ = data.GetEntriesDeleted(); + isCleared_ = data.IsCleared(); + LOGD("Onchangedata :%zu -- %zu -- %zu -- %d", inserted_.size(), updated_.size(), deleted_.size(), isCleared_); + LOGD("Onchange() called success!"); +} +} \ No newline at end of file diff --git a/mock/distributeddb/test/fuzztest/common/distributeddb_tools_test.h b/mock/distributeddb/test/fuzztest/common/distributeddb_tools_test.h new file mode 100644 index 00000000..c47f71b6 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/common/distributeddb_tools_test.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_TOOLS_TEST_H +#define DISTRIBUTEDDB_TOOLS_TEST_H + +#include +#include +#include +#include +#include +#include + +#include "db_types.h" +#include "kv_store_changed_data.h" +#include "kv_store_delegate_impl.h" +#include "kv_store_delegate_manager.h" +#include "kv_store_nb_delegate.h" +#include "kv_store_observer.h" +#include "log_print.h" +#include "message.h" +#include "query.h" + +namespace DistributedDBTest { +class DistributedDBToolsTest final { +public: + DistributedDBToolsTest() {} + ~DistributedDBToolsTest() {} + + static void TestDirInit(std::string &); + // remove the test db files in the test directory of dir. + static int RemoveTestDbFiles(const std::string &); + static int GetCurrentDir(std::string& dir); + static void GetRandomKeyValue(std::vector &value, uint32_t defaultSize = 0); +}; + +class KvStoreObserverTest : public DistributedDB::KvStoreObserver { +public: + KvStoreObserverTest(); + ~KvStoreObserverTest() {} + + // callback function will be called when the db data is changed. + void OnChange(const DistributedDB::KvStoreChangedData&); + +private: + unsigned long callCount_; + bool isCleared_; + std::list inserted_; + std::list updated_; + std::list deleted_; +}; +} // namespace DistributedDBTest +#endif // DISTRIBUTEDDB_TOOLS_TEST_H \ No newline at end of file diff --git a/mock/distributeddb/test/fuzztest/delegate_fuzzer/BUILD.gn b/mock/distributeddb/test/fuzztest/delegate_fuzzer/BUILD.gn new file mode 100644 index 00000000..8e12dd17 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/delegate_fuzzer/BUILD.gn @@ -0,0 +1,103 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +#####################hydra-fuzz################### +import("//build/config/features.gni") +import("//build/test.gni") + +##############################fuzztest########################################## +ohos_fuzztest("DelegateFuzzTest") { + module_out_path = "distributeddatamgr/distributeddb" + + include_dirs = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/syncer", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/storage", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/sqlite", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/multiver", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/src", + "//utils/native/base/include", + "//third_party/sqlite/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include/relational", + "//third_party/jsoncpp/include/json", + "//third_party/skia/third_party/externals/spirv-headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/skia/third_party/externals/swiftshader/third_party/SPIRV-Headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/jsoncpp/include/json", + "//third_party/grpc/src/core/lib/json", + ] + + cflags = [ + "-g", + "-O0", + "-Wno-unused-variable", + "-fno-omit-frame-pointer", + ] + + fuzz_config_file = "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/delegate_fuzzer" + + sources = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/db_common.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/log_print.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/platform_specific.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common/distributeddb_tools_test.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.cpp", + "delegate_fuzzer.cpp", + ] + + defines = [ + "SQLITE_ENABLE_SNAPSHOT", + "_LARGEFILE64_SOURCE", + "_FILE_OFFSET_BITS=64", + "SQLITE_HAS_CODEC", + "SQLITE_ENABLE_JSON1", + "USING_HILOG_LOGGER", + "USE_SQLITE_SYMBOLS", + "USING_DB_JSON_EXTRACT_AUTOMATICALLY", + "LOW_LEVEL_MEM_DEV", + "JSONCPP_USE_BUILDER", + "OMIT_FLATBUFFER", + "RELATIONAL_STORE", + "SQLITE_DISTRIBUTE_RELATIONAL", + ] + + deps = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb:distributeddb", + "//third_party/jsoncpp:jsoncpp", + "//third_party/openssl:libcrypto_shared", + "//utils/native/base:utils", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] +} + +############################################################################### + +group("fuzztest") { + testonly = true + deps = [] + deps += [ + # deps file + ":DelegateFuzzTest", + ] +} diff --git a/mock/distributeddb/test/fuzztest/delegate_fuzzer/corpus/init b/mock/distributeddb/test/fuzztest/delegate_fuzzer/corpus/init new file mode 100644 index 00000000..bc977bd9 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/delegate_fuzzer/corpus/init @@ -0,0 +1,14 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +FUZZ \ No newline at end of file diff --git a/mock/distributeddb/test/fuzztest/delegate_fuzzer/delegate_fuzzer.cpp b/mock/distributeddb/test/fuzztest/delegate_fuzzer/delegate_fuzzer.cpp new file mode 100644 index 00000000..3d526c52 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/delegate_fuzzer/delegate_fuzzer.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "delegate_fuzzer.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_test.h" + +using namespace DistributedDB; +using namespace DistributedDBTest; + +namespace OHOS { +std::vector CreateEntries(const uint8_t* data, size_t size, std::vector& keys) +{ + std::vector entries; + // key'length is less than 1024. + auto count = static_cast(std::min(size, size_t(1024))); + for (int i = 1; i < count; i++) { + Entry entry; + entry.key = std::vector(data, data + 1); + entry.value = std::vector(data, data + size); + keys.push_back(entry.key); + entries.push_back(entry); + } + return entries; +} + +void MultiCombineFuzzer(const uint8_t* data, size_t size, KvStoreDelegate::Option &option) +{ + static auto kvManger = KvStoreDelegateManager("APP_ID", "USER_ID"); + KvStoreConfig config; + DistributedDBToolsTest::TestDirInit(config.dataDir); + kvManger.SetKvStoreConfig(config); + KvStoreDelegate *kvDelegatePtr = nullptr; + kvManger.GetKvStore("distributed_delegate_test", option, + [&kvDelegatePtr](DBStatus status, KvStoreDelegate* kvDelegate) { + if (status == DBStatus::OK) { + kvDelegatePtr = kvDelegate; + } + }); + KvStoreObserverTest *observer = new (std::nothrow) KvStoreObserverTest; + if ((kvDelegatePtr == nullptr) || (observer == nullptr)) { + return; + } + + kvDelegatePtr->RegisterObserver(observer); + Key key = std::vector(data, data + (size % 1024)); /* 1024 is max */ + Value value = std::vector(data, data + size); + kvDelegatePtr->Put(key, value); + KvStoreSnapshotDelegate* kvStoreSnapshotPtr = nullptr; + kvDelegatePtr->GetKvStoreSnapshot(nullptr, + [&kvStoreSnapshotPtr](DBStatus status, KvStoreSnapshotDelegate* kvStoreSnapshot) { + kvStoreSnapshotPtr = std::move(kvStoreSnapshot); + }); + if (kvStoreSnapshotPtr == nullptr) { + return; + } + auto valueCallback = [&value] (DBStatus status, const Value &getValue) { + value = getValue; + }; + + kvStoreSnapshotPtr->Get(key, valueCallback); + kvDelegatePtr->Delete(key); + kvStoreSnapshotPtr->Get(key, valueCallback); + std::vector keys; + kvDelegatePtr->PutBatch(CreateEntries(data, size, keys)); + Key keyPrefix = std::vector(data, data + 1); + kvStoreSnapshotPtr->GetEntries(keyPrefix, [](DBStatus status, const std::vector &entries) { + (void) entries.size(); + }); + + kvDelegatePtr->DeleteBatch(keys); + kvDelegatePtr->Clear(); + kvDelegatePtr->UnRegisterObserver(observer); + kvDelegatePtr->ReleaseKvStoreSnapshot(kvStoreSnapshotPtr); + kvManger.CloseKvStore(kvDelegatePtr); + kvManger.DeleteKvStore("distributed_delegate_test"); + DistributedDBToolsTest::RemoveTestDbFiles(config.dataDir); +} +} + +/* Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + CipherPassword passwd; + KvStoreDelegate::Option option = {true, true, false, CipherType::DEFAULT, passwd}; + OHOS::MultiCombineFuzzer(data, size, option); + option = {true, false, false, CipherType::DEFAULT, passwd}; + OHOS::MultiCombineFuzzer(data, size, option); + return 0; +} + diff --git a/mock/distributeddb/test/fuzztest/delegate_fuzzer/delegate_fuzzer.h b/mock/distributeddb/test/fuzztest/delegate_fuzzer/delegate_fuzzer.h new file mode 100644 index 00000000..0c3b6b65 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/delegate_fuzzer/delegate_fuzzer.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 EDELEGATE_FUZZER_H +#define EDELEGATE_FUZZER_H + +#define FUZZ_PROJECT_NAME "Delegate_fuzzer" + +#endif // EDELEGATE_FUZZER_H diff --git a/mock/distributeddb/test/fuzztest/delegate_fuzzer/project.xml b/mock/distributeddb/test/fuzztest/delegate_fuzzer/project.xml new file mode 100644 index 00000000..be92381e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/delegate_fuzzer/project.xml @@ -0,0 +1,25 @@ + + + + + + 1000 + + 30 + + 4096 + + diff --git a/mock/distributeddb/test/fuzztest/fileoper_fuzzer/BUILD.gn b/mock/distributeddb/test/fuzztest/fileoper_fuzzer/BUILD.gn new file mode 100644 index 00000000..25bdaf6c --- /dev/null +++ b/mock/distributeddb/test/fuzztest/fileoper_fuzzer/BUILD.gn @@ -0,0 +1,104 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +#####################hydra-fuzz################### +import("//build/config/features.gni") +import("//build/test.gni") + +##############################fuzztest########################################## +ohos_fuzztest("FileOperFuzzTest") { + module_out_path = "distributeddatamgr/distributeddb" + + include_dirs = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/syncer", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/storage", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/sqlite", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/multiver", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include/relational", + "//third_party/jsoncpp/include/json", + "//third_party/skia/third_party/externals/spirv-headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/skia/third_party/externals/swiftshader/third_party/SPIRV-Headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/jsoncpp/include/json", + "//third_party/grpc/src/core/lib/json", + "//utils/native/base/include", + "//third_party/sqlite/include", + "//third_party/googletest/googletest/include/gtest", + ] + + cflags = [ + "-g", + "-O0", + "-Wno-unused-variable", + "-fno-omit-frame-pointer", + ] + + fuzz_config_file = "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/fileoper_fuzzer" + + sources = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/db_common.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/log_print.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/platform_specific.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common/distributeddb_tools_test.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.cpp", + "fileoper_fuzzer.cpp", + ] + + deps = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb:distributeddb", + "//third_party/jsoncpp:jsoncpp", + "//third_party/openssl:libcrypto_shared", + "//utils/native/base:utils", + ] + + defines = [ + "SQLITE_ENABLE_SNAPSHOT", + "_LARGEFILE64_SOURCE", + "_FILE_OFFSET_BITS=64", + "SQLITE_HAS_CODEC", + "SQLITE_ENABLE_JSON1", + "USING_HILOG_LOGGER", + "USE_SQLITE_SYMBOLS", + "USING_DB_JSON_EXTRACT_AUTOMATICALLY", + "LOW_LEVEL_MEM_DEV", + "JSONCPP_USE_BUILDER", + "OMIT_FLATBUFFER", + "RELATIONAL_STORE", + "SQLITE_DISTRIBUTE_RELATIONAL", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] +} + +############################################################################### +group("fuzztest") { + testonly = true + deps = [] + deps += [ + # deps file + ":FileOperFuzzTest", + ] +} +############################################################################### diff --git a/mock/distributeddb/test/fuzztest/fileoper_fuzzer/corpus/init b/mock/distributeddb/test/fuzztest/fileoper_fuzzer/corpus/init new file mode 100644 index 00000000..2f20d95e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/fileoper_fuzzer/corpus/init @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ + +FUZZ \ No newline at end of file diff --git a/mock/distributeddb/test/fuzztest/fileoper_fuzzer/fileoper_fuzzer.cpp b/mock/distributeddb/test/fuzztest/fileoper_fuzzer/fileoper_fuzzer.cpp new file mode 100644 index 00000000..05532d00 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/fileoper_fuzzer/fileoper_fuzzer.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "fileoper_fuzzer.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_test.h" +#include "process_communicator_test_stub.h" + +using namespace DistributedDB; +using namespace DistributedDBTest; + +namespace OHOS { +static auto g_kvManager = KvStoreDelegateManager("APP_ID", "USER_ID"); +std::vector CreateEntries(const uint8_t* data, size_t size) +{ + std::vector entries; + auto count = static_cast(std::min(size, size_t(1024))); + for (int i = 1; i < count; i++) { + Entry entry; + entry.key = std::vector (data, data + i); + entry.value = std::vector (data, data + size); + entries.push_back(entry); + } + return entries; +} + +void SingerVerExportAndImport(const uint8_t* data, size_t size, const std::string& testDir) +{ + KvStoreNbDelegate::Option nbOption = {true, false, true}; + KvStoreNbDelegate *kvNbDelegatePtr = nullptr; + + g_kvManager.GetKvStore("distributed_file_oper_single", nbOption, + [&kvNbDelegatePtr](DBStatus status, KvStoreNbDelegate* kvNbDelegate) { + if (status == DBStatus::OK) { + kvNbDelegatePtr = kvNbDelegate; + } + }); + if (kvNbDelegatePtr == nullptr) { + return; + } + kvNbDelegatePtr->PutBatch(CreateEntries(data, size)); + + std::string rawString(reinterpret_cast(data), size); + std::string singleExportFileName = testDir + "/" + rawString; + + CipherPassword passwd; + passwd.SetValue(data, size); + kvNbDelegatePtr->Export(singleExportFileName, passwd); + kvNbDelegatePtr->Import(singleExportFileName, passwd); + + g_kvManager.CloseKvStore(kvNbDelegatePtr); + g_kvManager.DeleteKvStore("distributed_file_oper_single"); +} + +void MultiVerExportAndImport(const uint8_t* data, size_t size, const std::string& testDir) +{ + CipherPassword passwd; + passwd.SetValue(data, size); + KvStoreDelegate::Option option = {true, false, true, CipherType::DEFAULT, passwd}; + KvStoreDelegate *kvDelegatePtr = nullptr; + g_kvManager.GetKvStore("distributed_file_oper_multi", option, + [&kvDelegatePtr](DBStatus status, KvStoreDelegate* kvStoreDelegate) { + if (status == DBStatus::OK) { + kvDelegatePtr = kvStoreDelegate; + } + }); + if (kvDelegatePtr == nullptr) { + return; + } + kvDelegatePtr->PutBatch(CreateEntries(data, size)); + std::string rawString(reinterpret_cast(data), size); + std::string multiExportFileName = testDir + "/" + rawString; + kvDelegatePtr->Export(multiExportFileName, passwd); + kvDelegatePtr->Import(multiExportFileName, passwd); + g_kvManager.CloseKvStore(kvDelegatePtr); + g_kvManager.DeleteKvStore("distributed_file_oper_multi"); +} +} + +/* Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + KvStoreConfig config; + DistributedDBToolsTest::TestDirInit(config.dataDir); + OHOS::g_kvManager.SetKvStoreConfig(config); + OHOS::g_kvManager.SetProcessLabel("FUZZ", "DISTRIBUTEDDB"); + OHOS::g_kvManager.SetProcessCommunicator(std::make_shared()); + OHOS::SingerVerExportAndImport(data, size, config.dataDir); + OHOS::MultiVerExportAndImport(data, size, config.dataDir); + DistributedDBToolsTest::RemoveTestDbFiles(config.dataDir); + return 0; +} + diff --git a/mock/distributeddb/test/fuzztest/fileoper_fuzzer/fileoper_fuzzer.h b/mock/distributeddb/test/fuzztest/fileoper_fuzzer/fileoper_fuzzer.h new file mode 100644 index 00000000..d22d0e88 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/fileoper_fuzzer/fileoper_fuzzer.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 FILEOPER_FUZZER_H +#define FILEOPER_FUZZER_H + +#define FUZZ_PROJECT_NAME "fileoper_fuzzer" + +#endif // FILEOPER_FUZZER_H diff --git a/mock/distributeddb/test/fuzztest/fileoper_fuzzer/project.xml b/mock/distributeddb/test/fuzztest/fileoper_fuzzer/project.xml new file mode 100644 index 00000000..be92381e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/fileoper_fuzzer/project.xml @@ -0,0 +1,25 @@ + + + + + + 1000 + + 30 + + 4096 + + diff --git a/mock/distributeddb/test/fuzztest/importfile_fuzzer/BUILD.gn b/mock/distributeddb/test/fuzztest/importfile_fuzzer/BUILD.gn new file mode 100644 index 00000000..e404fe10 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/importfile_fuzzer/BUILD.gn @@ -0,0 +1,103 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +#####################hydra-fuzz################### +import("//build/config/features.gni") +import("//build/test.gni") + +##############################fuzztest########################################## +ohos_fuzztest("ImportFileFuzzTest") { + module_out_path = "distributeddatamgr/distributeddb" + + include_dirs = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/syncer", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/storage", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/sqlite", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/multiver", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/src", + "//utils/native/base/include", + "//third_party/sqlite/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include/relational", + "//third_party/jsoncpp/include/json", + "//third_party/skia/third_party/externals/spirv-headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/skia/third_party/externals/swiftshader/third_party/SPIRV-Headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/jsoncpp/include/json", + "//third_party/grpc/src/core/lib/json", + ] + + cflags = [ + "-g", + "-O0", + "-Wno-unused-variable", + "-fno-omit-frame-pointer", + ] + + fuzz_config_file = "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/importfile_fuzzer" + + sources = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/db_common.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/log_print.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/platform_specific.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common/distributeddb_tools_test.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.cpp", + "importfile_fuzzer.cpp", + ] + + defines = [ + "SQLITE_ENABLE_SNAPSHOT", + "_LARGEFILE64_SOURCE", + "_FILE_OFFSET_BITS=64", + "SQLITE_HAS_CODEC", + "SQLITE_ENABLE_JSON1", + "USING_HILOG_LOGGER", + "USE_SQLITE_SYMBOLS", + "USING_DB_JSON_EXTRACT_AUTOMATICALLY", + "LOW_LEVEL_MEM_DEV", + "JSONCPP_USE_BUILDER", + "OMIT_FLATBUFFER", + "RELATIONAL_STORE", + "SQLITE_DISTRIBUTE_RELATIONAL", + ] + + deps = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb:distributeddb", + "//third_party/jsoncpp:jsoncpp", + "//third_party/openssl:libcrypto_shared", + "//utils/native/base:utils", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] +} + +############################################################################### + +group("fuzztest") { + testonly = true + deps = [] + deps += [ + # deps file + ":ImportFileFuzzTest", + ] +} diff --git a/mock/distributeddb/test/fuzztest/importfile_fuzzer/corpus/init b/mock/distributeddb/test/fuzztest/importfile_fuzzer/corpus/init new file mode 100644 index 00000000..bc977bd9 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/importfile_fuzzer/corpus/init @@ -0,0 +1,14 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +FUZZ \ No newline at end of file diff --git a/mock/distributeddb/test/fuzztest/importfile_fuzzer/importfile_fuzzer.cpp b/mock/distributeddb/test/fuzztest/importfile_fuzzer/importfile_fuzzer.cpp new file mode 100644 index 00000000..6e8e73c6 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/importfile_fuzzer/importfile_fuzzer.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "importfile_fuzzer.h" +#include +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_test.h" +#include "platform_specific.h" +#include "process_communicator_test_stub.h" + +using namespace DistributedDB; +using namespace DistributedDBTest; +using namespace std; + +static KvStoreConfig g_config; + +namespace OHOS { +void SingerVerImport(const uint8_t *data, size_t size, const std::string &importFile) +{ + static auto kvManager = KvStoreDelegateManager("APP_ID", "USER_ID"); + kvManager.SetKvStoreConfig(g_config); + kvManager.SetProcessLabel("FUZZ", "DISTRIBUTEDDB"); + kvManager.SetProcessCommunicator(std::make_shared()); + CipherPassword passwd; + passwd.SetValue(data, size); + KvStoreNbDelegate::Option option = {true, false, true, CipherType::DEFAULT, passwd}; + + KvStoreNbDelegate *kvNbDelegatePtr = nullptr; + kvManager.GetKvStore("distributed_import_single", option, + [&kvNbDelegatePtr](DBStatus status, KvStoreNbDelegate* kvNbDelegate) { + if (status == DBStatus::OK) { + kvNbDelegatePtr = kvNbDelegate; + } + }); + if (kvNbDelegatePtr == nullptr) { + return; + } + + kvNbDelegatePtr->Import(importFile, passwd); + kvManager.CloseKvStore(kvNbDelegatePtr); + kvManager.DeleteKvStore("distributed_import_single"); +} + +bool MakeImportFile(const uint8_t *data, size_t size, const std::string &realPath) +{ + std::ofstream ofs(realPath, std::ofstream::out); + if (!ofs.is_open()) { + LOGE("the file open failed"); + return false; + } + ofs.write(reinterpret_cast(data), size); + return true; +} +} + +/* Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + std::string dataDir; + DistributedDBToolsTest::TestDirInit(dataDir); + g_config.dataDir = dataDir; + std::string path = dataDir + "/fuzz" + std::to_string(U16_AT(data)); + std::string realPath; + OS::GetRealPath(path, realPath); + if (OHOS::MakeImportFile(data, size, realPath)) { + OHOS::SingerVerImport(data, size, realPath); + } + + DistributedDBToolsTest::RemoveTestDbFiles(dataDir); + return 0; +} + diff --git a/mock/distributeddb/test/fuzztest/importfile_fuzzer/importfile_fuzzer.h b/mock/distributeddb/test/fuzztest/importfile_fuzzer/importfile_fuzzer.h new file mode 100644 index 00000000..4e36950e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/importfile_fuzzer/importfile_fuzzer.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 IMPORTFILE_FUZZER_H +#define IMPORTFILE_FUZZER_H + +#define FUZZ_PROJECT_NAME "ImportFile_fuzzer" + +#include + +uint16_t U16_AT(const uint8_t * const &ptr) +{ + // 8 - 0 + return (ptr[0] << 8) | ptr[1]; +} + +uint32_t U32_AT(const uint8_t * const &ptr) +{ + // 24 - 16 - 8 - 0 + return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]; +} + +#endif // IMPORTFILE_FUZZER_H diff --git a/mock/distributeddb/test/fuzztest/importfile_fuzzer/project.xml b/mock/distributeddb/test/fuzztest/importfile_fuzzer/project.xml new file mode 100644 index 00000000..be92381e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/importfile_fuzzer/project.xml @@ -0,0 +1,25 @@ + + + + + + 1000 + + 30 + + 4096 + + diff --git a/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/BUILD.gn b/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/BUILD.gn new file mode 100644 index 00000000..94e6a46a --- /dev/null +++ b/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/BUILD.gn @@ -0,0 +1,102 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +#####################hydra-fuzz################### +import("//build/config/features.gni") +import("//build/test.gni") + +##############################fuzztest########################################## +ohos_fuzztest("IProcessCommunicatorFuzzTest") { + module_out_path = "distributeddatamgr/distributeddb" + + include_dirs = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/syncer", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/storage", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/sqlite", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/multiver", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/src", + "//utils/native/base/include", + "//third_party/sqlite/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include/relational", + "//third_party/jsoncpp/include/json", + "//third_party/skia/third_party/externals/spirv-headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/skia/third_party/externals/swiftshader/third_party/SPIRV-Headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/jsoncpp/include/json", + "//third_party/grpc/src/core/lib/json", + ] + + cflags = [ + "-g", + "-O0", + "-Wno-unused-variable", + "-fno-omit-frame-pointer", + ] + + fuzz_config_file = "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer" + + sources = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/db_common.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/log_print.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/platform_specific.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common/distributeddb_tools_test.cpp", + "iprocesscommunicator_fuzzer.cpp", + ] + + defines = [ + "SQLITE_ENABLE_SNAPSHOT", + "_LARGEFILE64_SOURCE", + "_FILE_OFFSET_BITS=64", + "SQLITE_HAS_CODEC", + "SQLITE_ENABLE_JSON1", + "USING_HILOG_LOGGER", + "USE_SQLITE_SYMBOLS", + "USING_DB_JSON_EXTRACT_AUTOMATICALLY", + "LOW_LEVEL_MEM_DEV", + "JSONCPP_USE_BUILDER", + "OMIT_FLATBUFFER", + "RELATIONAL_STORE", + "SQLITE_DISTRIBUTE_RELATIONAL", + ] + + deps = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb:distributeddb", + "//third_party/jsoncpp:jsoncpp", + "//third_party/openssl:libcrypto_shared", + "//utils/native/base:utils", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] +} + +############################################################################### + +group("fuzztest") { + testonly = true + deps = [] + deps += [ + # deps file + ":IProcessCommunicatorFuzzTest", + ] +} diff --git a/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/corpus/init b/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/corpus/init new file mode 100644 index 00000000..bc977bd9 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/corpus/init @@ -0,0 +1,14 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +FUZZ \ No newline at end of file diff --git a/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/iprocesscommunicator_fuzzer.cpp b/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/iprocesscommunicator_fuzzer.cpp new file mode 100644 index 00000000..8ddaf1cd --- /dev/null +++ b/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/iprocesscommunicator_fuzzer.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "iprocesscommunicator_fuzzer.h" +#include +#include "iprocess_communicator.h" +#include "distributeddb_tools_test.h" + +using namespace DistributedDB; +using namespace DistributedDBTest; + +class IProcessCommunicatorFuzzer { + /* Keep C++ file names the same as the class name */ +}; + +namespace OHOS { +class ProcessCommunicatorFuzzTest : public DistributedDB::IProcessCommunicator { +public: + ProcessCommunicatorFuzzTest() {} + ~ProcessCommunicatorFuzzTest() {} + DistributedDB::DBStatus Start(const std::string &processLabel) override + { + return DistributedDB::OK; + } + // The Stop should only be called after Start successfully + DistributedDB::DBStatus Stop() override + { + return DistributedDB::OK; + } + DistributedDB::DBStatus RegOnDeviceChange(const DistributedDB::OnDeviceChange &callback) override + { + onDeviceChange_ = callback; + return DistributedDB::OK; + } + DistributedDB::DBStatus RegOnDataReceive(const DistributedDB::OnDataReceive &callback) override + { + onDataReceive_ = callback; + return DistributedDB::OK; + } + DistributedDB::DBStatus SendData(const DistributedDB::DeviceInfos &dstDevInfo, + const uint8_t *datas, uint32_t length) override + { + return DistributedDB::OK; + } + uint32_t GetMtuSize() override + { + return 1 * 1024 * 1024; // 1 * 1024 * 1024 Byte. + } + DistributedDB::DeviceInfos GetLocalDeviceInfos() override + { + DistributedDB::DeviceInfos info; + info.identifier = "default"; + return info; + } + + std::vector GetRemoteOnlineDeviceInfosList() override + { + std::vector info; + return info; + } + + bool IsSameProcessLabelStartedOnPeerDevice(const DistributedDB::DeviceInfos &peerDevInfo) override + { + return true; + } + + void FuzzOnDeviceChange(const DistributedDB::DeviceInfos &devInfo, bool isOnline) + { + if (onDeviceChange_ == nullptr) { + return; + } + onDeviceChange_(devInfo, isOnline); + } + + void FuzzOnDataReceive(const DistributedDB::DeviceInfos &devInfo, const uint8_t* data, size_t size) + { + if (onDataReceive_ == nullptr) { + return; + } + onDataReceive_(devInfo, data, size); + } + +private: + DistributedDB::OnDeviceChange onDeviceChange_ = nullptr; + DistributedDB::OnDataReceive onDataReceive_ = nullptr; +}; + +void CommunicatorFuzzer(const uint8_t* data, size_t size) +{ + static auto kvManager = KvStoreDelegateManager("APP_ID", "USER_ID"); + std::string rawString(reinterpret_cast(data), size); + KvStoreDelegateManager::SetProcessLabel(rawString, "defaut"); + auto communicator = std::make_shared(); + KvStoreDelegateManager::SetProcessCommunicator(communicator); + std::string testDir; + DistributedDBToolsTest::TestDirInit(testDir); + KvStoreConfig config; + config.dataDir = testDir; + kvManager.SetKvStoreConfig(config); + KvStoreNbDelegate::Option option = {true, false, false}; + KvStoreNbDelegate *kvNbDelegatePtr = nullptr; + kvManager.GetKvStore(rawString, option, + [&kvNbDelegatePtr](DBStatus status, KvStoreNbDelegate* kvNbDelegate) { + if (status == DBStatus::OK) { + kvNbDelegatePtr = kvNbDelegate; + } + }); + DeviceInfos device = {"defaut"}; + communicator->FuzzOnDataReceive(device, data, size); + if (kvNbDelegatePtr != nullptr) { + kvManager.CloseKvStore(kvNbDelegatePtr); + kvManager.DeleteKvStore(rawString); + } +} +} + +/* Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + // 4 bytes is required + if (size < 4) { + return 0; + } + OHOS::CommunicatorFuzzer(data, size); + return 0; +} diff --git a/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/iprocesscommunicator_fuzzer.h b/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/iprocesscommunicator_fuzzer.h new file mode 100644 index 00000000..dd1cd5e5 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/iprocesscommunicator_fuzzer.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 IPROCESSCOMMUNICATOR_FUZZER_H +#define IPROCESSCOMMUNICATOR_FUZZER_H + +#define FUZZ_PROJECT_NAME "IprocessCommunicator_fuzzer" + +#endif // IPROCESSCOMMUNICATOR_FUZZER_H diff --git a/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/project.xml b/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/project.xml new file mode 100644 index 00000000..be92381e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/iprocesscommunicator_fuzzer/project.xml @@ -0,0 +1,25 @@ + + + + + + 1000 + + 30 + + 4096 + + diff --git a/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/BUILD.gn b/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/BUILD.gn new file mode 100644 index 00000000..ecb5962b --- /dev/null +++ b/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/BUILD.gn @@ -0,0 +1,103 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +#####################hydra-fuzz################### +import("//build/config/features.gni") +import("//build/test.gni") + +##############################fuzztest########################################## +ohos_fuzztest("KvStoreResultSetFuzzTest") { + module_out_path = "distributeddatamgr/distributeddb" + + include_dirs = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/syncer", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/storage", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/sqlite", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/multiver", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/src", + "//utils/native/base/include", + "//third_party/sqlite/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include/relational", + "//third_party/jsoncpp/include/json", + "//third_party/skia/third_party/externals/spirv-headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/skia/third_party/externals/swiftshader/third_party/SPIRV-Headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/jsoncpp/include/json", + "//third_party/grpc/src/core/lib/json", + ] + + cflags = [ + "-g", + "-O0", + "-Wno-unused-variable", + "-fno-omit-frame-pointer", + ] + + fuzz_config_file = "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/kvstoreresultset_fuzzer" + + sources = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/db_common.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/log_print.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/platform_specific.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common/distributeddb_tools_test.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.cpp", + "kvstoreresultset_fuzzer.cpp", + ] + + defines = [ + "SQLITE_ENABLE_SNAPSHOT", + "_LARGEFILE64_SOURCE", + "_FILE_OFFSET_BITS=64", + "SQLITE_HAS_CODEC", + "SQLITE_ENABLE_JSON1", + "USING_HILOG_LOGGER", + "USE_SQLITE_SYMBOLS", + "USING_DB_JSON_EXTRACT_AUTOMATICALLY", + "LOW_LEVEL_MEM_DEV", + "JSONCPP_USE_BUILDER", + "OMIT_FLATBUFFER", + "RELATIONAL_STORE", + "SQLITE_DISTRIBUTE_RELATIONAL", + ] + + deps = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb:distributeddb", + "//third_party/jsoncpp:jsoncpp", + "//third_party/openssl:libcrypto_shared", + "//utils/native/base:utils", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] +} + +############################################################################### + +group("fuzztest") { + testonly = true + deps = [] + deps += [ + # deps file + ":KvStoreResultSetFuzzTest", + ] +} diff --git a/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/corpus/init b/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/corpus/init new file mode 100644 index 00000000..bc977bd9 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/corpus/init @@ -0,0 +1,14 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +FUZZ \ No newline at end of file diff --git a/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/kvstoreresultset_fuzzer.cpp b/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/kvstoreresultset_fuzzer.cpp new file mode 100644 index 00000000..f88b8ded --- /dev/null +++ b/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/kvstoreresultset_fuzzer.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "kvstoreresultset_fuzzer.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_test.h" +#include "process_communicator_test_stub.h" + +using namespace DistributedDB; +using namespace DistributedDBTest; +using namespace std; + +namespace OHOS { +static auto g_kvManager = KvStoreDelegateManager("APP_ID", "USER_ID"); +void ResultSetFuzzer(const uint8_t* data, size_t size) +{ + KvStoreNbDelegate::Option option = {true, false, true}; + KvStoreNbDelegate *kvNbDelegatePtr = nullptr; + + g_kvManager.GetKvStore("distributed_nb_delegate_result_set_test", option, + [&kvNbDelegatePtr](DBStatus status, KvStoreNbDelegate * kvNbDelegate) { + if (status == DBStatus::OK) { + kvNbDelegatePtr = kvNbDelegate; + } + }); + if (kvNbDelegatePtr == nullptr) { + return; + } + + Key testKey; + Value testValue; + for (size_t i = 0; i < size; i++) { + testKey.clear(); + testValue.clear(); + testKey.push_back(data[i]); + testValue.push_back(data[i]); + kvNbDelegatePtr->Put(testKey, testValue); + } + + Key keyPrefix; + KvStoreResultSet *readResultSet = nullptr; + kvNbDelegatePtr->GetEntries(keyPrefix, readResultSet); + if (readResultSet != nullptr) { + readResultSet->GetCount(); + readResultSet->GetPosition(); + readResultSet->MoveToNext(); + readResultSet->MoveToPrevious(); + readResultSet->MoveToFirst(); + readResultSet->MoveToLast(); + + if (size == 0) { + return; + } + auto pos = U32_AT(data) % size; + readResultSet->MoveToPosition(pos++); + readResultSet->Move(0 - pos); + readResultSet->IsFirst(); + readResultSet->IsLast(); + readResultSet->IsBeforeFirst(); + readResultSet->IsAfterLast(); + kvNbDelegatePtr->CloseResultSet(readResultSet); + } + + g_kvManager.CloseKvStore(kvNbDelegatePtr); + g_kvManager.DeleteKvStore("distributed_nb_delegate_result_set_test"); +} +} + +/* Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + // u32 4 bytes + if (size < 4) { + return 0; + } + KvStoreConfig config; + DistributedDBToolsTest::TestDirInit(config.dataDir); + OHOS::g_kvManager.SetKvStoreConfig(config); + OHOS::ResultSetFuzzer(data, size); + DistributedDBToolsTest::RemoveTestDbFiles(config.dataDir); + return 0; +} + diff --git a/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/kvstoreresultset_fuzzer.h b/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/kvstoreresultset_fuzzer.h new file mode 100644 index 00000000..a4bee3fb --- /dev/null +++ b/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/kvstoreresultset_fuzzer.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 KVSTORERESULTSET_FUZZER_H +#define KVSTORERESULTSET_FUZZER_H + +#define FUZZ_PROJECT_NAME "kvstoreresultset_fuzzer" + +#include + +uint16_t U16_AT(const uint8_t * const &ptr) +{ + // 8 - 0 + return (ptr[0] << 8) | ptr[1]; +} + +uint32_t U32_AT(const uint8_t * const &ptr) +{ + // 24 - 16 - 8 - 0 + return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]; +} + +#endif // KVSTORERESULTSET_FUZZER_H diff --git a/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/project.xml b/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/project.xml new file mode 100644 index 00000000..be92381e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/kvstoreresultset_fuzzer/project.xml @@ -0,0 +1,25 @@ + + + + + + 1000 + + 30 + + 4096 + + diff --git a/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/BUILD.gn b/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/BUILD.gn new file mode 100644 index 00000000..da6612af --- /dev/null +++ b/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/BUILD.gn @@ -0,0 +1,103 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +#####################hydra-fuzz################### +import("//build/config/features.gni") +import("//build/test.gni") + +##############################fuzztest########################################## +ohos_fuzztest("NbDelegateFuzzTest") { + module_out_path = "distributeddatamgr/distributeddb" + + include_dirs = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/syncer", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/storage", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/sqlite", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/multiver", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/src", + "//utils/native/base/include", + "//third_party/sqlite/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include/relational", + "//third_party/jsoncpp/include/json", + "//third_party/skia/third_party/externals/spirv-headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/skia/third_party/externals/swiftshader/third_party/SPIRV-Headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/jsoncpp/include/json", + "//third_party/grpc/src/core/lib/json", + ] + + cflags = [ + "-g", + "-O0", + "-Wno-unused-variable", + "-fno-omit-frame-pointer", + ] + + fuzz_config_file = "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/nbdelegate_fuzzer" + + sources = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/db_common.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/log_print.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/platform_specific.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common/distributeddb_tools_test.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.cpp", + "nbdelegate_fuzzer.cpp", + ] + + defines = [ + "SQLITE_ENABLE_SNAPSHOT", + "_LARGEFILE64_SOURCE", + "_FILE_OFFSET_BITS=64", + "SQLITE_HAS_CODEC", + "SQLITE_ENABLE_JSON1", + "USING_HILOG_LOGGER", + "USE_SQLITE_SYMBOLS", + "USING_DB_JSON_EXTRACT_AUTOMATICALLY", + "LOW_LEVEL_MEM_DEV", + "JSONCPP_USE_BUILDER", + "OMIT_FLATBUFFER", + "RELATIONAL_STORE", + "SQLITE_DISTRIBUTE_RELATIONAL", + ] + + deps = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb:distributeddb", + "//third_party/jsoncpp:jsoncpp", + "//third_party/openssl:libcrypto_shared", + "//utils/native/base:utils", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] +} + +############################################################################### + +group("fuzztest") { + testonly = true + deps = [] + deps += [ + # deps file + ":NbDelegateFuzzTest", + ] +} diff --git a/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/corpus/init b/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/corpus/init new file mode 100644 index 00000000..bc977bd9 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/corpus/init @@ -0,0 +1,14 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +FUZZ \ No newline at end of file diff --git a/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/nbdelegate_fuzzer.cpp b/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/nbdelegate_fuzzer.cpp new file mode 100644 index 00000000..5138d498 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/nbdelegate_fuzzer.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "nbdelegate_fuzzer.h" +#include +#include "distributeddb_tools_test.h" +#include "kv_store_delegate.h" +#include "kv_store_delegate_manager.h" +#include "kv_store_observer.h" +#include "platform_specific.h" + +class KvStoreNbDelegateCURDFuzzer { + /* Keep C++ file names the same as the class name. */ +}; + +namespace OHOS { +using namespace DistributedDB; +using namespace DistributedDBTest; + +class KvStoreObserverFuzzTest : public DistributedDB::KvStoreObserver { +public: + KvStoreObserverFuzzTest(); + ~KvStoreObserverFuzzTest() = default; + KvStoreObserverFuzzTest(const KvStoreObserverFuzzTest &) = delete; + KvStoreObserverFuzzTest& operator=(const KvStoreObserverFuzzTest &) = delete; + KvStoreObserverFuzzTest(KvStoreObserverFuzzTest &&) = delete; + KvStoreObserverFuzzTest& operator=(KvStoreObserverFuzzTest &&) = delete; + // callback function will be called when the db data is changed. + void OnChange(const DistributedDB::KvStoreChangedData &); + + // reset the callCount_ to zero. + void ResetToZero(); + // get callback results. + unsigned long GetCallCount() const; + const std::list &GetEntriesInserted() const; + const std::list &GetEntriesUpdated() const; + const std::list &GetEntriesDeleted() const; + bool IsCleared() const; + +private: + unsigned long callCount_ = 0; + bool isCleared_ = false; + std::list inserted_ {}; + std::list updated_ {}; + std::list deleted_ {}; +}; + +KvStoreObserverFuzzTest::KvStoreObserverFuzzTest() +{ + callCount_ = 0; +} + +void KvStoreObserverFuzzTest::OnChange(const KvStoreChangedData &data) +{ + callCount_++; + inserted_ = data.GetEntriesInserted(); + updated_ = data.GetEntriesUpdated(); + deleted_ = data.GetEntriesDeleted(); + isCleared_ = data.IsCleared(); +} + +void KvStoreObserverFuzzTest::ResetToZero() +{ + callCount_ = 0; + isCleared_ = false; + inserted_.clear(); + updated_.clear(); + deleted_.clear(); +} + +unsigned long KvStoreObserverFuzzTest::GetCallCount() const +{ + return callCount_; +} + +const std::list &KvStoreObserverFuzzTest::GetEntriesInserted() const +{ + return inserted_; +} + +const std::list &KvStoreObserverFuzzTest::GetEntriesUpdated() const +{ + return updated_; +} +const std::list &KvStoreObserverFuzzTest::GetEntriesDeleted() const +{ + return deleted_; +} + +bool KvStoreObserverFuzzTest::IsCleared() const +{ + return isCleared_; +} + +std::vector CreateEntries(const uint8_t* data, size_t size, std::vector keys) +{ + std::vector entries; + // key'length is less than 1024. + auto count = static_cast(std::min(size, size_t(1024))); + for (int i = 1; i < count; i++) { + Entry entry; + entry.key = std::vector(data, data + i); + keys.push_back(entry.key); + entry.value = std::vector(data, data + size); + entries.push_back(entry); + } + return entries; +} + +void FuzzCURD(const uint8_t* data, size_t size, KvStoreNbDelegate *kvNbDelegatePtr) +{ + auto observer = new (std::nothrow) KvStoreObserverFuzzTest; + if ((observer == nullptr) || (kvNbDelegatePtr == nullptr)) { + return; + } + Key key = std::vector(data, data + (size % 1024)); /* 1024 is max */ + Value value = std::vector(data, data + size); + kvNbDelegatePtr->RegisterObserver(key, size, observer); + kvNbDelegatePtr->SetConflictNotifier(size, [](const KvStoreNbConflictData &data) { + (void)data.GetType(); + }); + + Value valueRead; + kvNbDelegatePtr->PutLocal(key, value); + kvNbDelegatePtr->GetLocal(key, valueRead); + kvNbDelegatePtr->DeleteLocal(key); + kvNbDelegatePtr->Put(key, value); + kvNbDelegatePtr->Put(key, value); + std::vector vect; + kvNbDelegatePtr->GetEntries(key, vect); + kvNbDelegatePtr->Delete(key); + kvNbDelegatePtr->Get(key, valueRead); + std::vector keys; + std::vector tmp = CreateEntries(data, size, keys); + kvNbDelegatePtr->PutBatch(tmp); + if (!keys.empty()) { + /* random deletePublic updateTimestamp 2 */ + kvNbDelegatePtr->UnpublishToLocal(keys[0], (data[0] > data[1]), (data[2] > data[1])); + } + kvNbDelegatePtr->DeleteBatch(keys); + kvNbDelegatePtr->UnRegisterObserver(observer); + kvNbDelegatePtr->PutLocalBatch(tmp); + + if (!keys.empty()) { + /* random deletePublic updateTimestamp 2 */ + kvNbDelegatePtr->PublishLocal(keys[0], (data[0] > data[1]), (data[2] > data[1]), nullptr); + } + kvNbDelegatePtr->DeleteBatch(keys); + std::string rawString(reinterpret_cast(data), size); + kvNbDelegatePtr->RemoveDeviceData(rawString); +} + +void CombineTest(const uint8_t* data, size_t size, KvStoreNbDelegate::Option &option) +{ + static auto kvManager = KvStoreDelegateManager("APP_ID", "USER_ID"); + KvStoreConfig config; + DistributedDBToolsTest::TestDirInit(config.dataDir); + kvManager.SetKvStoreConfig(config); + KvStoreNbDelegate *kvNbDelegatePtr = nullptr; + kvManager.GetKvStore("distributed_nb_delegate_test", option, + [&kvNbDelegatePtr] (DBStatus status, KvStoreNbDelegate* kvNbDelegate) { + if (status == DBStatus::OK) { + kvNbDelegatePtr = kvNbDelegate; + } + }); + FuzzCURD(data, size, kvNbDelegatePtr); + kvManager.CloseKvStore(kvNbDelegatePtr); + kvManager.DeleteKvStore("distributed_nb_delegate_test"); + DistributedDBToolsTest::RemoveTestDbFiles(config.dataDir); +} +} + +/* Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + DistributedDB::KvStoreNbDelegate::Option option = {true, false, false}; + OHOS::CombineTest(data, size, option); + option = {true, true, false}; + OHOS::CombineTest(data, size, option); + return 0; +} + diff --git a/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/nbdelegate_fuzzer.h b/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/nbdelegate_fuzzer.h new file mode 100644 index 00000000..e476b2a9 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/nbdelegate_fuzzer.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 NBDELEGATE_FUZZER_H +#define NBDELEGATE_FUZZER_H + +#define FUZZ_PROJECT_NAME "NbDelegate_fuzzer" + +#endif // NBDELEGATE_FUZZER_H diff --git a/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/project.xml b/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/project.xml new file mode 100644 index 00000000..be92381e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/nbdelegate_fuzzer/project.xml @@ -0,0 +1,25 @@ + + + + + + 1000 + + 30 + + 4096 + + diff --git a/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/BUILD.gn b/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/BUILD.gn new file mode 100644 index 00000000..93723536 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/BUILD.gn @@ -0,0 +1,107 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +#####################hydra-fuzz################### +import("//build/config/features.gni") +import("//build/test.gni") + +##############################fuzztest########################################## +ohos_fuzztest("ParseCkeckFuzzTest") { + module_out_path = "distributeddatamgr/distributeddb" + + include_dirs = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/syncer", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/storage", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/sqlite", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/multiver", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/src", + "//utils/native/base/include", + "//third_party/sqlite/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include/relational", + "//third_party/jsoncpp/include/json", + "//third_party/skia/third_party/externals/spirv-headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/skia/third_party/externals/swiftshader/third_party/SPIRV-Headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/jsoncpp/include/json", + "//third_party/grpc/src/core/lib/json", + ] + + cflags = [ + "-g", + "-O0", + "-Wno-unused-variable", + "-fno-omit-frame-pointer", + ] + + fuzz_config_file = "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/parseckeck_fuzzer" + + sources = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/db_common.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/flatbuffer_schema.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/json_object.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/log_print.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/platform_specific.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/schema_object.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/schema_utils.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common/distributeddb_tools_test.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.cpp", + "parseckeck_fuzzer.cpp", + ] + + defines = [ + "SQLITE_ENABLE_SNAPSHOT", + "_LARGEFILE64_SOURCE", + "_FILE_OFFSET_BITS=64", + "SQLITE_HAS_CODEC", + "SQLITE_ENABLE_JSON1", + "USING_HILOG_LOGGER", + "USE_SQLITE_SYMBOLS", + "USING_DB_JSON_EXTRACT_AUTOMATICALLY", + "LOW_LEVEL_MEM_DEV", + "JSONCPP_USE_BUILDER", + "OMIT_FLATBUFFER", + "RELATIONAL_STORE", + "SQLITE_DISTRIBUTE_RELATIONAL", + ] + + deps = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb:distributeddb", + "//third_party/jsoncpp:jsoncpp", + "//third_party/openssl:libcrypto_shared", + "//utils/native/base:utils", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] +} + +############################################################################### + +group("fuzztest") { + testonly = true + deps = [] + deps += [ + # deps file + ":ParseCkeckFuzzTest", + ] +} diff --git a/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/corpus/init b/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/corpus/init new file mode 100644 index 00000000..bc977bd9 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/corpus/init @@ -0,0 +1,14 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +FUZZ \ No newline at end of file diff --git a/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/parseckeck_fuzzer.cpp b/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/parseckeck_fuzzer.cpp new file mode 100644 index 00000000..9ad5ca65 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/parseckeck_fuzzer.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "parseckeck_fuzzer.h" +#include "distributeddb_tools_test.h" +#include "schema_object.h" +#include "schema_utils.h" + +using namespace DistributedDB; +using namespace DistributedDBTest; + +static KvStoreConfig g_config; + +namespace OHOS { +void GetSchmaKvstore(const uint8_t* data, size_t size) +{ + static auto kvManager = KvStoreDelegateManager("APP_ID", "USER_ID"); + kvManager.SetKvStoreConfig(g_config); + KvStoreNbDelegate::Option option = {true, false, false}; + std::string schemaString(reinterpret_cast(data), size); + option.schema = schemaString; + KvStoreNbDelegate *kvNbDelegatePtr = nullptr; + + kvManager.GetKvStore("distributed_nb_get_schemakvstore", option, + [&kvNbDelegatePtr] (DBStatus status, KvStoreNbDelegate* kvNbDelegate) { + if (status == DBStatus::OK) { + kvNbDelegatePtr = kvNbDelegate; + } + }); + + kvManager.CloseKvStore(kvNbDelegatePtr); + kvManager.DeleteKvStore("distributed_nb_get_schemakvstore"); +} + +void ParseSchemaString(const uint8_t* data, size_t size) +{ + std::string schemaString(reinterpret_cast(data), size); + SchemaObject schemaOri; + schemaOri.ParseFromSchemaString(schemaString); + schemaOri.CompareAgainstSchemaString(schemaString); +} + +void CompareSchemaString(const uint8_t* data, size_t size) +{ + // beginning half / 2 + std::string schemaString(data, data + (size / 2)); + // ending half / 2 ~ end. + std::string schemaString2(data + (size / 2), data + size); + SchemaObject schemaOri; + schemaOri.ParseFromSchemaString(schemaString); + schemaOri.ParseFromSchemaString(schemaString2); +} + +void CheckFieldName(const uint8_t* data, size_t size) +{ + std::string schemaAttrString(reinterpret_cast(data), size); + SchemaUtils::CheckFieldName(schemaAttrString); +} + +void ParseFieldPath(const uint8_t* data, size_t size) +{ + std::string schemaAttrString(reinterpret_cast(data), size); + FieldPath outPath; + SchemaUtils::ParseAndCheckFieldPath(schemaAttrString, outPath); +} + +void CheckSchemaAttribute(const uint8_t* data, size_t size) +{ + std::string schemaAttrString(reinterpret_cast(data), size); + SchemaAttribute outAttr; + SchemaUtils::ParseAndCheckSchemaAttribute(schemaAttrString, outAttr); + SchemaUtils::ParseAndCheckSchemaAttribute(schemaAttrString, outAttr); +} +} + +/* Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + DistributedDBToolsTest::TestDirInit(g_config.dataDir); + OHOS::GetSchmaKvstore(data, size); + OHOS::ParseSchemaString(data, size); + OHOS::CompareSchemaString(data, size); + OHOS::CheckFieldName(data, size); + OHOS::ParseFieldPath(data, size); + OHOS::CheckSchemaAttribute(data, size); + + DistributedDBToolsTest::RemoveTestDbFiles(g_config.dataDir); + return 0; +} + diff --git a/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/parseckeck_fuzzer.h b/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/parseckeck_fuzzer.h new file mode 100644 index 00000000..451c718a --- /dev/null +++ b/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/parseckeck_fuzzer.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 PARSECHECK_FUZZER_H +#define PARSECHECK_FUZZER_H + +#define FUZZ_PROJECT_NAME "ParseCkeck_fuzzer" + +#endif // PARSECHECK_FUZZER_H diff --git a/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/project.xml b/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/project.xml new file mode 100644 index 00000000..be92381e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/parseckeck_fuzzer/project.xml @@ -0,0 +1,25 @@ + + + + + + 1000 + + 30 + + 4096 + + diff --git a/mock/distributeddb/test/fuzztest/query_fuzzer/BUILD.gn b/mock/distributeddb/test/fuzztest/query_fuzzer/BUILD.gn new file mode 100644 index 00000000..b9379a1e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/query_fuzzer/BUILD.gn @@ -0,0 +1,104 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +#####################hydra-fuzz################### +import("//build/config/features.gni") +import("//build/test.gni") + +##############################fuzztest########################################## +ohos_fuzztest("QueryFuzzTest") { + module_out_path = "distributeddatamgr/distributeddb" + + include_dirs = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/syncer", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/storage", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/sqlite", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/multiver", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/src", + "//utils/native/base/include", + "//third_party/sqlite/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include/relational", + "//third_party/jsoncpp/include/json", + "//third_party/skia/third_party/externals/spirv-headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/skia/third_party/externals/swiftshader/third_party/SPIRV-Headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/jsoncpp/include/json", + "//third_party/grpc/src/core/lib/json", + ] + + cflags = [ + "-g", + "-O0", + "-Wno-unused-variable", + "-fno-omit-frame-pointer", + ] + + fuzz_config_file = "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/query_fuzzer" + + sources = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/db_common.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/json_object.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/log_print.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/platform_specific.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/schema_object.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/schema_utils.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.cpp", + "query_fuzzer.cpp", + ] + + defines = [ + "SQLITE_ENABLE_SNAPSHOT", + "_LARGEFILE64_SOURCE", + "_FILE_OFFSET_BITS=64", + "SQLITE_HAS_CODEC", + "SQLITE_ENABLE_JSON1", + "USING_HILOG_LOGGER", + "USE_SQLITE_SYMBOLS", + "USING_DB_JSON_EXTRACT_AUTOMATICALLY", + "LOW_LEVEL_MEM_DEV", + "JSONCPP_USE_BUILDER", + "OMIT_FLATBUFFER", + "RELATIONAL_STORE", + "SQLITE_DISTRIBUTE_RELATIONAL", + ] + + deps = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb:distributeddb", + "//third_party/jsoncpp:jsoncpp", + "//third_party/openssl:libcrypto_shared", + "//utils/native/base:utils", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] +} + +############################################################################### + +group("fuzztest") { + testonly = true + deps = [] + deps += [ + # deps file + ":QueryFuzzTest", + ] +} diff --git a/mock/distributeddb/test/fuzztest/query_fuzzer/corpus/init b/mock/distributeddb/test/fuzztest/query_fuzzer/corpus/init new file mode 100644 index 00000000..8eb5a7d6 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/query_fuzzer/corpus/init @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ + +FUZZ \ No newline at end of file diff --git a/mock/distributeddb/test/fuzztest/query_fuzzer/project.xml b/mock/distributeddb/test/fuzztest/query_fuzzer/project.xml new file mode 100644 index 00000000..be92381e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/query_fuzzer/project.xml @@ -0,0 +1,25 @@ + + + + + + 1000 + + 30 + + 4096 + + diff --git a/mock/distributeddb/test/fuzztest/query_fuzzer/query_fuzzer.cpp b/mock/distributeddb/test/fuzztest/query_fuzzer/query_fuzzer.cpp new file mode 100644 index 00000000..892a5c36 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/query_fuzzer/query_fuzzer.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "query_fuzzer.h" +#include "get_query_info.h" + +using namespace DistributedDB; +using namespace std; + +namespace { + constexpr const char *TEST_FIELD_NAME = "$.test"; +} + +namespace OHOS { +void FuzzEqualTo(const uint8_t* data, size_t size) +{ + std::string rawString(reinterpret_cast(data), size); + Query query = Query::Select().EqualTo(rawString, static_cast(size)); +} + +void FuzzNotEqualTo(const uint8_t* data, size_t size) +{ + std::string rawString(reinterpret_cast(data), size); + Query query = Query::Select().NotEqualTo(TEST_FIELD_NAME, rawString); +} + +void FuzzGreaterThan(const uint8_t* data, size_t size) +{ + std::string rawString(reinterpret_cast(data), size); + Query query = Query::Select().GreaterThan(rawString, static_cast(U32_AT(data))); +} + +void FuzzLessThan(const uint8_t* data, size_t size) +{ + std::string rawString(reinterpret_cast(data), size); + Query query = Query::Select().LessThan(TEST_FIELD_NAME, rawString); +} + +void FuzzGreaterThanOrEqualTo(const uint8_t* data, size_t size) +{ + std::string rawString(reinterpret_cast(data), size); + Query query = Query::Select().GreaterThanOrEqualTo(rawString, static_cast(size)); +} + +void FuzzLessThanOrEqualTo(const uint8_t* data, size_t size) +{ + std::string rawString(reinterpret_cast(data), size); + Query query = Query::Select().LessThanOrEqualTo(TEST_FIELD_NAME, rawString); +} + +void FuzzOrderBy(const uint8_t* data, size_t size) +{ + std::string rawString(reinterpret_cast(data), size); + Query query = Query::Select().GreaterThanOrEqualTo(rawString, true); + query = Query::Select().GreaterThanOrEqualTo(rawString, false); +} + +void FuzzLimit(const uint8_t* data, size_t size) +{ + Query query = Query::Select().Limit(static_cast(size), static_cast(U32_AT(data))); +} + +void FuzzLike(const uint8_t* data, size_t size) +{ + std::string rawString(reinterpret_cast(data), size); + Query query = Query::Select().Like(rawString, rawString); +} + +void FuzzNotLike(const uint8_t* data, size_t size) +{ + std::string rawString(reinterpret_cast(data), size); + Query query = Query::Select().NotLike(TEST_FIELD_NAME, rawString); +} + +void FuzzIn(const uint8_t* data, size_t size) +{ + std::string rawString(reinterpret_cast(data), size); + std::vector values; + // 512 max size + for (int i = 0; i < static_cast(U32_AT(data) % 512); i++) { + values.push_back(rawString); + } + Query query = Query::Select().In(TEST_FIELD_NAME, values); +} + +void FuzzNotIn(const uint8_t* data, size_t size) +{ + std::string rawString(reinterpret_cast(data), size); + std::vector values; + // 512 max size + for (int i = 0; i < static_cast(size % 512); i++) { + values.push_back(rawString); + } + Query query = Query::Select().NotIn(TEST_FIELD_NAME, values); +} + +void FuzzIsNull(const uint8_t* data, size_t size) +{ + std::string rawString(reinterpret_cast(data), size); + Query query = Query::Select().IsNull(rawString); +} +} + +/* Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + // u32 4 bytes + if (size < 4) { + return 0; + } + // Run your code on data + OHOS::FuzzEqualTo(data, size); + OHOS::FuzzNotEqualTo(data, size); + OHOS::FuzzGreaterThan(data, size); + OHOS::FuzzLessThan(data, size); + OHOS::FuzzGreaterThanOrEqualTo(data, size); + OHOS::FuzzLessThanOrEqualTo(data, size); + OHOS::FuzzOrderBy(data, size); + OHOS::FuzzLimit(data, size); + OHOS::FuzzLike(data, size); + OHOS::FuzzNotLike(data, size); + OHOS::FuzzIn(data, size); + OHOS::FuzzNotIn(data, size); + OHOS::FuzzIsNull(data, size); + return 0; +} + diff --git a/mock/distributeddb/test/fuzztest/query_fuzzer/query_fuzzer.h b/mock/distributeddb/test/fuzztest/query_fuzzer/query_fuzzer.h new file mode 100644 index 00000000..60c06746 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/query_fuzzer/query_fuzzer.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 QUERY_FUZZER_H +#define QUERY_FUZZER_H + +#define FUZZ_PROJECT_NAME "Query_fuzzer" + +#include + +uint16_t U16_AT(const uint8_t * const &ptr) +{ + // 8 - 0 + return (ptr[0] << 8) | ptr[1]; +} + +uint32_t U32_AT(const uint8_t * const &ptr) +{ + // 24 - 16 - 8 - 0 + return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]; +} + +#endif // QUERY_FUZZER_H diff --git a/mock/distributeddb/test/fuzztest/rekey_fuzzer/BUILD.gn b/mock/distributeddb/test/fuzztest/rekey_fuzzer/BUILD.gn new file mode 100644 index 00000000..59884e58 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/rekey_fuzzer/BUILD.gn @@ -0,0 +1,103 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +#####################hydra-fuzz################### +import("//build/config/features.gni") +import("//build/test.gni") + +##############################fuzztest########################################## +ohos_fuzztest("ReKeyFuzzTest") { + module_out_path = "distributeddatamgr/distributeddb" + + include_dirs = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/syncer", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/storage", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/sqlite", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/storage/src/multiver", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/communicator/src", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/syncer/src", + "//utils/native/base/include", + "//third_party/sqlite/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/include/relational", + "//third_party/jsoncpp/include/json", + "//third_party/skia/third_party/externals/spirv-headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/skia/third_party/externals/swiftshader/third_party/SPIRV-Headers/tools/buildHeaders/jsoncpp/dist/json", + "//third_party/jsoncpp/include/json", + "//third_party/grpc/src/core/lib/json", + ] + + cflags = [ + "-g", + "-O0", + "-Wno-unused-variable", + "-fno-omit-frame-pointer", + ] + + fuzz_config_file = "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/rekey_fuzzer" + + sources = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/db_common.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/log_print.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/common/src/platform_specific.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/fuzztest/common/distributeddb_tools_test.cpp", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.cpp", + "rekey_fuzzer.cpp", + ] + + defines = [ + "SQLITE_ENABLE_SNAPSHOT", + "_LARGEFILE64_SOURCE", + "_FILE_OFFSET_BITS=64", + "SQLITE_HAS_CODEC", + "SQLITE_ENABLE_JSON1", + "USING_HILOG_LOGGER", + "USE_SQLITE_SYMBOLS", + "USING_DB_JSON_EXTRACT_AUTOMATICALLY", + "LOW_LEVEL_MEM_DEV", + "JSONCPP_USE_BUILDER", + "OMIT_FLATBUFFER", + "RELATIONAL_STORE", + "SQLITE_DISTRIBUTE_RELATIONAL", + ] + + deps = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb:distributeddb", + "//third_party/jsoncpp:jsoncpp", + "//third_party/openssl:libcrypto_shared", + "//utils/native/base:utils", + ] + + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] +} + +############################################################################### + +group("fuzztest") { + testonly = true + deps = [] + deps += [ + # deps file + ":ReKeyFuzzTest", + ] +} diff --git a/mock/distributeddb/test/fuzztest/rekey_fuzzer/corpus/init b/mock/distributeddb/test/fuzztest/rekey_fuzzer/corpus/init new file mode 100644 index 00000000..bc977bd9 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/rekey_fuzzer/corpus/init @@ -0,0 +1,14 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +FUZZ \ No newline at end of file diff --git a/mock/distributeddb/test/fuzztest/rekey_fuzzer/project.xml b/mock/distributeddb/test/fuzztest/rekey_fuzzer/project.xml new file mode 100644 index 00000000..be92381e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/rekey_fuzzer/project.xml @@ -0,0 +1,25 @@ + + + + + + 1000 + + 30 + + 4096 + + diff --git a/mock/distributeddb/test/fuzztest/rekey_fuzzer/rekey_fuzzer.cpp b/mock/distributeddb/test/fuzztest/rekey_fuzzer/rekey_fuzzer.cpp new file mode 100644 index 00000000..212d2f1e --- /dev/null +++ b/mock/distributeddb/test/fuzztest/rekey_fuzzer/rekey_fuzzer.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "rekey_fuzzer.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_test.h" + +using namespace DistributedDB; +using namespace DistributedDBTest; + +namespace OHOS { +static auto g_kvManager = KvStoreDelegateManager("APP_ID", "USER_ID"); +std::vector CreateEntries(const uint8_t* data, size_t size) +{ + std::vector entries; + + auto count = static_cast(std::min(size, size_t(1024))); + for (int i = 1; i < count; i++) { + Entry entry; + entry.key = std::vector (data, data + i); + entry.value = std::vector (data, data + size); + entries.push_back(entry); + } + return entries; +} + +void SingerVerReKey(const uint8_t* data, size_t size) +{ + CipherPassword passwd; + // div 2 -> half + passwd.SetValue(data, (size / 2)); + + KvStoreNbDelegate::Option nbOption = {true, false, true, CipherType::DEFAULT, passwd}; + KvStoreNbDelegate *kvNbDelegatePtr = nullptr; + + g_kvManager.GetKvStore("distributed_nb_rekey_test", nbOption, + [&kvNbDelegatePtr](DBStatus status, KvStoreNbDelegate * kvNbDelegate) { + if (status == DBStatus::OK) { + kvNbDelegatePtr = kvNbDelegate; + } + }); + + if (kvNbDelegatePtr != nullptr) { + kvNbDelegatePtr->PutBatch(CreateEntries(data, size)); + passwd.SetValue(data, size); + kvNbDelegatePtr->Rekey(passwd); + g_kvManager.CloseKvStore(kvNbDelegatePtr); + } +} + +void MultiVerVerReKey(const uint8_t* data, size_t size) +{ + CipherPassword passwd; + // div 2 -> half + passwd.SetValue(data, (size / 2)); + + KvStoreNbDelegate::Option nbOption = {true, false, true, CipherType::DEFAULT, passwd}; + KvStoreNbDelegate *kvNbDelegatePtr = nullptr; + + g_kvManager.GetKvStore("distributed_rekey_test", nbOption, + [&kvNbDelegatePtr](DBStatus status, KvStoreNbDelegate * kvNbDelegate) { + if (status == DBStatus::OK) { + kvNbDelegatePtr = kvNbDelegate; + } + }); + + if (kvNbDelegatePtr != nullptr) { + kvNbDelegatePtr->PutBatch(CreateEntries(data, size)); + CipherPassword passwd; + passwd.SetValue(data, size); + kvNbDelegatePtr->Rekey(passwd); + g_kvManager.CloseKvStore(kvNbDelegatePtr); + } +} +} + +/* Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + KvStoreConfig config; + DistributedDBToolsTest::TestDirInit(config.dataDir); + OHOS::g_kvManager.SetKvStoreConfig(config); + OHOS::SingerVerReKey(data, size); + OHOS::MultiVerVerReKey(data, size); + DistributedDBToolsTest::RemoveTestDbFiles(config.dataDir); + return 0; +} + diff --git a/mock/distributeddb/test/fuzztest/rekey_fuzzer/rekey_fuzzer.h b/mock/distributeddb/test/fuzztest/rekey_fuzzer/rekey_fuzzer.h new file mode 100644 index 00000000..9524bc76 --- /dev/null +++ b/mock/distributeddb/test/fuzztest/rekey_fuzzer/rekey_fuzzer.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 REKEY_FUZZER_H +#define REKEY_FUZZER_H + +#define FUZZ_PROJECT_NAME "Rekey_fuzzer" + +#endif // REKEY_FUZZER_H diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_auto_launch_test.cpp b/mock/distributeddb/test/unittest/common/common/distributeddb_auto_launch_test.cpp new file mode 100644 index 00000000..970373b3 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_auto_launch_test.cpp @@ -0,0 +1,1014 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "auto_launch.h" +#include "db_common.h" +#include "db_errno.h" +#include "distributeddb_tools_unit_test.h" +#include "kv_store_nb_conflict_data.h" +#include "kvdb_manager.h" +#include "kvdb_pragma.h" +#include "log_print.h" +#include "platform_specific.h" +#include "virtual_communicator_aggregator.h" + +using namespace std; +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { + const std::string APP_ID = "appId"; + const std::string USER_ID = "userId"; + const std::string STORE_ID_0 = "storeId0"; + const std::string STORE_ID_1 = "storeId1"; + const std::string STORE_ID_2 = "storeId2"; + const std::string STORE_ID_3 = "storeId3"; + const std::string STORE_ID_4 = "storeId4"; + const std::string STORE_ID_5 = "storeId5"; + const std::string STORE_ID_6 = "storeId6"; + const std::string STORE_ID_7 = "storeId7"; + const std::string STORE_ID_8 = "storeId8"; + string g_testDir; + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + VirtualCommunicatorAggregator *g_communicatorAggregator = nullptr; + + const int TEST_ENABLE_CNT = 10; // 10 time + const int TEST_ONLINE_CNT = 200; // 10 time + const int WAIT_TIME = 1000; // 1000ms + const int LIFE_CYCLE_TIME = 5000; // 5000ms + const int WAIT_SHORT_TIME = 200; // 20ms + const Timestamp TIME_ADD = 1000; // not zero is ok + const std::string REMOTE_DEVICE_ID = "remote_device"; + const std::string THIS_DEVICE = "real_device"; + + const Key KEY1{'k', 'e', 'y', '1'}; + const Key KEY2{'k', 'e', 'y', '2'}; + const Value VALUE1{'v', 'a', 'l', 'u', 'e', '1'}; + const Value VALUE2{'v', 'a', 'l', 'u', 'e', '2'}; + KvDBProperties g_propA; + KvDBProperties g_propB; + KvDBProperties g_propC; + KvDBProperties g_propD; + KvDBProperties g_propE; + KvDBProperties g_propF; + KvDBProperties g_propG; + KvDBProperties g_propH; + KvDBProperties g_propI; + std::string g_identifierA; + std::string g_identifierB; + std::string g_identifierC; + std::string g_identifierD; + std::string g_identifierE; + std::string g_identifierF; + std::string g_identifierG; + std::string g_identifierH; + std::string g_identifierI; + std::string g_dualIdentifierA; + std::string g_dualIdentifierB; + std::string g_dualIdentifierC; + std::string g_dualIdentifierD; + std::string g_dualIdentifierE; + std::string g_dualIdentifierF; + std::string g_dualIdentifierG; + std::string g_dualIdentifierH; + std::string g_dualIdentifierI; +} + +class DistributedDBAutoLaunchUnitTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown() {}; +}; + +void DistributedDBAutoLaunchUnitTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + + string dir = g_testDir; + DIR *dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } + if (DistributedDBToolsUnitTest::RemoveTestDbFiles( + g_testDir + "/" + DBCommon::TransferStringToHex(g_identifierA) + "/single_ver") != 0) { + LOGE("rm test db files error!"); + } + g_communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator(); + ASSERT_TRUE(g_communicatorAggregator != nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(g_communicatorAggregator); +} + +void DistributedDBAutoLaunchUnitTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles( + g_testDir + "/" + DBCommon::TransferStringToHex(g_identifierA) + "/single_ver") != 0) { + LOGE("rm test db files error!"); + } + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); +} + +static void GetProperty(KvDBProperties &prop, std::string &identifier, std::string storeId, std::string &dualIdentifier) +{ + prop.SetStringProp(KvDBProperties::USER_ID, USER_ID); + prop.SetStringProp(KvDBProperties::APP_ID, APP_ID); + prop.SetStringProp(KvDBProperties::STORE_ID, storeId); + identifier = DBCommon::TransferHashString(USER_ID + "-" + APP_ID + "-" + storeId); + prop.SetStringProp(KvDBProperties::IDENTIFIER_DATA, identifier); + dualIdentifier = DBCommon::TransferHashString(APP_ID + "-" + storeId); + prop.SetStringProp(KvDBProperties::DUAL_TUPLE_IDENTIFIER_DATA, dualIdentifier); + std::string identifierDirA = DBCommon::TransferStringToHex(identifier); + prop.SetStringProp(KvDBProperties::IDENTIFIER_DIR, identifierDirA); + prop.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + prop.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + prop.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + prop.SetBoolProp(KvDBProperties::SYNC_DUAL_TUPLE_MODE, false); +} + +void DistributedDBAutoLaunchUnitTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + if (DistributedDBToolsUnitTest::RemoveTestDbFiles( + g_testDir + "/" + DBCommon::TransferStringToHex(g_identifierA) + "/single_ver") != 0) { + LOGE("rm test db files error!"); + } + GetProperty(g_propA, g_identifierA, STORE_ID_0, g_dualIdentifierA); + GetProperty(g_propB, g_identifierB, STORE_ID_1, g_dualIdentifierB); + GetProperty(g_propC, g_identifierC, STORE_ID_2, g_dualIdentifierC); + GetProperty(g_propD, g_identifierD, STORE_ID_3, g_dualIdentifierD); + GetProperty(g_propE, g_identifierE, STORE_ID_4, g_dualIdentifierE); + GetProperty(g_propF, g_identifierF, STORE_ID_5, g_dualIdentifierF); + GetProperty(g_propG, g_identifierG, STORE_ID_6, g_dualIdentifierG); + GetProperty(g_propH, g_identifierH, STORE_ID_7, g_dualIdentifierH); + GetProperty(g_propI, g_identifierI, STORE_ID_8, g_dualIdentifierI); +} + +static void PutSyncData(const KvDBProperties &prop, const Key &key, const Value &value) +{ + int errCode = E_OK; + auto kvStore = static_cast(KvDBManager::OpenDatabase(prop, errCode)); + ASSERT_NE(kvStore, nullptr); + auto *connection = kvStore->GetDBConnection(errCode); + ASSERT_NE(connection, nullptr); + if (kvStore != nullptr) { + std::vector vect; + Timestamp time; + kvStore->GetMaxTimestamp(time); + time += TIME_ADD; + LOGD("time:%" PRIu64, time); + vect.push_back({key, value, time, 0, DBCommon::TransferHashString(REMOTE_DEVICE_ID)}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(kvStore, vect, REMOTE_DEVICE_ID), E_OK); + } + RefObject::DecObjRef(kvStore); + connection->Close(); + connection = nullptr; +} + +static void SetLifeCycleTime(const KvDBProperties &prop) +{ + int errCode = E_OK; + auto kvStore = static_cast(KvDBManager::OpenDatabase(prop, errCode)); + ASSERT_NE(kvStore, nullptr); + auto *connection = kvStore->GetDBConnection(errCode); + ASSERT_NE(connection, nullptr); + uint32_t time = LIFE_CYCLE_TIME; + EXPECT_EQ(connection->Pragma(PRAGMA_SET_AUTO_LIFE_CYCLE, static_cast(&time)), E_OK); + RefObject::DecObjRef(kvStore); + connection->Close(); + connection = nullptr; +} + +/** + * @tc.name: AutoLaunch001 + * @tc.desc: basic enable/disable func + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch001, TestSize.Level3) +{ + /** + * @tc.steps: step1. right param A enable + * @tc.expected: step1. success. + */ + AutoLaunchOption option; + option.notifier = nullptr; + int errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propA, nullptr, option); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step2. wrong param B enable + * @tc.expected: step2. failed. + */ + g_propB.SetStringProp(KvDBProperties::IDENTIFIER_DATA, ""); + errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propB, nullptr, option); + EXPECT_TRUE(errCode != E_OK); + + /** + * @tc.steps: step3. right param C enable + * @tc.expected: step3. success. + */ + errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propC, nullptr, option); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step4. param A disable + * @tc.expected: step4. E_OK. + */ + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierA, g_dualIdentifierA, USER_ID); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step5. param B disable + * @tc.expected: step5. -E_NOT_FOUND. + */ + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierB, g_dualIdentifierB, USER_ID); + EXPECT_TRUE(errCode == -E_NOT_FOUND); + + /** + * @tc.steps: step6. param C disable + * @tc.expected: step6. E_OK. + */ + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierC, g_dualIdentifierC, USER_ID); + EXPECT_TRUE(errCode == E_OK); +} + +/** + * @tc.name: AutoLaunch002 + * @tc.desc: online callback + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch002, TestSize.Level3) +{ + std::mutex cvMutex; + std::condition_variable cv; + bool finished = false; + std::map statusMap; + + auto notifier = [&cvMutex, &cv, &finished, &statusMap] (const std::string &userId, const std::string &appId, + const std::string &storeId, AutoLaunchStatus status) { + LOGD("int AutoLaunch002 notifier status:%d", status); + std::string identifier = DBCommon::TransferHashString(userId + "-" + appId + "-" + storeId); + std::unique_lock lock(cvMutex); + statusMap[identifier] = status; + LOGD("int AutoLaunch002 notifier statusMap.size():%d", statusMap.size()); + if (statusMap.size() == 2) { // A and B + finished = true; + cv.notify_one(); + } + }; + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps: step1. right param A B enable + * @tc.expected: step1. success. + */ + AutoLaunchOption option; + option.notifier = nullptr; + option.observer = observer; + EXPECT_TRUE(RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propA, notifier, option) == E_OK); + EXPECT_TRUE(RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propB, notifier, option) == E_OK); + + /** + * @tc.steps: step2. RunOnConnectCallback + * @tc.expected: step2. success. + */ + g_communicatorAggregator->RunOnConnectCallback(REMOTE_DEVICE_ID, true); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + + /** + * @tc.steps: step3. PutSyncData + * @tc.expected: step3. notifier WRITE_OPENED + */ + PutSyncData(g_propA, KEY1, VALUE1); + PutSyncData(g_propB, KEY1, VALUE1); + { + std::unique_lock lock(cvMutex); + cv.wait(lock, [&finished] {return finished;}); + EXPECT_TRUE(statusMap[g_identifierA] == WRITE_OPENED); + EXPECT_TRUE(statusMap[g_identifierB] == WRITE_OPENED); + statusMap.clear(); + finished = false; + } + EXPECT_TRUE(observer->GetCallCount() == 2); // A and B + delete observer; + /** + * @tc.steps: step4. param A B disable + * @tc.expected: step4. notifier WRITE_CLOSED + */ + EXPECT_TRUE(RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierA, g_dualIdentifierA, USER_ID) + == E_OK); + EXPECT_TRUE(RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierB, g_dualIdentifierB, USER_ID) + == E_OK); + + std::unique_lock lock(cvMutex); + cv.wait(lock, [&finished] {return finished;}); + EXPECT_TRUE(statusMap[g_identifierA] == WRITE_CLOSED); + EXPECT_TRUE(statusMap[g_identifierB] == WRITE_CLOSED); + g_communicatorAggregator->RunOnConnectCallback(REMOTE_DEVICE_ID, false); +} + +/** + * @tc.name: AutoLaunch003 + * @tc.desc: CommunicatorLackCallback + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch003, TestSize.Level3) +{ + std::mutex cvMutex; + std::condition_variable cv; + bool finished = false; + std::map statusMap; + + auto notifier = [&cvMutex, &cv, &finished, &statusMap] (const std::string &userId, const std::string &appId, + const std::string &storeId, AutoLaunchStatus status) { + LOGD("int AutoLaunch002 notifier status:%d", status); + std::string identifier = DBCommon::TransferHashString(userId + "-" + appId + "-" + storeId); + std::unique_lock lock(cvMutex); + statusMap[identifier] = status; + LOGD("int AutoLaunch002 notifier statusMap.size():%zu", statusMap.size()); + finished = true; + cv.notify_one(); + }; + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + + /** + * @tc.steps: step1. right param A B enable + * @tc.expected: step1. success. + */ + AutoLaunchOption option; + option.notifier = nullptr; + option.observer = observer; + int errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propA, notifier, option); + EXPECT_TRUE(errCode == E_OK); + errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propB, notifier, option); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step2. RunCommunicatorLackCallback + * @tc.expected: step2. success. + */ + LabelType label(g_identifierA.begin(), g_identifierA.end()); + g_communicatorAggregator->RunCommunicatorLackCallback(label); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + + /** + * @tc.steps: step3. PutSyncData + * @tc.expected: step3. notifier WRITE_OPENED + */ + PutSyncData(g_propA, KEY2, VALUE2); + { + std::unique_lock lock(cvMutex); + cv.wait(lock, [&finished] {return finished;}); + EXPECT_TRUE(statusMap[g_identifierA] == WRITE_OPENED); + statusMap.clear(); + finished = false; + } + EXPECT_TRUE(observer->GetCallCount() == 1); // only A + delete observer; + /** + * @tc.steps: step4. param A B disable + * @tc.expected: step4. notifier WRITE_CLOSED + */ + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierB, g_dualIdentifierB, USER_ID); + EXPECT_TRUE(errCode == E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierA, g_dualIdentifierA, USER_ID); + EXPECT_TRUE(errCode == E_OK); + + std::unique_lock lock(cvMutex); + cv.wait(lock, [&finished] {return finished;}); + EXPECT_TRUE(statusMap[g_identifierA] == WRITE_CLOSED); + EXPECT_TRUE(statusMap.size() == 1); +} + +/** + * @tc.name: AutoLaunch004 + * @tc.desc: basic enable/disable func + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch004, TestSize.Level3) +{ + /** + * @tc.steps: step1. right param A~H enable + * @tc.expected: step1. success. + */ + AutoLaunchOption option; + option.notifier = nullptr; + EXPECT_TRUE(RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propA, nullptr, option) == E_OK); + EXPECT_TRUE(RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propB, nullptr, option) == E_OK); + EXPECT_TRUE(RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propC, nullptr, option) == E_OK); + EXPECT_TRUE(RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propD, nullptr, option) == E_OK); + EXPECT_TRUE(RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propE, nullptr, option) == E_OK); + EXPECT_TRUE(RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propF, nullptr, option) == E_OK); + EXPECT_TRUE(RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propG, nullptr, option) == E_OK); + EXPECT_TRUE(RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propH, nullptr, option) == E_OK); + + /** + * @tc.steps: step2. right param I enable + * @tc.expected: step2. -E_MAX_LIMITS. + */ + int errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propI, nullptr, option); + EXPECT_TRUE(errCode == -E_MAX_LIMITS); + + /** + * @tc.steps: step3. param A disable + * @tc.expected: step3. E_OK. + */ + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierA, g_dualIdentifierA, USER_ID); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step4. right param I enable + * @tc.expected: step4. E_OK. + */ + errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propI, nullptr, option); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step6. param B~I disable + * @tc.expected: step6. E_OK. + */ + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierB, g_dualIdentifierB, USER_ID); + EXPECT_TRUE(errCode == E_OK); + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierC, g_dualIdentifierC, USER_ID); + EXPECT_TRUE(errCode == E_OK); + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierD, g_dualIdentifierD, USER_ID); + EXPECT_TRUE(errCode == E_OK); + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierE, g_dualIdentifierE, USER_ID); + EXPECT_TRUE(errCode == E_OK); + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierF, g_dualIdentifierF, USER_ID); + EXPECT_TRUE(errCode == E_OK); + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierG, g_dualIdentifierG, USER_ID); + EXPECT_TRUE(errCode == E_OK); + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierH, g_dualIdentifierH, USER_ID); + EXPECT_TRUE(errCode == E_OK); + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierI, g_dualIdentifierI, USER_ID); + EXPECT_TRUE(errCode == E_OK); +} + +/** + * @tc.name: AutoLaunch005 + * @tc.desc: online device before enable + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch005, TestSize.Level3) +{ + std::mutex cvMutex; + std::condition_variable cv; + bool finished = false; + std::map statusMap; + + auto notifier = [&cvMutex, &cv, &finished, &statusMap] (const std::string &userId, const std::string &appId, + const std::string &storeId, AutoLaunchStatus status) { + LOGD("int AutoLaunch002 notifier status:%d", status); + std::string identifier = DBCommon::TransferHashString(userId + "-" + appId + "-" + storeId); + std::unique_lock lock(cvMutex); + statusMap[identifier] = status; + LOGD("int AutoLaunch002 notifier statusMap.size():%zu", statusMap.size()); + finished = true; + cv.notify_one(); + }; + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps: step1. RunOnConnectCallback + * @tc.expected: step1. success. + */ + g_communicatorAggregator->RunOnConnectCallback(REMOTE_DEVICE_ID, true); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + + /** + * @tc.steps: step2. right param A enable + * @tc.expected: step2. success. + */ + AutoLaunchOption option; + option.notifier = nullptr; + option.observer = observer; + int errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propA, notifier, option); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step3. PutSyncData + * @tc.expected: step3. notifier WRITE_OPENED + */ + PutSyncData(g_propA, KEY1, VALUE1); + { + std::unique_lock lock(cvMutex); + cv.wait(lock, [&finished] {return finished;}); + EXPECT_TRUE(statusMap[g_identifierA] == WRITE_OPENED); + statusMap.clear(); + finished = false; + } + EXPECT_TRUE(observer->GetCallCount() == 1); // only A + /** + * @tc.steps: step4. param A disable + * @tc.expected: step4. notifier WRITE_CLOSED + */ + std::string identifierA = g_propA.GetStringProp(KvDBProperties::DUAL_TUPLE_IDENTIFIER_DATA, ""); + std::string userIdA = g_propA.GetStringProp(KvDBProperties::USER_ID, ""); + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierA, g_dualIdentifierB, USER_ID); + EXPECT_TRUE(errCode == E_OK); + + std::unique_lock lock(cvMutex); + cv.wait(lock, [&finished] {return finished;}); + EXPECT_TRUE(statusMap[g_identifierA] == WRITE_CLOSED); + delete observer; + g_communicatorAggregator->RunOnConnectCallback(REMOTE_DEVICE_ID, false); +} + +/** + * @tc.name: AutoLaunch006 + * @tc.desc: online callback + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch006, TestSize.Level3) +{ + auto notifier = [] (const std::string &userId, const std::string &appId, + const std::string &storeId, AutoLaunchStatus status) { + LOGD("int AutoLaunch006 notifier status:%d", status); + }; + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + std::mutex cvLock; + std::condition_variable cv; + bool threadIsWorking = true; + thread aggregatorThread([&cvLock, &cv, &threadIsWorking]() { + LabelType label(g_identifierA.begin(), g_identifierA.end()); + for (int i = 0; i < TEST_ONLINE_CNT; i++) { + g_communicatorAggregator->RunOnConnectCallback(REMOTE_DEVICE_ID, true); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_SHORT_TIME)); + g_communicatorAggregator->RunOnConnectCallback(REMOTE_DEVICE_ID, false); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_SHORT_TIME)); + g_communicatorAggregator->RunCommunicatorLackCallback(label); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_SHORT_TIME)); + LOGD("AutoLaunch006 thread i:%d", i); + } + std::unique_lock lock(cvLock); + threadIsWorking = false; + cv.notify_one(); + }); + aggregatorThread.detach(); + AutoLaunchOption option; + option.notifier = nullptr; + option.observer = observer; + for (int i = 0; i < TEST_ENABLE_CNT; i++) { + int errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propA, notifier, option); + EXPECT_TRUE(errCode == E_OK); + errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propB, notifier, option); + EXPECT_TRUE(errCode == E_OK); + + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierA, g_dualIdentifierA, USER_ID); + EXPECT_TRUE(errCode == E_OK); + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierB, g_dualIdentifierB, USER_ID); + EXPECT_TRUE(errCode == E_OK); + LOGD("AutoLaunch006 disable i:%d", i); + } + std::unique_lock lock(cvLock); + cv.wait(lock, [&threadIsWorking] { return !threadIsWorking; }); + + delete observer; + observer = nullptr; + g_communicatorAggregator->RunOnConnectCallback(REMOTE_DEVICE_ID, false); +} + +namespace { +std::mutex g_cvMutex; +std::condition_variable g_cv; +bool g_finished = false; +std::map g_statusMap; +void ConflictNotifierCallback(const KvStoreNbConflictData &data) +{ + LOGD("in ConflictNotifierCallback"); + Key key; + Value oldValue; + Value newValue; + data.GetKey(key); + data.GetValue(KvStoreNbConflictData::ValueType::OLD_VALUE, oldValue); + data.GetValue(KvStoreNbConflictData::ValueType::NEW_VALUE, newValue); + EXPECT_TRUE(key == KEY1); + EXPECT_TRUE(oldValue == VALUE1); + EXPECT_TRUE(newValue == VALUE2); + g_finished = true; + g_cv.notify_one(); +} + +void TestAutoLaunchNotifier(const std::string &userId, const std::string &appId, const std::string &storeId, + AutoLaunchStatus status) +{ + LOGD("int AutoLaunchNotifier, status:%d", status); + std::string identifier = DBCommon::TransferHashString(userId + "-" + appId + "-" + storeId); + std::unique_lock lock(g_cvMutex); + g_statusMap[identifier] = status; + g_finished = true; + g_cv.notify_one(); +}; + +bool AutoLaunchCallBack(const std::string &identifier, AutoLaunchParam ¶m, KvStoreObserverUnitTest *observer, + bool ret) +{ + LOGD("int AutoLaunchCallBack"); + EXPECT_TRUE(identifier == g_identifierA); + param.userId = USER_ID; + param.appId = APP_ID; + param.storeId = STORE_ID_0; + CipherPassword passwd; + param.option = {true, false, CipherType::DEFAULT, passwd, "", false, g_testDir, observer, + CONFLICT_FOREIGN_KEY_ONLY, ConflictNotifierCallback}; + param.notifier = TestAutoLaunchNotifier; + return ret; +} + +bool AutoLaunchCallBackBadParam(const std::string &identifier, AutoLaunchParam ¶m) +{ + LOGD("int AutoLaunchCallBack"); + EXPECT_TRUE(identifier == g_identifierA); + param.notifier = TestAutoLaunchNotifier; + return true; +} +} + +/** + * @tc.name: AutoLaunch007 + * @tc.desc: enhancement callback return true + * @tc.type: FUNC + * @tc.require: AR000EPARJ + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch007, TestSize.Level3) +{ + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps: step1. SetAutoLaunchRequestCallback + * @tc.expected: step1. success. + */ + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback( + std::bind(AutoLaunchCallBack, std::placeholders::_1, std::placeholders::_2, observer, true), DBType::DB_KV); + /** + * @tc.steps: step2. RunCommunicatorLackCallback + * @tc.expected: step2. success. + */ + LabelType label(g_identifierA.begin(), g_identifierA.end()); + g_communicatorAggregator->RunCommunicatorLackCallback(label); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps: step3. PutSyncData key1 value1 + * @tc.expected: step3. notifier WRITE_OPENED + */ + PutSyncData(g_propA, KEY1, VALUE1); + { + std::unique_lock lock(g_cvMutex); + g_cv.wait(lock, [] {return g_finished;}); + EXPECT_TRUE(g_statusMap[g_identifierA] == WRITE_OPENED); + g_statusMap.clear(); + g_finished = false; + } + EXPECT_TRUE(observer->GetCallCount() == 1); // only A + /** + * @tc.steps: step4. PutSyncData key1 value2 + * @tc.expected: step4. ConflictNotifierCallback + */ + PutSyncData(g_propA, KEY1, VALUE2); + { + std::unique_lock lock(g_cvMutex); + g_cv.wait(lock, [] {return g_finished;}); + g_finished = false; + } + /** + * @tc.steps: step5. wait life cycle ,db close + * @tc.expected: step5. notifier WRITE_CLOSED + */ + SetLifeCycleTime(g_propA); + { + std::unique_lock lock(g_cvMutex); + g_cv.wait(lock, [] {return g_finished;}); + EXPECT_TRUE(g_statusMap[g_identifierA] == WRITE_CLOSED); + g_statusMap.clear(); + g_finished = false; + } + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback(nullptr, DBType::DB_KV); + delete observer; +} + +/** + * @tc.name: AutoLaunch008 + * @tc.desc: enhancement callback return false + * @tc.type: FUNC + * @tc.require: AR000EPARJ + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch008, TestSize.Level3) +{ + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps: step1. SetAutoLaunchRequestCallback + * @tc.expected: step1. success. + */ + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback( + std::bind(AutoLaunchCallBack, std::placeholders::_1, std::placeholders::_2, observer, false), DBType::DB_KV); + /** + * @tc.steps: step2. RunCommunicatorLackCallback + * @tc.expected: step2. success. + */ + LabelType label(g_identifierA.begin(), g_identifierA.end()); + g_communicatorAggregator->RunCommunicatorLackCallback(label); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps: step3. PutSyncData key1 value1 + * @tc.expected: step3. db not open + */ + PutSyncData(g_propA, KEY1, VALUE1); + PutSyncData(g_propA, KEY1, VALUE2); + + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + EXPECT_TRUE(observer->GetCallCount() == 0); + EXPECT_TRUE(g_finished == false); + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback(nullptr, DBType::DB_KV); + delete observer; +} + +/** + * @tc.name: AutoLaunch009 + * @tc.desc: enhancement callback return bad param + * @tc.type: FUNC + * @tc.require: AR000EPARJ + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch009, TestSize.Level3) +{ + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps: step1. SetAutoLaunchRequestCallback + * @tc.expected: step1. success. + */ + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback(AutoLaunchCallBackBadParam, DBType::DB_KV); + /** + * @tc.steps: step2. RunCommunicatorLackCallback + * @tc.expected: step2. success. + */ + LabelType label(g_identifierA.begin(), g_identifierA.end()); + g_communicatorAggregator->RunCommunicatorLackCallback(label); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps: step3. PutSyncData key1 value1 + * @tc.expected: step3. db not open, notify INVALID_PARAM + */ + PutSyncData(g_propA, KEY1, VALUE1); + PutSyncData(g_propA, KEY1, VALUE2); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + EXPECT_TRUE(observer->GetCallCount() == 0); + { + std::unique_lock lock(g_cvMutex); + g_cv.wait(lock, [] {return g_finished;}); + EXPECT_TRUE(g_statusMap[DBCommon::TransferHashString("--")] == INVALID_PARAM); + g_statusMap.clear(); + g_finished = false; + } + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback(nullptr, DBType::DB_KV); + delete observer; +} + +/** + * @tc.name: AutoLaunch010 + * @tc.desc: enhancement nullptr callback + * @tc.type: FUNC + * @tc.require: AR000EPARJ + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch010, TestSize.Level3) +{ + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps: step1. SetAutoLaunchRequestCallback, then set nullptr + * @tc.expected: step1. success. + */ + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback( + std::bind(AutoLaunchCallBack, std::placeholders::_1, std::placeholders::_2, observer, false), DBType::DB_KV); + + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback(nullptr, DBType::DB_KV); + /** + * @tc.steps: step2. RunCommunicatorLackCallback + * @tc.expected: step2. success. + */ + LabelType label(g_identifierA.begin(), g_identifierA.end()); + g_communicatorAggregator->RunCommunicatorLackCallback(label); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps: step3. PutSyncData key1 value1 + * @tc.expected: step3. db not open + */ + PutSyncData(g_propA, KEY1, VALUE1); + PutSyncData(g_propA, KEY1, VALUE2); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + EXPECT_TRUE(observer->GetCallCount() == 0); + EXPECT_TRUE(g_finished == false); + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback(nullptr, DBType::DB_KV); + delete observer; +} + +/** + * @tc.name: AutoLaunch011 + * @tc.desc: enhancement GetKvStoreIdentifier + * @tc.type: FUNC + * @tc.require: AR000EPARJ + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch011, TestSize.Level3) +{ + EXPECT_EQ(KvStoreDelegateManager::GetKvStoreIdentifier("", APP_ID, STORE_ID_0), ""); + EXPECT_EQ(KvStoreDelegateManager::GetKvStoreIdentifier( + USER_ID, APP_ID, STORE_ID_0), DBCommon::TransferHashString(USER_ID + "-" + APP_ID + "-" + STORE_ID_0)); +} + +/** + * @tc.name: AutoLaunch012 + * @tc.desc: CommunicatorLackCallback + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch012, TestSize.Level3) +{ + /** + * @tc.steps: step1. right param A B enable + * @tc.expected: step1. success. + */ + AutoLaunchOption option; + option.notifier = ConflictNotifierCallback; + option.conflictType = CONFLICT_FOREIGN_KEY_ONLY; + int errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propA, TestAutoLaunchNotifier, option); + EXPECT_TRUE(errCode == E_OK); + AutoLaunchOption option1; + option1.notifier = nullptr; + errCode = RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propB, nullptr, option1); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step2. RunCommunicatorLackCallback + * @tc.expected: step2. success. + */ + LabelType label(g_identifierA.begin(), g_identifierA.end()); + g_communicatorAggregator->RunCommunicatorLackCallback(label); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + PutSyncData(g_propA, KEY1, VALUE1); + { + std::unique_lock lock(g_cvMutex); + g_cv.wait(lock, [] {return g_finished;}); + EXPECT_TRUE(g_statusMap[g_identifierA] == WRITE_OPENED); + g_statusMap.clear(); + g_finished = false; + } + /** + * @tc.steps: step3. PutSyncData key1 value2 + * @tc.expected: step3. ConflictNotifierCallback + */ + PutSyncData(g_propA, KEY1, VALUE2); + { + std::unique_lock lock(g_cvMutex); + g_cv.wait(lock, [] {return g_finished;}); + g_finished = false; + } + /** + * @tc.steps: step4. wait life cycle ,db close + * @tc.expected: step4. notifier WRITE_CLOSED + */ + SetLifeCycleTime(g_propA); + { + std::unique_lock lock(g_cvMutex); + g_cv.wait(lock, [] {return g_finished;}); + EXPECT_TRUE(g_statusMap[g_identifierA] == WRITE_CLOSED); + g_statusMap.clear(); + g_finished = false; + } + /** + * @tc.steps: step5. param A B disable + * @tc.expected: step5. OK + */ + std::string identifierA = g_propA.GetStringProp(KvDBProperties::DUAL_TUPLE_IDENTIFIER_DATA, ""); + std::string userIdA = g_propA.GetStringProp(KvDBProperties::USER_ID, ""); + std::string identifierB = g_propB.GetStringProp(KvDBProperties::DUAL_TUPLE_IDENTIFIER_DATA, ""); + std::string userIdB = g_propB.GetStringProp(KvDBProperties::USER_ID, ""); + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierB, g_dualIdentifierB, USER_ID); + EXPECT_TRUE(errCode == E_OK); + errCode = RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierA, g_dualIdentifierA, USER_ID); + EXPECT_TRUE(errCode == E_OK); +} + +/** + * @tc.name: AutoLaunch013 + * @tc.desc: online callback + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBAutoLaunchUnitTest, AutoLaunch013, TestSize.Level3) +{ + auto notifier = [] (const std::string &userId, const std::string &appId, + const std::string &storeId, AutoLaunchStatus status) { + LOGD("int AutoLaunch013 notifier status:%d", status); + }; + /** + * @tc.steps: step1. right param b c enable, a SetAutoLaunchRequestCallback + * @tc.expected: step1. success. + */ + AutoLaunchOption option; + option.notifier = nullptr; + EXPECT_TRUE(RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propB, notifier, option) == E_OK); + EXPECT_TRUE(RuntimeContext::GetInstance()->EnableKvStoreAutoLaunch(g_propC, notifier, option) == E_OK); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback( + std::bind(AutoLaunchCallBack, std::placeholders::_1, std::placeholders::_2, observer, true), DBType::DB_KV); + + /** + * @tc.steps: step2. RunOnConnectCallback RunCommunicatorLackCallback + * @tc.expected: step2. success. + */ + g_communicatorAggregator->RunOnConnectCallback(REMOTE_DEVICE_ID, true); + LabelType label(g_identifierA.begin(), g_identifierA.end()); + g_communicatorAggregator->RunCommunicatorLackCallback(label); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + + /** + * @tc.steps: step3. PutSyncData + * @tc.expected: step3. notifier WRITE_OPENED + */ + PutSyncData(g_propA, KEY1, VALUE1); + PutSyncData(g_propB, KEY1, VALUE1); + PutSyncData(g_propC, KEY1, VALUE1); + { + std::unique_lock lock(g_cvMutex); + g_cv.wait(lock, [] {return g_finished;}); + EXPECT_TRUE(g_statusMap[g_identifierA] == WRITE_OPENED); + g_statusMap.clear(); + g_finished = false; + } + /** + * @tc.steps: step4. PutSyncData key1 value2 + * @tc.expected: step4. ConflictNotifierCallback + */ + PutSyncData(g_propA, KEY1, VALUE2); + { + std::unique_lock lock(g_cvMutex); + g_cv.wait(lock, [] {return g_finished;}); + g_finished = false; + } + /** + * @tc.steps: step5. wait life cycle ,db close + * @tc.expected: step5. notifier WRITE_CLOSED + */ + SetLifeCycleTime(g_propA); + { + std::unique_lock lock(g_cvMutex); + g_cv.wait(lock, [] {return g_finished;}); + EXPECT_TRUE(g_statusMap[g_identifierA] == WRITE_CLOSED); + g_statusMap.clear(); + g_finished = false; + } + RuntimeContext::GetInstance()->SetAutoLaunchRequestCallback(nullptr, DBType::DB_KV); + delete observer; + /** + * @tc.steps: step4. param A B disable + * @tc.expected: step4. notifier WRITE_CLOSED + */ + EXPECT_TRUE(RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierB, g_dualIdentifierB, USER_ID) + == E_OK); + EXPECT_TRUE(RuntimeContext::GetInstance()->DisableKvStoreAutoLaunch(g_identifierC, g_dualIdentifierC, USER_ID) + == E_OK); + g_communicatorAggregator->RunOnConnectCallback(REMOTE_DEVICE_ID, false); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_common_test.cpp b/mock/distributeddb/test/unittest/common/common/distributeddb_common_test.cpp new file mode 100644 index 00000000..33abc927 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_common_test.cpp @@ -0,0 +1,526 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_errno.h" +#include "db_common.h" +#include "distributeddb_data_generate_unit_test.h" +#include "log_print.h" +#include "platform_specific.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { + std::string g_testDir; + + // define some variables to init a KvStoreDelegateManager object. + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + std::placeholders::_1, std::placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); +} + +class DistributedDBCommonTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBCommonTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBCommonTest::TearDownTestCase(void) {} + +void DistributedDBCommonTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + DistributedDBToolsUnitTest::TestDirInit(g_testDir); +} + +void DistributedDBCommonTest::TearDown(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGI("rm test db files error!"); + } +} + +/** + * @tc.name: RemoveAllFilesOfDirectory + * @tc.desc: Test delete all file and dir. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBCommonTest, RemoveAllFilesOfDirectory, TestSize.Level1) +{ + EXPECT_EQ(DBCommon::CreateDirectory(g_testDir + "/dirLevel1_1/"), E_OK); + EXPECT_EQ(DBCommon::CreateDirectory(g_testDir + "/dirLevel1_1/" + "/dirLevel2_1/"), E_OK); + EXPECT_EQ(DBCommon::CreateDirectory(g_testDir + "/dirLevel1_1/" + "/dirLevel2_2/"), E_OK); + EXPECT_EQ(DBCommon::CreateDirectory(g_testDir + "/dirLevel1_1/" + "/dirLevel2_2/" + "/dirLevel3_1/"), E_OK); + + EXPECT_EQ(OS::CreateFileByFileName(g_testDir + "/fileLevel1_1"), E_OK); + EXPECT_EQ(OS::CreateFileByFileName(g_testDir + "/dirLevel1_1/" + "/fileLevel2_1"), E_OK); + EXPECT_EQ(DBCommon::CreateDirectory(g_testDir + "/dirLevel1_1/" + "/dirLevel2_2/" + + "/dirLevel3_1/"+ "/fileLevel4_1/"), E_OK); + + EXPECT_EQ(DBCommon::RemoveAllFilesOfDirectory(g_testDir), E_OK); + + EXPECT_EQ(OS::CheckPathExistence(g_testDir), false); +} + +#ifdef RUNNING_ON_LINUX +/** + * @tc.name: SameProcessReLockFile + * @tc.desc: Test same process repeat lock same file. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBCommonTest, SameProcessReLockFile, TestSize.Level1) +{ + // block mode + EXPECT_EQ(OS::CreateFileByFileName(g_testDir + "/blockmode"), E_OK); + OS::FileHandle fd; + EXPECT_EQ(OS::OpenFile(g_testDir + "/blockmode", fd), E_OK); + + EXPECT_EQ(OS::FileLock(fd, true), E_OK); + EXPECT_EQ(OS::FileLock(fd, true), E_OK); + + // normal mode + OS::FileHandle fd2; + EXPECT_EQ(OS::CreateFileByFileName(g_testDir + "/normalmode"), E_OK); + EXPECT_EQ(OS::OpenFile(g_testDir + "/normalmode", fd2), E_OK); + EXPECT_EQ(OS::FileLock(fd2, true), E_OK); + EXPECT_EQ(OS::FileLock(fd2, true), E_OK); + + // unlock + EXPECT_EQ(OS::FileUnlock(fd), E_OK); + EXPECT_EQ(OS::FileUnlock(fd2), E_OK); // unlock success will close fd +} + +/** + * @tc.name: SameProcessReUnLockFile + * @tc.desc: Test same process repeat lock same file. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBCommonTest, SameProcessReUnLockFile, TestSize.Level1) +{ + // unlock normal file twice + EXPECT_EQ(OS::CreateFileByFileName(g_testDir + "/normalmode"), E_OK); + OS::FileHandle fd; + EXPECT_EQ(OS::OpenFile(g_testDir + "/normalmode", fd), E_OK); + EXPECT_EQ(OS::FileUnlock(fd), E_OK); + EXPECT_EQ(OS::FileUnlock(fd), E_OK); // unlock success will close fd + + EXPECT_EQ(OS::FileLock(fd, true), -E_SYSTEM_API_FAIL); + EXPECT_EQ(OS::FileLock(fd, true), -E_SYSTEM_API_FAIL); + ASSERT_EQ(fd.handle, -1); + + // block mode + EXPECT_EQ(OS::CreateFileByFileName(g_testDir + "/blockmode"), E_OK); + EXPECT_EQ(OS::OpenFile(g_testDir + "/blockmode", fd), E_OK); + + EXPECT_EQ(OS::FileLock(fd, false), E_OK); + EXPECT_EQ(OS::FileLock(fd, false), E_OK); + + EXPECT_EQ(OS::FileUnlock(fd), E_OK); + EXPECT_EQ(OS::FileUnlock(fd), E_OK); +} + +/** + * @tc.name: CalFileSizeTest + * @tc.desc: Test the file size for function test and the performance test. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBCommonTest, CalFileSizeTest, TestSize.Level1) +{ + std::string filePath = g_testDir + "/testFileSize"; + std::ofstream ofs(filePath, std::ofstream::out); + ASSERT_TRUE(ofs.good()); + ofs << "test file size"; + ofs.close(); + uint64_t fileSize = 0; + EXPECT_EQ(OS::CalFileSize(filePath, fileSize), E_OK); + EXPECT_GT(fileSize, 0ULL); + EXPECT_EQ(OS::RemoveFile(filePath), E_OK); +} + +// Distributed db is not recommended to use multiple processes to access +// This testcase only guard for some wrong use on current product +#if defined(RUN_MULTI_PROCESS_TEST) +namespace { +// use file sync diff process information +bool waitForStep(int step, int retryTimes) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + while (retryTimes >= 0 && !OS::CheckPathExistence(g_testDir + "/LOCK_step_" + std::to_string(step))) { + retryTimes = retryTimes - 1; // wait 10ms one times + std::this_thread::sleep_for(std::chrono::milliseconds(10)); // once 10 ms + } + return (retryTimes > 0); +} + +void createStepFlag(int step) +{ + EXPECT_EQ(OS::CreateFileByFileName(g_testDir + "/LOCK_step_" + std::to_string(step)), E_OK); +} +} + +/** + * @tc.name: DiffProcessLockFile + * @tc.desc: Test different process repeat lock same file. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBCommonTest, DiffProcessLockFile, TestSize.Level1) +{ + OS::FileHandle fd; + EXPECT_EQ(OS::OpenFile(g_testDir + DBConstant::DB_LOCK_POSTFIX, fd), E_OK); + EXPECT_EQ(OS::FileLock(fd, false), E_OK); + sleep(1); + LOGI("begin fork new process!!"); + pid_t pid = fork(); + ASSERT_TRUE(pid >= 0); + if (pid < 0) { + return; + } + else if (pid == 0) { + LOGI("child process begin!"); + OS::FileHandle ChildFd; + EXPECT_EQ(OS::OpenFile(g_testDir + DBConstant::DB_LOCK_POSTFIX, ChildFd), E_OK); + ASSERT_TRUE(waitForStep(1, 10)); + EXPECT_EQ(OS::FileLock(ChildFd, false), -E_BUSY); + createStepFlag(2); + EXPECT_EQ(OS::CloseFile(ChildFd), E_OK); + exit(0); + } else { + LOGI("main process begin!"); + EXPECT_EQ(OS::FileLock(fd, false), E_OK); + createStepFlag(1); + + ASSERT_TRUE(waitForStep(2, 100)); + EXPECT_EQ(OS::CloseFile(fd), E_OK); // fd close, lock invalid + } +} + +/** + * @tc.name: DiffProcessLockFileBlocked + * @tc.desc: Test different process repeat lock same file. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBCommonTest, DiffProcessLockFileBlocked, TestSize.Level1) +{ + EXPECT_EQ(OS::CreateFileByFileName(g_testDir + DBConstant::DB_LOCK_POSTFIX), E_OK); + OS::FileHandle fd; + EXPECT_EQ(OS::OpenFile(g_testDir + DBConstant::DB_LOCK_POSTFIX, fd), E_OK); + EXPECT_EQ(OS::FileLock(fd, true), E_OK); + sleep(1); + LOGI("begin fork new process!!"); + int count = 10; // wait 10 times 10 ms for block wait + pid_t pid = fork(); + ASSERT_TRUE(pid >= 0); + if (pid < 0) { + return; + } + else if (pid == 0) { + LOGI("child process begin!"); + EXPECT_FALSE(OS::CheckPathExistence(g_testDir + "/LOCK_step_1")); + OS::FileHandle ChildFd; + EXPECT_EQ(OS::OpenFile(g_testDir + DBConstant::DB_LOCK_POSTFIX, ChildFd), E_OK); + EXPECT_EQ(OS::FileLock(ChildFd, true), E_OK); + createStepFlag(1); + EXPECT_EQ(OS::FileUnlock(ChildFd), E_OK); + LOGI("child process finish!"); + exit(0); + } else { + LOGI("main process begin!"); + while (count--) { + LOGI("main process waiting!"); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); // once 10 ms + } + ASSERT_FALSE(waitForStep(1, 10)); + EXPECT_EQ(OS::FileUnlock(fd), E_OK); + ASSERT_TRUE(waitForStep(1, 10)); + } +} + +/** + * @tc.name: DiffProcessGetDBBlocked + * @tc.desc: Test block other process get kvstore when db locked. + * @tc.type: FUNC + * @tc.require: AR000CQDV7 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBCommonTest, DiffProcessGetDBBlocked, TestSize.Level1) +{ + std::string storeId = "DiffProcessGetDBBlocked"; + std::string origId = USER_ID + "-" + APP_ID + "-" + storeId; + std::string identifier = DBCommon::TransferHashString(origId); + std::string hexDir = DBCommon::TransferStringToHex(identifier); + std::string lockFile = g_testDir + "/" + hexDir + DBConstant::DB_LOCK_POSTFIX; + EXPECT_EQ(DBCommon::CreateDirectory(g_testDir + "/" + hexDir), E_OK); + EXPECT_EQ(OS::CreateFileByFileName(lockFile), E_OK); + LOGI("Create lock file[%s]", lockFile.c_str()); + + LOGI("begin fork new process!!"); + pid_t pid = fork(); + OS::FileHandle fd; + ASSERT_TRUE(pid >= 0); + if (pid == 0) { + LOGI("child process begin!"); + ASSERT_TRUE(waitForStep(1, 10)); + KvStoreNbDelegate::Option option = {true, false, false}; + option.isNeedIntegrityCheck = true; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == BUSY); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + createStepFlag(2); + exit(0); + } else { + LOGI("main process begin!"); + EXPECT_EQ(OS::OpenFile(lockFile, fd), E_OK); + EXPECT_EQ(OS::FileLock(fd, false), E_OK); + createStepFlag(1); + } + + // Prevent the child process from not being completed, the main process ends to clean up resources + EXPECT_TRUE(waitForStep(2, 1000)); + EXPECT_EQ(OS::FileUnlock(fd), E_OK); +} + +/** + * @tc.name: DiffProcessDeleteDBBlocked + * @tc.desc: Test block other process delete kvstore when db locked. + * @tc.type: FUNC + * @tc.require: AR000CQDV7 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBCommonTest, DiffProcessDeleteDBBlocked, TestSize.Level1) +{ + std::string storeId = "DiffProcessDeleteDBBlocked"; + std::string origId = USER_ID + "-" + APP_ID + "-" + storeId; + std::string identifier = DBCommon::TransferHashString(origId); + std::string hexDir = DBCommon::TransferStringToHex(identifier); + std::string lockFile = g_testDir + "/" + hexDir + DBConstant::DB_LOCK_POSTFIX; + EXPECT_EQ(DBCommon::CreateDirectory(g_testDir + "/" + hexDir), E_OK); + EXPECT_EQ(OS::CreateFileByFileName(lockFile), E_OK); + LOGI("Create lock file[%s]", lockFile.c_str()); + + KvStoreNbDelegate::Option option = {true, false, false}; + option.isNeedIntegrityCheck = true; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + LOGI("begin fork new process!!"); + pid_t pid = fork(); + OS::FileHandle fd; + ASSERT_TRUE(pid >= 0); + if (pid == 0) { + LOGI("child process begin!"); + ASSERT_TRUE(waitForStep(1, 10)); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), BUSY); + createStepFlag(2); + exit(0); + } else { + LOGI("main process begin!"); + EXPECT_EQ(OS::OpenFile(lockFile, fd), E_OK); + EXPECT_EQ(OS::FileLock(fd, false), E_OK); + createStepFlag(1); + } + + // Prevent the child process from not being completed, the main process ends to clean up resources + EXPECT_TRUE(waitForStep(2, 1000)); + EXPECT_EQ(OS::FileUnlock(fd), E_OK); + g_mgr.CloseKvStore(g_kvNbDelegatePtr); +} + +/** + * @tc.name: DiffProcessGetDBBlocked001 + * @tc.desc: Test block other process get kvstore when db locked. + * @tc.type: FUNC + * @tc.require: AR000CQDV7 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBCommonTest, DiffProcessGetDBBlocked001, TestSize.Level1) +{ + std::string storeId = "DiffProcessGetDBBlocked001"; + std::string origId = USER_ID + "-" + APP_ID + "-" + storeId; + std::string identifier = DBCommon::TransferHashString(origId); + std::string hexDir = DBCommon::TransferStringToHex(identifier); + std::string lockFile = g_testDir + "/" + hexDir + DBConstant::DB_LOCK_POSTFIX; + EXPECT_EQ(DBCommon::CreateDirectory(g_testDir + "/" + hexDir), E_OK); + EXPECT_EQ(OS::CreateFileByFileName(lockFile), E_OK); + LOGI("Create lock file[%s]", lockFile.c_str()); + + LOGI("begin fork new process!!"); + pid_t pid = fork(); + OS::FileHandle fd; + ASSERT_TRUE(pid >= 0); + if (pid == 0) { + LOGI("child process begin!"); + ASSERT_TRUE(waitForStep(1, 10)); + KvStoreNbDelegate::Option option = {true, false, false}; + option.isNeedIntegrityCheck = true; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + createStepFlag(2); + exit(0); + } else { + LOGI("main process begin!"); + EXPECT_EQ(OS::OpenFile(lockFile, fd), E_OK); + EXPECT_EQ(OS::FileLock(fd, false), E_OK); + createStepFlag(1); + } + ASSERT_TRUE(waitForStep(1, 100)); + + EXPECT_EQ(OS::FileUnlock(fd), E_OK); + + ASSERT_TRUE(waitForStep(2, 100)); +} + +/** + * @tc.name: DiffProcessGetDB + * @tc.desc: Test block other process get kvstore. + * @tc.type: FUNC + * @tc.require: AR000CQDV7 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBCommonTest, DiffProcessGetDB, TestSize.Level1) +{ + std::string storeId = "DiffProcessGetDB"; + KvStoreNbDelegate::Option option = {true, false, false}; + option.isNeedIntegrityCheck = true; + LOGI("begin fork new process!!"); + pid_t pid = fork(); + ASSERT_TRUE(pid >= 0); + if (pid == 0) { + LOGI("child process begin!"); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + createStepFlag(2); + EXPECT_TRUE(waitForStep(1, 1000)); + exit(0); + } else { + LOGI("main process begin!"); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + createStepFlag(1); + } + EXPECT_TRUE(waitForStep(2, 100)); + // Prevent the child process from not being completed, the main process ends to clean up resources + g_mgr.CloseKvStore(g_kvNbDelegatePtr); +} + +/** + * @tc.name: DiffProcessDeleteDB + * @tc.desc: Test block other process delete kvstore. + * @tc.type: FUNC + * @tc.require: AR000CQDV7 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBCommonTest, DiffProcessDeleteDB, TestSize.Level1) +{ + std::string storeId = "DiffProcessGetDB"; + KvStoreNbDelegate::Option option = {true, false, false}; + option.isNeedIntegrityCheck = true; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + LOGI("begin fork new process!!"); + pid_t pid = fork(); + ASSERT_TRUE(pid >= 0); + if (pid == 0) { + LOGI("child process begin!"); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + createStepFlag(2); + EXPECT_TRUE(waitForStep(1, 1000)); + exit(0); + } else { + LOGI("main process begin!"); + g_mgr.DeleteKvStore(storeId); + createStepFlag(1); + } + EXPECT_TRUE(waitForStep(2, 100)); + + // Prevent the child process from not being completed, the main process ends to clean up resources + EXPECT_TRUE(waitForStep(1, 100)); +} + +/** + * @tc.name: DiffProcessGetAndDeleteDB + * @tc.desc: Test block other process delete kvstore. + * @tc.type: FUNC + * @tc.require: AR000CQDV7 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBCommonTest, DiffProcessGetAndDeleteDB, TestSize.Level1) +{ + std::string storeId = "DiffProcessGetAndDeleteDB"; + KvStoreNbDelegate::Option option = {true, false, false}; + option.isNeedIntegrityCheck = true; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + LOGI("begin fork new process!!"); + pid_t pid = fork(); + ASSERT_TRUE(pid >= 0); + if (pid == 0) { + LOGI("child process begin!"); + g_mgr.DeleteKvStore(storeId); // one process OK, one process NOT_FOUND + createStepFlag(2); + EXPECT_TRUE(waitForStep(1, 1000)); + exit(0); + } else { + LOGI("main process begin!"); + g_mgr.DeleteKvStore(storeId); + createStepFlag(1); + } + EXPECT_TRUE(waitForStep(2, 100)); + + // Prevent the child process from not being completed, the main process ends to clean up resources + EXPECT_TRUE(waitForStep(1, 1000)); +} +#endif +#endif + diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_data_compression_test.cpp b/mock/distributeddb/test/unittest/common/common/distributeddb_data_compression_test.cpp new file mode 100644 index 00000000..e3754c65 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_data_compression_test.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_tools_unit_test.h" +#include "data_compression.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { +#ifndef OMIT_ZLIB +// LENGTH IS 680. +unsigned char g_srcStr[] = + "I come from Alabama with my banjo on my knee," + "I'm going to Louisiana, my true love for to see," + "It rained all night the day I left," + "The weather it was dry," + "The sun so hot, I froze to death," + "Susanna don't you cry," + "Oh, Susanna," + "Oh don't you cry for me," + "For I come from Alabama," + "With my banjo on my knee," + "I had a dream the other night when everything was still," + "I thought I saw Susanna a-coming down the hill," + "The buckwheat cake was in her mouth," + "The tear was in her eye," + "Says I, I'm coming from the south," + "Susanna, don't you cry," + "Oh, Susanna," + "Oh don't you cry for me," + "I'm going to Louisiana," + "With my banjo on my knee," + "Oh, Susanna," + "Oh don't you cry for me," + "I'm going to Louisiana," + "With my banjo on my knee."; +} +#endif +class DistributedDBDataCompressionTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBDataCompressionTest::SetUpTestCase(void) +{} + +void DistributedDBDataCompressionTest::TearDownTestCase(void) +{} + +void DistributedDBDataCompressionTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBDataCompressionTest::TearDown(void) +{} + +/** + * @tc.name: DataCompression1 + * @tc.desc: To test the function compress and uncompress works well in normal situation. + * @tc.type: FUNC + * @tc.require: AR000G3QTT + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBDataCompressionTest, DataCompression1, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare a source data. And compress it. + * @tc.expected: step1. Compress successfully. Compressed data length is less than srcLen. + */ +#ifndef OMIT_ZLIB + const int origLen = sizeof(g_srcStr); + vector srcData(g_srcStr, g_srcStr + sizeof(g_srcStr)); + + vector compressedData; + EXPECT_EQ(DataCompression::GetInstance(CompressAlgorithm::ZLIB)->Compress(srcData, compressedData), E_OK); + EXPECT_LT(compressedData.size(), srcData.size()); + + /** + * @tc.steps:step2. Uncompress the compressed data. + * @tc.expected: step2. Uncompress successfully. Uncompressed data equals to source data. + */ + vector uncompressedData; + EXPECT_EQ(DataCompression::GetInstance(CompressAlgorithm::ZLIB)->Uncompress( + compressedData, uncompressedData, origLen), E_OK); + EXPECT_EQ(srcData, uncompressedData); +#endif // OMIT_ZLIB +} + +/** + * @tc.name: DataCompression2 + * @tc.desc: To test uncompress failed when compressed is destroyed. + * @tc.type: FUNC + * @tc.require: AR000G3QTT + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBDataCompressionTest, DataCompression2, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare a source data. And compress it. + * @tc.expected: step1. Compress successfully. Compressed data length is less than srcLen. + */ +#ifndef OMIT_ZLIB + const int origLen = sizeof(g_srcStr); + vector srcData(g_srcStr, g_srcStr + sizeof(g_srcStr)); + + vector compressedData; + EXPECT_EQ(DataCompression::GetInstance(CompressAlgorithm::ZLIB)->Compress(srcData, compressedData), E_OK); + EXPECT_LT(compressedData.size(), srcData.size()); + + /** + * @tc.steps:step2. Destroy the compressed data. + */ + *(compressedData.begin()) = ~*(compressedData.begin()); + + /** + * @tc.steps:step3. Uncompress the compressed data. + * @tc.expected: step3. Uncompressed failed and return -E_SYSTEM_API_FAIL. + */ + vector uncompressedData; + EXPECT_EQ(DataCompression::GetInstance(CompressAlgorithm::ZLIB)->Uncompress( + compressedData, uncompressedData, origLen), -E_SYSTEM_API_FAIL); +#endif // OMIT_ZLIB +} + +/** + * @tc.name: DataCompression3 + * @tc.desc: To test uncompress works when bufferLen is larger but under 30M limit, + and uncompress failed when bufferLen is beyond the 30M limit. + * @tc.type: FUNC + * @tc.require: AR000G3QTT + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBDataCompressionTest, DataCompression3, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare a source data. And compress it. + * @tc.expected: step1. Compress successfully. Compressed data length is less than srcLen. + */ +#ifndef OMIT_ZLIB + vector srcData(g_srcStr, g_srcStr + sizeof(g_srcStr)); + + vector compressedData; + EXPECT_EQ(DataCompression::GetInstance(CompressAlgorithm::ZLIB)->Compress(srcData, compressedData), E_OK); + EXPECT_LT(compressedData.size(), srcData.size()); + + /** + * @tc.steps:step2. Set origLen a larger num under 30M limit. And uncompress. + * @tc.expected: step1. Uncompress successfully. + */ + vector uncompressedData; + int incorrectLen = 10000; + EXPECT_EQ(DataCompression::GetInstance(CompressAlgorithm::ZLIB)->Uncompress( + compressedData, uncompressedData, incorrectLen), E_OK); + EXPECT_EQ(srcData, uncompressedData); + + /** + * @tc.steps:step2. Set origLen a larger num beyond 30M limit. And uncompress. + * @tc.expected: step1. Uncompress failed and return E_INVALID_ARGS. + */ + uncompressedData.clear(); + incorrectLen = 31457281; // 30M + 1 + EXPECT_EQ(DataCompression::GetInstance(CompressAlgorithm::ZLIB)->Uncompress( + compressedData, uncompressedData, incorrectLen), -E_INVALID_ARGS); +#endif // OMIT_ZLIB +} diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.cpp b/mock/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.cpp new file mode 100644 index 00000000..74c440cf --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_data_generate_unit_test.h" +#include "store_types.h" + +using namespace DistributedDB; + +namespace DistributedDBUnitTest { +void GenerateKey(int keyCount, int startPosition, Key &keyTest) +{ + if (keyCount <= 0) { + return; + } + int i; + for (i = 0; i < keyCount; i++) { + keyTest.push_back(KEY_NUM[(i + startPosition) % NUM_LENGTH]); + } +} + +void GenerateValue(int valueCount, int startPosition, Value &valueTest) +{ + if (valueCount <= 0) { + return; + } + int i; + for (i = 0; i < valueCount; i++) { + valueTest.push_back(VALUE_LETTER[(i + startPosition) % LETTER_LENGTH]); + } +} + +void GenerateEntry(int entryCount, int startPosition, Entry &entryTest) +{ + if (entryCount <= 0) { + return; + } + GenerateKey(entryCount, startPosition, entryTest.key); + GenerateValue(entryCount, startPosition, entryTest.value); +} + +void GenerateEntryVector(int entryVectorCount, int entryCount, std::vector &entrysTest) +{ + if (entryVectorCount <= 0 || entryCount <= 0) { + return; + } + int i; + for (i = 0; i < entryVectorCount; i++) { + Entry entry; + GenerateEntry(entryCount, i, entry); + entrysTest.push_back(entry); + } +} + +void GenerateRecords(int recordNum, std::vector &entries, std::vector &keys, int keySize, int valSize) +{ + Entry entry; + // start from index 1 + for (int recordIndex = 1; recordIndex <= recordNum; ++recordIndex) { + std::string cntStr = std::to_string(recordIndex); + int len = cntStr.length(); + if (keySize <= len) { + break; + } + if (valSize <= len) { + break; + } + + entry.key.assign((keySize - len), '0'); + entry.value.assign((valSize - len), 'v'); + for (auto item = cntStr.begin(); item != cntStr.end(); ++item) { + entry.key.push_back(*item); + entry.value.push_back(*item); + } + entries.push_back(entry); + keys.push_back(entry.key); + + entry.key.clear(); + entry.value.clear(); + } +} +} // namespace DistributedDBUnitTest \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.h b/mock/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.h new file mode 100644 index 00000000..3b6f8d8e --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_data_generate_unit_test.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_DATA_GENERATE_UNIT_H +#define DISTRIBUTEDDB_DATA_GENERATE_UNIT_H + +#include +#include +#include "distributeddb_tools_unit_test.h" +#include "store_types.h" + +namespace DistributedDBUnitTest { +// define some variables to init a KvStoreDelegateManager object. +const std::string APP_ID = "app0"; +const std::string SCHEMA_APP_ID = "app1"; +const std::string USER_ID = "user0"; + +const std::string STORE_ID_LOCAL = "distributed_local_db_test"; +const std::string STORE_ID_SYNC = "distributed_sync_db_test"; +const std::string STORE_ID_1 = "distributed_db_test1"; +const std::string STORE_ID_2 = "distributed_db_test2"; +const std::string STORE_ID_3 = "distributed_db_test3"; +const std::string STORE_ID_4 = "distributed_db_test4"; +const std::string STORE_ID_5 = "distributed_db_test5"; +const std::string STORE_ID_6 = "distributed_db_test6"; +const std::string STORE_ID_7 = "distributed_db_test7"; +const std::string STORE_ID_8 = "distributed_db_test8"; + +const int NUM_LENGTH = 10; +const int LETTER_LENGTH = 52; +const uint8_t KEY_NUM[NUM_LENGTH] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; +const uint8_t VALUE_LETTER[LETTER_LENGTH] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'L', 'M', 'L', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', + 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'l', 'm', 'l', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z' +}; + +const DistributedDB::Key KEY_1 = {'1'}; +const DistributedDB::Value VALUE_1 = {'a'}; +const DistributedDB::Key KEY_2 = {'2'}; +const DistributedDB::Value VALUE_2 = {'b'}; +const DistributedDB::Key KEY_3 = {'3'}; +const DistributedDB::Value VALUE_3 = {'c'}; +const DistributedDB::Key KEY_4 = {'4'}; +const DistributedDB::Value VALUE_4 = {'d'}; +const DistributedDB::Key KEY_5 = {'5'}; +const DistributedDB::Value VALUE_5 = {'e'}; +const DistributedDB::Key KEY_6 = {'6'}; +const DistributedDB::Value VALUE_6 = {'f'}; +const DistributedDB::Key KEY_7 = {'7'}; +const DistributedDB::Value VALUE_7 = {'g'}; + +const DistributedDB::Key NULL_KEY_1; +const DistributedDB::Value NULL_VALUE_1; + +const DistributedDB::Entry ENTRY_1 = {KEY_1, VALUE_1}; +const DistributedDB::Entry ENTRY_2 = {KEY_2, VALUE_2}; +const DistributedDB::Entry NULL_ENTRY_1 = {NULL_KEY_1, VALUE_1}; +const DistributedDB::Entry NULL_ENTRY_2 = {KEY_1, NULL_VALUE_1}; + +const DistributedDB::Entry ENTRY_3 = {KEY_3, VALUE_3}; +const DistributedDB::Entry ENTRY_4 = {KEY_4, VALUE_4}; + +const DistributedDB::Entry KV_ENTRY_1 = {KEY_1, VALUE_1}; +const DistributedDB::Entry KV_ENTRY_2 = {KEY_2, VALUE_2}; +const DistributedDB::Entry KV_ENTRY_3 = {KEY_3, VALUE_3}; +const DistributedDB::Entry KV_ENTRY_4 = {KEY_4, VALUE_4}; + +const std::vector ENTRY_VECTOR = {ENTRY_1, ENTRY_2}; + +const int DEFAULT_NB_KEY_VALUE_SIZE = 10; + +// generate a key, has keyCount chars, from KEY_NUM[startPosition], return keyTest +void GenerateKey(int keyCount, int startPosition, DistributedDB::Key &keyTest); + +// generate a value, has valueCount chars, from VALUE_LETTER[startPosition], return valueTest +void GenerateValue(int valueCount, int startPosition, DistributedDB::Value &valueTest); + +/* + * generate an entry, entry.key and entry.value have valueCount chars, + * from KEY_NUM[startPosition] and VALUE_LETTER[startPosition], return entryTest + */ +void GenerateEntry(int entryCount, int startPosition, DistributedDB::Entry &entryTest); + +/* + * generate a vector, vector.size() is entryVectorCount, entry.key and entry.value have valueCount chars, + * from KEY_NUM[startPosition] and VALUE_LETTER[startPosition], return entrysTest + */ +void GenerateEntryVector(int entryVectorCount, int entryCount, std::vector &entrysTest); + +void GenerateRecords(int recordNum, std::vector &entries, std::vector &keys, + int keySize = DEFAULT_NB_KEY_VALUE_SIZE, int valSize = DEFAULT_NB_KEY_VALUE_SIZE); +} // namespace DistributedDBUnitTest + +#endif // DISTRIBUTEDDB_DATA_GENERATE_UNIT_H diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_json_precheck_unit_test.cpp b/mock/distributeddb/test/unittest/common/common/distributeddb_json_precheck_unit_test.cpp new file mode 100644 index 00000000..ffae09ca --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_json_precheck_unit_test.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_JSON +#include +#include +#include "db_errno.h" +#include "distributeddb_tools_unit_test.h" +#include "json_object.h" + +using namespace std; +using namespace testing::ext; +using namespace DistributedDB; + +namespace { + const int MAX_DEPTH_FOR_TEST = 10; + const int STRING1_DEPTH = 12; + const int STRING3_DEPTH = 6; + + // nest depth = 12 and valid. + const string JSON_STRING1 = "{\"#14\":[[{\"#11\":{\"#8\":[{\"#5\":[[{\"#2\":[{\"#0\":\"value_\"},\"value_\"]," + "\"#3\":\"value_\"},\"value_\"],\"value_\"],\"#6\":\"value_\"},\"value_\"],\"#9\":\"value_\"}," + "\"#12\":\"value_\"},\"value_\"],\"value_\"],\"#15\":{\"#18\":{\"#16\":\"value_\"},\"#19\":\"value_\"}}"; + + // nest depth = 12 and invalid happens in nest depth = 2. + const string JSON_STRING2 = "{\"#17\":[\"just for mistake pls.[{\"#14\":[[{\"#11\":{\"#8\":{\"#5\":[{\"#2\":" + "{\"#0\":\"value_\"},\"#3\":\"value_\"},\"value_\"],\"#6\":\"value_\"},\"#9\":\"value_\"}," + "\"#12\":\"value_\"},\"value_\"],\"value_\"],\"#15\":\"value_\"},\"value_\"],\"value_\"]," + "\"#18\":{\"#21\":{\"#19\":\"value_\"},\"#22\":\"value_\"}}"; + + // nest depth = 6 and valid. + const string JSON_STRING3 = "{\"#5\":[{\"#2\":[[{\"#0\":\"value_\"},\"value_\"],\"value_\"],\"#3\":\"value_\"}," + "\"value_\"],\"#6\":{\"#7\":\"value_\",\"#8\":\"value_\"}}"; + + // nest depth = 6 and invalid happens in nest depth = 3. + const string JSON_STRING4 = "{\"#6\":[{\"#3\":\"just for mistake pls.[{\"#0\":[\"value_\"],\"#1\":\"value_\"}," + "\"value_\"],\"#4\":\"value_\"},\"value_\"],\"#7\":{\"#8\":\"value_\",\"#9\":\"value_\"}}"; + + // nest depth = 15 and invalid happens in nest depth = 11. + const string JSON_STRING5 = "{\"#35\":[{\"#29\":{\"#23\":{\"#17\":{\"#11\":{\"#8\":[{\"#5\":[{\"#2\":" + "\"just for mistake pls.[[[{\"#0\":\"value_\"},\"value_\"],\"value_\"],\"value_\"],\"#3\":\"value_\"}," + "\"value_\"],\"#6\":\"value_\"},\"value_\"],\"#9\":\"value_\"},\"#12\":{\"#13\":\"value_\"," + "\"#14\":\"value_\"}},\"#18\":{\"#19\":\"value_\",\"#20\":\"value_\"}},\"#24\":{\"#25\":\"value_\"," + "\"#26\":\"value_\"}},\"#30\":{\"#31\":\"value_\",\"#32\":\"value_\"}},\"value_\"],\"#36\":" + "{\"#37\":[\"value_\"],\"#38\":\"value_\"}}"; + + uint32_t g_oriMaxNestDepth = 0; +} + +class DistributedDBJsonPrecheckUnitTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown() {}; +}; + +void DistributedDBJsonPrecheckUnitTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Specifies a maximum nesting depth of 10. + */ + g_oriMaxNestDepth = JsonObject::SetMaxNestDepth(MAX_DEPTH_FOR_TEST); +} + +void DistributedDBJsonPrecheckUnitTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: Reset nesting depth to origin value. + */ + JsonObject::SetMaxNestDepth(g_oriMaxNestDepth); +} + +void DistributedDBJsonPrecheckUnitTest::SetUp() +{ + DistributedDBUnitTest::DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +/** + * @tc.name: Precheck Valid String 001 + * @tc.desc: json string is legal + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBJsonPrecheckUnitTest, ParseValidString001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Check legal json string with nesting depth of 12. + * @tc.expected: step1. return value = 12. + */ + int errCode = E_OK; + int stepOne = JsonObject::CalculateNestDepth(JSON_STRING1, errCode); + EXPECT_TRUE(errCode == E_OK); + EXPECT_TRUE(stepOne == STRING1_DEPTH); + + /** + * @tc.steps: step2. Parsing of legal json string with nesting depth greater than 10 failed. + * @tc.expected: step2. Parsing result failed. + */ + JsonObject tempObj; + int stepTwo = tempObj.Parse(JSON_STRING1); + EXPECT_TRUE(stepTwo != E_OK); +} + +/** + * @tc.name: Precheck Valid String 002 + * @tc.desc: json string is legal + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBJsonPrecheckUnitTest, ParseValidString002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Check legal json string with nesting depth of 6. + * @tc.expected: step1. return value = 6. + */ + int errCode = E_OK; + int stepOne = JsonObject::CalculateNestDepth(JSON_STRING3, errCode); + EXPECT_TRUE(errCode == E_OK); + EXPECT_TRUE(stepOne == STRING3_DEPTH); + + /** + * @tc.steps: step2. Parsing of legal json string with nesting depth less than 10 success. + * @tc.expected: step2. Parsing result success. + */ + JsonObject tempObj; + int stepTwo = tempObj.Parse(JSON_STRING3); + EXPECT_TRUE(stepTwo == E_OK); +} + +/** + * @tc.name: Precheck invalid String 001 + * @tc.desc: The json string has been detected illegal before exceeding the specified nesting depth. + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBJsonPrecheckUnitTest, ParseInvalidString001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Parsing of illegal json string with nesting depth greater than 10 success. + * @tc.expected: step1. Parsing result failed. + */ + JsonObject tempObj; + int stepOne = tempObj.Parse(JSON_STRING2); + EXPECT_TRUE(stepOne != E_OK); +} + +/** + * @tc.name: Precheck invalid String 002 + * @tc.desc: The json string has been detected illegal before exceeding the specified nesting depth. + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBJsonPrecheckUnitTest, ParseInvalidString002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Parsing of illegal json string with nesting depth less than 10 success. + * @tc.expected: step1. Parsing result failed. + */ + JsonObject tempObj; + int stepOne = tempObj.Parse(JSON_STRING4); + EXPECT_TRUE(stepOne != E_OK); +} + +/** + * @tc.name: Precheck invalid String 003 + * @tc.desc: The json string has been detected illegal before exceeding the specified nesting depth. + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBJsonPrecheckUnitTest, ParseInvalidString003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Detect illegal json string with nesting depth greater than 10. + * @tc.expected: step1. return value > 10. + */ + int errCode = E_OK; + int stepOne = JsonObject::CalculateNestDepth(JSON_STRING5, errCode); + EXPECT_TRUE(errCode == E_OK); + EXPECT_TRUE(stepOne > MAX_DEPTH_FOR_TEST); + + /** + * @tc.steps: step2. Parsing of illegal json string with nesting depth greater than 10 success. + * @tc.expected: step2. Parsing result failed. + */ + JsonObject tempObj; + int stepTwo = tempObj.Parse(JSON_STRING5); + EXPECT_TRUE(stepTwo != E_OK); +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_notification_chain_test.cpp b/mock/distributeddb/test/unittest/common/common/distributeddb_notification_chain_test.cpp new file mode 100644 index 00000000..7208d445 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_notification_chain_test.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_errno.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" +#include "notification_chain.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace std; +using namespace DistributedDBUnitTest; + +namespace { + const EventType COMMIT_EVENT = 1; + const int INVALID_EVENT = 0; + NotificationChain *g_notificationChain = nullptr; + NotificationChain::Listener *g_listener = nullptr; + int g_onEventTestNum = 0; + bool g_onFinalizeCalled = false; + + auto g_onEventFunction = [](void *arg) { + g_onEventTestNum = *(reinterpret_cast(arg)); + LOGI("g_onEventFunction called."); + }; + + auto g_onFinalize = []() { + g_onFinalizeCalled = true; + LOGI("g_onFinalize called."); + }; +} + +class DistributedDBNotificationChainTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBNotificationChainTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Create a NotificationChain. + */ + g_notificationChain = new (std::nothrow) NotificationChain(); + EXPECT_TRUE(g_notificationChain != nullptr); +} + +void DistributedDBNotificationChainTest::TearDownTestCase(void) +{ + /** + * @tc.setup: Release a NotificationChain. + */ + g_notificationChain->OnLastRef([]() { LOGI("g_notificationChain finalize called."); }); + g_notificationChain->KillAndDecObjRef(g_notificationChain); + g_notificationChain = nullptr; +} + +void DistributedDBNotificationChainTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: Register a listener to the NotificationChain + */ + g_onEventTestNum = 0; + g_onFinalizeCalled = false; + int result = g_notificationChain->RegisterEventType(COMMIT_EVENT); + EXPECT_TRUE(result == E_OK); + int errCode = E_OK; + g_listener = g_notificationChain->RegisterListener(COMMIT_EVENT, g_onEventFunction, g_onFinalize, errCode); + EXPECT_TRUE(g_listener != nullptr); +} + +void DistributedDBNotificationChainTest::TearDown(void) +{ + /** + * @tc.setup: Unregister a listener to the NotificationChain + */ + g_listener->Drop(); + g_listener = nullptr; + EXPECT_TRUE(g_notificationChain->UnRegisterEventType(COMMIT_EVENT) == E_OK); +} + +/** + * @tc.name: RegisterEvent001 + * @tc.desc: Register an exits event. + * @tc.type: FUNC + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBNotificationChainTest, RegisterEvent001, TestSize.Level0) +{ + /** + * @tc.steps: step1. call RegisterEventType to register a exist event + * @tc.expected: step1. function return -E_ALREADY_REGISTER + */ + int result = g_notificationChain->RegisterEventType(COMMIT_EVENT); + EXPECT_TRUE(result == -E_ALREADY_REGISTER); +} + +/** + * @tc.name: RegisterListener001 + * @tc.desc: Register and unregister a listener. + * @tc.type: FUNC + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBNotificationChainTest, RegisterListener001, TestSize.Level0) +{ + /** + * @tc.steps: step1. call RegisterListener to register a listener + * @tc.expected: step1. function return a not null listener + */ + int errCode = E_OK; + NotificationChain::Listener *listener = + g_notificationChain->RegisterListener(COMMIT_EVENT, g_onEventFunction, g_onFinalize, errCode); + EXPECT_TRUE(listener != nullptr); + + /** + * @tc.steps: step2. call Drop to unregister the listener + * @tc.expected: step2. function return E_OK + */ + int result = listener->Drop(); + EXPECT_TRUE(g_onFinalizeCalled); + EXPECT_TRUE(result == E_OK); +} + +/** + * @tc.name: NotifyEvent001 + * @tc.desc: notify an event to listener. + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBNotificationChainTest, NotifyEvent001, TestSize.Level0) +{ + int errCode = E_OK; + /** + * @tc.steps: step1. call RegisterListener to register a listener + * @tc.expected: step1. function return a not null listener + */ + NotificationChain::Listener *listener = + g_notificationChain->RegisterListener(COMMIT_EVENT, g_onEventFunction, g_onFinalize, errCode); + EXPECT_TRUE(listener != nullptr); + + /** + * @tc.steps: step2. call NotifyEvent to notify an event + * @tc.expected: step2. the listener's callback should be called + */ + int testNum = 2048; + g_notificationChain->NotifyEvent(COMMIT_EVENT, &testNum); + EXPECT_TRUE(g_onEventTestNum == testNum); + listener->Drop(); +} + +/** + * @tc.name: UnRegisterEvent001 + * @tc.desc: unregister a invalid event. + * @tc.type: FUNC + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBNotificationChainTest, UnRegisterEvent001, TestSize.Level0) +{ + /** + * @tc.steps: step1. UnRegisterEventType a invalid event + * @tc.expected: step1. function should return -E_NOT_FOUND + */ + int result = g_notificationChain->UnRegisterEventType(INVALID_EVENT); + EXPECT_EQ(result, -E_NOT_FOUND); +} diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_parcel_unit_test.cpp b/mock/distributeddb/test/unittest/common/common/distributeddb_parcel_unit_test.cpp new file mode 100644 index 00000000..ff527723 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_parcel_unit_test.cpp @@ -0,0 +1,680 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_tools_unit_test.h" + +#include + +#include "parcel.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +class DistributedDBParcelTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBParcelTest::SetUpTestCase(void) +{ +} + +void DistributedDBParcelTest::TearDownTestCase(void) +{ +} + +void DistributedDBParcelTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBParcelTest::TearDown(void) +{ +} + +/** + * @tc.name: WriteInt001 + * @tc.desc: write and read a integer. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteInt001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a vector, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + int writeData1 = INT_MAX; + uint32_t writeData2 = UINT32_MAX; + uint64_t writeData3 = 0; + + uint32_t len = Parcel::GetIntLen() + Parcel::GetUInt32Len() + Parcel::GetUInt64Len(); + len = Parcel::GetEightByteAlign(len); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteInt(writeData1); + EXPECT_TRUE(ret == E_OK); + ret = writeParcel.WriteUInt32(writeData2); + EXPECT_TRUE(ret == E_OK); + ret = writeParcel.WriteUInt64(writeData3); + EXPECT_TRUE(ret == E_OK); + // write overflow + ret = writeParcel.WriteUInt64(writeData3); + EXPECT_TRUE(ret != E_OK); + + /** + * @tc.steps: step2. read the vector, the vector should same as written vector; + * @tc.expected: step1. read out vector same as written vector; + */ + int readData1; + uint32_t readData2; + uint64_t readData3; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadInt(readData1); + EXPECT_TRUE(readLen == Parcel::GetIntLen()); + readLen += readParcel.ReadUInt32(readData2); + EXPECT_TRUE(readLen == Parcel::GetIntLen() + Parcel::GetUInt32Len()); + readLen += readParcel.ReadUInt64(readData3); + EXPECT_TRUE(readLen == Parcel::GetIntLen() + Parcel::GetUInt32Len() + Parcel::GetUInt64Len()); + EXPECT_TRUE(!readParcel.IsError()); + EXPECT_TRUE(readData1 == writeData1); + EXPECT_TRUE(readData2 == writeData2); + EXPECT_TRUE(readData3 == writeData3); + // read overflow + readLen = readParcel.ReadUInt64(readData3); + EXPECT_TRUE(readParcel.IsError()); + EXPECT_TRUE(readLen == 0); + delete []buf; +} + +/** + * @tc.name: WriteVector001 + * @tc.desc: write and read a vector. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteVector001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a vector, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + vector writeData = {1, 2, 5, 7, 20, 30, 99}; + uint32_t len = Parcel::GetVectorLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteVector(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step2. read the vector, the vector should same as written vector; + * @tc.expected: step1. read out vector same as written vector; + */ + vector readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadVector(readData); + EXPECT_TRUE(!readParcel.IsError()); + EXPECT_TRUE(readLen == len); + EXPECT_TRUE(readData.size() == writeData.size()); + EXPECT_TRUE(DistributedDBToolsUnitTest::CompareVector(writeData, readData)); + delete []buf; +} + +/** + * @tc.name: WriteVector002 + * @tc.desc: write and read an empty vector. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteVector002, TestSize.Level1) +{ + /** + * @tc.steps: step1. create an empty vector, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + vector writeData; + uint32_t len = Parcel::GetVectorLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteVector(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step2. read the vector, the vector should same as written vector; + * @tc.expected: step1. read out vector same as written vector; + */ + vector readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadVector(readData); + EXPECT_TRUE(!readParcel.IsError()); + EXPECT_TRUE(readLen == len); + EXPECT_TRUE(readData.size() == writeData.size()); + EXPECT_TRUE(DistributedDBToolsUnitTest::CompareVector(writeData, readData)); + delete []buf; +} + +/** + * @tc.name: WriteVector003 + * @tc.desc: write and read a vector. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteVector003, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a vector, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + vector writeData = {1, 2, 5, 7, 20, 30, 99, 0xffffffff, 0x5678, 0x98765432, 0xabcdef12}; + uint32_t len = Parcel::GetVectorLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteVector(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step2. read the vector, the vector should same as written vector; + * @tc.expected: step1. read out vector same as written vector; + */ + vector readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadVector(readData); + EXPECT_TRUE(!readParcel.IsError()); + EXPECT_TRUE(readLen == len); + EXPECT_TRUE(readData.size() == writeData.size()); + EXPECT_TRUE(DistributedDBToolsUnitTest::CompareVector(writeData, readData)); + delete []buf; +} + +/** + * @tc.name: WriteVector004 + * @tc.desc: write and read an empty vector. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteVector004, TestSize.Level1) +{ + /** + * @tc.steps: step1. create an empty vector, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + vector writeData; + uint32_t len = Parcel::GetVectorLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteVector(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step2. read the vector, the vector should same as written vector; + * @tc.expected: step1. read out vector same as written vector; + */ + vector readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadVector(readData); + EXPECT_TRUE(!readParcel.IsError()); + EXPECT_TRUE(readLen == len); + EXPECT_TRUE(readData.size() == writeData.size()); + EXPECT_TRUE(DistributedDBToolsUnitTest::CompareVector(writeData, readData)); + delete []buf; +} + +/** + * @tc.name: WriteVector005 + * @tc.desc: write and read a vector. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteVector005, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a vector, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + vector writeData = {1, 2, 5, 7, 20, 30, 99, 0xffffffffffffffff, + 0x5678, 0x98765432ffffffff, 0xabcdef1212345678}; + uint32_t len = Parcel::GetVectorLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteVector(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step2. read the vector, the vector should same as written vector; + * @tc.expected: step1. read out vector same as written vector; + */ + vector readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadVector(readData); + EXPECT_TRUE(!readParcel.IsError()); + EXPECT_TRUE(readLen == len); + EXPECT_TRUE(readData.size() == writeData.size()); + EXPECT_TRUE(DistributedDBToolsUnitTest::CompareVector(writeData, readData)); + delete []buf; +} + +/** + * @tc.name: WriteVector006 + * @tc.desc: write and read an empty vector. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteVector006, TestSize.Level1) +{ + /** + * @tc.steps: step1. create an empty vector, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + vector writeData; + uint32_t len = Parcel::GetVectorLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteVector(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step2. read the vector, the vector should same as written vector; + * @tc.expected: step1. read out vector same as written vector; + */ + vector readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadVector(readData); + EXPECT_TRUE(!readParcel.IsError()); + EXPECT_TRUE(readLen == len); + EXPECT_TRUE(readData.size() == writeData.size()); + EXPECT_TRUE(DistributedDBToolsUnitTest::CompareVector(writeData, readData)); + delete []buf; +} + +/** + * @tc.name: WriteVector007 + * @tc.desc: write and read a vector, insert a wrong len. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteVector007, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a vector, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + vector writeData = {1, 2, 5, 7, 20, 30, 99}; + uint32_t len = Parcel::GetVectorLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteVector(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step2. set a wrong len, the len is INT_MAX; + * @tc.expected: the vector should be empty, and isError should be true. + */ + *(reinterpret_cast(buf)) = HostToNet(static_cast(INT32_MAX)); + + /** + * @tc.steps: step3. read the vector + * @tc.expected: the vector should be empty, and isError should be true. + */ + vector readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadVector(readData); + EXPECT_TRUE(readParcel.IsError()); + EXPECT_TRUE(readLen == 0); + EXPECT_TRUE(readData.size() == 0); + delete []buf; +} + +/** + * @tc.name: WriteVector008 + * @tc.desc: write and read a vector, insert a wrong len. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteVector008, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a vector, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + vector writeData = {1, 2, 5, 7, 20, 30, 99, 0xff, 0xff, 0x1f, 0xab, 0x45}; + uint32_t len = Parcel::GetVectorLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteVector(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step2. set a wrong len, the len is bigger than it should be; + * @tc.expected: the vector should be empty, and isError should be true. + */ + *(reinterpret_cast(buf)) = HostToNet(static_cast(writeData.size()) + 1); + + /** + * @tc.steps: step3. read the vector + * @tc.expected: the vector should be empty, and isError should be true. + */ + vector readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadVector(readData); + EXPECT_TRUE(readParcel.IsError()); + EXPECT_TRUE(readLen == 0); + EXPECT_TRUE(readData.size() == 0); + delete []buf; +} + +/** + * @tc.name: WriteVector009 + * @tc.desc: write and read a vector, insert a wrong len. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteVector009, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a vector, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + vector writeData = {1, 2, 5, 7, 20, 30, 99}; + uint32_t len = Parcel::GetVectorLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteVector(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step2. set a wrong len, the len is smaller than it should be; + * @tc.expected: the vector should be empty, and isError should be true; + */ + *(reinterpret_cast(buf)) = HostToNet(static_cast(writeData.size()) - 1); + + /** + * @tc.steps: step3. read the vector + * @tc.expected: the vector should be same as writeData.sub(0, len - 1); + */ + vector readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadVector(readData); + EXPECT_TRUE(!readParcel.IsError()); + EXPECT_TRUE(readLen != 0); + EXPECT_TRUE(readData.size() == writeData.size() - 1); + EXPECT_TRUE(DistributedDBToolsUnitTest::CompareVectorN(readData, writeData, writeData.size() - 1)); + delete []buf; +} + +#ifndef LOW_LEVEL_MEM_DEV +/** + * @tc.name: WriteVector010 + * @tc.desc: write and read a vector, vector len is INT_MAX. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteVector010, TestSize.Level2) +{ + /** + * @tc.steps: step1. create a vector, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + vector writeData(INT32_MAX, 0xff); + uint32_t len = Parcel::GetVectorLen(writeData); + EXPECT_TRUE(len == 0); + len = Parcel::GetEightByteAlign(static_cast(INT32_MAX) + 4); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteVector(writeData); + delete []buf; + EXPECT_TRUE(ret != EOK); +} +#endif + +/** + * @tc.name: WriteString001 + * @tc.desc: write and read a string normally. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteString001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a string, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + string writeData("abcd1234ffff234"); + uint32_t len = Parcel::GetStringLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteString(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step3. read the string + * @tc.expected: the string should be read correctly; + */ + string readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadString(readData); + EXPECT_TRUE(!readParcel.IsError()); + EXPECT_TRUE(readLen == len); + EXPECT_TRUE(readData == writeData); + delete []buf; +} + +/** + * @tc.name: WriteString002 + * @tc.desc: write and read a string, insert a wrong len. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteString002, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a string, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + string writeData("abcd1234ffff234"); + uint32_t len = Parcel::GetStringLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteString(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step2. set a wrong len, the len is smaller than it should be; + * @tc.expected: the vector should be empty, and isError should be true; + */ + *(reinterpret_cast(buf)) = HostToNet(static_cast(writeData.size()) - 1); + + /** + * @tc.steps: step3. read the string + * @tc.expected: the string should be read correctly; + */ + string readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadString(readData); + EXPECT_TRUE(!readParcel.IsError()); + EXPECT_TRUE(readLen != 0); + EXPECT_TRUE(readData == writeData.substr(0, writeData.size() - 1)); + delete []buf; +} + +/** + * @tc.name: WriteString003 + * @tc.desc: write and read a string, insert a wrong len. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteString003, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a string, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + string writeData("abcd1234ffff2349poff"); + uint32_t len = Parcel::GetStringLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteString(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step2. set a wrong len, the len is bigger than it should be; + * @tc.expected: the string should be empty, and isError should be true; + */ + *(reinterpret_cast(buf)) = HostToNet(static_cast(writeData.size()) + 1); + + /** + * @tc.steps: step3. read the string + * @tc.expected: the string should be read with error; + */ + string readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadString(readData); + EXPECT_TRUE(readParcel.IsError()); + EXPECT_TRUE(readLen == 0); + EXPECT_TRUE(readData.size()== 0); + delete []buf; +} + +/** + * @tc.name: WriteString004 + * @tc.desc: write and read a string, string is empty. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteString004, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a string, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + string writeData; + uint32_t len = Parcel::GetStringLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteString(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step3. read the string + * @tc.expected: the string should be read with error; + */ + string readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadString(readData); + EXPECT_TRUE(!readParcel.IsError()); + EXPECT_TRUE(readLen != 0); + EXPECT_TRUE(readData.size()== 0); + delete []buf; +} + +/** + * @tc.name: WriteString005 + * @tc.desc: write and read a string, insert a INT_MAX len. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteString005, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a string, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + string writeData; + uint32_t len = Parcel::GetStringLen(writeData); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteString(writeData); + EXPECT_TRUE(ret == EOK); + + /** + * @tc.steps: step2. set a wrong len, the len is INT32_MAX; + * @tc.expected: the string should be empty, and isError should be true; + */ + *(reinterpret_cast(buf)) = HostToNet(static_cast(INT32_MAX)); + + /** + * @tc.steps: step3. read the string + * @tc.expected: the string should be read with error; + */ + string readData; + Parcel readParcel(buf, len); + uint32_t readLen = readParcel.ReadString(readData); + EXPECT_TRUE(readParcel.IsError()); + EXPECT_TRUE(readLen == 0); + EXPECT_TRUE(readData.size() == 0); + delete []buf; +} +#ifndef LOW_LEVEL_MEM_DEV +/** + * @tc.name: WriteString006 + * @tc.desc: write and read a string, string size is INT_MAX. + * @tc.type: FUNC + * @tc.require: AR000CQE0U + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBParcelTest, WriteString006, TestSize.Level2) +{ + /** + * @tc.steps: step1. create a string, and write it into a buffer; + * @tc.expected: step1. write ok; + */ + string writeData(INT32_MAX, 'z'); + uint32_t len = Parcel::GetStringLen(writeData); + EXPECT_TRUE(len == 0); + len = Parcel::GetEightByteAlign(static_cast(INT32_MAX) + 4); + uint8_t *buf = new (nothrow) uint8_t[len]; + ASSERT_NE(buf, nullptr); + Parcel writeParcel(buf, len); + int ret = writeParcel.WriteString(writeData); + delete []buf; + EXPECT_TRUE(ret != EOK); +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_relational_schema_object_test.cpp b/mock/distributeddb/test/unittest/common/common/distributeddb_relational_schema_object_test.cpp new file mode 100644 index 00000000..2192f068 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_relational_schema_object_test.cpp @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 OMIT_JSON +#include +#include + +#include "db_errno.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" +#include "relational_schema_object.h" +#include "schema_utils.h" +#include "schema_constant.h" +#include "schema_negotiate.h" + +using namespace std; +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { + const std::string NORMAL_SCHEMA = R""({ + "SCHEMA_VERSION": "2.0", + "SCHEMA_TYPE": "RELATIVE", + "TABLES": [{ + "NAME": "FIRST", + "DEFINE": { + "field_name1": { + "COLUMN_ID":1, + "TYPE": "STRING", + "NOT_NULL": true, + "DEFAULT": "abcd" + }, + "field_name2": { + "COLUMN_ID":2, + "TYPE": "MYINT(21)", + "NOT_NULL": false, + "DEFAULT": "222" + }, + "field_name3": { + "COLUMN_ID":3, + "TYPE": "INTGER", + "NOT_NULL": false, + "DEFAULT": "1" + } + }, + "AUTOINCREMENT": true, + "UNIQUE": ["field_name1", ["field_name2", "field_name3"]], + "PRIMARY_KEY": "field_name1", + "INDEX": { + "index_name1": ["field_name1", "field_name2"], + "index_name2": ["field_name3"] + } + }, { + "NAME": "SECOND", + "DEFINE": { + "key": { + "COLUMN_ID":1, + "TYPE": "BLOB", + "NOT_NULL": true + }, + "value": { + "COLUMN_ID":2, + "TYPE": "BLOB", + "NOT_NULL": false + } + }, + "PRIMARY_KEY": "field_name1" + }] + })""; + + const std::string INVALID_SCHEMA = R""({ + "SCHEMA_VERSION": "2.0", + "SCHEMA_TYPE": "RELATIVE", + "TABLES": [{ + "NAME": "FIRST", + "DEFINE": { + "field_name1": { + "COLUMN_ID":1, + "TYPE": "STRING", + "NOT_NULL": true, + "DEFAULT": "abcd" + },"field_name2": { + "COLUMN_ID":2, + "TYPE": "MYINT(21)", + "NOT_NULL": false, + "DEFAULT": "222" + } + }, + "PRIMARY_KEY": "field_name1" + }] + })""; + + const std::string INVALID_JSON_STRING = R""({ + "SCHEMA_VERSION": "2.0", + "SCHEMA_TYPE": "RELATIVE", + "TABLES": [{ + "NAME": "FIRST", + "DEFINE": { + "field_name1": {)""; + + const std::string SCHEMA_VERSION_STR_1 = R"("SCHEMA_VERSION": "1.0",)"; + const std::string SCHEMA_VERSION_STR_2 = R"("SCHEMA_VERSION": "2.0",)"; + const std::string SCHEMA_VERSION_STR_INVALID = R"("SCHEMA_VERSION": "awd3",)"; + const std::string SCHEMA_TYPE_STR_NONE = R"("SCHEMA_TYPE": "NONE",)"; + const std::string SCHEMA_TYPE_STR_JSON = R"("SCHEMA_TYPE": "JSON",)"; + const std::string SCHEMA_TYPE_STR_FLATBUFFER = R"("SCHEMA_TYPE": "FLATBUFFER",)"; + const std::string SCHEMA_TYPE_STR_RELATIVE = R"("SCHEMA_TYPE": "RELATIVE",)"; + const std::string SCHEMA_TYPE_STR_INVALID = R"("SCHEMA_TYPE": "adewaaSAD",)"; + + const std::string SCHEMA_TABLE_STR = R""("TABLES": [{ + "NAME": "FIRST", + "DEFINE": { + "field_name1": { + "COLUMN_ID":1, + "TYPE": "STRING", + "NOT_NULL": true, + "DEFAULT": "abcd" + },"field_name2": { + "COLUMN_ID":2, + "TYPE": "MYINT(21)", + "NOT_NULL": false, + "DEFAULT": "222" + } + }, + "PRIMARY_KEY": "field_name1" + }])""; + + const std::string TABLE_DEFINE_STR = R""({ + "NAME": "FIRST", + "DEFINE": { + "field_name1": { + "COLUMN_ID":1, + "TYPE": "STRING", + "NOT_NULL": true, + "DEFAULT": "abcd" + },"field_name2": { + "COLUMN_ID":2, + "TYPE": "MYINT(21)", + "NOT_NULL": false, + "DEFAULT": "222" + } + }, + "PRIMARY_KEY": "field_name1" + })""; + + const std::string TABLE_DEFINE_STR_NAME = R""("NAME": "FIRST",)""; + const std::string TABLE_DEFINE_STR_NAME_INVALID = R"("NAME": 123,)"; + const std::string TABLE_DEFINE_STR_FIELDS = R""("DEFINE": { + "field_name1": { + "COLUMN_ID":1, + "TYPE": "STRING", + "NOT_NULL": true, + "DEFAULT": "abcd" + },"field_name2": { + "COLUMN_ID":2, + "TYPE": "MYINT(21)", + "NOT_NULL": false, + "DEFAULT": "222" + } + },)""; + const std::string TABLE_DEFINE_STR_FIELDS_EMPTY = R""("DEFINE": {},)""; + const std::string TABLE_DEFINE_STR_FIELDS_NOTYPE = R""("DEFINE": { + "field_name1": { + "COLUMN_ID":1, + "NOT_NULL": true, + "DEFAULT": "abcd" + }},)""; + const std::string TABLE_DEFINE_STR_KEY = R""("PRIMARY_KEY": "field_name1")""; + const std::string TABLE_DEFINE_STR_KEY_INVALID = R""("PRIMARY_KEY": false)""; +} + +class DistributedDBRelationalSchemaObjectTest : public testing::Test { +public: + static void SetUpTestCase(void) {}; + static void TearDownTestCase(void) {}; + void SetUp() override; + void TearDown() override {}; +}; + +void DistributedDBRelationalSchemaObjectTest::SetUp() +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +/** + * @tc.name: RelationalSchemaParseTest001 + * @tc.desc: Test relational schema parse from json string + * @tc.type: FUNC + * @tc.require: AR000GK58I + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBRelationalSchemaObjectTest, RelationalSchemaParseTest001, TestSize.Level1) +{ + const std::string schemaStr = NORMAL_SCHEMA; + RelationalSchemaObject schemaObj; + int errCode = schemaObj.ParseFromSchemaString(schemaStr); + EXPECT_EQ(errCode, E_OK); + + RelationalSchemaObject schemaObj2; + schemaObj2.ParseFromSchemaString(schemaObj.ToSchemaString()); + EXPECT_EQ(errCode, E_OK); +} + +/** + * @tc.name: RelationalSchemaParseTest002 + * @tc.desc: Test relational schema parse from invalid json string + * @tc.type: FUNC + * @tc.require: AR000GK58I + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBRelationalSchemaObjectTest, RelationalSchemaParseTest002, TestSize.Level1) +{ + RelationalSchemaObject schemaObj; + + std::string schemaStr01(SchemaConstant::SCHEMA_STRING_SIZE_LIMIT + 1, 's'); + int errCode = schemaObj.ParseFromSchemaString(schemaStr01); + EXPECT_EQ(errCode, -E_INVALID_ARGS); + + errCode = schemaObj.ParseFromSchemaString(INVALID_JSON_STRING); + EXPECT_EQ(errCode, -E_JSON_PARSE_FAIL); + + std::string noVersion = "{" + SCHEMA_TYPE_STR_RELATIVE + SCHEMA_TABLE_STR + "}"; + errCode = schemaObj.ParseFromSchemaString(noVersion); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + std::string invalidVersion1 = "{" + SCHEMA_VERSION_STR_1 + SCHEMA_TYPE_STR_RELATIVE + SCHEMA_TABLE_STR + "}"; + errCode = schemaObj.ParseFromSchemaString(invalidVersion1); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + std::string invalidVersion2 = "{" + SCHEMA_VERSION_STR_INVALID + SCHEMA_TYPE_STR_RELATIVE + SCHEMA_TABLE_STR + "}"; + errCode = schemaObj.ParseFromSchemaString(invalidVersion2); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + std::string noType = "{" + SCHEMA_VERSION_STR_2 + SCHEMA_TABLE_STR + "}"; + errCode = schemaObj.ParseFromSchemaString(noType); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + std::string invalidType1 = "{" + SCHEMA_VERSION_STR_2 + SCHEMA_TYPE_STR_NONE + SCHEMA_TABLE_STR + "}"; + errCode = schemaObj.ParseFromSchemaString(invalidType1); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + std::string invalidType2 = "{" + SCHEMA_VERSION_STR_2 + SCHEMA_TYPE_STR_JSON + SCHEMA_TABLE_STR + "}"; + errCode = schemaObj.ParseFromSchemaString(invalidType2); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + std::string invalidType3 = "{" + SCHEMA_VERSION_STR_2 + SCHEMA_TYPE_STR_FLATBUFFER + SCHEMA_TABLE_STR + "}"; + errCode = schemaObj.ParseFromSchemaString(invalidType3); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + std::string invalidType4 = "{" + SCHEMA_VERSION_STR_2 + SCHEMA_TYPE_STR_INVALID + SCHEMA_TABLE_STR + "}"; + errCode = schemaObj.ParseFromSchemaString(invalidType4); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + std::string noTable = "{" + SCHEMA_VERSION_STR_2 + + SCHEMA_TYPE_STR_RELATIVE.substr(0, SCHEMA_TYPE_STR_RELATIVE.length() - 1) + "}"; + errCode = schemaObj.ParseFromSchemaString(noTable); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); +} + +namespace { +std::string GenerateFromTableStr(const std::string &tableStr) +{ + return R""({ + "SCHEMA_VERSION": "2.0", + "SCHEMA_TYPE": "RELATIVE", + "TABLES": )"" + tableStr + "}"; +} +} + +/** + * @tc.name: RelationalSchemaParseTest003 + * @tc.desc: Test relational schema parse from invalid json string + * @tc.type: FUNC + * @tc.require: AR000GK58I + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBRelationalSchemaObjectTest, RelationalSchemaParseTest003, TestSize.Level1) +{ + RelationalSchemaObject schemaObj; + int errCode = E_OK; + + errCode = schemaObj.ParseFromSchemaString(GenerateFromTableStr(TABLE_DEFINE_STR)); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + std::string invalidTableStr01 = "{" + TABLE_DEFINE_STR_FIELDS + TABLE_DEFINE_STR_KEY + "}"; + errCode = schemaObj.ParseFromSchemaString(GenerateFromTableStr("[" + invalidTableStr01 + "]")); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + std::string invalidTableStr02 = "{" + TABLE_DEFINE_STR_NAME_INVALID + TABLE_DEFINE_STR_FIELDS + + TABLE_DEFINE_STR_KEY + "}"; + errCode = schemaObj.ParseFromSchemaString(GenerateFromTableStr("[" + invalidTableStr02 + "]")); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + std::string invalidTableStr04 = "{" + TABLE_DEFINE_STR_NAME + TABLE_DEFINE_STR_FIELDS_NOTYPE + + TABLE_DEFINE_STR_KEY + "}"; + errCode = schemaObj.ParseFromSchemaString(GenerateFromTableStr("[" + invalidTableStr04 + "]")); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + std::string invalidTableStr05 = "{" + TABLE_DEFINE_STR_NAME + TABLE_DEFINE_STR_FIELDS + + TABLE_DEFINE_STR_KEY_INVALID + "}"; + errCode = schemaObj.ParseFromSchemaString(GenerateFromTableStr("[" + invalidTableStr05 + "]")); + EXPECT_EQ(errCode, -E_SCHEMA_PARSE_FAIL); + + errCode = schemaObj.ParseFromSchemaString(""); + EXPECT_EQ(errCode, -E_INVALID_ARGS); +} + +/** + * @tc.name: RelationalSchemaCompareTest001 + * @tc.desc: Test relational schema negotiate with same schema string + * @tc.type: FUNC + * @tc.require: AR000GK58I + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBRelationalSchemaObjectTest, RelationalSchemaCompareTest001, TestSize.Level1) +{ + RelationalSchemaObject schemaObj; + int errCode = schemaObj.ParseFromSchemaString(NORMAL_SCHEMA); + EXPECT_EQ(errCode, E_OK); + + RelationalSyncOpinion opinion = SchemaNegotiate::MakeLocalSyncOpinion(schemaObj, NORMAL_SCHEMA, + static_cast(SchemaType::RELATIVE)); + EXPECT_EQ(opinion.at("FIRST").permitSync, true); + EXPECT_EQ(opinion.at("FIRST").checkOnReceive, false); + EXPECT_EQ(opinion.at("FIRST").requirePeerConvert, false); +} + +/** + * @tc.name: RelationalTableCompareTest001 + * @tc.desc: Test relational schema negotiate with same schema string + * @tc.type: FUNC + * @tc.require: AR000GK58I + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBRelationalSchemaObjectTest, RelationalTableCompareTest001, TestSize.Level1) +{ + RelationalSchemaObject schemaObj; + int errCode = schemaObj.ParseFromSchemaString(NORMAL_SCHEMA); + EXPECT_EQ(errCode, E_OK); + TableInfo table1 = schemaObj.GetTable("FIRST"); + TableInfo table2 = schemaObj.GetTable("FIRST"); + EXPECT_EQ(table1.CompareWithTable(table2), -E_RELATIONAL_TABLE_EQUAL); + + table2.AddIndexDefine("indexname", {"field_name2", "field_name1"}); + EXPECT_EQ(table1.CompareWithTable(table2), -E_RELATIONAL_TABLE_COMPATIBLE); + + TableInfo table3 = schemaObj.GetTable("SECOND"); + EXPECT_EQ(table1.CompareWithTable(table3), -E_RELATIONAL_TABLE_INCOMPATIBLE); + + TableInfo table4 = schemaObj.GetTable("FIRST"); + table4.AddField(table3.GetFields().at("value")); + EXPECT_EQ(table1.CompareWithTable(table4), -E_RELATIONAL_TABLE_COMPATIBLE_UPGRADE); + + TableInfo table5 = schemaObj.GetTable("FIRST"); + table5.AddField(table3.GetFields().at("key")); + EXPECT_EQ(table1.CompareWithTable(table5), -E_RELATIONAL_TABLE_INCOMPATIBLE); +} + +/** + * @tc.name: RelationalSchemaOpinionTest001 + * @tc.desc: Test relational schema sync opinion + * @tc.type: FUNC + * @tc.require: AR000GK58I + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBRelationalSchemaObjectTest, RelationalSchemaOpinionTest001, TestSize.Level1) +{ + RelationalSyncOpinion opinion; + opinion["table_1"] = SyncOpinion {true, false, false}; + opinion["table_2"] = SyncOpinion {false, true, false}; + opinion["table_3"] = SyncOpinion {false, false, true}; + + uint32_t len = SchemaNegotiate::CalculateParcelLen(opinion); + std::vector buff(len, 0); + Parcel writeParcel(buff.data(), len); + int errCode = SchemaNegotiate::SerializeData(opinion, writeParcel); + EXPECT_EQ(errCode, E_OK); + + Parcel readParcel(buff.data(), len); + RelationalSyncOpinion opinionRecv; + errCode = SchemaNegotiate::DeserializeData(readParcel, opinionRecv); + EXPECT_EQ(errCode, E_OK); + + EXPECT_EQ(opinion.size(), opinionRecv.size()); + for (const auto &it : opinion) { + SyncOpinion tableOpinionRecv = opinionRecv.at(it.first); + EXPECT_EQ(it.second.permitSync, tableOpinionRecv.permitSync); + EXPECT_EQ(it.second.requirePeerConvert, tableOpinionRecv.requirePeerConvert); + } +} + +/** + * @tc.name: RelationalSchemaNegotiateTest001 + * @tc.desc: Test relational schema negotiate + * @tc.type: FUNC + * @tc.require: AR000GK58I + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBRelationalSchemaObjectTest, RelationalSchemaNegotiateTest001, TestSize.Level1) +{ + RelationalSyncOpinion localOpinion; + localOpinion["table_1"] = SyncOpinion {true, false, false}; + localOpinion["table_2"] = SyncOpinion {false, true, false}; + localOpinion["table_3"] = SyncOpinion {false, false, true}; + + RelationalSyncOpinion remoteOpinion; + remoteOpinion["table_2"] = SyncOpinion {true, false, false}; + remoteOpinion["table_3"] = SyncOpinion {false, true, false}; + remoteOpinion["table_4"] = SyncOpinion {false, false, true}; + RelationalSyncStrategy strategy = SchemaNegotiate::ConcludeSyncStrategy(localOpinion, remoteOpinion); + + EXPECT_EQ(strategy.size(), 2u); + EXPECT_EQ(strategy.at("table_2").permitSync, true); + EXPECT_EQ(strategy.at("table_3").permitSync, false); +} + +/** + * @tc.name: TableCompareTest001 + * @tc.desc: Test table compare + * @tc.type: FUNC + * @tc.require: AR000GK58I + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBRelationalSchemaObjectTest, TableCompareTest001, TestSize.Level1) +{ + FieldInfo field1; + field1.SetFieldName("a"); + FieldInfo field2; + field2.SetFieldName("b"); + FieldInfo field3; + field3.SetFieldName("c"); + FieldInfo field4; + field4.SetFieldName("d"); + + TableInfo table; + table.AddField(field2); + table.AddField(field3); + + TableInfo inTable1; + inTable1.AddField(field1); + inTable1.AddField(field2); + inTable1.AddField(field3); + EXPECT_EQ(table.CompareWithTable(inTable1), -E_RELATIONAL_TABLE_COMPATIBLE_UPGRADE); + + TableInfo inTable2; + inTable2.AddField(field1); + inTable2.AddField(field2); + inTable2.AddField(field4); + EXPECT_EQ(table.CompareWithTable(inTable2), -E_RELATIONAL_TABLE_INCOMPATIBLE); + + TableInfo inTable3; + inTable3.AddField(field3); + inTable3.AddField(field2); + EXPECT_EQ(table.CompareWithTable(inTable3), -E_RELATIONAL_TABLE_EQUAL); +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_schema_object_test.cpp b/mock/distributeddb/test/unittest/common/common/distributeddb_schema_object_test.cpp new file mode 100644 index 00000000..f7520db5 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_schema_object_test.cpp @@ -0,0 +1,1090 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_JSON +#include +#include + +#include "db_errno.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" +#include "schema_constant.h" +#include "schema_object.h" +#include "schema_utils.h" + +using namespace std; +using namespace testing::ext; +using namespace DistributedDB; + +namespace { + const std::string VALID_SCHEMA_FULL_DEFINE = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":{" + "\"field_name3\":\"INTEGER, NOT NULL\"," + "\"field_name4\":\"LONG, DEFAULT 100\"," + "\"field_name5\":\"DOUBLE, NOT NULL, DEFAULT 3.14\"," + "\"field_name6\":\"STRING, NOT NULL, DEFAULT '3.1415'\"," + "\"field_name7\":[]," + "\"field_name8\":{}" + "}" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\", \"$.field_name2.field_name6\"]}"; + const std::string VALID_SCHEMA_INDEX_EMPTY = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"" + "}," + "\"SCHEMA_INDEXES\":[]}"; + const std::string VALID_SCHEMA_NO_INDEX = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"" + "}}"; + const std::string VALID_SCHEMA_PRE_SUF_BLANK = "{\"SCHEMA_VERSION\":\" 1.0\"," + "\"SCHEMA_MODE\":\"STRICT \"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\" BOOL \"" + "}}"; + + const std::string INVALID_SCHEMA_INVALID_JSON = "[\"$.field_name1\", \"$.field_name2.field_name6\"]"; + const std::string INVALID_SCHEMA_LESS_META_FIELD = "{\"SCHEMA_VERSION\":\" 1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"}"; + const std::string INVALID_SCHEMA_MORE_META_FIELD = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"" + "}," + "\"SCHEMA_UNDEFINE_META_FIELD\":[]}"; + const std::string INVALID_SCHEMA_WRONG_VERSION = "{\"SCHEMA_VERSION\":\"1.1\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"" + "}}"; + const std::string INVALID_SCHEMA_WRONG_MODE = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"WRONG_MODE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"" + "}}"; + + const std::string INVALID_SCHEMA_DEFINE_EMPTY = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{}}"; + const std::string INVALID_SCHEMA_DEFINE_NEST_TOO_DEEP = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":{" + "\"field_name2\":{" + "\"field_name3\":{" + "\"field_name4\":{" + "\"field_name5\":{" + "}}}}}}}"; + const std::string INVALID_SCHEMA_DEFINE_INVALID_FIELD_NAME = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"12345\":\"BOOL\"" + "}}"; + const std::string INVALID_SCHEMA_DEFINE_INVALID_FIELD_ATTR = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL, DEFAULT null\"" + "}}"; + const std::string INVALID_SCHEMA_DEFINE_INVALID_ARRAY_TYPE = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":[3.14]" + "}}"; + + const std::string INVALID_SCHEMA_INDEX_INVALID = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"" + "}," + "\"SCHEMA_INDEXES\":[true, false]}"; + const std::string INVALID_SCHEMA_INDEX_PATH_INVALID = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"" + "}," + "\"SCHEMA_INDEXES\":[\".field_name1\"]}"; + const std::string INVALID_SCHEMA_INDEX_PATH_NOT_EXIST = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name2\"]}"; + const std::string INVALID_SCHEMA_INDEX_PATH_NOT_INDEXABLE = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":[]" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\"]}"; + const std::string INVALID_SCHEMA_INDEX_PATH_DUPLICATE = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\", \"$.field_name1\"]}"; + + const std::string SCHEMA_COMPARE_BASELINE = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL\"," + "\"field_name2\":\"INTEGER, DEFAULT 100\"," + "\"field_name3\":{" + "\"field_name4\":\"STRING\"," + "\"field_name5\":{}" + "}}," + "\"SCHEMA_INDEXES\":[\"field_name1\"]}"; + const std::string SCHEMA_DEFINE_MORE_FIELD = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL\"," + "\"field_name2\":\"INTEGER, DEFAULT 100\"," + "\"field_name3\":{" + "\"field_name4\":\"STRING\"," + "\"field_more1\":\"LONG\"," + "\"field_name5\":{" + "\"field_more2\":\"DOUBLE\"" + "}}}," + "\"SCHEMA_INDEXES\":[\"field_name1\"]}"; + const std::string SCHEMA_DEFINE_MORE_FIELD_NOTNULL_FORBID = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL\"," + "\"field_name2\":\"INTEGER, DEFAULT 100\"," + "\"field_name3\":{" + "\"field_name4\":\"STRING\"," + "\"field_more1\":\"LONG\"," + "\"field_name5\":{" + "\"field_more2\":\"DOUBLE, NOT NULL\"" + "}}}," + "\"SCHEMA_INDEXES\":[\"field_name1\"]}"; + const std::string SCHEMA_DEFINE_MORE_FIELD_NOTNULL_PERMIT = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL\"," + "\"field_name2\":\"INTEGER, DEFAULT 100\"," + "\"field_name3\":{" + "\"field_name4\":\"STRING\"," + "\"field_more1\":\"LONG, NOT NULL, DEFAULT 88\"," + "\"field_name5\":{" + "\"field_more2\":\"DOUBLE\"" + "}}}," + "\"SCHEMA_INDEXES\":[\"field_name1\"]}"; + const std::string SCHEMA_DEFINE_LESS_FIELD = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL\"," + "\"field_name2\":\"INTEGER, DEFAULT 100\"," + "\"field_name3\":{" + "\"field_name5\":{}" + "}}," + "\"SCHEMA_INDEXES\":[\"field_name1\"]}"; + const std::string SCHEMA_INDEX_MORE = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL\"," + "\"field_name2\":\"INTEGER, DEFAULT 100\"," + "\"field_name3\":{" + "\"field_name4\":\"STRING\"," + "\"field_name5\":{}" + "}}," + "\"SCHEMA_INDEXES\":[\"field_name1\", [\"field_name2\", \"field_name3.field_name4\"]]}"; + const std::string SCHEMA_INDEX_LESS = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL\"," + "\"field_name2\":\"INTEGER, DEFAULT 100\"," + "\"field_name3\":{" + "\"field_name4\":\"STRING\"," + "\"field_name5\":{}" + "}}," + "\"SCHEMA_INDEXES\":[]}"; + const std::string SCHEMA_INDEX_CHANGE = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL\"," + "\"field_name2\":\"INTEGER, DEFAULT 100\"," + "\"field_name3\":{" + "\"field_name4\":\"STRING\"," + "\"field_name5\":{}" + "}}," + "\"SCHEMA_INDEXES\":[\"field_name1\", [\"field_name2\", \"field_name1\", \"field_name3.field_name4\"]]}"; + const std::string SCHEMA_DEFINE_MORE_FIELD_MORE_INDEX = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL\"," + "\"field_name2\":\"INTEGER, DEFAULT 100\"," + "\"field_name3\":{" + "\"field_name4\":\"STRING\"," + "\"field_name5\":{" + "\"field_more1\":\"DOUBLE\"" + "}}}," + "\"SCHEMA_INDEXES\":[\"field_name1\", [\"field_name2\", \"field_name3.field_name4\"]]}"; + const std::string SCHEMA_SKIPSIZE_DIFFER = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_SKIPSIZE\":1," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL\"," + "\"field_name2\":\"INTEGER, DEFAULT 100\"," + "\"field_name3\":{" + "\"field_name4\":\"STRING\"," + "\"field_name5\":{}" + "}}," + "\"SCHEMA_INDEXES\":[\"field_name1\"]}"; + const std::string SCHEMA_DEFINE_TYPE_DIFFER = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL\"," + "\"field_name2\":\"INTEGER, DEFAULT 100\"," + "\"field_name3\":{" + "\"field_name4\":\"DOUBLE\"," + "\"field_name5\":{}" + "}}," + "\"SCHEMA_INDEXES\":[\"field_name1\"]}"; + const std::string SCHEMA_DEFINE_NOTNULL_DIFFER = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":\"INTEGER, DEFAULT 100\"," + "\"field_name3\":{" + "\"field_name4\":\"STRING\"," + "\"field_name5\":{}" + "}}," + "\"SCHEMA_INDEXES\":[\"field_name1\"]}"; + const std::string SCHEMA_DEFINE_DEFAULT_DIFFER = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL, NOT NULL\"," + "\"field_name2\":\"INTEGER, DEFAULT 88\"," + "\"field_name3\":{" + "\"field_name4\":\"STRING\"," + "\"field_name5\":{}" + "}}," + "\"SCHEMA_INDEXES\":[\"field_name1\"]}"; + + // Compare with VALID_SCHEMA_FULL_DEFINE + const std::string VALUE_LESS_FIELD = "{\"field_name1\":true," + "\"field_name2\":{" + "\"field_name3\":100," + "\"field_name8\":{" + "\"field_name9\":200" + "}" + "}}"; + const std::string VALUE_MORE_FIELD = "{\"field_name1\":true," + "\"field_name2\":{" + "\"field_name3\":100," + "\"field_name4\":8589934592," + "\"field_name5\":3.14," + "\"field_name6\":\"3.1415926\"," + "\"field_name7\":[true,1,\"inArray\"]," + "\"field_name8\":{" + "\"field_name9\":200" + "}," + "\"field_name10\":300" + "}}"; + const std::string VALUE_TYPE_MISMATCH = "{\"field_name1\":true," + "\"field_name2\":{" + "\"field_name3\":8589934592," + "\"field_name4\":100," + "\"field_name5\":3.14," + "\"field_name6\":\"3.1415926\"," + "\"field_name7\":[true,1,\"inArray\"]," + "\"field_name8\":{" + "\"field_name9\":200" + "}" + "}}"; + const std::string VALUE_NOT_NULL_VIOLATION = "{\"field_name1\":true," + "\"field_name2\":{" + "\"field_name3\":null," + "\"field_name4\":8589934592," + "\"field_name5\":3.14," + "\"field_name6\":\"3.1415926\"," + "\"field_name7\":[true,1,\"inArray\"]," + "\"field_name8\":{" + "\"field_name9\":200" + "}" + "}}"; + const std::string VALUE_MATCH_STRICT_SCHEMA = "{\"field_name1\":true," + "\"field_name2\":{" + "\"field_name3\":100," + "\"field_name4\":8589934592," + "\"field_name5\":3.14," + "\"field_name6\":\"3.1415926\"," + "\"field_name7\":[true,1,\"inArray\"]," + "\"field_name8\":{" + "\"field_name9\":200" + "}" + "}}"; + + // For test lacking field. + const std::string SCHEMA_FOR_TEST_NOTNULL_AND_DEFAULT = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"no_notnull_no_default\":\"BOOL\"," + "\"level_0_nest_0\":{" + "\"has_notnull_no_default\":\"INTEGER, NOT NULL\"," + "\"level_1_nest_0\":{" + "\"no_notnull_has_default\":\"LONG, DEFAULT 100\"," + "\"has_notnull_has_default\":\"DOUBLE, NOT NULL, DEFAULT 3.14\"," + "\"level_2_nest_0\":{" + "\"extra_0\":\"STRING, NOT NULL, DEFAULT '3.1415'\"," + "\"extra_1\":\"DOUBLE\"," + "\"extra_2\":[]" + "}," + "\"level_2_nest_1\":{" + "\"extra_3\":\"STRING\"," + "\"extra_4\":{}" + "}" + "}" + "}" + "}}"; + const std::string VALUE_NO_LACK_FIELD = "{" + "\"no_notnull_no_default\":true," + "\"level_0_nest_0\":{" + "\"has_notnull_no_default\":10010," + "\"level_1_nest_0\":{" + "\"no_notnull_has_default\":10086," + "\"has_notnull_has_default\":1.38064," + "\"level_2_nest_0\":{" + "\"extra_0\":\"BLOOM\"," + "\"extra_1\":2.71828," + "\"extra_2\":[]" + "}," + "\"level_2_nest_1\":{" + "\"extra_3\":\"Prejudice\"," + "\"extra_4\":{}" + "}" + "}" + "}}"; + const std::string VALUE_LACK_LEVEL_0_NEST_0 = "{\"no_notnull_no_default\":true}"; + const std::string VALUE_LEVEL_0_NEST_0_NOT_OBJECT = "{\"no_notnull_no_default\":true,\"level_0_nest_0\":1}"; + const std::string VALUE_LACK_LEVEL_1_NEST_0 = "{" + "\"no_notnull_no_default\":true," + "\"level_0_nest_0\":{" + "\"has_notnull_no_default\":10010" + "}}"; + +std::string SchemaSwitchMode(const std::string &oriSchemaStr) +{ + std::string resultSchemaStr = oriSchemaStr; + auto iterForStrict = std::search(resultSchemaStr.begin(), resultSchemaStr.end(), + SchemaConstant::KEYWORD_MODE_STRICT.begin(), SchemaConstant::KEYWORD_MODE_STRICT.end()); + auto iterForCompatible = std::search(resultSchemaStr.begin(), resultSchemaStr.end(), + SchemaConstant::KEYWORD_MODE_COMPATIBLE.begin(), SchemaConstant::KEYWORD_MODE_COMPATIBLE.end()); + if (iterForStrict != resultSchemaStr.end()) { + resultSchemaStr.replace(iterForStrict, iterForStrict + SchemaConstant::KEYWORD_MODE_STRICT.size(), + SchemaConstant::KEYWORD_MODE_COMPATIBLE.begin(), SchemaConstant::KEYWORD_MODE_COMPATIBLE.end()); + return resultSchemaStr; + } + if (iterForCompatible != resultSchemaStr.end()) { + resultSchemaStr.replace(iterForCompatible, iterForCompatible + SchemaConstant::KEYWORD_MODE_COMPATIBLE.size(), + SchemaConstant::KEYWORD_MODE_STRICT.begin(), SchemaConstant::KEYWORD_MODE_STRICT.end()); + return resultSchemaStr; + } + return oriSchemaStr; +} +} + +class DistributedDBSchemaObjectTest : public testing::Test { +public: + static void SetUpTestCase(void) {}; + static void TearDownTestCase(void) {}; + void SetUp() override; + void TearDown() override {}; +}; + +void DistributedDBSchemaObjectTest::SetUp() +{ + DistributedDBUnitTest::DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +/** + * @tc.name: Parse Valid Schema 001 + * @tc.desc: Parse Valid Schema + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, ParseValidSchema001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Parse valid schema with full define + * @tc.expected: step1. Parse Success. + */ + SchemaObject schema1; + int stepOne = schema1.ParseFromSchemaString(VALID_SCHEMA_FULL_DEFINE); + EXPECT_TRUE(stepOne == E_OK); + + /** + * @tc.steps: step2. Parse valid schema with empty index + * @tc.expected: step2. Parse Success. + */ + SchemaObject schema2; + int stepTwo = schema2.ParseFromSchemaString(VALID_SCHEMA_INDEX_EMPTY); + EXPECT_TRUE(stepTwo == E_OK); + + /** + * @tc.steps: step3. Parse valid schema with no index field + * @tc.expected: step3. Parse Success. + */ + SchemaObject schema3; + int stepThree = schema3.ParseFromSchemaString(VALID_SCHEMA_NO_INDEX); + EXPECT_TRUE(stepThree == E_OK); + + /** + * @tc.steps: step4. Parse valid schema with prefix of suffix blank + * @tc.expected: step4. Parse Success. + */ + SchemaObject schema4; + int stepFour = schema4.ParseFromSchemaString(VALID_SCHEMA_PRE_SUF_BLANK); + EXPECT_TRUE(stepFour == E_OK); +} + +/** + * @tc.name: Parse Invalid Schema 001 + * @tc.desc: Parse Invalid Schema + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, ParseInvalidSchema001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Parse invalid schema which is not valid json + * @tc.expected: step1. Parse Fail. + */ + SchemaObject schema1; + int stepOne = schema1.ParseFromSchemaString(INVALID_SCHEMA_INVALID_JSON); + EXPECT_TRUE(stepOne != E_OK); + + /** + * @tc.steps: step2. Parse invalid schema with less field in depth 0 + * @tc.expected: step2. Parse Fail. + */ + SchemaObject schema2; + int stepTwo = schema2.ParseFromSchemaString(INVALID_SCHEMA_LESS_META_FIELD); + EXPECT_TRUE(stepTwo != E_OK); + + /** + * @tc.steps: step3. Parse invalid schema with more field in depth 0 + * @tc.expected: step3. Parse Fail. + */ + SchemaObject schema3; + int stepThree = schema3.ParseFromSchemaString(INVALID_SCHEMA_MORE_META_FIELD); + EXPECT_TRUE(stepThree != E_OK); + + /** + * @tc.steps: step4. Parse invalid schema with wrong version + * @tc.expected: step4. Parse Fail. + */ + SchemaObject schema4; + int stepFour = schema4.ParseFromSchemaString(INVALID_SCHEMA_WRONG_VERSION); + EXPECT_TRUE(stepFour != E_OK); + + /** + * @tc.steps: step5. Parse invalid schema with wrong mode + * @tc.expected: step5. Parse Fail. + */ + SchemaObject schema5; + int stepFive = schema5.ParseFromSchemaString(INVALID_SCHEMA_WRONG_MODE); + EXPECT_TRUE(stepFive != E_OK); +} + +/** + * @tc.name: Parse Invalid Schema 002 + * @tc.desc: Parse Invalid Schema + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, ParseInvalidSchema002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Parse invalid schema which is empty define + * @tc.expected: step1. Parse Fail. + */ + SchemaObject schema1; + int stepOne = schema1.ParseFromSchemaString(INVALID_SCHEMA_DEFINE_EMPTY); + EXPECT_TRUE(stepOne != E_OK); + + /** + * @tc.steps: step2. Parse invalid schema with define nest too deep + * @tc.expected: step2. Parse Fail. + */ + SchemaObject schema2; + int stepTwo = schema2.ParseFromSchemaString(INVALID_SCHEMA_DEFINE_NEST_TOO_DEEP); + EXPECT_TRUE(stepTwo != E_OK); + + /** + * @tc.steps: step3. Parse invalid schema with invalid fieldname in define + * @tc.expected: step3. Parse Fail. + */ + SchemaObject schema3; + int stepThree = schema3.ParseFromSchemaString(INVALID_SCHEMA_DEFINE_INVALID_FIELD_NAME); + EXPECT_TRUE(stepThree != E_OK); + + /** + * @tc.steps: step4. Parse invalid schema with invalid field attribute in define + * @tc.expected: step4. Parse Fail. + */ + SchemaObject schema4; + int stepFour = schema4.ParseFromSchemaString(INVALID_SCHEMA_DEFINE_INVALID_FIELD_ATTR); + EXPECT_TRUE(stepFour != E_OK); + + /** + * @tc.steps: step5. Parse invalid schema with not empty array in define + * @tc.expected: step5. Parse Fail. + */ + SchemaObject schema5; + int stepFive = schema5.ParseFromSchemaString(INVALID_SCHEMA_DEFINE_INVALID_ARRAY_TYPE); + EXPECT_TRUE(stepFive != E_OK); +} + +/** + * @tc.name: Parse Invalid Schema 003 + * @tc.desc: Parse Invalid Schema + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, ParseInvalidSchema003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Parse invalid schema with invalid array content + * @tc.expected: step1. Parse Fail. + */ + SchemaObject schema1; + int stepOne = schema1.ParseFromSchemaString(INVALID_SCHEMA_INDEX_INVALID); + EXPECT_TRUE(stepOne != E_OK); + + /** + * @tc.steps: step2. Parse invalid schema with invalid path + * @tc.expected: step2. Parse Fail. + */ + SchemaObject schema2; + int stepTwo = schema2.ParseFromSchemaString(INVALID_SCHEMA_INDEX_PATH_INVALID); + EXPECT_TRUE(stepTwo != E_OK); + + /** + * @tc.steps: step3. Parse invalid schema with path not exist + * @tc.expected: step3. Parse Fail. + */ + SchemaObject schema3; + int stepThree = schema3.ParseFromSchemaString(INVALID_SCHEMA_INDEX_PATH_NOT_EXIST); + EXPECT_TRUE(stepThree != E_OK); + + /** + * @tc.steps: step4. Parse invalid schema with path not indexable + * @tc.expected: step4. Parse Fail. + */ + SchemaObject schema4; + int stepFour = schema4.ParseFromSchemaString(INVALID_SCHEMA_INDEX_PATH_NOT_INDEXABLE); + EXPECT_TRUE(stepFour != E_OK); + + /** + * @tc.steps: step5. Parse invalid schema with duplicate + * @tc.expected: step5. Parse Fail. + */ + SchemaObject schema5; + int stepFive = schema5.ParseFromSchemaString(INVALID_SCHEMA_INDEX_PATH_DUPLICATE); + EXPECT_TRUE(stepFive != E_OK); +} + +/** + * @tc.name: Compare Equal Exactly 001 + * @tc.desc: Compare Equal Exactly + * @tc.type: FUNC + * @tc.require: AR000DR9K4 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, CompareEqualExactly001, TestSize.Level1) +{ + SchemaObject schemaOri; + int errCode = schemaOri.ParseFromSchemaString(VALID_SCHEMA_FULL_DEFINE); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step1. Compare two same schema with full define + * @tc.expected: step1. Equal exactly. + */ + int stepOne = schemaOri.CompareAgainstSchemaString(VALID_SCHEMA_FULL_DEFINE); + EXPECT_TRUE(stepOne == -E_SCHEMA_EQUAL_EXACTLY); +} + +/** + * @tc.name: Compare Unequal Compatible 001 + * @tc.desc: Compare Unequal Compatible + * @tc.type: FUNC + * @tc.require: AR000DR9K4 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, CompareUnequalCompatible001, TestSize.Level1) +{ + SchemaObject compatibleSchema; + int errCode = compatibleSchema.ParseFromSchemaString(SCHEMA_COMPARE_BASELINE); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step1. new schema index more + * @tc.expected: step1. E_SCHEMA_UNEQUAL_COMPATIBLE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SCHEMA_INDEX_MORE); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_COMPATIBLE); + + /** + * @tc.steps: step2. new schema index less + * @tc.expected: step2. E_SCHEMA_UNEQUAL_COMPATIBLE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SCHEMA_INDEX_LESS); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_COMPATIBLE); + + /** + * @tc.steps: step3. new schema index change + * @tc.expected: step3. E_SCHEMA_UNEQUAL_COMPATIBLE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SCHEMA_INDEX_CHANGE); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_COMPATIBLE); +} + +/** + * @tc.name: Compare Unequal Compatible Upgrade 001 + * @tc.desc: Compare Unequal Compatible Upgrade + * @tc.type: FUNC + * @tc.require: AR000DR9K4 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, CompareUnequalCompatibleUpgrade001, TestSize.Level1) +{ + SchemaObject compatibleSchema; + int errCode = compatibleSchema.ParseFromSchemaString(SCHEMA_COMPARE_BASELINE); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step1. compatible new schema more field define + * @tc.expected: step1. E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SCHEMA_DEFINE_MORE_FIELD); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE); + + /** + * @tc.steps: step2. compatible new schema more field with not null and default + * @tc.expected: step2. E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SCHEMA_DEFINE_MORE_FIELD_NOTNULL_PERMIT); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE); + + /** + * @tc.steps: step3. compatible new schema more field and more index + * @tc.expected: step3. E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SCHEMA_DEFINE_MORE_FIELD_MORE_INDEX); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_COMPATIBLE_UPGRADE); +} + +/** + * @tc.name: Compare Unequal Incompatible 001 + * @tc.desc: Compare Unequal Incompatible + * @tc.type: FUNC + * @tc.require: AR000DR9K4 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, CompareUnequalIncompatible001, TestSize.Level1) +{ + SchemaObject strictSchema; + int errCode = strictSchema.ParseFromSchemaString(SchemaSwitchMode(SCHEMA_COMPARE_BASELINE)); + EXPECT_EQ(errCode, E_OK); + SchemaObject compatibleSchema; + errCode = compatibleSchema.ParseFromSchemaString(SCHEMA_COMPARE_BASELINE); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step1. strict new schema more field define + * @tc.expected: step1. E_SCHEMA_UNEQUAL_INCOMPATIBLE. + */ + errCode = strictSchema.CompareAgainstSchemaString(SchemaSwitchMode(SCHEMA_DEFINE_MORE_FIELD)); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_INCOMPATIBLE); + + /** + * @tc.steps: step2. compatible new schema more field but not null + * @tc.expected: step2. E_SCHEMA_UNEQUAL_INCOMPATIBLE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SCHEMA_DEFINE_MORE_FIELD_NOTNULL_FORBID); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_INCOMPATIBLE); + + /** + * @tc.steps: step3. new schema less field + * @tc.expected: step3. E_SCHEMA_UNEQUAL_INCOMPATIBLE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SCHEMA_DEFINE_LESS_FIELD); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_INCOMPATIBLE); + errCode = strictSchema.CompareAgainstSchemaString(SchemaSwitchMode(SCHEMA_DEFINE_LESS_FIELD)); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_INCOMPATIBLE); + + /** + * @tc.steps: step4. new schema skipsize differ + * @tc.expected: step4. E_SCHEMA_UNEQUAL_INCOMPATIBLE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SCHEMA_SKIPSIZE_DIFFER); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_INCOMPATIBLE); + + /** + * @tc.steps: step5. new schema type differ + * @tc.expected: step5. E_SCHEMA_UNEQUAL_INCOMPATIBLE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SCHEMA_DEFINE_TYPE_DIFFER); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_INCOMPATIBLE); + + /** + * @tc.steps: step6. new schema notnull differ + * @tc.expected: step6. E_SCHEMA_UNEQUAL_INCOMPATIBLE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SCHEMA_DEFINE_NOTNULL_DIFFER); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_INCOMPATIBLE); + + /** + * @tc.steps: step7. new schema default differ + * @tc.expected: step7. E_SCHEMA_UNEQUAL_INCOMPATIBLE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SCHEMA_DEFINE_DEFAULT_DIFFER); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_INCOMPATIBLE); + + /** + * @tc.steps: step8. new schema mode differ + * @tc.expected: step8. E_SCHEMA_UNEQUAL_INCOMPATIBLE. + */ + errCode = compatibleSchema.CompareAgainstSchemaString(SchemaSwitchMode(SCHEMA_COMPARE_BASELINE)); + EXPECT_EQ(errCode, -E_SCHEMA_UNEQUAL_INCOMPATIBLE); +} + +/** + * @tc.name: Check Value 001 + * @tc.desc: Check value both in strict and compatible mode + * @tc.type: FUNC + * @tc.require: AR000DR9K5 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, CheckValue001, TestSize.Level1) +{ + SchemaObject schemaStrict; + int errCode = schemaStrict.ParseFromSchemaString(VALID_SCHEMA_FULL_DEFINE); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step1. value has less field in strict mode + * @tc.expected: step1. E_VALUE_MATCH_AMENDED. + */ + ValueObject value1; + errCode = value1.Parse(VALUE_LESS_FIELD); + EXPECT_TRUE(errCode == E_OK); + int stepOne = schemaStrict.CheckValueAndAmendIfNeed(ValueSource::FROM_LOCAL, value1); + EXPECT_TRUE(stepOne == -E_VALUE_MATCH_AMENDED); + + /** + * @tc.steps: step2. value has more field in strict mode + * @tc.expected: step2. E_VALUE_MISMATCH_FEILD_COUNT. + */ + ValueObject value2; + errCode = value2.Parse(VALUE_MORE_FIELD); + EXPECT_TRUE(errCode == E_OK); + int stepTwo = schemaStrict.CheckValueAndAmendIfNeed(ValueSource::FROM_LOCAL, value2); + EXPECT_TRUE(stepTwo == -E_VALUE_MISMATCH_FEILD_COUNT); + + /** + * @tc.steps: step3. value type mismatch + * @tc.expected: step3. E_VALUE_MISMATCH_FEILD_TYPE. + */ + ValueObject value3; + errCode = value3.Parse(VALUE_TYPE_MISMATCH); + EXPECT_TRUE(errCode == E_OK); + int stepThree = schemaStrict.CheckValueAndAmendIfNeed(ValueSource::FROM_LOCAL, value3); + EXPECT_TRUE(stepThree == -E_VALUE_MISMATCH_FEILD_TYPE); + + /** + * @tc.steps: step4. value not null violation + * @tc.expected: step4. E_VALUE_MISMATCH_CONSTRAINT. + */ + ValueObject value4; + errCode = value4.Parse(VALUE_NOT_NULL_VIOLATION); + EXPECT_TRUE(errCode == E_OK); + int stepFour = schemaStrict.CheckValueAndAmendIfNeed(ValueSource::FROM_LOCAL, value4); + EXPECT_TRUE(stepFour == -E_VALUE_MISMATCH_CONSTRAINT); + + /** + * @tc.steps: step5. value exactly match strict mode + * @tc.expected: step5. E_VALUE_MATCH. + */ + ValueObject value5; + errCode = value5.Parse(VALUE_MATCH_STRICT_SCHEMA); + EXPECT_TRUE(errCode == E_OK); + int stepFive = schemaStrict.CheckValueAndAmendIfNeed(ValueSource::FROM_LOCAL, value5); + EXPECT_TRUE(stepFive == -E_VALUE_MATCH); + + /** + * @tc.steps: step6. value has more field in compatible mode + * @tc.expected: step6. E_VALUE_MATCH. + */ + std::string compatibleSchemaString = SchemaSwitchMode(VALID_SCHEMA_FULL_DEFINE); + SchemaObject schemaCompatible; + errCode = schemaCompatible.ParseFromSchemaString(compatibleSchemaString); + EXPECT_TRUE(errCode == E_OK); + + ValueObject value6; + std::vector moreFieldValueVector(VALUE_MORE_FIELD.begin(), VALUE_MORE_FIELD.end()); + errCode = value6.Parse(moreFieldValueVector); + EXPECT_TRUE(errCode == E_OK); + int stepSix = schemaCompatible.CheckValueAndAmendIfNeed(ValueSource::FROM_LOCAL, value6); + EXPECT_TRUE(stepSix == -E_VALUE_MATCH); +} + +/** + * @tc.name: Check Value 002 + * @tc.desc: Check value that has offset + * @tc.type: FUNC + * @tc.require: AR000DR9K5 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, CheckValue002, TestSize.Level1) +{ + SchemaObject schemaStrict; + int errCode = schemaStrict.ParseFromSchemaString(VALID_SCHEMA_FULL_DEFINE); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step1. value has less field in strict mode + * @tc.expected: step1. E_VALUE_MATCH and data before offset not change. + */ + std::string beforeOffset = "BOM_CONTENT:"; + std::string strValue = beforeOffset + VALUE_MATCH_STRICT_SCHEMA; + vector vecValue(strValue.begin(), strValue.end()); + + ValueObject value1; + errCode = value1.Parse(vecValue.data(), vecValue.data() + vecValue.size(), beforeOffset.size()); + EXPECT_TRUE(errCode == E_OK); + + int stepOne = schemaStrict.CheckValueAndAmendIfNeed(ValueSource::FROM_LOCAL, value1); + EXPECT_TRUE(stepOne == -E_VALUE_MATCH); + + std::string valueToString = value1.ToString(); + EXPECT_EQ(strValue.size(), valueToString.size()); + std::string valueBeforeOffset = valueToString.substr(0, beforeOffset.size()); + EXPECT_EQ(valueBeforeOffset, beforeOffset); +} + +/** + * @tc.name: Value Edit 001 + * @tc.desc: Edit the value in right way + * @tc.type: FUNC + * @tc.require: AR000DR9K5 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, ValueEdit001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Insert value to ValueObject in different depth + * @tc.expected: step1. Check insert successful + */ + ValueObject testObject; + FieldValue val; + + val.stringValue = "stringValue"; + int errCode = testObject.InsertField(FieldPath{"L1F1", "L2F1", "L3F1", "L4F1"}, FieldType::LEAF_FIELD_STRING, val); + EXPECT_TRUE(errCode == E_OK); + val.doubleValue = 1.1; // 1.1 for test + errCode = testObject.InsertField(FieldPath{"L1F1", "L2F1", "L3F1", "L4F2"}, FieldType::LEAF_FIELD_DOUBLE, val); + EXPECT_TRUE(errCode == E_OK); + val.longValue = INT64_MAX; + errCode = testObject.InsertField(FieldPath{"L1F1", "L2F1", "L3F2"}, FieldType::LEAF_FIELD_LONG, val); + EXPECT_TRUE(errCode == E_OK); + errCode = testObject.InsertField(FieldPath{"L1F1", "L2F2"}, FieldType::LEAF_FIELD_OBJECT, val); + EXPECT_TRUE(errCode == E_OK); + val.integerValue = INT32_MIN; + errCode = testObject.InsertField(FieldPath{"L1F1", "L2F2", "L3F3"}, FieldType::LEAF_FIELD_INTEGER, val); + EXPECT_TRUE(errCode == E_OK); + val.boolValue = true; + errCode = testObject.InsertField(FieldPath{"L1F1", "L2F2", "L3F4"}, FieldType::LEAF_FIELD_BOOL, val); + EXPECT_TRUE(errCode == E_OK); + errCode = testObject.InsertField(FieldPath{"L1F2"}, FieldType::LEAF_FIELD_NULL, val); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step2. Delete value in ValueObject + * @tc.expected: step2. Check delete successful + */ + errCode = testObject.DeleteField(FieldPath{"L1F1", "L2F1", "L3F1", "L4F1"}); + EXPECT_TRUE(errCode == E_OK); + errCode = testObject.DeleteField(FieldPath{"L1F1", "L2F1", "L3F1"}); + EXPECT_TRUE(errCode == E_OK); + errCode = testObject.DeleteField(FieldPath{"L1F1"}); + EXPECT_TRUE(errCode == E_OK); +} + +/** + * @tc.name: Value Edit 002 + * @tc.desc: Edit the value in wrong way + * @tc.type: FUNC + * @tc.require: AR000DR9K5 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, ValueEdit002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Insert value to ValueObject in different depth + * @tc.expected: step1. Check insert not successful + */ + ValueObject testObject; + FieldValue val; + + val.stringValue = "stringValue"; + int errCode = testObject.InsertField(FieldPath{"L1F1", "L2F1", "L3F1"}, FieldType::LEAF_FIELD_STRING, val); + EXPECT_TRUE(errCode == E_OK); + val.doubleValue = 1.1; // 1.1 for test + errCode = testObject.InsertField(FieldPath{"L1F1", "L2F1", "L3F1"}, FieldType::LEAF_FIELD_DOUBLE, val); + EXPECT_TRUE(errCode != E_OK); + val.longValue = INT64_MAX; + errCode = testObject.InsertField(FieldPath{"L1F1", "L2F1", "L3F1", "L4F1"}, FieldType::LEAF_FIELD_LONG, val); + EXPECT_TRUE(errCode != E_OK); + + /** + * @tc.steps: step2. Delete value in ValueObject + * @tc.expected: step2. Check delete not successful + */ + errCode = testObject.DeleteField(FieldPath{"L1F1", "L2F1", "L3F1", "L4F1"}); + EXPECT_TRUE(errCode != E_OK); +} + +namespace { +void CheckValueLackField(const SchemaObject &schema, const std::string &oriValue, const std::string &lackField, + int expectErrCode, ValueObject &externalValueObject) +{ + std::string valueStr = oriValue; + auto startIter = std::search(valueStr.begin(), valueStr.end(), lackField.begin(), lackField.end()); + valueStr.erase(startIter, startIter + lackField.size()); + int errCode = externalValueObject.Parse(valueStr); + EXPECT_EQ(errCode, E_OK); + errCode = schema.CheckValueAndAmendIfNeed(ValueSource::FROM_LOCAL, externalValueObject); + EXPECT_EQ(errCode, expectErrCode); +} + +void CheckValueLackField(const SchemaObject &schema, const std::string &oriValue, const std::string &lackField, + int expectErrCode) +{ + ValueObject valueObj; + CheckValueLackField(schema, oriValue, lackField, expectErrCode, valueObj); +} +} + +/** + * @tc.name: Value LackField 001 + * @tc.desc: check the value which lack field + * @tc.type: FUNC + * @tc.require: AR000DR9K5 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, ValueLackField001, TestSize.Level1) +{ + SchemaObject schema; + int errCode = schema.ParseFromSchemaString(SCHEMA_FOR_TEST_NOTNULL_AND_DEFAULT); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step1. check value lack no field + * @tc.expected: step1. E_VALUE_MATCH + */ + CheckValueLackField(schema, VALUE_NO_LACK_FIELD, "", -E_VALUE_MATCH); + + /** + * @tc.steps: step2. check value lack field on no_notnull_no_default + * @tc.expected: step2. E_VALUE_MATCH + */ + CheckValueLackField(schema, VALUE_NO_LACK_FIELD, "\"no_notnull_no_default\":true,", -E_VALUE_MATCH); + + /** + * @tc.steps: step3. check value lack field on has_notnull_no_default + * @tc.expected: step3. E_VALUE_MISMATCH_CONSTRAINT + */ + CheckValueLackField(schema, VALUE_NO_LACK_FIELD, "\"has_notnull_no_default\":10010,", + -E_VALUE_MISMATCH_CONSTRAINT); + + /** + * @tc.steps: step4. check value lack field on no_notnull_has_default + * @tc.expected: step4. E_VALUE_MATCH_AMENDED + */ + CheckValueLackField(schema, VALUE_NO_LACK_FIELD, "\"no_notnull_has_default\":10086,", + -E_VALUE_MATCH_AMENDED); + + /** + * @tc.steps: step5. check value lack field on has_notnull_has_default + * @tc.expected: step5. E_VALUE_MATCH_AMENDED + */ + CheckValueLackField(schema, VALUE_NO_LACK_FIELD, "\"has_notnull_has_default\":1.38064,", + -E_VALUE_MATCH_AMENDED); + + /** + * @tc.steps: step6. check value lack entire level_0_nest_0 + * @tc.expected: step6. E_VALUE_MISMATCH_CONSTRAINT + */ + CheckValueLackField(schema, VALUE_LACK_LEVEL_0_NEST_0, "", -E_VALUE_MISMATCH_CONSTRAINT); + + /** + * @tc.steps: step7. check value level_0_nest_0 not json_object + * @tc.expected: step7. E_VALUE_MISMATCH_FEILD_TYPE + */ + CheckValueLackField(schema, VALUE_LEVEL_0_NEST_0_NOT_OBJECT, "", -E_VALUE_MISMATCH_FEILD_TYPE); +} + +/** + * @tc.name: Value LackField 002 + * @tc.desc: check the value which lack field + * @tc.type: FUNC + * @tc.require: AR000DR9K5 + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBSchemaObjectTest, ValueLackField002, TestSize.Level1) +{ + SchemaObject schema; + int errCode = schema.ParseFromSchemaString(SCHEMA_FOR_TEST_NOTNULL_AND_DEFAULT); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step1. check value lack entire level_1_nest_0 + * @tc.expected: step1. E_VALUE_MATCH_AMENDED + */ + ValueObject val; + CheckValueLackField(schema, VALUE_LACK_LEVEL_1_NEST_0, "", -E_VALUE_MATCH_AMENDED, val); + // Check Field Existence or not + EXPECT_EQ(val.IsFieldPathExist(FieldPath{"level_0_nest_0", "level_1_nest_0"}), true); + EXPECT_EQ(val.IsFieldPathExist(FieldPath{"level_0_nest_0", "level_1_nest_0", "no_notnull_has_default"}), true); + EXPECT_EQ(val.IsFieldPathExist(FieldPath{"level_0_nest_0", "level_1_nest_0", "has_notnull_has_default"}), true); + EXPECT_EQ(val.IsFieldPathExist(FieldPath{"level_0_nest_0", "level_1_nest_0", "level_2_nest_0"}), true); + EXPECT_EQ(val.IsFieldPathExist(FieldPath{"level_0_nest_0", "level_1_nest_0", "level_2_nest_0", "extra_0"}), true); + EXPECT_EQ(val.IsFieldPathExist(FieldPath{"level_0_nest_0", "level_1_nest_0", "level_2_nest_0", "extra_1"}), false); + EXPECT_EQ(val.IsFieldPathExist(FieldPath{"level_0_nest_0", "level_1_nest_0", "level_2_nest_0", "extra_2"}), false); + EXPECT_EQ(val.IsFieldPathExist(FieldPath{"level_0_nest_0", "level_1_nest_0", "level_2_nest_1"}), false); + // Check Field value + FieldValue theValue; + EXPECT_EQ(val.GetFieldValueByFieldPath(FieldPath{"level_0_nest_0", "level_1_nest_0", "no_notnull_has_default"}, + theValue), E_OK); + EXPECT_EQ(theValue.integerValue, 100); + EXPECT_EQ(val.GetFieldValueByFieldPath(FieldPath{"level_0_nest_0", "level_1_nest_0", "has_notnull_has_default"}, + theValue), E_OK); + EXPECT_LT(std::abs(theValue.doubleValue - 3.14), 0.1); + EXPECT_EQ(val.GetFieldValueByFieldPath(FieldPath{"level_0_nest_0", "level_1_nest_0", "level_2_nest_0", "extra_0"}, + theValue), E_OK); + EXPECT_EQ(theValue.stringValue == std::string("3.1415"), true); +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_schema_unit_test.cpp b/mock/distributeddb/test/unittest/common/common/distributeddb_schema_unit_test.cpp new file mode 100644 index 00000000..8f6a5826 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_schema_unit_test.cpp @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_tools_unit_test.h" + +#include + +#include "db_errno.h" +#include "log_print.h" +#include "schema_utils.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +class DistributedDBSchemalTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBSchemalTest::SetUpTestCase(void) +{ +} + +void DistributedDBSchemalTest::TearDownTestCase(void) +{ +} + +void DistributedDBSchemalTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBSchemalTest::TearDown(void) +{ +} + +namespace { +map g_schemaAttrDefTestDataDir; + +void CheckSchemaAttribute(const SchemaAttribute &res, const SchemaAttribute &check) +{ + EXPECT_EQ(res.type, check.type); + EXPECT_EQ(res.isIndexable, check.isIndexable); + EXPECT_EQ(res.hasNotNullConstraint, check.hasNotNullConstraint); + EXPECT_EQ(res.hasDefaultValue, check.hasDefaultValue); + EXPECT_EQ(res.defaultValue.stringValue, check.defaultValue.stringValue); + EXPECT_EQ(memcmp(&res.defaultValue, &check.defaultValue, 8), 0); // only check this unit 8 byte +} + +void PreNumDataForParseAndCheckSchemaAttribute003() +{ + SchemaAttribute attributeRes; + attributeRes.type = FieldType::LEAF_FIELD_INTEGER; + attributeRes.defaultValue.integerValue = 0; + attributeRes.hasDefaultValue = true; + g_schemaAttrDefTestDataDir["INTEGER, DEFAULT 0"] = attributeRes; + + SchemaAttribute attributeRes1; + attributeRes1.type = FieldType::LEAF_FIELD_INTEGER; + attributeRes1.hasDefaultValue = true; + attributeRes1.hasNotNullConstraint = true; + attributeRes1.defaultValue.integerValue = INT32_MAX; + g_schemaAttrDefTestDataDir["INTEGER, NOT NULL, DEFAULT " + std::to_string(INT32_MAX)] = attributeRes1; + + SchemaAttribute attributeRes2; + attributeRes2.type = FieldType::LEAF_FIELD_INTEGER; + attributeRes2.hasDefaultValue = true; + attributeRes2.defaultValue.integerValue = 0; + g_schemaAttrDefTestDataDir["INTEGER, DEFAULT +0"] = attributeRes2; + + SchemaAttribute attributeRes3; + attributeRes3.type = FieldType::LEAF_FIELD_LONG; + attributeRes3.hasDefaultValue = true; + attributeRes3.defaultValue.longValue = 0; + g_schemaAttrDefTestDataDir["LONG, DEFAULT -0"] = attributeRes3; + + SchemaAttribute attributeRes4; + attributeRes4.type = FieldType::LEAF_FIELD_LONG; + attributeRes4.hasNotNullConstraint = true; + attributeRes4.hasDefaultValue = true; + attributeRes4.defaultValue.longValue = LONG_MAX; + g_schemaAttrDefTestDataDir["LONG, NOT NULL,DEFAULT " + std::to_string(LONG_MAX)] = attributeRes4; +} + +void PreStringDataForParseAndCheckSchemaAttribute003() +{ + SchemaAttribute attributeRes5; + attributeRes5.type = FieldType::LEAF_FIELD_STRING; + attributeRes5.hasDefaultValue = true; + attributeRes5.defaultValue.stringValue = "11ada%$%"; + g_schemaAttrDefTestDataDir["STRING , DEFAULT '11ada%$%'"] = attributeRes5; + + SchemaAttribute attributeRes6; + attributeRes6.type = FieldType::LEAF_FIELD_STRING; + attributeRes6.hasNotNullConstraint = true; + attributeRes6.hasDefaultValue = true; + attributeRes6.defaultValue.stringValue = "asdasd_\n\t"; + g_schemaAttrDefTestDataDir["STRING, NOT NULL , DEFAULT 'asdasd_\n\t'"] = attributeRes6; +} + +void PreDoubleDataForParseAndCheckSchemaAttribute003() +{ + SchemaAttribute attributeRes7; + attributeRes7.type = FieldType::LEAF_FIELD_DOUBLE; + attributeRes7.hasDefaultValue = true; + attributeRes7.defaultValue.doubleValue = 0; + g_schemaAttrDefTestDataDir["DOUBLE,DEFAULT 0.0"] = attributeRes7; + + SchemaAttribute attributeRes8; + attributeRes8.type = FieldType::LEAF_FIELD_DOUBLE; + attributeRes8.hasDefaultValue = true; + attributeRes8.defaultValue.doubleValue = 0; + g_schemaAttrDefTestDataDir["DOUBLE,DEFAULT 0."] = attributeRes8; + + SchemaAttribute attributeRes9; + attributeRes9.type = FieldType::LEAF_FIELD_DOUBLE; + attributeRes9.hasDefaultValue = true; + attributeRes9.defaultValue.doubleValue = 0.1; // 0.1 as test data + g_schemaAttrDefTestDataDir["DOUBLE,DEFAULT 0.1"] = attributeRes9; + + SchemaAttribute attributeRes10; + attributeRes10.type = FieldType::LEAF_FIELD_DOUBLE; + attributeRes10.hasNotNullConstraint = true; + attributeRes10.hasDefaultValue = true; + attributeRes10.defaultValue.doubleValue = -0.123456; // -0.123456 as test data + g_schemaAttrDefTestDataDir["DOUBLE, NOT NULL,DEFAULT -0.123456"] = attributeRes10; + + SchemaAttribute attributeRes11; + attributeRes11.type = FieldType::LEAF_FIELD_DOUBLE; + attributeRes11.hasNotNullConstraint = false; + attributeRes11.hasDefaultValue = true; + attributeRes11.defaultValue.doubleValue = 0; + g_schemaAttrDefTestDataDir["DOUBLE,DEFAULT +0.0"] = attributeRes11; + + // double -0 Has been manually verified + SchemaAttribute attributeRes13; + attributeRes13.type = FieldType::LEAF_FIELD_DOUBLE; + attributeRes13.hasNotNullConstraint = true; + attributeRes13.hasDefaultValue = true; + attributeRes13.defaultValue.doubleValue = DBL_MAX; + g_schemaAttrDefTestDataDir["DOUBLE, NOT NULL,DEFAULT " + std::to_string(DBL_MAX)] = attributeRes13; +} + +void PreBoolDataForParseAndCheckSchemaAttribute003() +{ + SchemaAttribute attributeRes14; + attributeRes14.type = FieldType::LEAF_FIELD_BOOL; + attributeRes14.hasNotNullConstraint = false; + attributeRes14.hasDefaultValue = true; + attributeRes14.defaultValue.boolValue = false; + g_schemaAttrDefTestDataDir["BOOL,DEFAULT false"] = attributeRes14; + + SchemaAttribute attributeRes15; + attributeRes15.type = FieldType::LEAF_FIELD_BOOL; + attributeRes15.hasNotNullConstraint = true; + attributeRes15.hasDefaultValue = true; + attributeRes15.defaultValue.boolValue = true; + g_schemaAttrDefTestDataDir["BOOL, NOT NULL,DEFAULT true"] = attributeRes15; +} +} // namespace + +/** + * @tc.name: ParseAndCheckSchemaAttribute001 + * @tc.desc: Ability to recognize and parse the correct schema attribute format + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBSchemalTest, ParseAndCheckSchemaAttribute001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Preset shcema attribute strings that are correctly written according to the definition. + */ + SchemaAttribute attributeRes; + attributeRes.type = FieldType::LEAF_FIELD_INTEGER; + g_schemaAttrDefTestDataDir["INTEGER"] = attributeRes; + + SchemaAttribute attributeRes1; + attributeRes1.type = FieldType::LEAF_FIELD_BOOL; + attributeRes1.hasNotNullConstraint = true; + g_schemaAttrDefTestDataDir["BOOL, NOT NULL"] = attributeRes1; + + SchemaAttribute attributeRes2; + attributeRes2.type = FieldType::LEAF_FIELD_STRING; + attributeRes2.hasDefaultValue = true; + attributeRes2.defaultValue.stringValue = "dasdads"; + g_schemaAttrDefTestDataDir["STRING,DEFAULT 'dasdads'"] = attributeRes2; + + SchemaAttribute attributeRes3; + attributeRes3.type = FieldType::LEAF_FIELD_DOUBLE; + attributeRes3.hasDefaultValue = true; + attributeRes3.hasNotNullConstraint = true; + attributeRes3.defaultValue.doubleValue = -1.0; + g_schemaAttrDefTestDataDir["\tDOUBLE \t,\t\t\tNOT NULL , DEFAULT -1.0"] = attributeRes3; + + SchemaAttribute attributeRes4; + attributeRes4.type = FieldType::LEAF_FIELD_LONG; + attributeRes4.hasNotNullConstraint = false; + attributeRes4.hasDefaultValue = false; + g_schemaAttrDefTestDataDir["LONG,DEFAULT null"] = attributeRes4; + + /** + * @tc.steps: step2. Call interface + * @tc.expected: step2. Returns E_OK and parses correctly. + */ + for (auto &iter : g_schemaAttrDefTestDataDir) { + SchemaAttribute attributeOut; + LOGD("Attr : %s", iter.first.c_str()); + EXPECT_EQ(SchemaUtils::ParseAndCheckSchemaAttribute(iter.first, attributeOut), E_OK); + CheckSchemaAttribute(iter.second, attributeOut); + } +} + +/** + * @tc.name: ParseAndCheckSchemaAttribute002 + * @tc.desc: Can identify the wrong schema attribute format and report an error. + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBSchemalTest, ParseAndCheckSchemaAttribute002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Preset shcema attributes based on definition error. + */ + std::vector preData = { + "", + " ", + "$INTEGER", + "INTEGER NOT_NULL DEFAULT 1", + "STRING \n DEFAULT 'a'", + "BOOL,NOT NULL", + "LONG,NOT\tNULL", + "BOOL,NOT null", + "bool,not null", + "BOOL,NOT NULL,default false", + "INTEGER,", + "BOOL, NOT NULL,", + "BOOL, NOT NULL,DEFAULT ", + "BOOL, DEFAULT false, NOT NULL", + "DEFAULT 1, LONG, NOT NULL", + "DEFAULT 1", + "NOT NULL, DEFAULT x", + ", NOT NULL DEFAULT 1", + "LONG, NOT NULL, DEFAULT null" + }; + string overflowDol = to_string(DBL_MAX); + overflowDol = '1' + overflowDol; + preData.push_back("DOUBLE, NOT NULL, DEFAULT " + overflowDol); + preData.push_back("DOUBLE, NOT NULL, DEFAULT -" + overflowDol); + + preData.push_back("INTEGER, NOT NULL, DEFAULT 2147483648"); // int max + 1; + preData.push_back("INTEGER, NOT NULL, DEFAULT -2147483649"); + preData.push_back("LONG, NOT NULL, DEFAULT 9223372036854775808"); // long max + 1; + preData.push_back("LONG, NOT NULL, DEFAULT -9223372036854775809"); + + /** + * @tc.steps: step2. Call interface ParseAndCheckSchemaAttribute. + * @tc.expected: step2. Returns -E_SCHEMA_PARSE_FAIL. + */ + for (auto &iter : preData) { + SchemaAttribute attributeOut; + LOGD("Attr : %s", iter.c_str()); + EXPECT_EQ(SchemaUtils::ParseAndCheckSchemaAttribute(iter, attributeOut), -E_SCHEMA_PARSE_FAIL); + } +} + +/** + * @tc.name: ParseAndCheckSchemaAttribute003 + * @tc.desc: Can correctly interpret the meaning of each keyword of the schema attribute + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBSchemalTest, ParseAndCheckSchemaAttribute003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Preset shcema attributes based on defining correct format and content. + */ + g_schemaAttrDefTestDataDir.clear(); + PreNumDataForParseAndCheckSchemaAttribute003(); + PreDoubleDataForParseAndCheckSchemaAttribute003(); + PreStringDataForParseAndCheckSchemaAttribute003(); + PreBoolDataForParseAndCheckSchemaAttribute003(); + + /** + * @tc.steps: step2. Call interface ParseAndCheckSchemaAttribute. + * @tc.expected: step2. Returns E_OK and parses correctly. + */ + for (auto &iter : g_schemaAttrDefTestDataDir) { + SchemaAttribute attributeOut; + EXPECT_EQ(SchemaUtils::ParseAndCheckSchemaAttribute(iter.first, attributeOut), E_OK); + CheckSchemaAttribute(iter.second, attributeOut); + } +} + +/** + * @tc.name: ParseAndCheckSchemaAttribute004 + * @tc.desc: Can correctly identify the meaning of the schema attribute field that is incorrectly parsed + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBSchemalTest, ParseAndCheckSchemaAttribute004, TestSize.Level1) +{ + /** + * @tc.steps: step1. Preset shcema attributes based on defining incorrect format and content. + */ + std::vector preData = { + "LONG,NOT NULL, DEFAULT '123'", + "STRING,DEFAULT true", + "INTEGER,NOT NULL,DEFAULT MAX+1", + "LONG,DEFAULT 0.0", + "INTEGER,NOT NULL,DEFAULT - 123", + "INTEGER,DEFAULT 12 3", + "LONG,NOT NULL,DEFAULT 0xFF", + "INTEGER,00", + "DOUBLE,DEFAULT 123a", + "DOUBLE,NOT NULL,DEFAULT 0..0", + "DOUBLE,DEFAULT 2e2", + "DOUBLE,NOT NULL,DEFAULT 1+1", + "DOUBLE,NOT NULL,DEFAULT .0", + "DOUBLE,DEFAULT MAX+1", + "STRING,DEFAULT 123", + "STRING,NOT NULL,DEFAULT 'ABC", + "BOOL,DEFAULT TRUE", + "INT", + "long", + "String", + "STRING DEFAULT 'a'a", + }; + + /** + * @tc.steps: step2. Call interface ParseAndCheckSchemaAttribute. + * @tc.expected: step2. Returns -E_SCHEMA_PARSE_FAIL. + */ + string overSize(4 * 1024 + 1, 'a'); + preData.push_back("STRING, DEFAULT '" + overSize + "'"); + LOGD("%s", preData[0].c_str()); + for (auto &iter : preData) { + SchemaAttribute attributeOut; + EXPECT_EQ(SchemaUtils::ParseAndCheckSchemaAttribute(iter, attributeOut), -E_SCHEMA_PARSE_FAIL); + } +} + +/** + * @tc.name: CheckFieldName001 + * @tc.desc: Correctly identify field names + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBSchemalTest, CheckFieldName001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Enter the preset correct string array into CheckFieldName and check. + * @tc.expected: step1. Returns E_OK. + */ + std::vector preData = { + "_abc", + "_123abc", + "a_123_", + }; + string overSize(64, 'a'); + preData.push_back(overSize); + for (auto &iter : preData) { + EXPECT_EQ(SchemaUtils::CheckFieldName(iter), E_OK); + } +} + +/** + * @tc.name: CheckFieldName002 + * @tc.desc: Identify illegal field name + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBSchemalTest, CheckFieldName002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Enter the preset incorrect string array into CheckFieldName and check. + * @tc.expected: step1. Returns -E_SCHEMA_PARSE_FAIL. + */ + std::vector preData = { + "123abc", + "$.LONG", + "", + " abc", + "\tabc" + }; + string overSize(65, 'a'); + preData.push_back(overSize); + for (auto &iter : preData) { + EXPECT_EQ(SchemaUtils::CheckFieldName(iter), -E_SCHEMA_PARSE_FAIL); + } +} + +/** + * @tc.name: ParseAndCheckFieldPath001 + * @tc.desc: Correctly identify and parse shema index fields + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBSchemalTest, ParseAndCheckFieldPath001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Enter the array of preset correct strings into ParseAndCheckFieldPath and check result. + * @tc.expected: step1. Returns E_OK and Parse correctly. + */ + vector > > testPreData { + // test + // ans + {"$.abc.def.fg", + {"abc", "def", "fg"}}, + + {"$.abc._def.fg", + {"abc", "_def", "fg"}}, + + {"$._.__.___", + {"_", "__", "___"}}, + + {"$._.__1234.abc455545", + {"_", "__1234", "abc455545"}}, + + {" $.abc._def.fg", + {"abc", "_def", "fg"}}, + + {" $.a.a.a.a", + {"a", "a", "a", "a"}}, + + {"$.abc._def.fg ", + {"abc", "_def", "fg"}}, + + {" $.abc._def.fg ", + {"abc", "_def", "fg"}}, + + {"\t$.abc.def.fg ", + {"abc", "def", "fg"}}, + + {" $.abc.def.fg\r\t", + {"abc", "def", "fg"}}, + + {"\r$.abc.def.fg\t\r", + {"abc", "def", "fg"}}, + }; + + for (auto &iter : testPreData) { + FieldPath ans; + EXPECT_EQ(SchemaUtils::ParseAndCheckFieldPath(iter.first, ans), E_OK); + EXPECT_EQ(ans, iter.second); + } +} + +/** + * @tc.name: ParseAndCheckFieldPath002 + * @tc.desc: Correctly identify illegal shema index fields + * @tc.type: FUNC + * @tc.require: AR000DR9K3 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBSchemalTest, ParseAndCheckFieldPath002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Enter the array of preset illegal strings into ParseAndCheckFieldPath and check result. + * @tc.expected: step1. Returns -E_SCHEMA_PARSE_FAIL. + */ + vector testPreData { + "", + "\t", + "\r", + "\r\t", + " ", + "$", + "$.", + " . ", + "$$", + "$.$", + "$.a.b.c.d.e", + "$..abc", + "$.abc..def.fg", + "$abc.def.fg.", + "$.123", + "$.abc123%", + "$.abc.\0.fg", + "$.abc.fg.\0", + "\"$.abc.def.fg\"", + "$.\"abc\".def.fg", + "$.\"abc\n.def.fg", + }; + + for (auto &iter : testPreData) { + FieldPath ans; + EXPECT_EQ(SchemaUtils::ParseAndCheckFieldPath(iter, ans), -E_SCHEMA_PARSE_FAIL); + } +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_tools_unit_test.cpp b/mock/distributeddb/test/unittest/common/common/distributeddb_tools_unit_test.cpp new file mode 100644 index 00000000..6d4db3a0 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_tools_unit_test.cpp @@ -0,0 +1,964 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_tools_unit_test.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "db_common.h" +#include "db_constant.h" +#include "generic_single_ver_kv_entry.h" +#include "platform_specific.h" +#include "single_ver_data_packet.h" +#include "value_hash_calc.h" + +using namespace DistributedDB; + +namespace DistributedDBUnitTest { +namespace { + const std::string CREATE_LOCAL_TABLE_SQL = + "CREATE TABLE IF NOT EXISTS local_data(" \ + "key BLOB PRIMARY KEY," \ + "value BLOB," \ + "timestamp INT," \ + "hash_key BLOB);"; + + const std::string CREATE_META_TABLE_SQL = + "CREATE TABLE IF NOT EXISTS meta_data(" \ + "key BLOB PRIMARY KEY NOT NULL," \ + "value BLOB);"; + + const std::string CREATE_SYNC_TABLE_SQL = + "CREATE TABLE IF NOT EXISTS sync_data(" \ + "key BLOB NOT NULL," \ + "value BLOB," \ + "timestamp INT NOT NULL," \ + "flag INT NOT NULL," \ + "device BLOB," \ + "ori_device BLOB," \ + "hash_key BLOB PRIMARY KEY NOT NULL," \ + "w_timestamp INT);"; + + const std::string CREATE_SYNC_TABLE_INDEX_SQL = + "CREATE INDEX IF NOT EXISTS key_index ON sync_data (key);"; + + const std::string CREATE_TABLE_SQL = + "CREATE TABLE IF NOT EXISTS version_data(key BLOB, value BLOB, oper_flag INTEGER, version INTEGER, " \ + "timestamp INTEGER, ori_timestamp INTEGER, hash_key BLOB, " \ + "PRIMARY key(hash_key, version));"; + + const std::string CREATE_SQL = + "CREATE TABLE IF NOT EXISTS data(key BLOB PRIMARY key, value BLOB);"; + + bool CompareEntry(const DistributedDB::Entry &a, const DistributedDB::Entry &b) + { + return (a.key < b.key); + } +} + +// OpenDbProperties.uri do not need +int DistributedDBToolsUnitTest::CreateMockSingleDb(DatabaseInfo &dbInfo, OpenDbProperties &properties) +{ + std::string identifier = dbInfo.userId + "-" + dbInfo.appId + "-" + dbInfo.storeId; + std::string hashIdentifier = DBCommon::TransferHashString(identifier); + std::string identifierName = DBCommon::TransferStringToHex(hashIdentifier); + + if (OS::GetRealPath(dbInfo.dir, properties.uri) != E_OK) { + LOGE("Failed to canonicalize the path."); + return -E_INVALID_ARGS; + } + + int errCode = DBCommon::CreateStoreDirectory(dbInfo.dir, identifierName, DBConstant::SINGLE_SUB_DIR, true); + if (errCode != E_OK) { + return errCode; + } + + properties.uri = dbInfo.dir + "/" + identifierName + "/" + + DBConstant::SINGLE_SUB_DIR + "/" + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + if (properties.sqls.empty()) { + std::vector defaultCreateTableSqls = { + CREATE_LOCAL_TABLE_SQL, + CREATE_META_TABLE_SQL, + CREATE_SYNC_TABLE_SQL, + CREATE_SYNC_TABLE_INDEX_SQL + }; + properties.sqls = defaultCreateTableSqls; + } + + sqlite3 *db = nullptr; + errCode = SQLiteUtils::OpenDatabase(properties, db); + if (errCode != E_OK) { + return errCode; + } + errCode = SQLiteUtils::SetUserVer(properties, dbInfo.dbUserVersion); + if (errCode != E_OK) { + return errCode; + } + + (void)sqlite3_close_v2(db); + db = nullptr; + return errCode; +} + +static int CreatMockMultiDb(OpenDbProperties &properties, DatabaseInfo &dbInfo) +{ + sqlite3 *db = nullptr; + (void)SQLiteUtils::OpenDatabase(properties, db); + int errCode = SQLiteUtils::SetUserVer(properties, dbInfo.dbUserVersion); + (void)sqlite3_close_v2(db); + db = nullptr; + if (errCode != E_OK) { + return errCode; + } + return errCode; +} + +int DistributedDBToolsUnitTest::OpenMockMultiDb(DatabaseInfo &dbInfo, OpenDbProperties &properties) +{ + std::string identifier = dbInfo.userId + "-" + dbInfo.appId + "-" + dbInfo.storeId; + std::string hashIdentifier = DBCommon::TransferHashString(identifier); + std::string identifierName = DBCommon::TransferStringToHex(hashIdentifier); + + OpenDbProperties commitProperties = properties; + commitProperties.uri = dbInfo.dir + "/" + identifierName + "/" + DBConstant::MULTI_SUB_DIR + + "/commit_logs" + DBConstant::SQLITE_DB_EXTENSION; + + commitProperties.sqls = {CREATE_SQL}; + + OpenDbProperties kvStorageProperties = commitProperties; + kvStorageProperties.uri = dbInfo.dir + "/" + identifierName + "/" + + DBConstant::MULTI_SUB_DIR + "/value_storage" + DBConstant::SQLITE_DB_EXTENSION; + OpenDbProperties metaStorageProperties = commitProperties; + metaStorageProperties.uri = dbInfo.dir + "/" + identifierName + "/" + + DBConstant::MULTI_SUB_DIR + "/meta_storage" + DBConstant::SQLITE_DB_EXTENSION; + + // test code, Don't needpay too much attention to exception handling + int errCode = CreatMockMultiDb(properties, dbInfo); + if (errCode != E_OK) { + return errCode; + } + + errCode = CreatMockMultiDb(kvStorageProperties, dbInfo); + if (errCode != E_OK) { + return errCode; + } + + errCode = CreatMockMultiDb(metaStorageProperties, dbInfo); + if (errCode != E_OK) { + return errCode; + } + + return errCode; +} + +// OpenDbProperties.uri do not need +int DistributedDBToolsUnitTest::CreateMockMultiDb(DatabaseInfo &dbInfo, OpenDbProperties &properties) +{ + std::string identifier = dbInfo.userId + "-" + dbInfo.appId + "-" + dbInfo.storeId; + std::string hashIdentifier = DBCommon::TransferHashString(identifier); + std::string identifierName = DBCommon::TransferStringToHex(hashIdentifier); + + if (OS::GetRealPath(dbInfo.dir, properties.uri) != E_OK) { + LOGE("Failed to canonicalize the path."); + return -E_INVALID_ARGS; + } + + int errCode = DBCommon::CreateStoreDirectory(dbInfo.dir, identifierName, DBConstant::MULTI_SUB_DIR, true); + if (errCode != E_OK) { + return errCode; + } + + properties.uri = dbInfo.dir + "/" + identifierName + "/" + DBConstant::MULTI_SUB_DIR + + "/" + DBConstant::MULTI_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + + if (properties.sqls.empty()) { + properties.sqls = {CREATE_TABLE_SQL}; + } + + OpenMockMultiDb(dbInfo, properties); + + return errCode; +} + +int DistributedDBToolsUnitTest::GetResourceDir(std::string& dir) +{ + int errCode = GetCurrentDir(dir); + if (errCode != E_OK) { + return -E_INVALID_PATH; + } + + return E_OK; +} + +int DistributedDBToolsUnitTest::GetCurrentDir(std::string &dir) +{ + static const int maxFileLength = 1024; + dir = ""; + char buffer[maxFileLength] = {0}; + int length = readlink("/proc/self/exe", buffer, maxFileLength); + if (length < 0 || length >= maxFileLength) { + LOGE("read directory err length:%d", length); + return -E_LENGTH_ERROR; + } + LOGD("DIR = %s", buffer); + dir = buffer; + if (std::string::npos == dir.rfind("/") && std::string::npos == dir.rfind("\\")) { + LOGE("current patch format err"); + return -E_INVALID_PATH; + } + + if (dir.rfind("/") != std::string::npos) { + dir.erase(dir.rfind("/") + 1); + } + return E_OK; +} + +void DistributedDBToolsUnitTest::TestDirInit(std::string& dir) +{ + if (GetCurrentDir(dir) != E_OK) { + dir = "/"; + } + + dir.append("testDbDir"); + DIR *dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + if (OS::MakeDBDirectory(dir) != 0) { + LOGI("MakeDirectory err!"); + dir = "/"; + return; + } + } else { + closedir(dirTmp); + } +} + +int DistributedDBToolsUnitTest::RemoveTestDbFiles(const std::string& dir) +{ + bool isExisted = OS::CheckPathExistence(dir); + if (!isExisted) { + return E_OK; + } + + int nFile = 0; + std::string dirName; + struct dirent *direntPtr = nullptr; + DIR *dirPtr = opendir(dir.c_str()); + if (dirPtr == nullptr) { + LOGE("opendir error!"); + return -E_INVALID_PATH; + } + while (true) { + direntPtr = readdir(dirPtr); + // condition to exit the loop + if (direntPtr == nullptr) { + break; + } + // only remove all *.db files + std::string str(direntPtr->d_name); + if (str == "." || str == "..") { + continue; + } + dirName.clear(); + dirName.append(dir).append("/").append(str); + if (direntPtr->d_type == DT_DIR) { + RemoveTestDbFiles(dirName); + rmdir(dirName.c_str()); + } else if (remove(dirName.c_str()) != 0) { + LOGI("remove file: %s failed!", dirName.c_str()); + continue; + } + nFile++; + } + closedir(dirPtr); + LOGI("Total %d test db files are removed!", nFile); + return 0; +} + +void DistributedDBToolsUnitTest::KvStoreDelegateCallback( + DBStatus statusSrc, KvStoreDelegate *kvStoreSrc, DBStatus &statusDst, KvStoreDelegate *&kvStoreDst) +{ + statusDst = statusSrc; + kvStoreDst = kvStoreSrc; +} + +void DistributedDBToolsUnitTest::KvStoreNbDelegateCallback( + DBStatus statusSrc, KvStoreNbDelegate* kvStoreSrc, DBStatus &statusDst, KvStoreNbDelegate *&kvStoreDst) +{ + statusDst = statusSrc; + kvStoreDst = kvStoreSrc; +} +void DistributedDBToolsUnitTest::SnapshotDelegateCallback( + DBStatus statusSrc, KvStoreSnapshotDelegate* snapshot, DBStatus &statusDst, KvStoreSnapshotDelegate *&snapshotDst) +{ + statusDst = statusSrc; + snapshotDst = snapshot; +} + +void DistributedDBToolsUnitTest::ValueCallback( + DBStatus statusSrc, const Value &valueSrc, DBStatus &statusDst, Value &valueDst) +{ + statusDst = statusSrc; + valueDst = valueSrc; +} + +void DistributedDBToolsUnitTest::EntryVectorCallback(DBStatus statusSrc, const std::vector &entrySrc, + DBStatus &statusDst, unsigned long &matchSize, std::vector &entryDst) +{ + statusDst = statusSrc; + matchSize = static_cast(entrySrc.size()); + entryDst = entrySrc; +} + +// size need bigger than prefixkey length +std::vector DistributedDBToolsUnitTest::GetRandPrefixKey(const std::vector &prefixKey, uint32_t size) +{ + std::vector value; + if (size <= prefixKey.size()) { + return value; + } + DistributedDBToolsUnitTest::GetRandomKeyValue(value, size - prefixKey.size()); + std::vector res(prefixKey); + res.insert(res.end(), value.begin(), value.end()); + return res; +} + +void DistributedDBToolsUnitTest::GetRandomKeyValue(std::vector &value, uint32_t defaultSize) +{ + uint32_t randSize = 0; + if (defaultSize == 0) { + uint8_t simSize = 0; + RAND_bytes(&simSize, 1); + randSize = (simSize == 0) ? 1 : simSize; + } else { + randSize = defaultSize; + } + + value.resize(randSize); + RAND_bytes(value.data(), randSize); +} + +bool DistributedDBToolsUnitTest::IsValueEqual(const DistributedDB::Value &read, const DistributedDB::Value &origin) +{ + if (read != origin) { + DBCommon::PrintHexVector(read, __LINE__, "read"); + DBCommon::PrintHexVector(origin, __LINE__, "origin"); + return false; + } + + return true; +} + +bool DistributedDBToolsUnitTest::IsEntryEqual(const DistributedDB::Entry &entryOrg, + const DistributedDB::Entry &entryRet) +{ + if (entryOrg.key != entryRet.key) { + LOGD("key not equal, entryOrg key size is [%zu], entryRet key size is [%zu]", entryOrg.key.size(), + entryRet.key.size()); + return false; + } + + if (entryOrg.value != entryRet.value) { + LOGD("value not equal, entryOrg value size is [%zu], entryRet value size is [%zu]", entryOrg.value.size(), + entryRet.value.size()); + return false; + } + + return true; +} + +bool DistributedDBToolsUnitTest::IsEntriesEqual(const std::vector &entriesOrg, + const std::vector &entriesRet, bool needSort) +{ + LOGD("entriesOrg size is [%zu], entriesRet size is [%zu]", entriesOrg.size(), + entriesRet.size()); + + if (entriesOrg.size() != entriesRet.size()) { + return false; + } + std::vector entries1 = entriesOrg; + std::vector entries2 = entriesRet; + + if (needSort) { + sort(entries1.begin(), entries1.end(), CompareEntry); + sort(entries2.begin(), entries2.end(), CompareEntry); + } + + for (size_t i = 0; i < entries1.size(); i++) { + if (entries1[i].key != entries2[i].key) { + LOGE("IsEntriesEqual failed, key of index[%zu] not match", i); + return false; + } + if (entries1[i].value != entries2[i].value) { + LOGE("IsEntriesEqual failed, value of index[%zu] not match", i); + return false; + } + } + + return true; +} + +bool DistributedDBToolsUnitTest::CheckObserverResult(const std::vector &orgEntries, + const std::list &resultLst) +{ + LOGD("orgEntries.size() is [%zu], resultLst.size() is [%zu]", orgEntries.size(), + resultLst.size()); + + if (orgEntries.size() != resultLst.size()) { + return false; + } + + int index = 0; + for (auto &entry : resultLst) { + if (entry.key != orgEntries[index].key) { + LOGE("CheckObserverResult failed, key of index[%d] not match", index); + return false; + } + if (entry.value != orgEntries[index].value) { + LOGE("CheckObserverResult failed, value of index[%d] not match", index); + return false; + } + index++; + } + + return true; +} + +bool DistributedDBToolsUnitTest::IsEntryExist(const DistributedDB::Entry &entry, + const std::vector &entries) +{ + std::set> sets; + for (const auto &iter : entries) { + sets.insert(iter.key); + } + + if (entries.size() != sets.size()) { + return false; + } + sets.clear(); + bool isFound = false; + for (const auto &iter : entries) { + if (entry.key == iter.key) { + if (entry.value == iter.value) { + isFound = true; + } + break; + } + } + return isFound; +} + +bool DistributedDBToolsUnitTest::IsItemValueExist(const DistributedDB::DataItem &item, + const std::vector &items) +{ + std::set sets; + for (const auto &iter : items) { + sets.insert(iter.key); + } + + if (items.size() != sets.size()) { + return false; + } + sets.clear(); + bool isFound = false; + for (const auto &iter : items) { + if (item.key == iter.key) { + if (item.value == iter.value) { + isFound = true; + } + break; + } + } + return isFound; +} + +bool DistributedDBToolsUnitTest::IsKvEntryExist(const DistributedDB::Entry &entry, + const std::vector &entries) +{ + std::set> sets; + for (const auto &iter : entries) { + sets.insert(iter.key); + } + + if (entries.size() != sets.size()) { + return false; + } + sets.clear(); + bool isFound = false; + for (const auto &iter : entries) { + if (entry.key == iter.key) { + if (entry.value == iter.value) { + isFound = true; + } + break; + } + } + + return isFound; +} + +int DistributedDBToolsUnitTest::ModifyDatabaseFile(const std::string &fileDir, uint64_t modifyPos, + uint32_t modifyCnt, uint32_t value) +{ + LOGI("Modify database file:%s", fileDir.c_str()); + std::fstream dataFile(fileDir, std::fstream::binary | std::fstream::out | std::fstream::in); + if (!dataFile.is_open()) { + LOGD("Open the database file failed"); + return -E_UNEXPECTED_DATA; + } + + if (!dataFile.seekg(0, std::fstream::end)) { + return -E_UNEXPECTED_DATA; + } + + uint64_t fileSize; + std::ios::pos_type pos = dataFile.tellg(); + if (pos < 0) { + return -E_UNEXPECTED_DATA; + } else { + fileSize = static_cast(pos); + if (fileSize < 1024) { // the least page size is 1024 bytes. + LOGE("Invalid database file:%" PRIu64 ".", fileSize); + return -E_UNEXPECTED_DATA; + } + } + + if (fileSize <= modifyPos) { + return E_OK; + } + + if (!dataFile.seekp(modifyPos)) { + return -E_UNEXPECTED_DATA; + } + for (uint32_t i = 0; i < modifyCnt; i++) { + if (!dataFile.write(reinterpret_cast(&value), sizeof(uint32_t))) { + return -E_UNEXPECTED_DATA; + } + } + + dataFile.flush(); + return E_OK; +} + +int DistributedDBToolsUnitTest::GetSyncDataTest(const SyncInputArg &syncInputArg, SQLiteSingleVerNaturalStore *store, + std::vector &dataItems, ContinueToken &continueStmtToken) +{ + std::vector entries; + DataSizeSpecInfo syncDataSizeInfo = {syncInputArg.blockSize_, DBConstant::MAX_HPMODE_PACK_ITEM_SIZE}; + int errCode = store->GetSyncData(syncInputArg.begin_, syncInputArg.end_, entries, + continueStmtToken, syncDataSizeInfo); + + ConvertSingleVerEntryToItems(entries, dataItems); + return errCode; +} + +int DistributedDBToolsUnitTest::GetSyncDataNextTest(SQLiteSingleVerNaturalStore *store, uint32_t blockSize, + std::vector &dataItems, ContinueToken &continueStmtToken) +{ + std::vector entries; + DataSizeSpecInfo syncDataSizeInfo = {blockSize, DBConstant::MAX_HPMODE_PACK_ITEM_SIZE}; + int errCode = store->GetSyncDataNext(entries, continueStmtToken, syncDataSizeInfo); + + ConvertSingleVerEntryToItems(entries, dataItems); + return errCode; +} + +int DistributedDBToolsUnitTest::PutSyncDataTest(SQLiteSingleVerNaturalStore *store, + const std::vector &dataItems, const std::string &deviceName) +{ + QueryObject query(Query::Select()); + return PutSyncDataTest(store, dataItems, deviceName, query); +} + +int DistributedDBToolsUnitTest::PutSyncDataTest(SQLiteSingleVerNaturalStore *store, + const std::vector &dataItems, const std::string &deviceName, const QueryObject &query) +{ + std::vector entries; + std::vector items = dataItems; + for (auto &item : items) { + auto *entry = new (std::nothrow) GenericSingleVerKvEntry(); + if (entry == nullptr) { + ReleaseSingleVerEntry(entries); + return -E_OUT_OF_MEMORY; + } + entry->SetEntryData(std::move(item)); + entry->SetWriteTimestamp(entry->GetTimestamp()); + entries.push_back(entry); + } + + int errCode = store->PutSyncDataWithQuery(query, entries, deviceName); + ReleaseSingleVerEntry(entries); + return errCode; +} + +int DistributedDBToolsUnitTest::ConvertItemsToSingleVerEntry(const std::vector &dataItems, + std::vector &entries) +{ + std::vector items = dataItems; + for (auto &item : items) { + GenericSingleVerKvEntry *entry = new (std::nothrow) GenericSingleVerKvEntry(); + if (entry == nullptr) { + ReleaseSingleVerEntry(entries); + return -E_OUT_OF_MEMORY; + } + entry->SetEntryData(std::move(item)); + entries.push_back(entry); + } + return E_OK; +} + +void DistributedDBToolsUnitTest::ConvertSingleVerEntryToItems(std::vector &entries, + std::vector &dataItems) +{ + for (auto &itemEntry : entries) { + GenericSingleVerKvEntry *entry = reinterpret_cast(itemEntry); + if (entry != nullptr) { + DataItem item; + item.origDev = entry->GetOrigDevice(); + item.flag = entry->GetFlag(); + item.timestamp = entry->GetTimestamp(); + entry->GetKey(item.key); + entry->GetValue(item.value); + dataItems.push_back(item); + // clear vector entry + delete itemEntry; + itemEntry = nullptr; + } + } + entries.clear(); +} + +void DistributedDBToolsUnitTest::ReleaseSingleVerEntry(std::vector &entries) +{ + for (auto &item : entries) { + delete item; + item = nullptr; + } + entries.clear(); +} + +void DistributedDBToolsUnitTest::CalcHash(const std::vector &value, std::vector &hashValue) +{ + ValueHashCalc hashCalc; + hashCalc.Initialize(); + hashCalc.Update(value); + hashCalc.GetResult(hashValue); +} + +KvStoreObserverUnitTest::KvStoreObserverUnitTest() : callCount_(0), isCleared_(false) +{} + +void KvStoreObserverUnitTest::OnChange(const KvStoreChangedData& data) +{ + callCount_++; + inserted_ = data.GetEntriesInserted(); + updated_ = data.GetEntriesUpdated(); + deleted_ = data.GetEntriesDeleted(); + isCleared_ = data.IsCleared(); + LOGD("Onchangedata :%zu -- %zu -- %zu -- %d", inserted_.size(), updated_.size(), deleted_.size(), isCleared_); + LOGD("Onchange() called success!"); +} + +void KvStoreObserverUnitTest::ResetToZero() +{ + callCount_ = 0; + isCleared_ = false; + inserted_.clear(); + updated_.clear(); + deleted_.clear(); +} + +unsigned long KvStoreObserverUnitTest::GetCallCount() const +{ + return callCount_; +} + +const std::list &KvStoreObserverUnitTest::GetEntriesInserted() const +{ + return inserted_; +} + +const std::list &KvStoreObserverUnitTest::GetEntriesUpdated() const +{ + return updated_; +} + +const std::list &KvStoreObserverUnitTest::GetEntriesDeleted() const +{ + return deleted_; +} + +bool KvStoreObserverUnitTest::IsCleared() const +{ + return isCleared_; +} + +RelationalStoreObserverUnitTest::RelationalStoreObserverUnitTest() : callCount_(0) +{ +} + +unsigned long RelationalStoreObserverUnitTest::GetCallCount() const +{ + return callCount_; +} + +void RelationalStoreObserverUnitTest::OnChange(const StoreChangedData& data) +{ + callCount_++; + changeDevice_ = data.GetDataChangeDevice(); + data.GetStoreProperty(storeProperty_); + LOGD("Onchangedata : %s", changeDevice_.c_str()); + LOGD("Onchange() called success!"); +} + +void RelationalStoreObserverUnitTest::ResetToZero() +{ + callCount_ = 0; + changeDevice_.clear(); + storeProperty_ = {}; +} + +const std::string RelationalStoreObserverUnitTest::GetDataChangeDevice() const +{ + return changeDevice_; +} + +DistributedDB::StoreProperty RelationalStoreObserverUnitTest::GetStoreProperty() const +{ + return storeProperty_; +} + +DBStatus DistributedDBToolsUnitTest::SyncTest(KvStoreNbDelegate* delegate, + const std::vector& devices, SyncMode mode, + std::map& statuses, const Query &query) +{ + statuses.clear(); + DBStatus callStatus = delegate->Sync(devices, mode, + [&statuses, this](const std::map& statusMap) { + statuses = statusMap; + std::unique_lock innerlock(this->syncLock_); + this->syncCondVar_.notify_one(); + }, query, false); + + std::unique_lock lock(syncLock_); + syncCondVar_.wait(lock, [callStatus, &statuses]() { + if (callStatus != OK) { + return true; + } + return !statuses.empty(); + }); + return callStatus; +} + +DBStatus DistributedDBToolsUnitTest::SyncTest(KvStoreNbDelegate* delegate, + const std::vector& devices, SyncMode mode, + std::map& statuses, bool wait) +{ + statuses.clear(); + DBStatus callStatus = delegate->Sync(devices, mode, + [&statuses, this](const std::map& statusMap) { + statuses = statusMap; + std::unique_lock innerlock(this->syncLock_); + this->syncCondVar_.notify_one(); + }, wait); + if (!wait) { + std::unique_lock lock(syncLock_); + syncCondVar_.wait(lock, [callStatus, &statuses]() { + if (callStatus != OK) { + return true; + } + if (statuses.size() != 0) { + return true; + } + return false; + }); + } + return callStatus; +} + +void KvStoreCorruptInfo::CorruptCallBack(const std::string &appId, const std::string &userId, + const std::string &storeId) +{ + DatabaseInfo databaseInfo; + databaseInfo.appId = appId; + databaseInfo.userId = userId; + databaseInfo.storeId = storeId; + LOGD("appId :%s, userId:%s, storeId:%s", appId.c_str(), userId.c_str(), storeId.c_str()); + databaseInfoVect_.push_back(databaseInfo); +} + +size_t KvStoreCorruptInfo::GetDatabaseInfoSize() const +{ + return databaseInfoVect_.size(); +} + +bool KvStoreCorruptInfo::IsDataBaseCorrupted(const std::string &appId, const std::string &userId, + const std::string &storeId) const +{ + for (const auto &item : databaseInfoVect_) { + if (item.appId == appId && + item.userId == userId && + item.storeId == storeId) { + return true; + } + } + return false; +} + +void KvStoreCorruptInfo::Reset() +{ + databaseInfoVect_.clear(); +} + +int DistributedDBToolsUnitTest::GetRandInt(const int randMin, const int randMax) +{ + std::random_device randDev; + std::mt19937 genRand(randDev()); + std::uniform_int_distribution disRand(randMin, randMax); + return disRand(genRand); +} + +int64_t DistributedDBToolsUnitTest::GetRandInt64(const int64_t randMin, const int64_t randMax) +{ + std::random_device randDev; + std::mt19937_64 genRand(randDev()); + std::uniform_int_distribution disRand(randMin, randMax); + return disRand(genRand); +} + +void DistributedDBToolsUnitTest::PrintTestCaseInfo() +{ + testing::UnitTest *test = testing::UnitTest::GetInstance(); + ASSERT_NE(test, nullptr); + const testing::TestInfo *testInfo = test->current_test_info(); + ASSERT_NE(testInfo, nullptr); + LOGI("Start unit test: %s.%s", testInfo->test_case_name(), testInfo->name()); +} + +int DistributedDBToolsUnitTest::BuildMessage(const DataSyncMessageInfo &messageInfo, + DistributedDB::Message *&message) +{ + auto packet = new (std::nothrow) DataRequestPacket; + if (packet == nullptr) { + return -E_OUT_OF_MEMORY; + } + message = new (std::nothrow) Message(messageInfo.messageId_); + if (message == nullptr) { + delete packet; + packet = nullptr; + return -E_OUT_OF_MEMORY; + } + packet->SetBasicInfo(messageInfo.sendCode_, messageInfo.version_, messageInfo.mode_); + packet->SetWaterMark(messageInfo.localMark_, messageInfo.peerMark_, messageInfo.deleteMark_); + std::vector reserved {messageInfo.packetId_}; + packet->SetReserved(reserved); + message->SetMessageType(messageInfo.messageType_); + message->SetSessionId(messageInfo.sessionId_); + message->SetSequenceId(messageInfo.sequenceId_); + message->SetExternalObject(packet); + return E_OK; +} + +sqlite3 *RelationalTestUtils::CreateDataBase(const std::string &dbUri) +{ + sqlite3 *db = nullptr; + if (int r = sqlite3_open_v2(dbUri.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr) != SQLITE_OK) { + LOGE("Open database [%s] failed. %d", dbUri.c_str(), r); + if (db != nullptr) { + (void)sqlite3_close_v2(db); + db = nullptr; + } + } + return db; +} + +int RelationalTestUtils::ExecSql(sqlite3 *db, const std::string &sql) +{ + if (db == nullptr || sql.empty()) { + return -E_INVALID_ARGS; + } + char *errMsg = nullptr; + int errCode = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errMsg); + if (errCode != SQLITE_OK && errMsg != nullptr) { + LOGE("Execute sql failed. %d err: %s", errCode, errMsg); + } + sqlite3_free(errMsg); + return errCode; +} + +void RelationalTestUtils::CreateDeviceTable(sqlite3 *db, const std::string &table, const std::string &device) +{ + ASSERT_NE(db, nullptr); + std::string deviceTable = DBCommon::GetDistributedTableName(device, table); + TableInfo baseTbl; + ASSERT_EQ(SQLiteUtils::AnalysisSchema(db, table, baseTbl), E_OK); + EXPECT_EQ(SQLiteUtils::CreateSameStuTable(db, baseTbl, deviceTable), E_OK); + EXPECT_EQ(SQLiteUtils::CloneIndexes(db, table, deviceTable), E_OK); +} + +int RelationalTestUtils::CheckSqlResult(sqlite3 *db, const std::string &sql, bool &result) +{ + if (db == nullptr || sql.empty()) { + return -E_INVALID_ARGS; + } + sqlite3_stmt *stmt = nullptr; + int errCode = SQLiteUtils::GetStatement(db, sql, stmt); + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(stmt); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + result = true; + errCode = E_OK; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + result = false; + errCode = E_OK; + } +END: + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; +} + +int RelationalTestUtils::CheckTableRecords(sqlite3 *db, const std::string &table) +{ + if (db == nullptr || table.empty()) { + return -E_INVALID_ARGS; + } + int count = -1; + std::string sql = "select count(1) from " + table + ";"; + + sqlite3_stmt *stmt = nullptr; + int errCode = SQLiteUtils::GetStatement(db, sql, stmt); + if (errCode != E_OK) { + goto END; + } + + errCode = SQLiteUtils::StepWithRetry(stmt); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + count = sqlite3_column_int(stmt, 0); + } +END: + SQLiteUtils::ResetStatement(stmt, true, errCode); + return count; +} +} // namespace DistributedDBUnitTest diff --git a/mock/distributeddb/test/unittest/common/common/distributeddb_tools_unit_test.h b/mock/distributeddb/test/unittest/common/common/distributeddb_tools_unit_test.h new file mode 100644 index 00000000..02686a85 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/distributeddb_tools_unit_test.h @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_TOOLS_UNIT_TEST_H +#define DISTRIBUTEDDB_TOOLS_UNIT_TEST_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "db_types.h" +#include "kv_store_changed_data.h" +#include "kv_store_delegate_impl.h" +#include "kv_store_delegate_manager.h" +#include "kv_store_nb_delegate.h" +#include "kv_store_observer.h" +#include "kv_store_snapshot_delegate_impl.h" +#include "log_print.h" +#include "message.h" +#include "query.h" +#include "relational_store_sqlite_ext.h" +#include "store_observer.h" +#include "store_changed_data.h" +#include "single_ver_kv_entry.h" +#include "sqlite_single_ver_natural_store.h" +#include "sqlite_utils.h" +#include "sync_types.h" +#include "store_types.h" +namespace DistributedDBUnitTest { +struct DatabaseInfo { + std::string appId{}; + std::string userId{}; + std::string storeId{}; + std::string dir{}; + int dbUserVersion = 0; +}; + +struct SyncInputArg { + uint64_t begin_{}; + uint64_t end_{}; + uint32_t blockSize_{}; + SyncInputArg(uint64_t begin, uint64_t end, uint32_t blockSize) + : begin_(begin), end_(end), blockSize_(blockSize) + {} +}; + +struct DataSyncMessageInfo { + int messageId_ = DistributedDB::INVALID_MESSAGE_ID; + uint16_t messageType_ = DistributedDB::TYPE_INVALID; + uint32_t sequenceId_ = 0; + uint32_t sessionId_ = 0; + int sendCode_ = DistributedDB::E_OK; + uint32_t version_ = 0; + int32_t mode_ = DistributedDB::PUSH; + DistributedDB::WaterMark localMark_ = 0; + DistributedDB::WaterMark peerMark_ = 0; + DistributedDB::WaterMark deleteMark_ = 0; + uint64_t packetId_ = 0; +}; + +class DistributedDBToolsUnitTest final { +public: + DistributedDBToolsUnitTest() {} + ~DistributedDBToolsUnitTest() {} + + DistributedDBToolsUnitTest(const DistributedDBToolsUnitTest&) = delete; + DistributedDBToolsUnitTest& operator=(const DistributedDBToolsUnitTest&) = delete; + DistributedDBToolsUnitTest(DistributedDBToolsUnitTest&&) = delete; + DistributedDBToolsUnitTest& operator=(DistributedDBToolsUnitTest&&) = delete; + + // compare whether two vectors are equal. + template + static bool CompareVector(std::vector& vec1, std::vector& vec2) + { + if (vec1.size() != vec2.size()) { + return false; + } + for (size_t i = 0; i < vec2.size(); i++) { + if (vec1[i] != vec2[i]) { + return false; + } + } + return true; + } + + // compare whether two vectors are equal. + template + static bool CompareVectorN(std::vector& vec1, std::vector& vec2, uint32_t n) + { + if (n > std::min(vec1.size(), vec2.size())) { + return false; + } + for (uint32_t i = 0; i < n; i++) { + if (vec1[i] != vec2[i]) { + return false; + } + } + return true; + } + // init the test directory of dir. + static void TestDirInit(std::string&); + + // remove the test db files in the test directory of dir. + static int RemoveTestDbFiles(const std::string&); + + // callback function for get a KvStoreDelegate pointer. + static void KvStoreDelegateCallback(DistributedDB::DBStatus, DistributedDB::KvStoreDelegate*, + DistributedDB::DBStatus &, DistributedDB::KvStoreDelegate *&); + + // callback function for get a KvStoreDelegate pointer. + static void KvStoreNbDelegateCallback(DistributedDB::DBStatus, DistributedDB::KvStoreNbDelegate*, + DistributedDB::DBStatus &, DistributedDB::KvStoreNbDelegate *&); + + // callback function for get a KvStoreSnapshotDelegate pointer. + static void SnapshotDelegateCallback(DistributedDB::DBStatus, DistributedDB::KvStoreSnapshotDelegate*, + DistributedDB::DBStatus &, DistributedDB::KvStoreSnapshotDelegate *&); + + // callback function for get the value. + static void ValueCallback( + DistributedDB::DBStatus, const DistributedDB::Value &, DistributedDB::DBStatus &, DistributedDB::Value &); + + // callback function for get an entry vector. + static void EntryVectorCallback(DistributedDB::DBStatus, const std::vector &, + DistributedDB::DBStatus &, unsigned long &, std::vector &); + + // sync test helper + DistributedDB::DBStatus SyncTest(DistributedDB::KvStoreNbDelegate* delegate, + const std::vector& devices, DistributedDB::SyncMode mode, + std::map& statuses, bool wait = false); + + // sync test helper + DistributedDB::DBStatus SyncTest(DistributedDB::KvStoreNbDelegate* delegate, + const std::vector& devices, DistributedDB::SyncMode mode, + std::map& statuses, const DistributedDB::Query &query); + + static void GetRandomKeyValue(std::vector &value, uint32_t defaultSize = 0); + + static bool IsValueEqual(const DistributedDB::Value &read, const DistributedDB::Value &origin); + + static bool IsEntryEqual(const DistributedDB::Entry &entryOrg, const DistributedDB::Entry &entryRet); + + static bool IsEntriesEqual(const std::vector &entriesOrg, + const std::vector &entriesRet, bool needSort = false); + + static bool CheckObserverResult(const std::vector &orgEntries, + const std::list &resultLst); + + static bool IsItemValueExist(const DistributedDB::DataItem &item, + const std::vector &items); + + static bool IsEntryExist(const DistributedDB::Entry &entry, + const std::vector &entries); + + static bool IsKvEntryExist(const DistributedDB::Entry &entry, + const std::vector &entries); + + static void CalcHash(const std::vector &value, std::vector &hashValue); + + static int CreateMockSingleDb(DatabaseInfo &dbId, DistributedDB::OpenDbProperties &properties); + + static int CreateMockMultiDb(DatabaseInfo &dbInfo, DistributedDB::OpenDbProperties &properties); + + static int ModifyDatabaseFile(const std::string &fileDir, uint64_t modifyPos = 0, + uint32_t modifyCnt = 256, uint32_t value = 0x1F1F1F1F); + + static int GetSyncDataTest(const SyncInputArg &syncInputArg, DistributedDB::SQLiteSingleVerNaturalStore *store, + std::vector &dataItems, DistributedDB::ContinueToken &continueStmtToken); + + static int GetSyncDataNextTest(DistributedDB::SQLiteSingleVerNaturalStore *store, uint32_t blockSize, + std::vector &dataItems, DistributedDB::ContinueToken &continueStmtToken); + + static int PutSyncDataTest(DistributedDB::SQLiteSingleVerNaturalStore *store, + const std::vector &dataItems, const std::string &deviceName); + + static int PutSyncDataTest(DistributedDB::SQLiteSingleVerNaturalStore *store, + const std::vector &dataItems, const std::string &deviceName, + const DistributedDB::QueryObject &query); + + static int ConvertItemsToSingleVerEntry(const std::vector &dataItems, + std::vector &entries); + + static void ConvertSingleVerEntryToItems(std::vector &entries, + std::vector &dataItems); + + static void ReleaseSingleVerEntry(std::vector &entries); + + static std::vector GetRandPrefixKey(const std::vector &prefixKey, uint32_t size); + + static int GetCurrentDir(std::string& dir); + + static int GetResourceDir(std::string& dir); + + static int GetRandInt(const int randMin, const int randMax); + static int64_t GetRandInt64(const int64_t randMin, const int64_t randMax); + + static void PrintTestCaseInfo(); + + static int BuildMessage(const DataSyncMessageInfo &messageInfo, DistributedDB::Message *&message); + +private: + static int OpenMockMultiDb(DatabaseInfo &dbInfo, DistributedDB::OpenDbProperties &properties); + + std::mutex syncLock_{}; + std::condition_variable syncCondVar_{}; +}; + +class KvStoreObserverUnitTest : public DistributedDB::KvStoreObserver { +public: + KvStoreObserverUnitTest(); + ~KvStoreObserverUnitTest() {} + + KvStoreObserverUnitTest(const KvStoreObserverUnitTest&) = delete; + KvStoreObserverUnitTest& operator=(const KvStoreObserverUnitTest&) = delete; + KvStoreObserverUnitTest(KvStoreObserverUnitTest&&) = delete; + KvStoreObserverUnitTest& operator=(KvStoreObserverUnitTest&&) = delete; + + // callback function will be called when the db data is changed. + void OnChange(const DistributedDB::KvStoreChangedData&); + + // reset the callCount_ to zero. + void ResetToZero(); + + // get callback results. + unsigned long GetCallCount() const; + const std::list &GetEntriesInserted() const; + const std::list &GetEntriesUpdated() const; + const std::list &GetEntriesDeleted() const; + bool IsCleared() const; +private: + unsigned long callCount_; + bool isCleared_; + std::list inserted_; + std::list updated_; + std::list deleted_; +}; + +class RelationalStoreObserverUnitTest : public DistributedDB::StoreObserver { +public: + RelationalStoreObserverUnitTest(); + ~RelationalStoreObserverUnitTest() {} + + RelationalStoreObserverUnitTest(const RelationalStoreObserverUnitTest&) = delete; + RelationalStoreObserverUnitTest& operator=(const RelationalStoreObserverUnitTest&) = delete; + RelationalStoreObserverUnitTest(RelationalStoreObserverUnitTest&&) = delete; + RelationalStoreObserverUnitTest& operator=(RelationalStoreObserverUnitTest&&) = delete; + + // callback function will be called when the db data is changed. + void OnChange(const DistributedDB::StoreChangedData &data); + + // reset the callCount_ to zero. + void ResetToZero(); + + // get callback results. + unsigned long GetCallCount() const; + const std::string GetDataChangeDevice() const; + DistributedDB::StoreProperty GetStoreProperty() const; +private: + unsigned long callCount_; + std::string changeDevice_; + DistributedDB::StoreProperty storeProperty_; +}; + +class KvStoreCorruptInfo { +public: + KvStoreCorruptInfo() {} + ~KvStoreCorruptInfo() {} + + KvStoreCorruptInfo(const KvStoreCorruptInfo&) = delete; + KvStoreCorruptInfo& operator=(const KvStoreCorruptInfo&) = delete; + KvStoreCorruptInfo(KvStoreCorruptInfo&&) = delete; + KvStoreCorruptInfo& operator=(KvStoreCorruptInfo&&) = delete; + + // callback function will be called when the db data is changed. + void CorruptCallBack(const std::string &appId, const std::string &userId, const std::string &storeId); + size_t GetDatabaseInfoSize() const; + bool IsDataBaseCorrupted(const std::string &appId, const std::string &userId, const std::string &storeId) const; + void Reset(); +private: + std::vector databaseInfoVect_; +}; + +class RelationalTestUtils { +public: + static sqlite3 *CreateDataBase(const std::string &dbUri); + static int ExecSql(sqlite3 *db, const std::string &sql); + static void CreateDeviceTable(sqlite3 *db, const std::string &table, const std::string &device); + static int CheckSqlResult(sqlite3 *db, const std::string &sql, bool &result); + static int CheckTableRecords(sqlite3 *db, const std::string &table); +}; +} // namespace DistributedDBUnitTest + +#endif // DISTRIBUTEDDB_TOOLS_UNIT_TEST_H diff --git a/mock/distributeddb/test/unittest/common/common/evloop_timer_unit_test.cpp b/mock/distributeddb/test/unittest/common/common/evloop_timer_unit_test.cpp new file mode 100644 index 00000000..b9e48b9d --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/evloop_timer_unit_test.cpp @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_errno.h" +#include "distributeddb_tools_unit_test.h" +#include "evloop/include/ievent.h" +#include "evloop/include/ievent_loop.h" +#include "log_print.h" +#include "platform_specific.h" + +using namespace testing::ext; +using namespace DistributedDB; + +namespace { + IEventLoop *g_loop = nullptr; + constexpr int MAX_RETRY_TIMES = 1000; + constexpr int RETRY_TIMES_5 = 5; + constexpr EventTime TIME_INACCURACY = 100LL; + constexpr EventTime TIME_PIECE_1 = 1LL; + constexpr EventTime TIME_PIECE_10 = 10LL; + constexpr EventTime TIME_PIECE_50 = 50LL; + constexpr EventTime TIME_PIECE_100 = 100LL; + constexpr EventTime TIME_PIECE_1000 = 1000LL; + constexpr EventTime TIME_PIECE_10000 = 10000LL; +} + +class TimerTester { +public: + static EventTime GetCurrentTime(); +}; + +EventTime TimerTester::GetCurrentTime() +{ + uint64_t now; + int errCode = OS::GetCurrentSysTimeInMicrosecond(now); + if (errCode != E_OK) { + LOGE("Get current time failed."); + return 0; + } + return now / 1000; // 1 ms equals to 1000 us +} + +class DistributedDBEventLoopTimerTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBEventLoopTimerTest::SetUpTestCase(void) {} + +void DistributedDBEventLoopTimerTest::TearDownTestCase(void) {} + +void DistributedDBEventLoopTimerTest::SetUp(void) +{ + DistributedDBUnitTest::DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: Create a loop object. + */ + if (g_loop == nullptr) { + int errCode = E_OK; + g_loop = IEventLoop::CreateEventLoop(errCode); + if (g_loop == nullptr) { + LOGE("Prepare loop in SetUp() failed."); + } + } +} + +void DistributedDBEventLoopTimerTest::TearDown(void) +{ + /** + * @tc.teardown: Destroy the loop object. + */ + if (g_loop != nullptr) { + g_loop->KillAndDecObjRef(g_loop); + g_loop = nullptr; + } +} + +/** + * @tc.name: EventLoopTimerTest001 + * @tc.desc: Create and destroy the event loop object. + * @tc.type: FUNC + * @tc.require: AR000CKRTB AR000CQE0C + * @tc.author: fangyi + */ +HWTEST_F(DistributedDBEventLoopTimerTest, EventLoopTimerTest001, TestSize.Level0) +{ + /** + * @tc.steps: step1. create a loop. + * @tc.expected: step1. create successfully. + */ + int errCode = E_OK; + IEventLoop *loop = IEventLoop::CreateEventLoop(errCode); + ASSERT_EQ(loop != nullptr, true); + + /** + * @tc.steps: step2. destroy the loop. + * @tc.expected: step2. destroy successfully. + */ + bool finalized = false; + loop->OnLastRef([&finalized]() { finalized = true; }); + loop->DecObjRef(loop); + loop = nullptr; + EXPECT_EQ(finalized, true); +} + +/** + * @tc.name: EventLoopTimerTest002 + * @tc.desc: Start and stop the loop + * @tc.type: FUNC + * @tc.require: AR000CKRTB AR000CQE0C + * @tc.author: fangyi + */ +HWTEST_F(DistributedDBEventLoopTimerTest, EventLoopTimerTest002, TestSize.Level1) +{ + // ready data + ASSERT_EQ(g_loop != nullptr, true); + + /** + * @tc.steps: step1. create a loop. + * @tc.expected: step1. create successfully. + */ + std::atomic running(false); + EventTime delta = 0; + std::thread loopThread([&running, &delta]() { + running = true; + EventTime start = TimerTester::GetCurrentTime(); + g_loop->Run(); + EventTime end = TimerTester::GetCurrentTime(); + delta = end - start; + }); + while (!running) { + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_PIECE_1)); + } + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_PIECE_100)); + g_loop->KillObj(); + loopThread.join(); + EXPECT_EQ(delta > TIME_PIECE_50, true); +} + +/** + * @tc.name: EventLoopTimerTest003 + * @tc.desc: Create and destroy a timer object. + * @tc.type: FUNC + * @tc.require: AR000CKRTB AR000CQE0C + * @tc.author: fangyi + */ +HWTEST_F(DistributedDBEventLoopTimerTest, EventLoopTimerTest003, TestSize.Level0) +{ + /** + * @tc.steps: step1. create event(timer) object. + * @tc.expected: step1. create successfully. + */ + int errCode = E_OK; + IEvent *timer = IEvent::CreateEvent(TIME_PIECE_1, errCode); + ASSERT_EQ(timer != nullptr, true); + + /** + * @tc.steps: step2. destroy the event object. + * @tc.expected: step2. destroy successfully. + */ + bool finalized = false; + errCode = timer->SetAction([](EventsMask revents) -> int { + return E_OK; + }, [&finalized]() { + finalized = true; + }); + EXPECT_EQ(errCode, E_OK); + timer->KillAndDecObjRef(timer); + timer = nullptr; + EXPECT_EQ(finalized, true); +} + +/** + * @tc.name: EventLoopTimerTest004 + * @tc.desc: Start a timer + * @tc.type: FUNC + * @tc.require: AR000CKRTB AR000CQE0C + * @tc.author: fangyi + */ +HWTEST_F(DistributedDBEventLoopTimerTest, EventLoopTimerTest004, TestSize.Level1) +{ + // ready data + ASSERT_EQ(g_loop != nullptr, true); + + /** + * @tc.steps: step1. start the loop. + * @tc.expected: step1. start successfully. + */ + std::atomic running(false); + std::thread loopThread([&running]() { + running = true; + g_loop->Run(); + }); + + int tryCounter = 0; + while (!running && ++tryCounter < MAX_RETRY_TIMES) { + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_PIECE_1)); + } + EXPECT_EQ(running, true); + + /** + * @tc.steps: step2. create and start a timer. + * @tc.expected: step2. start successfully. + */ + int errCode = E_OK; + IEvent *timer = IEvent::CreateEvent(TIME_PIECE_10, errCode); + ASSERT_EQ(timer != nullptr, true); + std::atomic counter(0); + errCode = timer->SetAction([&counter](EventsMask revents) -> int { ++counter; return E_OK; }, nullptr); + EXPECT_EQ(errCode, E_OK); + errCode = g_loop->Add(timer); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step3. wait and check. + * @tc.expected: step3. 'counter' increased by the timer. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_PIECE_100)); + EXPECT_EQ(counter > 0, true); + g_loop->KillObj(); + loopThread.join(); + timer->DecObjRef(timer); +} + +/** + * @tc.name: EventLoopTimerTest005 + * @tc.desc: Stop a timer + * @tc.type: FUNC + * @tc.require: AR000CKRTB AR000CQE0C + * @tc.author: fangyi + */ +HWTEST_F(DistributedDBEventLoopTimerTest, EventLoopTimerTest005, TestSize.Level1) +{ + // ready data + ASSERT_EQ(g_loop != nullptr, true); + + /** + * @tc.steps: step1. start the loop. + * @tc.expected: step1. start successfully. + */ + std::atomic running(false); + std::thread loopThread([&running]() { + running = true; + g_loop->Run(); + }); + + int tryCounter = 0; + while (!running && ++tryCounter <= MAX_RETRY_TIMES) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + EXPECT_EQ(running, true); + + /** + * @tc.steps: step2. create and start a timer. + * @tc.expected: step2. start successfully. + */ + int errCode = E_OK; + IEvent *timer = IEvent::CreateEvent(10, errCode); + ASSERT_EQ(timer != nullptr, true); + std::atomic counter(0); + std::atomic finalize(false); + errCode = timer->SetAction( + [&counter](EventsMask revents) -> int { + ++counter; + return E_OK; + }, [&finalize]() { finalize = true; }); + EXPECT_EQ(errCode, E_OK); + errCode = g_loop->Add(timer); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step3. wait and check. + * @tc.expected: step3. 'counter' increased by the timer and the timer object finalized. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_PIECE_100)); + timer->KillAndDecObjRef(timer); + timer = nullptr; + g_loop->KillObj(); + loopThread.join(); + EXPECT_EQ(counter > 0, true); + EXPECT_EQ(finalize, true); +} + +/** + * @tc.name: EventLoopTimerTest006 + * @tc.desc: Stop a timer + * @tc.type: FUNC + * @tc.require: AR000CKRTB AR000CQE0C + * @tc.author: fangyi + */ +HWTEST_F(DistributedDBEventLoopTimerTest, EventLoopTimerTest006, TestSize.Level1) +{ + // ready data + ASSERT_EQ(g_loop != nullptr, true); + + /** + * @tc.steps: step1. start the loop. + * @tc.expected: step1. start successfully. + */ + std::atomic running(false); + std::thread loopThread([&running]() { + running = true; + g_loop->Run(); + }); + + int tryCounter = 0; + while (!running && ++tryCounter <= MAX_RETRY_TIMES) { + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_PIECE_10)); + } + EXPECT_EQ(running, true); + + /** + * @tc.steps: step2. create and start a timer. + * @tc.expected: step2. start successfully. + */ + int errCode = E_OK; + IEvent *timer = IEvent::CreateEvent(TIME_PIECE_10, errCode); + ASSERT_EQ(timer != nullptr, true); + std::atomic counter(0); + std::atomic finalize(false); + errCode = timer->SetAction([&counter](EventsMask revents) -> int { ++counter; return -E_STALE; }, + [&finalize]() { finalize = true; }); + EXPECT_EQ(errCode, E_OK); + errCode = g_loop->Add(timer); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step3. wait and check. + * @tc.expected: step3. 'counter' increased by the timer and the timer object finalized. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_PIECE_100)); + g_loop->KillObj(); + loopThread.join(); + timer->DecObjRef(timer); + timer = nullptr; + EXPECT_EQ(finalize, true); + EXPECT_EQ(counter > 0, true); +} + +/** + * @tc.name: EventLoopTimerTest007 + * @tc.desc: Modify a timer + * @tc.type: FUNC + * @tc.require: AR000CKRTB AR000CQE0C + * @tc.author: fangyi + */ +HWTEST_F(DistributedDBEventLoopTimerTest, EventLoopTimerTest007, TestSize.Level2) +{ + // ready data + ASSERT_EQ(g_loop != nullptr, true); + + /** + * @tc.steps: step1. start the loop. + * @tc.expected: step1. start successfully. + */ + std::atomic running(false); + std::thread loopThread([&running]() { + running = true; + g_loop->Run(); + }); + + int tryCounter = 0; + while (!running && ++tryCounter <= MAX_RETRY_TIMES) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + EXPECT_EQ(running, true); + + /** + * @tc.steps: step2. create and start a timer. + * @tc.expected: step2. start successfully. + */ + int errCode = E_OK; + IEvent *timer = IEvent::CreateEvent(TIME_PIECE_1000, errCode); + ASSERT_EQ(timer != nullptr, true); + int counter = 1; // Interval: 1 * TIME_PIECE_100 + EventTime lastTime = TimerTester::GetCurrentTime(); + errCode = timer->SetAction( + [timer, &counter, &lastTime](EventsMask revents) -> int { + EventTime now = TimerTester::GetCurrentTime(); + EventTime delta = now - lastTime; + delta -= counter * TIME_PIECE_1000; + EXPECT_EQ(delta >= -TIME_INACCURACY && delta <= TIME_INACCURACY, true); + if (++counter > RETRY_TIMES_5) { + return -E_STALE; + } + lastTime = TimerTester::GetCurrentTime(); + int ret = timer->SetTimeout(counter * TIME_PIECE_1000); + EXPECT_EQ(ret, E_OK); + return E_OK; + }, nullptr); + EXPECT_EQ(errCode, E_OK); + errCode = g_loop->Add(timer); + EXPECT_EQ(errCode, E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_PIECE_10000)); + g_loop->KillObj(); + loopThread.join(); + timer->DecObjRef(timer); +} diff --git a/mock/distributeddb/test/unittest/common/common/process_communicator_test_stub.h b/mock/distributeddb/test/unittest/common/common/process_communicator_test_stub.h new file mode 100644 index 00000000..1a31ba62 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/common/process_communicator_test_stub.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 PROCESSCOMMUNICATOR_TEST_STUB_H_H +#define PROCESSCOMMUNICATOR_TEST_STUB_H_H + +#include +#include +#include +#include + +#include "iprocess_communicator.h" +#include "store_types.h" + +namespace DistributedDB { +class ProcessCommunicatorTestStub : public IProcessCommunicator { +public: + ProcessCommunicatorTestStub() {} + ~ProcessCommunicatorTestStub() override {} + + DBStatus Start(const std::string &processLabel) override + { + return OK; + } + + // The Stop should only be called after Start successfully + DBStatus Stop() override + { + return OK; + } + + DBStatus RegOnDeviceChange(const OnDeviceChange &callback) override + { + return OK; + } + DBStatus RegOnDataReceive(const OnDataReceive &callback) override + { + return OK; + } + + DBStatus SendData(const DeviceInfos &dstDevInfo, const uint8_t *data, uint32_t length) override + { + if (isCommErr) { + return COMM_FAILURE; + } + return OK; + } + + uint32_t GetMtuSize() override + { + return 1 * 1024 * 1024; // 1MB + } + + DeviceInfos GetLocalDeviceInfos() override + { + DeviceInfos info; + info.identifier = "default"; + return info; + } + + std::vector GetRemoteOnlineDeviceInfosList() override + { + std::vector info; + return info; + } + + bool IsSameProcessLabelStartedOnPeerDevice(const DeviceInfos &peerDevInfo) override + { + return true; + } + + void SetCommErr(bool commErr) + { + isCommErr = commErr; + } +private: + bool isCommErr = false; +}; +} // namespace DistributedDB + +#endif // PROCESSCOMMUNICATOR_TEST_STUB_H_H diff --git a/mock/distributeddb/test/unittest/common/communicator/adapter_stub.cpp b/mock/distributeddb/test/unittest/common/communicator/adapter_stub.cpp new file mode 100644 index 00000000..6304efdd --- /dev/null +++ b/mock/distributeddb/test/unittest/common/communicator/adapter_stub.cpp @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "adapter_stub.h" +#include +#include "db_errno.h" +#include "endian_convert.h" +#include "frame_header.h" +#include "iprocess_communicator.h" +#include "log_print.h" +#include "distributeddb_communicator_common.h" + +using namespace DistributedDB; + +namespace { + const uint32_t STUB_MTU_SIZE = 5 * 1024 * 1024; // 5 M, 1024 is scale + const uint32_t STUB_TIME_OUT = 5 * 1000; // 5 S, 1000 is scale +} + +/* + * Override Part + */ +AdapterStub::~AdapterStub() +{ + // Do nothing +} + +int AdapterStub::StartAdapter() +{ + return E_OK; +} + +void AdapterStub::StopAdapter() +{ + // Do nothing +} + +uint32_t AdapterStub::GetMtuSize() +{ + return STUB_MTU_SIZE; +} + +uint32_t AdapterStub::GetMtuSize(const std::string &target) +{ + (void)target; + return GetMtuSize(); +} + +uint32_t AdapterStub::GetTimeout() +{ + return STUB_TIME_OUT; +} + +uint32_t AdapterStub::GetTimeout(const std::string &target) +{ + (void)target; + return GetTimeout(); +} + +int AdapterStub::GetLocalIdentity(std::string &outTarget) +{ + outTarget = localTarget_; + return E_OK; +} + +int AdapterStub::SendBytes(const std::string &dstTarget, const uint8_t *bytes, uint32_t length) +{ + LOGI("[UT][Stub][Send] Send length=%u to dstTarget=%s begin.", length, dstTarget.c_str()); + ApplySendBlock(); + + if (QuerySendRetry(dstTarget)) { + LOGI("[UT][Stub][Send] Retry for %s true.", dstTarget.c_str()); + return -E_WAIT_RETRY; + } + + if (QuerySendTotalLoss()) { + LOGI("[UT][Stub][Send] Total loss for %s true.", dstTarget.c_str()); + return E_OK; + } + + if (QuerySendPartialLoss()) { + LOGI("[UT][Stub][Send] Partial loss for %s true.", dstTarget.c_str()); + return E_OK; + } + + std::lock_guard onChangeLockGuard(onChangeMutex_); + if (targetMapAdapter_.count(dstTarget) == 0) { + LOGI("[UT][Stub][Send] dstTarget=%s not found.", dstTarget.c_str()); + return -E_NOT_FOUND; + } + + ApplySendBitError(bytes, length); + + AdapterStub *toAdapter = targetMapAdapter_[dstTarget]; + toAdapter->DeliverBytes(localTarget_, bytes, length); + LOGI("[UT][Stub][Send] Send to dstTarget=%s end.", dstTarget.c_str()); + return E_OK; +} + +int AdapterStub::RegBytesReceiveCallback(const BytesReceiveCallback &onReceive, const Finalizer &inOper) +{ + std::lock_guard onReceiveLockGuard(onReceiveMutex_); + return RegCallBack(onReceive, onReceiveHandle_, inOper, onReceiveFinalizer_); +} + +int AdapterStub::RegTargetChangeCallback(const TargetChangeCallback &onChange, const Finalizer &inOper) +{ + std::lock_guard onChangeLockGuard(onChangeMutex_); + return RegCallBack(onChange, onChangeHandle_, inOper, onChangeFinalizer_); +} + +int AdapterStub::RegSendableCallback(const SendableCallback &onSendable, const Finalizer &inOper) +{ + std::lock_guard onSendableLockGuard(onSendableMutex_); + return RegCallBack(onSendable, onSendableHandle_, inOper, onSendableFinalizer_); +} + + +bool AdapterStub::IsDeviceOnline(const std::string &device) +{ + (void)device; + return true; +} + +std::shared_ptr AdapterStub::GetExtendHeaderHandle(const ExtendInfo ¶mInfo) +{ + std::shared_ptr handle = std::make_shared(paramInfo); + return handle; +} +/* + * Extended Part + */ +void AdapterStub::ConnectAdapterStub(AdapterStub *thisStub, AdapterStub *thatStub) +{ + LOGI("[UT][Stub][ConnectAdapter] thisStub=%s, thatStub=%s.", thisStub->GetLocalTarget().c_str(), + thatStub->GetLocalTarget().c_str()); + thisStub->Connect(thatStub); + thatStub->Connect(thisStub); +} + +void AdapterStub::DisconnectAdapterStub(AdapterStub *thisStub, AdapterStub *thatStub) +{ + LOGI("[UT][Stub][DisconnectAdapter] thisStub=%s, thatStub=%s.", thisStub->GetLocalTarget().c_str(), + thatStub->GetLocalTarget().c_str()); + thisStub->Disconnect(thatStub); + thatStub->Disconnect(thisStub); +} + +AdapterStub::AdapterStub(const std::string &inLocalTarget) + : localTarget_(inLocalTarget) +{ +} + +const std::string &AdapterStub::GetLocalTarget() +{ + return localTarget_; +} + +void AdapterStub::Connect(AdapterStub *inStub) +{ + LOGI("[UT][Stub][Connect] thisStub=%s, thatStub=%s.", localTarget_.c_str(), inStub->GetLocalTarget().c_str()); + std::lock_guard onChangeLockGuard(onChangeMutex_); + targetMapAdapter_[inStub->GetLocalTarget()] = inStub; + if (onChangeHandle_) { + onChangeHandle_(inStub->GetLocalTarget(), true); + } +} + +void AdapterStub::Disconnect(AdapterStub *inStub) +{ + LOGI("[UT][Stub][Disconnect] thisStub=%s, thatStub=%s.", localTarget_.c_str(), inStub->GetLocalTarget().c_str()); + std::lock_guard onChangeLockGuard(onChangeMutex_); + targetMapAdapter_.erase(inStub->GetLocalTarget()); + if (onChangeHandle_) { + onChangeHandle_(inStub->GetLocalTarget(), false); + } +} + +void AdapterStub::DeliverBytes(const std::string &srcTarget, const uint8_t *bytes, uint32_t length) +{ + std::lock_guard onReceiveLockGuard(onReceiveMutex_); + if (onReceiveHandle_) { + uint32_t headLength = 0; + std::string userId; + CheckAndGetDataHeadInfo(bytes, length, headLength, userId); + onReceiveHandle_(srcTarget, bytes + headLength, length - headLength, userId); + } +} + +void AdapterStub::CheckAndGetDataHeadInfo(const uint8_t *data, uint32_t totalLen, uint32_t &headLength, + std::string &userId) +{ + auto info = reinterpret_cast(data); + NetToHost(info->magic); + if (info->magic == ExtendHeaderHandleTest::MAGIC_NUM) { + NetToHost(info->length); + NetToHost(info->version); + headLength = info->length; + std::string tmpUserId(BUFF_LEN, 0); + for (uint8_t i = 0; i < BUFF_LEN; i++) { + tmpUserId[i] = info->userId[i]; + } + userId = tmpUserId; + } else { + headLength = 0; + } +} + +/* + * Simulate Part + */ +void AdapterStub::SimulateSendBlock() +{ + LOGI("[UT][Stub][Block] Before Lock."); + block_.lock(); + LOGI("[UT][Stub][Block] After Lock."); +} + +void AdapterStub::SimulateSendBlockClear() +{ + LOGI("[UT][Stub][UnBlock] Before UnLock."); + block_.unlock(); + LOGI("[UT][Stub][UnBlock] After UnLock."); +} + +void AdapterStub::SimulateSendRetry(const std::string &dstTarget) +{ + std::lock_guard retryLockGuard(retryMutex_); + targetRetrySet_.insert(dstTarget); +} + +void AdapterStub::SimulateSendRetryClear(const std::string &dstTarget) +{ + bool isSetBefore = false; + { + std::lock_guard retryLockGuard(retryMutex_); + if (targetRetrySet_.count(dstTarget) == 0) { + return; + } + isSetBefore = true; + targetRetrySet_.erase(dstTarget); + } + if (isSetBefore) { + std::lock_guard onSendableLockGuard(onSendableMutex_); + if (onSendableHandle_) { + onSendableHandle_(dstTarget); + } + } +} + +void AdapterStub::SimulateSendPartialLoss() +{ + isPartialLossSimulated_ = true; +} + +void AdapterStub::SimulateSendPartialLossClear() +{ + isPartialLossSimulated_ = false; +} + +void AdapterStub::SimulateSendTotalLoss() +{ + isTotalLossSimulated_ = true; +} + +void AdapterStub::SimulateSendTotalLossClear() +{ + isTotalLossSimulated_ = false; +} + +void AdapterStub::SimulateSendBitErrorInMagicField(bool doFlag, uint16_t inMagic) +{ + doChangeMagicFlag_ = doFlag; + magicField_ = inMagic; +} + +void AdapterStub::SimulateSendBitErrorInVersionField(bool doFlag, uint16_t inVersion) +{ + doChangeVersionFlag_ = doFlag; + versionField_ = inVersion; +} + +void AdapterStub::SimulateSendBitErrorInCheckSumField(bool doFlag, uint64_t inCheckSum) +{ + doChangeCheckSumFlag_ = doFlag; + checkSumField_ = inCheckSum; +} + +void AdapterStub::SimulateSendBitErrorInPacketLenField(bool doFlag, uint32_t inPacketLen) +{ + doChangePacketLenFlag_ = doFlag; + packetLenField_ = inPacketLen; +} + +void AdapterStub::SimulateSendBitErrorInPacketTypeField(bool doFlag, uint8_t inPacketType) +{ + doChangePacketTypeFlag_ = doFlag; + packetTypeField_ = inPacketType; +} + +void AdapterStub::SimulateSendBitErrorInPaddingLenField(bool doFlag, uint8_t inPaddingLen) +{ + doChangePaddingLenFlag_ = doFlag; + paddingLenField_ = inPaddingLen; +} + +void AdapterStub::SimulateSendBitErrorInMessageIdField(bool doFlag, uint32_t inMessageId) +{ + doChangeMessageIdFlag_ = doFlag; + messageIdField_ = inMessageId; +} + +void AdapterStub::ApplySendBlock() +{ + LOGI("[UT][Stub][ApplyBlock] Before Lock&UnLock."); + block_.lock(); + block_.unlock(); + LOGI("[UT][Stub][ApplyBlock] After Lock&UnLock."); +} + +bool AdapterStub::QuerySendRetry(const std::string &dstTarget) +{ + std::lock_guard retryLockGuard(retryMutex_); + if (targetRetrySet_.count(dstTarget) == 0) { + return false; + } else { + return true; + } +} + +bool AdapterStub::QuerySendPartialLoss() +{ + if (isPartialLossSimulated_) { + uint64_t count = countForPartialLoss_.fetch_add(1, std::memory_order_seq_cst); + if (count % 2 == 0) { // 2 is half + return true; + } + } + return false; +} + +bool AdapterStub::QuerySendTotalLoss() +{ + return isTotalLossSimulated_; +} + +namespace { +uint64_t CalculateXorSum(const uint8_t *bytes, uint32_t length) +{ + if (length % sizeof(uint64_t) != 0) { + return 0; + } + int count = length / sizeof(uint64_t); + auto array = reinterpret_cast(bytes); + uint64_t outSum = 0; + for (int i = 0; i < count; i++) { + outSum ^= array[i]; + } + return outSum; +} +const uint32_t LENGTH_BEFORE_SUM_RANGE = sizeof(uint64_t) + sizeof(uint64_t); +} + +void AdapterStub::ApplySendBitError(const uint8_t *bytes, uint32_t length) +{ + // Change field in CommPhyHeader + if (length < sizeof(CommPhyHeader)) { + return; + } + auto edibleBytes = const_cast(bytes); + auto phyHeader = reinterpret_cast(edibleBytes); + if (doChangeMagicFlag_) { + phyHeader->magic = HostToNet(magicField_); + } + if (doChangeVersionFlag_) { + phyHeader->version = HostToNet(versionField_); + } + if (doChangeCheckSumFlag_) { + phyHeader->checkSum = HostToNet(checkSumField_); + } + if (doChangePacketLenFlag_) { + phyHeader->packetLen = HostToNet(packetLenField_); + } + if (doChangePacketTypeFlag_) { + phyHeader->packetType = HostToNet(packetTypeField_); + } + if (doChangePaddingLenFlag_) { + phyHeader->paddingLen = HostToNet(paddingLenField_); + } + // Change field in MessageHeader. Assumpt that no fragment + if (length < sizeof(CommPhyHeader) + sizeof(CommDivergeHeader) + sizeof(MessageHeader)) { + return; + } + edibleBytes += (sizeof(CommPhyHeader) + sizeof(CommDivergeHeader)); + auto msgHeader = reinterpret_cast(edibleBytes); + if (doChangeMessageIdFlag_) { + msgHeader->messageId = HostToNet(messageIdField_); + phyHeader->checkSum = HostToNet(CalculateXorSum(bytes + LENGTH_BEFORE_SUM_RANGE, + length - LENGTH_BEFORE_SUM_RANGE)); + } +} diff --git a/mock/distributeddb/test/unittest/common/communicator/adapter_stub.h b/mock/distributeddb/test/unittest/common/communicator/adapter_stub.h new file mode 100644 index 00000000..bc942a5a --- /dev/null +++ b/mock/distributeddb/test/unittest/common/communicator/adapter_stub.h @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 ADAPTER_STUB_H +#define ADAPTER_STUB_H + +#include +#include +#include +#include +#include +#include +#include "iadapter.h" + +namespace DistributedDB { +class AdapterStub : public IAdapter { +public: + /* + * Override Part + */ + ~AdapterStub() override; + + int StartAdapter() override; + void StopAdapter() override; + uint32_t GetMtuSize() override; + uint32_t GetMtuSize(const std::string &target) override; + uint32_t GetTimeout() override; + uint32_t GetTimeout(const std::string &target) override; + int GetLocalIdentity(std::string &outTarget) override; + + int SendBytes(const std::string &dstTarget, const uint8_t *bytes, uint32_t length) override; + + int RegBytesReceiveCallback(const BytesReceiveCallback &onReceive, const Finalizer &inOper) override; + int RegTargetChangeCallback(const TargetChangeCallback &onChange, const Finalizer &inOper) override; + int RegSendableCallback(const SendableCallback &onSendable, const Finalizer &inOper) override; + + bool IsDeviceOnline(const std::string &device) override; + + std::shared_ptr GetExtendHeaderHandle(const ExtendInfo ¶mInfo) override; + + void CheckAndGetDataHeadInfo(const uint8_t *data, uint32_t totalLen, uint32_t &headLength, + std::string &userId); + + /* + * Extended Part + */ + static void ConnectAdapterStub(AdapterStub *thisStub, AdapterStub *thatStub); + static void DisconnectAdapterStub(AdapterStub *thisStub, AdapterStub *thatStub); + + explicit AdapterStub(const std::string &inLocalTarget); + const std::string &GetLocalTarget(); + + void SimulateSendBlock(); + void SimulateSendBlockClear(); + + void SimulateSendRetry(const std::string &dstTarget); + void SimulateSendRetryClear(const std::string &dstTarget); + + void SimulateSendPartialLoss(); + void SimulateSendPartialLossClear(); + + void SimulateSendTotalLoss(); + void SimulateSendTotalLossClear(); + + void SimulateSendBitErrorInMagicField(bool doFlag, uint16_t inMagic); + void SimulateSendBitErrorInVersionField(bool doFlag, uint16_t inVersion); + void SimulateSendBitErrorInCheckSumField(bool doFlag, uint64_t inCheckSum); + void SimulateSendBitErrorInPacketLenField(bool doFlag, uint32_t inPacketLen); + void SimulateSendBitErrorInPacketTypeField(bool doFlag, uint8_t inPacketType); + void SimulateSendBitErrorInPaddingLenField(bool doFlag, uint8_t inPaddingLen); + void SimulateSendBitErrorInMessageIdField(bool doFlag, uint32_t inMessageId); +private: + void Connect(AdapterStub *inStub); + void Disconnect(AdapterStub *inStub); + void DeliverBytes(const std::string &srcTarget, const uint8_t *bytes, uint32_t length); + + void ApplySendBlock(); + bool QuerySendRetry(const std::string &dstTarget); + bool QuerySendPartialLoss(); + bool QuerySendTotalLoss(); + void ApplySendBitError(const uint8_t *bytes, uint32_t length); + + std::string localTarget_; + std::map targetMapAdapter_; + + BytesReceiveCallback onReceiveHandle_; + TargetChangeCallback onChangeHandle_; + SendableCallback onSendableHandle_; + Finalizer onReceiveFinalizer_; + Finalizer onChangeFinalizer_; + Finalizer onSendableFinalizer_; + std::mutex onReceiveMutex_; + std::mutex onChangeMutex_; + std::mutex onSendableMutex_; + + // Member for simulation + std::mutex block_; + + std::mutex retryMutex_; + std::set targetRetrySet_; + + std::atomic isPartialLossSimulated_{false}; + std::atomic countForPartialLoss_{0}; + + std::atomic isTotalLossSimulated_{false}; + + bool doChangeMagicFlag_ = false; + bool doChangeVersionFlag_ = false; + bool doChangeCheckSumFlag_ = false; + bool doChangePacketLenFlag_ = false; + bool doChangePacketTypeFlag_ = false; + bool doChangePaddingLenFlag_ = false; + bool doChangeMessageIdFlag_ = false; + uint16_t magicField_ = 0; + uint16_t versionField_ = 0; + uint64_t checkSumField_ = 0; + uint32_t packetLenField_ = 0; + uint8_t packetTypeField_ = 0; + uint8_t paddingLenField_ = 0; + uint32_t messageIdField_ = 0; +}; +} + +#endif // ADAPTER_STUB_H \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_common.cpp b/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_common.cpp new file mode 100644 index 00000000..27d86ff9 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_common.cpp @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_communicator_common.h" +#include +#include "db_errno.h" +#include "log_print.h" +#include "message_transform.h" +#include "securec.h" + +using namespace std; +using namespace DistributedDB; + +bool SetUpEnv(EnvHandle &inEnv, const string &inName) +{ + if (inEnv.adapterHandle != nullptr || inEnv.commAggrHandle != nullptr) { + LOGI("[UT][Common][SetUp] Already Setup for %s", inName.c_str()); + return false; + } + + inEnv.adapterHandle = new (nothrow) AdapterStub(inName); + if (inEnv.adapterHandle == nullptr) { + LOGI("[UT][Common][SetUp] Create AdapterStub fail for %s", inName.c_str()); + return false; + } + + inEnv.commAggrHandle = new (nothrow) CommunicatorAggregator(); + if (inEnv.commAggrHandle == nullptr) { + LOGI("[UT][Common][SetUp] Create CommunicatorAggregator fail for %s", inName.c_str()); + return false; + } + + int errCode = inEnv.commAggrHandle->Initialize(inEnv.adapterHandle); + if (errCode != E_OK) { + LOGI("[UT][Common][SetUp] Init CommunicatorAggregator fail for %s", inName.c_str()); + return false; + } + + return true; +} + +void TearDownEnv(EnvHandle &inEnv) +{ + if (inEnv.commAggrHandle != nullptr) { + inEnv.commAggrHandle->Finalize(); + inEnv.commAggrHandle->DecObjRef(inEnv.commAggrHandle); + inEnv.commAggrHandle = nullptr; + } + + if (inEnv.adapterHandle != nullptr) { + delete inEnv.adapterHandle; + inEnv.adapterHandle = nullptr; + } +} + +static void RegFuncForTinyMsg() +{ + TransformFunc funcForTinyMsg; + funcForTinyMsg.computeFunc = [](const Message *inMsg)->uint32_t{return TINY_SIZE;}; + funcForTinyMsg.serializeFunc = [](uint8_t *buffer, uint32_t length, const Message *inMsg)->int{ + const RegedTinyObject *outObj = inMsg->GetObject(); + EXPECT_NE(outObj, nullptr); + return E_OK; + }; + funcForTinyMsg.deserializeFunc = [](const uint8_t *buffer, uint32_t length, Message *inMsg)->int{ + int errCode = inMsg->SetCopiedObject(RegedTinyObject()); + EXPECT_EQ(errCode, E_OK); + return E_OK; + }; + + MessageTransform::RegTransformFunction(REGED_TINY_MSG_ID, funcForTinyMsg); +} + +static void RegFuncForHugeMsg() +{ + TransformFunc funcForHugeMsg; + funcForHugeMsg.computeFunc = [](const Message *inMsg)->uint32_t{return HUGE_SIZE;}; + funcForHugeMsg.serializeFunc = [](uint8_t *buffer, uint32_t length, const Message *inMsg)->int{ + const RegedHugeObject *outObj = inMsg->GetObject(); + EXPECT_NE(outObj, nullptr); + return E_OK; + }; + funcForHugeMsg.deserializeFunc = [](const uint8_t *buffer, uint32_t length, Message *inMsg)->int{ + int errCode = inMsg->SetCopiedObject(RegedHugeObject()); + EXPECT_EQ(errCode, E_OK); + return E_OK; + }; + + MessageTransform::RegTransformFunction(REGED_HUGE_MSG_ID, funcForHugeMsg); +} + +static void RegFuncForGiantMsg() +{ + TransformFunc funcForGiantMsg; + funcForGiantMsg.computeFunc = [](const Message *inMsg)->uint32_t{ + const RegedGiantObject *outObj = inMsg->GetObject(); + if (outObj == nullptr) { + return 0; + } + return outObj->rawData_.size(); + }; + funcForGiantMsg.serializeFunc = [](uint8_t *buffer, uint32_t length, const Message *inMsg)->int{ + const RegedGiantObject *outObj = inMsg->GetObject(); + if (outObj == nullptr) { + return -E_INVALID_ARGS; + } + if (outObj->rawData_.size() != length) { + return -E_LENGTH_ERROR; + } + errno_t errCode = memcpy_s(buffer, length, &(outObj->rawData_[0]), length); + if (errCode != EOK) { + return -E_SECUREC_ERROR; + } + return E_OK; + }; + funcForGiantMsg.deserializeFunc = [](const uint8_t *buffer, uint32_t length, Message *inMsg)->int{ + RegedGiantObject *obj = new (nothrow) RegedGiantObject(); + if (obj == nullptr) { + return -E_OUT_OF_MEMORY; + } + obj->rawData_.resize(length); + errno_t retCode = memcpy_s(&(obj->rawData_[0]), length, buffer, length); + if (retCode != EOK) { + delete obj; + return -E_SECUREC_ERROR; + } + int errCode = inMsg->SetExternalObject(obj); + if (errCode != E_OK) { + delete obj; + return errCode; + } + return E_OK; + }; + + MessageTransform::RegTransformFunction(REGED_GIANT_MSG_ID, funcForGiantMsg); +} + +static void RegFuncForOverSizeMsg() +{ + TransformFunc funcForOverSizeMsg; + funcForOverSizeMsg.computeFunc = [](const Message *inMsg)->uint32_t{return OVER_SIZE;}; + funcForOverSizeMsg.serializeFunc = [](uint8_t *buffer, uint32_t length, const Message *inMsg)->int{ + const RegedOverSizeObject *outObj = inMsg->GetObject(); + EXPECT_NE(outObj, nullptr); + return E_OK; + }; + funcForOverSizeMsg.deserializeFunc = [](const uint8_t *buffer, uint32_t length, Message *inMsg)->int{ + int errCode = inMsg->SetCopiedObject(RegedOverSizeObject()); + EXPECT_EQ(errCode, E_OK); + return E_OK; + }; + + MessageTransform::RegTransformFunction(REGED_OVERSIZE_MSG_ID, funcForOverSizeMsg); +} + +void DoRegTransformFunction() +{ + RegFuncForTinyMsg(); + RegFuncForHugeMsg(); + RegFuncForGiantMsg(); + RegFuncForOverSizeMsg(); +} + +Message *BuildRegedTinyMessage() +{ + RegedTinyObject *obj = new (nothrow) RegedTinyObject(); + if (obj == nullptr) { + return nullptr; + } + + Message *outMsg = new (nothrow) Message(REGED_TINY_MSG_ID); + if (outMsg == nullptr) { + delete obj; + obj = nullptr; + return nullptr; + } + + int errCode = outMsg->SetExternalObject(obj); + if (errCode != E_OK) { + delete obj; + obj = nullptr; + delete outMsg; + outMsg = nullptr; + return nullptr; + } + outMsg->SetMessageType(TYPE_REQUEST); + outMsg->SetSessionId(FIXED_SESSIONID); + outMsg->SetSequenceId(FIXED_SEQUENCEID); + + return outMsg; +} + +Message *BuildRegedHugeMessage() +{ + RegedHugeObject *obj = new (nothrow) RegedHugeObject(); + if (obj == nullptr) { + return nullptr; + } + + Message *outMsg = new (nothrow) Message(REGED_HUGE_MSG_ID); + if (outMsg == nullptr) { + delete obj; + return nullptr; + } + + int errCode = outMsg->SetExternalObject(obj); + if (errCode != E_OK) { + delete obj; + obj = nullptr; + delete outMsg; + outMsg = nullptr; + return nullptr; + } + outMsg->SetMessageType(TYPE_RESPONSE); + outMsg->SetSessionId(FIXED_SESSIONID); + outMsg->SetSequenceId(FIXED_SEQUENCEID); + + return outMsg; +} + +// length should be a multiple of four +Message *BuildRegedGiantMessage(uint32_t length) +{ + uint32_t count = length / sizeof(uint32_t); + if (count == 0) { + return nullptr; + } + + RegedGiantObject *obj = new (nothrow) RegedGiantObject(); + if (obj == nullptr) { + return nullptr; + } + + Message *outMsg = new (nothrow) Message(REGED_GIANT_MSG_ID); + if (outMsg == nullptr) { + delete obj; + obj = nullptr; + return nullptr; + } + + obj->rawData_.resize(count * sizeof(uint32_t)); + auto dataPtr = reinterpret_cast(&(obj->rawData_[0])); + uint32_t value = 0; + while (value < count) { + *dataPtr++ = value++; + } + + int errCode = outMsg->SetExternalObject(obj); + if (errCode != E_OK) { + delete obj; + obj = nullptr; + delete outMsg; + outMsg = nullptr; + return nullptr; + } + outMsg->SetMessageType(TYPE_NOTIFY); + outMsg->SetSessionId(FIXED_SESSIONID); + outMsg->SetSequenceId(FIXED_SEQUENCEID); + + return outMsg; +} + +Message *BuildRegedOverSizeMessage() +{ + RegedOverSizeObject *obj = new (nothrow) RegedOverSizeObject(); + if (obj == nullptr) { + return nullptr; + } + + Message *outMsg = new (nothrow) Message(REGED_OVERSIZE_MSG_ID); + if (outMsg == nullptr) { + delete obj; + return nullptr; + } + + int errCode = outMsg->SetExternalObject(obj); + if (errCode != E_OK) { + delete obj; + obj = nullptr; + delete outMsg; + outMsg = nullptr; + return nullptr; + } + outMsg->SetMessageType(TYPE_NOTIFY); + outMsg->SetSessionId(FIXED_SESSIONID); + outMsg->SetSequenceId(FIXED_SEQUENCEID); + + return outMsg; +} + +Message *BuildUnRegedTinyMessage() +{ + UnRegedTinyObject *obj = new (nothrow) UnRegedTinyObject(); + if (obj == nullptr) { + return nullptr; + } + + Message *outMsg = new (nothrow) Message(UNREGED_TINY_MSG_ID); + if (outMsg == nullptr) { + delete obj; + return nullptr; + } + + int errCode = outMsg->SetExternalObject(obj); + if (errCode != E_OK) { + delete obj; + obj = nullptr; + delete outMsg; + outMsg = nullptr; + return nullptr; + } + outMsg->SetMessageType(TYPE_NOTIFY); + outMsg->SetSessionId(FIXED_SESSIONID); + outMsg->SetSequenceId(FIXED_SEQUENCEID); + + return outMsg; +} + +bool RegedGiantObject::CheckEqual(const RegedGiantObject &inLeft, const RegedGiantObject &inRight) +{ + if (inLeft.rawData_.size() != inRight.rawData_.size()) { + return false; + } + uint32_t index = 0; + for (auto &left : inLeft.rawData_) { + uint8_t right = inRight.rawData_[index]; + if (left != right) { + LOGE("[RegedGiantObject][CheckEqual] RawData unequal at index=%u", index); + return false; + } + index++; + } + return true; +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_common.h b/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_common.h new file mode 100644 index 00000000..11be295a --- /dev/null +++ b/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_common.h @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_COMMUNICATOR_COMMON_H +#define DISTRIBUTEDDB_COMMUNICATOR_COMMON_H + +#include +#include +#include +#include "endian_convert.h" +#include "message.h" +#include "adapter_stub.h" +#include "frame_header.h" +#include "iprocess_communicator.h" +#include "communicator_aggregator.h" +#include "store_types.h" + +struct EnvHandle { + DistributedDB::AdapterStub *adapterHandle = nullptr; + DistributedDB::CommunicatorAggregator *commAggrHandle = nullptr; +}; + +struct OnOfflineDevice { + std::set onlineDevices; + std::string latestOnlineDevice; + std::string latestOfflineDevice; +}; + +bool SetUpEnv(EnvHandle &inEnv, const std::string &inName); +void TearDownEnv(EnvHandle &inEnv); + +struct RegedTinyObject { + uint32_t placeHolder_ = 0; +}; + +struct RegedHugeObject { + uint32_t placeHolder_ = 0; +}; + +struct RegedGiantObject { + std::vector rawData_; + static bool CheckEqual(const RegedGiantObject &inLeft, const RegedGiantObject &inRight); +}; + +struct RegedOverSizeObject { + uint32_t placeHolder_ = 0; +}; + +struct UnRegedTinyObject { + uint32_t placeHolder_ = 0; +}; + +const uint32_t BUFF_LEN = 16; +struct ExtendHeadInfo { + uint32_t magic = 0; + uint32_t length = 0; + uint32_t version = 0; + uint8_t userId[BUFF_LEN] = {0}; +}; + +class ExtendHeaderHandleTest : public DistributedDB::ExtendHeaderHandle { +public: + explicit ExtendHeaderHandleTest(const DistributedDB::ExtendInfo &info) : headSize_(0) + { + localDbProperty_.appId = info.appId; + localDbProperty_.storeId = info.storeId; + localDbProperty_.userId = info.userId; + localDbProperty_.dstTarget = info.dstTarget; + }; + ~ExtendHeaderHandleTest() {}; + // headSize should be 8 byte align + // return OK and headSize = 0 if no need to fill Head Data + // return OK and headSize > 0 if permit sync and will call FillHeadData + // return NO_PERMISSION if not permit sync + DistributedDB::DBStatus GetHeadDataSize(uint32_t &headSize) override + { + headSize_ = sizeof(ExtendHeadInfo); + headSize_ = BYTE_8_ALIGN(headSize_); + headSize = headSize_; + return DistributedDB::OK; + }; + + DistributedDB::DBStatus FillHeadData(uint8_t *data, uint32_t headSize, uint32_t totalLen) override + { + ExtendHeadInfo info = {MAGIC_NUM, headSize_, 0}; + DistributedDB::HostToNet(info.magic); + DistributedDB::HostToNet(info.length); + DistributedDB::HostToNet(info.version); + for (uint8_t i = 0; i < BUFF_LEN; i++) { + info.userId[i] = localDbProperty_.userId[i]; + } + auto errCode = memcpy_s(data, totalLen, &info, sizeof(ExtendHeadInfo)); + if (errCode != EOK) { + return DistributedDB::DB_ERROR; + } + return DistributedDB::OK; + }; + static constexpr int MAGIC_NUM = 0xF2; +private: + DistributedDB::ExtendInfo localDbProperty_; + uint32_t headSize_; +}; + +const std::string DEVICE_NAME_A = "DeviceA"; +const std::string DEVICE_NAME_B = "DeviceB"; +const std::string DEVICE_NAME_C = "DeviceC"; +constexpr uint64_t LABEL_A = 1234; +constexpr uint64_t LABEL_B = 2345; +constexpr uint64_t LABEL_C = 3456; +constexpr uint32_t REGED_TINY_MSG_ID = 1111; +constexpr uint32_t REGED_HUGE_MSG_ID = 2222; +constexpr uint32_t REGED_GIANT_MSG_ID = 3333; +constexpr uint32_t REGED_OVERSIZE_MSG_ID = 4444; +constexpr uint32_t UNREGED_TINY_MSG_ID = 5555; +constexpr uint32_t FIXED_SESSIONID = 98765; +constexpr uint32_t FIXED_SEQUENCEID = 87654; +constexpr uint32_t TINY_SIZE = 100; // 100 Bytes +constexpr uint32_t HUGE_SIZE = 4 * 1024 * 1024; // 4 MBytes, 1024 is scale +constexpr uint32_t OVER_SIZE = 100 * 1024 * 1024; // 100 MBytes, 1024 is scale +constexpr uint32_t HEADER_SIZE = sizeof(DistributedDB::CommPhyHeader) + sizeof(DistributedDB::CommDivergeHeader) + + sizeof(DistributedDB::MessageHeader); // 96 Bytes For Header, 32 phyHeader, 40 divergeHeader, 24 msgHeader +constexpr uint32_t MAX_CAPACITY = 64 * 1024 * 1024; // 64 MBytes, 1024 is scale + +void DoRegTransformFunction(); + +DistributedDB::Message *BuildRegedTinyMessage(); +DistributedDB::Message *BuildRegedHugeMessage(); +DistributedDB::Message *BuildRegedGiantMessage(uint32_t length); +DistributedDB::Message *BuildRegedOverSizeMessage(); +DistributedDB::Message *BuildUnRegedTinyMessage(); + +#define ASSERT_NOT_NULL_AND_ACTIVATE(communicator) \ +{ \ + ASSERT_NE(communicator, nullptr); \ + (communicator)->Activate(); \ +} + +#endif // DISTRIBUTEDDB_COMMUNICATOR_COMMON_H \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_deep_test.cpp b/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_deep_test.cpp new file mode 100644 index 00000000..5bf31555 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_deep_test.cpp @@ -0,0 +1,562 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_errno.h" +#include "distributeddb_communicator_common.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" +#include "message.h" +#include "serial_buffer.h" + +using namespace std; +using namespace testing::ext; +using namespace DistributedDB; + +namespace { + EnvHandle g_envDeviceA; + EnvHandle g_envDeviceB; + EnvHandle g_envDeviceC; + ICommunicator *g_commAA = nullptr; + ICommunicator *g_commAB = nullptr; + ICommunicator *g_commBB = nullptr; + ICommunicator *g_commBC = nullptr; + ICommunicator *g_commCC = nullptr; + ICommunicator *g_commCA = nullptr; +} + +class DistributedDBCommunicatorDeepTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBCommunicatorDeepTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Create and init CommunicatorAggregator and AdapterStub + */ + LOGI("[UT][DeepTest][SetUpTestCase] Enter."); + bool errCode = SetUpEnv(g_envDeviceA, DEVICE_NAME_A); + ASSERT_EQ(errCode, true); + errCode = SetUpEnv(g_envDeviceB, DEVICE_NAME_B); + ASSERT_EQ(errCode, true); + errCode = SetUpEnv(g_envDeviceC, DEVICE_NAME_C); + ASSERT_EQ(errCode, true); + DoRegTransformFunction(); + CommunicatorAggregator::EnableCommunicatorNotFoundFeedback(false); +} + +void DistributedDBCommunicatorDeepTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: Finalize and release CommunicatorAggregator and AdapterStub + */ + LOGI("[UT][DeepTest][TearDownTestCase] Enter."); + std::this_thread::sleep_for(std::chrono::seconds(7)); // Wait 7 s to make sure all thread quiet and memory released + TearDownEnv(g_envDeviceA); + TearDownEnv(g_envDeviceB); + TearDownEnv(g_envDeviceC); + CommunicatorAggregator::EnableCommunicatorNotFoundFeedback(true); +} + +namespace { +void AllocAllCommunicator() +{ + int errorNo = E_OK; + g_commAA = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_A, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(g_commAA); + g_commAB = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_B, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(g_commAB); + g_commBB = g_envDeviceB.commAggrHandle->AllocCommunicator(LABEL_B, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(g_commBB); + g_commBC = g_envDeviceB.commAggrHandle->AllocCommunicator(LABEL_C, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(g_commBC); + g_commCC = g_envDeviceC.commAggrHandle->AllocCommunicator(LABEL_C, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(g_commCC); + g_commCA = g_envDeviceC.commAggrHandle->AllocCommunicator(LABEL_A, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(g_commCA); +} + +void ReleaseAllCommunicator() +{ + g_envDeviceA.commAggrHandle->ReleaseCommunicator(g_commAA); + g_commAA = nullptr; + g_envDeviceA.commAggrHandle->ReleaseCommunicator(g_commAB); + g_commAB = nullptr; + g_envDeviceB.commAggrHandle->ReleaseCommunicator(g_commBB); + g_commBB = nullptr; + g_envDeviceB.commAggrHandle->ReleaseCommunicator(g_commBC); + g_commBC = nullptr; + g_envDeviceC.commAggrHandle->ReleaseCommunicator(g_commCC); + g_commCC = nullptr; + g_envDeviceC.commAggrHandle->ReleaseCommunicator(g_commCA); + g_commCA = nullptr; +} +} + +void DistributedDBCommunicatorDeepTest::SetUp() +{ + DistributedDBUnitTest::DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: Alloc communicator AA, AB, BB, BC, CC, CA + */ + AllocAllCommunicator(); +} + +void DistributedDBCommunicatorDeepTest::TearDown() +{ + /** + * @tc.teardown: Release communicator AA, AB, BB, BC, CC, CA + */ + ReleaseAllCommunicator(); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Wait 200 ms to make sure all thread quiet +} + +/** + * @tc.name: WaitAndRetrySend 001 + * @tc.desc: Test send retry semantic + * @tc.type: FUNC + * @tc.require: AR000BVDGI AR000CQE0M + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorDeepTest, WaitAndRetrySend001, TestSize.Level2) +{ + // Preset + Message *msgForBB = nullptr; + g_commBB->RegOnMessageCallback([&msgForBB](const std::string &srcTarget, Message *inMsg) { + msgForBB = inMsg; + }, nullptr); + + /** + * @tc.steps: step1. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Wait 200 ms to make sure quiet + + /** + * @tc.steps: step2. device A simulate send retry + */ + g_envDeviceA.adapterHandle->SimulateSendRetry(DEVICE_NAME_B); + + /** + * @tc.steps: step3. device A send message to device B using communicator AB + * @tc.expected: step3. communicator BB received no message + */ + Message *msgForAB = BuildRegedTinyMessage(); + ASSERT_NE(msgForAB, nullptr); + SendConfig conf = {true, false, 0}; + int errCode = g_commAB->SendMessage(DEVICE_NAME_B, msgForAB, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait 100 ms + EXPECT_EQ(msgForBB, nullptr); + + /** + * @tc.steps: step4. device A simulate sendable feedback + * @tc.expected: step4. communicator BB received the message + */ + g_envDeviceA.adapterHandle->SimulateSendRetryClear(DEVICE_NAME_B); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait 100 ms + EXPECT_NE(msgForBB, nullptr); + delete msgForBB; + msgForBB = nullptr; + + // CleanUp + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +static int CreateBufferThenAddIntoScheduler(SendTaskScheduler &scheduler, const std::string &dstTarget, Priority inPrio) +{ + SerialBuffer *eachBuff = new (std::nothrow) SerialBuffer(); + if (eachBuff == nullptr) { + return -E_OUT_OF_MEMORY; + } + int errCode = eachBuff->AllocBufferByTotalLength(100, 0); // 100 totallen without header + if (errCode != E_OK) { + delete eachBuff; + eachBuff = nullptr; + return errCode; + } + SendTask task{eachBuff, dstTarget}; + errCode = scheduler.AddSendTaskIntoSchedule(task, inPrio); + if (errCode != E_OK) { + delete eachBuff; + eachBuff = nullptr; + return errCode; + } + return E_OK; +} + +/** + * @tc.name: SendSchedule 001 + * @tc.desc: Test schedule in Priority order than in send order + * @tc.type: FUNC + * @tc.require: AR000BVDGI AR000CQE0M + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorDeepTest, SendSchedule001, TestSize.Level2) +{ + // Preset + SendTaskScheduler scheduler; + scheduler.Initialize(); + + /** + * @tc.steps: step1. Add low priority target A buffer to schecduler + */ + int errCode = CreateBufferThenAddIntoScheduler(scheduler, DEVICE_NAME_A, Priority::LOW); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. Add low priority target B buffer to schecduler + */ + errCode = CreateBufferThenAddIntoScheduler(scheduler, DEVICE_NAME_B, Priority::LOW); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step3. Add normal priority target B buffer to schecduler + */ + errCode = CreateBufferThenAddIntoScheduler(scheduler, DEVICE_NAME_B, Priority::NORMAL); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step4. Add normal priority target C buffer to schecduler + */ + errCode = CreateBufferThenAddIntoScheduler(scheduler, DEVICE_NAME_C, Priority::NORMAL); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step5. Add high priority target C buffer to schecduler + */ + errCode = CreateBufferThenAddIntoScheduler(scheduler, DEVICE_NAME_C, Priority::HIGH); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step6. Add high priority target A buffer to schecduler + */ + errCode = CreateBufferThenAddIntoScheduler(scheduler, DEVICE_NAME_A, Priority::HIGH); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step7. schedule out buffers one by one + * @tc.expected: step7. the order is: high priority target C + * high priority target A + * normal priority target B + * normal priority target C + * low priority target A + * low priority target B + */ + SendTask outTask; + SendTaskInfo outTaskInfo; + // high priority target C + errCode = scheduler.ScheduleOutSendTask(outTask, outTaskInfo); + ASSERT_EQ(errCode, E_OK); + EXPECT_EQ(outTask.dstTarget, DEVICE_NAME_C); + EXPECT_EQ(outTaskInfo.taskPrio, Priority::HIGH); + scheduler.FinalizeLastScheduleTask(); + // high priority target A + errCode = scheduler.ScheduleOutSendTask(outTask, outTaskInfo); + ASSERT_EQ(errCode, E_OK); + EXPECT_EQ(outTask.dstTarget, DEVICE_NAME_A); + EXPECT_EQ(outTaskInfo.taskPrio, Priority::HIGH); + scheduler.FinalizeLastScheduleTask(); + // normal priority target B + errCode = scheduler.ScheduleOutSendTask(outTask, outTaskInfo); + ASSERT_EQ(errCode, E_OK); + EXPECT_EQ(outTask.dstTarget, DEVICE_NAME_B); + EXPECT_EQ(outTaskInfo.taskPrio, Priority::NORMAL); + scheduler.FinalizeLastScheduleTask(); + // normal priority target C + errCode = scheduler.ScheduleOutSendTask(outTask, outTaskInfo); + ASSERT_EQ(errCode, E_OK); + EXPECT_EQ(outTask.dstTarget, DEVICE_NAME_C); + EXPECT_EQ(outTaskInfo.taskPrio, Priority::NORMAL); + scheduler.FinalizeLastScheduleTask(); + // low priority target A + errCode = scheduler.ScheduleOutSendTask(outTask, outTaskInfo); + ASSERT_EQ(errCode, E_OK); + EXPECT_EQ(outTask.dstTarget, DEVICE_NAME_A); + EXPECT_EQ(outTaskInfo.taskPrio, Priority::LOW); + scheduler.FinalizeLastScheduleTask(); + // low priority target B + errCode = scheduler.ScheduleOutSendTask(outTask, outTaskInfo); + ASSERT_EQ(errCode, E_OK); + EXPECT_EQ(outTask.dstTarget, DEVICE_NAME_B); + EXPECT_EQ(outTaskInfo.taskPrio, Priority::LOW); + scheduler.FinalizeLastScheduleTask(); +} + +/** + * @tc.name: Fragment 001 + * @tc.desc: Test fragmentation in send and receive + * @tc.type: FUNC + * @tc.require: AR000BVDGI AR000CQE0M + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorDeepTest, Fragment001, TestSize.Level2) +{ + // Preset + Message *recvMsgForBB = nullptr; + g_commBB->RegOnMessageCallback([&recvMsgForBB](const std::string &srcTarget, Message *inMsg) { + recvMsgForBB = inMsg; + }, nullptr); + + /** + * @tc.steps: step1. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + + /** + * @tc.steps: step2. device A send message(registered and giant) to device B using communicator AB + * @tc.expected: step2. communicator BB received the message + */ + const uint32_t dataLength = 13 * 1024 * 1024; // 13 MB, 1024 is scale + Message *sendMsgForAB = BuildRegedGiantMessage(dataLength); + ASSERT_NE(sendMsgForAB, nullptr); + SendConfig conf = {false, false, 0}; + int errCode = g_commAB->SendMessage(DEVICE_NAME_B, sendMsgForAB, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(2600)); // Wait 2600 ms to make sure send done + ASSERT_NE(recvMsgForBB, nullptr); + ASSERT_EQ(recvMsgForBB->GetMessageId(), REGED_GIANT_MSG_ID); + + /** + * @tc.steps: step3. Compare received data with send data + * @tc.expected: step3. equal + */ + Message *oriMsgForAB = BuildRegedGiantMessage(dataLength); + ASSERT_NE(oriMsgForAB, nullptr); + const RegedGiantObject *oriObjForAB = oriMsgForAB->GetObject(); + ASSERT_NE(oriObjForAB, nullptr); + const RegedGiantObject *recvObjForBB = recvMsgForBB->GetObject(); + ASSERT_NE(recvObjForBB, nullptr); + bool isEqual = RegedGiantObject::CheckEqual(*oriObjForAB, *recvObjForBB); + EXPECT_EQ(isEqual, true); + + // CleanUp + delete oriMsgForAB; + oriMsgForAB = nullptr; + delete recvMsgForBB; + recvMsgForBB = nullptr; + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +/** + * @tc.name: Fragment 002 + * @tc.desc: Test fragmentation in partial loss + * @tc.type: FUNC + * @tc.require: AR000BVDGI AR000CQE0M + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorDeepTest, Fragment002, TestSize.Level2) +{ + // Preset + Message *recvMsgForCC = nullptr; + g_commCC->RegOnMessageCallback([&recvMsgForCC](const std::string &srcTarget, Message *inMsg) { + recvMsgForCC = inMsg; + }, nullptr); + + /** + * @tc.steps: step1. connect device B with device C + */ + AdapterStub::ConnectAdapterStub(g_envDeviceB.adapterHandle, g_envDeviceC.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Wait 200 ms to make sure quiet + + /** + * @tc.steps: step2. device B simulate partial loss + */ + g_envDeviceB.adapterHandle->SimulateSendPartialLoss(); + + /** + * @tc.steps: step3. device B send message(registered and giant) to device C using communicator BC + * @tc.expected: step3. communicator CC not receive the message + */ + uint32_t dataLength = 13 * 1024 * 1024; // 13 MB, 1024 is scale + Message *sendMsgForBC = BuildRegedGiantMessage(dataLength); + ASSERT_NE(sendMsgForBC, nullptr); + SendConfig conf = {false, false, 0}; + int errCode = g_commBC->SendMessage(DEVICE_NAME_C, sendMsgForBC, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(2600)); // Wait 2600 ms to make sure send done + EXPECT_EQ(recvMsgForCC, nullptr); + + /** + * @tc.steps: step4. device B not simulate partial loss + */ + g_envDeviceB.adapterHandle->SimulateSendPartialLossClear(); + + /** + * @tc.steps: step5. device B send message(registered and giant) to device C using communicator BC + * @tc.expected: step5. communicator CC received the message, the length equal to the one that is second send + */ + dataLength = 17 * 1024 * 1024; // 17 MB, 1024 is scale + Message *resendMsgForBC = BuildRegedGiantMessage(dataLength); + ASSERT_NE(resendMsgForBC, nullptr); + errCode = g_commBC->SendMessage(DEVICE_NAME_C, resendMsgForBC, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(3400)); // Wait 3400 ms to make sure send done + ASSERT_NE(recvMsgForCC, nullptr); + ASSERT_EQ(recvMsgForCC->GetMessageId(), REGED_GIANT_MSG_ID); + const RegedGiantObject *recvObjForCC = recvMsgForCC->GetObject(); + ASSERT_NE(recvObjForCC, nullptr); + EXPECT_EQ(dataLength, recvObjForCC->rawData_.size()); + + // CleanUp + delete recvMsgForCC; + recvMsgForCC = nullptr; + AdapterStub::DisconnectAdapterStub(g_envDeviceB.adapterHandle, g_envDeviceC.adapterHandle); +} + +/** + * @tc.name: Fragment 003 + * @tc.desc: Test fragmentation simultaneously + * @tc.type: FUNC + * @tc.require: AR000BVDGI AR000CQE0M + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorDeepTest, Fragment003, TestSize.Level3) +{ + // Preset + std::atomic count {0}; + OnMessageCallback callback = [&count](const std::string &srcTarget, Message *inMsg) { + delete inMsg; + inMsg = nullptr; + count.fetch_add(1, std::memory_order_seq_cst); + }; + g_commBB->RegOnMessageCallback(callback, nullptr); + g_commBC->RegOnMessageCallback(callback, nullptr); + + /** + * @tc.steps: step1. connect device A with device B, then device B with device C + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + AdapterStub::ConnectAdapterStub(g_envDeviceB.adapterHandle, g_envDeviceC.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(400)); // Wait 400 ms to make sure quiet + + /** + * @tc.steps: step2. device A and device C simulate send block + */ + g_envDeviceA.adapterHandle->SimulateSendBlock(); + g_envDeviceC.adapterHandle->SimulateSendBlock(); + + /** + * @tc.steps: step3. device A send message(registered and giant) to device B using communicator AB + */ + uint32_t dataLength = 23 * 1024 * 1024; // 23 MB, 1024 is scale + Message *sendMsgForAB = BuildRegedGiantMessage(dataLength); + ASSERT_NE(sendMsgForAB, nullptr); + SendConfig conf = {false, false, 0}; + int errCode = g_commAB->SendMessage(DEVICE_NAME_B, sendMsgForAB, conf); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step4. device C send message(registered and giant) to device B using communicator CC + */ + Message *sendMsgForCC = BuildRegedGiantMessage(dataLength); + ASSERT_NE(sendMsgForCC, nullptr); + errCode = g_commCC->SendMessage(DEVICE_NAME_B, sendMsgForCC, conf); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step5. device A and device C not simulate send block + * @tc.expected: step5. communicator BB and BV received the message + */ + g_envDeviceA.adapterHandle->SimulateSendBlockClear(); + g_envDeviceC.adapterHandle->SimulateSendBlockClear(); + std::this_thread::sleep_for(std::chrono::milliseconds(9200)); // Wait 9200 ms to make sure send done + EXPECT_EQ(count, 2); // 2 combined message received + + // CleanUp + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + AdapterStub::DisconnectAdapterStub(g_envDeviceB.adapterHandle, g_envDeviceC.adapterHandle); +} + +namespace { +void ClearPreviousTestCaseInfluence() +{ + ReleaseAllCommunicator(); + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + AdapterStub::ConnectAdapterStub(g_envDeviceB.adapterHandle, g_envDeviceC.adapterHandle); + AdapterStub::ConnectAdapterStub(g_envDeviceC.adapterHandle, g_envDeviceA.adapterHandle); + std::this_thread::sleep_for(std::chrono::seconds(10)); // Wait 10 s to make sure all thread quiet + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + AdapterStub::DisconnectAdapterStub(g_envDeviceB.adapterHandle, g_envDeviceC.adapterHandle); + AdapterStub::DisconnectAdapterStub(g_envDeviceC.adapterHandle, g_envDeviceA.adapterHandle); + AllocAllCommunicator(); +} +} + +/** + * @tc.name: ReliableOnline 001 + * @tc.desc: Test device online reliability + * @tc.type: FUNC + * @tc.require: AR000BVDGJ AR000CQE0N + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorDeepTest, ReliableOnline001, TestSize.Level2) +{ + // Preset + ClearPreviousTestCaseInfluence(); + std::atomic count {0}; + OnConnectCallback callback = [&count](const std::string &target, bool isConnect) { + if (isConnect) { + count.fetch_add(1, std::memory_order_seq_cst); + } + }; + g_commAA->RegOnConnectCallback(callback, nullptr); + g_commAB->RegOnConnectCallback(callback, nullptr); + g_commBB->RegOnConnectCallback(callback, nullptr); + g_commBC->RegOnConnectCallback(callback, nullptr); + g_commCC->RegOnConnectCallback(callback, nullptr); + g_commCA->RegOnConnectCallback(callback, nullptr); + + /** + * @tc.steps: step1. device A and device B and device C simulate send total loss + */ + g_envDeviceA.adapterHandle->SimulateSendTotalLoss(); + g_envDeviceB.adapterHandle->SimulateSendTotalLoss(); + g_envDeviceC.adapterHandle->SimulateSendTotalLoss(); + + /** + * @tc.steps: step2. connect device A with device B, device B with device C, device C with device A + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + AdapterStub::ConnectAdapterStub(g_envDeviceB.adapterHandle, g_envDeviceC.adapterHandle); + AdapterStub::ConnectAdapterStub(g_envDeviceC.adapterHandle, g_envDeviceA.adapterHandle); + + /** + * @tc.steps: step3. wait a long time + * @tc.expected: step3. no communicator received the online callback + */ + std::this_thread::sleep_for(std::chrono::seconds(7)); // Wait 7 s to make sure quiet + EXPECT_EQ(count, 0); // no online callback received + + /** + * @tc.steps: step4. device A and device B and device C not simulate send total loss + */ + g_envDeviceA.adapterHandle->SimulateSendTotalLossClear(); + g_envDeviceB.adapterHandle->SimulateSendTotalLossClear(); + g_envDeviceC.adapterHandle->SimulateSendTotalLossClear(); + std::this_thread::sleep_for(std::chrono::seconds(7)); // Wait 7 s to make sure send done + EXPECT_EQ(count, 6); // 6 online callback received in total + + // CleanUp + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + AdapterStub::DisconnectAdapterStub(g_envDeviceB.adapterHandle, g_envDeviceC.adapterHandle); + AdapterStub::DisconnectAdapterStub(g_envDeviceC.adapterHandle, g_envDeviceA.adapterHandle); +} diff --git a/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_send_receive_test.cpp b/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_send_receive_test.cpp new file mode 100644 index 00000000..ea0d11a2 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_send_receive_test.cpp @@ -0,0 +1,748 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_errno.h" +#include "distributeddb_communicator_common.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" +#include "message.h" +#include "protocol_proto.h" +#include "time_sync.h" +#include "sync_types.h" + +using namespace std; +using namespace testing::ext; +using namespace DistributedDB; + +namespace { + constexpr int SEND_COUNT_GOAL = 20; // Send 20 times + + EnvHandle g_envDeviceA; + EnvHandle g_envDeviceB; + ICommunicator *g_commAA = nullptr; + ICommunicator *g_commBA = nullptr; + ICommunicator *g_commBB = nullptr; +} + +class DistributedDBCommunicatorSendReceiveTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBCommunicatorSendReceiveTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Create and init CommunicatorAggregator and AdapterStub + */ + LOGI("[UT][SendRecvTest][SetUpTestCase] Enter."); + bool errCode = SetUpEnv(g_envDeviceA, DEVICE_NAME_A); + ASSERT_EQ(errCode, true); + errCode = SetUpEnv(g_envDeviceB, DEVICE_NAME_B); + ASSERT_EQ(errCode, true); + DoRegTransformFunction(); + CommunicatorAggregator::EnableCommunicatorNotFoundFeedback(false); +} + +void DistributedDBCommunicatorSendReceiveTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: Finalize and release CommunicatorAggregator and AdapterStub + */ + LOGI("[UT][SendRecvTest][TearDownTestCase] Enter."); + std::this_thread::sleep_for(std::chrono::seconds(7)); // Wait 7 s to make sure all thread quiet and memory released + TearDownEnv(g_envDeviceA); + TearDownEnv(g_envDeviceB); + CommunicatorAggregator::EnableCommunicatorNotFoundFeedback(true); +} + +void DistributedDBCommunicatorSendReceiveTest::SetUp() +{ + DistributedDBUnitTest::DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: Alloc communicator AA, BA, BB + */ + int errorNo = E_OK; + g_commAA = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_A, errorNo); + ASSERT_EQ(errorNo, E_OK); + ASSERT_NOT_NULL_AND_ACTIVATE(g_commAA); + + errorNo = E_OK; + g_commBA = g_envDeviceB.commAggrHandle->AllocCommunicator(LABEL_A, errorNo); + ASSERT_EQ(errorNo, E_OK); + ASSERT_NOT_NULL_AND_ACTIVATE(g_commBA); + + errorNo = E_OK; + g_commBB = g_envDeviceB.commAggrHandle->AllocCommunicator(LABEL_B, errorNo); + ASSERT_EQ(errorNo, E_OK); + ASSERT_NOT_NULL_AND_ACTIVATE(g_commBB); +} + +void DistributedDBCommunicatorSendReceiveTest::TearDown() +{ + /** + * @tc.teardown: Release communicator AA, BA, BB + */ + g_envDeviceA.commAggrHandle->ReleaseCommunicator(g_commAA); + g_commAA = nullptr; + g_envDeviceB.commAggrHandle->ReleaseCommunicator(g_commBA); + g_commBA = nullptr; + g_envDeviceB.commAggrHandle->ReleaseCommunicator(g_commBB); + g_commBA = nullptr; + std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Wait 200 ms to make sure all thread quiet +} + +static Message *BuildAppLayerFrameMessage() +{ + DistributedDBUnitTest::DataSyncMessageInfo info; + info.messageId_ = DistributedDB::TIME_SYNC_MESSAGE; + info.messageType_ = TYPE_REQUEST; + DistributedDB::Message *message = nullptr; + DistributedDBUnitTest::DistributedDBToolsUnitTest::BuildMessage(info, message); + return message; +} + +/** + * @tc.name: Send And Receive 001 + * @tc.desc: Test send and receive based on equipment communicator + * @tc.type: FUNC + * @tc.require: AR000BVDGI AR000CQE0M + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorSendReceiveTest, SendAndReceive001, TestSize.Level1) +{ + // Preset + string srcTargetForAA; + Message *recvMsgForAA = nullptr; + string srcTargetForBA; + Message *recvMsgForBA = nullptr; + string srcTargetForBB; + Message *recvMsgForBB = nullptr; + g_commAA->RegOnMessageCallback([&srcTargetForAA, &recvMsgForAA](const std::string &srcTarget, Message *inMsg) { + srcTargetForAA = srcTarget; + recvMsgForAA = inMsg; + }, nullptr); + g_commBA->RegOnMessageCallback([&srcTargetForBA, &recvMsgForBA](const std::string &srcTarget, Message *inMsg) { + srcTargetForBA = srcTarget; + recvMsgForBA = inMsg; + }, nullptr); + g_commBB->RegOnMessageCallback([&srcTargetForBB, &recvMsgForBB](const std::string &srcTarget, Message *inMsg) { + srcTargetForBB = srcTarget; + recvMsgForBB = inMsg; + }, nullptr); + + /** + * @tc.steps: step1. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + + /** + * @tc.steps: step2. device A send message(registered and tiny) to device B using communicator AA + * @tc.expected: step2. communicator BA received the message + */ + Message *msgForAA = BuildRegedTinyMessage(); + ASSERT_NE(msgForAA, nullptr); + SendConfig conf = {false, false, 0}; + int errCode = g_commAA->SendMessage(DEVICE_NAME_B, msgForAA, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); // sleep 200 ms + EXPECT_EQ(recvMsgForBB, nullptr); + EXPECT_EQ(srcTargetForBA, DEVICE_NAME_A); + ASSERT_NE(recvMsgForBA, nullptr); + EXPECT_EQ(recvMsgForBA->GetMessageId(), REGED_TINY_MSG_ID); + EXPECT_EQ(recvMsgForBA->GetMessageType(), TYPE_REQUEST); + EXPECT_EQ(recvMsgForBA->GetSessionId(), FIXED_SESSIONID); + EXPECT_EQ(recvMsgForBA->GetSequenceId(), FIXED_SEQUENCEID); + EXPECT_EQ(recvMsgForBA->GetErrorNo(), NO_ERROR); + delete recvMsgForBA; + recvMsgForBA = nullptr; + + /** + * @tc.steps: step3. device B send message(registered and tiny) to device A using communicator BB + * @tc.expected: step3. communicator AA did not receive the message + */ + Message *msgForBB = BuildRegedTinyMessage(); + ASSERT_NE(msgForBB, nullptr); + conf = {true, 0}; + errCode = g_commBB->SendMessage(DEVICE_NAME_A, msgForBB, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(srcTargetForAA, ""); + + // CleanUp + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +/** + * @tc.name: Send And Receive 002 + * @tc.desc: Test send oversize message will fail + * @tc.type: FUNC + * @tc.require: AR000BVDGK AR000CQE0O + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorSendReceiveTest, SendAndReceive002, TestSize.Level1) +{ + /** + * @tc.steps: step1. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + + /** + * @tc.steps: step2. device A send message(registered and oversize) to device B using communicator AA + * @tc.expected: step2. send fail + */ + Message *msgForAA = BuildRegedOverSizeMessage(); + ASSERT_NE(msgForAA, nullptr); + SendConfig conf = {true, false, 0}; + int errCode = g_commAA->SendMessage(DEVICE_NAME_B, msgForAA, conf); + EXPECT_NE(errCode, E_OK); + delete msgForAA; + msgForAA = nullptr; + + // CleanUp + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +/** + * @tc.name: Send And Receive 003 + * @tc.desc: Test send unregistered message will fail + * @tc.type: FUNC + * @tc.require: AR000BVDGK AR000CQE0O + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorSendReceiveTest, SendAndReceive003, TestSize.Level1) +{ + /** + * @tc.steps: step1. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + + /** + * @tc.steps: step2. device A send message(unregistered and tiny) to device B using communicator AA + * @tc.expected: step2. send fail + */ + Message *msgForAA = BuildUnRegedTinyMessage(); + ASSERT_NE(msgForAA, nullptr); + SendConfig conf = {true, false, 0}; + int errCode = g_commAA->SendMessage(DEVICE_NAME_B, msgForAA, conf); + EXPECT_NE(errCode, E_OK); + delete msgForAA; + msgForAA = nullptr; + + // CleanUp + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +/** + * @tc.name: Send Flow Control 001 + * @tc.desc: Test send in nonblock way + * @tc.type: FUNC + * @tc.require: AR000BVDGI AR000CQE0M + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorSendReceiveTest, SendFlowControl001, TestSize.Level1) +{ + // Preset + int countForBA = 0; + int countForBB = 0; + g_commBA->RegOnSendableCallback([&countForBA](){ countForBA++; }, nullptr); + g_commBB->RegOnSendableCallback([&countForBB](){ countForBB++; }, nullptr); + + /** + * @tc.steps: step1. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait 100 ms to make sure send cause by online done + countForBA = 0; + countForBB = 0; + + /** + * @tc.steps: step2. device B simulates send block + */ + g_envDeviceB.adapterHandle->SimulateSendBlock(); + + /** + * @tc.steps: step3. device B send as much as possible message(unregistered and huge) in nonblock way + * to device A using communicator BA until send fail; + * @tc.expected: step3. send fail will happen. + */ + int sendCount = 0; + while (true) { + Message *msgForBA = BuildRegedHugeMessage(); + ASSERT_NE(msgForBA, nullptr); + SendConfig conf = {true, false, 0}; + int errCode = g_commBA->SendMessage(DEVICE_NAME_A, msgForBA, conf); + if (errCode == E_OK) { + sendCount++; + } else { + delete msgForBA; + msgForBA = nullptr; + break; + } + } + + /** + * @tc.steps: step4. device B simulates send block terminate + * @tc.expected: step4. send count before fail is equal as expected. sendable callback happened. + */ + g_envDeviceB.adapterHandle->SimulateSendBlockClear(); + int expectSendCount = MAX_CAPACITY / (HUGE_SIZE + HEADER_SIZE) + + (MAX_CAPACITY % (HUGE_SIZE + HEADER_SIZE) == 0 ? 0 : 1); + EXPECT_EQ(sendCount, expectSendCount); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + EXPECT_GE(countForBA, 1); + EXPECT_GE(countForBB, 1); + + // CleanUp + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +/** + * @tc.name: Send Flow Control 002 + * @tc.desc: Test send in block(without timeout) way + * @tc.type: FUNC + * @tc.require: AR000BVDGI AR000CQE0M + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorSendReceiveTest, SendFlowControl002, TestSize.Level1) +{ + // Preset + int cntForBA = 0; + int cntForBB = 0; + g_commBA->RegOnSendableCallback([&cntForBA](){ cntForBA++; }, nullptr); + g_commBB->RegOnSendableCallback([&cntForBB](){ cntForBB++; }, nullptr); + + /** + * @tc.steps: step1. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait 100 ms to make sure send cause by online done + cntForBA = 0; + cntForBB = 0; + + /** + * @tc.steps: step2. device B simulates send block + */ + g_envDeviceB.adapterHandle->SimulateSendBlock(); + + /** + * @tc.steps: step3. device B send a certain message(unregistered and huge) in block way + * without timeout to device A using communicator BA; + */ + int sendCount = 0; + int sendFailCount = 0; + std::thread sendThread([&sendCount, &sendFailCount]() { + while (sendCount < SEND_COUNT_GOAL) { + Message *msgForBA = BuildRegedHugeMessage(); + ASSERT_NE(msgForBA, nullptr); + SendConfig conf = {false, false, 0}; + int errCode = g_commBA->SendMessage(DEVICE_NAME_A, msgForBA, conf); + if (errCode != E_OK) { + delete msgForBA; + msgForBA = nullptr; + sendFailCount++; + } + sendCount++; + } + }); + + /** + * @tc.steps: step4. device B simulates send block terminate + * @tc.expected: step4. send fail count is zero. sendable callback happened. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + g_envDeviceB.adapterHandle->SimulateSendBlockClear(); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + sendThread.join(); + EXPECT_EQ(sendCount, SEND_COUNT_GOAL); + EXPECT_EQ(sendFailCount, 0); + EXPECT_GE(cntForBA, 1); + EXPECT_GE(cntForBB, 1); + + // CleanUp + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +/** + * @tc.name: Send Flow Control 003 + * @tc.desc: Test send in block(with timeout) way + * @tc.type: FUNC + * @tc.require: AR000BVDGI AR000CQE0M + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorSendReceiveTest, SendFlowControl003, TestSize.Level1) +{ + // Preset + int cntsForBA = 0; + int cntsForBB = 0; + g_commBA->RegOnSendableCallback([&cntsForBA](){ cntsForBA++; }, nullptr); + g_commBB->RegOnSendableCallback([&cntsForBB](){ cntsForBB++; }, nullptr); + + /** + * @tc.steps: step1. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + cntsForBA = 0; + cntsForBB = 0; + + /** + * @tc.steps: step2. device B simulates send block + */ + g_envDeviceB.adapterHandle->SimulateSendBlock(); + + /** + * @tc.steps: step3. device B send a certain message(unregistered and huge) in block way + * with timeout to device A using communicator BA; + */ + int sendCnt = 0; + int sendFailCnt = 0; + std::thread sendThread([&sendCnt, &sendFailCnt]() { + while (sendCnt < SEND_COUNT_GOAL) { + Message *msgForBA = BuildRegedHugeMessage(); + ASSERT_NE(msgForBA, nullptr); + SendConfig conf = {false, false, 100}; + int errCode = g_commBA->SendMessage(DEVICE_NAME_A, msgForBA, conf); // 100 ms timeout + if (errCode != E_OK) { + delete msgForBA; + msgForBA = nullptr; + sendFailCnt++; + } + sendCnt++; + } + }); + + /** + * @tc.steps: step4. device B simulates send block terminate + * @tc.expected: step4. send fail count is no more than expected. sendable callback happened. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(300)); // wait 300 ms + g_envDeviceB.adapterHandle->SimulateSendBlockClear(); + std::this_thread::sleep_for(std::chrono::milliseconds(1200)); // wait 1200 ms + sendThread.join(); + EXPECT_EQ(sendCnt, SEND_COUNT_GOAL); + EXPECT_LE(sendFailCnt, 4); + EXPECT_GE(cntsForBA, 1); + EXPECT_GE(cntsForBB, 1); + + // CleanUp + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +/** + * @tc.name: Receive Check 001 + * @tc.desc: Receive packet field check + * @tc.type: FUNC + * @tc.require: AR000BVRNU AR000CQE0J + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorSendReceiveTest, ReceiveCheck001, TestSize.Level1) +{ + // Preset + int recvCount = 0; + g_commAA->RegOnMessageCallback([&recvCount](const std::string &srcTarget, Message *inMsg) { + recvCount++; + if (inMsg != nullptr) { + delete inMsg; + inMsg = nullptr; + } + }, nullptr); + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + + /** + * @tc.steps: step1. create packet with magic field error + * @tc.expected: step1. no message callback + */ + g_envDeviceB.adapterHandle->SimulateSendBitErrorInMagicField(true, 0xFFFF); + Message *msgForBA = BuildRegedTinyMessage(); + SendConfig conf = {true, false, 0}; + int errCode = g_commBA->SendMessage(DEVICE_NAME_A, msgForBA, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(recvCount, 0); + g_envDeviceB.adapterHandle->SimulateSendBitErrorInMagicField(false, 0); + + /** + * @tc.steps: step2. create packet with version field error + * @tc.expected: step2. no message callback + */ + g_envDeviceB.adapterHandle->SimulateSendBitErrorInVersionField(true, 0xFFFF); + msgForBA = BuildRegedTinyMessage(); + errCode = g_commBA->SendMessage(DEVICE_NAME_A, msgForBA, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(recvCount, 0); + g_envDeviceB.adapterHandle->SimulateSendBitErrorInVersionField(false, 0); + + /** + * @tc.steps: step3. create packet with checksum field error + * @tc.expected: step3. no message callback + */ + g_envDeviceB.adapterHandle->SimulateSendBitErrorInCheckSumField(true, 0xFFFF); + msgForBA = BuildRegedTinyMessage(); + errCode = g_commBA->SendMessage(DEVICE_NAME_A, msgForBA, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(recvCount, 0); + g_envDeviceB.adapterHandle->SimulateSendBitErrorInCheckSumField(false, 0); + + // CleanUp + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +/** + * @tc.name: Receive Check 002 + * @tc.desc: Receive packet field check + * @tc.type: FUNC + * @tc.require: AR000BVRNU AR000CQE0J + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorSendReceiveTest, ReceiveCheck002, TestSize.Level1) +{ + // Preset + int recvCount = 0; + g_commAA->RegOnMessageCallback([&recvCount](const std::string &srcTarget, Message *inMsg) { + recvCount++; + if (inMsg != nullptr) { + delete inMsg; + inMsg = nullptr; + } + }, nullptr); + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + + /** + * @tc.steps: step1. create packet with packetLen field error + * @tc.expected: step1. no message callback + */ + g_envDeviceB.adapterHandle->SimulateSendBitErrorInPacketLenField(true, 0xFFFF); + Message *msgForBA = BuildRegedTinyMessage(); + SendConfig conf = {true, false, 0}; + int errCode = g_commBA->SendMessage(DEVICE_NAME_A, msgForBA, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(recvCount, 0); + g_envDeviceB.adapterHandle->SimulateSendBitErrorInPacketLenField(false, 0); + + /** + * @tc.steps: step1. create packet with packetType field error + * @tc.expected: step1. no message callback + */ + g_envDeviceB.adapterHandle->SimulateSendBitErrorInPacketTypeField(true, 0xFF); + msgForBA = BuildRegedTinyMessage(); + errCode = g_commBA->SendMessage(DEVICE_NAME_A, msgForBA, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(recvCount, 0); + g_envDeviceB.adapterHandle->SimulateSendBitErrorInPacketTypeField(false, 0); + + /** + * @tc.steps: step1. create packet with paddingLen field error + * @tc.expected: step1. no message callback + */ + g_envDeviceB.adapterHandle->SimulateSendBitErrorInPaddingLenField(true, 0xFF); + msgForBA = BuildRegedTinyMessage(); + errCode = g_commBA->SendMessage(DEVICE_NAME_A, msgForBA, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(recvCount, 0); + g_envDeviceB.adapterHandle->SimulateSendBitErrorInPaddingLenField(false, 0); + + // CleanUp + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +/** + * @tc.name: Send Result Notify 001 + * @tc.desc: Test send result notify + * @tc.type: FUNC + * @tc.require: AR000CQE0M + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorSendReceiveTest, SendResultNotify001, TestSize.Level1) +{ + // preset + std::vector sendResult; + auto sendResultNotifier = [&sendResult](int result) { + sendResult.push_back(result); + }; + + /** + * @tc.steps: step1. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + + /** + * @tc.steps: step2. device A send message to device B using communicator AA + * @tc.expected: step2. notify send done and success + */ + Message *msgForAA = BuildRegedTinyMessage(); + ASSERT_NE(msgForAA, nullptr); + SendConfig conf = {false, false, 0}; + int errCode = g_commAA->SendMessage(DEVICE_NAME_B, msgForAA, conf, sendResultNotifier); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + ASSERT_EQ(sendResult.size(), static_cast(1)); // 1 notify + EXPECT_EQ(sendResult[0], E_OK); + + /** + * @tc.steps: step3. disconnect device A with device B + */ + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + + /** + * @tc.steps: step4. device A send message to device B using communicator AA + * @tc.expected: step2. notify send done and fail + */ + msgForAA = BuildRegedTinyMessage(); + ASSERT_NE(msgForAA, nullptr); + errCode = g_commAA->SendMessage(DEVICE_NAME_B, msgForAA, conf, sendResultNotifier); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + ASSERT_EQ(sendResult.size(), static_cast(2)); // 2 notify + EXPECT_NE(sendResult[1], E_OK); // 1 for second element +} + +#define REG_MESSAGE_CALLBACK(src, label) \ + string srcTargetFor##src##label; \ + Message *recvMsgFor##src##label = nullptr; \ + g_comm##src##label->RegOnMessageCallback( \ + [&srcTargetFor##src##label, &recvMsgFor##src##label](const std::string &srcTarget, Message *inMsg) { \ + srcTargetFor##src##label = srcTarget; \ + recvMsgFor##src##label = inMsg; \ + }, nullptr); + +/** + * @tc.name: Message Feedback 001 + * @tc.desc: Test feedback not support messageid and communicator not found + * @tc.type: FUNC + * @tc.require: AR000CQE0M + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorSendReceiveTest, MessageFeedback001, TestSize.Level1) +{ + CommunicatorAggregator::EnableCommunicatorNotFoundFeedback(true); + // preset + REG_MESSAGE_CALLBACK(A, A); + REG_MESSAGE_CALLBACK(B, A); + REG_MESSAGE_CALLBACK(B, B); + + /** + * @tc.steps: step1. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + + /** + * @tc.steps: step2. device B send message to device A using communicator BB + * @tc.expected: step2. communicator BB receive communicator not found feedback + */ + Message *msgForBB = BuildRegedTinyMessage(); + ASSERT_NE(msgForBB, nullptr); + SendConfig conf = {false, false, 0}; + int errCode = g_commBB->SendMessage(DEVICE_NAME_A, msgForBB, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + ASSERT_NE(recvMsgForBB, nullptr); + EXPECT_EQ(srcTargetForBB, DEVICE_NAME_A); + EXPECT_EQ(recvMsgForBB->GetMessageId(), REGED_TINY_MSG_ID); + EXPECT_EQ(recvMsgForBB->GetMessageType(), TYPE_RESPONSE); + EXPECT_EQ(recvMsgForBB->GetSessionId(), FIXED_SESSIONID); + EXPECT_EQ(recvMsgForBB->GetSequenceId(), FIXED_SEQUENCEID); + EXPECT_EQ(recvMsgForBB->GetErrorNo(), static_cast(E_FEEDBACK_COMMUNICATOR_NOT_FOUND)); + EXPECT_EQ(recvMsgForBB->GetObject(), nullptr); + delete recvMsgForBB; + recvMsgForBB = nullptr; + + /** + * @tc.steps: step3. simulate messageid not registered + */ + g_envDeviceB.adapterHandle->SimulateSendBitErrorInMessageIdField(true, UNREGED_TINY_MSG_ID); + + /** + * @tc.steps: step4. device B send message to device A using communicator BA + * @tc.expected: step4. communicator BA receive messageid not register feedback + */ + Message *msgForBA = BuildRegedTinyMessage(); + ASSERT_NE(msgForBA, nullptr); + errCode = g_commBA->SendMessage(DEVICE_NAME_A, msgForBA, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + ASSERT_NE(recvMsgForBA, nullptr); + EXPECT_EQ(srcTargetForBA, DEVICE_NAME_A); + EXPECT_EQ(recvMsgForBA->GetMessageId(), UNREGED_TINY_MSG_ID); + EXPECT_EQ(recvMsgForBA->GetMessageType(), TYPE_RESPONSE); + EXPECT_EQ(recvMsgForBA->GetSessionId(), FIXED_SESSIONID); + EXPECT_EQ(recvMsgForBA->GetSequenceId(), FIXED_SEQUENCEID); + EXPECT_EQ(recvMsgForBA->GetErrorNo(), static_cast(E_FEEDBACK_UNKNOWN_MESSAGE)); + EXPECT_EQ(recvMsgForBA->GetObject(), nullptr); + delete recvMsgForBA; + recvMsgForBA = nullptr; + + // CleanUp + g_envDeviceB.adapterHandle->SimulateSendBitErrorInMessageIdField(false, 0); + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + CommunicatorAggregator::EnableCommunicatorNotFoundFeedback(false); +} + +/** + * @tc.name: SendAndReceiveWithExtendHead001 + * @tc.desc: Test fill extendHead func + * @tc.type: FUNC + * @tc.require: AR000BVDGI AR000CQE0M + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBCommunicatorSendReceiveTest, SendAndReceiveWithExtendHead001, TestSize.Level1) +{ + // Preset + string srcTargetForAA; + Message *recvMsgForAA = nullptr; + string srcTargetForBA; + Message *recvMsgForBA = nullptr; + TimeSync::RegisterTransformFunc(); + g_commAA->RegOnMessageCallback([&srcTargetForAA, &recvMsgForAA](const std::string &srcTarget, Message *inMsg) { + srcTargetForAA = srcTarget; + recvMsgForAA = inMsg; + }, nullptr); + g_commBA->RegOnMessageCallback([&srcTargetForBA, &recvMsgForBA](const std::string &srcTarget, Message *inMsg) { + srcTargetForBA = srcTarget; + recvMsgForBA = inMsg; + }, nullptr); + + /** + * @tc.steps: step1. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + + /** + * @tc.steps: step2. device A send ApplayerFrameMessage to device B using communicator AA with extednHead + * @tc.expected: step2. communicator BA received the message + */ + Message *msgForAA = BuildAppLayerFrameMessage(); + ASSERT_NE(msgForAA, nullptr); + SendConfig conf = {false, true, 0, {"appId", "storeId", "userId", "deviceB"}}; + int errCode = g_commAA->SendMessage(DEVICE_NAME_B, msgForAA, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); // sleep 200 ms + EXPECT_EQ(srcTargetForBA, DEVICE_NAME_A); + ASSERT_NE(recvMsgForBA, nullptr); + delete recvMsgForBA; + recvMsgForBA = nullptr; + DistributedDB::ProtocolProto::UnRegTransformFunction(DistributedDB::TIME_SYNC_MESSAGE); + // CleanUp + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_test.cpp b/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_test.cpp new file mode 100644 index 00000000..ab7e1b01 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/communicator/distributeddb_communicator_test.cpp @@ -0,0 +1,764 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_errno.h" +#include "distributeddb_communicator_common.h" +#include "distributeddb_tools_unit_test.h" +#include "endian_convert.h" +#include "log_print.h" + +using namespace std; +using namespace testing::ext; +using namespace DistributedDB; + +namespace { + EnvHandle g_envDeviceA; + EnvHandle g_envDeviceB; + EnvHandle g_envDeviceC; +} + +static void HandleConnectChange(OnOfflineDevice &onlines, const std::string &target, bool isConnect) +{ + if (isConnect) { + onlines.onlineDevices.insert(target); + onlines.latestOnlineDevice = target; + onlines.latestOfflineDevice.clear(); + } else { + onlines.onlineDevices.erase(target); + onlines.latestOnlineDevice.clear(); + onlines.latestOfflineDevice = target; + } +} + +class DistributedDBCommunicatorTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBCommunicatorTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Create and init CommunicatorAggregator and AdapterStub + */ + LOGI("[UT][Test][SetUpTestCase] Enter."); + bool errCode = SetUpEnv(g_envDeviceA, DEVICE_NAME_A); + ASSERT_EQ(errCode, true); + errCode = SetUpEnv(g_envDeviceB, DEVICE_NAME_B); + ASSERT_EQ(errCode, true); + DoRegTransformFunction(); + CommunicatorAggregator::EnableCommunicatorNotFoundFeedback(false); +} + +void DistributedDBCommunicatorTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: Finalize and release CommunicatorAggregator and AdapterStub + */ + LOGI("[UT][Test][TearDownTestCase] Enter."); + std::this_thread::sleep_for(std::chrono::seconds(7)); // Wait 7 s to make sure all thread quiet and memory released + TearDownEnv(g_envDeviceA); + TearDownEnv(g_envDeviceB); + CommunicatorAggregator::EnableCommunicatorNotFoundFeedback(true); +} + +void DistributedDBCommunicatorTest::SetUp() +{ + DistributedDBUnitTest::DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBCommunicatorTest::TearDown() +{ + /** + * @tc.teardown: Wait 100 ms to make sure all thread quiet + */ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait 100 ms +} + +/** + * @tc.name: Communicator Management 001 + * @tc.desc: Test alloc and release communicator + * @tc.type: FUNC + * @tc.require: AR000BVDGG AR000CQE0L + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorTest, CommunicatorManagement001, TestSize.Level1) +{ + /** + * @tc.steps: step1. alloc communicator A using label A + * @tc.expected: step1. alloc return OK. + */ + int errorNo = E_OK; + ICommunicator *commA = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_A, errorNo); + EXPECT_EQ(errorNo, E_OK); + EXPECT_NE(commA, nullptr); + + /** + * @tc.steps: step2. alloc communicator B using label B + * @tc.expected: step2. alloc return OK. + */ + errorNo = E_OK; + ICommunicator *commB = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_B, errorNo); + EXPECT_EQ(errorNo, E_OK); + EXPECT_NE(commA, nullptr); + + /** + * @tc.steps: step3. alloc communicator C using label A + * @tc.expected: step3. alloc return not OK. + */ + errorNo = E_OK; + ICommunicator *commC = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_A, errorNo); + EXPECT_NE(errorNo, E_OK); + EXPECT_EQ(commC, nullptr); + + /** + * @tc.steps: step4. release communicator A and communicator B + */ + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commA); + commA = nullptr; + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commB); + commB = nullptr; + + /** + * @tc.steps: step5. alloc communicator D using label A + * @tc.expected: step5. alloc return OK. + */ + errorNo = E_OK; + ICommunicator *commD = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_A, errorNo); + EXPECT_EQ(errorNo, E_OK); + EXPECT_NE(commD, nullptr); + + /** + * @tc.steps: step6. release communicator D + */ + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commD); + commD = nullptr; +} + +static void ConnectWaitDisconnect() +{ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +/** + * @tc.name: Online And Offline 001 + * @tc.desc: Test functionality triggered by physical devices online and offline + * @tc.type: FUNC + * @tc.require: AR000BVRNS AR000CQE0H + * @tc.author: wudongxing + */ +HWTEST_F(DistributedDBCommunicatorTest, OnlineAndOffline001, TestSize.Level1) +{ + /** + * @tc.steps: step1. device A alloc communicator AA using label A and register callback + * @tc.expected: step1. no callback. + */ + int errorNo = E_OK; + ICommunicator *commAA = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_A, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(commAA); + OnOfflineDevice onlineForAA; + commAA->RegOnConnectCallback([&onlineForAA](const std::string &target, bool isConnect) { + HandleConnectChange(onlineForAA, target, isConnect);}, nullptr); + EXPECT_EQ(onlineForAA.onlineDevices.size(), static_cast(0)); + + /** + * @tc.steps: step2. connect device A with device B and then disconnect + * @tc.expected: step2. no callback. + */ + ConnectWaitDisconnect(); + EXPECT_EQ(onlineForAA.onlineDevices.size(), static_cast(0)); + + /** + * @tc.steps: step3. device B alloc communicator BB using label B and register callback + * @tc.expected: step3. no callback. + */ + ICommunicator *commBB = g_envDeviceB.commAggrHandle->AllocCommunicator(LABEL_B, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(commBB); + OnOfflineDevice onlineForBB; + commBB->RegOnConnectCallback([&onlineForBB](const std::string &target, bool isConnect) { + HandleConnectChange(onlineForBB, target, isConnect);}, nullptr); + EXPECT_EQ(onlineForAA.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForBB.onlineDevices.size(), static_cast(0)); + + /** + * @tc.steps: step4. connect device A with device B and then disconnect + * @tc.expected: step4. no callback. + */ + ConnectWaitDisconnect(); + EXPECT_EQ(onlineForAA.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForBB.onlineDevices.size(), static_cast(0)); + + /** + * @tc.steps: step5. device B alloc communicator BA using label A and register callback + * @tc.expected: step5. no callback. + */ + ICommunicator *commBA = g_envDeviceB.commAggrHandle->AllocCommunicator(LABEL_A, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(commBA); + OnOfflineDevice onlineForBA; + commBA->RegOnConnectCallback([&onlineForBA](const std::string &target, bool isConnect) { + HandleConnectChange(onlineForBA, target, isConnect);}, nullptr); + EXPECT_EQ(onlineForAA.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForBB.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForBA.onlineDevices.size(), static_cast(0)); + + /** + * @tc.steps: step6. connect device A with device B + * @tc.expected: step6. communicator AA has callback of device B online; + * communicator BA has callback of device A online; + * communicator BB no callback + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(onlineForAA.onlineDevices.size(), static_cast(1)); + EXPECT_EQ(onlineForBB.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForBA.onlineDevices.size(), static_cast(1)); + EXPECT_EQ(onlineForAA.latestOnlineDevice, DEVICE_NAME_B); + EXPECT_EQ(onlineForBA.latestOnlineDevice, DEVICE_NAME_A); + + /** + * @tc.steps: step7. disconnect device A with device B + * @tc.expected: step7. communicator AA has callback of device B offline; + * communicator BA has callback of device A offline; + * communicator BB no callback + */ + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + EXPECT_EQ(onlineForAA.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForBB.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForBA.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForAA.latestOfflineDevice, DEVICE_NAME_B); + EXPECT_EQ(onlineForBA.latestOfflineDevice, DEVICE_NAME_A); + + // Clean up + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commAA); + g_envDeviceB.commAggrHandle->ReleaseCommunicator(commBB); + g_envDeviceB.commAggrHandle->ReleaseCommunicator(commBA); +} + +#define REG_CONNECT_CALLBACK(communicator, online) \ +{ \ + communicator->RegOnConnectCallback([&online](const std::string &target, bool isConnect) { \ + HandleConnectChange(online, target, isConnect); \ + }, nullptr); \ +} + +#define CONNECT_AND_WAIT(waitTime) \ +{ \ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); \ + std::this_thread::sleep_for(std::chrono::milliseconds(waitTime)); \ +} + +/** + * @tc.name: Online And Offline 002 + * @tc.desc: Test functionality triggered by alloc and release communicator + * @tc.type: FUNC + * @tc.require: AR000BVRNT AR000CQE0I + * @tc.author: wudongxing + */ +HWTEST_F(DistributedDBCommunicatorTest, OnlineAndOffline002, TestSize.Level1) +{ + /** + * @tc.steps: step1. connect device A with device B + */ + CONNECT_AND_WAIT(200); // Sleep 200 ms + + /** + * @tc.steps: step2. device A alloc communicator AA using label A and register callback + * @tc.expected: step2. no callback. + */ + int errorNo = E_OK; + ICommunicator *commAA = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_A, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(commAA); + OnOfflineDevice onlineForAA; + REG_CONNECT_CALLBACK(commAA, onlineForAA); + EXPECT_EQ(onlineForAA.onlineDevices.size(), static_cast(0)); + + /** + * @tc.steps: step3. device B alloc communicator BB using label B and register callback + * @tc.expected: step3. no callback. + */ + ICommunicator *commBB = g_envDeviceB.commAggrHandle->AllocCommunicator(LABEL_B, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(commBB); + OnOfflineDevice onlineForBB; + REG_CONNECT_CALLBACK(commBB, onlineForBB); + EXPECT_EQ(onlineForAA.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForBB.onlineDevices.size(), static_cast(0)); + + /** + * @tc.steps: step4. device B alloc communicator BA using label A and register callback + * @tc.expected: step4. communicator AA has callback of device B online; + * communicator BA has callback of device A online; + * communicator BB no callback. + */ + ICommunicator *commBA = g_envDeviceB.commAggrHandle->AllocCommunicator(LABEL_A, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(commBA); + OnOfflineDevice onlineForBA; + REG_CONNECT_CALLBACK(commBA, onlineForBA); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(onlineForAA.onlineDevices.size(), static_cast(1)); + EXPECT_EQ(onlineForBB.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForBA.onlineDevices.size(), static_cast(1)); + EXPECT_EQ(onlineForAA.latestOnlineDevice, DEVICE_NAME_B); + EXPECT_EQ(onlineForBA.latestOnlineDevice, DEVICE_NAME_A); + + /** + * @tc.steps: step5. device A alloc communicator AB using label B and register callback + * @tc.expected: step5. communicator AB has callback of device B online; + * communicator BB has callback of device A online; + */ + ICommunicator *commAB = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_B, errorNo); + ASSERT_NOT_NULL_AND_ACTIVATE(commAB); + OnOfflineDevice onlineForAB; + REG_CONNECT_CALLBACK(commAB, onlineForAB); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(onlineForAB.onlineDevices.size(), static_cast(1)); + EXPECT_EQ(onlineForBB.onlineDevices.size(), static_cast(1)); + EXPECT_EQ(onlineForAB.latestOnlineDevice, DEVICE_NAME_B); + EXPECT_EQ(onlineForBB.latestOnlineDevice, DEVICE_NAME_A); + + /** + * @tc.steps: step6. device A release communicator AA + * @tc.expected: step6. communicator BA has callback of device A offline; + * communicator AB and BB no callback; + */ + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commAA); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(onlineForBA.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForAB.onlineDevices.size(), static_cast(1)); + EXPECT_EQ(onlineForBB.onlineDevices.size(), static_cast(1)); + EXPECT_EQ(onlineForBA.latestOfflineDevice, DEVICE_NAME_A); + + /** + * @tc.steps: step7. device B release communicator BA + * @tc.expected: step7. communicator AB and BB no callback; + */ + g_envDeviceB.commAggrHandle->ReleaseCommunicator(commBA); + EXPECT_EQ(onlineForAB.onlineDevices.size(), static_cast(1)); + EXPECT_EQ(onlineForBB.onlineDevices.size(), static_cast(1)); + + /** + * @tc.steps: step8. device B release communicator BB + * @tc.expected: step8. communicator AB has callback of device B offline; + */ + g_envDeviceB.commAggrHandle->ReleaseCommunicator(commBB); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(onlineForAB.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForAB.latestOfflineDevice, DEVICE_NAME_B); + + // Clean up + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commAB); + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +/** + * @tc.name: Report Device Connect Change 001 + * @tc.desc: Test CommunicatorAggregator support report device connect change event + * @tc.type: FUNC + * @tc.require: AR000DR9KV + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorTest, ReportDeviceConnectChange001, TestSize.Level1) +{ + /** + * @tc.steps: step1. device A and device B register connect callback to CommunicatorAggregator + */ + OnOfflineDevice onlineForA; + int errCode = g_envDeviceA.commAggrHandle->RegOnConnectCallback( + [&onlineForA](const std::string &target, bool isConnect) { + HandleConnectChange(onlineForA, target, isConnect); + }, nullptr); + EXPECT_EQ(errCode, E_OK); + OnOfflineDevice onlineForB; + errCode = g_envDeviceB.commAggrHandle->RegOnConnectCallback( + [&onlineForB](const std::string &target, bool isConnect) { + HandleConnectChange(onlineForB, target, isConnect); + }, nullptr); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. connect device A with device B + * @tc.expected: step2. device A callback B online; device B callback A online; + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + EXPECT_EQ(onlineForA.onlineDevices.size(), static_cast(1)); + EXPECT_EQ(onlineForB.onlineDevices.size(), static_cast(1)); + EXPECT_EQ(onlineForA.latestOnlineDevice, DEVICE_NAME_B); + EXPECT_EQ(onlineForB.latestOnlineDevice, DEVICE_NAME_A); + + /** + * @tc.steps: step3. connect device A with device B + * @tc.expected: step3. device A callback B offline; device B callback A offline; + */ + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + EXPECT_EQ(onlineForA.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForB.onlineDevices.size(), static_cast(0)); + EXPECT_EQ(onlineForA.latestOfflineDevice, DEVICE_NAME_B); + EXPECT_EQ(onlineForB.latestOfflineDevice, DEVICE_NAME_A); + + // Clean up + g_envDeviceA.commAggrHandle->RegOnConnectCallback(nullptr, nullptr); + g_envDeviceB.commAggrHandle->RegOnConnectCallback(nullptr, nullptr); +} + +namespace { +LabelType ToLabelType(uint64_t commLabel) +{ + uint64_t netOrderLabel = HostToNet(commLabel); + uint8_t *eachByte = reinterpret_cast(&netOrderLabel); + std::vector realLabel(COMM_LABEL_LENGTH, 0); + for (int i = 0; i < static_cast(sizeof(uint64_t)); i++) { + realLabel[i] = eachByte[i]; + } + return realLabel; +} +} + +/** + * @tc.name: Report Communicator Not Found 001 + * @tc.desc: Test CommunicatorAggregator support report communicator not found event + * @tc.type: FUNC + * @tc.require: AR000DR9KV + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorTest, ReportCommunicatorNotFound001, TestSize.Level1) +{ + /** + * @tc.steps: step1. device B register communicator not found callback to CommunicatorAggregator + */ + std::vector lackLabels; + int errCode = g_envDeviceB.commAggrHandle->RegCommunicatorLackCallback( + [&lackLabels](const LabelType &commLabel, const std::string &userId)->int { + lackLabels.push_back(commLabel); + return -E_NOT_FOUND; + }, nullptr); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + + /** + * @tc.steps: step3. device A alloc communicator AA using label A and send message to B + * @tc.expected: step3. device B callback that label A not found. + */ + ICommunicator *commAA = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_A, errCode); + ASSERT_NOT_NULL_AND_ACTIVATE(commAA); + Message *msgForAA = BuildRegedTinyMessage(); + ASSERT_NE(msgForAA, nullptr); + SendConfig conf = {true, false, 0}; + errCode = commAA->SendMessage(DEVICE_NAME_B, msgForAA, conf); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + ASSERT_EQ(lackLabels.size(), static_cast(1)); + EXPECT_EQ(lackLabels[0], ToLabelType(LABEL_A)); + + /** + * @tc.steps: step4. device B alloc communicator BA using label A and register message callback + * @tc.expected: step4. communicator BA will not receive message. + */ + ICommunicator *commBA = g_envDeviceB.commAggrHandle->AllocCommunicator(LABEL_A, errCode); + ASSERT_NE(commBA, nullptr); + Message *recvMsgForBA = nullptr; + commBA->RegOnMessageCallback([&recvMsgForBA](const std::string &srcTarget, Message *inMsg) { + recvMsgForBA = inMsg; + }, nullptr); + commBA->Activate(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + EXPECT_EQ(recvMsgForBA, nullptr); + + // Clean up + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commAA); + g_envDeviceB.commAggrHandle->ReleaseCommunicator(commBA); + g_envDeviceB.commAggrHandle->RegCommunicatorLackCallback(nullptr, nullptr); + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +#define DO_SEND_MESSAGE(src, dst, label, session) \ +{ \ + Message *msgFor##src##label = BuildRegedTinyMessage(); \ + ASSERT_NE(msgFor##src##label, nullptr); \ + msgFor##src##label->SetSessionId(session); \ + SendConfig conf = {true, false, 0}; \ + errCode = comm##src##label->SendMessage(DEVICE_NAME_##dst, msgFor##src##label, conf); \ + EXPECT_EQ(errCode, E_OK); \ +} + +#define DO_SEND_GIANT_MESSAGE(src, dst, label, size) \ +{ \ + Message *msgFor##src##label = BuildRegedGiantMessage(size); \ + ASSERT_NE(msgFor##src##label, nullptr); \ + SendConfig conf = {false, false, 0}; \ + errCode = comm##src##label->SendMessage(DEVICE_NAME_##dst, msgFor##src##label, conf); \ + EXPECT_EQ(errCode, E_OK); \ +} + +#define ALLOC_AND_SEND_MESSAGE(src, dst, label, session) \ + ICommunicator *comm##src##label = g_envDevice##src.commAggrHandle->AllocCommunicator(LABEL_##label, errCode); \ + ASSERT_NOT_NULL_AND_ACTIVATE(comm##src##label); \ + DO_SEND_MESSAGE(src, dst, label, session) + +#define REG_MESSAGE_CALLBACK(src, label) \ + string srcTargetFor##src##label; \ + Message *recvMsgFor##src##label = nullptr; \ + comm##src##label->RegOnMessageCallback( \ + [&srcTargetFor##src##label, &recvMsgFor##src##label](const std::string &srcTarget, Message *inMsg) { \ + srcTargetFor##src##label = srcTarget; \ + recvMsgFor##src##label = inMsg; \ + }, nullptr); + +/** + * @tc.name: ReDeliver Message 001 + * @tc.desc: Test CommunicatorAggregator support redeliver message + * @tc.type: FUNC + * @tc.require: AR000DR9KV + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorTest, ReDeliverMessage001, TestSize.Level1) +{ + /** + * @tc.steps: step1. device B register communicator not found callback to CommunicatorAggregator + */ + std::vector lackLabels; + int errCode = g_envDeviceB.commAggrHandle->RegCommunicatorLackCallback( + [&lackLabels](const LabelType &commLabel, const std::string &userId)->int { + lackLabels.push_back(commLabel); + return E_OK; + }, nullptr); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + + /** + * @tc.steps: step3. device A alloc communicator AA using label A and send message to B + * @tc.expected: step3. device B callback that label A not found. + */ + ALLOC_AND_SEND_MESSAGE(A, B, A, 100); // session id 100 + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + ASSERT_EQ(lackLabels.size(), static_cast(1)); + EXPECT_EQ(lackLabels[0], ToLabelType(LABEL_A)); + + /** + * @tc.steps: step4. device A alloc communicator AB using label B and send message to B + * @tc.expected: step4. device B callback that label B not found. + */ + ALLOC_AND_SEND_MESSAGE(A, B, B, 200); // session id 200 + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + ASSERT_EQ(lackLabels.size(), static_cast(2)); + EXPECT_EQ(lackLabels[1], ToLabelType(LABEL_B)); // 1 for second element + + /** + * @tc.steps: step5. device B alloc communicator BA using label A and register message callback + * @tc.expected: step5. communicator BA will receive message. + */ + ICommunicator *commBA = g_envDeviceB.commAggrHandle->AllocCommunicator(LABEL_A, errCode); + ASSERT_NE(commBA, nullptr); + REG_MESSAGE_CALLBACK(B, A); + commBA->Activate(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + EXPECT_EQ(srcTargetForBA, DEVICE_NAME_A); + ASSERT_NE(recvMsgForBA, nullptr); + EXPECT_EQ(recvMsgForBA->GetSessionId(), 100U); // session id 100 + delete recvMsgForBA; + recvMsgForBA = nullptr; + + /** + * @tc.steps: step6. device B alloc communicator BB using label B and register message callback + * @tc.expected: step6. communicator BB will receive message. + */ + ICommunicator *commBB = g_envDeviceB.commAggrHandle->AllocCommunicator(LABEL_B, errCode); + ASSERT_NE(commBB, nullptr); + REG_MESSAGE_CALLBACK(B, B); + commBB->Activate(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + EXPECT_EQ(srcTargetForBB, DEVICE_NAME_A); + ASSERT_NE(recvMsgForBB, nullptr); + EXPECT_EQ(recvMsgForBB->GetSessionId(), 200U); // session id 200 + delete recvMsgForBB; + recvMsgForBB = nullptr; + + // Clean up + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commAA); + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commAB); + g_envDeviceB.commAggrHandle->ReleaseCommunicator(commBA); + g_envDeviceB.commAggrHandle->ReleaseCommunicator(commBB); + g_envDeviceB.commAggrHandle->RegCommunicatorLackCallback(nullptr, nullptr); + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} + +/** + * @tc.name: ReDeliver Message 002 + * @tc.desc: Test CommunicatorAggregator support redeliver message by order + * @tc.type: FUNC + * @tc.require: AR000DR9KV + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorTest, ReDeliverMessage002, TestSize.Level1) +{ + /** + * @tc.steps: step1. device C create CommunicatorAggregator and initialize + */ + bool step1 = SetUpEnv(g_envDeviceC, DEVICE_NAME_C); + ASSERT_EQ(step1, true); + + /** + * @tc.steps: step2. device B register communicator not found callback to CommunicatorAggregator + */ + int errCode = g_envDeviceB.commAggrHandle->RegCommunicatorLackCallback([](const LabelType &commLabel, + const std::string &userId)->int { + return E_OK; + }, nullptr); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step3. connect device A with device B, then device B with device C + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + AdapterStub::ConnectAdapterStub(g_envDeviceB.adapterHandle, g_envDeviceC.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + + /** + * @tc.steps: step4. device A alloc communicator AA using label A and send message to B + */ + ALLOC_AND_SEND_MESSAGE(A, B, A, 100); // session id 100 + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + + /** + * @tc.steps: step5. device C alloc communicator CA using label A and send message to B + */ + ALLOC_AND_SEND_MESSAGE(C, B, A, 200); // session id 200 + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + DO_SEND_MESSAGE(A, B, A, 300); // session id 300 + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + DO_SEND_MESSAGE(C, B, A, 400); // session id 400 + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + + /** + * @tc.steps: step6. device B alloc communicator BA using label A and register message callback + * @tc.expected: step6. communicator BA will receive message in order of sessionid 100, 200, 300, 400. + */ + ICommunicator *commBA = g_envDeviceB.commAggrHandle->AllocCommunicator(LABEL_A, errCode); + ASSERT_NE(commBA, nullptr); + std::vector> msgCallbackForBA; + commBA->RegOnMessageCallback([&msgCallbackForBA](const std::string &srcTarget, Message *inMsg) { + msgCallbackForBA.push_back({srcTarget, inMsg}); + }, nullptr); + commBA->Activate(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + ASSERT_EQ(msgCallbackForBA.size(), static_cast(4)); // total 4 callback + EXPECT_EQ(msgCallbackForBA[0].first, DEVICE_NAME_A); // the 0 order element + EXPECT_EQ(msgCallbackForBA[1].first, DEVICE_NAME_C); // the 1 order element + EXPECT_EQ(msgCallbackForBA[2].first, DEVICE_NAME_A); // the 2 order element + EXPECT_EQ(msgCallbackForBA[3].first, DEVICE_NAME_C); // the 3 order element + for (uint32_t i = 0; i < msgCallbackForBA.size(); i++) { + EXPECT_EQ(msgCallbackForBA[i].second->GetSessionId(), static_cast((i + 1) * 100)); // 1 sessionid 100 + delete msgCallbackForBA[i].second; + msgCallbackForBA[i].second = nullptr; + } + + // Clean up + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commAA); + g_envDeviceC.commAggrHandle->ReleaseCommunicator(commCA); + g_envDeviceB.commAggrHandle->ReleaseCommunicator(commBA); + g_envDeviceB.commAggrHandle->RegCommunicatorLackCallback(nullptr, nullptr); + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + AdapterStub::DisconnectAdapterStub(g_envDeviceB.adapterHandle, g_envDeviceC.adapterHandle); + TearDownEnv(g_envDeviceC); +} + +/** + * @tc.name: ReDeliver Message 003 + * @tc.desc: For observe memory in unusual scenario + * @tc.type: FUNC + * @tc.require: AR000DR9KV + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBCommunicatorTest, ReDeliverMessage003, TestSize.Level2) +{ + /** + * @tc.steps: step1. device B register communicator not found callback to CommunicatorAggregator + */ + int errCode = g_envDeviceB.commAggrHandle->RegCommunicatorLackCallback([](const LabelType &commLabel, + const std::string &userId)->int { + return E_OK; + }, nullptr); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. connect device A with device B + */ + AdapterStub::ConnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + + /** + * @tc.steps: step3. device A alloc communicator AA,AB,AC using label A,B,C + */ + ICommunicator *commAA = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_A, errCode); + ASSERT_NOT_NULL_AND_ACTIVATE(commAA); + ICommunicator *commAB = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_B, errCode); + ASSERT_NOT_NULL_AND_ACTIVATE(commAB); + ICommunicator *commAC = g_envDeviceA.commAggrHandle->AllocCommunicator(LABEL_C, errCode); + ASSERT_NOT_NULL_AND_ACTIVATE(commAC); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep 100 ms + + /** + * @tc.steps: step4. device A Continuously send tiny message to B using communicator AA,AB,AC + */ + for (int turn = 0; turn < 11; turn++) { // Total 11 turns + DO_SEND_MESSAGE(A, B, A, 0); + DO_SEND_MESSAGE(A, B, B, 0); + DO_SEND_MESSAGE(A, B, C, 0); + } + + /** + * @tc.steps: step5. device A Continuously send giant message to B using communicator AA,AB,AC + */ + for (int turn = 0; turn < 5; turn++) { // Total 5 turns + DO_SEND_GIANT_MESSAGE(A, B, A, (3 * 1024 * 1024)); // 3 MBytes, 1024 is scale + DO_SEND_GIANT_MESSAGE(A, B, B, (6 * 1024 * 1024)); // 6 MBytes, 1024 is scale + DO_SEND_GIANT_MESSAGE(A, B, C, (7 * 1024 * 1024)); // 7 MBytes, 1024 is scale + } + DO_SEND_GIANT_MESSAGE(A, B, A, (30 * 1024 * 1024)); // 30 MBytes, 1024 is scale + + /** + * @tc.steps: step6. wait a long time then send last frame + */ + for (int sec = 0; sec < 15; sec++) { // Total 15 s + std::this_thread::sleep_for(std::chrono::seconds(1)); // Sleep 1 s + LOGI("[UT][Test][ReDeliverMessage003] Sleep and wait=%d.", sec); + } + DO_SEND_MESSAGE(A, B, A, 0); + std::this_thread::sleep_for(std::chrono::seconds(1)); // Sleep 1 s + + // Clean up + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commAA); + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commAB); + g_envDeviceA.commAggrHandle->ReleaseCommunicator(commAC); + g_envDeviceB.commAggrHandle->RegCommunicatorLackCallback(nullptr, nullptr); + AdapterStub::DisconnectAdapterStub(g_envDeviceA.adapterHandle, g_envDeviceB.adapterHandle); +} diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_auto_launch_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_auto_launch_test.cpp new file mode 100644 index 00000000..f3772f70 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_auto_launch_test.cpp @@ -0,0 +1,727 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_constant.h" +#include "db_errno.h" +#include "distributeddb_tools_unit_test.h" +#include "kv_store_delegate_manager.h" +#include "kvdb_manager.h" +#include "kvdb_pragma.h" +#include "runtime_context.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { + // define some variables to init a KvStoreDelegateManager object. + const std::string APP_ID1 = "app1"; + const std::string USER_ID1 = "user1"; + const Key KEY_1 = {'K', '1'}; + KvStoreDelegateManager g_mgr(APP_ID1, USER_ID1); + std::string g_testDir; + + constexpr int MAX_AUTO_LAUNCH_NUM = 8; + constexpr uint32_t AUTO_LAUNCH_CYCLE_TIME = 6000; + constexpr uint32_t AUTO_LAUNCH_CHECK_TIME = (AUTO_LAUNCH_CYCLE_TIME / 2) + 500; // 500ms more than half. + constexpr int WAIT_FOR_RESPONSE_TIME = 200; + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvStoreStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvStore = nullptr; + auto g_kvNbDelegateCallback = std::bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + std::placeholders::_1, std::placeholders::_2, std::ref(g_kvStoreStatus), std::ref(g_kvStore)); + + const std::string SCHEMA_DEFINE1 = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":\"INTEGER, NOT NULL\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\"]}"; + const std::string SCHEMA_DEFINE2 = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name3\":\"INTEGER, NOT NULL\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\"]}"; + class StoreCommunicatorAggregator : public ICommunicatorAggregator { + public: + // Return 0 as success. Return negative as error + int Initialize(IAdapter *inAdapter) override + { + return E_OK; + } + + void Finalize() override + {} + + // If not success, return nullptr and set outErrorNo + ICommunicator *AllocCommunicator(uint64_t commLabel, int &outErrorNo) override + { + outErrorNo = -E_OUT_OF_MEMORY; + return nullptr; + } + ICommunicator *AllocCommunicator(const LabelType &commLabel, int &outErrorNo) override + { + outErrorNo = -E_OUT_OF_MEMORY; + return nullptr; + } + + void ReleaseCommunicator(ICommunicator *inCommunicator) override + {} + + int RegCommunicatorLackCallback(const CommunicatorLackCallback &onCommLack, const Finalizer &inOper) override + { + lackCallback_ = onCommLack; + return E_OK; + } + int RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) override + { + return E_OK; + } + + void PutCommLackInfo(const std::string &identifier) const + { + if (lackCallback_) { + std::vector vect(identifier.begin(), identifier.end()); + lackCallback_(vect, USER_ID1); + } + } + + int GetLocalIdentity(std::string &outTarget) const override + { + return E_OK; + } + private: + CommunicatorLackCallback lackCallback_; + }; + + struct AutoLaunchNotifyInfo { + void Reset() + { + triggerTimes = 0; + } + int triggerTimes = 0; + std::string userId; + std::string appId; + std::string storeId; + AutoLaunchStatus status = WRITE_CLOSED; + }; + AutoLaunchNotifyInfo g_autoLaunchNotifyInfo; + + void AutoLaunchNotifierCallback(AutoLaunchNotifyInfo &info, const std::string &userId, const std::string &appId, + const std::string &storeId, AutoLaunchStatus status) + { + info.triggerTimes++; + info.userId = userId; + info.appId = appId; + info.storeId = storeId; + info.status = status; + } + + auto g_autoLaunchNotifyFunc = std::bind(&AutoLaunchNotifierCallback, std::ref(g_autoLaunchNotifyInfo), + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + + StoreCommunicatorAggregator *g_aggregator = nullptr; +} + +class DistributedDBInterfacesAutoLaunchTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesAutoLaunchTest::SetUpTestCase(void) +{ + LOGI("Start test interface auto launch test"); + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + KvStoreConfig config; + config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(config); + g_aggregator = new (std::nothrow) StoreCommunicatorAggregator; + ASSERT_NE(g_aggregator, nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(g_aggregator); +} + +void DistributedDBInterfacesAutoLaunchTest::TearDownTestCase(void) +{ + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesAutoLaunchTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_kvStoreStatus = INVALID_ARGS; + g_kvStore = nullptr; +} + +void DistributedDBInterfacesAutoLaunchTest::TearDown(void) +{ + g_autoLaunchNotifyInfo.Reset(); +} +#if !defined(OMIT_ENCRYPT) && !defined(OMIT_JSON) +/** + * @tc.name: EnableKvStoreAutoLaunch001 + * @tc.desc: Enable the kvstore with the diff parameters. + * @tc.type: FUNC + * @tc.require: AR000DR9KU + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesAutoLaunchTest, EnableKvStoreAutoLaunch001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create the kv store with passwd and no schema. + * @tc.expected: step1. Returns a non-null kvstore. + */ + CipherPassword passwd; + std::vector passwdVect = {'p', 's', 'd', '1'}; + passwd.SetValue(passwdVect.data(), passwdVect.size()); + KvStoreNbDelegate::Option option = {true, false, true, CipherType::DEFAULT, passwd, SCHEMA_DEFINE1, false}; + std::string storeId = "test1"; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvStore != nullptr); + EXPECT_TRUE(g_kvStoreStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvStore), OK); + + /** + * @tc.steps: step2. Enable the kv store with different password. + * @tc.expected: step2. Returns INVALID_PASSWD_OR_CORRUPTED_DB. + */ + passwdVect = {'p', 's', 'd', '2'}; + CipherPassword passwdOther; + passwdOther.SetValue(passwdVect.data(), passwdVect.size()); + AutoLaunchOption launchOption = {true, true, CipherType::DEFAULT, passwdOther, "", false, g_testDir, nullptr}; + DBStatus status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId, + launchOption, nullptr); + EXPECT_NE(status, OK); + + /** + * @tc.steps: step3. Enable the kv store with different schema. + * @tc.expected: step3. Returns not OK. + */ + launchOption.passwd = passwd; + launchOption.schema = SCHEMA_DEFINE2; + status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId, + launchOption, nullptr); + EXPECT_NE(status, OK); + + /** + * @tc.steps: step4. Enable the kv store with correct parameter. + * @tc.expected: step4. Returns OK. + */ + launchOption.passwd = passwd; + launchOption.schema = SCHEMA_DEFINE1; + status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId, + launchOption, nullptr); + EXPECT_EQ(status, OK); + KvStoreDelegateManager::DisableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId); + g_mgr.DeleteKvStore(storeId); +} +#endif +/** + * @tc.name: EnableKvStoreAutoLaunch002 + * @tc.desc: Enable the kv store auto launch for the change of createIfNecessary. + * @tc.type: FUNC + * @tc.require: AR000DR9KU + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesAutoLaunchTest, EnableKvStoreAutoLaunch002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Enable the kv store with createIfNecessary is false. + * @tc.expected: step1. Returns not OK. + */ + CipherPassword passwd; + std::string storeId = "test2"; + AutoLaunchOption launchOption = {false, false, CipherType::DEFAULT, passwd, "", false, g_testDir, nullptr}; + DBStatus status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId, + launchOption, nullptr); + EXPECT_NE(status, OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), NOT_FOUND); + + /** + * @tc.steps: step2. Enable the kv store with createIfNecessary is true. + * @tc.expected: step2. Returns OK. + */ + launchOption.createIfNecessary = true; + status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId, + launchOption, nullptr); + EXPECT_EQ(status, OK); + EXPECT_EQ(KvStoreDelegateManager::DisableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); +} + +namespace { +IKvDB *GetKvDB(const std::string &storeId) +{ + KvDBProperties prop; + prop.SetStringProp(KvDBProperties::USER_ID, USER_ID1); + prop.SetStringProp(KvDBProperties::APP_ID, APP_ID1); + prop.SetStringProp(KvDBProperties::STORE_ID, storeId); + std::string identifier = DBCommon::TransferHashString(USER_ID1 + "-" + APP_ID1 + "-" + storeId); + + prop.SetStringProp(KvDBProperties::IDENTIFIER_DATA, identifier); + std::string identifierDir = DBCommon::TransferStringToHex(identifier); + prop.SetStringProp(KvDBProperties::IDENTIFIER_DIR, identifierDir); + prop.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + prop.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + prop.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + int errCode = E_OK; + return KvDBManager::OpenDatabase(prop, errCode); +} + +void PutSyncData(const std::string &storeId, const Key &key, const Value &value, bool isCover) +{ + auto kvStore = static_cast(GetKvDB(storeId)); + ASSERT_NE(kvStore, nullptr); + int errCode; + auto *connection = kvStore->GetDBConnection(errCode); + ASSERT_NE(connection, nullptr); + if (kvStore != nullptr) { + std::vector vect; + Timestamp time = 100; // initial valid timestamp. + kvStore->GetMaxTimestamp(time); + if (isCover) { + time += 10; // add the diff for 10. + } else { + time -= 10; // add the diff for -10. + } + vect.push_back({key, value, time, 0, DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(kvStore, vect, "deviceB"), E_OK); + } + RefObject::DecObjRef(kvStore); + connection->Close(); + connection = nullptr; +} + +void GetSyncData(const std::string &storeId) +{ + auto kvStore = static_cast(GetKvDB(storeId)); + ASSERT_NE(kvStore, nullptr); + int errCode; + auto *connection = kvStore->GetDBConnection(errCode); + ASSERT_NE(connection, nullptr); + + std::vector entries; + ContinueToken token = nullptr; + DataSizeSpecInfo syncDataSizeInfo = {DBConstant::MAX_VALUE_SIZE, DBConstant::MAX_HPMODE_PACK_ITEM_SIZE}; + kvStore->GetSyncData(0, UINT64_MAX / 2, entries, token, syncDataSizeInfo); // half of the max timestamp. + SingleVerKvEntry::Release(entries); + if (token != nullptr) { + kvStore->ReleaseContinueToken(token); + } + + RefObject::DecObjRef(kvStore); + connection->Close(); + connection = nullptr; +} + +void PrePutDataIntoDatabase(const std::string &storeId) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvStore != nullptr); + EXPECT_TRUE(g_kvStoreStatus == OK); + + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + + EXPECT_EQ(g_kvStore->Put(KEY_1, value), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvStore), OK); +} + +void TriggerAutoLaunch(const std::string &storeId, bool isWriteCovered) +{ + /** + * @tc.steps: step1. Enable the auto launch of the database. + * @tc.expected: step1. Returns OK. + */ + PrePutDataIntoDatabase(storeId); + CipherPassword passwd; + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_NE(observer, nullptr); + + AutoLaunchOption launchOption = {true, false, CipherType::DEFAULT, passwd, "", false, g_testDir, observer}; + DBStatus status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId, launchOption, + g_autoLaunchNotifyFunc); + EXPECT_EQ(status, OK); + + /** + * @tc.steps: step2. Trigger the auto launch of the database. + */ + std::string identifier = DBCommon::TransferHashString(USER_ID1 + "-" + APP_ID1 + "-" + storeId); + g_aggregator->PutCommLackInfo(identifier); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_RESPONSE_TIME)); + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + PutSyncData(storeId, KEY_1, value, isWriteCovered); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_RESPONSE_TIME)); + /** + * @tc.steps: step3. Check the notifier and the observer. + */ + if (!isWriteCovered) { + EXPECT_EQ(g_autoLaunchNotifyInfo.triggerTimes, 0); + } else { + EXPECT_GT(g_autoLaunchNotifyInfo.triggerTimes, 0); + EXPECT_EQ(g_autoLaunchNotifyInfo.status, WRITE_OPENED); + EXPECT_GT(observer->GetCallCount(), 0UL); + } + + EXPECT_EQ(KvStoreDelegateManager::DisableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_RESPONSE_TIME)); + delete observer; + observer = nullptr; + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); +} +} + +/** + * @tc.name: EnableKvStoreAutoLaunch003 + * @tc.desc: test the data change and the notifier of the auto open for no data changed. + * @tc.type: FUNC + * @tc.require: AR000DR9KU + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesAutoLaunchTest, EnableKvStoreAutoLaunch003, TestSize.Level2) +{ + /** + * @tc.steps: step1. Enable the auto launch of the database. + * @tc.steps: step2. Trigger the auto launch of the database. + * @tc.steps: step3. Put the data which would be dispatched into the database by sync. + * @tc.steps: step4. Check the notifier and the observer change. + * @tc.expected: step1. Returns OK. + * @tc.expected: step4. The notifier and the observer wouldn't be triggered. + */ + TriggerAutoLaunch("test3", false); +} + +/** + * @tc.name: EnableKvStoreAutoLaunch004 + * @tc.desc: test the data change and the notifier of the auto open for data changed. + * @tc.type: FUNC + * @tc.require: AR000DR9KU + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesAutoLaunchTest, EnableKvStoreAutoLaunch004, TestSize.Level2) +{ + /** + * @tc.steps: step1. Enable the auto launch of the database. + * @tc.steps: step2. Trigger the auto launch of the database. + * @tc.steps: step3. Put the data which would overwrite into the database by sync. + * @tc.steps: step4. Check the notifier and the observer change. + * @tc.expected: step1. Returns OK. + * @tc.expected: step4. The notifier and the observer would be triggered. + */ + TriggerAutoLaunch("test4", true); +} + +/** + * @tc.name: EnableKvStoreAutoLaunch005 + * @tc.desc: Test enable the same database twice. + * @tc.type: FUNC + * @tc.require: AR000DR9KU + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesAutoLaunchTest, EnableKvStoreAutoLaunch005, TestSize.Level1) +{ + /** + * @tc.steps: step1. Enable the kv store auto launch. + * @tc.expected: step1. Returns OK. + */ + std::string storeId = "test5"; + CipherPassword passwd; + AutoLaunchOption launchOption = {true, false, CipherType::DEFAULT, passwd, "", false, g_testDir, nullptr}; + DBStatus status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId, launchOption, + nullptr); + EXPECT_EQ(status, OK); + + /** + * @tc.steps: step2. Ee-enable the kv store auto launch. + * @tc.expected: step2. Returns not OK. + */ + status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId, launchOption, + nullptr); + EXPECT_NE(status, OK); + EXPECT_EQ(KvStoreDelegateManager::DisableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); +} + +/** + * @tc.name: EnableKvStoreAutoLaunch005 + * @tc.desc: test the over limits for the enable list. + * @tc.type: FUNC + * @tc.require: AR000DR9KU + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesAutoLaunchTest, EnableKvStoreAutoLaunch006, TestSize.Level2) +{ + /** + * @tc.steps: step1. Enable the 8 kv store auto launch. + * @tc.expected: step1. Returns OK. + */ + CipherPassword passwd; + AutoLaunchOption launchOption = {true, false, CipherType::DEFAULT, passwd, "", false, g_testDir, nullptr}; + for (int i = 0; i < MAX_AUTO_LAUNCH_NUM; i++) { + std::string storeId = "store_" + std::to_string(i + 1); + DBStatus status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId, + launchOption, nullptr); + EXPECT_EQ(status, OK); + } + + /** + * @tc.steps: step2. Enable the 9th kv store auto launch. + * @tc.expected: step2. Returns OK. + */ + DBStatus status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, "store_9", + launchOption, nullptr); + EXPECT_EQ(status, OVER_MAX_LIMITS); + + /** + * @tc.steps: step3. Disable the 1th kv store auto launch. + * @tc.expected: step3. Returns OK. + */ + EXPECT_EQ(KvStoreDelegateManager::DisableKvStoreAutoLaunch(USER_ID1, APP_ID1, "store_1"), OK); + /** + * @tc.steps: step4. Enable the 9th kv store auto launch. + * @tc.expected: step4. Returns OK. + */ + status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, "store_9", + launchOption, nullptr); + EXPECT_EQ(status, OK); + + /** + * @tc.steps: step5. Disable all the kv stores auto launched. + * @tc.expected: step5. Returns OK. + */ + for (int i = 1; i <= MAX_AUTO_LAUNCH_NUM; i++) { + std::string storeId = "store_" + std::to_string(i + 1); + EXPECT_EQ(KvStoreDelegateManager::DisableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId), OK); + } + /** + * @tc.steps: step6. Disable the kv stores which is not enabled. + * @tc.expected: step6. Returns NOT_FOUND. + */ + EXPECT_EQ(KvStoreDelegateManager::DisableKvStoreAutoLaunch(USER_ID1, APP_ID1, "store_1"), NOT_FOUND); +} + +namespace { +void SetAutoLaunchLifeCycleTime(const std::string &storeId, uint32_t time) +{ + LOGI("SetAutoLifeTime:%u", time); + auto kvStore = static_cast(GetKvDB(storeId)); + ASSERT_NE(kvStore, nullptr); + int errCode; + auto *connection = kvStore->GetDBConnection(errCode); + ASSERT_NE(connection, nullptr); + EXPECT_EQ(connection->Pragma(PRAGMA_SET_AUTO_LIFE_CYCLE, static_cast(&time)), E_OK); + RefObject::DecObjRef(kvStore); + connection->Close(); + connection = nullptr; +} +} +/** + * @tc.name: EnableKvStoreAutoLaunch007 + * @tc.desc: test the over limits for the enable list. + * @tc.type: FUNC + * @tc.require: AR000DR9KU + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesAutoLaunchTest, DisableKvStoreAutoLaunch001, TestSize.Level3) +{ + /** + * @tc.steps: step1. Enable the auto launch for 'test7'. + * @tc.expected: step1. Returns OK. + */ + CipherPassword passwd; + AutoLaunchOption launchOption = {true, false, CipherType::DEFAULT, passwd, "", false, g_testDir, nullptr}; + std::string storeId = "test7"; + DBStatus status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId, launchOption, + g_autoLaunchNotifyFunc); + EXPECT_EQ(status, OK); + /** + * @tc.steps: step2. Disable the auto launch for 'test7'. + * @tc.expected: step2. Returns OK. + */ + EXPECT_EQ(KvStoreDelegateManager::DisableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_RESPONSE_TIME)); + /** + * @tc.steps: step3. Trigger the auto launch and check the status of the database. + * @tc.expected: step3. The database was not auto launched. + */ + std::string identifier = DBCommon::TransferHashString(USER_ID1 + "-" + APP_ID1 + "-" + storeId); + g_aggregator->PutCommLackInfo(identifier); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_RESPONSE_TIME)); + SetAutoLaunchLifeCycleTime(storeId, AUTO_LAUNCH_CYCLE_TIME); + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + PutSyncData(storeId, KEY_1, value, true); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_RESPONSE_TIME)); + EXPECT_EQ(g_autoLaunchNotifyInfo.triggerTimes, 0); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); +} + +/** + * @tc.name: AutoLaunchLifeCycle001 + * @tc.desc: test the auto closed for the database auto launched by the msg. + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesAutoLaunchTest, AutoLaunchLifeCycle001, TestSize.Level3) +{ + /** + * @tc.steps: step1. Enable the auto launch for 'test8'. + * @tc.expected: step1. Returns OK. + */ + CipherPassword passwd; + AutoLaunchOption launchOption = {true, false, CipherType::DEFAULT, passwd, "", false, g_testDir, nullptr}; + std::string storeId = "test8"; + DBStatus status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId, launchOption, + g_autoLaunchNotifyFunc); + EXPECT_EQ(status, OK); + + /** + * @tc.steps: step2. Trigger the auto launch. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_RESPONSE_TIME)); + std::string identifier = DBCommon::TransferHashString(USER_ID1 + "-" + APP_ID1 + "-" + storeId); + g_aggregator->PutCommLackInfo(identifier); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_RESPONSE_TIME)); + SetAutoLaunchLifeCycleTime(storeId, AUTO_LAUNCH_CYCLE_TIME); + /** + * @tc.steps: step3. Put data into the database by sync. + */ + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + PutSyncData(storeId, KEY_1, value, true); + std::this_thread::sleep_for(std::chrono::milliseconds(AUTO_LAUNCH_CHECK_TIME)); + /** + * @tc.steps: step4. Check the notifier. + * @tc.expected: step4. notifier is triggered for the opened change. + */ + EXPECT_GT(g_autoLaunchNotifyInfo.triggerTimes, 0); + EXPECT_NE(g_mgr.DeleteKvStore(storeId), OK); + g_autoLaunchNotifyInfo.Reset(); + std::this_thread::sleep_for(std::chrono::milliseconds(AUTO_LAUNCH_CHECK_TIME)); + /** + * @tc.steps: step5. Check the notifier for waiting for more than the life time of the auto launched database. + * @tc.expected: step5. notifier is triggered for the closed change. + */ + EXPECT_GT(g_autoLaunchNotifyInfo.triggerTimes, 0); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); + + EXPECT_EQ(KvStoreDelegateManager::DisableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_RESPONSE_TIME)); +} + +namespace { +void DelayAutoLaunchCycle(const std::string &storeId, bool isWrite) +{ + CipherPassword passwd; + AutoLaunchOption launchOption = {true, false, CipherType::DEFAULT, passwd, "", false, g_testDir, nullptr}; + /** + * @tc.steps: step1. Enable the auto launch for 'test8'. + * @tc.expected: step1. Returns OK. + */ + DBStatus status = KvStoreDelegateManager::EnableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId, launchOption, + nullptr); + EXPECT_EQ(status, OK); + + /** + * @tc.steps: step2. Trigger the auto launch. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_RESPONSE_TIME)); + std::string identifier = DBCommon::TransferHashString(USER_ID1 + "-" + APP_ID1 + "-" + storeId); + g_aggregator->PutCommLackInfo(identifier); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_RESPONSE_TIME)); + SetAutoLaunchLifeCycleTime(storeId, AUTO_LAUNCH_CYCLE_TIME); + + /** + * @tc.steps: step3. Write/Read the data into/from the database by sync. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(AUTO_LAUNCH_CHECK_TIME)); + EXPECT_NE(g_mgr.DeleteKvStore(storeId), OK); + if (isWrite) { + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + PutSyncData(storeId, KEY_1, value, true); + } else { + GetSyncData(storeId); + } + + /** + * @tc.steps: step5. Check the status of the auto launched database. + * @tc.expected: step5. the life cycle of the auto launched database is prolonged by the sync operation. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(AUTO_LAUNCH_CHECK_TIME)); + EXPECT_NE(g_mgr.DeleteKvStore(storeId), OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(AUTO_LAUNCH_CHECK_TIME)); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); + + EXPECT_EQ(KvStoreDelegateManager::DisableKvStoreAutoLaunch(USER_ID1, APP_ID1, storeId), OK); +} +} + +/** + * @tc.name: AutoLaunchLifeCycle002 + * @tc.desc: test the over limits for the enable list. + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesAutoLaunchTest, AutoLaunchLifeCycle002, TestSize.Level3) +{ + /** + * @tc.steps: step1. Enable the auto launch for 'test_9'. + * @tc.steps: step2. Trigger the auto launch. + * @tc.steps: step3. Trigger the sync writing operation. + * @tc.steps: step4. Check the status of the auto launched database. + * @tc.expected: step1. Returns OK. + * @tc.expected: step4. The life cycle is prolonged for the writing operation. + */ + DelayAutoLaunchCycle("test_9", true); +} + +/** + * @tc.name: AutoLaunchLifeCycle003 + * @tc.desc: test the life cycle of the auto launched database in the sync reading scene. + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesAutoLaunchTest, AutoLaunchLifeCycle003, TestSize.Level3) +{ + /** + * @tc.steps: step1. Enable the auto launch for 'test_10'. + * @tc.steps: step2. Trigger the auto launch. + * @tc.steps: step3. Trigger the sync reading operation. + * @tc.steps: step4. Check the status of the auto launched database. + * @tc.expected: step1. Returns OK. + * @tc.expected: step4. The life cycle is prolonged for the reading operation. + */ + DelayAutoLaunchCycle("test_10", false); +} diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_data_operation_syncdb_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_data_operation_syncdb_test.cpp new file mode 100644 index 00000000..4f44a1c9 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_data_operation_syncdb_test.cpp @@ -0,0 +1,1381 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_errno.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "sqlite_single_ver_natural_store.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + const bool LOCAL_ONLY = false; + const string STORE_ID = STORE_ID_SYNC; + const int OBSERVER_SLEEP_TIME = 100; + + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + + // define the g_snapshotDelegateCallback, used to get some information when open a kv snapshot. + DBStatus g_snapshotDelegateStatus = INVALID_ARGS; + KvStoreSnapshotDelegate *g_snapshotDelegatePtr = nullptr; + // the type of g_snapshotDelegateCallback is function + auto g_snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_snapshotDelegateStatus), std::ref(g_snapshotDelegatePtr)); + + // define the g_valueCallback, used to query a value object data from the kvdb. + DBStatus g_valueStatus = INVALID_ARGS; + Value g_value; + // the type of g_valueCallback is function + auto g_valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(g_valueStatus), std::ref(g_value)); + + // define the g_entryVectorCallback, used to query a vector object data from the kvdb. + DBStatus g_entryVectorStatus = INVALID_ARGS; + unsigned long g_matchSize = 0; + std::vector g_entriesVector; + // the type of g_entryVectorCallback is function)> + auto g_entryVectorCallback = bind(&DistributedDBToolsUnitTest::EntryVectorCallback, placeholders::_1, + placeholders::_2, std::ref(g_entryVectorStatus), std::ref(g_matchSize), std::ref(g_entriesVector)); + + const uint32_t MAX_KEY_SIZE = 1024; + const uint32_t MAX_VAL_SIZE = 4194304; + const uint32_t INVALID_KEY_SIZE = 1025; + + Entry g_entryA; + Entry g_entryB; + Entry g_entryC; + Entry g_entryD; + + void GetSnapshotUnitTest() + { + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, g_snapshotDelegateCallback); + EXPECT_TRUE(g_snapshotDelegateStatus == OK); + ASSERT_TRUE(g_snapshotDelegatePtr != nullptr); + } +} + +class DistributedDBInterfacesDataOperationSyncDBTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesDataOperationSyncDBTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesDataOperationSyncDBTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesDataOperationSyncDBTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + // init values. + g_valueStatus = INVALID_ARGS; + g_value.clear(); + g_entryVectorStatus = INVALID_ARGS; + g_matchSize = 0; + + /* + * Here, we create STORE_ID.db before test, + * and it will be closed in TearDown(). + */ + CipherPassword passwd; + KvStoreDelegate::Option option = {true, LOCAL_ONLY, false, CipherType::DEFAULT, passwd}; + g_mgr.GetKvStore(STORE_ID, option, g_kvDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); +} + +void DistributedDBInterfacesDataOperationSyncDBTest::TearDown(void) +{ + if (g_kvDelegatePtr != nullptr && g_snapshotDelegatePtr != nullptr) { + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; + } + + if (g_kvDelegatePtr != nullptr) { + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + } + + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID), OK); +} + +/** + * @tc.name: Put001 + * @tc.desc: Put a data(non-empty key, non-empty value) into an exist distributed db + * @tc.type: FUNC + * @tc.require: AR000CQDTM AR000CQS3R + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, Put001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the data(non-empty key and non-empty value) into the database. + * @tc.expected: step1. Put returns OK. + */ + Key keyTmp; + keyTmp.push_back(1); + Value valueTmp; + valueTmp.push_back('7'); + EXPECT_TRUE(g_kvDelegatePtr->Put(keyTmp, valueTmp) == OK); + /** + * @tc.steps: step2. Get the value according the key through the snapshot. + * @tc.expected: step2. Get returns OK. + */ + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == OK); +} + +/** + * @tc.name: Put002 + * @tc.desc: Put a data(empty key) into an exist distributed db + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, Put002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the data(empty key) into the database. + * @tc.expected: step1. Put returns INVALID_ARGS. + */ + Key keyTmp; + Value valueTmp; + valueTmp.push_back('7'); + EXPECT_TRUE(g_kvDelegatePtr->Put(keyTmp, valueTmp) == INVALID_ARGS); +} + +/** + * @tc.name: Put003 + * @tc.desc: Put a data(non-empty key, empty value) into an exist distributed db + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, Put003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the data(empty value) into the database. + * @tc.expected: step1. Put returns OK. + */ + Key keyTmp; + keyTmp.push_back(1); + Value valueTmp; + + EXPECT_TRUE(g_kvDelegatePtr->Put(keyTmp, valueTmp) == OK); +} + +/** + * @tc.name: Put004 + * @tc.desc: Put data into the multiversion database + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, Put004, TestSize.Level1) +{ + /** + * @tc.steps: step1. clear the database. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + + Key keyTmp; + keyTmp.push_back(1); + + Value valueTmp; + valueTmp.push_back('7'); + + /** + * @tc.steps: step2. Put one data into the database. + * @tc.expected: step2. Put returns OK. + */ + Value valueTest; + valueTest.push_back('9'); + EXPECT_TRUE(g_kvDelegatePtr->Put(keyTmp, valueTmp) == OK); + + /** + * @tc.steps: step3. Get the data from the database. + * @tc.expected: step3. Get returns OK and the read value is equal to the value put before. + */ + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == OK); + EXPECT_TRUE(g_value.size() > 0); + if (g_value.size() > 0) { + EXPECT_TRUE(g_value.front() == '7'); + } + + /** + * @tc.steps: step4. Change the value, and Put the data into the database. + * @tc.expected: step4. Put returns OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(keyTmp, valueTest) == OK); + + if (g_kvDelegatePtr != nullptr && g_snapshotDelegatePtr != nullptr) { + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; + } + GetSnapshotUnitTest(); + /** + * @tc.steps: step5. Get the data from the database. + * @tc.expected: step5. Get returns OK and the read value is equal to the new put value. + */ + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == OK); + EXPECT_TRUE(g_value.size() > 0); + if (g_value.size() > 0) { + EXPECT_TRUE(g_value.front() == '9'); + } +} + +/** + * @tc.name: Clear001 + * @tc.desc: Clear the data from an exist distributed db + * @tc.type: FUNC + * @tc.require: AR000CQDTM AR000CQS3R + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, Clear001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the valid data into the database. + */ + Key keyTmp; + DistributedDBToolsUnitTest::GetRandomKeyValue(keyTmp); + Value valueTmp; + DistributedDBToolsUnitTest::GetRandomKeyValue(valueTmp); + EXPECT_TRUE(g_kvDelegatePtr->Put(keyTmp, valueTmp) == OK); + /** + * @tc.steps: step2. Clear the database. + * @tc.expected: step2. Clear returns OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + /** + * @tc.steps: step3. Get the data from the database according the inserted key before clear. + * @tc.expected: step3. Get returns NOT_FOUND. + */ + Key key; + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_EQ(g_valueStatus, NOT_FOUND); +} + +/** + * @tc.name: PutBatch001 + * @tc.desc: Putbatch data into the multiversion database + * @tc.type: FUNC + * @tc.require: AR000CQDTM AR000CQS3R + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, PutBatch001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the prepared data. + */ + vector entries; + for (int i = 1; i < 10; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + } + /** + * @tc.steps: step2. PutBatch the prepared data. + * @tc.expected: step2. PutBatch returns OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + + /** + * @tc.steps: step3. Get the data from the database. + * @tc.expected: step3. Get returns OK and the get value is equal to the inserted value before. + */ + GetSnapshotUnitTest(); + for (int i = 1; i < 10; i++) { + Key keyTmp; + keyTmp.push_back(i); + + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == OK); + EXPECT_TRUE(g_value.size() > 0); + if (g_value.size() > 0) { + EXPECT_TRUE(g_value.front() == '8'); + } + } +} + +/** + * @tc.name: PutBatch002 + * @tc.desc: PutBatch modified data into the multiversion database + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, PutBatch002, TestSize.Level1) +{ + /** + * @tc.steps: step1. prepare the batch data. + */ + vector entries; + for (int i = 1; i < 10; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('2'); + entries.push_back(entry); + } + /** + * @tc.steps: step2. PutBatch the prepared batch data. + * @tc.expected: step2. PutBatch returns OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + /** + * @tc.steps: step3. Get data from the database according the inserted keys. + * @tc.expected: step3. Get returns OK and the read value is equal to the inserted value. + */ + GetSnapshotUnitTest(); + for (int i = 1; i < 10; i++) { + Key keyTmp; + keyTmp.push_back(i); + + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == OK); + EXPECT_TRUE(g_value.size() > 0); + if (g_value.size() > 0) { + EXPECT_TRUE(g_value.front() == '2'); + } + } +} + +/** + * @tc.name: Delete001 + * @tc.desc: Delete existed data from the multiversion database + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, Delete001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the prepared data. + */ + Key keyTmp; + keyTmp.push_back(1); + Value valueTmp; + valueTmp.push_back(3); + EXPECT_EQ(g_kvDelegatePtr->Put(keyTmp, valueTmp), OK); + /** + * @tc.steps: step2. Delete the existed data from the database. + * @tc.expected: step2. Delete returns OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Delete(keyTmp), OK); + /** + * @tc.steps: step3. Get the deleted data from the database. + * @tc.expected: step3. Get returns NOT_FOUND. + */ + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == NOT_FOUND); +} + +/** + * @tc.name: Delete002 + * @tc.desc: Delete non-existed data from the multiversion database + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, Delete002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Clear the database. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + + /** + * @tc.steps: step2. Delete the non-existed data from the database. + * @tc.expected: step2. Delete returns OK. + */ + Key keyTmp; + keyTmp.push_back(1); + EXPECT_EQ(g_kvDelegatePtr->Delete(keyTmp), OK); +} + +/** + * @tc.name: DeleteBatch001 + * @tc.desc: Delete the existed batch data from the multiversion database + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, DeleteBatch001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the batch data into the database. + */ + vector entries; + for (int i = 1; i < 4; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('2'); + entries.push_back(entry); + } + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + + /** + * @tc.steps: step2. Delete the batch data from the database. + * @tc.steps: step2. DeleteBatch returns OK. + */ + vector keys; + for (int i = 1; i < 4; i++) { + Key key; + key.push_back(i); + keys.push_back(key); + } + EXPECT_TRUE(g_kvDelegatePtr->DeleteBatch(keys) == OK); + + /** + * @tc.steps: step3. Get all the data from the database. + * @tc.steps: step3. GetEntries result NOT_FOUND. + */ + Key keyTmp; + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->GetEntries(keyTmp, g_entryVectorCallback); + EXPECT_EQ(g_entryVectorStatus, NOT_FOUND); +} + +/** + * @tc.name: DeleteBatch002 + * @tc.desc: Delete the non-existed batch data from the multiversion database + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, DeleteBatch002, TestSize.Level1) +{ + /** + * @tc.steps: step1. clear the database. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + /** + * @tc.steps: step2. Delete the batch non-existed data from the database. + * @tc.expected: step2. DeleteBatch returns OK + */ + vector keys; + for (int i = 1; i < 10; i++) { + Key key; + key.push_back(i); + keys.push_back(key); + } + EXPECT_TRUE(g_kvDelegatePtr->DeleteBatch(keys) == OK); +} + +/** + * @tc.name: GetEntries001 + * @tc.desc: Get the batch data from the non-empty database by the prefix key. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, GetEntries001, TestSize.Level1) +{ + /** + * @tc.steps: step1. insert batch data into the database. + */ + vector entries; + for (int i = 1; i <= 10; i++) { + Entry entry; + for (int j = 1; j <= i; j++) { + entry.key.push_back(j); + } + entry.value.push_back(i); + entries.push_back(entry); + } + + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + + Key keyPrefix; + for (int j = 1; j <= 5; j++) { + keyPrefix.push_back(j); + } + /** + * @tc.steps: step2. Get batch data from the database using the prefix key. + * @tc.expected: step2. GetEntries results OK and the result entries size is the match size. + */ + unsigned long matchSize = 6; + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->GetEntries(keyPrefix, g_entryVectorCallback); + ASSERT_TRUE(g_matchSize == matchSize); + EXPECT_TRUE(g_entryVectorStatus == OK); +} + +/** + * @tc.name: GetEntries002 + * @tc.desc: Get all the data(empty prefixkey) from the empty database. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, GetEntries002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get all the data from the empty database. + * @tc.expected: step1. GetEntries results NOT_FOUND. + */ + Key keyPrefix; + for (int j = 1; j <= 5; j++) { + keyPrefix.push_back('a'); + } + + unsigned long matchSize = 0; + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->GetEntries(keyPrefix, g_entryVectorCallback); + ASSERT_TRUE(g_matchSize == matchSize); + EXPECT_TRUE(g_entryVectorStatus == NOT_FOUND); +} + +/** + * @tc.name: GetEntries003 + * @tc.desc: Get all the data(empty prefixkey) from the database. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, GetEntries003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put batch data into the database. + */ + vector entries; + const unsigned long entriesSize = 10; + for (unsigned long i = 1; i <= entriesSize; i++) { + Entry entry; + for (unsigned long j = 1; j <= i; j++) { + entry.key.push_back(j); + } + entry.value.push_back(i); + entries.push_back(entry); + } + + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + + /** + * @tc.steps: step2. Get all the data from the database using the empty prefix key. + * @tc.expected: step2. GetEntries results OK and the entries size is the put batch data size. + */ + Key keyPrefix; + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->GetEntries(keyPrefix, g_entryVectorCallback); + ASSERT_EQ(g_matchSize, entriesSize); + EXPECT_TRUE(g_entryVectorStatus == OK); +} + +static void TestSnapshotCreateAndRelease() +{ + DBStatus status; + KvStoreSnapshotDelegate *snapshot = nullptr; + KvStoreObserver *observer = nullptr; + auto snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshot)); + + /** + * @tc.steps: step1. Obtain the snapshot object snapshot through + * the GetKvStoreSnapshot interface of the delegate. + * @tc.expected: step1. Returns a non-empty snapshot. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(observer, snapshotDelegateCallback); + + EXPECT_TRUE(status == OK); + EXPECT_NE(snapshot, nullptr); + + /** + * @tc.steps: step2. Release the obtained snapshot through + * the ReleaseKvStoreSnapshot interface of the delegate. + * @tc.expected: step2. Release successfully. + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshot), OK); +} + +/** + * @tc.name: GetSnapshot001 + * @tc.desc: Get observer is empty, whether you get the snapshot. + * @tc.type: FUNC + * @tc.require: AR000BVRNF AR000CQDTI + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, GetSnapshot001, TestSize.Level1) +{ + /** + * @tc.steps: step1.Obtain the snapshot object whose observer is null + * by using the GetKvStoreSnapshot interface of the delegate. + * @tc.expected: step1. The obtained snapshot is not empty. + */ + TestSnapshotCreateAndRelease(); +} + +/** + * @tc.name: GetSnapshot002 + * @tc.desc: Get observer is not empty, whether you get the snapshot. + * @tc.type: FUNC + * @tc.require: AR000BVRNF AR000CQDTI + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, GetSnapshot002, TestSize.Level1) +{ + /** + * @tc.steps: step1.Obtain the snapshot object whose observer is null + * by using the GetKvStoreSnapshot interface of the delegate. + * @tc.expected: step1. The obtained snapshot is not empty. + */ + DBStatus status; + KvStoreSnapshotDelegate *snapshot = nullptr; + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_NE(observer, nullptr); + auto snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshot)); + g_kvDelegatePtr->GetKvStoreSnapshot(observer, snapshotDelegateCallback); + + EXPECT_TRUE(status == OK); + EXPECT_NE(snapshot, nullptr); + + /** + * @tc.steps: step2. Release the snapshot get before. + * @tc.expected: step2. ReleaseKvStoreSnapshot returns OK. + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshot), OK); + delete observer; + observer = nullptr; +} + +/** + * @tc.name: ReleaseSnapshot001 + * @tc.desc: To test the function of releasing an empty snapshot. + * @tc.type: FUNC + * @tc.require: AR000BVRNF AR000CQDTI + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, ReleaseSnapshot001, TestSize.Level1) +{ + /** + * @tc.steps: step1.Release the null pointer snapshot through + * the ReleaseKvStoreSnapshot interface of the delegate. + * @tc.expected: step1. Return ERROR. + */ + KvStoreSnapshotDelegate *snapshot = nullptr; + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshot), DB_ERROR); +} + +/** + * @tc.name: ReleaseSnapshot002 + * @tc.desc: Release the obtained snapshot object that is not empty. + * @tc.type: FUNC + * @tc.require: AR000BVRNF AR000CQDTI + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, ReleaseSnapshot002, TestSize.Level1) +{ + TestSnapshotCreateAndRelease(); +} + +static void TestSnapshotEntryPut() +{ + KvStoreObserverUnitTest *observer = nullptr; + DBStatus status; + KvStoreSnapshotDelegate *snapshotA = nullptr; + auto snapshotDelegateCallbackA = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotA)); + + /** + * @tc.steps: step1.Release the null pointer snapshot through + * the ReleaseKvStoreSnapshot interface of the delegate. + * @tc.expected: step1. Return not empty snapshot. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(observer, snapshotDelegateCallbackA); + ASSERT_NE(snapshotA, nullptr); + Key keyA; + Value valueA; + Value valueB; + DistributedDBToolsUnitTest::GetRandomKeyValue(keyA); + DistributedDBToolsUnitTest::GetRandomKeyValue(valueA); + DistributedDBToolsUnitTest::GetRandomKeyValue(valueB); + + /** + * @tc.steps: step2. Obtain the keyA data through the Get interface of the snapshotA. + * @tc.expected: step2. Return NOT_FOUND. + */ + snapshotA->Get(keyA, g_valueCallback); + EXPECT_EQ(g_valueStatus, NOT_FOUND); + + /** + * @tc.steps: step3. Insert the data of keyA and valueA through the Put interface of the delegate. + */ + g_kvDelegatePtr->Put(keyA, valueA); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + KvStoreSnapshotDelegate *snapshotB = nullptr; + auto snapshotDelegateCallbackB = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotB)); + + /** + * @tc.steps: step5. Obtain the snapshot object snapshotB through the GetKvStoreSnapshot + * interface of the delegate. Obtain the keyA data through the Get interface of the snapshotB. + * @tc.expected: step5. Return a non-empty snapshot. The value of keyA is valueA.. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(observer, snapshotDelegateCallbackB); + ASSERT_NE(snapshotA, nullptr); + + /** + * @tc.steps: step4. Obtain the keyA data through the Get interface of the snapshotA. + * @tc.expected: step4. Return NOT_FOUND. + */ + snapshotA->Get(keyA, g_valueCallback); + EXPECT_EQ(g_valueStatus, NOT_FOUND); + snapshotB->Get(keyA, g_valueCallback); + EXPECT_EQ(g_valueStatus, OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(g_value, valueA), true); + + /** + * @tc.steps: step6. Insert the data of keyA and valueB through the Put interface of the delegate.. + */ + g_kvDelegatePtr->Put(keyA, valueB); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + KvStoreSnapshotDelegate *snapshotC = nullptr; + auto snapshotDelegateCallbackC = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotC)); + + /** + * @tc.steps: step7. Obtain the snapshotC through the GetKvStoreSnapshot interface + * of the delegate and obtain the data of the keyA through the Get interface. + * @tc.expected: step7. Return a non-empty snapshot. The value of keyA is valueB. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(observer, snapshotDelegateCallbackC); + ASSERT_NE(snapshotC, nullptr); + + /** + * @tc.steps: step8. Obtain the keyA data through the Get interface of the snapshotB. + * @tc.expected: step8. Return OK, and the value of keyA is valueA.. + */ + snapshotB->Get(keyA, g_valueCallback); + EXPECT_EQ(g_valueStatus, OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(g_value, valueA), true); + snapshotC->Get(keyA, g_valueCallback); + EXPECT_EQ(g_valueStatus, OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(g_value, valueB), true); + + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotA); + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotB); + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotC); +} + +static void TestSnapshotEntryDelete() +{ + KvStoreObserverUnitTest *observer = nullptr; + DBStatus status; + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + + g_kvDelegatePtr->Put(key, value); + KvStoreSnapshotDelegate *snapshotA = nullptr; + auto snapshotDelegateCallbackA = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotA)); + g_kvDelegatePtr->GetKvStoreSnapshot(observer, snapshotDelegateCallbackA); + ASSERT_NE(snapshotA, nullptr); + snapshotA->Get(key, g_valueCallback); + EXPECT_EQ(g_valueStatus, OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(g_value, value), true); + + /** + * @tc.steps: step9. Delete the keyA data through + * the Delete interface of the delegate. + */ + g_kvDelegatePtr->Delete(key); + KvStoreSnapshotDelegate *snapshotB = nullptr; + auto snapshotDelegateCallbackB = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotB)); + + /** + * @tc.steps:step10 Obtain the snapshot object snapshotB through the GetKvStoreSnapshot interface of the delegate. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(observer, snapshotDelegateCallbackB); + ASSERT_NE(snapshotB, nullptr); + + /** + * @tc.steps: step11. Obtain the value of keyA through the Get interface of snapshotB. + * @tc.expected: step11. Return NOT_FOUND. + */ + snapshotB->Get(key, g_valueCallback); + EXPECT_EQ(g_valueStatus, NOT_FOUND); + + /** + * @tc.steps: step12. Obtain the value of keyA through the Get interface of snapshotA. + * @tc.expected: step12. Return OK, the value of keyA is valueB. + */ + snapshotA->Get(key, g_valueCallback); + EXPECT_EQ(g_valueStatus, OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(g_value, value), true); + + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotA); + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotB); +} + +/** + * @tc.name: get_snapshot_entry_001 + * @tc.desc: Obtain data from the obtained snapshot object and test the impact of the write + * database on the snapshot obtained after the snapshot is obtained. + * @tc.type: FUNC + * @tc.require: AR000BVRNH AR000CQDTJ + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, GetSnapshotEntry001, TestSize.Level1) +{ + TestSnapshotEntryPut(); + TestSnapshotEntryDelete(); +} + +/** + * @tc.name: get_snapshot_entry_002 + * @tc.desc: Read the data of the invalid key from the obtained snapshot object. + * @tc.type: FUNC + * @tc.require: AR000BVRNH AR000CQDTJ + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, GetSnapshotEntry002, TestSize.Level1) +{ + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key, MAX_KEY_SIZE); // max key size. + DistributedDBToolsUnitTest::GetRandomKeyValue(value, MAX_VAL_SIZE); // max valueSize; + + /** + * @tc.steps: step1.Insert [keyA, valueA] data through the Put interface of the delegate. + */ + g_kvDelegatePtr->Put(key, value); + KvStoreSnapshotDelegate *snapshot = nullptr; + KvStoreObserverUnitTest *observer = nullptr; + DBStatus status; + auto snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshot)); + + /** + * @tc.steps: step2. Obtain the snapshot object snapshotA through + * the GetKvStoreSnapshot interface of the delegate. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(observer, snapshotDelegateCallback); + + snapshot->Get(key, g_valueCallback); + ASSERT_EQ(g_valueStatus, OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(g_value, value), true); + + /** + * @tc.steps: step3. Obtain the empty key data through the Get interface of the snapshotA. + * @tc.expected: step3. Return ERROR. + */ + Key keyEmpty; + snapshot->Get(keyEmpty, g_valueCallback); + ASSERT_EQ(g_valueStatus, INVALID_ARGS); + + /** + * @tc.steps: step4. Obtain the data whose key size exceeds 1024 through the Get interface of the snapshotA. + * @tc.expected: step4. Return ERROR. + */ + Key keyMax; + DistributedDBToolsUnitTest::GetRandomKeyValue(keyMax, INVALID_KEY_SIZE); // max add one + snapshot->Get(keyMax, g_valueCallback); + ASSERT_EQ(g_valueStatus, INVALID_ARGS); + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshot); +} + +static void SnapshotTestPreEntriesPutInner(KvStoreSnapshotDelegate *snapshotA, KvStoreSnapshotDelegate *&snapshotB) +{ + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entryA.key); + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entryA.value); + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entryB.value); + g_entryB.key = g_entryA.key; + + g_entryB.key.push_back(std::rand() % 0xFF); // push back random one. + g_entryC = g_entryB; + uint8_t tmp = (g_entryC.key[0] == 0xFF) ? 0 : 0xFF; + g_entryC.key.insert(g_entryC.key.begin(), tmp); + + /** + * @tc.steps: step2. Obtain the data whose keyPrefix is empty through + * the GetEntries interface of the snapshotA. + * @tc.expected: step2. Return NOT_FOUND. + */ + snapshotA->GetEntries(g_entryA.key, g_entryVectorCallback); + EXPECT_EQ(g_entryVectorStatus, NOT_FOUND); + + /** + * @tc.steps: step3. Obtain the data whose keyPrefix is empty through + * the GetEntries interface of the snapshotA. + * @tc.expected: step3. Return NOT_FOUND. + */ + g_kvDelegatePtr->Put(g_entryA.key, g_entryA.value); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + g_kvDelegatePtr->Put(g_entryB.key, g_entryB.value); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + g_kvDelegatePtr->Put(g_entryC.key, g_entryC.value); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + DBStatus status; + auto snapshotDelegateCallbackB = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotB)); + /** + * @tc.steps: step5. Obtain the snapshot object snapshotB + * through the GetKvStoreSnapshot interface of the delegate. + * Obtain the data whose keyPrefix is empty through the GetEntries interface of the snapshotB. + * @tc.expected: step5. Return a non-empty snapshot. GetEntries Obtain data [keyA, valueA], [keyB, valueB]. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, snapshotDelegateCallbackB); + ASSERT_NE(snapshotB, nullptr); +} + +static void SnapshotTestPreEntriesPut() +{ + DBStatus status; + KvStoreSnapshotDelegate *snapshotA = nullptr; + auto snapshotDelegateCallbackA = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotA)); + + /** + * @tc.steps: step1. Obtain the snapshot object snapshotA through + * the GetKvStoreSnapshot interface of the delegate. + * @tc.expected: step1. Returns a non-empty snapsho. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, snapshotDelegateCallbackA); + ASSERT_NE(snapshotA, nullptr); + + KvStoreSnapshotDelegate *snapshotB = nullptr; + SnapshotTestPreEntriesPutInner(snapshotA, snapshotB); + + snapshotA->GetEntries(g_entryA.key, g_entryVectorCallback); + EXPECT_EQ(g_entryVectorStatus, NOT_FOUND); + snapshotB->GetEntries(g_entryA.key, g_entryVectorCallback); + EXPECT_EQ(g_matchSize, 2UL); + + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryA, g_entriesVector), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryB, g_entriesVector), true); + + g_entryD = g_entryA; + g_entryD.value.push_back(std::rand() % 0xFF); // random one byte. + + /** + * @tc.steps: step6. Insert [keyA, valueC] data through the Put interface of the delegate. + */ + g_kvDelegatePtr->Put(g_entryD.key, g_entryD.value); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + KvStoreSnapshotDelegate *snapshotC = nullptr; + auto snapshotDelegateCallbackC = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotC)); + + /** + * @tc.steps: step7. Obtain the snapshot object snapshotC + * through the GetKvStoreSnapshot interface of the delegate. Obtain the data whose + * keyPrefix is empty through the GetEntries interface of the snapshotC. + * @tc.expected: step5. Return a non-empty snapshot. GetEntries Obtain data [keyA, valueC], [keyB, valueB]. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, snapshotDelegateCallbackC); + ASSERT_NE(snapshotC, nullptr); + + /** + * @tc.steps: step8. Obtain the data whose keyPrefix is empty + * through the GetEntries interface of the snapshotB. + * @tc.expected: step8. Return OK, GetEntries obtains data [keyA, valueA], [keyB, valueB].. + */ + snapshotB->GetEntries(g_entryA.key, g_entryVectorCallback); + EXPECT_EQ(g_entryVectorStatus, OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryA, g_entriesVector), true); + snapshotC->GetEntries(g_entryA.key, g_entryVectorCallback); + EXPECT_EQ(g_entryVectorStatus, OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryD, g_entriesVector), true); + + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotA); + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotB); + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotC); +} + +static void SnapshotTestPreEntriesDelete() +{ + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entryA.key); + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entryA.value); + g_entryB.key = g_entryA.key; + g_entryB.key.push_back(std::rand() % 0xFF); + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entryB.value); + + g_kvDelegatePtr->Put(g_entryA.key, g_entryA.value); + g_kvDelegatePtr->Put(g_entryB.key, g_entryB.value); + + DBStatus status; + KvStoreSnapshotDelegate *snapshotA = nullptr; + auto snapshotDelegateCallbackA = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotA)); + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, snapshotDelegateCallbackA); + ASSERT_NE(snapshotA, nullptr); + + snapshotA->GetEntries(g_entryA.key, g_entryVectorCallback); + EXPECT_EQ(g_entryVectorStatus, OK); + EXPECT_EQ(g_matchSize, 2UL); // entryA and entryB + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryA, g_entriesVector), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryB, g_entriesVector), true); + + /** + * @tc.steps: step9. Delete the keyA data through the Delete interface of the delegate. + */ + g_kvDelegatePtr->Delete(g_entryA.key); + + KvStoreSnapshotDelegate *snapshotB = nullptr; + auto snapshotDelegateCallbackB = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotB)); + + /** + * @tc.steps: step10. Obtain the snapshot object snapshotB + * through the GetKvStoreSnapshot interface of the delegate. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, snapshotDelegateCallbackB); + ASSERT_NE(snapshotB, nullptr); + + /** + * @tc.steps: step11\12. Obtain the value of keyA through the Get interface of snapshotA\B. + */ + snapshotA->GetEntries(g_entryA.key, g_entryVectorCallback); + EXPECT_EQ(g_entryVectorStatus, OK); + EXPECT_EQ(g_matchSize, 2UL); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryA, g_entriesVector), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryB, g_entriesVector), true); + + snapshotB->GetEntries(g_entryA.key, g_entryVectorCallback); + EXPECT_EQ(g_matchSize, 1UL); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryA, g_entriesVector), false); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryB, g_entriesVector), true); + + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotA); + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotB); +} + +/** + * @tc.name: GetSnapshotEntries001 + * @tc.desc: To test the function of obtaining full data when keyPrefix is set to null. + * @tc.type: FUNC + * @tc.require: AR000BVRNI AR000CQDTK + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, GetSnapshotEntries001, TestSize.Level1) +{ + std::srand(std::time(nullptr)); + SnapshotTestPreEntriesPut(); + SnapshotTestPreEntriesDelete(); +} + +static void SnapshotTestEmptyPreEntriesPut() +{ + DBStatus status; + Key emptyKey; + KvStoreSnapshotDelegate *snapshotA = nullptr; + auto snapshotDelegateCallbackA = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotA)); + + /** + * @tc.steps: step1.Obtain the snapshot object snapshotA + * through the GetKvStoreSnapshot interface of the delegate. + * @tc.expected: step1. Returns a non-empty snapshot. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, snapshotDelegateCallbackA); + ASSERT_NE(snapshotA, nullptr); + + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entryA.key); + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entryA.value); + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entryB.key, g_entryA.key.size() + 1); // more one + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entryB.value); + + g_kvDelegatePtr->Put(g_entryA.key, g_entryA.value); + g_kvDelegatePtr->Put(g_entryB.key, g_entryB.value); + + /** + * @tc.steps: step2. Obtain the data whose keyPrefix is AB from the GetEntries interface of the snapshotA. + * @tc.expected: step2. Return NOT_FOUND. + */ + snapshotA->GetEntries(emptyKey, g_entryVectorCallback); + EXPECT_EQ(g_entryVectorStatus, NOT_FOUND); + + KvStoreSnapshotDelegate *snapshotB = nullptr; + auto snapshotDelegateCallbackB = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotB)); + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, snapshotDelegateCallbackB); + ASSERT_NE(snapshotB, nullptr); + snapshotA->GetEntries(emptyKey, g_entryVectorCallback); + EXPECT_EQ(g_entryVectorStatus, NOT_FOUND); + snapshotB->GetEntries(emptyKey, g_entryVectorCallback); + EXPECT_EQ(g_matchSize, 2UL); + + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryA, g_entriesVector), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryB, g_entriesVector), true); + + g_entryC = g_entryA; + g_entryC.value.push_back(std::rand() % 0xFF); // random one byte. + + /** + * @tc.steps: step3.Insert the data of ["AB", valueA], ["AE", valueB], ["ABC", valueC], + * and ["CAB", valueD] through the Put interface of the delegate. + */ + g_kvDelegatePtr->Put(g_entryC.key, g_entryC.value); + KvStoreSnapshotDelegate *snapshotC = nullptr; + auto snapshotDelegateCallbackC = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotC)); + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, snapshotDelegateCallbackC); + ASSERT_NE(snapshotC, nullptr); + + /** + * @tc.steps: step5\6\7\8. Obtain the snapshot object snapshot + * through the GetKvStoreSnapshot interface of the delegate. + * Obtain the data whose keyPrefix is "AB" through the GetEntries interface of the snapshot. + * @tc.expected: step5\6\7\8. Get Returns a non-empty snapshot and data. + */ + snapshotB->GetEntries(emptyKey, g_entryVectorCallback); + EXPECT_EQ(g_entryVectorStatus, OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryA, g_entriesVector), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryB, g_entriesVector), true); + snapshotC->GetEntries(emptyKey, g_entryVectorCallback); + EXPECT_EQ(g_entryVectorStatus, OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryB, g_entriesVector), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryC, g_entriesVector), true); + + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotA); + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotB); + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotC); +} + +static void SnapshotTestEmptyPreEntriesDelete() +{ + DBStatus status; + Key emptyKey; + KvStoreSnapshotDelegate *snapshotA = nullptr; + auto snapshotDelegateCallbackA = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotA)); + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, snapshotDelegateCallbackA); + + ASSERT_NE(snapshotA, nullptr); + + /** + * @tc.steps: step9. Delete the "AB" data through the Delete interface of the delegate. + */ + g_kvDelegatePtr->Delete(g_entryC.key); + + /** + * @tc.steps: step11. Obtain the data whose keyPrefix is "AB" through the GetEntries interface of the snapshot. + * @tc.expected: step11. Return OK.get [ABC,valueC]. + */ + snapshotA->GetEntries(emptyKey, g_entryVectorCallback); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryC, g_entriesVector), true); + + KvStoreSnapshotDelegate *snapshotB = nullptr; + auto snapshotDelegateCallbackB = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshotB)); + + /** + * @tc.steps: step10.Obtain the snapshot object snapshot through the GetKvStoreSnapshot interface of the delegate. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, snapshotDelegateCallbackB); + ASSERT_NE(snapshotB, nullptr); + + /** + * @tc.steps: step12. Obtain the data whose keyPrefix is "AB" through the GetEntries interface of the snapshot. + * @tc.expected: step12. Return OK. + */ + snapshotB->GetEntries(emptyKey, g_entryVectorCallback); + EXPECT_EQ(g_entryVectorStatus, OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsEntryExist(g_entryC, g_entriesVector), false); + + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotA); + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotB); +} + +/** + * @tc.name: GetSnapshotEntries002 + * @tc.desc: To test the function of obtaining the prefix data when keyPrefix is set to a non-empty value. + * @tc.type: FUNC + * @tc.require: AR000BVRNI AR000CQDTK + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, GetSnapshotEntries002, TestSize.Level1) +{ + std::srand(std::time(nullptr)); + SnapshotTestEmptyPreEntriesPut(); + SnapshotTestEmptyPreEntriesDelete(); +} + +/** + * @tc.name: GetSnapshotEntries003 + * @tc.desc: To test whether data can be obtained when keyPrefix is set to an ultra-long key. + * @tc.type: FUNC + * @tc.require: AR000BVRNI AR000CQDTK + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, GetSnapshotEntries003, TestSize.Level1) +{ + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key, MAX_KEY_SIZE); // max key size. + DistributedDBToolsUnitTest::GetRandomKeyValue(value, MAX_VAL_SIZE); // max valueSize; + + g_kvDelegatePtr->Put(key, value); + KvStoreSnapshotDelegate *snapshot = nullptr; + KvStoreObserverUnitTest *observer = nullptr; + DBStatus status; + auto snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshot)); + + /** + * @tc.steps: step1. Obtain the snapshot object snapshot through the GetKvStoreSnapshot interface of the delegate. + * @tc.expected: step1. Returns a non-empty snapshot. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(observer, snapshotDelegateCallback); + + snapshot->GetEntries(key, g_entryVectorCallback); + ASSERT_EQ(g_entryVectorStatus, OK); + EXPECT_EQ(g_matchSize, 1UL); + Entry entry = {key, value}; + DistributedDBToolsUnitTest::IsEntryExist(entry, g_entriesVector); + + /** + * @tc.steps: step2.Obtain the data of the key whose keyPrefix exceeds 1024 bytes + * through the GetEntries interface of the snapshotA. + * @tc.expected: step2. Return ERROR. + */ + Key keyMax; + DistributedDBToolsUnitTest::GetRandomKeyValue(keyMax, INVALID_KEY_SIZE); // max add one + snapshot->GetEntries(keyMax, g_entryVectorCallback); + ASSERT_EQ(g_entryVectorStatus, INVALID_ARGS); + g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshot); +} + +/** + * @tc.name: TestTransactionException001 + * @tc.desc: An exception occurred while the test transaction was executing. + * @tc.type: FUNC + * @tc.require: AR000C0F0F AR000CQDTN or SR000BVRNJ + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationSyncDBTest, TransactionException001, TestSize.Level1) +{ + // pre-set: create a db for test. + vector entries, entries1; + + /** + * @tc.steps: step1. Start the transaction, insert the data in bulk, end the transaction, + * insert the data of the data of the key1, the value1, the data of the key2, the value2, the transaction. + */ + entries.push_back(ENTRY_1); + entries.push_back(ENTRY_2); + + EXPECT_EQ(g_kvDelegatePtr->StartTransaction(), OK); + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + EXPECT_TRUE(g_kvDelegatePtr->Commit() == OK); + + /** + * @tc.steps: step2. Start the transaction, insert the data in bulk, end the transaction, + * insert the data of the data of the key3, the value3, the data of the key4, the value4, the transaction. + */ + entries1.push_back(ENTRY_3); + entries1.push_back(ENTRY_4); + + EXPECT_EQ(g_kvDelegatePtr->StartTransaction(), OK); + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries1) == OK); + EXPECT_EQ(g_kvDelegatePtr->Commit(), OK); + + /** + * @tc.steps: step2. Close database and Delegate, + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + + /** + * @tc.steps: step4. Simulated scenes where the log library is not written to complete (power-down): + * read the commit-log database through the sqlite3 interface, delete the latest head node, + * and set the parent node of the header node as the head node. + */ + IKvDBFactory *factory = IKvDBFactory::GetCurrent(); + ASSERT_NE(factory, nullptr); + if (factory == nullptr) { + LOGE("failed to get DefaultFactory!"); + return; + } + int result = E_OK; + IKvDBCommitStorage *commitStorage = factory->CreateMultiVerCommitStorage(result); + ASSERT_EQ(result, E_OK); + ASSERT_NE(commitStorage, nullptr); + + std::string origIdentifier = USER_ID + "-" + APP_ID + "-" + STORE_ID_SYNC; + std::string hashIdentifier = DBCommon::TransferHashString(origIdentifier); + std::string hexDir = DBCommon::TransferStringToHex(hashIdentifier); + IKvDBCommitStorage::Property property = {g_testDir, hexDir, false}; + + ASSERT_EQ(commitStorage->Open(property), E_OK); + int errCode = E_OK; + result = commitStorage->RemoveCommit(commitStorage->GetHeader(errCode)); + ASSERT_EQ(result, E_OK); + delete commitStorage; + commitStorage = nullptr; + + /** + * @tc.steps: step5. Get delegate with GetKvStore, create snapshot, get data from key3 and key4. + * @tc.expected: step5. Data on key3 and key4 could not be obtained. + */ + CipherPassword passwd; + KvStoreDelegate::Option option = {true, false, false, CipherType::DEFAULT, passwd}; + g_mgr.GetKvStore(STORE_ID, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, g_snapshotDelegateCallback); + EXPECT_TRUE(g_snapshotDelegateStatus == OK); + ASSERT_TRUE(g_snapshotDelegatePtr != nullptr); + + g_snapshotDelegatePtr->Get(KEY_3, g_valueCallback); + EXPECT_EQ(g_valueStatus, NOT_FOUND); + + g_valueStatus = INVALID_ARGS; + g_snapshotDelegatePtr->Get(KEY_4, g_valueCallback); + EXPECT_EQ(g_valueStatus, NOT_FOUND); +} diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_data_operation_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_data_operation_test.cpp new file mode 100644 index 00000000..99f1d4db --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_data_operation_test.cpp @@ -0,0 +1,2006 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + const bool LOCAL_ONLY = true; + const string STORE_ID = STORE_ID_LOCAL; + + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatusForQuery = INVALID_ARGS; + KvStoreDelegate *g_kvDelegatePtrForQuery = nullptr; + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallbackForQuery = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatusForQuery), std::ref(g_kvDelegatePtrForQuery)); + KvStoreNbDelegate *g_kvNbDelegatePtrForQuery = nullptr; + auto g_kvNbDelegateCallbackForQuery = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatusForQuery), std::ref(g_kvNbDelegatePtrForQuery)); + + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + + // define the g_snapshotDelegateCallback, used to get some information when open a kv snapshot. + DBStatus g_snapshotDelegateStatus = INVALID_ARGS; + KvStoreSnapshotDelegate *g_snapshotDelegatePtr = nullptr; + // the type of g_snapshotDelegateCallback is function + auto g_snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_snapshotDelegateStatus), std::ref(g_snapshotDelegatePtr)); + + // define the g_valueCallback, used to query a value object data from the kvdb. + DBStatus g_valueStatus = INVALID_ARGS; + Value g_value; + // the type of g_valueCallback is function + auto g_valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(g_valueStatus), std::ref(g_value)); + + // define the g_entryVectorCallback, used to query a vector object data from the kvdb. + DBStatus g_entryVectorStatus = INVALID_ARGS; + unsigned long g_matchSize = 0; + std::vector g_entriesVector; + // the type of g_entryVectorCallback is function)> + auto g_entryVectorCallback = bind(&DistributedDBToolsUnitTest::EntryVectorCallback, placeholders::_1, + placeholders::_2, std::ref(g_entryVectorStatus), std::ref(g_matchSize), std::ref(g_entriesVector)); + + const string SCHEMA_STRING = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":\"BOOL\"," + "\"field_name3\":\"INTEGER, NOT NULL\"," + "\"field_name4\":\"LONG, DEFAULT 100\"," + "\"field_name5\":\"DOUBLE, NOT NULL, DEFAULT 3.14\"," + "\"field_name6\":\"STRING, NOT NULL, DEFAULT '3.1415'\"," + "\"field_name7\":\"LONG, DEFAULT 100\"," + "\"field_name8\":\"LONG, DEFAULT 100\"," + "\"field_name9\":\"LONG, DEFAULT 100\"," + "\"field_name10\":\"LONG, DEFAULT 100\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\", \"$.field_name2\"]}"; + + void GetSnapshotUnitTest() + { + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, g_snapshotDelegateCallback); + EXPECT_TRUE(g_snapshotDelegateStatus == OK); + ASSERT_TRUE(g_snapshotDelegatePtr != nullptr); + } +#ifndef OMIT_JSON + const int CIRCLE_COUNT = 3; + void PutValidEntries1() + { + std::string validData = "{\"field_name1\":true,"; + validData += "\"field_name2\":true,"; + validData += "\"field_name3\":10,"; + validData += "\"field_name4\":20,"; + validData += "\"field_name5\":3.14,"; + validData += "\"field_name6\":\"3.1415\","; + validData += "\"field_name7\":100,"; + validData += "\"field_name8\":100,"; + validData += "\"field_name9\":100,"; + validData += "\"field_name10\":100}"; + + Value value(validData.begin(), validData.end()); + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(key, value), OK); + } + void PutValidEntries2() + { + std::string validData = "{\"field_name1\":true," + "\"field_name2\":false," + "\"field_name3\":10," + "\"field_name4\":20," + "\"field_name5\":3.14," + "\"field_name6\":\"3.1415\"," + "\"field_name7\":100," + "\"field_name8\":100," + "\"field_name9\":100," + "\"field_name10\":100}"; + Value value(validData.begin(), validData.end()); + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(key, value), OK); + } + void PutValidEntries3() + { + std::string validData = "{\"field_name1\":true," + "\"field_name2\":true," + "\"field_name3\":20," + "\"field_name4\":20," + "\"field_name5\":3.14," + "\"field_name6\":\"3.1415\"," + "\"field_name7\":100," + "\"field_name8\":100," + "\"field_name9\":100," + "\"field_name10\":100}"; + Value value(validData.begin(), validData.end()); + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(key, value), OK); + } + void PutValidEntries4() + { + std::string validData = "{\"field_name1\":true," + "\"field_name2\":true," + "\"field_name3\":20," + "\"field_name4\":2," + "\"field_name5\":3.14," + "\"field_name6\":\"3.1415\"," + "\"field_name7\":100," + "\"field_name8\":100," + "\"field_name9\":100," + "\"field_name10\":100}"; + Value value(validData.begin(), validData.end()); + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(key, value), OK); + } + void PutValidEntries5() + { + std::string validData = "{\"field_name1\":true," + "\"field_name2\":true," + "\"field_name3\":20," + "\"field_name4\":2," + "\"field_name5\":3.15," + "\"field_name6\":\"3.1415\"," + "\"field_name7\":1001," + "\"field_name8\":100," + "\"field_name9\":100," + "\"field_name10\":100}"; + Value value(validData.begin(), validData.end()); + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(key, value), OK); + } + void PutValidEntries6() + { + std::string validData = "{\"field_name1\":true," + "\"field_name2\":true," + "\"field_name3\":20," + "\"field_name4\":2," + "\"field_name5\":3.15," + "\"field_name6\":\"4.141\"," + "\"field_name7\":1002," + "\"field_name8\":100," + "\"field_name9\":100," + "\"field_name10\":100}"; + Value value(validData.begin(), validData.end()); + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(key, value), OK); + } + void PutValidEntries7() + { + std::string validData = "{\"field_name1\":true," + "\"field_name2\":true," + "\"field_name3\":20," + "\"field_name4\":2," + "\"field_name5\":3.15," + "\"field_name6\":\"4.141\"," + "\"field_name7\":100," + "\"field_name8\":200," + "\"field_name9\":100," + "\"field_name10\":100}"; + Value value(validData.begin(), validData.end()); + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(key, value), OK); + } + void PutValidEntries8() + { + std::string validData = "{\"field_name1\":true," + "\"field_name2\":true," + "\"field_name3\":20," + "\"field_name4\":2," + "\"field_name5\":3.15," + "\"field_name6\":\"4.141\"," + "\"field_name7\":1009," + "\"field_name8\":200," + "\"field_name9\":100," + "\"field_name10\":500}"; + Value value(validData.begin(), validData.end()); + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(key, value), OK); + } + + void PutValidEntries() + { + for (int i = 0; i < CIRCLE_COUNT; i++) { + PutValidEntries1(); + PutValidEntries2(); + PutValidEntries3(); + PutValidEntries4(); + PutValidEntries5(); + PutValidEntries6(); + PutValidEntries7(); + PutValidEntries8(); + } + } + + const std::string SCHEMA_DEFINE2 = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"INTEGER\"," + "\"field_name2\":\"DOUBLE\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\", \"$.field_name2\"]}"; + + void PresetDataForPreifxAndOrderBy001() + { + std::string validData = "{\"field_name1\":1, \"field_name2\":1}"; + Value value(validData.begin(), validData.end()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(KEY_1, value), OK); + validData = "{\"field_name1\":1, \"field_name2\":2}"; + Value value2(validData.begin(), validData.end()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(KEY_2, value2), OK); + validData = "{\"field_name1\":2, \"field_name2\":3}"; + Value value3(validData.begin(), validData.end()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(KEY_3, value3), OK); + validData = "{\"field_name1\":2, \"field_name2\":4}"; + Value value4(validData.begin(), validData.end()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(KEY_4, value4), OK); + validData = "{\"field_name1\":3, \"field_name2\":5}"; + Value value5(validData.begin(), validData.end()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(KEY_5, value5), OK); + } +#endif + void TestSnapshotCreateAndRelease() + { + DBStatus status; + KvStoreSnapshotDelegate *snapshot = nullptr; + KvStoreObserver *observer = nullptr; + auto snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshot)); + + /** + * @tc.steps: step1. Obtain the snapshot object snapshot through + * the GetKvStoreSnapshot interface of the delegate. + * @tc.expected: step1. Returns a non-empty snapshot. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(observer, snapshotDelegateCallback); + + EXPECT_TRUE(status == OK); + EXPECT_NE(snapshot, nullptr); + + /** + * @tc.steps: step2. Release the obtained snapshot through + * the ReleaseKvStoreSnapshot interface of the delegate. + * @tc.expected: step2. Release successfully. + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshot), OK); + } +#ifndef OMIT_JSON + vector PreDataForQueryByPreFixKey() + { + vector res; + for (int i = 0; i < 5; i++) { // rand num 5 for test + Key key = DistributedDBToolsUnitTest::GetRandPrefixKey({'a', 'b'}, 1024); // rand num 1024 for test + std::string validData = "{\"field_name1\":null, \"field_name2\":" + std::to_string(rand()) + "}"; + Value value(validData.begin(), validData.end()); + res.push_back({key, value}); + } + + for (int i = 0; i < 5; i++) { // rand num 5 for test + Key key = DistributedDBToolsUnitTest::GetRandPrefixKey({'a', 'c'}, 1024); // rand num 1024 for test + std::string validData = "{\"field_name1\":null, \"field_name2\":" + std::to_string(rand()) + "}"; + Value value(validData.begin(), validData.end()); + res.push_back({key, value}); + } + return res; + } + + static void PreDataForGroupTest() + { + std::string validData = "{\"field_name1\":1, \"field_name2\":1}"; + Value value(validData.begin(), validData.end()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(KEY_1, value), OK); + validData = "{\"field_name1\":2, \"field_name2\":2}"; + Value value2(validData.begin(), validData.end()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(KEY_2, value2), OK); + validData = "{\"field_name1\":3, \"field_name2\":3}"; + Value value3(validData.begin(), validData.end()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(KEY_3, value3), OK); + validData = "{\"field_name1\":4, \"field_name2\":4}"; + Value value4(validData.begin(), validData.end()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(KEY_4, value4), OK); + validData = "{\"field_name1\":5, \"field_name2\":5}"; + Value value5(validData.begin(), validData.end()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(KEY_5, value5), OK); + } +#endif +} + +class DistributedDBInterfacesDataOperationTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesDataOperationTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesDataOperationTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesDataOperationTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + // init values. + g_valueStatus = INVALID_ARGS; + g_value.clear(); + g_entryVectorStatus = INVALID_ARGS; + g_matchSize = 0; + + /* + * Here, we create STORE_ID.db before test, + * and it will be closed in TearDown(). + */ + CipherPassword passwd; + KvStoreDelegate::Option option = {true, LOCAL_ONLY, false, CipherType::DEFAULT, passwd}; + g_mgr.GetKvStore(STORE_ID, option, g_kvDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); +} + +void DistributedDBInterfacesDataOperationTest::TearDown(void) +{ + if (g_kvDelegatePtr != nullptr && g_snapshotDelegatePtr != nullptr) { + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; + } + + if (g_kvDelegatePtr != nullptr) { + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + } +} + +/** + * @tc.name: Put001 + * @tc.desc: Put a data(non-empty key, non-empty value) into an exist distributed db + * @tc.type: FUNC + * @tc.require: AR000CQDTM AR000CQS3Q + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, Put001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the data(non-empty key and non-empty value) into the database. + * @tc.expected: step1. Put returns OK. + */ + Key keyTmp; + keyTmp.push_back(1); + Value valueTmp; + valueTmp.push_back('7'); + EXPECT_TRUE(g_kvDelegatePtr->Put(keyTmp, valueTmp) == OK); + + /** + * @tc.steps: step2. Get the value according the key through the snapshot. + * @tc.expected: step2. Get returns OK. + */ + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == OK); +} + +/** + * @tc.name: Put002 + * @tc.desc: Put a data(empty key) into an exist distributed db + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, Put002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the data(empty key) into the database. + * @tc.expected: step1. Put returns INVALID_ARGS. + */ + Key keyTmp; + Value valueTmp; + valueTmp.push_back('7'); + EXPECT_TRUE(g_kvDelegatePtr->Put(keyTmp, valueTmp) == INVALID_ARGS); +} + +/** + * @tc.name: Put003 + * @tc.desc: Put a data(non-empty key, empty value) into an exist distributed db + * @tc.type: FUNC + * @tc.require: AR000CQDTM AR000CQS3Q + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, Put003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the data(empty value) into the database. + * @tc.expected: step1. Put returns OK. + */ + Key keyTmp; + keyTmp.push_back(1); + Value valueTmp; + + EXPECT_TRUE(g_kvDelegatePtr->Put(keyTmp, valueTmp) == OK); +} + +/** + * @tc.name: Put004 + * @tc.desc: Put data into the local database + * @tc.type: FUNC + * @tc.require: AR000CQDVD AR000CQS3Q + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, Put004, TestSize.Level1) +{ + /** + * @tc.steps: step1. clear the database. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + + Key keyTmp; + keyTmp.push_back(1); + + Value valueTmp; + valueTmp.push_back('7'); + + /** + * @tc.steps: step2. Put one data into the database. + * @tc.expected: step2. Put returns OK. + */ + Value valueTest; + valueTest.push_back('9'); + EXPECT_TRUE(g_kvDelegatePtr->Put(keyTmp, valueTmp) == OK); + + /** + * @tc.steps: step3. Get the data from the database. + * @tc.expected: step3. Get returns OK and the read value is equal to the value put before. + */ + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == OK); + EXPECT_TRUE(g_value.size() > 0); + if (g_value.size() > 0) { + EXPECT_TRUE(g_value.front() == '7'); + } + + /** + * @tc.steps: step4. Change the value, and Put the data into the database. + * @tc.expected: step4. Put returns OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(keyTmp, valueTest) == OK); + + if (g_kvDelegatePtr != nullptr && g_snapshotDelegatePtr != nullptr) { + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; + } + GetSnapshotUnitTest(); + /** + * @tc.steps: step5. Get the data from the database. + * @tc.expected: step5. Get returns OK and the read value is equal to the new put value. + */ + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == OK); + EXPECT_TRUE(g_value.size() > 0); + if (g_value.size() > 0) { + EXPECT_TRUE(g_value.front() == '9'); + } +} + +/** + * @tc.name: Clear001 + * @tc.desc: Clear the data from an exist distributed db + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, Clear001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the valid data into the database. + */ + Key keyTmp; + DistributedDBToolsUnitTest::GetRandomKeyValue(keyTmp); + Value valueTmp; + DistributedDBToolsUnitTest::GetRandomKeyValue(valueTmp); + EXPECT_TRUE(g_kvDelegatePtr->Put(keyTmp, valueTmp) == OK); + + /** + * @tc.steps: step2. Clear the database. + * @tc.expected: step2. Clear returns OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + + /** + * @tc.steps: step3. Get the data from the database according the inserted key before clear. + * @tc.expected: step3. Get returns NOT_FOUND. + */ + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_EQ(g_valueStatus, NOT_FOUND); +} + +/** + * @tc.name: PutBatch001 + * @tc.desc: Putbatch data into the local database + * @tc.type: FUNC + * @tc.require: AR000BVDFE AR000CQDVC + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, PutBatch001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the prepared data. + */ + vector entries; + for (int i = 1; i < 10; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + } + /** + * @tc.steps: step2. PutBatch the prepared data. + * @tc.expected: step2. PutBatch returns OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + + /** + * @tc.steps: step3. Get the data from the database. + * @tc.expected: step3. Get returns OK and the get value is equal to the inserted value before. + */ + GetSnapshotUnitTest(); + for (int i = 1; i < 10; i++) { + Key keyTmp; + keyTmp.push_back(i); + + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == OK); + EXPECT_TRUE(g_value.size() > 0); + if (g_value.size() > 0) { + EXPECT_TRUE(g_value.front() == '8'); + } + } +} + +/** + * @tc.name: PutBatch002 + * @tc.desc: PutBatch modified data into the local database + * @tc.type: FUNC + * @tc.require: AR000BVDFE AR000CQDVC + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, PutBatch002, TestSize.Level1) +{ + /** + * @tc.steps: step1. prepare the batch data. + */ + vector entries; + for (int i = 1; i < 10; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('2'); + entries.push_back(entry); + } + /** + * @tc.steps: step2. PutBatch the prepared batch data. + * @tc.expected: step2. PutBatch returns OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + /** + * @tc.steps: step3. Get data from the database according the inserted keys. + * @tc.expected: step3. Get returns OK and the read value is equal to the inserted value. + */ + GetSnapshotUnitTest(); + for (int i = 1; i < 10; i++) { + Key keyTmp; + keyTmp.push_back(i); + + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == OK); + EXPECT_TRUE(g_value.size() > 0); + if (g_value.size() > 0) { + EXPECT_TRUE(g_value.front() == '2'); + } + } +} + +/** + * @tc.name: Delete001 + * @tc.desc: Delete existed data from the local database + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, Delete001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the prepared data. + */ + Key keyTmp; + keyTmp.push_back(1); + Value valueTmp; + valueTmp.push_back(3); + EXPECT_EQ(g_kvDelegatePtr->Put(keyTmp, valueTmp), OK); + /** + * @tc.steps: step2. Delete the existed data from the database. + * @tc.expected: step2. Delete returns OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Delete(keyTmp), OK); + /** + * @tc.steps: step3. Get the deleted data from the database. + * @tc.expected: step3. Get returns NOT_FOUND. + */ + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == NOT_FOUND); +} + +/** + * @tc.name: Delete002 + * @tc.desc: Delete non-existed data from the local database + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, Delete002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Clear the database. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + + /** + * @tc.steps: step2. Delete the non-existed data from the database. + * @tc.expected: step2. Delete returns OK. + */ + Key keyTmp; + keyTmp.push_back(1); + EXPECT_TRUE(g_kvDelegatePtr->Delete(keyTmp) == OK); +} + +/** + * @tc.name: DeleteBatch001 + * @tc.desc: Delete the existed batch data from the local database + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, DeleteBatch001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the batch data into the database. + */ + vector entries; + for (int i = 1; i < 4; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('2'); + entries.push_back(entry); + } + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + + /** + * @tc.steps: step2. Delete the batch data from the database. + * @tc.steps: step2. DeleteBatch returns OK. + */ + vector keys; + for (int i = 1; i < 4; i++) { + Key key; + key.push_back(i); + keys.push_back(key); + } + EXPECT_TRUE(g_kvDelegatePtr->DeleteBatch(keys) == OK); + + /** + * @tc.steps: step3. Get all the data from the database. + * @tc.steps: step3. GetEntries result NOT_FOUND. + */ + Key keyTmp; + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->Get(keyTmp, g_valueCallback); + EXPECT_TRUE(g_valueStatus == INVALID_ARGS); +} + +/** + * @tc.name: DeleteBatch002 + * @tc.desc: Delete the non-existed batch data from the local database + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, DeleteBatch002, TestSize.Level1) +{ + /** + * @tc.steps: step1. clear the database. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + /** + * @tc.steps: step2. Delete the batch non-existed data from the database. + * @tc.expected: step2. DeleteBatch returns OK + */ + vector keys; + for (int i = 1; i < 10; i++) { + Key key; + key.push_back(i); + keys.push_back(key); + } + EXPECT_TRUE(g_kvDelegatePtr->DeleteBatch(keys) == OK); +} + +/** + * @tc.name: GetEntries001 + * @tc.desc: Get the batch data from the non-empty database by the prefix key. + * @tc.type: FUNC + * @tc.require: AR000C6TUQ AR000CQDV3 + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, GetEntries001, TestSize.Level1) +{ + /** + * @tc.steps: step1. insert batch data into the database. + */ + vector entries; + for (int i = 1; i <= 10; i++) { + Entry entry; + for (int j = 1; j <= i; j++) { + entry.key.push_back(j); + } + entry.value.push_back(i); + entries.push_back(entry); + } + + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + + Key keyPrefix; + for (int j = 1; j <= 5; j++) { + keyPrefix.push_back(j); + } + /** + * @tc.steps: step2. Get batch data from the database using the prefix key. + * @tc.expected: step2. GetEntries results OK and the result entries size is the match size. + */ + unsigned long matchSize = 6; + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->GetEntries(keyPrefix, g_entryVectorCallback); + ASSERT_TRUE(g_matchSize == matchSize); + EXPECT_TRUE(g_entryVectorStatus == OK); +} + +/** + * @tc.name: GetEntries002 + * @tc.desc: Get all the data(empty prefixkey) from the empty database. + * @tc.type: FUNC + * @tc.require: AR000C6TUQ AR000CQDV3 + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, GetEntries002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get all the data from the empty database. + * @tc.expected: step1. GetEntries results NOT_FOUND. + */ + Key keyPrefix; + for (int j = 1; j <= 5; j++) { + keyPrefix.push_back('a'); + } + + unsigned long matchSize = 0; + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->GetEntries(keyPrefix, g_entryVectorCallback); + ASSERT_TRUE(g_matchSize == matchSize); + EXPECT_TRUE(g_entryVectorStatus == NOT_FOUND); +} + +/** + * @tc.name: GetEntries003 + * @tc.desc: Get all the data(empty prefixkey) from the database. + * @tc.type: FUNC + * @tc.require: AR000C6TUQ AR000CQDV3 + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, GetEntries003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put batch data into the database. + */ + vector entries; + for (int i = 1; i <= 10; i++) { + Entry entry; + for (int j = 1; j <= i; j++) { + entry.key.push_back(j); + } + entry.value.push_back(i); + entries.push_back(entry); + } + + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + + /** + * @tc.steps: step2. Get all the data from the database using the empty prefix key. + * @tc.expected: step2. GetEntries results OK and the entries size is the put batch data size. + */ + Key keyPrefix; + unsigned long matchSize = 10; + GetSnapshotUnitTest(); + g_snapshotDelegatePtr->GetEntries(keyPrefix, g_entryVectorCallback); + ASSERT_TRUE(g_matchSize == matchSize); + EXPECT_TRUE(g_entryVectorStatus == OK); +} + +/** + * @tc.name: GetSnapshot001 + * @tc.desc: Get observer is empty, whether you get the snapshot. + * @tc.type: FUNC + * @tc.require: AR000BVRNF AR000CQDTI + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, GetSnapshot001, TestSize.Level1) +{ + /** + * @tc.steps: step1.Obtain the snapshot object whose observer is null + * by using the GetKvStoreSnapshot interface of the delegate. + * @tc.expected: step1. The obtained snapshot is not empty. + */ + TestSnapshotCreateAndRelease(); +} + +/** + * @tc.name: GetSnapshot002 + * @tc.desc: Get observer is not empty, whether you get the snapshot. + * @tc.type: FUNC + * @tc.require: AR000BVRNF AR000CQDTI + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, GetSnapshot002, TestSize.Level1) +{ + /** + * @tc.steps: step1.Obtain the snapshot object whose observer is null + * by using the GetKvStoreSnapshot interface of the delegate. + * @tc.expected: step1. The obtained snapshot is not empty. + */ + DBStatus status; + KvStoreSnapshotDelegate *snapshot = nullptr; + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_NE(observer, nullptr); + auto snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshot)); + g_kvDelegatePtr->GetKvStoreSnapshot(observer, snapshotDelegateCallback); + + EXPECT_TRUE(status == OK); + EXPECT_NE(snapshot, nullptr); + + /** + * @tc.steps: step2. Release the snapshot get before. + * @tc.expected: step2. ReleaseKvStoreSnapshot returns OK. + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshot), OK); + delete observer; + observer = nullptr; +} + +/** + * @tc.name: ReleaseSnapshot001 + * @tc.desc: To test the function of releasing an empty snapshot. + * @tc.type: FUNC + * @tc.require: AR000BVRNF AR000CQDTI + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, ReleaseSnapshot001, TestSize.Level1) +{ + /** + * @tc.steps: step1.Release the null pointer snapshot through + * the ReleaseKvStoreSnapshot interface of the delegate. + * @tc.expected: step1. Return ERROR. + */ + KvStoreSnapshotDelegate *snapshot = nullptr; + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshot), DB_ERROR); +} + +/** + * @tc.name: ReleaseSnapshot002 + * @tc.desc: Release the obtained snapshot object that is not empty. + * @tc.type: FUNC + * @tc.require: AR000BVRNF AR000CQDTI + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, ReleaseSnapshot002, TestSize.Level1) +{ + TestSnapshotCreateAndRelease(); +} + +/** + * @tc.name: SetConflictResolutionPolicySuccessTest001 + * @tc.desc: Verify SetConflictResolutionPolicy() return OK with valid input. + * @tc.type: FUNC + * @tc.require: AR000CQE12 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, SetConflictResolutionPolicySuccessTest001, TestSize.Level1) +{ + /** + * @tc.steps: step1. get g_kvDelegatePtr pointer + * @tc.expected: step1. g_kvDelegatePtr is not nullptr + */ + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + + /** + * @tc.steps: step2. invoke SetConflictResolutionPolicy() method by g_kvDelegatePtr + * @tc.expected: step2. SetConflictResolutionPolicy() return OK + */ + EXPECT_EQ(g_kvDelegatePtr->SetConflictResolutionPolicy(AUTO_LAST_WIN, nullptr), OK); +} + +/** + * @tc.name: SetConflictResolutionPolicyFailedTest001 + * @tc.desc: Verify SetConflictResolutionPolicy() return INVALID_ARGS with invalid input. + * @tc.type: FUNC + * @tc.require: AR000CQE12 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, SetConflictResolutionPolicyFailedTest001, TestSize.Level1) +{ + /** + * @tc.steps: step1. get g_kvDelegatePtr pointer + * @tc.expected: step1. g_kvDelegatePtr is not nullptr + */ + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + + /** + * @tc.steps: step2. invoke SetConflictResolutionPolicy() method by g_kvDelegatePtr + * @tc.expected: step2. SetConflictResolutionPolicy() return not OK + */ + EXPECT_NE(g_kvDelegatePtr->SetConflictResolutionPolicy(CUSTOMER_RESOLUTION, nullptr), OK); +} +#ifndef OMIT_JSON +/** + * @tc.name: GetEntriesWithQuery001 + * @tc.desc: check query_format. + * @tc.type: FUNC + * @tc.require: AR000DR9K7 + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, GetEntriesWithQuery001, TestSize.Level1) +{ + /** + * @tc.steps: step1. get a non-schema store, Getentries with query + * @tc.expected: step1. get store ok, Getentries return OK + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("GetEntriesWithQuery001_001", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + Query query = Query::Select(); + std::vector entries; + DBStatus ret = g_kvNbDelegatePtrForQuery->GetEntries(query, entries); + EXPECT_EQ(ret, NOT_FOUND); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("GetEntriesWithQuery001_001") == OK); + /** + * @tc.steps: step2. get a schema store, Getentries with empty query + * @tc.expected: step2. get store ok, Getentries return OK + */ + option.schema = SCHEMA_STRING; + g_mgr.GetKvStore("GetEntriesWithQuery001_002", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + ret = g_kvNbDelegatePtrForQuery->GetEntries(query, entries); + EXPECT_TRUE(ret == NOT_FOUND); + /** + * @tc.steps: step3. Getentries by query, query undefined field + * @tc.expected: step3. Getentries return INVALID_QUERY_FIELD + */ + query = query.EqualTo("$.field_name200", 10); + ret = g_kvNbDelegatePtrForQuery->GetEntries(query, entries); + EXPECT_EQ(ret, INVALID_QUERY_FIELD); + /** + * @tc.steps: step4. Getentries by query, query has invalid linker; + * @tc.expected: step4. Getentries return INVALID_QUERY_FORMAT + */ + Query invalidQuery1 = Query::Select().EqualTo("$.field_name1", true).And().Or(); + ret = g_kvNbDelegatePtrForQuery->GetEntries(invalidQuery1, entries); + EXPECT_TRUE(ret == INVALID_QUERY_FORMAT); + /** + * @tc.steps: step5. Getentries by query, query has invalid linker; + * @tc.expected: step5. Getentries return INVALID_QUERY_FORMAT + */ + Query invalidQuery2 = Query::Select().And(); + ret = g_kvNbDelegatePtrForQuery->GetEntries(invalidQuery2, entries); + EXPECT_TRUE(ret == INVALID_QUERY_FORMAT); + /** + * @tc.steps: step6. Getentries by query, query has invalid limit; + * @tc.expected: step6. Getentries return INVALID_QUERY_FORMAT + */ + Query invalidQuery3 = Query::Select().Limit(10, 0).EqualTo("$.field_name1", true); + ret = g_kvNbDelegatePtrForQuery->GetEntries(invalidQuery3, entries); + EXPECT_TRUE(ret == INVALID_QUERY_FORMAT); + /** + * @tc.steps: step7. Getentries by query, query has invalid orderby; + * @tc.expected: step7. Getentries return INVALID_QUERY_FORMAT + */ + Query invalidQuery4 = Query::Select().OrderBy("$.field_name1", true).EqualTo("field_name1", true); + ret = g_kvNbDelegatePtrForQuery->GetEntries(invalidQuery4, entries); + EXPECT_TRUE(ret == INVALID_QUERY_FORMAT); + /** + * @tc.steps: step8. Getentries by query, query has invalid orderby and limit; + * @tc.expected: step8. Getentries return INVALID_QUERY_FORMAT + */ + Query invalidQuery5 = Query::Select().Limit(10, 0).OrderBy("$.field_name1", true); + ret = g_kvNbDelegatePtrForQuery->GetEntries(invalidQuery5, entries); + EXPECT_TRUE(ret == INVALID_QUERY_FORMAT); + /** + * @tc.steps: step9. Getentries by query, query has invalid field type; + * @tc.expected: step9. Getentries return OK + */ + std::string queryType = "true"; + Query invalidQuery6 = Query::Select().EqualTo("$.field_name1", queryType); + ret = g_kvNbDelegatePtrForQuery->GetEntries(invalidQuery6, entries); + EXPECT_TRUE(ret == NOT_FOUND); + + Query invalidQuery7 = Query::Select().EqualTo("$.field_name1", queryType).IsNull("$.field_name1"); + ret = g_kvNbDelegatePtrForQuery->GetEntries(invalidQuery7, entries); + EXPECT_TRUE(ret == INVALID_QUERY_FORMAT); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("GetEntriesWithQuery001_002") == OK); +} + +/** + * @tc.name: GetEntriesWithQuery002 + * @tc.desc: GetEntries(const Query &query, std::vector &entries) interface test. + * @tc.type: FUNC + * @tc.require: AR000DR9K7 + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, GetEntriesWithQuery002, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_STRING; + g_mgr.GetKvStore("GetEntriesWithQuery002_002", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + PutValidEntries(); + /** + * @tc.steps: step1. Getentries by query, query is empty; + * @tc.expected: step1. Getentries return OK, entries size == totalSize; + */ + Query query = Query::Select(); + std::vector entries; + int ret = g_kvNbDelegatePtrForQuery->GetEntries(query, entries); + EXPECT_EQ(ret, OK); + EXPECT_EQ(entries.size(), 24ul); + /** + * @tc.steps: step2. Getentries by query, query is full-set; + * @tc.expected: step2. Getentries return OK, ; + */ + std::vector inCondition = {1, 10, 100, 200}; + Query fullQuery = Query::Select().EqualTo("$.field_name1", true).And().NotEqualTo("$.field_name2", false). + And().GreaterThanOrEqualTo("$.field_name3", 10).And().LessThan("$.field_name4", 10).And(). + Like("$.field_name6", "4%").And().In("$.field_name7", inCondition).OrderBy("$.field_name9").Limit(20); + ret = g_kvNbDelegatePtrForQuery->GetEntries(fullQuery, entries); + EXPECT_EQ(ret, OK); + EXPECT_EQ(entries.size(), 3ul); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("GetEntriesWithQuery002_002") == OK); +} + +/** + * @tc.name: GetEntriesWithQuery003 + * @tc.desc: GetEntries(const Query &query, std::vector &entries) interface test. + * @tc.type: FUNC + * @tc.require: AR000DR9K7 + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, GetEntriesWithQuery003, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_STRING; + g_mgr.GetKvStore("GetEntriesWithQuery003", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + PutValidEntries(); + /** + * @tc.steps: step1. Getentries by query, query is empty; + * @tc.expected: step1. Getentries return OK, entries size == totalSize; + */ + Query query = Query::Select(); + int count = 0; + int ret = g_kvNbDelegatePtrForQuery->GetCount(query, count); + EXPECT_TRUE(ret == OK); + EXPECT_TRUE(count == 24); + /** + * @tc.steps: step2. Getentries by query, query is full-set; + * @tc.expected: step2. Getentries return OK, ; + */ + std::vector inCondition = {1, 10, 100, 200}; + Query fullQuery = Query::Select().EqualTo("$.field_name1", true).And().NotEqualTo("$.field_name2", false). + And().GreaterThan("$.field_name3", 10).And().LessThan("$.field_name4", 10).And().Like("$.field_name6", "4%"). + And().In("$.field_name7", inCondition); + ret = g_kvNbDelegatePtrForQuery->GetCount(fullQuery, count); + EXPECT_TRUE(ret == OK); + EXPECT_TRUE(count == 3); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("GetEntriesWithQuery003") == OK); +} + +/** + * @tc.name: GetEntriesWithQuery004 + * @tc.desc: GetEntries(const Query &query, std::vector &entries) interface test. + * @tc.type: FUNC + * @tc.require: AR000DR9K7 + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, GetEntriesWithQuery004, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_STRING; + g_mgr.GetKvStore("GetEntriesWithQuery004", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + PutValidEntries(); + /** + * @tc.steps: step1. Getentries by query, query is empty; + * @tc.expected: step1. Getentries return OK, entries size == totalSize; + */ + Query query = Query::Select(); + KvStoreResultSet *resultSet = nullptr; + int ret = g_kvNbDelegatePtrForQuery->GetEntries(query, resultSet); + ASSERT_TRUE(resultSet != nullptr); + EXPECT_TRUE(ret == OK); + EXPECT_EQ(resultSet->GetCount(), 24); + EXPECT_EQ(resultSet->GetPosition(), -1); + EXPECT_TRUE(!resultSet->IsFirst()); + EXPECT_TRUE(resultSet->MoveToFirst()); + EXPECT_TRUE(resultSet->IsFirst()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet), OK); + EXPECT_TRUE(resultSet == nullptr); + /** + * @tc.steps: step2. Getentries by query, query is full-set; + * @tc.expected: step2. Getentries return OK, ; + */ + std::vector inCondition = {1, 10, 100, 200}; + Query fullQuery = Query::Select().EqualTo("$.field_name1", true).And().NotEqualTo("$.field_name2", false). + And().GreaterThan("$.field_name3", 10).And().LessThan("$.field_name4", 10).And().Like("$.field_name6", "4%"). + And().In("$.field_name7", inCondition).OrderBy("$.field_name9").Limit(20, 1); + ret = g_kvNbDelegatePtrForQuery->GetEntries(fullQuery, resultSet); + ASSERT_TRUE(resultSet != nullptr); + EXPECT_EQ(ret, OK); + EXPECT_EQ(resultSet->GetCount(), 2); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet), OK); + EXPECT_TRUE(resultSet == nullptr); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("GetEntriesWithQuery004") == OK); +} + +/** + * @tc.name: QueryIsNotNull001 + * @tc.desc: IsNotNull interface normal function + * @tc.type: FUNC + * @tc.require: AR000EPARK + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, QueryIsNotNull001, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_DEFINE2; + g_mgr.GetKvStore("QueryIsNotNull001", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + std::string validData = "{\"field_name1\":null, \"field_name2\":1}"; + Value value(validData.begin(), validData.end()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(KEY_1, value), OK); + validData = "{\"field_name1\":2, \"field_name2\":2}"; + Value value2(validData.begin(), validData.end()); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->Put(KEY_2, value2), OK); + /** + * @tc.steps: step1. Get Query object by IsNotNull + */ + Query query = Query::Select().IsNotNull("$.field_name1"); + /** + * @tc.steps: step2. Use GetEntries get KV + * @tc.expected: step2. Getentries return OK, Get K1V1; + */ + std::vector entries; + int errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, entries); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(entries.size(), 1ul); + EXPECT_EQ(entries[0].key, KEY_2); + EXPECT_EQ(entries[0].value, value2); + /** + * @tc.steps: step3. Use GetCount to get number of item + * @tc.expected: step3. Get count = 1; + */ + int count = -1; + g_kvNbDelegatePtrForQuery->GetCount(query, count); + EXPECT_EQ(count, 1); + /** + * @tc.steps: step4. Use GetEntries to get resultSet + * @tc.expected: step4. Getentries return OK, Get K1V1; + */ + KvStoreResultSet *resultSet = nullptr; + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, resultSet); + ASSERT_TRUE(resultSet != nullptr); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(resultSet->GetCount(), 1); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet), OK); + EXPECT_TRUE(resultSet == nullptr); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("QueryIsNotNull001") == OK); +} + +/** + * @tc.name: QueryPreFixKey001 + * @tc.desc: Normal function of query by prefix key + * @tc.type: FUNC + * @tc.require: AR000EPARK + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, QueryPreFixKey001, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_DEFINE2; + g_mgr.GetKvStore("QueryPreFixKey001", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + + vector entries = PreDataForQueryByPreFixKey(); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->PutBatch(entries), OK); + + /** + * @tc.steps: step1. Get Query object by PrefixKey ac + */ + Query query = Query::Select().PrefixKey({'a', 'c'}); + + /** + * @tc.steps: step2. Use GetEnties to get same key prefix ac + * @tc.expected: step2. Get count = 5, Key6~10 + */ + std::vector entriesRes; + int errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, entriesRes); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(entriesRes.size(), 5ul); + for (size_t i = 0; i < entriesRes.size(); i++) { + EXPECT_EQ(entriesRes[i].key.front(), 'a'); + EXPECT_EQ(entriesRes[i].key[1], 'c'); + } + /** + * @tc.steps: step3. Use GetCount to get number of item of this query object + * @tc.expected: step3. Get count = 5 + */ + int count = -1; + g_kvNbDelegatePtrForQuery->GetCount(query, count); + EXPECT_EQ(count, 5); + /** + * @tc.steps: step4. Use GetEnties to get same key prefix ac of resultSet + * @tc.expected: step4. Get resultSet of key6~10 + */ + KvStoreResultSet *resultSet = nullptr; + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, resultSet); + ASSERT_TRUE(resultSet != nullptr); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(resultSet->GetCount(), 5); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet), OK); + EXPECT_TRUE(resultSet == nullptr); + + /** + * @tc.steps: step5. Get Query object by null PrefixKey + */ + Query query1 = Query::Select().PrefixKey({}); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entriesRes); + /** + * @tc.steps: step6. Use GetEnties and GetCount to null key prefix query object + * @tc.expected: step6. Get all KV from database + */ + EXPECT_EQ(errCode, OK); + EXPECT_EQ(entriesRes.size(), 10ul); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntriesEqual(entries, entriesRes, true)); + + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, resultSet); + ASSERT_TRUE(resultSet != nullptr); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(resultSet->GetCount(), 10); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet), OK); + EXPECT_TRUE(resultSet == nullptr); + + Query query2 = Query::Select().PrefixKey(Key(1025, 'a')); // 1025 over max key length 1 byte + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query2, entriesRes); + EXPECT_EQ(errCode, INVALID_ARGS); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("QueryPreFixKey001") == OK); +} + +/** + * @tc.name: QueryPreFixKey003 + * @tc.desc: For special key prefix combination condition of query + * @tc.type: FUNC + * @tc.require: AR000EPARK + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, QueryPreFixKey003, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_DEFINE2; + g_mgr.GetKvStore("QueryPreFixKey003", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + + vector entries = PreDataForQueryByPreFixKey(); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->PutBatch(entries), OK); + + /** + * @tc.steps: step1. Get Query object by double PrefixKey + */ + Query query = Query::Select().PrefixKey({'a', 'c'}).PrefixKey({}); + std::vector entriesRes; + /** + * @tc.steps: step2. Use GetEnties for double prefixkey query object + * @tc.expected: step2. return INVALID_QUERY_FORMAT + */ + int errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, entriesRes); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + /** + * @tc.steps: step3. Use GetEnties for double prefixkey query object to get resultSet + * @tc.expected: step3. return INVALID_QUERY_FORMAT + */ + KvStoreResultSet *resultSet = nullptr; + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, resultSet); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + EXPECT_TRUE(resultSet == nullptr); + /** + * @tc.steps: step4. Get Query object by PrefixKey and orderBy + */ + Query query1 = Query::Select().PrefixKey({'a', 'b'}).OrderBy("$.field_name1"); + /** + * @tc.steps: step3. Use GetEnties and GetCount for this query object + * @tc.expected: step3. Can get content by GetEntries, but GetCount can not use for query object include orderBy + */ + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entriesRes); + EXPECT_EQ(entriesRes.size(), 5ul); + int count = -1; + errCode = g_kvNbDelegatePtrForQuery->GetCount(query1, count); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, resultSet); + ASSERT_TRUE(resultSet != nullptr); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(resultSet->GetCount(), 5); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet), OK); + EXPECT_TRUE(resultSet == nullptr); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("QueryPreFixKey003") == OK); +} + +/** + * @tc.name: QueryPreFixKey004 + * @tc.desc: Query a prefix that does not exist + * @tc.type: FUNC + * @tc.require: AR000EPARK + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, QueryPreFixKey004, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_DEFINE2; + g_mgr.GetKvStore("QueryPreFixKey004", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + + vector entries = PreDataForQueryByPreFixKey(); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->PutBatch(entries), OK); + /** + * @tc.steps: step1. Get Query object by PrefixKey that does not exist + */ + Query query = Query::Select().PrefixKey({'c'}); + /** + * @tc.steps: step2. Use GetEnties and GetCount to get result + * @tc.expected: step2. Return NOT_FOUND, get result OK, number of KV is 0 + */ + std::vector entriesRes; + int errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, entriesRes); + EXPECT_EQ(errCode, NOT_FOUND); + + int count = -1; + g_kvNbDelegatePtrForQuery->GetCount(query, count); + EXPECT_EQ(count, 0); + + KvStoreResultSet *resultSet = nullptr; + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, resultSet); + ASSERT_TRUE(resultSet != nullptr); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(resultSet->GetCount(), 0); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet), OK); + EXPECT_TRUE(resultSet == nullptr); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("QueryPreFixKey004") == OK); +} + +/** + * @tc.name: QueryGroup001 + * @tc.desc: Query group nomal ability to change operation priority + * @tc.type: FUNC + * @tc.require: AR000EPARK + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, QueryGroup001, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_DEFINE2; + g_mgr.GetKvStore("QueryGroup001", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + + PreDataForGroupTest(); + + /** + * @tc.steps: step1. Get Query object: + * query: <4 and =4 or >1 + * query1: (<4 and =4) or >1 + * query2: <4 and (=4 or >1) + */ + Query query = Query::Select().LessThan("$.field_name1", 4).And().EqualTo("$.field_name1", 4). + Or().GreaterThan("$.field_name1", 1); + Query query1 = Query::Select().BeginGroup().LessThan("$.field_name1", 4).And(). + EqualTo("$.field_name1", 4).EndGroup().Or().GreaterThan("$.field_name1", 1); + Query query2 = Query::Select().LessThan("$.field_name1", 4).And().BeginGroup(). + EqualTo("$.field_name1", 4).Or().GreaterThan("$.field_name1", 1).EndGroup(); + + /** + * @tc.steps: step2. Use GetEnties to get different result + * @tc.expected: step2. Result: + * query: count = 4 + * query1: count = 4 + * query2: count = 2 + */ + std::vector entries; + int errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, entries); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(entries.size(), 4ul); + int count = -1; + g_kvNbDelegatePtrForQuery->GetCount(query, count); + EXPECT_EQ(count, 4); + + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query2, entries); + EXPECT_EQ(errCode, OK); + + EXPECT_EQ(entries.size(), 2ul); + g_kvNbDelegatePtrForQuery->GetCount(query2, count); + EXPECT_EQ(count, 2); + KvStoreResultSet *resultSet = nullptr; + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query2, resultSet); + ASSERT_TRUE(resultSet != nullptr); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(resultSet->GetCount(), 2); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet), OK); + EXPECT_TRUE(resultSet == nullptr); + + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entries); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(entries.size(), 4ul); + g_kvNbDelegatePtrForQuery->GetCount(query1, count); + EXPECT_EQ(count, 4); + + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, resultSet); + ASSERT_TRUE(resultSet != nullptr); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(resultSet->GetCount(), 4); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet), OK); + EXPECT_TRUE(resultSet == nullptr); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("QueryGroup001") == OK); +} + +/** + * @tc.name: QueryGroup002 + * @tc.desc: Test for illegal Group query object + * @tc.type: FUNC + * @tc.require: AR000EPARK + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, QueryGroup002, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_DEFINE2; + g_mgr.GetKvStore("QueryGroup002", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + + PreDataForGroupTest(); + + /** + * @tc.steps: step1. Get Query object: + * query: (<4 and (=4 or) >1) + * query1: (<4 and =4 or >1 + * query2: <4 and =4) or >1 + * query3: )<4 and =4( or >1 + * query4: <4 (and = 4 or >1) + */ + Query query = Query::Select().BeginGroup().LessThan("$.field_name1", 4).And().BeginGroup(). + EqualTo("$.field_name1", 4).Or().EndGroup().GreaterThan("$.field_name1", 1).EndGroup(); + Query query1 = Query::Select().BeginGroup().LessThan("$.field_name1", 4).And(). + EqualTo("$.field_name1", 4).Or().GreaterThan("$.field_name1", 1); + Query query2 = Query::Select().LessThan("$.field_name1", 4).And(). + EqualTo("$.field_name1", 4).EndGroup().Or().GreaterThan("$.field_name1", 1); + Query query3 = Query::Select().EndGroup().LessThan("$.field_name1", 4).And(). + EqualTo("$.field_name1", 4).BeginGroup().Or().GreaterThan("$.field_name1", 1); + Query query4 = Query::Select().LessThan("$.field_name1", 4).BeginGroup().And(). + EqualTo("$.field_name1", 4).Or().GreaterThan("$.field_name1", 1).EndGroup(); + + /** + * @tc.steps: step2. Use GetEnties and GetCount to get result + * @tc.expected: step2. All query object is illegal, reeturn INVALID_QUERY_FORMAT + */ + std::vector entries; + int errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, entries); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entries); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query2, entries); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query3, entries); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query4, entries); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + + KvStoreResultSet *resultSet = nullptr; + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, resultSet); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + EXPECT_TRUE(resultSet == nullptr); + + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, resultSet); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + EXPECT_TRUE(resultSet == nullptr); + + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query2, resultSet); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + EXPECT_TRUE(resultSet == nullptr); + + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query3, resultSet); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + EXPECT_TRUE(resultSet == nullptr); + + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query4, resultSet); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + EXPECT_TRUE(resultSet == nullptr); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("QueryGroup002") == OK); +} + +/** + * @tc.name: QueryGroup003 + * @tc.desc: Query expressions containing nested parentheses + * @tc.type: FUNC + * @tc.require: AR000EPARK + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, QueryGroup003, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_DEFINE2; + g_mgr.GetKvStore("QueryGroup003", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + + PreDataForGroupTest(); + + /** + * @tc.steps: step1. Get Query object for (<=5 and (=4 or >1) and <3) + */ + Query query = Query::Select().BeginGroup().LessThan("$.field_name1", 5).And().BeginGroup(). + EqualTo("$.field_name1", 4).Or().GreaterThan("$.field_name1", 1).EndGroup().And(). + LessThan("$.field_name1", 3).EndGroup(); + + /** + * @tc.steps: step2. Use GetEnties and GetCount to get result + * @tc.expected: step2. reeturn OK, count = 1 + */ + std::vector entries; + int errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, entries); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(entries.size(), 1ul); + int count = -1; + g_kvNbDelegatePtrForQuery->GetCount(query, count); + EXPECT_EQ(count, 1); + + KvStoreResultSet *resultSet = nullptr; + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, resultSet); + ASSERT_TRUE(resultSet != nullptr); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(resultSet->GetCount(), 1); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet), OK); + EXPECT_TRUE(resultSet == nullptr); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("QueryGroup003") == OK); +} + +/** + * @tc.name: multiOrderBy001 + * @tc.desc: Test multiple orderby conditions together to query + * @tc.type: FUNC + * @tc.require: AR000DR9K7 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, multiOrderBy001, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_DEFINE2; + g_mgr.GetKvStore("multiOrderBy001", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + + PreDataForGroupTest(); + + Query query = Query::Select().PrefixKey({}).OrderBy("$.field_name1").OrderBy("$.field_name1"); + std::vector entries; + int errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, entries); + EXPECT_EQ(errCode, OK); + + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query.Limit(2, 2), entries); + EXPECT_EQ(errCode, OK); + + Query query1 = Query::Select().PrefixKey({}).Limit(2, 2).OrderBy("$.field_name1").OrderBy("$.field_name1"); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entries); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + + Query query2 = Query::Select().PrefixKey({}).OrderBy("$.field_name1").Limit(2, 2).OrderBy("$.field_name1"); + KvStoreResultSet *resultSet = nullptr; + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query2, resultSet); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + + Query query3 = Query::Select().PrefixKey({}).OrderBy("$.field_name1"). + OrderBy("$.field_name1").OrderBy("$.field_name1"); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query3, resultSet); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet), OK); + EXPECT_TRUE(resultSet == nullptr); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("multiOrderBy001") == OK); +} + +/** + * @tc.name: multiOrderBy001 + * @tc.desc: For multiple order query. + * @tc.type: FUNC + * @tc.require: AR000EPARK + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, PreifxAndOrderBy001, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_DEFINE2; + g_mgr.GetKvStore("PreifxAndOrderBy001", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + + PresetDataForPreifxAndOrderBy001(); + + Query query = Query::Select().PrefixKey({}).OrderBy("$.field_name1", false); + std::vector entriesRes; + int errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, entriesRes); + EXPECT_EQ(errCode, OK); + ASSERT_EQ(entriesRes.size(), 5ul); + EXPECT_EQ(entriesRes[0].key, KEY_5); + EXPECT_EQ(entriesRes[1].key, KEY_3); + EXPECT_EQ(entriesRes[2].key, KEY_4); + EXPECT_EQ(entriesRes[3].key, KEY_1); + EXPECT_EQ(entriesRes[4].key, KEY_2); + + Query query1 = Query::Select().OrderBy("$.field_name1", false); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entriesRes); + ASSERT_EQ(entriesRes.size(), 5ul); + EXPECT_EQ(entriesRes[0].key, KEY_5); + EXPECT_EQ(entriesRes[1].key, KEY_4); + EXPECT_EQ(entriesRes[2].key, KEY_3); + EXPECT_EQ(entriesRes[3].key, KEY_2); + EXPECT_EQ(entriesRes[4].key, KEY_1); + + Query query2 = Query::Select().PrefixKey({}).OrderBy("$.field_name1", false).OrderBy("$.field_name2", false); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query2, entriesRes); + ASSERT_EQ(entriesRes.size(), 5ul); + EXPECT_EQ(entriesRes[0].key, KEY_5); + EXPECT_EQ(entriesRes[1].key, KEY_4); + EXPECT_EQ(entriesRes[2].key, KEY_3); + EXPECT_EQ(entriesRes[3].key, KEY_2); + EXPECT_EQ(entriesRes[4].key, KEY_1); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("PreifxAndOrderBy001") == OK); +} + +/** + * @tc.name: PrefixAndOther001 + * @tc.desc: Combination of prefix query and logical filtering + * @tc.type: FUNC + * @tc.require: AR000EPARK + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, PrefixAndOther001, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_DEFINE2; + g_mgr.GetKvStore("PrefixAndOther001", option, g_kvNbDelegateCallbackForQuery); + ASSERT_TRUE(g_kvNbDelegatePtrForQuery != nullptr); + EXPECT_TRUE(g_kvDelegateStatusForQuery == OK); + + PresetDataForPreifxAndOrderBy001(); + + std::vector entriesRes; + Query query1 = Query::Select().EqualTo("$.field_name1", 1).PrefixKey({}); + int errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entriesRes); + EXPECT_EQ(errCode, OK); + query1 = Query::Select().PrefixKey({}).EqualTo("$.field_name1", 1); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entriesRes); + EXPECT_EQ(errCode, OK); + + query1 = Query::Select().EqualTo("$.field_name1", 1).PrefixKey({}).And().EqualTo("$.field_name1", 1); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entriesRes); + EXPECT_EQ(errCode, OK); + + query1 = Query::Select().EqualTo("$.field_name1", 1).PrefixKey({}).And().EqualTo("$.field_name1", 2); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entriesRes); + EXPECT_EQ(errCode, NOT_FOUND); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("PrefixAndOther001") == OK); +} + +/** + * @tc.name: InKeys001 + * @tc.desc: InKeys query base function + * @tc.type: FUNC + * @tc.require: AR000GOH06 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, InKeys001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create a database And Preset Data + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("InKeys001", option, g_kvNbDelegateCallbackForQuery); + ASSERT_NE(g_kvNbDelegatePtrForQuery, nullptr); + EXPECT_EQ(g_kvDelegateStatusForQuery, OK); + + Key key = {'1'}; + DBStatus status = g_kvNbDelegatePtrForQuery->Put(key, VALUE_1); + ASSERT_EQ(status, OK); + const int dataSize = 10; // 10 data for test + std::set keys; + for (uint8_t i = 0; i < dataSize; i++) { + key.push_back(i); + DBStatus status = g_kvNbDelegatePtrForQuery->Put(key, VALUE_1); + ASSERT_EQ(status, OK); + keys.emplace(key); + key.pop_back(); + } + + /** + * @tc.steps: step2. Call GetEntries With Query, set all keys at Inkeys. + * @tc.expected: step2. Returns KvStoreResultSet, the count is dataSize, + * all data are equals the preset data + */ + KvStoreResultSet *resultSet = nullptr; + g_kvNbDelegatePtrForQuery->GetEntries(Query::Select().InKeys(keys), resultSet); + ASSERT_NE(resultSet, nullptr); + ASSERT_EQ(resultSet->GetCount(), dataSize); + for (int i = 0; i < dataSize; i++) { + resultSet->MoveToPosition(i); + Entry entry; + resultSet->GetEntry(entry); + key.push_back(i); + EXPECT_EQ(key, entry.key); + EXPECT_EQ(entry.value, VALUE_1); + key.pop_back(); + } + g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet); + + /** + * @tc.steps: step3. Call GetEntries With Query, set one other key at Inkeys. + * @tc.expected: step3. Returns KvStoreResultSet, the count is 0, + */ + g_kvNbDelegatePtrForQuery->GetEntries(Query::Select().InKeys({KEY_7}), resultSet); + ASSERT_NE(resultSet, nullptr); + ASSERT_EQ(resultSet->GetCount(), 0); + g_kvNbDelegatePtrForQuery->CloseResultSet(resultSet); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("InKeys001"), OK); +} + +/** + * @tc.name: InKeysLimit001 + * @tc.desc: InKeys query limit verification + * @tc.type: FUNC + * @tc.require: AR000GOH06 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, InKeysLimit001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create a database + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("InKeysLimit001", option, g_kvNbDelegateCallbackForQuery); + ASSERT_NE(g_kvNbDelegatePtrForQuery, nullptr); + EXPECT_EQ(g_kvDelegateStatusForQuery, OK); + + /** + * @tc.steps: step2. Construct a key set, and the key size over MAX_BATCH_SIZE + */ + std::set keys; + for (uint8_t i = 0; i < DBConstant::MAX_BATCH_SIZE + 1; i++) { + Key key = { i }; + keys.emplace(key); + } + + /** + * @tc.steps: step3. Call GetEntries With Query, set keys at Inkeys. + * @tc.expected: step3. Returns OVER_MAX_LIMITS, the resultSet is nullptr, + */ + KvStoreResultSet *resultSet = nullptr; + DBStatus status = g_kvNbDelegatePtrForQuery->GetEntries(Query::Select().InKeys(keys), resultSet); + EXPECT_EQ(status, OVER_MAX_LIMITS); + EXPECT_EQ(resultSet, nullptr); + + /** + * @tc.steps: step4. Call GetEntries With Query, set keys empty. + * @tc.expected: step4. Returns INVALID_ARGS, the resultSet is nullptr, + */ + status = g_kvNbDelegatePtrForQuery->GetEntries(Query::Select().InKeys({}), resultSet); + EXPECT_EQ(status, INVALID_ARGS); + EXPECT_EQ(resultSet, nullptr); + + /** + * @tc.steps: step4. Call GetEntries With Query, set a invalid key. + * @tc.expected: step4. Returns INVALID_ARGS, the resultSet is nullptr, + */ + status = g_kvNbDelegatePtrForQuery->GetEntries(Query::Select().InKeys({{}}), resultSet); + EXPECT_EQ(status, INVALID_ARGS); + EXPECT_EQ(resultSet, nullptr); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("InKeysLimit001"), OK); +} + +/** + * @tc.name: InKeysAndOther001 + * @tc.desc: Combination of InKeys query and logical filtering + * @tc.type: FUNC + * @tc.require: AR000GOH06 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, InKeysAndOther001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create a database And Preset Data + */ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_DEFINE2; + g_mgr.GetKvStore("InKeysAndOther001", option, g_kvNbDelegateCallbackForQuery); + ASSERT_NE(g_kvNbDelegatePtrForQuery, nullptr); + EXPECT_EQ(g_kvDelegateStatusForQuery, OK); + + PresetDataForPreifxAndOrderBy001(); + + std::set keys = { KEY_1, KEY_2, KEY_4 }; + std::vector entriesRes; + + /** + * @tc.steps: step2. Call GetEntries With Query, use EqualTo and InKeys + * @tc.expected: step2. Returns OK + */ + Query query1 = Query::Select().EqualTo("$.field_name1", 1).InKeys(keys); + int errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entriesRes); + EXPECT_EQ(errCode, OK); + + /** + * @tc.steps: step3. Call GetEntries With Query, use InKeys and EqualTo + * @tc.expected: step3. Returns OK + */ + query1 = Query::Select().InKeys(keys).EqualTo("$.field_name1", 1); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entriesRes); + EXPECT_EQ(errCode, OK); + + /** + * @tc.steps: step4. Call GetEntries With Query, use EqualTo, InKeys and EqualTo, all valid + * @tc.expected: step4. Returns OK + */ + query1 = Query::Select().EqualTo("$.field_name1", 1).InKeys(keys).And().EqualTo("$.field_name2", 2); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entriesRes); + EXPECT_EQ(errCode, OK); + + /** + * @tc.steps: step4. Call GetEntries With Query, use EqualTo, InKeys and EqualTo, has invalid + * @tc.expected: step4. Returns NOT_FOUND + */ + query1 = Query::Select().EqualTo("$.field_name1", 1).InKeys(keys).And().EqualTo("$.field_name1", 2); + errCode = g_kvNbDelegatePtrForQuery->GetEntries(query1, entriesRes); + EXPECT_EQ(errCode, NOT_FOUND); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("InKeysAndOther001"), OK); +} + +/** + * @tc.name: InKeysAndOther002 + * @tc.desc: Combination of InKeys query and logical filtering + * @tc.type: FUNC + * @tc.require: AR000GOH06 + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesDataOperationTest, InKeysAndOther002, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_DEFINE2; + g_mgr.GetKvStore("InKeysAndOther001", option, g_kvNbDelegateCallbackForQuery); + ASSERT_NE(g_kvNbDelegatePtrForQuery, nullptr); + EXPECT_EQ(g_kvDelegateStatusForQuery, OK); + + std::set keys = { KEY_1, KEY_2, KEY_4 }; + std::vector queries = { + Query::Select().PrefixKey({}).InKeys(keys).Or().EqualTo("$.field_name1", 2), + Query::Select().PrefixKey({}).InKeys(keys).And().EqualTo("$.field_name1", 2), + Query::Select().InKeys(keys).Or().EqualTo("$.field_name1", 2), + Query::Select().InKeys(keys).And().EqualTo("$.field_name1", 2), + Query::Select().PrefixKey({}).Or().EqualTo("$.field_name1", 2), + Query::Select().PrefixKey({}).And().EqualTo("$.field_name1", 2), + Query::Select().PrefixKey({}).And().InKeys(keys).EqualTo("$.field_name1", 2), + Query::Select().And().InKeys(keys).EqualTo("$.field_name1", 2), + Query::Select().Or().PrefixKey({}).EqualTo("$.field_name1", 2), + Query::Select().BeginGroup().PrefixKey({}).Or().EqualTo("$.field_name1", 2).EndGroup(), + Query::Select().EqualTo("$.field_name1", 2).Or().InKeys(keys), + Query::Select().EqualTo("$.field_name1", 2).Or().PrefixKey({}), + }; + + for (const auto &query : queries) { + std::vector entriesRes; + int errCode = g_kvNbDelegatePtrForQuery->GetEntries(query, entriesRes); + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); + } + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrForQuery), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("InKeysAndOther001"), OK); +} +#endif // OMIT_JSON \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_data_value_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_data_value_test.cpp new file mode 100644 index 00000000..570d23d8 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_data_value_test.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "distributeddb_tools_unit_test.h" +#include "data_value.h" + +using namespace testing::ext; +using namespace DistributedDB; + +namespace { +const int ONE_HUNDERED = 100; +const char DEFAULT_CHAR = 'D'; +const std::string DEFAULT_TEXT = "This is a text"; + +void DataValueDefaultNullCheck(DataValue &dataValue) +{ + /** + * @tc.steps: step1. create a dataValue + * @tc.expected: dataValue type is null. + */ + dataValue.ResetValue(); + EXPECT_EQ(dataValue.GetType(), StorageType::STORAGE_TYPE_NULL); +} + +void DataValueDoubleCheck(DataValue &dataValue) +{ + /** + * @tc.steps: step1. create a dataValue and set true + * @tc.expected: dataValue type is double. + */ + double targetDoubleVal = 1.0; + dataValue = targetDoubleVal; + EXPECT_EQ(dataValue.GetType(), StorageType::STORAGE_TYPE_REAL); + /** + * @tc.steps: step2. get a double from dataValue + * @tc.expected: get ok and value is equal to targetDoubleVal. + */ + double val = 0; + EXPECT_EQ(dataValue.GetDouble(val), E_OK); + EXPECT_EQ(val, targetDoubleVal); +} + +void DataValueInt64Check(DataValue &dataValue) +{ + /** + * @tc.steps: step1. create a dataValue and set INTE64_MAX + * @tc.expected: dataValue type is int64. + */ + int64_t targetInt64Val = INT64_MAX; + dataValue = targetInt64Val; + EXPECT_EQ(dataValue.GetType(), StorageType::STORAGE_TYPE_INTEGER); + /** + * @tc.steps: step2. get a int64 from dataValue + * @tc.expected: get ok and value is equal to INTE64_MAX. + */ + int64_t val = INT64_MIN; + EXPECT_EQ(dataValue.GetInt64(val), E_OK); + EXPECT_EQ(val, targetInt64Val); +} + +void DataValueStringCheck(DataValue &dataValue) +{ + /** + * @tc.steps: step1. set a string + * @tc.expected: dataValue type is string. + */ + dataValue.SetText(DEFAULT_TEXT); + EXPECT_EQ(dataValue.GetType(), StorageType::STORAGE_TYPE_TEXT); + /** + * @tc.steps: step2. get a string from dataValue + * @tc.expected: get ok and string is equal to DEFAULT_TEXT. + */ + std::string val; + EXPECT_EQ(dataValue.GetText(val), E_OK); + EXPECT_EQ(val, DEFAULT_TEXT); +} + +void DataValueBlobCheck(DataValue &dataValue) +{ + /** + * @tc.steps: step1. set a blob + * @tc.expected: dataValue type is blob. + */ + Blob targetVal; + std::vector vector(ONE_HUNDERED, DEFAULT_CHAR); + targetVal.WriteBlob(reinterpret_cast(vector.data()), vector.size()); + dataValue.SetBlob(targetVal); + EXPECT_EQ(dataValue.GetType(), StorageType::STORAGE_TYPE_BLOB); + /** + * @tc.steps: step2. get a blob from dataValue + * @tc.expected: get ok and value is equal to DEFAULT_TEXT. + */ + Blob val; + EXPECT_EQ(dataValue.GetBlob(val), E_OK); + for (int i = 0; i < ONE_HUNDERED; i++) { + EXPECT_EQ(targetVal.GetData()[i], val.GetData()[i]); + } +} + +const std::vector g_checkFuncList = { + &DataValueDefaultNullCheck, &DataValueInt64Check, + &DataValueDoubleCheck, &DataValueStringCheck, &DataValueBlobCheck +}; + +class DistributedDBInterfacesDataValueTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesDataValueTest::SetUpTestCase(void) +{ +} + +void DistributedDBInterfacesDataValueTest::TearDownTestCase(void) +{ +} + +void DistributedDBInterfacesDataValueTest::SetUp(void) +{ +} + +void DistributedDBInterfacesDataValueTest::TearDown(void) +{ +} + +/** + * @tc.name: DataValueCheck001 + * @tc.desc: To test dataValue work correctly when data is nullptr, bool, string, double, int64, uint8_t*. + * @tc.type: Func + * @tc.require: + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBInterfacesDataValueTest, DataValueCheck001, TestSize.Level1) +{ + for (const auto &func : g_checkFuncList) { + DataValue dataValue; + func(dataValue); + } +} + +/** + * @tc.name: DataValueCheck002 + * @tc.desc: To test dataValue work correctly when different type overwrite into dataValue. + * @tc.type: Func + * @tc.require: + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBInterfacesDataValueTest, DataValueCheck002, TestSize.Level1) +{ + for (uint32_t lastWriteIndex = 0; lastWriteIndex < g_checkFuncList.size(); lastWriteIndex++) { + DataValue dataValue; + for (uint32_t i = 0; i < g_checkFuncList.size(); i++) { + uint32_t index = (lastWriteIndex + i + 1) % static_cast(g_checkFuncList.size()); + g_checkFuncList[index](dataValue); + } + } +} +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_database_corrupt_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_database_corrupt_test.cpp new file mode 100644 index 00000000..795d7e66 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_database_corrupt_test.cpp @@ -0,0 +1,505 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_constant.h" +#include "distributeddb_tools_unit_test.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { + const std::string APP_NAME = "app"; + const std::string USER_NAME = "account0"; + const int PASSWD_SIZE = 20; + const int WAIT_CALLBACK_TIME = 100; + KvStoreDelegateManager g_mgr(APP_NAME, USER_NAME); + string g_testDir; + KvStoreConfig g_config; + + DBStatus g_kvDelegateStatus = INVALID_ARGS; + DBStatus g_kvNbDelegateStatus = INVALID_ARGS; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + auto g_kvDelegateCallback = std::bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, std::placeholders::_1, + std::placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + auto g_kvNbDelegateCallback = std::bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + std::placeholders::_1, std::placeholders::_2, std::ref(g_kvNbDelegateStatus), std::ref(g_kvNbDelegatePtr)); + + std::string GetKvStoreDirectory(const std::string &storeId, int databaseType) + { + std::string identifier = USER_NAME + "-" + APP_NAME + "-" + storeId; + std::string hashIdentifierName = DBCommon::TransferHashString(identifier); + std::string identifierName = DBCommon::TransferStringToHex(hashIdentifierName); + std::string filePath = g_testDir + "/" + identifierName + "/"; + if (databaseType == DBConstant::DB_TYPE_LOCAL) { // local + filePath += (DBConstant::LOCAL_SUB_DIR + "/" + DBConstant::LOCAL_DATABASE_NAME + + DBConstant::SQLITE_DB_EXTENSION); + } else if (databaseType == DBConstant::DB_TYPE_SINGLE_VER) { // single ver + filePath += (DBConstant::SINGLE_SUB_DIR + "/" + DBConstant::MAINDB_DIR + "/" + + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION); + } else if (databaseType == DBConstant::DB_TYPE_MULTI_VER) { // multi ver + filePath += (DBConstant::MULTI_SUB_DIR + "/" + DBConstant::MULTI_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION); + } else { + filePath = ""; + } + + return filePath; + } + + int PutDataIntoDatabase(KvStoreDelegate *kvDelegate, KvStoreNbDelegate *kvNbDelegate) + { + if (kvDelegate == nullptr && kvNbDelegate == nullptr) { + return DBStatus::DB_ERROR; + } + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + DBStatus status = OK; + if (kvDelegate != nullptr) { + status = kvDelegate->Put(key, value); + if (status != OK) { + return status; + } + } + if (kvNbDelegate != nullptr) { + status = kvNbDelegate->Put(key, value); + if (status != OK) { + return status; + } + } + return status; + } +} + +class DistributedDBInterfacesDatabaseCorruptTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesDatabaseCorruptTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesDatabaseCorruptTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesDatabaseCorruptTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_kvDelegateStatus = INVALID_ARGS; + g_kvDelegatePtr = nullptr; +} + +void DistributedDBInterfacesDatabaseCorruptTest::TearDown(void) +{ + g_mgr.SetKvStoreCorruptionHandler(nullptr); +} + +/** + * @tc.name: DatabaseCorruptionHandleTest001 + * @tc.desc: Check the corruption detect without setting the corrupt handler. + * @tc.type: FUNC + * @tc.require: AR000D487C SR000D4878 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDatabaseCorruptTest, DatabaseCorruptionHandleTest001, TestSize.Level3) +{ + /** + * @tc.steps: step1. Obtain the kvStore. + * @tc.steps: step2. Put one data into the store. + * @tc.steps: step3. Close the store. + */ + CipherPassword passwd; + Key randomPassword; + DistributedDBToolsUnitTest::GetRandomKeyValue(randomPassword, PASSWD_SIZE); + int errCode = passwd.SetValue(randomPassword.data(), randomPassword.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + KvStoreDelegate::Option option = {true, true, false, CipherType::DEFAULT, passwd}; + g_mgr.GetKvStore("corrupt1", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + ASSERT_EQ(PutDataIntoDatabase(g_kvDelegatePtr, nullptr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + + /** + * @tc.steps: step4. Modify the database file. + */ + std::string filePath = GetKvStoreDirectory("corrupt1", DBConstant::DB_TYPE_LOCAL); // local database. + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath); + + /** + * @tc.steps: step5. Re-obtain the kvStore. + * @tc.expected: step5. Returns null kvstore. + */ + g_mgr.GetKvStore("corrupt1", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == INVALID_PASSWD_OR_CORRUPTED_DB); + g_mgr.DeleteKvStore("corrupt1"); +} + +/** + * @tc.name: DatabaseCorruptionHandleTest002 + * @tc.desc: Get kv store through different parameters for the same storeID. + * @tc.type: FUNC + * @tc.require: AR000D487C SR000D4878 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDatabaseCorruptTest, DatabaseCorruptionHandleTest002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the kvStore. + * @tc.steps: step2. Put data into the store. + * @tc.steps: step3. Close the store. + */ + CipherPassword passwd; + Key randomPassword; + DistributedDBToolsUnitTest::GetRandomKeyValue(randomPassword, PASSWD_SIZE); + int errCode = passwd.SetValue(randomPassword.data(), randomPassword.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + KvStoreDelegate::Option option = {true, false, false, CipherType::DEFAULT, passwd}; + KvStoreNbDelegate::Option nbOption = {true, false, false, CipherType::DEFAULT, passwd}; + + g_mgr.GetKvStore("corrupt2", option, g_kvDelegateCallback); + g_mgr.GetKvStore("corrupt3", nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + ASSERT_EQ(PutDataIntoDatabase(g_kvDelegatePtr, g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_kvNbDelegatePtr = nullptr; + + /** + * @tc.steps: step4. Modify the database file. + */ + std::string filePath = GetKvStoreDirectory("corrupt2", DBConstant::DB_TYPE_MULTI_VER); + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath); + filePath = GetKvStoreDirectory("corrupt3", DBConstant::DB_TYPE_SINGLE_VER); // single ver database. + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath); + KvStoreCorruptInfo corruptInfo; + auto notifier = bind(&KvStoreCorruptInfo::CorruptCallBack, &corruptInfo, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + g_mgr.SetKvStoreCorruptionHandler(notifier); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_CALLBACK_TIME)); + /** + * @tc.steps: step5. Re-obtain the kvStore. + * @tc.expected: step5. Returns null kvstore. + */ + g_mgr.GetKvStore("corrupt2", option, g_kvDelegateCallback); + g_mgr.GetKvStore("corrupt3", nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvDelegateStatus != OK); + ASSERT_TRUE(g_kvNbDelegateStatus != OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_CALLBACK_TIME)); + EXPECT_EQ(corruptInfo.GetDatabaseInfoSize(), 2UL); // 2 callback + EXPECT_EQ(corruptInfo.IsDataBaseCorrupted(APP_NAME, USER_NAME, "corrupt2"), true); + EXPECT_EQ(corruptInfo.IsDataBaseCorrupted(APP_NAME, USER_NAME, "corrupt3"), true); + g_mgr.DeleteKvStore("corrupt2"); + g_mgr.DeleteKvStore("corrupt3"); +} + +/** + * @tc.name: DatabaseCorruptionHandleTest003 + * @tc.desc: Test the CloseKvStore Interface and check whether the database file can be closed. + * @tc.type: FUNC + * @tc.require: AR000D487C SR000D4878 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDatabaseCorruptTest, DatabaseCorruptionHandleTest003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the kvStore. + * @tc.steps: step2. Put data into the store. + * @tc.steps: step3. Close the store. + */ + CipherPassword passwd; + Key randomPassword; + DistributedDBToolsUnitTest::GetRandomKeyValue(randomPassword, PASSWD_SIZE); + int errCode = passwd.SetValue(randomPassword.data(), randomPassword.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + KvStoreDelegate::Option option = {true, true, false, CipherType::DEFAULT, passwd}; + KvStoreNbDelegate::Option nbOption = {true, false, false, CipherType::DEFAULT, passwd}; + + g_mgr.GetKvStore("corrupt4", option, g_kvDelegateCallback); + g_mgr.GetKvStore("corrupt5", nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + ASSERT_EQ(PutDataIntoDatabase(g_kvDelegatePtr, g_kvNbDelegatePtr), OK); + + /** + * @tc.steps: step4. Modify the database file. + */ + std::string filePath = GetKvStoreDirectory("corrupt4", DBConstant::DB_TYPE_LOCAL); // local database. + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath); + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath + "-wal"); + filePath = GetKvStoreDirectory("corrupt5", DBConstant::DB_TYPE_SINGLE_VER); // single ver database. + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath); + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath + "-wal"); + KvStoreCorruptInfo corruptInfo; + auto notifier = bind(&KvStoreCorruptInfo::CorruptCallBack, &corruptInfo, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + g_mgr.SetKvStoreCorruptionHandler(notifier); + + /** + * @tc.steps: step5. Put data into the kvStore. + * @tc.expected: step5. The corrupt handler is called twice. + */ + ASSERT_NE(PutDataIntoDatabase(g_kvDelegatePtr, nullptr), OK); + ASSERT_NE(PutDataIntoDatabase(nullptr, g_kvNbDelegatePtr), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_CALLBACK_TIME)); + EXPECT_TRUE(corruptInfo.GetDatabaseInfoSize() >= 2UL); // 2 more callback + EXPECT_EQ(corruptInfo.IsDataBaseCorrupted(APP_NAME, USER_NAME, "corrupt4"), true); + EXPECT_EQ(corruptInfo.IsDataBaseCorrupted(APP_NAME, USER_NAME, "corrupt5"), true); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_kvNbDelegatePtr = nullptr; + EXPECT_EQ(g_mgr.DeleteKvStore("corrupt4"), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("corrupt5"), OK); +} + +/** + * @tc.name: DatabaseCorruptionHandleTest004 + * @tc.desc: Test the DeleteKvStore Interface and check whether the database files can be removed. + * @tc.type: FUNC + * @tc.require: AR000D487C SR000D4878 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDatabaseCorruptTest, DatabaseCorruptionHandleTest004, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the kvStore. + * @tc.steps: step2. Put data into the store. + * @tc.steps: step3. Close the store. + */ + CipherPassword passwd; + Key randomPassword; + DistributedDBToolsUnitTest::GetRandomKeyValue(randomPassword, PASSWD_SIZE); + int errCode = passwd.SetValue(randomPassword.data(), randomPassword.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + KvStoreDelegate::Option option = {true, true, false, CipherType::DEFAULT, passwd}; + KvStoreNbDelegate::Option nbOption = {true, false, false, CipherType::DEFAULT, passwd}; + + g_mgr.GetKvStore("corrupt6", option, g_kvDelegateCallback); + g_mgr.GetKvStore("corrupt7", nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + ASSERT_EQ(PutDataIntoDatabase(g_kvDelegatePtr, g_kvNbDelegatePtr), OK); + + /** + * @tc.steps: step4. Modify the database file. + */ + std::string filePath = GetKvStoreDirectory("corrupt6", DBConstant::DB_TYPE_LOCAL); // local database. + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath); + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath + "-wal"); + filePath = GetKvStoreDirectory("corrupt7", DBConstant::DB_TYPE_SINGLE_VER); // single ver database. + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath); + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath + "-wal"); + KvStoreCorruptInfo corruptInfo; + KvStoreCorruptInfo corruptInfoNew; + auto notifier = bind(&KvStoreCorruptInfo::CorruptCallBack, &corruptInfo, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + g_mgr.SetKvStoreCorruptionHandler(notifier); + auto notifierNew = bind(&KvStoreCorruptInfo::CorruptCallBack, &corruptInfoNew, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3); + g_mgr.SetKvStoreCorruptionHandler(notifierNew); + /** + * @tc.steps: step5. Re-obtain the kvStore. + * @tc.expected: step5. Returns null kvstore. + */ + ASSERT_NE(PutDataIntoDatabase(g_kvDelegatePtr, nullptr), OK); + ASSERT_NE(PutDataIntoDatabase(nullptr, g_kvNbDelegatePtr), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_CALLBACK_TIME)); + EXPECT_EQ(corruptInfo.GetDatabaseInfoSize(), 0UL); // no callback + EXPECT_TRUE(corruptInfoNew.GetDatabaseInfoSize() >= 2UL); // 2 more callback + EXPECT_EQ(corruptInfoNew.IsDataBaseCorrupted(APP_NAME, USER_NAME, "corrupt6"), true); + EXPECT_EQ(corruptInfoNew.IsDataBaseCorrupted(APP_NAME, USER_NAME, "corrupt7"), true); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_kvNbDelegatePtr = nullptr; + EXPECT_EQ(g_mgr.DeleteKvStore("corrupt6"), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("corrupt7"), OK); +} + +namespace { +const uint32_t MODIFY_SIZE = 12; // Modify size is 12 * sizeof(uint32_t); +const uint32_t MODIFY_VALUE = 0xF3F3F3F3; // random value, make sure to destroy the page header. +void TestDatabaseIntegrityCheckOption(const std::string &storeId, bool isEncrypted) +{ + KvStoreNbDelegate::Option nbOption = {true, false, isEncrypted}; + nbOption.isNeedIntegrityCheck = false; + nbOption.isNeedRmCorruptedDb = false; + if (isEncrypted) { + Key randPassword; + DistributedDBToolsUnitTest::GetRandomKeyValue(randPassword, PASSWD_SIZE); + ASSERT_EQ(nbOption.passwd.SetValue(randPassword.data(), randPassword.size()), CipherPassword::ErrorCode::OK); + } + auto filePath = GetKvStoreDirectory(storeId, DBConstant::DB_TYPE_SINGLE_VER); + g_mgr.GetKvStore(storeId, nbOption, g_kvNbDelegateCallback); + ASSERT_EQ(g_kvNbDelegateStatus, OK); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + ASSERT_EQ(PutDataIntoDatabase(nullptr, g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + /** + * @tc.steps: step1. Modify the database file header to destroy the header and call the GetKvStore. + * @tc.expected: step1. Returns null kv store and the errCode is INVALID_PASSWD_OR_CORRUPTED_DB. + */ + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath, 0, MODIFY_SIZE, MODIFY_VALUE); + g_mgr.GetKvStore(storeId, nbOption, g_kvNbDelegateCallback); + ASSERT_EQ(g_kvNbDelegateStatus, INVALID_PASSWD_OR_CORRUPTED_DB); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + + /** + * @tc.steps: step2. call the GetKvStore with integrity check option is true. + * @tc.expected: step2. Returns null kv store and the errCode is INVALID_PASSWD_OR_CORRUPTED_DB. + */ + nbOption.isNeedIntegrityCheck = true; + g_mgr.GetKvStore(storeId, nbOption, g_kvNbDelegateCallback); + ASSERT_EQ(g_kvNbDelegateStatus, INVALID_PASSWD_OR_CORRUPTED_DB); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + + /** + * @tc.steps: step3. call the GetKvStore with remove corrupted database option is true. + * @tc.expected: step3. Returns non-null kv store and the errCode is OK. + */ + nbOption.isNeedIntegrityCheck = false; + nbOption.isNeedRmCorruptedDb = true; + g_mgr.GetKvStore(storeId, nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + + ASSERT_EQ(PutDataIntoDatabase(nullptr, g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + /** + * @tc.steps: step4. Modify the second page of the database file and Get the kv store. + * @tc.expected: step4. Returns non-null kv store and the errCode is OK(GetKvStore skip the check of the page 2). + */ + size_t filePos = isEncrypted ? 1024 : 4096; // 1024 and 4096 is the page size. + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath, filePos, MODIFY_SIZE, MODIFY_VALUE); + nbOption.isNeedRmCorruptedDb = false; + g_mgr.GetKvStore(storeId, nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + /** + * @tc.steps: step5. Get the kv store with check the integrity. + * @tc.expected: step5. Returns null kv store and the errCode is INVALID_PASSWD_OR_CORRUPTED_DB. + */ + nbOption.isNeedIntegrityCheck = true; + g_mgr.GetKvStore(storeId, nbOption, g_kvNbDelegateCallback); + ASSERT_EQ(g_kvNbDelegateStatus, INVALID_PASSWD_OR_CORRUPTED_DB); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + + /** + * @tc.steps: step5. Get the kv store with check the integrity and the rm corrupted database option. + * @tc.expected: step5. Returns non-null kv store and the errCode is OK. + */ + nbOption.isNeedRmCorruptedDb = true; + g_mgr.GetKvStore(storeId, nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); +} +} + +/** + * @tc.name: DatabaseIntegrityCheck001 + * @tc.desc: Test the integrity check option. + * @tc.type: FUNC + * @tc.require: AR000D487C SR000D4878 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDatabaseCorruptTest, DatabaseIntegrityCheck001, TestSize.Level2) +{ + LOGI("Begin to check the unencrypted database"); + TestDatabaseIntegrityCheckOption("integrity_check001", false); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + LOGI("Begin to check the encrypted database"); + TestDatabaseIntegrityCheckOption("integrity_check002", true); +} + +/** + * @tc.name: DatabaseIntegrityCheck002 + * @tc.desc: Test the integrity check interface. + * @tc.type: FUNC + * @tc.require: AR000D487C SR000D4878 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDatabaseCorruptTest, DatabaseIntegrityCheck002, TestSize.Level1) +{ + CipherPassword passwd; + KvStoreNbDelegate::Option nbOption = {true, false, false, CipherType::DEFAULT, passwd}; + nbOption.isNeedIntegrityCheck = true; + nbOption.isNeedRmCorruptedDb = true; + auto filePath = GetKvStoreDirectory("integrity021", DBConstant::DB_TYPE_SINGLE_VER); + for (uint32_t i = 1; i < 4; i++) { + LOGI("%u th test!", i); + g_mgr.GetKvStore("integrity021", nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + ASSERT_EQ(g_kvNbDelegatePtr->CheckIntegrity(), OK); + ASSERT_EQ(PutDataIntoDatabase(nullptr, g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath, i * 4096, MODIFY_SIZE, MODIFY_VALUE); // page size 4096 + g_mgr.GetKvStore("integrity021", nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->CheckIntegrity(), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("integrity021"), OK); + } + LOGI("Begin the encrypted check"); + Key randomPassword; + DistributedDBToolsUnitTest::GetRandomKeyValue(randomPassword, PASSWD_SIZE); + int errCode = passwd.SetValue(randomPassword.data(), randomPassword.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + nbOption = {true, false, true, CipherType::DEFAULT, passwd}; + nbOption.isNeedIntegrityCheck = true; + nbOption.isNeedRmCorruptedDb = true; + filePath = GetKvStoreDirectory("integrity022", DBConstant::DB_TYPE_SINGLE_VER); + for (uint32_t i = 1; i < 4; i++) { + LOGI("%u th test the encrypted database!", i); + g_mgr.GetKvStore("integrity022", nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + ASSERT_EQ(g_kvNbDelegatePtr->CheckIntegrity(), OK); + ASSERT_EQ(PutDataIntoDatabase(nullptr, g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + DistributedDBToolsUnitTest::ModifyDatabaseFile(filePath, i * 1024, MODIFY_SIZE, MODIFY_VALUE); // page size 1024 + g_mgr.GetKvStore("integrity022", nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->CheckIntegrity(), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("integrity022"), OK); + } +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_database_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_database_test.cpp new file mode 100644 index 00000000..95b076c2 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_database_test.cpp @@ -0,0 +1,1435 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "kvdb_manager.h" +#include "platform_specific.h" +#include "process_system_api_adapter_impl.h" +#include "runtime_context.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + enum { + SCHEMA_TYPE1 = 1, + SCHEMA_TYPE2 + }; + static int g_conflictCount = 0; + // define some variables to init a KvStoreDelegateManager object. + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + string g_testDir; + KvStoreConfig g_config; + + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); +#ifndef OMIT_JSON + const int PASSWD_LEN = 10; + const int PASSWD_VAL = 45; + void GenerateValidSchemaString(std::string &string, int num = SCHEMA_TYPE1) + { + switch (num) { + case SCHEMA_TYPE1: + string = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":{" + "\"field_name3\":\"INTEGER, NOT NULL\"," + "\"field_name4\":\"LONG, DEFAULT 100\"," + "\"field_name5\":\"DOUBLE, NOT NULL, DEFAULT 3.14\"," + "\"field_name6\":\"STRING, NOT NULL, DEFAULT '3.1415'\"," + "\"field_name7\":[]," + "\"field_name8\":{}" + "}" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\", \"$.field_name2.field_name6\"]}"; + break; + case SCHEMA_TYPE2: + string = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"LONG, DEFAULT 100\"," + "\"field_name2\":{" + "\"field_name3\":\"INTEGER, NOT NULL\"," + "\"field_name4\":\"LONG, DEFAULT 100\"," + "\"field_name5\":\"DOUBLE, NOT NULL, DEFAULT 3.14\"," + "\"field_name6\":\"STRING, NOT NULL, DEFAULT '3.1415'\"" + "}" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\", \"$.field_name2.field_name6\"]}"; + break; + default: + return; + } + } + + void GenerateInvalidSchemaString(std::string &string) + { + string = "123"; + } + + void GenerateEmptySchemaString(std::string &string) + { + string.clear(); + } + + int WriteValidDataIntoKvStore() + { + return OK; + } + + void OpenOpenedKvstoreWithSchema(const std::string &storeId, bool isEncrypt) + { + /** + * @tc.steps: step1. create a new db(non-memory, encrypt), with schema; + * @tc.expected: step1. Returns a non-null kvstore and error code is OK. + */ + KvStoreNbDelegate::Option option = {true, false, isEncrypt}; + if (isEncrypt) { + CipherPassword passwd; + vector passwdBuffer(PASSWD_LEN, PASSWD_VAL); + passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + option.passwd = passwd; + } + GenerateValidSchemaString(option.schema); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + KvStoreNbDelegate *kvNbDelegatePtr1 = g_kvNbDelegatePtr; + /** + * @tc.steps: step2. open an opened db, with same schema; + * @tc.expected: step2. Returns a non-null kvstore and error code is OK. + */ + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + KvStoreNbDelegate *kvNbDelegatePtr2 = g_kvNbDelegatePtr; + /** + * @tc.steps: step3. open an opened db, with valid but different schema; + * @tc.expected: step3. Returns a null kvstore and error code is SCHEMA_MISMATCH. + */ + GenerateValidSchemaString(option.schema, SCHEMA_TYPE2); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == SCHEMA_MISMATCH); + + /** + * @tc.steps: step4. open an opened db, with invalid schema; + * @tc.expected: step4. Returns a null kvstore and error code is INVALID_SCHEMA. + */ + GenerateInvalidSchemaString(option.schema); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == INVALID_SCHEMA); + + /** + * @tc.steps: step5. open an opened db, with empty schema; + * @tc.expected: step5. Returns a null kvstore and error code is INVALID_SCHEMA. + */ + std::string emptySchema; + option.schema = emptySchema; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == SCHEMA_MISMATCH); + + EXPECT_EQ(g_mgr.CloseKvStore(kvNbDelegatePtr1), OK); + EXPECT_EQ(g_mgr.CloseKvStore(kvNbDelegatePtr2), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore(storeId) == OK); + } + + void OpenClosedSchemaKvStore(const std::string &storeId, bool isEncrypt, std::string &inSchema) + { + /** + * @tc.steps: step1. create a new db(non-memory), with input schema; + * @tc.expected: step1. Returns a non-null kvstore and error code is OK. + */ + KvStoreNbDelegate::Option option = {true, false, isEncrypt}; + option.schema = inSchema; + if (isEncrypt) { + CipherPassword passwd; + vector passwdBuffer(PASSWD_LEN, PASSWD_VAL); + passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + option.passwd = passwd; + } + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(WriteValidDataIntoKvStore() == OK); + /** + * @tc.steps: step2. close the created kvstore; + * @tc.expected: step2. Return OK. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + + /** + * @tc.steps: step3. reopen the kvstore with same schema; + * @tc.expected: step3. Return OK. + */ + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(WriteValidDataIntoKvStore() == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + + /** + * @tc.steps: step4. reopen the kvstore with valid schema, but the schema is not equal to inSchema; + * @tc.expected: step4. Return a null kvstore and retCode is SCHEMA_MISMATCH. + */ + GenerateValidSchemaString(option.schema, SCHEMA_TYPE2); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == SCHEMA_MISMATCH); + /** + * @tc.steps: step5. reopen the kvstore with invalid schema; + * @tc.expected: step5. Return a null kvstore and retCode is INVALID_SCHEMA. + */ + GenerateInvalidSchemaString(option.schema); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == INVALID_SCHEMA); + /** + * @tc.steps: step6. reopen the kvstore with empty schema; + * @tc.expected: step6. Return a read-only kvstore and retCode is READ_ONLY. + */ + GenerateEmptySchemaString(option.schema); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + // here should return READ_ONLY + EXPECT_TRUE(WriteValidDataIntoKvStore() == OK); + KvStoreNbDelegate *kvNbDelegatePtr1 = g_kvNbDelegatePtr; + + // Open another kvstore with empty schema + GenerateEmptySchemaString(option.schema); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + KvStoreNbDelegate *kvNbDelegatePtr2 = g_kvNbDelegatePtr; + // here should return READ_ONLY + EXPECT_TRUE(WriteValidDataIntoKvStore() == OK); + + // Open another kvstore with origin schema + option.schema = inSchema; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == SCHEMA_MISMATCH); + + EXPECT_EQ(g_mgr.CloseKvStore(kvNbDelegatePtr1), OK); + EXPECT_EQ(g_mgr.CloseKvStore(kvNbDelegatePtr2), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore(storeId) == OK); + } + + void OpenClosedNormalKvStore(const std::string &storeId, bool isEncrypt) + { + /** + * @tc.steps: step1. create a new db(non-memory), without schema; + * @tc.expected: step1. Returns a non-null kvstore and error code is OK. + */ + KvStoreNbDelegate::Option option = {true, false, isEncrypt}; + if (isEncrypt) { + CipherPassword passwd; + vector passwdBuffer(PASSWD_LEN, PASSWD_VAL); + passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + option.passwd = passwd; + } + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(WriteValidDataIntoKvStore() == OK); + /** + * @tc.steps: step2. close the created kvstore; + * @tc.expected: step2. Return OK. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + + /** + * @tc.steps: step3. reopen the kvstore with empty schema; + * @tc.expected: step3. Return a kvstore and retCode is OK. + */ + GenerateEmptySchemaString(option.schema); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(WriteValidDataIntoKvStore() == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + /** + * @tc.steps: step4. reopen the kvstore with valid schema; + * @tc.expected: step4. Return OK. + */ + GenerateValidSchemaString(option.schema); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(WriteValidDataIntoKvStore() == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + + /** + * @tc.steps: step5. reopen the kvstore with invalid schema; + * @tc.expected: step5. Return a null kvstore and retCode is SCHEMA_MISMATCH. + */ + GenerateInvalidSchemaString(option.schema); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == INVALID_SCHEMA); + EXPECT_TRUE(g_mgr.DeleteKvStore(storeId) == OK); + } +#endif +} + +class DistributedDBInterfacesDatabaseTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesDatabaseTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); +} + +void DistributedDBInterfacesDatabaseTest::TearDownTestCase(void) +{ + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); +} + +void DistributedDBInterfacesDatabaseTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_kvDelegateStatus = INVALID_ARGS; + g_kvDelegatePtr = nullptr; +} + +void DistributedDBInterfacesDatabaseTest::TearDown(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +/** + * @tc.name: GetKvStore001 + * @tc.desc: Get kv store through different parameters. + * @tc.type: FUNC + * @tc.require: AR000CQDV4 AR000CQS3P + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, GetKvStore001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter the normal storId, createIfNecessary(true) and isLocal(true). + * @tc.steps: step2. Close the kvStore through the CloseKvStore interface of the delegate manager. + * @tc.expected: step1. Returns a non-null kvstore. + * @tc.expected: step2. Returns OK. + */ + KvStoreDelegate::Option option = {true, true, false}; + g_mgr.GetKvStore("distributed_db_test1", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + /** + * @tc.steps: step3. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter the normal storId, createIfNecessary(true) and isLocal(false). + * @tc.steps: step4. Close the kvStore through the CloseKvStore interface of the delegate manager. + * @tc.expected: step3. Returns a non-null kvstore. + * @tc.expected: step4. Returns OK. + */ + option = {true, false, false}; + g_mgr.GetKvStore("distributed_db_test2", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + /** + * @tc.steps: step5. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter the normal storId, createIfNecessary(false) and isLocal(true). + * @tc.expected: step5. Returns a non-null kvstore and error code is ERROR. + */ + option = {false, true, false}; + g_mgr.GetKvStore("distributed_db_test3", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + EXPECT_NE(g_kvDelegateStatus, OK); + + /** + * @tc.steps: step6. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter the normal storId, createIfNecessary(false) and isLocal(false). + * @tc.expected: step6. Returns a non-null kvstore and error code is ERROR. + */ + option = {false, false, false}; + g_mgr.GetKvStore("distributed_db_test4", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + EXPECT_NE(g_kvDelegateStatus, OK); + + /** + * @tc.steps: step7. Obtain the kvStore through the GetKvStore interface of the delegate manager + * which is initialized with the empty appid. + * @tc.expected: step7. Returns a non-null kvstore and error code is INVALID_ARGS. + */ + KvStoreDelegateManager invalidMgrFirst("", USER_ID); + invalidMgrFirst.SetKvStoreConfig(g_config); + option = {true, true, false}; + invalidMgrFirst.GetKvStore("distributed_db_test5", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == INVALID_ARGS); + + /** + * @tc.steps: step8. Obtain the kvStore through the GetKvStore interface of the delegate manager + * which is initialized with the empty userid. + * @tc.expected: step8. Returns a non-null kvstore and error code is INVALID_ARGS. + */ + KvStoreDelegateManager invalidMgrSecond(APP_ID, ""); + invalidMgrSecond.SetKvStoreConfig(g_config); + invalidMgrSecond.GetKvStore("distributed_db_test6", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == INVALID_ARGS); + + /** + * @tc.steps: step9. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter the empty storId, createIfNecessary(true) and isLocal(true). + * @tc.expected: step9. Returns a non-null kvstore and error code is INVALID_ARGS. + */ + g_mgr.GetKvStore("", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == INVALID_ARGS); + + /** + * @tc.steps: step10. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter the invalid storId, createIfNecessary(true) and isLocal(true). + * @tc.expected: step10. Returns a non-null kvstore and error code is INVALID_ARGS. + */ + g_mgr.GetKvStore("$@.test", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == INVALID_ARGS); + + /** + * @tc.steps: step11. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter: all alphabet string storId, createIfNecessary(true) and isLocal(true). + * @tc.expected: step11. Returns a non-null kvstore and error code is OK. + */ + g_mgr.GetKvStore("TEST", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + /** + * @tc.steps: step12. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter: digital string storId, createIfNecessary(true) and isLocal(true). + * @tc.expected: step12. Returns a non-null kvstore and error code is OK. + */ + g_mgr.GetKvStore("123", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + /** + * @tc.steps: step13. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parmater: digital and alphabet combined string storId, createIfNecessary(true) and isLocal(true). + * @tc.expected: step13. Returns a non-null kvstore and error code is OK. + */ + g_mgr.GetKvStore("TEST_test_123", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); +} + +/** + * @tc.name: GetKvStore002 + * @tc.desc: Get kv store through different parameters for the same storeID. + * @tc.type: FUNC + * @tc.require: AR000CQDV5 AR000CQS3P + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, GetKvStore002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter createIfNecessary(true) and isLocal(true). + * @tc.expected: step1. Returns a non-null kvstore and error code is OK. + */ + CipherPassword passwd; + KvStoreDelegate::Option option = {true, true, false}; + g_mgr.GetKvStore("distributed_getkvstore_002", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + /** + * @tc.steps: step2. Re-Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter createIfNecessary(true) and isLocal(false). + * @tc.expected: step2. Returns a non-null kvstore and error code is OK. + */ + option.localOnly = false; + g_mgr.GetKvStore("distributed_getkvstore_002", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + /** + * @tc.steps: step3. Re-Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter createIfNecessary(false) and isLocal(true). + * @tc.expected: step3. Returns a non-null kvstore and error code is OK. + */ + option = {false, true, false}; + g_mgr.GetKvStore("distributed_getkvstore_002", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + /** + * @tc.steps: step4. Re-Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter createIfNecessary(false) and isLocal(false). + * @tc.expected: step4. Returns a non-null kvstore and error code is OK. + */ + option = {false, false, false}; + g_mgr.GetKvStore("distributed_getkvstore_002", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + /** + * @tc.steps: step5. Re-Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter createIfNecessary(false) and isLocal(false). + * @tc.expected: step5. Returns a non-null kvstore and error code is OK. + */ + option = {true, true, false}; + g_mgr.GetKvStore("distributed_getkvstore_002", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + string retStoreId = g_kvDelegatePtr->GetStoreId(); + EXPECT_TRUE(retStoreId.compare("distributed_getkvstore_002") == 0); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); +} + +/** + * @tc.name: GetKvStore003 + * @tc.desc: Get kv store through different SecurityOption, abnormal or normal. + * @tc.type: FUNC + * @tc.require: AR000EV1G2 + * @tc.author: liuwenkai + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, GetKvStore003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter secOption(abnormal). + * @tc.expected: step1. Returns a null kvstore and error code is not OK. + */ + std::shared_ptr g_adapter = std::make_shared(); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(g_adapter); + KvStoreNbDelegate::Option option = {true, false, false}; + int abnormalNum = -100; + option.secOption.securityLabel = abnormalNum; + option.secOption.securityFlag = abnormalNum; + g_mgr.GetKvStore("distributed_getkvstore_003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus != OK); + + /** + * @tc.steps: step2. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter secOption(normal). + * @tc.expected: step2. Returns a non-null kvstore and error code is OK. + */ + option.secOption.securityLabel = S3; + option.secOption.securityFlag = 0; + g_mgr.GetKvStore("distributed_getkvstore_003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + KvStoreNbDelegate *kvNbDelegatePtr1 = g_kvNbDelegatePtr; + + /** + * @tc.steps: step3. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter secOption(normal but not same as last). + * @tc.expected: step3. Returns a null kvstore and error code is not OK. + */ + option.secOption.securityLabel = S3; + option.secOption.securityFlag = 1; + g_mgr.GetKvStore("distributed_getkvstore_003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus != OK); + + /** + * @tc.steps: step4. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter secOption(normal and same as last). + * @tc.expected: step4. Returns a non-null kvstore and error code is OK. + */ + option.secOption.securityLabel = S3; + option.secOption.securityFlag = 0; + g_mgr.GetKvStore("distributed_getkvstore_003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(kvNbDelegatePtr1), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore("distributed_getkvstore_003") == OK); +} + +static void NotifierCallback(const KvStoreNbConflictData &data) +{ + LOGE("Trigger conflict callback!"); + g_conflictCount++; +} + +/** + * @tc.name: GetKvStore004 + * @tc.desc: Get kv store parameters with Observer and Notifier, then trigger callback. + * @tc.type: FUNC + * @tc.require: AR000EV1G2 + * @tc.author: liuwenkai + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, GetKvStore004, TestSize.Level1) +{ + /** + * @tc.steps: step1. Obtain the kvStore through the GetKvStore interface of the delegate manager + * using the parameter observer, notifier, key. + * @tc.expected: step1. Returns a non-null kvstore and error code is OK. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_NE(observer, nullptr); + Key key; + Value value1; + Value value2; + key.push_back(1); + value1.push_back(1); + value2.push_back(2); + option.conflictType = CONFLICT_NATIVE_ALL; + option.notifier = NotifierCallback; + option.key = key; + option.observer = observer; + option.mode = OBSERVER_CHANGES_NATIVE; + g_conflictCount = 0; + int sleepTime = 100; + g_mgr.GetKvStore("distributed_getkvstore_004", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step2. Put(k1,v1) to db and check the observer info. + * @tc.expected: step2. Put successfully and trigger notifier callback. + */ + EXPECT_TRUE(g_kvNbDelegatePtr->Put(key, value1) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime)); + LOGI("observer count:%lu", observer->GetCallCount()); + EXPECT_TRUE(observer->GetCallCount() == 1); + + /** + * @tc.steps: step3. put(k1,v2) to db and check the observer info. + * @tc.expected: step3. put successfully and trigger conflict callback. + */ + EXPECT_TRUE(g_kvNbDelegatePtr->Put(key, value2) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime)); + LOGI("observer count:%lu", observer->GetCallCount()); + EXPECT_TRUE(observer->GetCallCount() == 2); + LOGI("call conflictNotifier count:%d, 1 means trigger success.", g_conflictCount); + EXPECT_EQ(g_conflictCount, 1); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + delete observer; + observer = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore("distributed_getkvstore_004") == OK); +} + +/** + * @tc.name: CloseKvStore001 + * @tc.desc: Test the CloseKvStore Interface and check whether the database file can be closed. + * @tc.type: FUNC + * @tc.require: AR000CQDV6 AR000CQS3P + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, CloseKvStore001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Obtain the kvStore of the non-existed database through the GetKvStore interface of + * the delegate manager using the parameter createdIfNecessary(true) + * @tc.steps: step2. Close the valid kvStore. + * @tc.expected: step1. Returns a non-null kvstore and error code is OK. + * @tc.expected: step2. Returns OK. + */ + CipherPassword passwd; + KvStoreDelegate::Option option = {true, true, false}; + g_mgr.GetKvStore("CloseKvStore_001", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + + /** + * @tc.steps: step3. Obtain the kvStore of the existed database through the GetKvStore interface of + * the delegate manager using the parameter createIfNecessary(false) + * @tc.steps: step4. Close the valid kvStore. + * @tc.expected: step3. Returns a non-null kvstore and error code is OK. + * @tc.expected: step4. Returns OK. + */ + option = {false, true, false}; + g_mgr.GetKvStore("CloseKvStore_001", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + + /** + * @tc.steps: step5. Close the invalid kvStore which is nullptr. + * @tc.expected: step5. Returns INVALID_ARGS. + */ + KvStoreDelegate *storeDelegate = nullptr; + EXPECT_EQ(g_mgr.CloseKvStore(storeDelegate), INVALID_ARGS); +} + +/** + * @tc.name: DeleteKvStore001 + * @tc.desc: Test the DeleteKvStore Interface and check whether the database files can be removed. + * @tc.type: FUNC + * @tc.require: AR000C2F0C AR000CQDV7 + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, DeleteKvStore001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Obtain the kvStore through the GetKvStore interface of + * the delegate manager using the parameter createIfNecessary(true) + * @tc.steps: step2. Close the kvStore. + * @tc.expected: step1. Returns a non-null kvstore and error code is OK. + * @tc.expected: step2. Returns OK. + */ + CipherPassword passwd; + KvStoreDelegate::Option option = {true, true, false}; + const std::string storeId("DeleteKvStore_001"); + g_mgr.GetKvStore(storeId, option, g_kvDelegateCallback); + std::string origIdentifierName = USER_ID + "-" + APP_ID + "-" + storeId; + std::string hashIdentifierName = DBCommon::TransferHashString(origIdentifierName); + std::string identifierName = DBCommon::TransferStringToHex(hashIdentifierName); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + + /** + * @tc.steps: step3. Check the database file + * @tc.expected: step3. the database file are existed. + */ + string dbFileName = g_testDir + "/" + identifierName + "/local/local.db"; + ifstream dbFile(dbFileName); + EXPECT_TRUE(dbFile); + dbFile.close(); + string walFileName = g_testDir + "/" + identifierName + "/local/local.db-wal"; + fstream walFile(walFileName, fstream::out); + EXPECT_TRUE(walFile.is_open()); + walFile.close(); + string shmFileName = g_testDir + "/" + identifierName + "/local/local.db-shm"; + fstream shmFile(shmFileName, fstream::out); + EXPECT_TRUE(shmFile.is_open()); + shmFile.close(); + + std::string dataBaseDir = g_testDir + "/" + identifierName; + EXPECT_GE(access(dataBaseDir.c_str(), F_OK), 0); + + /** + * @tc.steps: step4. Delete the kvStore through the DeleteKvStore interface of + * the delegate manager + * @tc.steps: step5. Check the database files and the storage paths. + * @tc.expected: step4. Returns OK. + * @tc.expected: step5. The database files and the storage paths are not existed. + */ + EXPECT_TRUE(g_mgr.DeleteKvStore(storeId) == OK); + ifstream dbFileAfter(dbFileName); + ifstream walFileAfter(walFileName); + ifstream shmFileAfter(shmFileName); + EXPECT_FALSE(dbFileAfter); + EXPECT_FALSE(walFileAfter); + EXPECT_FALSE(shmFileAfter); + ASSERT_EQ(OS::CheckPathExistence(dataBaseDir), false); + std::string storeIdOnlyIdentifier = DBCommon::TransferHashString(storeId); + std::string storeIdOnlyIdentifierName = DBCommon::TransferStringToHex(storeIdOnlyIdentifier); + std::string storeIdOnlyIdDataBaseDir = g_testDir + "/" + storeIdOnlyIdentifierName; + ASSERT_EQ(OS::CheckPathExistence(storeIdOnlyIdDataBaseDir), false); + + /** + * @tc.steps: step6. Re-Delete the kvStore through the DeleteKvStore interface of + * the delegate manager + * @tc.expected: step6. Returns NOT_FOUND. + */ + EXPECT_TRUE(g_mgr.DeleteKvStore(storeId) == NOT_FOUND); +} + +/** + * @tc.name: RepeatCloseKvStore001 + * @tc.desc: Close the kv store repeatedly and check the database. + * @tc.type: FUNC + * @tc.require: AR000C2F0C AR000CQDV7 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, RepeatCloseKvStore001, TestSize.Level2) +{ + /** + * @tc.steps: step1. Obtain the kvStore through the GetKvStore interface of + * the delegate manager using the parameter createIfNecessary(true) + * @tc.expected: step1. Returns a non-null kvstore and error code is OK. + */ + CipherPassword passwd; + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("RepeatCloseKvStore_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + static const size_t totalSize = 50; + + /** + * @tc.steps: step2. Put into the database some data. + * @tc.expected: step2. Put returns OK. + */ + std::vector keys; + for (size_t i = 0; i < totalSize; i++) { + Entry entry; + DistributedDBToolsUnitTest::GetRandomKeyValue(entry.key, static_cast(i + 1)); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry.value); + EXPECT_EQ(g_kvNbDelegatePtr->Put(entry.key, entry.value), OK); + keys.push_back(entry.key); + } + + /** + * @tc.steps: step3. Delete the data from the database, and close the database, reopen the database and + * get the data. + * @tc.expected: step3. Delete returns OK, Close returns OK and Get returns NOT_FOUND. + */ + for (size_t i = 0; i < keys.size(); i++) { + Value value; + EXPECT_EQ(g_kvNbDelegatePtr->Delete(keys[i]), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Get(keys[i], value), NOT_FOUND); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_mgr.GetKvStore("RepeatCloseKvStore_001", option, g_kvNbDelegateCallback); + EXPECT_EQ(g_kvNbDelegatePtr->Get(keys[i], value), NOT_FOUND); + } + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + /** + * @tc.steps: step4. Delete the kvstore created before. + * @tc.expected: step4. Delete returns OK. + */ + EXPECT_EQ(g_mgr.DeleteKvStore("RepeatCloseKvStore_001"), OK); +} +#ifndef OMIT_JSON +/** + * @tc.name: CreatKvStoreWithSchema001 + * @tc.desc: Create non-memory KvStore with schema, check if create success. + * @tc.type: FUNC + * @tc.require: AR000DR9K2 + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, CreatKvStoreWithSchema001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a new db(non-memory, non-encrypt), with valid schema; + * @tc.expected: step1. Returns a non-null kvstore and error code is OK. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + GenerateValidSchemaString(option.schema); + g_mgr.GetKvStore("CreatKvStoreWithSchema_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore("CreatKvStoreWithSchema_001") == OK); + /** + * @tc.steps: step2. create a new db(non-memory, non-encrypt), with invalid schema; + * @tc.expected: step2. Returns null kvstore and error code is INVALID_SCHEMA. + */ + option = {true, false, false}; + GenerateInvalidSchemaString(option.schema); + g_mgr.GetKvStore("CreatKvStoreWithSchema_001_invalid_schema", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == INVALID_SCHEMA); + EXPECT_TRUE(g_mgr.DeleteKvStore("CreatKvStoreWithSchema_001_invalid_schema") == NOT_FOUND); +#ifndef OMIT_ENCRYPT + /** + * @tc.steps: step3. create a new db(non-memory, encrypt), with valid schema; + * @tc.expected: step3. Returns a non-null kvstore and error code is OK. + */ + CipherPassword passwd; + vector passwdBuffer(10, 45); + passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + option = {true, false, true}; + GenerateValidSchemaString(option.schema); + option.passwd = passwd; + g_mgr.GetKvStore("CreatKvStoreWithSchema_001_002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore("CreatKvStoreWithSchema_001_002") == OK); + + /** + * @tc.steps: step4. create a new db(non-memory, non-encrypt), with invalid schema; + * @tc.expected: step2. Returns null kvstore and error code is INVALID_SCHEMA. + */ + option = {true, false, false}; + GenerateInvalidSchemaString(option.schema); + g_mgr.GetKvStore("CreatKvStoreWithSchema_002_invalid_schema", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == INVALID_SCHEMA); + EXPECT_TRUE(g_mgr.DeleteKvStore("CreatKvStoreWithSchema_002_invalid_schema") == NOT_FOUND); +#endif +} + +/** + * @tc.name: CreatKvStoreWithSchema002 + * @tc.desc: Create memory KvStore with schema, check if create success. + * @tc.type: FUNC + * @tc.require: AR000DR9K2 + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, CreatKvStoreWithSchema002, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a new db(memory, non-encrypt), with valid schema; + * @tc.expected: step1. Returns a null kvstore and error code is NOT_SUPPORT. + */ + KvStoreNbDelegate::Option option = {true, true, false}; + GenerateValidSchemaString(option.schema); + g_mgr.GetKvStore("CreatKvStoreWithSchema_002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == NOT_SUPPORT); + + /** + * @tc.steps: step2. create a new db(memory, non-encrypt), with invalid schema; + * @tc.expected: step2. Returns null kvstore and error code is NOT_SUPPORT. + */ + option = {true, true, false}; + GenerateInvalidSchemaString(option.schema); + g_mgr.GetKvStore("CreatKvStoreWithSchema_002_invalid", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == NOT_SUPPORT); +} +#ifndef OMIT_ENCRYPT +/** + * @tc.name: OpenKvStoreWithSchema001 + * @tc.desc: open an opened kvstore(non-memory, no-schema) with schema, check if open success. + * @tc.type: FUNC + * @tc.require: AR000DR9K2 + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, OpenKvStoreWithSchema001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a new db(non-memory, non-encrypt), without schema; + * @tc.expected: step1. Returns a non-null kvstore and error code is OK. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("OpenKvStoreWithSchema_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + KvStoreNbDelegate *kvNbDelegatePtr = g_kvNbDelegatePtr; + /** + * @tc.steps: step2. open the db with valid schema; + * @tc.expected: step2. Returns a null kvstore and error code is SCHEMA_MISMATCH. + */ + GenerateValidSchemaString(option.schema); + g_mgr.GetKvStore("OpenKvStoreWithSchema_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == SCHEMA_MISMATCH); + + /** + * @tc.steps: step3. open the db with invalid schema; + * @tc.expected: step3. Returns a null kvstore and error code is SCHEMA_MISMATCH. + */ + GenerateInvalidSchemaString(option.schema); + g_mgr.GetKvStore("OpenKvStoreWithSchema_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == INVALID_SCHEMA); + + EXPECT_EQ(g_mgr.CloseKvStore(kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore("OpenKvStoreWithSchema_001") == OK); + /** + * @tc.steps: step4. create a new db(non-memory, encrypt), without schema; + * @tc.expected: step4. Returns a non-null kvstore and error code is OK. + */ + option = {true, false, true}; + CipherPassword passwd; + vector passwdBuffer(10, 45); + passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + option.passwd = passwd; + g_mgr.GetKvStore("OpenKvStoreWithSchema_001_encrypt", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + kvNbDelegatePtr = g_kvNbDelegatePtr; + /** + * @tc.steps: step5. open the db with valid schema; + * @tc.expected: step5. Returns a null kvstore and error code is SCHEMA_MISMATCH. + */ + GenerateValidSchemaString(option.schema); + g_mgr.GetKvStore("OpenKvStoreWithSchema_001_encrypt", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == SCHEMA_MISMATCH); + + /** + * @tc.steps: step6. open the db with invalid schema; + * @tc.expected: step6. Returns a null kvstore and error code is SCHEMA_MISMATCH. + */ + GenerateInvalidSchemaString(option.schema); + g_mgr.GetKvStore("OpenKvStoreWithSchema_001_encrypt", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == INVALID_SCHEMA); + + EXPECT_EQ(g_mgr.CloseKvStore(kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore("OpenKvStoreWithSchema_001_encrypt") == OK); +} +#endif +/** + * @tc.name: OpenKvStoreWithSchema002 + * @tc.desc: open an opened kvstore(non-memory, schema) with schema, check if open success. + * @tc.type: FUNC + * @tc.require: AR000DR9K2 + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, OpenKvStoreWithSchema002, TestSize.Level1) +{ +#ifndef OMIT_ENCRYPT + /** + * @tc.steps: step1. open an opened kvstore(non-memory, non-encrypt), with different schemas; + */ + OpenOpenedKvstoreWithSchema("OpenKvStoreWithSchema_002", true); +#endif + /** + * @tc.steps: step2. open an opened kvstore(non-memory, encrypt), with different schemas; + */ + OpenOpenedKvstoreWithSchema("OpenKvStoreWithSchema_002_encrypt", false); +} + +/** + * @tc.name: OpenKvStoreWithSchema003 + * @tc.desc: open an opened kvstore(memory) with different schemas, check if open success. + * @tc.type: FUNC + * @tc.require: AR000DR9K2 + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, OpenKvStoreWithSchema003, TestSize.Level1) +{ + /** + * @tc.steps: step1. create a new db(memory, non-encrypt), without schema; + * @tc.expected: step1. Returns a non-null kvstore and error code is OK. + */ + KvStoreNbDelegate::Option option = {true, true, false}; + g_mgr.GetKvStore("OpenKvStoreWithSchema_003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + KvStoreNbDelegate *kvNbDelegatePtr1 = g_kvNbDelegatePtr; + /** + * @tc.steps: step2. open a new db(memory, non-encrypt), without schema; + * @tc.expected: step2. Returns a non-null kvstore and error code is OK. + */ + g_mgr.GetKvStore("OpenKvStoreWithSchema_003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + KvStoreNbDelegate *kvNbDelegatePtr2 = g_kvNbDelegatePtr; + /** + * @tc.steps: step3. open a new db(memory, non-encrypt), with valid schema; + * @tc.expected: step3. Returns a null kvstore and error code is NOT_SUPPORT. + */ + GenerateValidSchemaString(option.schema); + g_mgr.GetKvStore("OpenKvStoreWithSchema_003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == NOT_SUPPORT); + + /** + * @tc.steps: step4. open a new db(memory, non-encrypt), with invalid schema; + * @tc.expected: step4. Returns a null kvstore and error code is NOT_SUPPORT. + */ + GenerateInvalidSchemaString(option.schema); + g_mgr.GetKvStore("OpenKvStoreWithSchema_003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == NOT_SUPPORT); + EXPECT_EQ(g_mgr.CloseKvStore(kvNbDelegatePtr1), OK); + EXPECT_EQ(g_mgr.CloseKvStore(kvNbDelegatePtr2), OK); +} + +/** + * @tc.name: OpenKvStoreWithSchema004 + * @tc.desc: open a totally closed schema-kvstore(non-memory) with different schemas, check if open success. + * @tc.type: FUNC + * @tc.require: AR000DR9K2 + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, OpenKvStoreWithSchema004, TestSize.Level1) +{ + /** + * @tc.steps: step1. open a new db(non-memory, non-encrypt), with different schemas; + * @tc.expected: step1. Returns a null kvstore and error code is NOT_SUPPORT. + */ + std::string schema; + GenerateValidSchemaString(schema); + OpenClosedSchemaKvStore("OpenKvStoreWithSchema_004", false, schema); +#ifndef OMIT_ENCRYPT + /** + * @tc.steps: step2. open a new db(non-memory, encrypt), with different schemas; + * @tc.expected: step2. Returns a null kvstore and error code is NOT_SUPPORT. + */ + OpenClosedSchemaKvStore("OpenKvStoreWithSchema_004_encrypt", true, schema); +#endif +} + +/** + * @tc.name: OpenKvStoreWithSchema005 + * @tc.desc: open a totally closed non-schema-kvstore(non-memory) with different schemas, check if open success. + * @tc.type: FUNC + * @tc.require: AR000DR9K2 + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, OpenKvStoreWithSchema005, TestSize.Level1) +{ + /** + * @tc.steps: step1. open a new db(non-memory, non-encrypt, non-schema), with different schemas; + * @tc.expected: step1. Returns a different result. + */ + OpenClosedNormalKvStore("OpenKvStoreWithSchema_005", false); +#ifndef OMIT_ENCRYPT + /** + * @tc.steps: step2. open a new db(non-memory, encrypt, non-schema), with different schemas; + * @tc.expected: step2. Returns a different result. + */ + OpenClosedNormalKvStore("OpenKvStoreWithSchema_005", true); +#endif +} + +/** + * @tc.name: OpenKvStoreWithSchema006 + * @tc.desc: open a memory non-schema-kvstore with different schemas, check if open success. + * @tc.type: FUNC + * @tc.require: AR000DR9K2 + * @tc.author: weifeng + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, OpenKvStoreWithSchema006, TestSize.Level1) +{ + /** + * @tc.steps: step1. open a new db(memory, non-encrypt, non-schema), without schema; + * @tc.expected: step1. Returns OK. + */ + KvStoreNbDelegate::Option option = {true, true, false}; + g_mgr.GetKvStore("OpenKvStoreWithSchema_006", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps: step2. close the kvstore; + * @tc.expected: step2. Returns OK. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + + /** + * @tc.steps: step3. reopen the kvstore without schema; + * @tc.expected: step3. Returns OK. + */ + g_mgr.GetKvStore("OpenKvStoreWithSchema_006", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(WriteValidDataIntoKvStore() == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + + /** + * @tc.steps: step4. reopen the kvstore with valid schema; + * @tc.expected: step4. Returns OK. + */ + GenerateValidSchemaString(option.schema); + g_mgr.GetKvStore("OpenKvStoreWithSchema_006", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == NOT_SUPPORT); + + /** + * @tc.steps: step4. reopen the kvstore with invalid schema; + * @tc.expected: step4. Returns OK. + */ + GenerateInvalidSchemaString(option.schema); + g_mgr.GetKvStore("OpenKvStoreWithSchema_006", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus == NOT_SUPPORT); +} +#endif +/** + * @tc.name: OpenKvStoreWithStoreOnly001 + * @tc.desc: open the kv store with the option that createDirByStoreIdOnly is true. + * @tc.type: FUNC + * @tc.require: AR000DR9K2 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, OpenKvStoreWithStoreOnly001, TestSize.Level1) +{ + /** + * @tc.steps: step1. open the kv store with the option that createDirByStoreIdOnly is true. + * @tc.expected: step1. Returns OK. + */ + KvStoreNbDelegate::Option option; + option.createDirByStoreIdOnly = true; + g_mgr.GetKvStore("StoreOnly001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + auto kvStorePtr = g_kvNbDelegatePtr; + /** + * @tc.steps: step2. open the same store with the option that createDirByStoreIdOnly is false. + * @tc.expected: step2. Returns NOT OK. + */ + option.createDirByStoreIdOnly = false; + g_kvNbDelegatePtr = nullptr; + g_mgr.GetKvStore("StoreOnly001", option, g_kvNbDelegateCallback); + EXPECT_EQ(g_kvDelegateStatus, INVALID_ARGS); + /** + * @tc.steps: step3. close the kvstore and delete the kv store; + * @tc.expected: step3. Returns OK. + */ + EXPECT_EQ(g_mgr.CloseKvStore(kvStorePtr), OK); + kvStorePtr = nullptr; + EXPECT_EQ(g_mgr.DeleteKvStore("StoreOnly001"), OK); +} + +/** + * @tc.name: GetDBWhileOpened001 + * @tc.desc: open the kv store with the option that createDirByStoreIdOnly is true. + * @tc.type: FUNC + * @tc.require: AR000E8S2V + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, GetDBWhileOpened001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the connection. + * @tc.expected: step1. Returns OK. + */ + KvDBProperties property; + std::string storeId = "openTest"; + std::string origId = USER_ID + "-" + APP_ID + "-" + storeId; + std::string identifier = DBCommon::TransferHashString(origId); + std::string hexDir = DBCommon::TransferStringToHex(identifier); + property.SetStringProp(KvDBProperties::IDENTIFIER_DATA, identifier); + property.SetStringProp(KvDBProperties::IDENTIFIER_DIR, hexDir); + property.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + property.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + property.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + property.SetBoolProp(KvDBProperties::MEMORY_MODE, false); + property.SetBoolProp(KvDBProperties::ENCRYPTED_MODE, false); + property.SetBoolProp(KvDBProperties::CREATE_DIR_BY_STORE_ID_ONLY, true); + property.SetStringProp(KvDBProperties::APP_ID, APP_ID); + property.SetStringProp(KvDBProperties::USER_ID, USER_ID); + property.SetStringProp(KvDBProperties::APP_ID, storeId); + + int errCode = E_OK; + auto connection1 = KvDBManager::GetDatabaseConnection(property, errCode, false); + EXPECT_EQ(errCode, E_OK); + /** + * @tc.steps: step2. Get the connection with the para: isNeedIfOpened is false. + * @tc.expected: step2. Returns -E_ALREADY_OPENED. + */ + auto connection2 = KvDBManager::GetDatabaseConnection(property, errCode, false); + EXPECT_EQ(errCode, -E_ALREADY_OPENED); + EXPECT_EQ(connection2, nullptr); + + /** + * @tc.steps: step3. Get the connection with the para: isNeedIfOpened is true. + * @tc.expected: step3. Returns E_OK. + */ + auto connection3 = KvDBManager::GetDatabaseConnection(property, errCode, true); + EXPECT_EQ(errCode, OK); + EXPECT_NE(connection3, nullptr); + + KvDBManager::ReleaseDatabaseConnection(connection1); + KvDBManager::ReleaseDatabaseConnection(connection3); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); +} +namespace { + void OpenCloseDatabase(const std::string &storeId) + { + KvStoreNbDelegate::Option option; + DBStatus status; + KvStoreNbDelegate *delegate = nullptr; + auto nbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(delegate)); + int totalNum = 0; + for (size_t i = 0; i < 100; i++) { // cycle 100 times. + g_mgr.GetKvStore(storeId, option, nbDelegateCallback); + if (delegate != nullptr) { + totalNum++; + } + g_mgr.CloseKvStore(delegate); + delegate = nullptr; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + LOGD("Succeed %d times", totalNum); + } +} + +/** + * @tc.name: FreqOpenCloseDel001 + * @tc.desc: Open/close/delete the kv store concurrently. + * @tc.type: FUNC + * @tc.require: AR000DR9K2 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, FreqOpenCloseDel001, TestSize.Level2) +{ + std::string storeId = "FrqOpenCloseDelete001"; + std::thread t1(OpenCloseDatabase, storeId); + std::thread t2([&]() { + for (int i = 0; i < 10000; i++) { + g_mgr.DeleteKvStore(storeId); + } + }); + t1.join(); + t2.join(); +} + +/** + * @tc.name: FreqOpenClose001 + * @tc.desc: Open and close the kv store concurrently. + * @tc.type: FUNC + * @tc.require: AR000DR9K2 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, FreqOpenClose001, TestSize.Level2) +{ + std::string storeId = "FrqOpenClose001"; + std::thread t1(OpenCloseDatabase, storeId); + std::thread t2(OpenCloseDatabase, storeId); + std::thread t3(OpenCloseDatabase, storeId); + std::thread t4(OpenCloseDatabase, storeId); + t1.join(); + t2.join(); + t3.join(); + t4.join(); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); +} + +/** + * @tc.name: CheckKvStoreDir001 + * @tc.desc: Delete the kv store with the option that createDirByStoreIdOnly is true. + * @tc.type: FUNC + * @tc.require: AR000CQDV7 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, CheckKvStoreDir001, TestSize.Level1) +{ + /** + * @tc.steps: step1. open the kv store with the option that createDirByStoreIdOnly is true. + * @tc.expected: step1. Returns OK. + */ + KvStoreNbDelegate::Option option; + option.createDirByStoreIdOnly = true; + const std::string storeId("StoreOnly002"); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + std::string testSubDir; + EXPECT_EQ(KvStoreDelegateManager::GetDatabaseDir(storeId, testSubDir), OK); + std::string dataBaseDir = g_testDir + "/" + testSubDir; + EXPECT_GE(access(dataBaseDir.c_str(), F_OK), 0); + + /** + * @tc.steps: step2. delete the kv store, and check the directory. + * @tc.expected: step2. the directory is removed. + */ + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + g_kvNbDelegatePtr = nullptr; + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); + LOGI("[%s]", dataBaseDir.c_str()); + ASSERT_EQ(OS::CheckPathExistence(dataBaseDir), false); +} + +/** + * @tc.name: CompressionRate1 + * @tc.desc: Open the kv store with invalid compressionRate and open successfully. + * @tc.type: FUNC + * @tc.require: AR000G3QTT + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBInterfacesDatabaseTest, CompressionRate1, TestSize.Level1) +{ + /** + * @tc.steps: step1. Open the kv store with the option that comressionRate is invalid. + * @tc.expected: step1. Open kv store successfully. Returns OK. + */ + KvStoreNbDelegate::Option option; + option.isNeedCompressOnSync = true; + option.compressionRate = 0; // 0 is invalid. + const std::string storeId("CompressionRate1"); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + g_kvNbDelegatePtr = nullptr; + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); +} + +HWTEST_F(DistributedDBInterfacesDatabaseTest, DataInterceptor1, TestSize.Level1) +{ + /** + * @tc.steps: step1. Open the kv store with the option that comressionRate is invalid. + * @tc.expected: step1. Open kv store successfully. Returns OK. + */ + KvStoreNbDelegate::Option option; + const std::string storeId("DataInterceptor1"); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + g_kvNbDelegatePtr->SetPushDataInterceptor( + [](InterceptedData &data, const std::string &sourceID, const std::string &targetID) { + int errCode = OK; + auto entries = data.GetEntries(); + for (size_t i = 0; i < entries.size(); i++) { + if (entries[i].key.empty() || entries[i].key.at(0) != 'A') { + continue; + } + auto newKey = entries[i].key; + newKey[0] = 'B'; + errCode = data.ModifyKey(i, newKey); + if (errCode != OK) { + break; + } + } + return errCode; + } + ); + + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + g_kvNbDelegatePtr = nullptr; + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_device_identifier_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_device_identifier_test.cpp new file mode 100644 index 00000000..ea23f80e --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_device_identifier_test.cpp @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_constant.h" +#include "db_errno.h" +#include "distributeddb_data_generate_unit_test.h" +#include "kv_store_nb_delegate_impl.h" +#include "platform_specific.h" +#include "sqlite_single_ver_natural_store.h" +#include "sqlite_single_ver_natural_store_connection.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + DBStatus g_kvDelegateStatus = INVALID_ARGS; + SQLiteSingleVerNaturalStore *g_store = nullptr; + DistributedDB::SQLiteSingleVerNaturalStoreConnection *g_connection = nullptr; + const string STORE_ID = STORE_ID_SYNC; + const int TIME_LAG = 100; + const std::string DEVICE_ID_1 = "ABC"; + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); +} + +class DistributedDBDeviceIdentifierTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBDeviceIdentifierTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + + string dir = g_testDir + STORE_ID + "/" + DBConstant::SINGLE_SUB_DIR; + DIR *dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } +} + +void DistributedDBDeviceIdentifierTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir + STORE_ID + "/" + DBConstant::SINGLE_SUB_DIR) != 0) { + LOGE("rm test db files error!"); + } + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); +} + +void DistributedDBDeviceIdentifierTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(STORE_ID, option, g_kvNbDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + + KvDBProperties property; + property.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + property.SetStringProp(KvDBProperties::STORE_ID, STORE_ID); + property.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + + g_store = new (std::nothrow) SQLiteSingleVerNaturalStore; + ASSERT_NE(g_store, nullptr); + ASSERT_EQ(g_store->Open(property), E_OK); + + int erroCode = E_OK; + g_connection = static_cast(g_store->GetDBConnection(erroCode)); + ASSERT_NE(g_connection, nullptr); + g_store->DecObjRef(g_store); + EXPECT_EQ(erroCode, E_OK); +} + +void DistributedDBDeviceIdentifierTest::TearDown(void) +{ + if (g_connection != nullptr) { + g_connection->Close(); + } + + g_store = nullptr; + + if (g_kvNbDelegatePtr != nullptr) { + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore(STORE_ID) == OK); + } + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); +} + +/** + * @tc.name: DeviceIdentifier001 + * @tc.desc: Set pragma to be GET_DEVICE_IDENTIFIER_OF_ENTRY, + * set Key to be null and origDevice to be false, expect return INVALID_ARGS. + * @tc.type: FUNC + * @tc.require: AR000D08KV + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBDeviceIdentifierTest, DeviceIdentifier001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Sync 1K data from DEVICE_B into database, with Key= KEY_1, and Value = VALUE_1. + * @tc.expected: step1. Expect return true. + */ + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + + /** + * @tc.steps:step2. Set PragmaCmd to be GET_DEVICE_IDENTIFIER_OF_ENTRY, and set input key to be null + * @tc.expected: step2. Expect return INVALID_ARGS. + */ + Key keyNull; + PragmaEntryDeviceIdentifier param; + param.key = keyNull; + param.origDevice = false; + PragmaData input = static_cast(¶m); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(GET_DEVICE_IDENTIFIER_OF_ENTRY, input), INVALID_ARGS); +} + +/** + * @tc.name: DeviceIdentifier002 + * @tc.desc: Set pragma to be GET_DEVICE_IDENTIFIER_OF_ENTRY, + * set Key to be null and origDevice to be true, expect return INVALID_ARGS. + * @tc.type: FUNC + * @tc.require: AR000D08KV + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBDeviceIdentifierTest, DeviceIdentifier002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Sync 1K data from DEVICE_B into database, with Key= KEY_1, and Value = VALUE_1. + * @tc.expected: step1. Expect return true. + */ + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + + /** + * @tc.steps:step2. Set PragmaCmd to be GET_DEVICE_IDENTIFIER_OF_ENTRY, and set input key to be null + * @tc.expected: step2. Expect return INVALID_ARGS. + */ + Key keyNull; + PragmaEntryDeviceIdentifier param; + param.key = keyNull; + param.origDevice = true; + PragmaData input = static_cast(¶m); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(GET_DEVICE_IDENTIFIER_OF_ENTRY, input), INVALID_ARGS); +} + +/** + * @tc.name: DeviceIdentifier003 + * @tc.desc: Set pragma to be GET_DEVICE_IDENTIFIER_OF_ENTRY and origDevice to be false. + * Check if a non-existing key will return NOT_FOUND. + * @tc.type: FUNC + * @tc.require: AR000D08KV + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBDeviceIdentifierTest, DeviceIdentifier003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Sync 1K data from DEVICE_B into database, with Key= KEY_1, and Value = VALUE_1. + * @tc.expected: step1. Expect return true. + */ + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + /** + * @tc.steps:step2. Set PragmaCmd to be GET_DEVICE_IDENTIFIER_OF_ENTRY, and set Key= Key_2 + * @tc.expected: step2. Expect return NOT_FOUND. + */ + PragmaEntryDeviceIdentifier param; + param.key = KEY_2; + param.origDevice = false; + PragmaData input = static_cast(¶m); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(GET_DEVICE_IDENTIFIER_OF_ENTRY, input), NOT_FOUND); +} + +/** + * @tc.name: DeviceIdentifier004 + * @tc.desc: Set pragma to be GET_DEVICE_IDENTIFIER_OF_ENTRY and origDevice to be true. + * Check if a non-existing key will return NOT_FOUND. + * @tc.type: FUNC + * @tc.require: AR000D08KV + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBDeviceIdentifierTest, DeviceIdentifier004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Sync 1K data from DEVICE_B into database, with Key= KEY_1, and Value = VALUE_1. + * @tc.expected: step1. Expect return true. + */ + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + /** + * @tc.steps:step2. Set PragmaCmd to be GET_DEVICE_IDENTIFIER_OF_ENTRY, and set Key= Key_2 + * @tc.expected: step2. Expect return NOT_FOUND. + */ + PragmaEntryDeviceIdentifier param; + param.key = KEY_2; + param.origDevice = true; + PragmaData input = static_cast(¶m); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(GET_DEVICE_IDENTIFIER_OF_ENTRY, input), NOT_FOUND); +} + +/** + * @tc.name: DeviceIdentifier005 + * @tc.desc: Set pragma to be GET_DEVICE_IDENTIFIER_OF_ENTRY and origDevice to be false. check if returns OK. + * @tc.type: FUNC + * @tc.require: AR000D08KV + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBDeviceIdentifierTest, DeviceIdentifier005, TestSize.Level1) +{ + /** + * @tc.steps:step1. Sync 1K data from DEVICE_B into database, with Key= KEY_1, and Value = VALUE_1. + * @tc.expected: step1. Expect return true. + */ + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + /** + * @tc.steps:step2. Set PragmaCmd = GET_DEVICE_IDENTIFIER_OF_ENTRY, Key= Key_1, origDevice = false. + * @tc.expected: step2. Expect return deviceIdentifier is the same as deviceIdentifier of DEVICE_B. + */ + PragmaEntryDeviceIdentifier param; + param.key = KEY_1; + param.origDevice = false; + PragmaData input = static_cast(¶m); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(GET_DEVICE_IDENTIFIER_OF_ENTRY, input), OK); +} + +/** + * @tc.name: DeviceIdentifier006 + * @tc.desc: Set pragma to be GET_DEVICE_IDENTIFIER_OF_ENTRY and origDevice to be true. check if returns OK. + * @tc.type: FUNC + * @tc.require: AR000D08KV + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBDeviceIdentifierTest, DeviceIdentifier006, TestSize.Level1) +{ + /** + * @tc.steps:step1. Sync 1K data from DEVICE_B into database, with Key= KEY_1, and Value = VALUE_1. + * @tc.expected: step1. Expect return true. + */ + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + /** + * @tc.steps:step2. Set PragmaCmd = GET_DEVICE_IDENTIFIER_OF_ENTRY, Key= Key_1, origDevice = false. + * @tc.expected: step2. Expect return deviceIdentifier is the same as deviceIdentifier of DEVICE_B. + */ + PragmaEntryDeviceIdentifier param; + param.key = KEY_1; + param.origDevice = true; + PragmaData input = static_cast(¶m); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(GET_DEVICE_IDENTIFIER_OF_ENTRY, input), OK); +} + +/** + * @tc.name: DeviceIdentifier007 + * @tc.desc: Set pragma to be GET_IDENTIFIER_OF_DEVICE. check if empty deviceID returns INVALID_ARGS. + * @tc.type: FUNC + * @tc.require: AR000D08KV + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBDeviceIdentifierTest, DeviceIdentifier007, TestSize.Level1) +{ + /** + * @tc.steps:step1. Set PragmaCmd = GET_IDENTIFIER_OF_DEVICE, deviceID= NULL. + * @tc.expected: step1. Expect return INVALID_ARGS. + */ + PragmaDeviceIdentifier param; + param.deviceID = ""; + PragmaData input = static_cast(¶m); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(GET_IDENTIFIER_OF_DEVICE, input), INVALID_ARGS); +} + +/** + * @tc.name: DeviceIdentifier008 + * @tc.desc: Set pragma to be GET_IDENTIFIER_OF_DEVICE. check if deviceIdentifier matches deviceID. + * @tc.type: FUNC + * @tc.require: AR000D08KV + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBDeviceIdentifierTest, DeviceIdentifier008, TestSize.Level1) +{ + /** + * @tc.steps:step1. Set PragmaCmd = GET_IDENTIFIER_OF_DEVICE, deviceID = DEVICE_ID_1 + * @tc.expected: step1. Expect return deviceIdentifier is the same as deviceIdentifier of DEVICE_ID_1. + */ + PragmaDeviceIdentifier param; + param.deviceID = DEVICE_ID_1; + PragmaData input = static_cast(¶m); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(GET_IDENTIFIER_OF_DEVICE, input), OK); + EXPECT_EQ(param.deviceIdentifier, DBCommon::TransferHashString(DEVICE_ID_1)); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_encrypt_database_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_encrypt_database_test.cpp new file mode 100644 index 00000000..645b3c7c --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_encrypt_database_test.cpp @@ -0,0 +1,497 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_ENCRYPT +#include + +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + + const string STORE_ID1 = "store1"; + const string STORE_ID2 = "store2"; + + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; +} + +class DistributedDBInterfacesEncryptDatabaseTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + static void CheckRekeyWithMultiKvStore(bool isLocal); + static void CheckRekeyWithExistedSnapshot(bool isLocal); + static void CheckRekeyWithExistedObserver(bool isLocal); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesEncryptDatabaseTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesEncryptDatabaseTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesEncryptDatabaseTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBInterfacesEncryptDatabaseTest::TearDown(void) +{ +} + +void DistributedDBInterfacesEncryptDatabaseTest::CheckRekeyWithMultiKvStore(bool isLocal) +{ + DBStatus status; + KvStoreDelegate *kvStore1 = nullptr; + auto delegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(status), std::ref(kvStore1)); + /** + * @tc.steps:step1. Get the delegate. + */ + KvStoreDelegate::Option option = {true, isLocal, false}; + g_mgr.GetKvStore(STORE_ID1, option, delegateCallback); + ASSERT_TRUE(kvStore1 != nullptr); + + KvStoreDelegate *kvStore2 = nullptr; + auto delegateCallback2 = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(status), std::ref(kvStore2)); + /** + * @tc.steps:step2. Get another delegate. + */ + option.createIfNecessary = false; + g_mgr.GetKvStore(STORE_ID1, option, delegateCallback2); + ASSERT_TRUE(kvStore2 != nullptr); + + /** + * @tc.steps:step3. Execute the rekey operation. + * @tc.expected: step3. return BUSY. + */ + CipherPassword passwd; // random password + vector passwdBuffer(10, 45); // 10 and 45 as random password. + int errCode = passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + EXPECT_EQ(kvStore1->Rekey(passwd), BUSY); + /** + * @tc.steps:step4. Close the kv store delegate. + */ + EXPECT_EQ(g_mgr.CloseKvStore(kvStore2), OK); + /** + * @tc.steps:step5. Execute the rekey operation. + * @tc.expected: step5. return OK. + */ + EXPECT_EQ(kvStore1->Rekey(passwd), OK); + EXPECT_EQ(g_mgr.CloseKvStore(kvStore1), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); +} + +void DistributedDBInterfacesEncryptDatabaseTest::CheckRekeyWithExistedSnapshot(bool isLocal) +{ + DBStatus status; + KvStoreDelegate *kvStore = nullptr; + auto delegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(status), std::ref(kvStore)); + /** + * @tc.steps:step1. Get the delegate. + */ + KvStoreDelegate::Option option = {true, isLocal, false}; + g_mgr.GetKvStore(STORE_ID1, option, delegateCallback); + ASSERT_TRUE(kvStore != nullptr); + + KvStoreSnapshotDelegate *snapshot = nullptr; + auto snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(status), std::ref(snapshot)); + /** + * @tc.steps:step2. Get the snapshot through the delegate. + */ + kvStore->GetKvStoreSnapshot(nullptr, snapshotDelegateCallback); + EXPECT_NE(snapshot, nullptr); + /** + * @tc.steps:step3. Execute the rekey operation. + * @tc.expected: step3. return BUSY. + */ + CipherPassword passwd; // random password + vector passwdBuffer(10, 45); // 10 and 45 as random password. + int errCode = passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + EXPECT_EQ(kvStore->Rekey(passwd), BUSY); + /** + * @tc.steps:step4. Release the snapshot. + */ + EXPECT_EQ(kvStore->ReleaseKvStoreSnapshot(snapshot), OK); + /** + * @tc.steps:step5. Execute the rekey operation. + * @tc.expected: step5. return OK. + */ + EXPECT_EQ(kvStore->Rekey(passwd), OK); + EXPECT_EQ(g_mgr.CloseKvStore(kvStore), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); +} + +void DistributedDBInterfacesEncryptDatabaseTest::CheckRekeyWithExistedObserver(bool isLocal) +{ + DBStatus status; + KvStoreDelegate *kvStore = nullptr; + auto delegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(status), std::ref(kvStore)); + /** + * @tc.steps:step1. Get the delegate. + */ + KvStoreDelegate::Option option = {true, isLocal, false}; + g_mgr.GetKvStore(STORE_ID1, option, delegateCallback); + ASSERT_TRUE(kvStore != nullptr); + /** + * @tc.steps:step2. Register the non-null observer. + */ + auto observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + ASSERT_EQ(kvStore->RegisterObserver(observer), OK); + /** + * @tc.steps:step3. Execute the rekey operation. + * @tc.expected: step3. return BUSY. + */ + CipherPassword passwd; // random password + vector passwdBuffer(10, 45); // 10 and 45 as random password. + int errCode = passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + EXPECT_EQ(kvStore->Rekey(passwd), BUSY); + /** + * @tc.steps:step4. Unregister the observer. + */ + EXPECT_EQ(kvStore->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + /** + * @tc.steps:step5. Execute the rekey operation. + * @tc.expected: step5. return OK. + */ + EXPECT_EQ(kvStore->Rekey(passwd), OK); + EXPECT_EQ(g_mgr.CloseKvStore(kvStore), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); +} + +/** + * @tc.name: LocalDatabaseRekeyCheck001 + * @tc.desc: Attempt to rekey while another delegate is existed. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDatabaseTest, LocalDatabaseRekeyCheck001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the local delegate. + */ + /** + * @tc.steps:step2. Get another local delegate. + */ + /** + * @tc.steps:step3. Execute the rekey operation. + * @tc.expected: step3. return BUSY. + */ + /** + * @tc.steps:step4. Close the kv store delegate. + */ + /** + * @tc.steps:step5. Execute the rekey operation. + * @tc.expected: step5. return OK. + */ + CheckRekeyWithMultiKvStore(true); +} + +/** + * @tc.name: LocalDatabaseRekeyCheck002 + * @tc.desc: Attempt to rekey while the snapshot is existed. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDatabaseTest, LocalDatabaseRekeyCheck002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the local delegate. + */ + /** + * @tc.steps:step2. Get the snapshot through the delegate. + */ + /** + * @tc.steps:step3. Execute the rekey operation. + * @tc.expected: step3. return BUSY. + */ + /** + * @tc.steps:step4. Release the snapshot. + */ + /** + * @tc.steps:step5. Execute the rekey operation. + * @tc.expected: step5. return OK. + */ + CheckRekeyWithExistedSnapshot(true); +} + +/** + * @tc.name: MultiVerRekeyCheck001 + * @tc.desc: Attempt to rekey while another delegate is existed. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDatabaseTest, MultiVerRekeyCheck001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the multi version delegate. + */ + /** + * @tc.steps:step2. Get another multi version delegate. + */ + /** + * @tc.steps:step3. Execute the rekey operation. + * @tc.expected: step3. return BUSY. + */ + /** + * @tc.steps:step4. Close the kv store delegate. + */ + /** + * @tc.steps:step5. Execute the rekey operation. + * @tc.expected: step5. return OK. + */ + CheckRekeyWithMultiKvStore(false); +} + +/** + * @tc.name: MultiVerRekeyCheck002 + * @tc.desc: Attempt to rekey while the snapshot is existed. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDatabaseTest, MultiVerRekeyCheck002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the multi version delegate. + */ + /** + * @tc.steps:step2. Get the snapshot through the delegate. + */ + /** + * @tc.steps:step3. Execute the rekey operation. + * @tc.expected: step3. return BUSY. + */ + /** + * @tc.steps:step4. Release the snapshot. + */ + /** + * @tc.steps:step5. Execute the rekey operation. + * @tc.expected: step5. return OK. + */ + CheckRekeyWithExistedSnapshot(false); +} + +/** + * @tc.name: MultiVerRekeyCheck003 + * @tc.desc: Attempt to rekey while the observer is existed. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDatabaseTest, MultiVerRekeyCheck003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the delegate. + */ + /** + * @tc.steps:step2. Register the non-null observer. + */ + /** + * @tc.steps:step3. Execute the rekey operation. + * @tc.expected: step3. return BUSY. + */ + /** + * @tc.steps:step4. Unregister the observer. + */ + /** + * @tc.steps:step5. Execute the rekey operation. + * @tc.expected: step5. return OK. + */ + CheckRekeyWithExistedObserver(false); +} + +/** + * @tc.name: SingleVerRekeyCheck001 + * @tc.desc: Attempt to rekey while another delegate is existed. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDatabaseTest, SingleVerRekeyCheck001, TestSize.Level1) +{ + DBStatus status; + KvStoreNbDelegate *kvStore1 = nullptr; + auto delegateCallback1 = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(status), std::ref(kvStore1)); + /** + * @tc.steps:step1. Get the single version delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(STORE_ID2, option, delegateCallback1); + ASSERT_TRUE(kvStore1 != nullptr); + + KvStoreNbDelegate *kvStore2 = nullptr; + auto delegateCallback2 = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(status), std::ref(kvStore2)); + /** + * @tc.steps:step2. Get another single version delegate. + */ + option.createIfNecessary = false; + g_mgr.GetKvStore(STORE_ID2, option, delegateCallback2); + ASSERT_TRUE(kvStore2 != nullptr); + + /** + * @tc.steps:step3. Execute the rekey operation. + * @tc.expected: step3. return BUSY. + */ + CipherPassword passwd; + vector passwdBuffer(10, 45); + int errCode = passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + EXPECT_EQ(kvStore1->Rekey(passwd), BUSY); + /** + * @tc.steps:step4. Close the kv store delegate. + */ + EXPECT_EQ(g_mgr.CloseKvStore(kvStore2), OK); + /** + * @tc.steps:step5. Execute the rekey operation. + * @tc.expected: step5. return OK. + */ + EXPECT_EQ(kvStore1->Rekey(passwd), OK); + EXPECT_EQ(g_mgr.CloseKvStore(kvStore1), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID2), OK); +} + +/** + * @tc.name: SingleVerRekeyCheck002 + * @tc.desc: Attempt to rekey when the observer exists. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDatabaseTest, SingleVerRekeyCheck002, TestSize.Level1) +{ + DBStatus status; + KvStoreNbDelegate *kvStore = nullptr; + auto delegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(status), std::ref(kvStore)); + /** + * @tc.steps:step1. Get the single version delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(STORE_ID2, option, delegateCallback); + ASSERT_TRUE(kvStore != nullptr); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the empty key. + */ + Key key; + EXPECT_EQ(kvStore->RegisterObserver(key, 4, observer), OK); // Only use the event 4. + + /** + * @tc.steps:step3. Execute the rekey operation. + * @tc.expected: step3. return BUSY. + */ + CipherPassword passwd; + vector passwdBuffer(10, 45); + int errCode = passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + EXPECT_EQ(kvStore->Rekey(passwd), BUSY); + /** + * @tc.steps:step4. Unregister the observer. + */ + EXPECT_EQ(kvStore->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + /** + * @tc.steps:step5. Execute the rekey operation. + * @tc.expected: step5. return OK. + */ + EXPECT_EQ(kvStore->Rekey(passwd), OK); + EXPECT_EQ(g_mgr.CloseKvStore(kvStore), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID2), OK); +} + +static void NotifierCallback(const KvStoreNbConflictData &data) +{ +} +/** + * @tc.name: SingleVerRekeyCheck003 + * @tc.desc: Attempt to rekey while the conflict notifier is set. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDatabaseTest, SingleVerRekeyCheck003, TestSize.Level1) +{ + DBStatus status; + KvStoreNbDelegate *kvStore = nullptr; + auto delegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(status), std::ref(kvStore)); + /** + * @tc.steps:step1. Get the single version delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(STORE_ID2, option, delegateCallback); + ASSERT_TRUE(kvStore != nullptr); + /** + * @tc.steps:step2. Set the non-null conflict notifier. + */ + const int conflictAll = 15; + ASSERT_EQ(kvStore->SetConflictNotifier(conflictAll, NotifierCallback), OK); + CipherPassword passwd; + vector passwdBuffer(10, 45); + int errCode = passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + /** + * @tc.steps:step3. Execute the rekey operation. + * @tc.expected: step3. return BUSY. + */ + EXPECT_EQ(kvStore->Rekey(passwd), BUSY); + /** + * @tc.steps:step4. Set the null conflict notifier to unregister the conflict notifier. + */ + EXPECT_EQ(kvStore->SetConflictNotifier(1, nullptr), OK); + /** + * @tc.steps:step5. Execute the rekey operation. + * @tc.expected: step5. return OK. + */ + EXPECT_EQ(kvStore->Rekey(passwd), OK); + EXPECT_EQ(g_mgr.CloseKvStore(kvStore), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID2), OK); +} +#endif diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_encrypt_delegate_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_encrypt_delegate_test.cpp new file mode 100644 index 00000000..f2d9b53f --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_encrypt_delegate_test.cpp @@ -0,0 +1,1112 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_ENCRYPT +#include + +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "kv_store_nb_conflict_data.h" +#include "log_print.h" +#include "sqlite_single_ver_natural_store.h" +#include "sqlite_utils.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + const string STORE_ID1 = "store1_singleVersionDB"; + const string STORE_ID2 = "store2_localDB"; + const string STORE_ID3 = "store3_multiVersionDB"; + CipherPassword g_passwd1; // 5 '1' + CipherPassword g_passwd2; // 5 '2' + CipherPassword g_passwd3; // 5 '3' + const CipherPassword PASSWD_EMPTY; + const int CONFLICT_ALL = 15; + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + string g_testDir; + KvStoreConfig g_config; + DBStatus g_errCode = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_errCode), std::ref(g_kvNbDelegatePtr)); + + // define the delegate call back + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_errCode), std::ref(g_kvDelegatePtr)); + + DBStatus g_snapshotDelegateStatus = INVALID_ARGS; + KvStoreSnapshotDelegate *g_snapshotDelegatePtr = nullptr; + + auto g_snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_snapshotDelegateStatus), std::ref(g_snapshotDelegatePtr)); + + DBStatus g_valueStatus = INVALID_ARGS; + Value g_value; + + auto g_valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(g_valueStatus), std::ref(g_value)); + + void GetSnapshotAndValueCheck(const Key &testKey, const Value &testValue, DBStatus errCode) + { + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, g_snapshotDelegateCallback); + EXPECT_TRUE(g_snapshotDelegateStatus == OK); + ASSERT_TRUE(g_snapshotDelegatePtr != nullptr); + // check value and errCode + g_snapshotDelegatePtr->Get(testKey, g_valueCallback); + EXPECT_EQ(g_valueStatus, errCode); + + if (errCode == OK) { + EXPECT_TRUE(g_value.size() > 0); + if (g_value.size() > 0) { + EXPECT_EQ(g_value.front(), testValue[0]); + } + } + } + + void GetNbKvStoreAndCheckFun(const std::string &storeId, const KvStoreNbDelegate::Option &option, + const Key &testKey, const Value &testValue) + { + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(testKey, testValue), OK); + Value valueRead; + EXPECT_EQ(g_kvNbDelegatePtr->Get(testKey, valueRead), OK); + EXPECT_EQ(valueRead, testValue); + } + + void NotifierConnectTestCallback(const KvStoreNbConflictData &data) + { + g_errCode = INVALID_ARGS; + } +} + +class DistributedDBInterfacesEncryptDelegateTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesEncryptDelegateTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + vector passwdBuffer1(5, 1); // 5 and 1 as random password. + int errCode = g_passwd1.SetValue(passwdBuffer1.data(), passwdBuffer1.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + vector passwdBuffer2(5, 2); // 5 and 2 as random password. + errCode = g_passwd2.SetValue(passwdBuffer2.data(), passwdBuffer2.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + vector passwdBuffer3(5, 3); // 5 and 3 as random password. + errCode = g_passwd3.SetValue(passwdBuffer3.data(), passwdBuffer3.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); +} + +void DistributedDBInterfacesEncryptDelegateTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesEncryptDelegateTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_errCode = INVALID_ARGS; + g_kvNbDelegatePtr = nullptr; + g_kvDelegatePtr = nullptr; +} + +void DistributedDBInterfacesEncryptDelegateTest::TearDown(void) {} + +/** + * @tc.name: EncryptedDbOperation001 + * @tc.desc: Test the single version db encrypted function. + * @tc.type: FUNC + * @tc.require: AR000CQDT5 AR000CQDT6 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbOperation001, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID1, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + ASSERT_TRUE(g_errCode == OK); + /** + * @tc.steps: step1. Put [KEY_1, V1] + * @tc.expected: step1. Get result OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + /** + * @tc.steps: step2. Close db and open it again ,then get the value of K1 + * @tc.expected: step2. Close and open db successfully, value of K1 is V1 + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + + GetNbKvStoreAndCheckFun(STORE_ID1, option, KEY_1, VALUE_1); + /** + * @tc.steps: step3. Put [KEY_1, V2] + * @tc.expected: step3. Get result OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_2), OK); + /** + * @tc.steps: step4. Close db and open it again ,then get the value of K1 + * @tc.expected: step4. Close and open db successfully, value of K1 is V2 + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + g_mgr.GetKvStore(STORE_ID1, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_errCode == OK); + Value valueRead2; + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, valueRead2), OK); + EXPECT_EQ(valueRead2, VALUE_2); + /** + * @tc.steps: step5. Delete record K1 + * @tc.expected: step5. Get result OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Delete(KEY_1), OK); + /** + * @tc.steps: step6. Close db and open it again ,then get the value of K1 + * @tc.expected: step6. Close and open db successfully, get value of K1 NOT_FOUND + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + g_mgr.GetKvStore(STORE_ID1, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_errCode == OK); + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, valueRead2), NOT_FOUND); + + // additional test + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + option.passwd = g_passwd2; + g_mgr.GetKvStore(STORE_ID1, option, g_kvNbDelegateCallback); + EXPECT_EQ(g_kvNbDelegatePtr, nullptr); + EXPECT_EQ(g_errCode, INVALID_PASSWD_OR_CORRUPTED_DB); + + // finilize logic + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), INVALID_ARGS); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: EncryptedDbOperation002 + * @tc.desc: Test the local db encrypted function. + * @tc.type: FUNC + * @tc.require: AR000CQDT5 AR000CQDT6 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbOperation002, TestSize.Level1) +{ + KvStoreDelegate::Option option = {true, true, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID2, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + /** + * @tc.steps: step1. Put [KEY_1, V1] + * @tc.expected: step1. Get result OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_1), OK); + /** + * @tc.steps: step2. Close db and open it again ,then get the value of K1 + * @tc.expected: step2. Close and open db successfully, value of K1 is V1 + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + g_mgr.GetKvStore(STORE_ID2, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_errCode == OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_1, OK); + + /** + * @tc.steps: step3. Put [KEY_1, V2] + * @tc.expected: step3. Get result OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_2), OK); + /** + * @tc.steps: step4. Close db and open it again ,then get the value of K1 + * @tc.expected: step4. Close and open db successfully, value of K1 is V2 + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + g_mgr.GetKvStore(STORE_ID2, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_errCode == OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_2, OK); + /** + * @tc.steps: step5. Delete record K1 + * @tc.expected: step5. Get result OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Delete(KEY_1), OK); + /** + * @tc.steps: step6. Close db and open it again ,then get the value of K1 + * @tc.expected: step6. Close and open db successfully, get value of K1 NOT_FOUND + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + g_mgr.GetKvStore(STORE_ID2, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_errCode == OK); + GetSnapshotAndValueCheck(KEY_1, VALUE_2, NOT_FOUND); + + // additional test + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + option.passwd = g_passwd2; + g_mgr.GetKvStore(STORE_ID2, option, g_kvDelegateCallback); + EXPECT_TRUE(g_kvDelegatePtr == nullptr); + EXPECT_EQ(g_errCode, INVALID_PASSWD_OR_CORRUPTED_DB); + + // finilize logic + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID2), OK); +} + +/** + * @tc.name: EncryptedDbOperation003 + * @tc.desc: Test the multi version db encrypted function. + * @tc.type: FUNC + * @tc.require: AR000CQDT5 AR000CQDT6 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbOperation003, TestSize.Level1) +{ + KvStoreDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID3, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + /** + * @tc.steps: step1. Put [KEY_1, V1] + * @tc.expected: step1. Get result OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_1), OK); + /** + * @tc.steps: step2. Close db and open it again ,then get the value of K1 + * @tc.expected: step2. Close and open db successfully, value of K1 is V1 + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + g_mgr.GetKvStore(STORE_ID3, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_errCode == OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_1, OK); + /** + * @tc.steps: step3. Put [KEY_1, V2] + * @tc.expected: step3. Get result OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_2), OK); + /** + * @tc.steps: step4. Close db and open it again ,then get the value of K1 + * @tc.expected: step4. Close and open db successfully, value of K1 is V2 + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + g_mgr.GetKvStore(STORE_ID3, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_errCode == OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_2, OK); + /** + * @tc.steps: step5. Delete record K1 + * @tc.expected: step5. Get result OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Delete(KEY_1), OK); + /** + * @tc.steps: step6. Close db and open it again ,then get the value of K1 + * @tc.expected: step6. Close and open db successfully, get value of K1 NOT_FOUND + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + g_mgr.GetKvStore(STORE_ID3, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_errCode == OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_2, NOT_FOUND); + + // additional test + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + option.passwd = g_passwd2; + g_mgr.GetKvStore(STORE_ID3, option, g_kvDelegateCallback); + EXPECT_TRUE(g_kvDelegatePtr == nullptr); + EXPECT_EQ(g_errCode, INVALID_PASSWD_OR_CORRUPTED_DB); + + // finilize logic + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID3), OK); +} + +/** + * @tc.name: EncryptedDbSwitch001 + * @tc.desc: Test the single version db for Rekey function. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch001, TestSize.Level1) +{ + /** + * @tc.steps: step1/step2. Put and get [KEY_1, V1] + * @tc.expected: step1/step2. Get value of KEY_1, value of K1 is V1. + */ + KvStoreNbDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd1}; + GetNbKvStoreAndCheckFun(STORE_ID1, option, KEY_1, VALUE_1); + /** + * @tc.steps: step3. Rekey passwd to passwd2 + * @tc.expected: step3. result is OK + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rekey(g_passwd2), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + /** + * @tc.steps: step4/step5. Put and get [KEY_1, V2] + * @tc.expected: step4/step5. Get value of KEY_1, value of K1 is V2. + */ + option.passwd = g_passwd2; + GetNbKvStoreAndCheckFun(STORE_ID1, option, KEY_1, VALUE_2); + + /** + * @tc.steps: step6. Rekey passwd to empty + * @tc.expected: step6. result is OK + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rekey(PASSWD_EMPTY), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + /** + * @tc.steps: step7/step8. Put and get [KEY_1, V3] + * @tc.expected: step7/step8. Get value of KEY_1, value of K1 is V3. + */ + option.isEncryptedDb = false; + GetNbKvStoreAndCheckFun(STORE_ID1, option, KEY_1, VALUE_3); + /** + * @tc.steps: step9. Rekey passwd to passwd3 + * @tc.expected: step3. result is OK + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rekey(g_passwd3), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + /** + * @tc.steps: step10/step11. Put and get [KEY_1, V4] + * @tc.expected: step10/step11. Get value of KEY_1, value of K1 is V4. + */ + option.isEncryptedDb = true; + option.passwd = g_passwd3; + GetNbKvStoreAndCheckFun(STORE_ID1, option, KEY_1, VALUE_4); + + // finilize logic + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: EncryptedDbSwitch002 + * @tc.desc: Test the single version db Rekey function return BUSY because of multiple instances. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch002, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID1, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_errCode == OK); + /** + * @tc.steps: step1. open same database again + * @tc.expected: step1. Get result OK + */ + DBStatus errCode = INVALID_ARGS; + KvStoreNbDelegate *kvNbDelegatePtr = nullptr; + auto kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(errCode), std::ref(kvNbDelegatePtr)); + g_mgr.GetKvStore(STORE_ID1, option, kvNbDelegateCallback); + ASSERT_TRUE(kvNbDelegatePtr != nullptr); + EXPECT_TRUE(errCode == OK); + /** + * @tc.steps: step2. invoke rekey logic + * @tc.expected: step2. Get result BUSY + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rekey(g_passwd2), BUSY); + // finilize logic + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); +} + +/** + * @tc.name: EncryptedDbSwitch003 + * @tc.desc: Test the single version db Rekey function return BUSY because of observer. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch003, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID1, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_errCode == OK); + /** + * @tc.steps: step1. register observer + * @tc.expected: step1. Get result OK + */ + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_NE(observer, nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(KEY_1, OBSERVER_CHANGES_NATIVE, observer), OK); + /** + * @tc.steps: step2. invoke rekey logic + * @tc.expected: step2. Get result BUSY + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rekey(g_passwd2), BUSY); + // finilize logic + delete observer; + observer = nullptr; + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); +} + +/** + * @tc.name: EncryptedDbSwitch004 + * @tc.desc: Test the single version db Rekey function return BUSY because of conflict notifier. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch004, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID1, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_errCode == OK); + /** + * @tc.steps: step1. register observer + * @tc.expected: step1. Get result OK + */ + EXPECT_EQ(g_kvNbDelegatePtr->SetConflictNotifier(CONFLICT_ALL, NotifierConnectTestCallback), OK); + /** + * @tc.steps: step2. invoke rekey logic + * @tc.expected: step2. Get result BUSY + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rekey(g_passwd2), BUSY); + // finilize logic + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); +} + +/** + * @tc.name: EncryptedDbSwitch008 + * @tc.desc: Test the multi version db Rekey function return BUSY because of Snapshot not close. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch008, TestSize.Level1) +{ +} + +/** + * @tc.name: EncryptedDbSwitch009 + * @tc.desc: Test the single version db Rekey function from password1 to password2. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch009, TestSize.Level1) +{ + /** + * @tc.steps: step1/step2. Put and get [KEY_1, V1] + * @tc.expected: step1/step2. Get value of KEY_1, value of K1 is V1. + */ + KvStoreNbDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd1}; + GetNbKvStoreAndCheckFun(STORE_ID1, option, KEY_1, VALUE_1); + /** + * @tc.steps: step3. Rekey passwd to passwd2 + * @tc.expected: step3. result is OK + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rekey(g_passwd2), OK); + Value valueRead1; + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, valueRead1), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + /** + * @tc.steps: step4/step5. Put and get [KEY_1, V2] + * @tc.expected: step4/step5. Get value of KEY_1, value of K1 is V2. + */ + option.passwd = g_passwd2; + GetNbKvStoreAndCheckFun(STORE_ID1, option, KEY_1, VALUE_2); + // finilize logic + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: EncryptedDbSwitch010 + * @tc.desc: Test the single version db Rekey function from encrypted db unencrypted db . + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch010, TestSize.Level1) +{ + /** + * @tc.steps: step1/step2. Put and get [KEY_1, V1] + * @tc.expected: step1/step2. Get value of KEY_1, value of K1 is V1. + */ + KvStoreNbDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd1}; + GetNbKvStoreAndCheckFun(STORE_ID1, option, KEY_1, VALUE_1); + /** + * @tc.steps: step6. Rekey passwd to empty + * @tc.expected: step6. result is OK + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rekey(PASSWD_EMPTY), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + /** + * @tc.steps: step7/step8. Put and get [KEY_1, V3] + * @tc.expected: step7/step8. Get value of KEY_1, value of K1 is V3. + */ + option.isEncryptedDb = false; + GetNbKvStoreAndCheckFun(STORE_ID1, option, KEY_1, VALUE_3); + // finilize logic + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: EncryptedDbSwitch011 + * @tc.desc: Test the single version db Rekey function from unencrypted db to encrypted db. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch011, TestSize.Level1) +{ + /** + * @tc.steps: step1/step2. Put and get [KEY_1, V1] + * @tc.expected: step1/step2. Get value of KEY_1, value of K1 is V1. + */ + KvStoreNbDelegate::Option option = {true, false, false, CipherType::DEFAULT, PASSWD_EMPTY}; + GetNbKvStoreAndCheckFun(STORE_ID1, option, KEY_1, VALUE_1); + /** + * @tc.steps: step3. Rekey passwd to passwd2 + * @tc.expected: step3. result is OK + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rekey(g_passwd2), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + /** + * @tc.steps: step4/step5. Put and get [KEY_1, V2] + * @tc.expected: step4/step5. Get value of KEY_1, value of K1 is V2. + */ + option.passwd = g_passwd2; + option.isEncryptedDb = true; + GetNbKvStoreAndCheckFun(STORE_ID1, option, KEY_1, VALUE_2); + // finilize logic + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: EncryptedDbSwitch012 + * @tc.desc: Test the local db Rekey function from password1 to password2. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch012, TestSize.Level1) +{ + KvStoreDelegate::Option option = {true, true, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID2, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + /** + * @tc.steps: step1/step2. Put and get [KEY_1, V1] + * @tc.expected: step1/step2. Get value of KEY_1, value of K1 is V1. + */ + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_1), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_1, OK); + + /** + * @tc.steps: step3. Rekey passwd to passwd2 + * @tc.expected: step3. result is OK + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_kvDelegatePtr->Rekey(g_passwd2), OK); + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_3), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + /** + * @tc.steps: step4/step5. Put and get [KEY_1, V2] + * @tc.expected: step4/step5. Get value of KEY_1, value of K1 is V2. + */ + option.passwd = g_passwd2; + g_mgr.GetKvStore(STORE_ID2, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_2), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_2, OK); + // finilize logic + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID2), OK); + g_kvDelegatePtr = nullptr; +} + +/** + * @tc.name: EncryptedDbSwitch013 + * @tc.desc: Test the local db Rekey function from encrypted db unencrypted db . + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch013, TestSize.Level1) +{ + KvStoreDelegate::Option option = {true, true, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID2, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + /** + * @tc.steps: step1/step2. Put and get [KEY_1, V1] + * @tc.expected: step1/step2. Get value of KEY_1, value of K1 is V1. + */ + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_1), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_1, OK); + /** + * @tc.steps: step3. Rekey passwd to empty + * @tc.expected: step3. result is OK + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_kvDelegatePtr->Rekey(PASSWD_EMPTY), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + /** + * @tc.steps: step4/step5. Put and get [KEY_1, V2] + * @tc.expected: step4/step5. Get value of KEY_1, value of K1 is V2. + */ + option.isEncryptedDb = false; + g_mgr.GetKvStore(STORE_ID2, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_2), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_2, OK); + // finilize logic + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID2), OK); + g_kvDelegatePtr = nullptr; +} + +/** + * @tc.name: EncryptedDbSwitch014 + * @tc.desc: Test the local db Rekey function from unencrypted db to encrypted db. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch014, TestSize.Level1) +{ + KvStoreDelegate::Option option = {true, true, false, CipherType::DEFAULT, PASSWD_EMPTY}; + g_mgr.GetKvStore(STORE_ID2, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + /** + * @tc.steps: step1/step2. Put and get [KEY_1, V1] + * @tc.expected: step1/step2. Get value of KEY_1, value of K1 is V1. + */ + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_1), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_1, OK); + /** + * @tc.steps: step3. Rekey passwd to passwd2 + * @tc.expected: step3. result is OK + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_kvDelegatePtr->Rekey(g_passwd2), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + /** + * @tc.steps: step4/step5. Put and get [KEY_1, V2] + * @tc.expected: step4/step5. Get value of KEY_1, value of K1 is V2. + */ + option.passwd = g_passwd2; + option.isEncryptedDb = true; + g_mgr.GetKvStore(STORE_ID2, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_2), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_2, OK); + // finilize logic + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID2), OK); + g_kvDelegatePtr = nullptr; +} + +/** + * @tc.name: EncryptedDbSwitch015 + * @tc.desc: Test the multi version db Rekey function from password1 to password2. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch015, TestSize.Level1) +{ + KvStoreDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID3, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + /** + * @tc.steps: step1/step2. Put and get [KEY_1, V1] + * @tc.expected: step1/step2. Get value of KEY_1, value of K1 is V1. + */ + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_1), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_1, OK); + /** + * @tc.steps: step3. Rekey passwd to passwd2 + * @tc.expected: step3. result is OK + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_kvDelegatePtr->Rekey(g_passwd2), OK); + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_3), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_3, OK); + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + /** + * @tc.steps: step4/step5. Put and get [KEY_1, V2] + * @tc.expected: step4/step5. Get value of KEY_1, value of K1 is V2. + */ + option.passwd = g_passwd2; + g_mgr.GetKvStore(STORE_ID3, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_2), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_2, OK); + // finilize logic + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID3), OK); + g_kvDelegatePtr = nullptr; +} + +/** + * @tc.name: EncryptedDbSwitch016 + * @tc.desc: Test the multi version db Rekey function from encrypted db unencrypted db . + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch016, TestSize.Level1) +{ + KvStoreDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID3, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + /** + * @tc.steps: step1/step2. Put and get [KEY_1, V1] + * @tc.expected: step1/step2. Get value of KEY_1, value of K1 is V1. + */ + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_1), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_1, OK); + /** + * @tc.steps: step3. Rekey passwd to empty + * @tc.expected: step3. result is OK + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_kvDelegatePtr->Rekey(PASSWD_EMPTY), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + /** + * @tc.steps: step4/step5. Put and get [KEY_1, V2] + * @tc.expected: step4/step5. Get value of KEY_1, value of K1 is V2. + */ + option.isEncryptedDb = false; + g_mgr.GetKvStore(STORE_ID3, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_2), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_2, OK); + // finilize logic + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID3), OK); + g_kvDelegatePtr = nullptr; +} + +/** + * @tc.name: EncryptedDbSwitch017 + * @tc.desc: Test the multi version db Rekey function from unencrypted db to encrypted db. + * @tc.type: FUNC + * @tc.require: AR000CQDT7 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, EncryptedDbSwitch017, TestSize.Level1) +{ + KvStoreDelegate::Option option = {true, false, false, CipherType::DEFAULT, PASSWD_EMPTY}; + g_mgr.GetKvStore(STORE_ID3, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + /** + * @tc.steps: step1/step2. Put and get [KEY_1, V1] + * @tc.expected: step1/step2. Get value of KEY_1, value of K1 is V1. + */ + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_1), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_1, OK); + /** + * @tc.steps: step3. Rekey passwd to passwd2 + * @tc.expected: step3. result is OK + */ + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_kvDelegatePtr->Rekey(g_passwd2), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + g_errCode = INVALID_ARGS; + /** + * @tc.steps: step4/step5. Put and get [KEY_1, V2] + * @tc.expected: step4/step5. Get value of KEY_1, value of K1 is V2. + */ + option.passwd = g_passwd2; + option.isEncryptedDb = true; + g_mgr.GetKvStore(STORE_ID3, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_1, VALUE_2), OK); + + GetSnapshotAndValueCheck(KEY_1, VALUE_2, OK); + // finilize logic + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID3), OK); + g_kvDelegatePtr = nullptr; +} + +/** + * @tc.name: OpenEncryptedDb001 + * @tc.desc: Test create an encrypted database successfully. + * @tc.type: FUNC + * @tc.require: AR000CQDT5 AR000CQDT6 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, OpenEncryptedDb001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create single version encrypted database + * @tc.expected: step1. Get result OK. + */ + KvStoreNbDelegate::Option option1 = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID1, option1, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_errCode == OK); + /** + * @tc.steps: step2. create multi version encrypted database + * @tc.expected: step2. Get result OK. + */ + KvStoreDelegate::Option option2 = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID3, option2, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + /** + * @tc.steps: step3. Close db. + * @tc.expected: step3. Get result ok. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID3), OK); +} + +/** + * @tc.name: OpenEncryptedDb002 + * @tc.desc: Test create an encrypted database failed. + * @tc.type: FUNC + * @tc.require: AR000CQDT8 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, OpenEncryptedDb002, TestSize.Level1) +{ + /** + * @tc.steps: step1. create single version encrypted database + * @tc.expected: step1. Get result INVALID_ARGS. + */ + KvStoreNbDelegate::Option option1 = {true, false, true, CipherType::DEFAULT, PASSWD_EMPTY}; + g_mgr.GetKvStore(STORE_ID1, option1, g_kvNbDelegateCallback); + EXPECT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_EQ(g_errCode, INVALID_ARGS); + /** + * @tc.steps: step2. create multi version encrypted database + * @tc.expected: step2. Get result INVALID_ARGS. + */ + KvStoreDelegate::Option option2 = {true, false, true, CipherType::DEFAULT, PASSWD_EMPTY}; + g_mgr.GetKvStore(STORE_ID3, option2, g_kvDelegateCallback); + EXPECT_TRUE(g_kvDelegatePtr == nullptr); + EXPECT_EQ(g_errCode, INVALID_ARGS); + /** + * @tc.steps: step3. Close db. + * @tc.expected: step3. Get result ok. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), INVALID_ARGS); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), INVALID_ARGS); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), NOT_FOUND); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID3), NOT_FOUND); +} + +/** + * @tc.name: OpenEncryptedDb003 + * @tc.desc: Test reopen an encrypted database successfully. + * @tc.type: FUNC + * @tc.require: AR000CQDT8 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, OpenEncryptedDb003, TestSize.Level1) +{ + KvStoreNbDelegate::Option option1 = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID1, option1, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + KvStoreDelegate::Option option2 = {true, false, true, CipherType::DEFAULT, g_passwd3}; + g_mgr.GetKvStore(STORE_ID3, option2, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + /** + * @tc.steps: step1. create single version encrypted database + * @tc.expected: step1. Get result INVALID_ARGS. + */ + KvStoreNbDelegate::Option option3 = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID1, option3, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + /** + * @tc.steps: step2. create multi version encrypted database + * @tc.expected: step2. Get result INVALID_ARGS. + */ + KvStoreDelegate::Option option4 = {true, false, true, CipherType::DEFAULT, g_passwd3}; + g_mgr.GetKvStore(STORE_ID3, option4, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + /** + * @tc.steps: step3. Close db. + * @tc.expected: step3. Get result ok. + */ + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID3), OK); +} + +/** + * @tc.name: OpenEncryptedDb004 + * @tc.desc: Test reopen an encrypted database failed. + * @tc.type: FUNC + * @tc.require: AR000CQDT8 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesEncryptDelegateTest, OpenEncryptedDb004, TestSize.Level1) +{ + KvStoreNbDelegate::Option option1 = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(STORE_ID1, option1, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + KvStoreDelegate::Option option2 = {true, false, true, CipherType::DEFAULT, g_passwd3}; + g_mgr.GetKvStore(STORE_ID3, option2, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_errCode, OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + /** + * @tc.steps: step1. create single version encrypted database + * @tc.expected: step1. Get result INVALID_ARGS. + */ + KvStoreNbDelegate::Option option3 = {true, false, true, CipherType::DEFAULT, g_passwd2}; + g_mgr.GetKvStore(STORE_ID1, option3, g_kvNbDelegateCallback); + EXPECT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_EQ(g_errCode, INVALID_PASSWD_OR_CORRUPTED_DB); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), INVALID_ARGS); + /** + * @tc.steps: step2. create multi version encrypted database + * @tc.expected: step2. Get result INVALID_ARGS. + */ + KvStoreDelegate::Option option4 = {true, false, true, CipherType::DEFAULT, g_passwd2}; + g_mgr.GetKvStore(STORE_ID3, option4, g_kvDelegateCallback); + EXPECT_TRUE(g_kvDelegatePtr == nullptr); + EXPECT_EQ(g_errCode, INVALID_PASSWD_OR_CORRUPTED_DB); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), INVALID_ARGS); + /** + * @tc.steps: step3. Close db. + * @tc.expected: step3. Get result ok. + */ + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID1), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID3), OK); +} +#endif diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_import_and_export_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_import_and_export_test.cpp new file mode 100644 index 00000000..28279b62 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_import_and_export_test.cpp @@ -0,0 +1,1132 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_ENCRYPT +#include + +#include "distributeddb_data_generate_unit_test.h" +#include "platform_specific.h" +#include "process_communicator_test_stub.h" +#include "process_system_api_adapter_impl.h" + +using namespace std; +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { + // define some variables to init a KvStoreDelegateManager object. + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + string g_testDir; + KvStoreConfig g_config; + std::string g_exportFileDir; + std::vector g_junkFilesList; + + // define the g_kvNbDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + KvStoreNbDelegate *g_kvNbDelegatePtrWithoutPasswd = nullptr; + KvStoreDelegate *g_kvDelegatePtrWithoutPasswd = nullptr; + KvStoreDelegate::Option g_option; + const size_t MAX_PASSWD_SIZE = 128; + + // define the g_valueCallback, used to query a value object data from the kvdb. + DBStatus g_valueStatus = INVALID_ARGS; + Value g_value; + + CipherPassword g_passwd1; + CipherPassword g_passwd2; + CipherPassword g_passwd3; + CipherPassword g_passwd4; + // the type of g_valueCallback is function + auto g_valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(g_valueStatus), std::ref(g_value)); + + // the type of g_kvNbDelegateCallback is function + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); + + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + + void RemoveJunkFile(const std::vector &fileList) + { + for (auto &junkFile : fileList) { + std::ifstream file(junkFile); + if (file) { + file.close(); + int result = remove(junkFile.c_str()); + if (result < 0) { + LOGE("failed to delete the db file:%d", errno); + } + } + } + return; + } +} + +class DistributedDBInterfacesImportAndExportTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesImportAndExportTest::SetUpTestCase(void) +{ + g_mgr.SetProcessLabel("6666", "8888"); + g_mgr.SetProcessCommunicator(std::make_shared()); + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + + g_exportFileDir = g_testDir + "/ExportDir"; + OS::MakeDBDirectory(g_exportFileDir); + vector passwdBuffer1(5, 1); // 5 and 1 as random password. + int errCode = g_passwd1.SetValue(passwdBuffer1.data(), passwdBuffer1.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + vector passwdBuffer2(5, 2); // 5 and 2 as random password. + errCode = g_passwd2.SetValue(passwdBuffer2.data(), passwdBuffer2.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + vector passwdBuffer3(5, 3); // 5 and 3 as random password. + errCode = g_passwd3.SetValue(passwdBuffer3.data(), passwdBuffer3.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + vector passwdBuffer4(5, 4); // 5 and 4 as random password. + errCode = g_passwd4.SetValue(passwdBuffer4.data(), passwdBuffer4.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); +} + +void DistributedDBInterfacesImportAndExportTest::TearDownTestCase(void) +{ + OS::RemoveDBDirectory(g_exportFileDir); + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesImportAndExportTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_junkFilesList.clear(); + g_kvDelegateStatus = INVALID_ARGS; + g_kvNbDelegatePtr = nullptr; + g_kvDelegatePtr = nullptr; +} + +void DistributedDBInterfacesImportAndExportTest::TearDown(void) +{ + RemoveJunkFile(g_junkFilesList); +} + +/** + * @tc.name: NormalExport001 + * @tc.desc: The data of the current version of the board is exported and the package file is single. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, NormalExport001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Pre-create folder dir + */ + std::string singleExportFileName = g_exportFileDir + "/singleNormalExport001.$$"; + std::string singleStoreId = "distributed_ExportSingle_001"; + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step2. Specify the path to export the non-encrypted board database. + * @tc.expected: step2. Returns OK + */ + CipherPassword passwd; + EXPECT_EQ(g_kvNbDelegatePtr->Export(singleExportFileName, passwd), OK); + + std::string mulitExportFileName = g_exportFileDir + "/mulitNormalExport001.$$"; + std::string multiStoreId = "distributed_ExportMulit_001"; + g_mgr.GetKvStore(multiStoreId, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step3. Specify the path to export the multi-version non-encrypted database. + * @tc.expected: step3. Returns OK + */ + EXPECT_EQ(g_kvDelegatePtr->Export(mulitExportFileName, passwd), OK); + + // clear resource + g_junkFilesList.push_back(singleExportFileName); + g_junkFilesList.push_back(mulitExportFileName); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(singleStoreId), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(multiStoreId), OK); +} + +/** + * @tc.name: UndisturbedlSingleExport001 + * @tc.desc: Check that the export action is an independent transaction. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, UndisturbedlSingleExport001, TestSize.Level1) +{ + std::string singleStoreId = "distributed_ExportSingle_002"; + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step1. Three known data records are preset in the board database. + */ + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + g_kvNbDelegatePtr->Put(KEY_2, VALUE_2); + g_kvNbDelegatePtr->Put(KEY_3, VALUE_3); + + /** + * @tc.steps: step2. Execute the export action. + */ + std::string singleExportFileName = g_exportFileDir + "/UndisturbedlSingleExport001.$$"; + CipherPassword passwd; + EXPECT_EQ(g_kvNbDelegatePtr->Export(singleExportFileName, passwd), OK); + + /** + * @tc.steps: step3. Insert multiple new data records into the database. + */ + g_kvNbDelegatePtr->Put(KEY_4, VALUE_4); + g_kvNbDelegatePtr->Put(KEY_5, VALUE_5); + + /** + * @tc.steps: step4. Import backup data. + * @tc.expected: step4. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, passwd), OK); + + /** + * @tc.steps: step5. Check whether the imported data is the preset content in step 1. + * @tc.expected: step5. Three preset data records are found. + */ + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_2); + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_3, readValue), OK); + EXPECT_EQ(readValue, VALUE_3); + + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_4, readValue), NOT_FOUND); + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_5, readValue), NOT_FOUND); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(singleStoreId), OK); + g_junkFilesList.push_back(singleExportFileName); +} + +static void GetSnapshotUnitTest(KvStoreDelegate *&kvDelegatePtr, KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus snapshotDelegateStatus = INVALID_ARGS; + auto snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(snapshotDelegateStatus), std::ref(snapshotDelegatePtr)); + + kvDelegatePtr->GetKvStoreSnapshot(nullptr, snapshotDelegateCallback); + EXPECT_TRUE(snapshotDelegateStatus == OK); + ASSERT_TRUE(snapshotDelegatePtr != nullptr); +} + +/** + * @tc.name: UndisturbedlMultiExport001 + * @tc.desc: Check that the export action is an independent transaction. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, UndisturbedlMultiExport001, TestSize.Level1) +{ + std::string multiStoreId = "distributed_Exportmulit_001"; + g_mgr.GetKvStore(multiStoreId, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step1. Three known data records are preset in the board database. + */ + g_kvDelegatePtr->Put(KEY_1, VALUE_1); + g_kvDelegatePtr->Put(KEY_2, VALUE_2); + g_kvDelegatePtr->Put(KEY_3, VALUE_3); + + /** + * @tc.steps: step2. Execute the export action. + */ + std::string mulitExportFileName = g_exportFileDir + "/UndisturbedlMultiExport001.$$"; + CipherPassword passwd; + EXPECT_EQ(g_kvDelegatePtr->Export(mulitExportFileName, passwd), OK); + + /** + * @tc.steps: step3. Insert multiple new data records into the database. + */ + g_kvDelegatePtr->Put(KEY_4, VALUE_4); + g_kvDelegatePtr->Put(KEY_5, VALUE_5); + + /** + * @tc.steps: step4. Import backup data. + * @tc.expected: step4. Returns OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Import(mulitExportFileName, passwd), OK); + + KvStoreSnapshotDelegate *snapshotDelegatePtr = nullptr; + GetSnapshotUnitTest(g_kvDelegatePtr, snapshotDelegatePtr); + + /** + * @tc.steps: step5. Check whether the imported data is the preset content in step 1. + * @tc.expected: step5. Three preset data records are found. + */ + snapshotDelegatePtr->Get(KEY_1, g_valueCallback); + EXPECT_EQ(g_valueStatus, OK); + EXPECT_EQ(g_value, VALUE_1); + snapshotDelegatePtr->Get(KEY_2, g_valueCallback); + EXPECT_EQ(g_valueStatus, OK); + EXPECT_EQ(g_value, VALUE_2); + snapshotDelegatePtr->Get(KEY_3, g_valueCallback); + EXPECT_EQ(g_valueStatus, OK); + EXPECT_EQ(g_value, VALUE_3); + + snapshotDelegatePtr->Get(KEY_4, g_valueCallback); + EXPECT_EQ(g_valueStatus, NOT_FOUND); + snapshotDelegatePtr->Get(KEY_5, g_valueCallback); + EXPECT_EQ(g_valueStatus, NOT_FOUND); + + if (g_kvDelegatePtr != nullptr && snapshotDelegatePtr != nullptr) { + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotDelegatePtr) == OK); + snapshotDelegatePtr = nullptr; + } + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(multiStoreId), OK); + g_junkFilesList.push_back(mulitExportFileName); +} + +/** + * @tc.name: ExportParameterCheck001 + * @tc.desc: Check the verification of abnormal interface parameters. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, ExportParameterCheck001, TestSize.Level1) +{ + std::string singleStoreId = "distributed_ExportSingle_003"; + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + + /** + * @tc.steps: step1. The filePath path does not exist. + * @tc.expected: step1. Return INVALID_ARGS. + */ + std::string invalidFileName = g_exportFileDir + "/tempNotCreated/" + "/ExportParameterCheck001.$$"; + CipherPassword passwd; + EXPECT_EQ(g_kvNbDelegatePtr->Export(invalidFileName, passwd), INVALID_ARGS); + + /** + * @tc.steps: step2. Password length MAX_PASSWD_SIZE + 1 + * @tc.expected: step2. Return INVALID_ARGS. + */ + vector passwdBuffer(MAX_PASSWD_SIZE + 1, MAX_PASSWD_SIZE); + int errCode = passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OVERSIZE); + /** + * @tc.steps: step3. Password length MAX_PASSWD_SIZE + * @tc.expected: step3. Return OK. + */ + passwdBuffer.resize(MAX_PASSWD_SIZE, MAX_PASSWD_SIZE); + errCode = passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + std::string singleExportFileName = g_exportFileDir + "/ExportParameterCheck001.$$"; + EXPECT_EQ(g_kvNbDelegatePtr->Export(singleExportFileName, passwd), OK); + // Check export FILE_ALREADY_EXISTED + EXPECT_EQ(g_kvNbDelegatePtr->Export(singleExportFileName, passwd), FILE_ALREADY_EXISTED); + + /** + * @tc.steps: step4. Delete the database. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(singleStoreId), OK); + + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step5. Use the password to import the file again, + * @tc.expected: step5. Return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, passwd), OK); + Value readValue; + g_kvNbDelegatePtr->Get(KEY_1, readValue); + EXPECT_EQ(readValue, VALUE_1); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(singleStoreId), OK); + g_junkFilesList.push_back(singleExportFileName); +} + +/** + * @tc.name: ExportParameterCheck002 + * @tc.desc: Check the verification of abnormal interface parameters. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, ExportParameterCheck002, TestSize.Level1) +{ + std::string multiStoreId = "distributed_ExportMulti_003"; + g_mgr.GetKvStore(multiStoreId, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + g_kvDelegatePtr->Put(KEY_1, VALUE_1); + + /** + * @tc.steps: step1. The filePath path does not exist. + * @tc.expected: step1. Return INVALID_ARGS. + */ + std::string invalidExportFileName = g_exportFileDir + "/tempNotCreated/" + "/ExportParameterCheck002.$$"; + CipherPassword passwd; + EXPECT_EQ(g_kvDelegatePtr->Export(invalidExportFileName, passwd), INVALID_ARGS); + + /** + * @tc.steps: step2. Password length MAX_PASSWD_SIZE + 1 + * @tc.expected: step2. Return INVALID_ARGS. + */ + vector passwdBuffer(MAX_PASSWD_SIZE + 1, MAX_PASSWD_SIZE); + int errCode = passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OVERSIZE); + /** + * @tc.steps: step3. Password length MAX_PASSWD_SIZE + * @tc.expected: step3. Return OK. + */ + passwdBuffer.resize(MAX_PASSWD_SIZE, MAX_PASSWD_SIZE); + errCode = passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + std::string multiExportFileName = g_exportFileDir + "/ExportParameterCheck002.$$"; + EXPECT_EQ(g_kvDelegatePtr->Export(multiExportFileName, passwd), OK); + EXPECT_EQ(g_kvDelegatePtr->Export(multiExportFileName, passwd), FILE_ALREADY_EXISTED); // Check export INVALID_FILE + + /** + * @tc.steps: step4. Delete the database. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(multiStoreId), OK); + + g_mgr.GetKvStore(multiStoreId, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step5. Use the password to import the file again, + * @tc.expected: step5. Return OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Import(multiExportFileName, passwd), OK); + + KvStoreSnapshotDelegate *snapshotDelegatePtr = nullptr; + GetSnapshotUnitTest(g_kvDelegatePtr, snapshotDelegatePtr); + + snapshotDelegatePtr->Get(KEY_1, g_valueCallback); + EXPECT_EQ(g_valueStatus, OK); + EXPECT_EQ(g_value, VALUE_1); + + if (g_kvDelegatePtr != nullptr && snapshotDelegatePtr != nullptr) { + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotDelegatePtr) == OK); + snapshotDelegatePtr = nullptr; + } + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(multiStoreId), OK); + g_junkFilesList.push_back(multiExportFileName); +} + +/** + * @tc.name: NormalImport001 + * @tc.desc: Normal import capability for single version, parameter verification capability + * @tc.type: FUNC + * @tc.require: AR000D487A + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, NormalImport001, TestSize.Level1) +{ + std::string singleExportFileName = g_exportFileDir + "/NormalImport001.$$"; + std::string singleStoreId = "distributed_Importmulti_001"; + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + + CipherPassword passwd; + EXPECT_EQ(g_kvNbDelegatePtr->Export(singleExportFileName, passwd), OK); + + /** + * @tc.steps: step1. Import the invalid path. + * @tc.expected: step1. Return INVALID_ARGS. + */ + std::string invalidPath = g_exportFileDir + "sdad" + "/NormalImport001.$$"; + EXPECT_EQ(g_kvNbDelegatePtr->Import(invalidPath, passwd), INVALID_ARGS); + + /** + * @tc.steps: step2. Import an authorized path with an incorrect password. + * @tc.expected: step2. Return INVALID_FILE. + */ + vector passwdBuffer(MAX_PASSWD_SIZE, MAX_PASSWD_SIZE); + int errCode = passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, passwd), INVALID_FILE); + + /** + * @tc.steps: step3. Import a permission path without a password. + * @tc.expected: step3. Return OK. + */ + errCode = passwd.Clear(); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, passwd), OK); + + /** + * @tc.steps: step4. Check whether the data is the same as the backup database. + * @tc.expected: step4. Same database data. + */ + Value readValue; + g_kvNbDelegatePtr->Get(KEY_1, readValue); + EXPECT_EQ(readValue, VALUE_1); + + // clear resource + g_junkFilesList.push_back(singleExportFileName); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(singleStoreId), OK); +} + +/** + * @tc.name: NormalImport001 + * @tc.desc: Normal import capability for multi version, parameter verification capability + * @tc.type: FUNC + * @tc.require: AR000D487A + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, NormalImport002, TestSize.Level1) +{ + std::string multiExportFileName = g_exportFileDir + "/NormalImport002.$$"; + std::string multiStoreId = "distributed_ImportSingle_002"; + g_mgr.GetKvStore(multiStoreId, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + g_kvDelegatePtr->Put(KEY_1, VALUE_1); + + CipherPassword passwd; + EXPECT_EQ(g_kvDelegatePtr->Export(multiExportFileName, passwd), OK); + + /** + * @tc.steps: step1. Import the invalid path. + * @tc.expected: step1. Return INVALID_ARGS. + */ + std::string invalidPath = g_exportFileDir + "sdad" + "/NormalImport002.$$"; + EXPECT_EQ(g_kvDelegatePtr->Import(invalidPath, passwd), INVALID_ARGS); + + /** + * @tc.steps: step2. Import an authorized path with an incorrect password. + * @tc.expected: step2. Return INVALID_FILE. + */ + vector passwdBuffer(MAX_PASSWD_SIZE, MAX_PASSWD_SIZE); + int errCode = passwd.SetValue(passwdBuffer.data(), passwdBuffer.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + EXPECT_EQ(g_kvDelegatePtr->Import(multiExportFileName, passwd), INVALID_FILE); + + g_kvDelegatePtr->Delete(KEY_1); + /** + * @tc.steps: step3. Import a permission path without a password. + * @tc.expected: step3. Return OK. + */ + errCode = passwd.Clear(); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + EXPECT_EQ(g_kvDelegatePtr->Import(multiExportFileName, passwd), OK); + + KvStoreSnapshotDelegate *snapshotDelegatePtr = nullptr; + GetSnapshotUnitTest(g_kvDelegatePtr, snapshotDelegatePtr); + + /** + * @tc.steps: step4. Check whether the data is the same as the backup database. + * @tc.expected: step4. Same database data. + */ + snapshotDelegatePtr->Get(KEY_1, g_valueCallback); + EXPECT_EQ(g_valueStatus, OK); + EXPECT_EQ(g_value, VALUE_1); + + if (g_kvDelegatePtr != nullptr && snapshotDelegatePtr != nullptr) { + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshotDelegatePtr) == OK); + snapshotDelegatePtr = nullptr; + } + + // clear resource + g_junkFilesList.push_back(multiExportFileName); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(multiStoreId), OK); +} + +/** + * @tc.name: ExceptionFileImport001 + * @tc.desc: Normal import capability for single version, parameter verification capability + * @tc.type: FUNC + * @tc.require: AR000D487A + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, ExceptionFileImport001, TestSize.Level1) +{ + std::string singleExportFileName = g_exportFileDir + "/ExceptionFileImport001.$$"; + std::string singleStoreId = "distributed_ImportExceptionsigle_001"; + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + g_kvNbDelegatePtr->Put(KEY_2, VALUE_2); + + CipherPassword passwd; + EXPECT_EQ(g_kvNbDelegatePtr->Export(singleExportFileName, passwd), OK); + + /** + * @tc.steps: step1. Repeat import backup file to same database. + * @tc.expected: step1. Return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, passwd), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, passwd), OK); + + /** + * @tc.steps: step2. Change the name of file1 to file2. + */ + std::string newSingleExportFileName = g_exportFileDir + "/newExceptionFileImport001.$$"; + EXPECT_EQ(rename(singleExportFileName.c_str(), newSingleExportFileName.c_str()), 0); + + /** + * @tc.steps: step3. Import file1 into the database. + * @tc.expected: step3. Return INVALID_FILE. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, passwd), INVALID_FILE); + + /** + * @tc.steps: step4. Import file2 into the database. + * @tc.expected: step4. Return INVALID_FILE. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Import(newSingleExportFileName, passwd), OK); + + // clear resource + g_junkFilesList.push_back(singleExportFileName); + g_junkFilesList.push_back(newSingleExportFileName); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(singleStoreId), OK); +} + +/** + * @tc.name: ExceptionFileImport002 + * @tc.desc: Normal import capability for multi version, parameter verification capability + * @tc.type: FUNC + * @tc.require: AR000D487A + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, ExceptionFileImport002, TestSize.Level1) +{ + std::string multiExportFileName = g_exportFileDir + "/ExceptionFileImport002.$$"; + std::string multiStoreId = "distributed_ImportExceptionMulti_001"; + g_mgr.GetKvStore(multiStoreId, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + g_kvDelegatePtr->Put(KEY_1, VALUE_1); + + CipherPassword passwd; + EXPECT_EQ(g_kvDelegatePtr->Export(multiExportFileName, passwd), OK); + + /** + * @tc.steps: step1. Import the backup file that has been tampered with to the multi-version database. + * @tc.expected: step1. Return INVALID_FILE. + */ + EXPECT_EQ(DistributedDBToolsUnitTest::ModifyDatabaseFile(multiExportFileName), 0); + EXPECT_EQ(g_kvDelegatePtr->Import(multiExportFileName, passwd), INVALID_FILE); + + // clear resource + g_junkFilesList.push_back(multiExportFileName); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(multiStoreId), OK); +} + +/** + * @tc.name: ExceptionFileImport003 + * @tc.desc: The data of the current version of the board is exported and the package file is single. + * @tc.type: FUNC + * @tc.require: AR000D487A + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, ExceptionFileImport003, TestSize.Level1) +{ + std::string singleExportFileName = g_exportFileDir + "/singleExceptionFileImport003.$$"; + std::string singleStoreId = "distributed_ExportSingle_001"; + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + CipherPassword passwd; + EXPECT_EQ(g_kvNbDelegatePtr->Export(singleExportFileName, passwd), OK); + + std::string mulitExportFileName = g_exportFileDir + "/mulitExceptionFileImport003.$$"; + std::string multiStoreId = "distributed_ExportMulit_001"; + g_mgr.GetKvStore(multiStoreId, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_kvDelegatePtr->Export(mulitExportFileName, passwd), OK); + + /** + * @tc.steps: step1. Use the single ver import interface. The file path is a multi-version backup file. + * @tc.expected: step1. Return INVALID_FILE. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Import(mulitExportFileName, passwd), INVALID_FILE); + + /** + * @tc.steps: step2. Use the single ver import interface. The file path is a single-version backup file. + * @tc.expected: step2. Return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, passwd), OK); + + /** + * @tc.steps: step3. Use the multi-version import interface. The file path is a single-version backup file. + * @tc.expected: step3. Return INVALID_FILE. + */ + EXPECT_EQ(g_kvDelegatePtr->Import(singleExportFileName, passwd), INVALID_FILE); + + /** + * @tc.steps: step4. Use the multi-version import interface. The file path is a multi-version backup file. + * @tc.expected: step4. Return INVALID_FILE. + */ + EXPECT_EQ(g_kvDelegatePtr->Import(mulitExportFileName, passwd), OK); + + // clear resource + g_junkFilesList.push_back(singleExportFileName); + g_junkFilesList.push_back(mulitExportFileName); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(singleStoreId), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(multiStoreId), OK); +} + +/** + * @tc.name: ExceptionFileImport004 + * @tc.desc: The data of the current version of the board is exported and the package file is single. + * @tc.type: FUNC + * @tc.require: AR000D487A + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, ExceptionFileImport004, TestSize.Level1) +{ + std::string singleExportFileName = g_exportFileDir + "/singleExceptionFileImport004.$$"; + std::string singleStoreId = "distributed_ExportSingle_004"; + KvStoreNbDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + ASSERT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->Export(singleExportFileName, g_passwd2), OK); + + std::string mulitExportFileName = g_exportFileDir + "/mulitExceptionFileImport004.$$"; + std::string multiStoreId = "distributed_ExportMulit_004"; + + KvStoreDelegate::Option multiOption = {true, false, true, CipherType::DEFAULT, g_passwd1}; + g_mgr.GetKvStore(multiStoreId, multiOption, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + EXPECT_EQ(g_kvDelegatePtr->Export(mulitExportFileName, g_passwd2), OK); + + /** + * @tc.steps: step1. Use the diff passwd, try to import database. + */ + CipherPassword passwd; + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, passwd), INVALID_FILE); + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, g_passwd1), INVALID_FILE); + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, g_passwd2), OK); + + EXPECT_EQ(g_kvDelegatePtr->Import(mulitExportFileName, passwd), INVALID_FILE); + EXPECT_EQ(g_kvDelegatePtr->Import(mulitExportFileName, g_passwd1), INVALID_FILE); + EXPECT_EQ(g_kvDelegatePtr->Import(mulitExportFileName, g_passwd2), OK); + + // clear resource + g_junkFilesList.push_back(singleExportFileName); + g_junkFilesList.push_back(mulitExportFileName); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(singleStoreId), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(multiStoreId), OK); +} + +static void TryDbForPasswordIndependence001() +{ + std::string singleStoreIdNoPasswd = "distributed_ExportSingle_005"; + std::string singleStoreId = "distributed_ExportSingle_006"; + + /** + * @tc.steps: step4. Run the p3 command to open the database db1. + * @tc.expected: step4. Return ERROR. + */ + KvStoreNbDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd3}; + g_mgr.GetKvStore(singleStoreIdNoPasswd, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_NE(g_kvDelegateStatus, OK); + + /** + * @tc.steps: step5. Run the p4 command to open the database db2. + * @tc.expected: step5. Return ERROR. + */ + option = {true, false, true, CipherType::DEFAULT, g_passwd4}; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + ASSERT_TRUE(g_kvDelegateStatus != OK); + + /** + * @tc.steps: step6. Open the db1 directly. + * @tc.expected: step6. Return OK. + */ + option = {true, false, false, CipherType::DEFAULT, g_passwd3}; + g_mgr.GetKvStore(singleStoreIdNoPasswd, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + ASSERT_TRUE(g_kvDelegateStatus == OK); + g_kvNbDelegatePtrWithoutPasswd = g_kvNbDelegatePtr; + + /** + * @tc.steps: step7. Open the db1 directly + * @tc.expected: step7. Return ERROR. + */ + option = {true, false, false, CipherType::DEFAULT, g_passwd3}; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + ASSERT_TRUE(g_kvDelegateStatus != OK); + + /** + * @tc.steps: step8. Run the p2 command to open the db2 file. + * @tc.expected: step8. Return ERROR. + */ + option = {true, false, true, CipherType::DEFAULT, g_passwd2}; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + ASSERT_TRUE(g_kvDelegateStatus == OK); +} + +/** + * @tc.name: PasswordIndependence001 + * @tc.desc: The data of the current version of the board is exported and the package file is single. + * @tc.type: FUNC + * @tc.require: AR000D487B + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, PasswordIndependence001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Back up a single database db1 No password backup password p3 + */ + std::string singleExportFileNameNoPasswd = g_exportFileDir + "/singleNoPasswdIndependence001.$$"; + std::string singleStoreIdNoPasswd = "distributed_ExportSingle_005"; + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(singleStoreIdNoPasswd, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + g_kvNbDelegatePtrWithoutPasswd = g_kvNbDelegatePtr; + + EXPECT_EQ(g_kvNbDelegatePtr->Export(singleExportFileNameNoPasswd, g_passwd3), OK); + + /** + * @tc.steps: step2. Back up the database of the single version db2 Password p2 Backup file password p4 + */ + std::string singleExportFileName = g_exportFileDir + "/singleIndependence001.$$"; + std::string singleStoreId = "distributed_ExportSingle_006"; + option = {true, false, true, CipherType::DEFAULT, g_passwd2}; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + ASSERT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->Export(singleExportFileName, g_passwd4), OK); + + /** + * @tc.steps: step3. Recover the backup file. + */ + EXPECT_EQ(g_kvNbDelegatePtrWithoutPasswd->Import(singleExportFileNameNoPasswd, g_passwd3), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, g_passwd4), OK); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrWithoutPasswd), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + (void)TryDbForPasswordIndependence001(); + + // clear resource + g_junkFilesList.push_back(singleExportFileName); + g_junkFilesList.push_back(singleExportFileNameNoPasswd); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(singleStoreId), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtrWithoutPasswd), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(singleStoreIdNoPasswd), OK); +} + +static void TryDbForPasswordIndependence002() +{ + std::string multiStoreIdNoPasswd = "distributed_ExportMulti_007"; + std::string multiStoreId = "distributed_ExportMulti_008"; + + KvStoreDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd3}; + g_mgr.GetKvStore(multiStoreIdNoPasswd, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + ASSERT_TRUE(g_kvDelegateStatus != OK); + + option = {true, false, true, CipherType::DEFAULT, g_passwd4}; + g_mgr.GetKvStore(multiStoreId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + ASSERT_TRUE(g_kvDelegateStatus != OK); + + option = {true, false, false, CipherType::DEFAULT, g_passwd3}; + g_mgr.GetKvStore(multiStoreIdNoPasswd, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + ASSERT_TRUE(g_kvDelegateStatus == OK); + g_kvDelegatePtrWithoutPasswd = g_kvDelegatePtr; + + option = {true, false, false, CipherType::DEFAULT, g_passwd3}; + g_mgr.GetKvStore(multiStoreId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + ASSERT_TRUE(g_kvDelegateStatus != OK); + + option = {true, false, true, CipherType::DEFAULT, g_passwd2}; + g_mgr.GetKvStore(multiStoreId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + ASSERT_TRUE(g_kvDelegateStatus == OK); +} + +/** + * @tc.name: PasswordIndependence002 + * @tc.desc: The data of the current version of the board is exported and the package file is single. + * @tc.type: FUNC + * @tc.require: AR000D487B + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, PasswordIndependence002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Back up a single database db1 No password backup password p3 + */ + std::string multiExportFileNameNoPasswd = g_exportFileDir + "/multiNoPasswdIndependence001.$$"; + std::string multiStoreIdNoPasswd = "distributed_ExportMulti_007"; + KvStoreDelegate::Option option; + g_mgr.GetKvStore(multiStoreIdNoPasswd, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + g_kvDelegatePtrWithoutPasswd = g_kvDelegatePtr; + + EXPECT_EQ(g_kvDelegatePtr->Export(multiExportFileNameNoPasswd, g_passwd3), OK); + + /** + * @tc.steps: step2. Back up the database of the single version db2 Password p2 Backup file password p4 + */ + std::string multiExportFileName = g_exportFileDir + "/multiIndependence001.$$"; + std::string multiStoreId = "distributed_ExportMulti_008"; + option = {true, false, true, CipherType::DEFAULT, g_passwd2}; + g_mgr.GetKvStore(multiStoreId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + ASSERT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvDelegatePtr->Export(multiExportFileName, g_passwd4), OK); + + /** + * @tc.steps: step3. Recover the backup file. + */ + EXPECT_EQ(g_kvDelegatePtrWithoutPasswd->Import(multiExportFileNameNoPasswd, g_passwd3), OK); + EXPECT_EQ(g_kvDelegatePtr->Import(multiExportFileName, g_passwd4), OK); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtrWithoutPasswd), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + /** + * @tc.steps: step4. Try diff passwd. + */ + (void)TryDbForPasswordIndependence002(); + + // clear resource + g_junkFilesList.push_back(multiExportFileName); + g_junkFilesList.push_back(multiExportFileNameNoPasswd); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(multiStoreId), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtrWithoutPasswd), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(multiStoreIdNoPasswd), OK); +} + +/** + * @tc.name: PasswordIndependence002 + * @tc.desc: The data of the current version of the board is exported and the package file is single. + * @tc.type: FUNC + * @tc.require: AR000D487B + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, PasswordIndependence003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Back up the (passwd1) encryption single-version (passwd2) database. + */ + std::string singleExportFileName = g_exportFileDir + "/singleIndependence003.$$"; + std::string singleStoreId = "distributed_ExportSingle_009"; + KvStoreNbDelegate::Option option = {true, false, true, CipherType::DEFAULT, g_passwd2}; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + ASSERT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->Export(singleExportFileName, g_passwd1), OK); + + /** + * @tc.steps: step2. Rekey The password by passwd3 + */ + g_kvNbDelegatePtr->Rekey(g_passwd3); + + /** + * @tc.steps: step3. Import the database using passwd3. + * @tc.expected: step3. Return INVALID_FILE. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, g_passwd3), INVALID_FILE); + + /** + * @tc.steps: step4. Import the database using passwd1. + * @tc.expected: step4. Return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, g_passwd1), OK); + + /** + * @tc.steps: step5. Repeat step 1 - 4. + */ + std::string multiExportFileName = g_exportFileDir + "/multiIndependence003.$$"; + std::string multiStoreId = "distributed_ExportMulti_010"; + KvStoreDelegate::Option multiOption = {true, false, true, CipherType::DEFAULT, g_passwd2}; + g_mgr.GetKvStore(multiStoreId, multiOption, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + ASSERT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvDelegatePtr->Export(multiExportFileName, g_passwd1), OK); + remove(singleExportFileName.c_str()); + + EXPECT_EQ(g_kvDelegatePtr->Import(multiExportFileName, g_passwd3), INVALID_FILE); + EXPECT_EQ(g_kvDelegatePtr->Import(multiExportFileName, g_passwd1), OK); + + // clear resource + g_junkFilesList.push_back(multiExportFileName); + g_junkFilesList.push_back(singleExportFileName); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(multiStoreId), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(singleStoreId), OK); +} + +/** + * @tc.name: SeparaDbExportAndImport + * @tc.desc: Import and export after Separate database. + * @tc.type: FUNC + * @tc.require: AR000D487B + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, SeparaDbExportAndImport, TestSize.Level1) +{ + std::shared_ptr adapter = std::make_shared(); + EXPECT_TRUE(adapter != nullptr); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(adapter); + + std::string singleExportFileName = g_exportFileDir + "/SeparaDbExportAndImport.$$"; + std::string singleStoreId = "distributed_ExportSingle_010"; + KvStoreNbDelegate::Option option = {true, false, false}; + SecurityOption secOption{SecurityLabel::S3, SecurityFlag::SECE}; + option.secOption = secOption; + + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + + CipherPassword passwd; + EXPECT_EQ(g_kvNbDelegatePtr->Export(singleExportFileName, passwd), OK); + + g_kvNbDelegatePtr->Put(KEY_2, VALUE_2); + + EXPECT_EQ(g_kvNbDelegatePtr->Import(singleExportFileName, passwd), OK); + Value valueRead; + g_kvNbDelegatePtr->Get(KEY_2, valueRead); + EXPECT_EQ(valueRead, Value()); + g_kvNbDelegatePtr->Get(KEY_1, valueRead); + EXPECT_EQ(valueRead, VALUE_1); + g_kvNbDelegatePtr->Put(KEY_3, VALUE_3); + + EXPECT_EQ(g_kvNbDelegatePtr->Rekey(g_passwd1), OK); + g_kvNbDelegatePtr->Get(KEY_3, valueRead); + EXPECT_EQ(valueRead, VALUE_3); + + // clear resource + g_junkFilesList.push_back(singleExportFileName); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + + option.passwd = g_passwd1; + option.isEncryptedDb = true; + g_mgr.GetKvStore(singleStoreId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(singleStoreId), OK); +} + +/** + * @tc.name: SeparaDbExportAndImport + * @tc.desc: Import and export after Separate database. + * @tc.type: FUNC + * @tc.require: AR000D487B + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesImportAndExportTest, SeparaDbNoPasswdRekey, TestSize.Level1) +{ + std::shared_ptr adapter = std::make_shared(); + EXPECT_TRUE(adapter != nullptr); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(adapter); + + KvStoreNbDelegate::Option option = {true, false, true}; + SecurityOption secOption{SecurityLabel::S3, SecurityFlag::SECE}; + option.secOption = secOption; + option.passwd = g_passwd1; + g_mgr.GetKvStore("SeparaDbNoPasswdRekey", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + + EXPECT_EQ(g_kvDelegateStatus, OK); + EXPECT_EQ(g_kvNbDelegatePtr->Rekey(g_passwd2), OK); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + option.passwd = g_passwd2; + g_mgr.GetKvStore("SeparaDbNoPasswdRekey", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("SeparaDbNoPasswdRekey"), OK); +} +#endif diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_index_unit_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_index_unit_test.cpp new file mode 100644 index 00000000..895eb2f2 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_index_unit_test.cpp @@ -0,0 +1,857 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_JSON +#include +#include +#include "db_common.h" +#include "db_constant.h" +#include "distributeddb_tools_unit_test.h" +#include "query.h" +#include "schema_constant.h" +#include "schema_utils.h" +#include "sqlite_import.h" +#include "sqlite_local_kvdb_connection.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + // Directory and delegate related + string g_testDir; + KvStoreConfig g_config; + const string USER_NAME = "TEST0"; + const string APP_NAME = "OHOS"; + KvStoreDelegateManager g_mgr(APP_NAME, USER_NAME); + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + DBStatus g_kvDelegateStatus2 = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr2 = nullptr; + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); + auto g_kvNbDelegateCallback2 = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus2), std::ref(g_kvNbDelegatePtr2)); + + string GetKvStoreDirectory(const string &storeId, int databaseType) + { + string identifier = USER_NAME + "-" + APP_NAME + "-" + storeId; + string hashIdentifierName = DBCommon::TransferHashString(identifier); + string identifierName = DBCommon::TransferStringToHex(hashIdentifierName); + string filePath = g_testDir + "/" + identifierName + "/"; + if (databaseType == DBConstant::DB_TYPE_LOCAL) { // local + filePath += (DBConstant::LOCAL_SUB_DIR + "/" + DBConstant::LOCAL_DATABASE_NAME + + DBConstant::SQLITE_DB_EXTENSION); + } else if (databaseType == DBConstant::DB_TYPE_SINGLE_VER) { // single ver + filePath += (DBConstant::SINGLE_SUB_DIR + "/" + DBConstant::MAINDB_DIR + "/" + + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION); + } else if (databaseType == DBConstant::DB_TYPE_MULTI_VER) { // multi ver + filePath += (DBConstant::MULTI_SUB_DIR + "/" + DBConstant::MULTI_VER_DATA_STORE + + DBConstant::SQLITE_DB_EXTENSION); + } else { + filePath = ""; + } + + return filePath; + } + + // Query sqlite_master related + const string SQL_QUERY_INDEX = "SELECT COUNT(*) FROM sqlite_master where type = 'index' and name = "; + int CallbackReturnCount(void *data, int argc, char **argv, char **azColName) + { + if (argc == 1) { + int count = strtol(*(argv), nullptr, 10); // 10: decimal + if (data != nullptr) { + int *mid = static_cast(data); + *mid = count; + } + } + return 0; + } + + // Schema and value info related + FieldName GenerateFieldName(uint32_t serial, uint32_t level, bool fullLength) + { + FieldName result = "Serial_"; + result += to_string(serial); + result += "_Level_"; + result += to_string(level); + if (fullLength) { + while (result.size() < SchemaConstant::SCHEMA_FEILD_NAME_LENGTH_MAX) { + result.push_back('_'); + } + } + return result; + } + + FieldPath GenerateFieldPath(uint32_t totalLevel, uint32_t serial, bool fullLength) + { + FieldPath result; + for (uint32_t level = 0; level < totalLevel; level++) { + string fieldName = GenerateFieldName(serial, level, fullLength); + result.push_back(fieldName); + } + return result; + } + + string GenerateSchemaIndexArray(const vector &indexAll) + { + string result = "["; + for (auto &entry : indexAll) { + result += "\""; + result += SchemaUtils::FieldPathString(entry); + result += "\","; + } + if (!indexAll.empty()) { + result.pop_back(); + } + result += "]"; + return result; + } + + string GenerateEachSchemaDefine(const FieldPath &eachPath) + { + string result; + for (auto iter = eachPath.rbegin(); iter != eachPath.rend(); iter++) { + if (result.empty()) { + result = string("\"") + *iter + "\":\"INTEGER\""; + } else { + result = string("\"") + *iter + "\":{" + result + "}"; + } + } + return result; + } + + string GenerateSchemaString(const vector &define, const vector &index, int skipSize, + bool hasIndex, bool hasSkipSize) + { + string result = "{\"SCHEMA_VERSION\":\"1.0\",\"SCHEMA_MODE\":\"STRICT\",\"SCHEMA_DEFINE\":{"; + for (auto &entry : define) { + string defineStr = GenerateEachSchemaDefine(entry); + result += defineStr; + result += ","; + } + if (!define.empty()) { + result.pop_back(); + } + result += "}"; + if (hasIndex) { + result += ",\"SCHEMA_INDEXES\":"; + result += GenerateSchemaIndexArray(index); + } + if (hasSkipSize) { + result += ",\"SCHEMA_SKIPSIZE\":"; + result += to_string(skipSize); + } + result += "}"; + return result; + } + + string GenerateValueItem(const FieldPath &eachPath, int intValue) + { + string result; + for (auto iter = eachPath.rbegin(); iter != eachPath.rend(); iter++) { + if (result.empty()) { + result = string("\"") + *iter + "\":" + to_string(intValue); + } else { + result = string("\"") + *iter + "\":{" + result + "}"; + } + } + return result; + } + string GenerateValue(const vector &define, uint32_t skipSize) + { + int intValue = 0; + string result(skipSize, '*'); + result += "{"; + for (auto &entry : define) { + string defineStr = GenerateValueItem(entry, intValue++); + result += defineStr; + result += ","; + } + if (!define.empty()) { + result.pop_back(); + } + result += "}"; + return result; + } + + vector g_pathGroup1; + vector g_pathGroup2; + vector g_pathStrGroup1; + vector g_pathStrGroup2; + vector g_definePath; + string g_schemaString1; + string g_schemaString2; + string g_valueString1; + string g_valueString2; + + void ResetGlobalVariable() + { + g_pathGroup1.clear(); + g_pathGroup2.clear(); + g_pathStrGroup1.clear(); + g_pathStrGroup2.clear(); + g_definePath.clear(); + g_schemaString1.clear(); + g_schemaString2.clear(); + g_valueString1.clear(); + g_valueString2.clear(); + } + + void PrepareCommonInfo(bool fullLength) + { + int serial = 0; + for (uint32_t level = 1; level <= SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX; level++) { + FieldPath path = GenerateFieldPath(level, serial, fullLength); + string pathStr = SchemaUtils::FieldPathString(path); + g_pathGroup1.push_back(path); + g_pathStrGroup1.push_back(pathStr); + serial++; + } + for (uint32_t level = 1; level <= SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX; level++) { + FieldPath path = GenerateFieldPath(level, serial, fullLength); + string pathStr = SchemaUtils::FieldPathString(path); + g_pathGroup2.push_back(path); + g_pathStrGroup2.push_back(pathStr); + serial++; + } + } + + inline void CheckIndexFromDbFile(sqlite3 *db, const vector &indexToCheck, int expectCount) + { + for (auto &str : indexToCheck) { + string querySeq = SQL_QUERY_INDEX + "'" + str + "'"; + int count = -1; + EXPECT_EQ(sqlite3_exec(db, querySeq.c_str(), CallbackReturnCount, &count, nullptr), SQLITE_OK); + EXPECT_EQ(count, expectCount); + } + } +} + +class DistributedDBInterfacesIndexUnitTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown() {}; +}; + +void DistributedDBInterfacesIndexUnitTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesIndexUnitTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("[TestSuiteTearDown] rm test db files error!"); + } +} + +void DistributedDBInterfacesIndexUnitTest::SetUp() +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +namespace { + void PrepareInfoForCrudIndex001() + { + PrepareCommonInfo(false); + g_definePath.insert(g_definePath.end(), g_pathGroup1.begin(), g_pathGroup1.end()); + g_definePath.insert(g_definePath.end(), g_pathGroup2.begin(), g_pathGroup2.end()); + g_schemaString1 = GenerateSchemaString(g_definePath, g_pathGroup1, 0, true, false); + g_schemaString2 = GenerateSchemaString(g_definePath, g_definePath, 0, true, false); + LOGI("[PrepareInfoForCrudIndex001] g_schemaString1=%s", g_schemaString1.c_str()); + LOGI("[PrepareInfoForCrudIndex001] g_schemaString2=%s", g_schemaString2.c_str()); + } +} +/** + * @tc.name: CrudIndex001 + * @tc.desc: Test whether adding index is normal + * @tc.type: FUNC + * @tc.require: AR000DR9K8 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBInterfacesIndexUnitTest, CrudIndex001, TestSize.Level1) +{ + PrepareInfoForCrudIndex001(); + sqlite3 *db = nullptr; + string storeId = "CrudIndex001"; + string filePath = GetKvStoreDirectory(storeId, DBConstant::DB_TYPE_SINGLE_VER); + KvStoreNbDelegate::Option option = {true, false, false}; + /** + * @tc.steps:step1. Specify the schema containing all levels of index to open the schema mode database. + * @tc.expected: step1. return OK. + */ + option.schema = g_schemaString1; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + /** + * @tc.steps:step2. Use the sql statement to get the count of the following index fields that will be added. + * @tc.expected: step2. count == 0. + */ + EXPECT_EQ(sqlite3_open_v2(filePath.c_str(), &db, SQLITE_OPEN_READWRITE, nullptr), SQLITE_OK); + CheckIndexFromDbFile(db, g_pathStrGroup2, 0); + sqlite3_close(db); + /** + * @tc.steps:step3. Close the database. + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + /** + * @tc.steps:step4. The original schema adds the above index fields, + * generates a new schema and opens the database with this schema. + * @tc.expected: step4. return OK. + */ + option.schema = g_schemaString2; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + /** + * @tc.steps:step5. Use the sql statement to get the count of the following index fields that are added. + * @tc.expected: step5. count == 1. + */ + EXPECT_EQ(sqlite3_open_v2(filePath.c_str(), &db, SQLITE_OPEN_READWRITE, nullptr), SQLITE_OK); + CheckIndexFromDbFile(db, g_pathStrGroup2, 1); + sqlite3_close(db); + // Clear + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("CrudIndex001"), OK); + ResetGlobalVariable(); +} + +namespace { + void PrepareInfoForCrudIndex002() + { + PrepareCommonInfo(false); + g_definePath.insert(g_definePath.end(), g_pathGroup1.begin(), g_pathGroup1.end()); + g_schemaString1 = GenerateSchemaString(g_definePath, g_pathGroup1, 0, true, false); + g_schemaString2 = GenerateSchemaString(g_definePath, vector(), 0, true, false); + LOGI("[PrepareInfoForCrudIndex002] g_schemaString1=%s", g_schemaString1.c_str()); + LOGI("[PrepareInfoForCrudIndex002] g_schemaString2=%s", g_schemaString2.c_str()); + } +} +/** + * @tc.name: CrudIndex002 + * @tc.desc: Test whether deleting index is normal + * @tc.type: FUNC + * @tc.require: AR000DR9K8 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBInterfacesIndexUnitTest, CrudIndex002, TestSize.Level1) +{ + PrepareInfoForCrudIndex002(); + sqlite3 *db = nullptr; + string storeId = "CrudIndex002"; + string filePath = GetKvStoreDirectory(storeId, DBConstant::DB_TYPE_SINGLE_VER); + KvStoreNbDelegate::Option option = {true, false, false}; + /** + * @tc.steps:step1. Specify the schema containing all levels of index to open the schema mode database. + * @tc.expected: step1. return OK. + */ + option.schema = g_schemaString1; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + /** + * @tc.steps:step2. Use the sql statement to get the count of the following index fields that will be deleted. + * @tc.expected: step2. count == 1. + */ + EXPECT_EQ(sqlite3_open_v2(filePath.c_str(), &db, SQLITE_OPEN_READWRITE, nullptr), SQLITE_OK); + CheckIndexFromDbFile(db, g_pathStrGroup1, 1); + sqlite3_close(db); + /** + * @tc.steps:step3. Close the database. + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + /** + * @tc.steps:step4. The original schema delete the above index fields, + * generates a new schema and opens the database with this schema. + * @tc.expected: step4. return OK. + */ + option.schema = g_schemaString2; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + /** + * @tc.steps:step5. Use the sql statement to get the count of the following index fields that are deleted. + * @tc.expected: step5. count == 0. + */ + EXPECT_EQ(sqlite3_open_v2(filePath.c_str(), &db, SQLITE_OPEN_READWRITE, nullptr), SQLITE_OK); + CheckIndexFromDbFile(db, g_pathStrGroup1, 0); + sqlite3_close(db); + // Clear + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("CrudIndex002"), OK); + ResetGlobalVariable(); +} + +namespace { + void PrepareInfoForCrudIndex003() + { + PrepareCommonInfo(false); + g_definePath.insert(g_definePath.end(), g_pathGroup1.begin(), g_pathGroup1.end()); + g_definePath.insert(g_definePath.end(), g_pathGroup2.begin(), g_pathGroup2.end()); + g_schemaString1 = GenerateSchemaString(g_definePath, g_pathGroup1, 0, true, false); + g_schemaString2 = GenerateSchemaString(g_definePath, g_pathGroup2, 0, true, false); + LOGI("[PrepareInfoForCrudIndex003] g_schemaString1=%s", g_schemaString1.c_str()); + LOGI("[PrepareInfoForCrudIndex003] g_schemaString2=%s", g_schemaString2.c_str()); + } +} +/** + * @tc.name: CrudIndex003 + * @tc.desc: Test whether updating index is normal + * @tc.type: FUNC + * @tc.require: AR000DR9K8 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBInterfacesIndexUnitTest, CrudIndex003, TestSize.Level1) +{ + PrepareInfoForCrudIndex003(); + sqlite3 *db = nullptr; + string storeId = "CrudIndex003"; + string filePath = GetKvStoreDirectory(storeId, DBConstant::DB_TYPE_SINGLE_VER); + KvStoreNbDelegate::Option option = {true, false, false}; + /** + * @tc.steps:step1. Specify the schema containing all levels of index to open the schema mode database. + * @tc.expected: step1. return OK. + */ + option.schema = g_schemaString1; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + /** + * @tc.steps:step2. Use the sql statement to get the count of the following index fields that will be deleted. + * @tc.expected: step2. count == 1. + */ + EXPECT_EQ(sqlite3_open_v2(filePath.c_str(), &db, SQLITE_OPEN_READWRITE, nullptr), SQLITE_OK); + CheckIndexFromDbFile(db, g_pathStrGroup1, 1); + /** + * @tc.steps:step3. Use the sql statement to get the count of the following index fields that will be added. + * @tc.expected: step3. count == 0. + */ + CheckIndexFromDbFile(db, g_pathStrGroup2, 0); + sqlite3_close(db); + /** + * @tc.steps:step3. Close the database. + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + /** + * @tc.steps:step4. The original schema update the above index fields, + * generates a new schema and opens the database with this schema. + * @tc.expected: step4. return OK. + */ + option.schema = g_schemaString2; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + /** + * @tc.steps:step5. Use the sql statement to get the count of the following index fields that are deleted. + * @tc.expected: step5. count == 0. + */ + EXPECT_EQ(sqlite3_open_v2(filePath.c_str(), &db, SQLITE_OPEN_READWRITE, nullptr), SQLITE_OK); + CheckIndexFromDbFile(db, g_pathStrGroup1, 0); + /** + * @tc.steps:step5. Use the sql statement to get the count of the following index fields that are added. + * @tc.expected: step5. count == 1. + */ + CheckIndexFromDbFile(db, g_pathStrGroup2, 1); + sqlite3_close(db); + // Clear + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("CrudIndex003"), OK); + ResetGlobalVariable(); +} + +namespace { + void PrepareInfoForCreateIndex001() + { + PrepareCommonInfo(true); + g_definePath.insert(g_definePath.end(), g_pathGroup1.begin(), g_pathGroup1.end()); + g_schemaString1 = GenerateSchemaString(g_definePath, g_definePath, 8, true, true); // skipsize 8 in schema + g_valueString1 = GenerateValue(g_definePath, 8); // skipsize 8 in value + g_valueString2 = GenerateValue(g_definePath, 10); // skipsize 10 in value + LOGI("[PrepareInfoForCreateIndex001] g_schemaString1=%s", g_schemaString1.c_str()); + LOGI("[PrepareInfoForCreateIndex001] g_valueString1=%s", g_valueString1.c_str()); + LOGI("[PrepareInfoForCreateIndex001] g_valueString2=%s", g_valueString2.c_str()); + } +} +/** + * @tc.name: CreateIndex001 + * @tc.desc: Test whether the index creation is normal + * @tc.type: FUNC + * @tc.require: AR000DR9K9 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBInterfacesIndexUnitTest, CreateIndex001, TestSize.Level1) +{ + PrepareInfoForCreateIndex001(); + sqlite3 *db = nullptr; + string storeId = "CreateIndex001"; + string filePath = GetKvStoreDirectory(storeId, DBConstant::DB_TYPE_SINGLE_VER); + KvStoreNbDelegate::Option option = {true, false, false}; + /** + * @tc.steps:step1. Specify the schema containing all levels of index to open the schema mode database. + * The four-level index has 64 bytes per field. + * @tc.expected: step1. return OK. + */ + option.schema = g_schemaString1; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + /** + * @tc.steps:step2. Use the sql statement to get each index count count from the sqlite_master table; + * @tc.expected: step2. count == 1. + */ + EXPECT_EQ(sqlite3_open_v2(filePath.c_str(), &db, SQLITE_OPEN_READWRITE, nullptr), SQLITE_OK); + CheckIndexFromDbFile(db, g_pathStrGroup1, 1); + sqlite3_close(db); + /** + * @tc.steps:step3. Write a value with 8 prefix bytes and the json part strictly conforms + * to the value of the schema. Call the query interface to query the inserted data. + * @tc.expected: step3. The insertion is successful and the number of entries obtained by the query is 1. + */ + Key key001{'1'}; + Value value001(g_valueString1.begin(), g_valueString1.end()); + EXPECT_EQ(g_kvNbDelegatePtr->Put(key001, value001), OK); + Query query = Query::Select().GreaterThanOrEqualTo(g_pathStrGroup1.front(), 0); + vector entries; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(query, entries), OK); + EXPECT_EQ(entries.size(), 1ul); + /** + * @tc.steps:step4. Write a value with 10 prefix bytes and the json part strictly conforms + * to the value of the schema. + * @tc.expected: step4. The insertion is failed. + */ + Key key002{'2'}; + Value value002(g_valueString2.begin(), g_valueString2.end()); + EXPECT_TRUE(g_kvNbDelegatePtr->Put(key002, value002) != OK); + // Clear + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("CreateIndex001"), OK); + ResetGlobalVariable(); +} + +namespace { + void PrepareInfoForCreateIndex002() + { + for (uint32_t serial = 0; serial < SchemaConstant::SCHEMA_INDEX_COUNT_MAX; serial++) { + FieldPath path = GenerateFieldPath(SchemaConstant::SCHEMA_FEILD_PATH_DEPTH_MAX, serial, true); + string pathStr = SchemaUtils::FieldPathString(path); + g_pathGroup1.push_back(path); + g_pathStrGroup1.push_back(pathStr); + } + g_definePath.insert(g_definePath.end(), g_pathGroup1.begin(), g_pathGroup1.end()); + g_schemaString1 = GenerateSchemaString(g_definePath, g_definePath, 0, true, false); + LOGI("[PrepareInfoForCreateIndex002] g_schemaString1=%s", g_schemaString1.c_str()); + } +} +/** + * @tc.name: CreateIndex002 + * @tc.desc: Test whether it is possible to insert 32 four-level indexes with each filed being 64. + * @tc.type: FUNC + * @tc.require: AR000DR9K9 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBInterfacesIndexUnitTest, CreateIndex002, TestSize.Level1) +{ + PrepareInfoForCreateIndex002(); + sqlite3 *db = nullptr; + string storeId = "CreateIndex002"; + string filePath = GetKvStoreDirectory(storeId, DBConstant::DB_TYPE_SINGLE_VER); + KvStoreNbDelegate::Option option = {true, false, false}; + /** + * @tc.steps:step1. Specifies that a schema with 32 four-level indexes + * with each filed being 64 opens the schema mode database + * @tc.expected: step1. return OK. + */ + option.schema = g_schemaString1; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + /** + * @tc.steps:step2. Use the sql statement to get each index count count from the sqlite_master table; + * @tc.expected: step2. count == 1. + */ + EXPECT_EQ(sqlite3_open_v2(filePath.c_str(), &db, SQLITE_OPEN_READWRITE, nullptr), SQLITE_OK); + CheckIndexFromDbFile(db, g_pathStrGroup1, 1); + sqlite3_close(db); + // Clear + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("CreateIndex002"), OK); + ResetGlobalVariable(); +} + +namespace { + void PrepareInfoForCheckSchemaSkipsize001() + { + PrepareCommonInfo(false); + g_definePath.insert(g_definePath.end(), g_pathGroup1.begin(), g_pathGroup1.end()); + g_schemaString1 = GenerateSchemaString(g_definePath, g_pathGroup1, 0, true, false); + g_valueString1 = GenerateValue(g_definePath, 0); + g_valueString2 = GenerateValue(g_definePath, 8); // skipsize 8 in value + LOGI("[PrepareInfoForCheckSchemaSkipsize001] g_schemaString1=%s", g_schemaString1.c_str()); + LOGI("[PrepareInfoForCheckSchemaSkipsize001] g_valueString1=%s", g_valueString1.c_str()); + LOGI("[PrepareInfoForCheckSchemaSkipsize001] g_valueString2=%s", g_valueString2.c_str()); + } +} +/** + * @tc.name: Check schema skipsize 001 + * @tc.desc: When SCHEMA_SKIPSIZE is not defined, check if the default is 0 + * @tc.type: FUNC + * @tc.require: AR000DR9K9 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBInterfacesIndexUnitTest, CheckSchemaSkipsize001, TestSize.Level1) +{ + PrepareInfoForCheckSchemaSkipsize001(); + string storeId = "CheckSchemaSkipsize001"; + string filePath = GetKvStoreDirectory(storeId, DBConstant::DB_TYPE_SINGLE_VER); + KvStoreNbDelegate::Option option = {true, false, false}; + /** + * @tc.steps:step1. Specify an undefined skipsize schema to open the schema database. + * @tc.expected: step1. return OK. + */ + option.schema = g_schemaString1; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + /** + * @tc.steps:step2. Write a value without prefix and strictly in accordance with the schema. + * @tc.expected: step2. return OK. + */ + Key key001{'1'}; + Value value001(g_valueString1.begin(), g_valueString1.end()); + EXPECT_EQ(g_kvNbDelegatePtr->Put(key001, value001), OK); + /** + * @tc.steps:step3. Write a value whose prefix is 8 and strictly in accordance with the schema. + * @tc.expected: step3. return not OK. + */ + Key key002{'2'}; + Value value002(g_valueString2.begin(), g_valueString2.end()); + EXPECT_TRUE(g_kvNbDelegatePtr->Put(key002, value002) != OK); + // Clear + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("CheckSchemaSkipsize001"), OK); + ResetGlobalVariable(); +} + +/** + * @tc.name: Check schema skipsize 002 + * @tc.desc: SCHEMA_SKIPSIZE range is [0,4MB-2] + * @tc.type: FUNC + * @tc.require: AR000DR9K9 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBInterfacesIndexUnitTest, CheckSchemaSkipsize002, TestSize.Level1) +{ + PrepareCommonInfo(false); + string storeId = "CheckSchemaSkipsize002"; + string filePath = GetKvStoreDirectory(storeId, DBConstant::DB_TYPE_SINGLE_VER); + KvStoreNbDelegate::Option option = {true, false, false}; + /** + * @tc.steps:step1. Set "SCHEMA_SKIPSIZE" in the schema as -1 to create the schema database. + * @tc.expected: step1. return not OK. + */ + option.schema = GenerateSchemaString(g_pathGroup1, vector(), -1, false, true); // skipsize -1 in schema + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + EXPECT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus != OK); + + /** + * @tc.steps:step2. Set "SCHEMA_SKIPSIZE" in the schema as 0 to create the schema database. + * @tc.expected: step2. return not OK. + */ + option.schema = GenerateSchemaString(g_pathGroup1, vector(), 0, false, true); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("CheckSchemaSkipsize002"), OK); + + /** + * @tc.steps:step3. Set "SCHEMA_SKIPSIZE" in the schema as 8 to create the schema database. + * @tc.expected: step3. return OK. + */ + option.schema = GenerateSchemaString(g_pathGroup1, g_pathGroup1, 8, true, true); // skipsize 8 in schema + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("CheckSchemaSkipsize002"), OK); + + /** + * @tc.steps:step4. Set "SCHEMA_SKIPSIZE" in the schema as 4MB-2 to create the schema database. + * @tc.expected: step6. return OK. + */ + option.schema = GenerateSchemaString(g_pathGroup1, vector(), + 4 * 1024 * 1024 - 2, false, true); // skipsize in schema, 4M - 2, 1024 is scale + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("CheckSchemaSkipsize002"), OK); + + /** + * @tc.steps:step5. Set SCHEMA_SKIPSIZE in the schema as 4MB-1 to create the schema database. + * @tc.expected: step6. return not OK. + */ + option.schema = GenerateSchemaString(g_pathGroup1, vector(), + 4 * 1024 * 1024 - 1, false, true); // skipsize in schema, 4M - 1, 1024 is scale + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + EXPECT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus != OK); + // Clear + ResetGlobalVariable(); +} + +namespace { + void PrepareInfoForCheckSchemaSkipsize003() + { + PrepareCommonInfo(false); + g_definePath.insert(g_definePath.end(), g_pathGroup1.begin(), g_pathGroup1.end()); + g_schemaString1 = GenerateSchemaString(g_definePath, g_pathGroup1, 20, true, true); // skipsize 20 in schema + g_valueString1 = GenerateValue(g_definePath, 19); // skipsize 19 in value + g_valueString2 = GenerateValue(g_definePath, 20); // skipsize 20 in value + LOGI("[PrepareInfoForCheckSchemaSkipsize003] g_schemaString1=%s", g_schemaString1.c_str()); + LOGI("[PrepareInfoForCheckSchemaSkipsize003] g_valueString1=%s", g_valueString1.c_str()); + LOGI("[PrepareInfoForCheckSchemaSkipsize003] g_valueString2=%s", g_valueString2.c_str()); + } +} +/** + * @tc.name: Check schema skipsize 003 + * @tc.desc: When "SCHEMA_SKIPSIZE" is greater than or equal to the size of Value, + * the Value verification must fail + * @tc.type: FUNC + * @tc.require: AR000DR9K9 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBInterfacesIndexUnitTest, CheckSchemaSkipsize003, TestSize.Level1) +{ + PrepareInfoForCheckSchemaSkipsize003(); + string storeId = "CheckSchemaSkipsize003"; + string filePath = GetKvStoreDirectory(storeId, DBConstant::DB_TYPE_SINGLE_VER); + KvStoreNbDelegate::Option option = {true, false, false}; + /** + * @tc.steps:step1. Set "SCHEMA_SKIPSIZE" in the schema as 20 to create the schema database. + * @tc.expected: step1. return OK. + */ + option.schema = g_schemaString1; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + + /** + * @tc.steps:step5. Write a value whose prefix is 19 and strictly in accordance with the schema. + * @tc.expected: step5. return OK. + */ + Key key001{'1'}; + Value value001(g_valueString1.begin(), g_valueString1.end()); + EXPECT_TRUE(g_kvNbDelegatePtr->Put(key001, value001) != OK); + /** + * @tc.steps:step5. Write a value whose prefix is 20 and strictly in accordance with the schema. + * @tc.expected: step5. return OK. + */ + Key key002{'2'}; + Value value002(g_valueString2.begin(), g_valueString2.end()); + EXPECT_TRUE(g_kvNbDelegatePtr->Put(key002, value002) == OK); + // Clear + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("CheckSchemaSkipsize003"), OK); + ResetGlobalVariable(); +} + +/** + * @tc.name: schema compare with skipsize 004 + * @tc.desc: When the SCHEMA_SKIPSIZE definitions of two Schemas are different, + * they will be regarded as inconsistent and incompatible + * @tc.type: FUNC + * @tc.require: AR000DR9K9 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBInterfacesIndexUnitTest, SchemaCompareSkipsize004, TestSize.Level1) +{ + PrepareCommonInfo(false); + string storeId = "SchemaCompareSkipsize004"; + string filePath = GetKvStoreDirectory(storeId, DBConstant::DB_TYPE_SINGLE_VER); + KvStoreNbDelegate::Option option = {true, false, false}; + /** + * @tc.steps:step1. Set "SCHEMA_SKIPSIZE" in the schema as 0 to create the schema database. + * @tc.expected: step1. return OK. + */ + option.schema = GenerateSchemaString(g_pathGroup1, vector(), 0, false, true); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + + /** + * @tc.steps:step2. Modify the schema, SCHEMA_SKIPSIZE in the new schema is not defined, + * open the database repeatedly. + * @tc.expected: step2. return OK. + */ + option.schema = GenerateSchemaString(g_pathGroup1, vector(), 0, false, false); + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback2); + ASSERT_TRUE(g_kvNbDelegatePtr2 != nullptr); + EXPECT_EQ(g_kvDelegateStatus2, OK); + + /** + * @tc.steps:step3. Close the database. + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr2), OK); + + /** + * @tc.steps:step4. SCHEMA_SKIPSIZE in the schema is not defined, reopen the database; + * @tc.expected: step4. return OK. + */ + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvDelegateStatus, OK); + + /** + * @tc.steps:step5. Modify the schema, set SCHEMA_SKIPSIZE to 8 in the new schema, + * and open the database repeatedly; + * @tc.expected: step5. return OK. + */ + option.schema = GenerateSchemaString(g_pathGroup1, vector(), 8, false, true); // skipsize 8 in schema + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback2); + EXPECT_TRUE(g_kvNbDelegatePtr2 == nullptr); + EXPECT_TRUE(g_kvDelegateStatus2 != OK); + + /** + * @tc.steps:step6. Close the database. + * @tc.expected: step6. return OK. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + /** + * @tc.steps:step4. Modify the schema, set SCHEMA_SKIPSIZE to 8 in the new schema, reopen the database; + * @tc.expected: step4. return OK. + */ + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + EXPECT_TRUE(g_kvNbDelegatePtr2 == nullptr); + EXPECT_TRUE(g_kvDelegateStatus != OK); + EXPECT_EQ(g_mgr.DeleteKvStore("SchemaCompareSkipsize004"), OK); + ResetGlobalVariable(); +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_delegate_local_batch_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_delegate_local_batch_test.cpp new file mode 100644 index 00000000..50a53dbb --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_delegate_local_batch_test.cpp @@ -0,0 +1,1138 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_constant.h" +#include "db_errno.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" +#include "sqlite_single_ver_natural_store.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + KvStoreConfig g_config; + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + + const int OBSERVER_SLEEP_TIME = 100; + const int BATCH_PRESET_SIZE_TEST = 10; + const int DIVIDE_BATCH_PRESET_SIZE = 5; + const int VALUE_OFFSET = 5; + const int DEFAULT_KEY_VALUE_SIZE = 10; + + const Key KEY1{'k', 'e', 'y', '1'}; + const Key KEY2{'k', 'e', 'y', '2'}; + const Value VALUE1{'v', 'a', 'l', 'u', 'e', '1'}; + const Value VALUE2{'v', 'a', 'l', 'u', 'e', '2'}; + + const std::string VALID_SCHEMA_STRICT_DEFINE = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":\"INTEGER, NOT NULL\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\"]}"; + + CipherPassword g_passwd; + KvStoreNbDelegate::Option g_strictOpt = { + true, false, false, CipherType::DEFAULT, g_passwd, + VALID_SCHEMA_STRICT_DEFINE + }; + + // the type of g_kvNbDelegateCallback is function + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); + + static void CreatEntrys(int recordSize, vector &keys, vector &values, vector &entries) + { + keys.clear(); + values.clear(); + entries.clear(); + for (int i = 0; i < recordSize; i++) { + string temp = to_string(i); + Entry entry; + Key keyTemp; + Value valueTemp; + for (auto &iter : temp) { + entry.key.push_back(iter); + entry.value.push_back(iter); + keyTemp.push_back(iter); + valueTemp.push_back(iter); + } + keys.push_back(keyTemp); + values.push_back(valueTemp); + entries.push_back(entry); + } + } +} + +class DistributedDBInterfacesNBDelegateLocalBatchTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesNBDelegateLocalBatchTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesNBDelegateLocalBatchTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesNBDelegateLocalBatchTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_kvDelegateStatus = INVALID_ARGS; + g_kvNbDelegatePtr = nullptr; +} + +void DistributedDBInterfacesNBDelegateLocalBatchTest::TearDown(void) +{ + if (g_kvNbDelegatePtr != nullptr) { + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + g_kvNbDelegatePtr = nullptr; + } +} + +/** + * @tc.name: PutLocalBatch001 + * @tc.desc: This test case use to verify the PutLocalBatch interface function + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, PutLocalBatch001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get singleVer kvStore by GetKvStore. + * @tc.expected: step1. Get database success. + */ + const KvStoreNbDelegate::Option option = {true, true}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_PutLocalBatch_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step2. Insert 10 records into database. + * @tc.expected: step2. Insert successfully. + */ + vector entries; + for (int i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(i); + entries.push_back(entry); + } + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entries), OK); + + for (int i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + Key key; + key.push_back(i); + Value value; + g_kvNbDelegatePtr->GetLocal(key, value); + EXPECT_EQ(key, value); + } + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutLocalBatch001 + * @tc.desc: Check for illegal parameters + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, SingleVerPutLocalBatch001, TestSize.Level1) +{ + /** + * @tc.steps: step1. + * Create and construct three sets of vector , each set of three data contains records: + * (K1, V1) It is illegal for K1 to be greater than 1K, and V1 is 1K in size + * (K2, V2) K2 is legal, V2 is greater than 4M + * (K3, V3) are not legal. + */ + Key illegalKey; + DistributedDBToolsUnitTest::GetRandomKeyValue(illegalKey, DBConstant::MAX_KEY_SIZE + 1); // 1K + 1 + Value illegalValue; + DistributedDBToolsUnitTest::GetRandomKeyValue(illegalValue, DBConstant::MAX_VALUE_SIZE + 1); // 4M + 1 + vector entrysKeyIllegal = {KV_ENTRY_1, KV_ENTRY_2, {illegalKey, VALUE_3}}; + vector entrysValueIllegal = {KV_ENTRY_1, KV_ENTRY_2, {KEY_3, illegalValue}}; + vector entrysIllegal = {KV_ENTRY_1, KV_ENTRY_2, {illegalKey, illegalValue}}; + + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_SingleVerPutLocalBatch_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps: step2. PutBatch operates on three sets of data. + * @tc.expected: step2. All three operations return INVALID_ARGS. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrysKeyIllegal), INVALID_ARGS); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrysValueIllegal), INVALID_ARGS); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrysIllegal), INVALID_ARGS); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutLocalBatch_001"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutLocalBatch002 + * @tc.desc: PutLocalBatch normal insert function test. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, SingleVerPutLocalBatch002, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_SingleVerPutLocalBatch_002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps: step1. + * Create and build 4 groups of vector , which are: + * Vect of empty objects; + * Vect1 of a legal Entry record; + * 128 legal Entry records Vect2; + * 129 legal Entry records Vect3; + */ + vector entrysMaxNumber; + for (size_t i = 0; i < DBConstant::MAX_BATCH_SIZE; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(i); + entrysMaxNumber.push_back(entry); + } + Key keyTemp = {'1', '1'}; + Value valueTemp; + Entry entryTemp = {keyTemp, VALUE_1}; + vector entrysOneRecord = {entryTemp}; + vector entrysOverSize = entrysMaxNumber; + entrysOverSize.push_back(entryTemp); + /** + * @tc.steps: step2. PutBatch operates on four sets of data. and use get check the result of Vect3. + * @tc.expected: step2. Returns INVALID_ARGS for 129 records, and returns OK for the rest. all get return NOT_FOUND. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrysOverSize), INVALID_ARGS); + for (size_t i = 0; i < entrysOverSize.size(); i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(entrysOverSize[i].key, valueTemp), NOT_FOUND); + } + /** + * @tc.steps: step3. Use get check the result of Vect2. + * @tc.expected: step3. Return OK and get the correct value. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrysOneRecord), OK); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(keyTemp, valueTemp), OK); + EXPECT_EQ(valueTemp, VALUE_1); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrysMaxNumber), OK); + /** + * @tc.steps: step4. Use get check the result of Vect3. + * @tc.expected: step4. Return OK and get the correct value. + */ + for (size_t i = 0; i < entrysMaxNumber.size(); i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(entrysMaxNumber[i].key, valueTemp), OK); + EXPECT_EQ(valueTemp, entrysMaxNumber[i].value); + } + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutLocalBatch_002"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutLocalBatch003 + * @tc.desc: Check interface atomicity + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, SingleVerPutLocalBatch003, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_SingleVerPutLocalBatch_003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps: step1. Create and construct a set of vector with a total of 128 data, + * including one illegal data. And call PutBatch interface to insert. + */ + vector entrysMaxNumber; + for (size_t i = 0; i < DBConstant::MAX_BATCH_SIZE; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(i); + entrysMaxNumber.push_back(entry); + } + Key illegalKey; + Value valueTemp; + DistributedDBToolsUnitTest::GetRandomKeyValue(illegalKey, DBConstant::MAX_KEY_SIZE + 1); // 1K + 1 + entrysMaxNumber[0].key = illegalKey; + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrysMaxNumber), INVALID_ARGS); + /** + * @tc.steps: step2. Use Get interface to query 128 corresponding key values. + * @tc.expected: step2. All Get interface return NOT_FOUND. + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(entrysMaxNumber[0].key, valueTemp), INVALID_ARGS); + for (size_t i = 1; i < entrysMaxNumber.size(); i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(entrysMaxNumber[i].key, valueTemp), NOT_FOUND); + } + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutLocalBatch_003"), OK); + g_kvNbDelegatePtr = nullptr; +} + +static void PreparePutLocalBatch004(vector &entrys1, vector &entrys2, vector &entrys3) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_SingleVerPutLocalBatch_004", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + for (int i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(i); + entrys1.push_back(entry); + } + + for (int i = 0; i < DIVIDE_BATCH_PRESET_SIZE; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(i + VALUE_OFFSET); + entrys2.push_back(entry); + } + + for (int i = DIVIDE_BATCH_PRESET_SIZE; i < BATCH_PRESET_SIZE_TEST; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(i - VALUE_OFFSET); + entrys3.push_back(entry); + } +} + +/** + * @tc.name: SingleVerPutLocalBatch004 + * @tc.desc: Check interface data insertion and update functions. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, SingleVerPutLocalBatch004, TestSize.Level1) +{ + /** + * @tc.steps: step1. + * Construct three groups of three vector : + * (1) entrys1: key1 ~ 10, corresponding to Value1 ~ 10; + * (2) entrys2: key1 ~ 5, corresponding to Value6 ~ 10; + * (3) entrys3: key6 ~ 10, corresponding to Value1 ~ 5; + */ + vector entrys1; + vector entrys2; + vector entrys3; + PreparePutLocalBatch004(entrys1, entrys2, entrys3); + /** + * @tc.steps: step2. PutBatch entrys2. + * @tc.expected: step2. PutBatch return OK. + */ + Value valueRead; + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrys2), OK); + /** + * @tc.steps: step3. Check PutBatch result. + * @tc.expected: step3. Get correct value of key1~5. Key6~10 return NOT_FOUND. + */ + for (int i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + Key keyTemp; + keyTemp.push_back(i); + if (i < DIVIDE_BATCH_PRESET_SIZE) { + Value valueTemp; + valueTemp.push_back(i + VALUE_OFFSET); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(keyTemp, valueRead), OK); + EXPECT_EQ(valueRead, valueTemp); + continue; + } + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(keyTemp, valueRead), NOT_FOUND); + } + /** + * @tc.steps: step4. PutBatch entrys1. + * @tc.expected: step4. PutBatch return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrys1), OK); + /** + * @tc.steps: step5. Check PutBatch result. + * @tc.expected: step5. Update and insert value of key1~10 to value1~10. + */ + for (int i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + Key keyTemp; + keyTemp.push_back(i); + if (i < DIVIDE_BATCH_PRESET_SIZE) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(keyTemp, valueRead), OK); + EXPECT_EQ(valueRead, keyTemp); + continue; + } + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(keyTemp, valueRead), OK); + EXPECT_EQ(valueRead, keyTemp); + } + /** + * @tc.steps: step6. PutBatch entrys3. + * @tc.expected: step6. PutBatch return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrys3), OK); + /** + * @tc.steps: step7. Check PutBatch result of key1~10. + * @tc.expected: step7. Update value of key5~10 to value1~5. + */ + for (int i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + Key keyTemp; + keyTemp.push_back(i); + if (i < DIVIDE_BATCH_PRESET_SIZE) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(keyTemp, valueRead), OK); + EXPECT_EQ(valueRead, keyTemp); + continue; + } + Value valueTemp; + valueTemp.push_back(i - VALUE_OFFSET); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(keyTemp, valueRead), OK); + EXPECT_EQ(valueRead, valueTemp); + } + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutLocalBatch_004"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerDeleteLocalBatch001 + * @tc.desc: Check for illegal parameters. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, SingleVerDeleteLocalBatch001, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_SingleVerDeleteLocalBatch_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps: step1. Create and construct a set of vector , containing a total of 10 data keys1 ~ 10, + * Value1 ~ 10, and call Putbatch interface to insert data. + * @tc.expected: step1. PutBatch successfully. + */ + vector entries; + vector keys; + vector values; + Value valueRead; + CreatEntrys(BATCH_PRESET_SIZE_TEST, keys, values, entries); + vector entrysBase = entries; + vector keysBase = keys; + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrysBase), OK); + /** + * @tc.steps: step2. Use Get to check data in database. + * @tc.expected: step2. Get value1~10 by key1~10 successfully. + */ + for (size_t i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(entrysBase[i].key, valueRead), OK); + } + /** + * @tc.steps: step3. Use DeleteBatch interface to transfer 10 + 119 extra keys (total 129). + * @tc.expected: step3. Return INVALID_ARGS. + */ + CreatEntrys(DBConstant::MAX_BATCH_SIZE + 1, keys, values, entries); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(keys), INVALID_ARGS); + /** + * @tc.steps: step4. Use Get to check data in database. + * @tc.expected: step4. Key1~10 still in database. + */ + for (size_t i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(entrysBase[i].key, valueRead), OK); + } + /** + * @tc.steps: step5. Use the DeleteBatch interface to pass in 10 included + * keys6 ~ 10 + 123 additional key values ​​(128 in total). + * @tc.expected: step5. DeleteBatch OK. + */ + CreatEntrys(DBConstant::MAX_BATCH_SIZE + DIVIDE_BATCH_PRESET_SIZE, keys, values, entries); + keys.erase(keys.begin(), keys.begin() + DIVIDE_BATCH_PRESET_SIZE); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(keys), OK); + /** + * @tc.steps: step6. Use Get to check key1~10 in database. + * @tc.expected: step6. Key1~5 in database, key6~10 have been deleted. + */ + for (size_t i = 0; i < DIVIDE_BATCH_PRESET_SIZE; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(entrysBase[i].key, valueRead), OK); + } + for (size_t i = DIVIDE_BATCH_PRESET_SIZE; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(entrysBase[i].key, valueRead), NOT_FOUND); + } + /** + * @tc.steps: step7. Repeat Putbatch key1~10, value1~10. + * @tc.expected: step7. Return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrysBase), OK); + + Key illegalKey; + DistributedDBToolsUnitTest::GetRandomKeyValue(illegalKey, DBConstant::MAX_KEY_SIZE + 1); // 1K + 1 + keysBase.push_back(illegalKey); + /** + * @tc.steps: step8. Use DeleteBatch interface to pass in 10 + 1(larger than 1K) keys. + * @tc.expected: step8. Return INVALID_ARGS. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(keysBase), INVALID_ARGS); + /** + * @tc.steps: step9. Use Get to check key1~10 in database. + * @tc.expected: step9. Delete those data failed. + */ + for (size_t i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(entrysBase[i].key, valueRead), OK); + } + /** + * @tc.steps: step10. Use DeleteBatch interface to pass in 10(in database) + 1 valid keys. + * @tc.expected: step10. Delete those data successfully. + */ + keysBase.back().erase(keysBase.back().begin(), keysBase.back().begin() + 1); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(keysBase), OK); + /** + * @tc.steps: step11. Check data. + * @tc.expected: step11. DeleteBatch successfully. + */ + for (size_t i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(entrysBase[i].key, valueRead), NOT_FOUND); + } + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerDeleteLocalBatch_001"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerDeleteLocalBatch002 + * @tc.desc: Check normal delete batch ability. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, SingleVerDeleteLocalBatch002, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_SingleVerDeleteLocalBatch_002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps: step1. Create a group of vector , containing a total of 10 data keys1 ~ 10, Value1 ~ 10, + * call the Putbatch interface to insert data. + * @tc.expected: step1. Insert to database successfully. + */ + vector entries; + vector keysBase; + vector values; + CreatEntrys(BATCH_PRESET_SIZE_TEST, keysBase, values, entries); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entries), OK); + /** + * @tc.steps: step2. Check data. + * @tc.expected: step2. Get key1~10 successfully. + */ + Value valueRead; + for (size_t i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(keysBase[i], valueRead), OK); + } + /** + * @tc.steps: step3. DeleteBatch key1~5. + * @tc.expected: step3. Return OK. + */ + vector keys(keysBase.begin(), keysBase.begin() + DIVIDE_BATCH_PRESET_SIZE); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(keys), OK); + /** + * @tc.steps: step4. Check key1~10. + * @tc.expected: step4. Key1~5 deleted, key6~10 existed. + */ + for (size_t i = 0; i < DIVIDE_BATCH_PRESET_SIZE; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(keysBase[i], valueRead), NOT_FOUND); + } + for (size_t i = DIVIDE_BATCH_PRESET_SIZE; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(keysBase[i], valueRead), OK); + } + /** + * @tc.steps: step5. DeleteBatch key1~10. + * @tc.expected: step5. Return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(keysBase), OK); + /** + * @tc.steps: step6. Check key1~10. + * @tc.expected: step6. Key1~10 deleted successfully. + */ + for (size_t i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(keysBase[i], valueRead), NOT_FOUND); + } + /** + * @tc.steps: step7. DeleteBatch key1~10 once again. + * @tc.expected: step7. Return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(keysBase), OK); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerDeleteLocalBatch_002"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutLocalBatchObserver001 + * @tc.desc: Test the observer function of PutLocalBatch() interface. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, SingleVerPutLocalBatchObserver001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("distributed_SingleVerPutLocalBatchObserver_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_LOCAL_ONLY, observer), OK); + /** + * @tc.steps:step3. Put batch data. + * @tc.expected: step3. Returns OK. + */ + vector entrysBase; + vector keysBase; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST + 1, entrysBase, keysBase); + + vector entries(entrysBase.begin(), entrysBase.end() - 1); + EXPECT_EQ(entries.size(), 10UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entries), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entries, observer->GetEntriesInserted())); + /** + * @tc.steps:step4. Delete the batch data. + * @tc.expected: step4. Returns OK. + */ + vector keys(keysBase.begin() + 5, keysBase.end()); + EXPECT_EQ(keys.size(), 6UL); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(keys), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + vector entrysDel(entrysBase.begin() + 5, entrysBase.end() - 1); + EXPECT_EQ(entrysDel.size(), 5UL); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entrysDel, observer->GetEntriesDeleted())); + /** + * @tc.steps:step5. UnRegister the observer. + * @tc.expected: step5. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + + /** + * @tc.steps:step6. Close the kv store. + * @tc.expected: step6. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutLocalBatchObserver_001"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutLocalBatchObserver002 + * @tc.desc: Test the observer function of PutLocalBatch() for invalid input. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, SingleVerPutLocalBatchObserver002, TestSize.Level4) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("distributed_SingleVerPutLocalBatchObserver_002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_LOCAL_ONLY, observer), OK); + /** + * @tc.steps:step3. Put 129 batch data. + * @tc.expected: step3. Returns INVALID_ARGS. + */ + vector entrys1; + vector keys1; + DistributedDBUnitTest::GenerateRecords(DBConstant::MAX_BATCH_SIZE + 1, entrys1, keys1); + + EXPECT_EQ(entrys1.size(), 129UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrys1), INVALID_ARGS); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(observer->GetEntriesInserted().empty()); + /** + * @tc.steps:step4. Put invalid batch data. + * @tc.expected: step4. Returns INVALID_ARGS. + */ + vector entrys2; + vector keys2; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrys2, keys2); + EXPECT_EQ(entrys2.size(), 10UL); + + vector entrysInvalid; + vector keysInvalid; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrysInvalid, keysInvalid, + DBConstant::MAX_KEY_SIZE + 10); + EXPECT_EQ(entrysInvalid.size(), 10UL); + entrys2[0].key = entrysInvalid[0].key; + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrys2), INVALID_ARGS); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(observer->GetEntriesInserted().empty()); + /** + * @tc.steps:step5. Put MAX valid value batch data. + * @tc.expected: step5. Returns OK. + */ + vector entrys3; + vector keys3; + + DistributedDBUnitTest::GenerateRecords(DBConstant::MAX_BATCH_SIZE, entrys3, keys3); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrys3), OK); + LOGD("sleep begin"); + // sleep 20 seconds + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME * 10)); + LOGD("sleep end"); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entrys3, observer->GetEntriesInserted())); + /** + * @tc.steps:step6. UnRegister the observer. + * @tc.expected: step6. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + + /** + * @tc.steps:step7. Close the kv store. + * @tc.expected: step7. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutLocalBatchObserver_002"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutLocalBatchObserver003 + * @tc.desc: Test the observer function of PutLocalBatch() update function. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, SingleVerPutLocalBatchObserver003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("distributed_SingleVerPutLocalBatchObserver_003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_LOCAL_ONLY, observer), OK); + /** + * @tc.steps:step3. Put batch data. + * @tc.expected: step3. Returns OK. + */ + vector entrysAdd; + vector keysAdd; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrysAdd, keysAdd); + + EXPECT_EQ(entrysAdd.size(), 10UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrysAdd), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entrysAdd, observer->GetEntriesInserted())); + /** + * @tc.steps:step4. Update the batch data. + * @tc.expected: step4. Returns OK. + */ + vector entrysUpdate; + vector keysUpdate; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrysUpdate, keysUpdate, DEFAULT_KEY_VALUE_SIZE, + DEFAULT_KEY_VALUE_SIZE + 10); + + EXPECT_EQ(entrysUpdate.size(), 10UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrysUpdate), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entrysUpdate, observer->GetEntriesUpdated())); + /** + * @tc.steps:step5. UnRegister the observer. + * @tc.expected: step5. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + + /** + * @tc.steps:step6. Close the kv store. + * @tc.expected: step6. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutLocalBatchObserver_003"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutLocalBatchObserver004 + * @tc.desc: Test the observer function of PutLocalBatch(), same keys handle. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, SingleVerPutLocalBatchObserver004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("distributed_SingleVerPutLocalBatchObserver_004", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_LOCAL_ONLY, observer), OK); + /** + * @tc.steps:step3. Put batch data. + * @tc.expected: step3. Returns OK. + */ + vector entrys1; + vector keys1; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrys1, keys1); + vector entrys2; + vector keys2; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrys2, keys2, DEFAULT_KEY_VALUE_SIZE, + DEFAULT_KEY_VALUE_SIZE + 10); + entrys1.insert(entrys1.end(), entrys2.begin(), entrys2.end()); + + EXPECT_EQ(entrys1.size(), 20UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrys1), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entrys2, observer->GetEntriesInserted())); + EXPECT_EQ(observer->GetEntriesUpdated().size(), 0UL); + + vector entrys3; + vector keys3; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrys3, keys3, DEFAULT_KEY_VALUE_SIZE, + DEFAULT_KEY_VALUE_SIZE + 20); + vector entrys4; + vector keys4; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrys4, keys4, DEFAULT_KEY_VALUE_SIZE, + DEFAULT_KEY_VALUE_SIZE + 30); + entrys3.insert(entrys3.end(), entrys4.begin(), entrys4.end()); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entrys3), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entrys4, observer->GetEntriesUpdated())); + EXPECT_EQ(observer->GetEntriesInserted().size(), 0UL); + + /** + * @tc.steps:step4. UnRegister the observer. + * @tc.expected: step4. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + + /** + * @tc.steps:step5. Close the kv store. + * @tc.expected: step5. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutLocalBatchObserver_004"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerDeleteLocalBatchObserver001 + * @tc.desc: Test the observer function of DeleteLocalBatch() interface. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, SingleVerDeleteLocalBatchObserver001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("distributed_SingleVerDeleteLocalBatchObserver_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_LOCAL_ONLY, observer), OK); + /** + * @tc.steps:step3. Put batch data. + * @tc.expected: step3. Returns OK. + */ + vector entries; + vector keys; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entries, keys); + EXPECT_EQ(entries.size(), 10UL); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entries), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entries, observer->GetEntriesInserted())); + /** + * @tc.steps:step4. Delete the batch data. + * @tc.expected: step4. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(keys), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entries, observer->GetEntriesDeleted())); + /** + * @tc.steps:step5. UnRegister the observer. + * @tc.expected: step5. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + + /** + * @tc.steps:step6. Close the kv store. + * @tc.expected: step6. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerDeleteLocalBatchObserver_001"), OK); + g_kvNbDelegatePtr = nullptr; +} +#ifndef OMIT_JSON +/** + * @tc.name: LocalDataBatchNotCheckSchema001 + * @tc.desc: Local data does not check schema. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, LocalDataBatchNotCheckSchema001, TestSize.Level1) +{ + g_mgr.GetKvStore("distributed_LocalDataBatchNotCheckSchema_001", g_strictOpt, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step1. Put one data whose value has more fields than the schema. + * @tc.expected: step1. Return OK, because PutLocal does not verify the validity of the schema. + */ + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + std::string moreData = "{\"field_name1\":true,\"field_name2\":10,\"field_name3\":10}"; + Value value(moreData.begin(), moreData.end()); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(key, value), OK); + Value getValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(key, getValue), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsValueEqual(getValue, value)); + + /** + * @tc.steps:step2. Delete local data + * @tc.expected: step2. DeleteLocal return OK, GetLocal return NOT_FOUND + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocal(key), OK); + getValue.clear(); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(key, getValue), NOT_FOUND); + + /** + * @tc.steps:step3. PutLocalBatch local data whose value is mismatch with the schema. + * @tc.expected: step3. return OK. + */ + key.clear(); + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + std::string invalidData = "{\"field_name1\":true, \"field_name2\":null}"; + value.assign(invalidData.begin(), invalidData.end()); + std::vector keys; + std::vector entries; + entries.push_back({key, value}); + keys.push_back(key); + + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + std::string validData = "{\"field_name1\":true, \"field_name2\":0}"; + value.assign(validData.begin(), validData.end()); + entries.push_back({key, value}); + keys.push_back(key); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entries), OK); + std::vector getEntries; + Key keyPrefix; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocalEntries(keyPrefix, getEntries), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntriesEqual(entries, getEntries, true)); + + /** + * @tc.steps:step4. Delete local data + * @tc.expected: step4. DeleteLocal return OK, GetLocal return NOT_FOUND + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(keys), OK); + getEntries.clear(); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocalEntries(keyPrefix, getEntries), NOT_FOUND); + EXPECT_TRUE(getEntries.empty()); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_LocalDataBatchNotCheckSchema_001"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: LocalDataBatchNotCheckReadOnly001 + * @tc.desc: Local data does not check readOnly. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateLocalBatchTest, LocalDataBatchNotCheckReadOnly001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open the kv store with valid schema, and close it. + * @tc.expected: step1. opened & closeed successfully - return OK. + */ + g_mgr.GetKvStore("distributed_LocalDataBatchNotCheckReadOnly_001", g_strictOpt, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + /** + * @tc.steps:step2. Open the kv store with no schema. + * @tc.expected: step2. return OK. + */ + DistributedDB::KvStoreNbDelegate::Option option = g_strictOpt; + option.schema.clear(); + g_mgr.GetKvStore("distributed_LocalDataBatchNotCheckReadOnly_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step3. CRUD single local the data. + * @tc.expected: step3. return OK. + */ + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + std::string valueData = "{\"field_name1\":true,\"field_name2\":20}"; + Value value(valueData.begin(), valueData.end()); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(key, value), OK); + + Value getValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(key, getValue), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsValueEqual(getValue, value)); + + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocal(key), OK); + getValue.clear(); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(key, getValue), NOT_FOUND); + + /** + * @tc.steps:step3. CRUD batch local the data. + * @tc.expected: step3. return OK. + */ + key.clear(); + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + std::string invalidData = "{\"field_name1\":true, \"field_name2\":null}"; + value.assign(invalidData.begin(), invalidData.end()); + std::vector keys; + std::vector entries; + entries.push_back({key, value}); + keys.push_back(key); + + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + std::string validData = "{\"field_name1\":true, \"field_name2\":0}"; + value.assign(validData.begin(), validData.end()); + entries.push_back({key, value}); + keys.push_back(key); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(entries), OK); + std::vector getEntries; + Key keyPrefix; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocalEntries(keyPrefix, getEntries), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntriesEqual(entries, getEntries, true)); + + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(keys), OK); + getEntries.clear(); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocalEntries(keyPrefix, getEntries), NOT_FOUND); + EXPECT_TRUE(getEntries.empty()); + + /** + * @tc.steps:step4. Close the kv store. + * @tc.expected: step4. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_LocalDataBatchNotCheckReadOnly_001"), OK); + g_kvNbDelegatePtr = nullptr; +} +#endif diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_delegate_schema_put_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_delegate_schema_put_test.cpp new file mode 100644 index 00000000..7cebfcae --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_delegate_schema_put_test.cpp @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_JSON +#include +#include +#include + +#include "distributeddb_tools_unit_test.h" +#include "kv_store_delegate_manager.h" +#include "kv_store_nb_delegate.h" +#include "query.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { + const std::string VALID_SCHEMA_STRICT_DEFINE = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":\"INTEGER, NOT NULL\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\"]}"; + const std::string VALID_SCHEMA_COMPA_DEFINE = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":\"INTEGER, NOT NULL\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\"]}"; + // define some variables to init a KvStoreDelegateManager object. + KvStoreDelegateManager g_mgr("app0", "user0"); + std::string g_testDir; + KvStoreConfig g_config; + std::string g_storeName = "schema_put_test"; + + // define the g_kvNbDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvStore = nullptr; + CipherPassword g_passwd; + KvStoreNbDelegate::Option g_strictOpt = {true, false, false, CipherType::DEFAULT, g_passwd, + VALID_SCHEMA_STRICT_DEFINE}; + KvStoreNbDelegate::Option g_compOpt = {true, false, false, CipherType::DEFAULT, g_passwd, + VALID_SCHEMA_COMPA_DEFINE}; + + void KvStoreNbDelegateCallback( + DBStatus statusSrc, KvStoreNbDelegate* kvStoreSrc, DBStatus* statusDst, KvStoreNbDelegate** kvStoreDst) + { + *statusDst = statusSrc; + *kvStoreDst = kvStoreSrc; + } + + // the type of g_kvNbDelegateCallback is function + auto g_kvNbDelegateCallback = std::bind(&KvStoreNbDelegateCallback, std::placeholders::_1, + std::placeholders::_2, &g_kvDelegateStatus, &g_kvStore); + + void CheckPutSchemaData(KvStoreNbDelegate *kvStore) + { + ASSERT_NE(kvStore, nullptr); + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + /** + * @tc.steps:step1. Put one data whose value has less fields than the schema(less value is not null). + * @tc.expected: step1. return CONSTRAIN_VIOLATION. + */ + std::string lessData = "{\"field_name1\":true}"; + Value value(lessData.begin(), lessData.end()); + EXPECT_EQ(g_kvStore->Put(key, value), CONSTRAIN_VIOLATION); + + /** + * @tc.steps:step2. Put one data whose value has different fields with the schema(less value is not null). + * @tc.expected: step2. return CONSTRAIN_VIOLATION. + */ + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + std::string filedDiffData = "{\"field_name1\":true,\"field_name3\":10}"; + value.assign(filedDiffData.begin(), filedDiffData.end()); + EXPECT_EQ(kvStore->Put(key, value), CONSTRAIN_VIOLATION); + + /** + * @tc.steps:step3. Put one data whose value has different type with the schema. + * @tc.expected: step3. return INVALID_FIELD_TYPE. + */ + std::string typeDiffData = "{\"field_name1\":30,\"field_name2\":10}"; + value.assign(typeDiffData.begin(), typeDiffData.end()); + EXPECT_EQ(kvStore->Put(key, value), INVALID_FIELD_TYPE); + + /** + * @tc.steps:step4. Put one data whose value has constrain violation with the schema. + * @tc.expected: step4. return CONSTRAIN_VIOLATION. + */ + std::string constrainDiffData = "{\"field_name1\":false,\"field_name2\":null}"; + value.assign(constrainDiffData.begin(), constrainDiffData.end()); + EXPECT_EQ(kvStore->Put(key, value), CONSTRAIN_VIOLATION); + + /** + * @tc.steps:step5. Put one data whose value has invalid json. + * @tc.expected: step5. return INVALID_FORMAT. + */ + std::string invalidJsonData = "{\"field_name1\":false,\"field_name2\":10"; + value.assign(invalidJsonData.begin(), invalidJsonData.end()); + EXPECT_EQ(kvStore->Put(key, value), INVALID_FORMAT); + + /** + * @tc.steps:step6. Put one data whose value is empty. + * @tc.expected: step6. return INVALID_FORMAT. + */ + value.clear(); + EXPECT_EQ(kvStore->Put(key, value), INVALID_FORMAT); + + /** + * @tc.steps:step7. Put one data whose value is match with the schema. + * @tc.expected: step7. return INVALID_FORMAT. + */ + std::string validJsonData = "{\"field_name1\":false,\"field_name2\":10}"; + value.assign(validJsonData.begin(), validJsonData.end()); + EXPECT_EQ(kvStore->Put(key, value), OK); + } + + void CheckPutBatchSchemaData(KvStoreNbDelegate *kvStore) + { + ASSERT_NE(kvStore, nullptr); + /** + * @tc.steps:step1. Put the batch data, one data is invalid. + * @tc.expected: step1. return INVALID_FORMAT. + */ + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + std::string invalidData = "{\"field_name1\":true, \"field_name2\":null}"; + Value value(invalidData.begin(), invalidData.end()); + std::vector entries; + entries.push_back({key, value}); + + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + std::string validData = "{\"field_name1\":true, \"field_name2\":0}"; + value.assign(validData.begin(), validData.end()); + entries.push_back({key, value}); + + EXPECT_NE(kvStore->PutBatch(entries), INVALID_FORMAT); + + entries.clear(); + entries.push_back({key, value}); + + /** + * @tc.steps:step2. Put the batch data, both valid. + * @tc.expected: step2. return OK. + */ + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + validData = "{\"field_name1\":null, \"field_name2\":30}"; + value.assign(validData.begin(), validData.end()); + entries.push_back({key, value}); + + EXPECT_EQ(kvStore->PutBatch(entries), OK); + } +} +class DistributedDBInterfacesNBDelegateSchemaPutTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesNBDelegateSchemaPutTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesNBDelegateSchemaPutTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesNBDelegateSchemaPutTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBInterfacesNBDelegateSchemaPutTest::TearDown(void) +{ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvStore), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(g_storeName), OK); + g_kvStore = nullptr; + g_kvDelegateStatus = INVALID_ARGS; +} + +/** + * @tc.name: PutValueStrictSchemaCheck001 + * @tc.desc: Check the value in the strict schema mode. + * @tc.type: FUNC + * @tc.require: AR000DR9K5 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBDelegateSchemaPutTest, PutValueStrictSchemaCheck001, TestSize.Level1) +{ + g_mgr.GetKvStore(g_storeName, g_strictOpt, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvStore != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step1. Put one data whose value has more fields than the schema. + * @tc.expected: step1. return INVALID_VALUE_FIELDS. + */ + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + std::string moreData = "{\"field_name1\":true,\"field_name2\":10,\"field_name3\":10}"; + Value value(moreData.begin(), moreData.end()); + EXPECT_EQ(g_kvStore->Put(key, value), INVALID_VALUE_FIELDS); + /** + * @tc.steps:step2. Put the data whose value is mismatch with the schema. + * @tc.expected: step2. return not OK. + */ + CheckPutSchemaData(g_kvStore); + CheckPutBatchSchemaData(g_kvStore); +} + +/** + * @tc.name: PutValueReadOnlyCheck001 + * @tc.desc: Test writing the data into the no-schema kvStore which has schema originally. + * @tc.type: FUNC + * @tc.require: AR000DR9K5 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBDelegateSchemaPutTest, PutValueCompaSchemaCheck001, TestSize.Level1) +{ + g_mgr.GetKvStore(g_storeName, g_compOpt, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvStore != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps:step1. Put one data whose value has more fields than the schema. + * @tc.expected: step1. return OK. + */ + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + std::string moreData = "{\"field_name1\":true,\"field_name2\":10,\"field_name3\":10}"; + Value value(moreData.begin(), moreData.end()); + EXPECT_EQ(g_kvStore->Put(key, value), OK); + /** + * @tc.steps:step2. Put the data whose value is mismatch with the schema. + * @tc.expected: step2. return not OK. + */ + CheckPutSchemaData(g_kvStore); + CheckPutBatchSchemaData(g_kvStore); +} + +/** + * @tc.name: PutValueReadOnlyCheck001 + * @tc.desc: Test writing the data into the no-schema kvStore which has schema originally. + * @tc.type: FUNC + * @tc.require: AR000DR9K5 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBDelegateSchemaPutTest, PutValueReadOnlyCheck001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open the kv store with valid schema, and close it. + */ + g_mgr.GetKvStore(g_storeName, g_compOpt, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvStore != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvStore), OK); + /** + * @tc.steps:step2. Open the kv store with no schema. + * @tc.expected: step2. return OK. + */ + DistributedDB::KvStoreNbDelegate::Option option = g_compOpt; + option.schema.clear(); + g_mgr.GetKvStore(g_storeName, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvStore != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps:step3. Put the data. + * @tc.expected: step3. return READ_ONLY. + */ + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + std::string valueData = "{\"field_name1\":true,\"field_name2\":20}"; + Value value(valueData.begin(), valueData.end()); + EXPECT_EQ(g_kvStore->Put(key, value), READ_ONLY); +} + +/** + * @tc.name: QueryDeleted001 + * @tc.desc: Test the query in the deleted scene. + * @tc.type: FUNC + * @tc.require: AR000DR9K5 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBDelegateSchemaPutTest, QueryDeleted001, TestSize.Level1) +{ + g_mgr.GetKvStore(g_storeName, g_strictOpt, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvStore != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step1. Put 2 schema data. + * @tc.expected: step1. return OK. + */ + Key key1; + std::string valueData = "{\"field_name1\":true,\"field_name2\":1}"; + Value value(valueData.begin(), valueData.end()); + DistributedDBToolsUnitTest::GetRandomKeyValue(key1); + EXPECT_EQ(g_kvStore->Put(key1, value), OK); + + Key key2; + valueData = "{\"field_name1\":true,\"field_name2\":2}"; + value.assign(valueData.begin(), valueData.end()); + DistributedDBToolsUnitTest::GetRandomKeyValue(key2); + EXPECT_EQ(g_kvStore->Put(key2, value), OK); + + /** + * @tc.steps:step2. Get the data through the query condition where the field value is 1. + * @tc.expected: step2. GetEntries return OK, and the data num is 1. + */ + std::vector entries; + KvStoreResultSet *resultSet = nullptr; + Query query = Query::Select().EqualTo("$.field_name2", 1); + EXPECT_EQ(g_kvStore->GetEntries(query, entries), OK); + EXPECT_EQ(g_kvStore->GetEntries(query, resultSet), OK); + ASSERT_NE(resultSet, nullptr); + EXPECT_EQ(resultSet->GetCount(), 1); + int count = 0; + EXPECT_EQ(g_kvStore->GetCount(query, count), OK); + EXPECT_EQ(count, 1); + EXPECT_EQ(g_kvStore->CloseResultSet(resultSet), OK); + + /** + * @tc.steps:step3. Delete the data whose field value is 1. + */ + EXPECT_EQ(g_kvStore->Delete(key1), OK); + + /** + * @tc.steps:step4. Get the data whose field value is 1. + * @tc.expected: step4. GetEntries return NOT_FOUND, and the data num is 0. + */ + EXPECT_EQ(g_kvStore->GetEntries(query, entries), NOT_FOUND); + EXPECT_EQ(g_kvStore->GetCount(query, count), NOT_FOUND); + EXPECT_EQ(g_kvStore->GetEntries(query, resultSet), OK); + ASSERT_NE(resultSet, nullptr); + EXPECT_EQ(resultSet->GetCount(), 0); + EXPECT_EQ(g_kvStore->CloseResultSet(resultSet), OK); +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_delegate_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_delegate_test.cpp new file mode 100644 index 00000000..8b75d8a6 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_delegate_test.cpp @@ -0,0 +1,2019 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_constant.h" +#include "db_errno.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" +#include "platform_specific.h" +#include "process_system_api_adapter_impl.h" +#include "runtime_context.h" +#include "sqlite_single_ver_natural_store.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + // define some variables to init a KvStoreDelegateManager object. + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + string g_testDir; + KvStoreConfig g_config; + Key g_keyPrefix = {'A', 'B', 'C'}; + const int RESULT_SET_COUNT = 9; + const int RESULT_SET_INIT_POS = -1; + uint8_t g_testDict[RESULT_SET_COUNT] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'}; + + // define the g_kvNbDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + const int OBSERVER_SLEEP_TIME = 100; + const int BATCH_PRESET_SIZE_TEST = 10; + const int DIVIDE_BATCH_PRESET_SIZE = 5; + const int VALUE_OFFSET = 5; + + const int DEFAULT_KEY_VALUE_SIZE = 10; + + const int CON_PUT_THREAD_NUM = 4; + const int PER_THREAD_PUT_NUM = 100; + + // the type of g_kvNbDelegateCallback is function + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); + + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + + enum LockState { + UNLOCKED = 0, + LOCKED + }; + + void InitResultSet() + { + Key testKey; + Value testValue; + for (int i = 0; i < RESULT_SET_COUNT; i++) { + testKey.clear(); + testValue.clear(); + // set key + testKey = g_keyPrefix; + testKey.push_back(g_testDict[i]); + // set value + testValue.push_back(g_testDict[i]); + // insert entry + EXPECT_EQ(g_kvNbDelegatePtr->Put(testKey, testValue), OK); + } + } + + void ReadResultSet(KvStoreResultSet *readResultSet) + { + // index from 0 to 8(first to last) + for (int i = 0; i < RESULT_SET_COUNT; i++) { + Entry entry; + std::vector cursorKey = g_keyPrefix; + cursorKey.push_back(g_testDict[i]); + std::vector cursorValue; + cursorValue.push_back(g_testDict[i]); + EXPECT_TRUE(readResultSet->MoveToNext()); + EXPECT_EQ(readResultSet->GetEntry(entry), OK); + EXPECT_EQ(entry.key, cursorKey); + EXPECT_EQ(entry.value, cursorValue); + EXPECT_TRUE(!readResultSet->IsBeforeFirst()); + EXPECT_TRUE(!readResultSet->IsAfterLast()); + } + // change index to 8(last) + EXPECT_EQ(readResultSet->GetPosition(), RESULT_SET_COUNT - 1); + EXPECT_TRUE(!readResultSet->IsFirst()); + EXPECT_TRUE(readResultSet->IsLast()); + EXPECT_TRUE(!readResultSet->IsBeforeFirst()); + EXPECT_TRUE(!readResultSet->IsAfterLast()); + } + + void CheckResultSetValue(KvStoreResultSet *readResultSet, DBStatus errCode, int position) + { + Entry entry; + EXPECT_EQ(readResultSet->GetPosition(), position); + EXPECT_EQ(readResultSet->GetEntry(entry), errCode); + if (errCode == OK) { + std::vector cursorKey; + std::vector cursorValue; + if (position > RESULT_SET_INIT_POS && position < RESULT_SET_COUNT) { + uint8_t keyPostfix = g_testDict[position]; + // set key + cursorKey = g_keyPrefix; + cursorKey.push_back(keyPostfix); + // set value + cursorValue.push_back(keyPostfix); + } + // check key and value + EXPECT_EQ(entry.key, cursorKey); + EXPECT_EQ(entry.value, cursorValue); + } + } + + std::vector g_entriesForConcurrency; + void PutData(KvStoreNbDelegate *kvStore, int flag) + { + for (int i = 0; i < PER_THREAD_PUT_NUM; i++) { + int index = flag * PER_THREAD_PUT_NUM + i; + kvStore->Put(g_entriesForConcurrency[index].key, g_entriesForConcurrency[index].value); + } + LOGD("%dth put has been finished", flag); + } + + bool CheckDataTimestamp(const std::string &storeId) + { + std::string identifier = USER_ID + "-" + APP_ID + "-" + storeId; + std::string hashIdentifier = DBCommon::TransferHashString(identifier); + std::string identifierName = DBCommon::TransferStringToHex(hashIdentifier); + std::string storeDir = g_testDir + "/" + identifierName + "/" + DBConstant::SINGLE_SUB_DIR + "/" + + DBConstant::MAINDB_DIR + "/" + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + sqlite3 *db = nullptr; + EXPECT_EQ(sqlite3_open_v2(storeDir.c_str(), &db, SQLITE_OPEN_READWRITE, nullptr), SQLITE_OK); + if (db == nullptr) { + return false; + } + + static const std::string selectSQL = "select timestamp from sync_data order by rowid;"; + sqlite3_stmt *statement = nullptr; + EXPECT_EQ(sqlite3_prepare(db, selectSQL.c_str(), -1, &statement, NULL), SQLITE_OK); + std::vector timeVect; + while (sqlite3_step(statement) == SQLITE_ROW) { + timeVect.push_back(sqlite3_column_int64(statement, 0)); + } + + sqlite3_finalize(statement); + statement = nullptr; + (void)sqlite3_close_v2(db); + db = nullptr; + EXPECT_EQ(timeVect.size(), g_entriesForConcurrency.size()); + bool resultCheck = true; + if (g_entriesForConcurrency.size() > 1) { + for (size_t i = 1; i < timeVect.size(); i++) { + if (timeVect[i] <= timeVect[i - 1]) { + resultCheck = false; + break; + } + } + } + + return resultCheck; + } +} +class DistributedDBInterfacesNBDelegateTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesNBDelegateTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesNBDelegateTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); +} + +void DistributedDBInterfacesNBDelegateTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_kvDelegateStatus = INVALID_ARGS; + g_kvNbDelegatePtr = nullptr; + g_kvDelegatePtr = nullptr; +} + +void DistributedDBInterfacesNBDelegateTest::TearDown(void) +{ + if (g_kvDelegatePtr != nullptr) { + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + g_kvNbDelegatePtr = nullptr; + } + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); +} + +/** + * @tc.name: CombineTest001 + * @tc.desc: Test the NbDelegate for combined operation. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, CombineTest001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("distributed_nb_delegate_test", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + Key key; + key = {'A', 'C', 'Q'}; + Value value; + value = {'G', 'D', 'O'}; + Value valueRead; + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_LOCAL_ONLY, observer), OK); + /** + * @tc.steps:step3. Put the local data. + * @tc.expected: step3. Put returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(key, value), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + /** + * @tc.steps:step4. Check the local data. + * @tc.expected: step4. The get data is equal to the put data. + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(key, valueRead), OK); + /** + * @tc.steps:step5. Delete the local data. + * @tc.expected: step5. Delete returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocal(key), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + /** + * @tc.steps:step6. Check the local data. + * @tc.expected: step6. Couldn't find the deleted data. + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(key, valueRead), NOT_FOUND); + /** + * @tc.steps:step7. UnRegister the observer. + * @tc.expected: step7. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + Key key1; + key1 = {'D', 'B', 'N'}; + Value value1; + value1 = {'P', 'D', 'G'}; + + Key key2 = key1; + Value value2; + key2.push_back('U'); + value2 = {'C'}; + /** + * @tc.steps:step8. Put the data. + * @tc.expected: step8. Put returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(key1, value1), OK); + Value valueRead2; + /** + * @tc.steps:step9. Check the data. + * @tc.expected: step9. Getting the put data returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(key1, valueRead2), OK); + /** + * @tc.steps:step10. Put another data. + * @tc.expected: step10. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(key2, value2), OK); + std::vector vect; + /** + * @tc.steps:step10. Get the batch data using the prefix key. + * @tc.expected: step10. Results OK and the batch data size is equal to the put data size. + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(key1, vect), OK); + EXPECT_EQ(vect.size(), 2UL); + /** + * @tc.steps:step11. Delete one data. + * @tc.expected: step11. Results OK and couldn't get the deleted data. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Delete(key1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Get(key1, valueRead2), NOT_FOUND); + + LOGD("Close store"); + /** + * @tc.steps:step12. Close the kv store. + * @tc.expected: step12. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_delegate_test"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: CreateMemoryDb001 + * @tc.desc: Create memory database after. + * @tc.type: FUNC + * @tc.require: AR000CRAKN + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, CreateMemoryDb001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create Memory database by GetKvStore. + * @tc.expected: step1. Create successfully. + */ + const KvStoreNbDelegate::Option option = {true, true}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_Memorykvstore_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + KvStoreNbDelegate *kvNbDelegatePtr001 = g_kvNbDelegatePtr; + + /** + * @tc.steps: step2. Duplicate create Memory database by GetKvStore. + * @tc.expected: step2. Duplicate create successfully. + */ + g_mgr.GetKvStore("distributed_Memorykvstore_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + /** + * @tc.steps: step3. Duplicate create Memory database by GetKvStore. + * @tc.expected: step3. Duplicate create successfully. + */ + g_mgr.GetKvStore("distributed_Memorykvstore_002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + KvStoreNbDelegate *kvNbDelegatePtr002 = g_kvNbDelegatePtr; + + g_mgr.CloseKvStore(kvNbDelegatePtr001); + g_mgr.CloseKvStore(kvNbDelegatePtr002); +} + +/** + * @tc.name: CreateMemoryDb002 + * @tc.desc: The MemoryDB cannot be created or open, when the physical database has been opened + * @tc.type: FUNC + * @tc.require: AR000CRAKN + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, CreateMemoryDb002, TestSize.Level1) +{ + KvStoreNbDelegate::Option option = {true, true}; + /** + * @tc.steps: step1. Create SingleVer database by GetKvStore. + * @tc.expected: step1. Create database success. + */ + g_mgr.GetKvStore("distributed_Memorykvstore_002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + KvStoreNbDelegate *delegate1 = g_kvNbDelegatePtr; + g_kvNbDelegatePtr = nullptr; + + /** + * @tc.steps: step2. Create Memory database by GetKvStore. + * @tc.expected: step2. Create Memory database fail. + */ + option.isMemoryDb = false; + g_mgr.GetKvStore("distributed_Memorykvstore_002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus != OK); + g_mgr.CloseKvStore(delegate1); + delegate1 = nullptr; +} + +/** + * @tc.name: CreateMemoryDb003 + * @tc.desc: The physical database cannot be created or open, when the MemoryDB has been opened. + * @tc.type: FUNC + * @tc.require: AR000CRAKN + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, CreateMemoryDb003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get singleVer kvStore by GetKvStore. + * @tc.expected: step1. Get database success. + */ + KvStoreDelegate::Option option; + g_mgr.GetKvStore("distributed_Memorykvstore_003", option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step2. Create Memory database by GetKvStore. + * @tc.expected: step2. Create Memory database fail. + */ + KvStoreNbDelegate::Option nbOption = {true, true}; + g_mgr.GetKvStore("distributed_Memorykvstore_003", nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr == nullptr); + EXPECT_TRUE(g_kvDelegateStatus != OK); + g_mgr.CloseKvStore(g_kvDelegatePtr); + g_kvDelegatePtr = nullptr; +} + +/** + * @tc.name: OperMemoryDbData001 + * @tc.desc: Operate memory database + * @tc.type: FUNC + * @tc.require: AR000CRAKN + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, OperMemoryDbData001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create Memory database by GetKvStore. + */ + const KvStoreNbDelegate::Option option = {true, true}; + g_mgr.GetKvStore("distributed_OperMemorykvstore_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step2. Put (KEY_1,VALUE_1)(KEY_2,VALUE_2) to Memory database. + * @tc.expected: step2. Success. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_2), OK); + + /** + * @tc.steps: step3. Get (KEY_1,VALUE_1)(KEY_2,VALUE_2) to Memory database. + * @tc.expected: step3. Success. + */ + Value readValueKey1; + Value readValueKey2; + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValueKey1), OK); + EXPECT_EQ(readValueKey1, VALUE_1); + + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_2, readValueKey2), OK); + EXPECT_EQ(readValueKey2, VALUE_2); + + /** + * @tc.steps: step4. Delete K1 from Memory database. + * @tc.expected: step4. Success. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Delete(KEY_1), OK); + + /** + * @tc.steps: step5. Get K1 from Memory database. + * @tc.expected: step5. NOT_FOUND. + */ + readValueKey1.clear(); + readValueKey2.clear(); + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValueKey1), NOT_FOUND); + + /** + * @tc.steps: step6. Update K2 value from Memory database. + * @tc.expected: step6. Get the right value after the update. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_3), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_2, readValueKey2), OK); + EXPECT_EQ(readValueKey2, VALUE_3); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); +} + +/** + * @tc.name: CloseMemoryDb001 + * @tc.desc: Operate memory database after reopen memory database + * @tc.type: FUNC + * @tc.require: AR000CRAKN + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, CloseMemoryDb001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create Memory database by GetKvStore. + */ + const KvStoreNbDelegate::Option option = {true, true}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_CloseMemorykvstore_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step2/3. Put and get to Memory database. + * @tc.expected: step2/3. Success and the value is right. + */ + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + + /** + * @tc.steps: step4. Close the Memory database. + * @tc.expected: step4. Success. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + /** + * @tc.steps: step5. Reopen the Memory database. + * @tc.expected: step5. Success. + */ + g_mgr.GetKvStore("distributed_CloseMemorykvstore_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step6. Get the key1 which has been put into the Memory database. + * @tc.expected: step6. Return NOT_FOUND. + */ + readValue.clear(); + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), NOT_FOUND); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); +} + +/** + * @tc.name: ResultSetTest001 + * @tc.desc: Test the NbDelegate for result set function. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, ResultSetTest001, TestSize.Level1) +{ + /** + * @tc.steps: step1. initialize result set. + * @tc.expected: step1. Success. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("distributed_nb_delegate_result_set_test", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + InitResultSet(); + + /** + * @tc.steps: step2. get entries using result set. + * @tc.expected: step2. Success. + */ + KvStoreResultSet *readResultSet = nullptr; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(g_keyPrefix, readResultSet), OK); + ASSERT_TRUE(readResultSet != nullptr); + EXPECT_EQ(readResultSet->GetCount(), RESULT_SET_COUNT); + + /** + * @tc.steps: step3. result function check. + * @tc.expected: step3. Success. + */ + CheckResultSetValue(readResultSet, NOT_FOUND, RESULT_SET_INIT_POS); + // index from 0 to 8(first to last) + ReadResultSet(readResultSet); + // change index to 9(after last) + EXPECT_TRUE(!readResultSet->MoveToNext()); + CheckResultSetValue(readResultSet, NOT_FOUND, RESULT_SET_COUNT); + // change index to 8(last) + EXPECT_TRUE(readResultSet->MoveToPrevious()); + CheckResultSetValue(readResultSet, OK, RESULT_SET_COUNT - 1); + // change index to 0(first) + EXPECT_TRUE(readResultSet->MoveToFirst()); + CheckResultSetValue(readResultSet, OK, RESULT_SET_INIT_POS + 1); + // change index to 8(last) + EXPECT_TRUE(readResultSet->MoveToLast()); + CheckResultSetValue(readResultSet, OK, RESULT_SET_COUNT - 1); + // move to -4: change index to -1 + EXPECT_TRUE(!readResultSet->MoveToPosition(RESULT_SET_INIT_POS - 3)); + CheckResultSetValue(readResultSet, NOT_FOUND, RESULT_SET_INIT_POS); + // move to 10: change index to 9 + EXPECT_TRUE(!readResultSet->MoveToPosition(RESULT_SET_COUNT + 1)); + CheckResultSetValue(readResultSet, NOT_FOUND, RESULT_SET_COUNT); + // change index to 2 + EXPECT_TRUE(readResultSet->MoveToPosition(RESULT_SET_INIT_POS + 3)); + CheckResultSetValue(readResultSet, OK, RESULT_SET_INIT_POS + 3); + // move 0: change index to 2 + EXPECT_TRUE(readResultSet->Move(0)); + CheckResultSetValue(readResultSet, OK, RESULT_SET_INIT_POS + 3); + // change index to 6 + EXPECT_TRUE(readResultSet->Move(RESULT_SET_INIT_POS + 5)); + CheckResultSetValue(readResultSet, OK, RESULT_SET_INIT_POS + 7); + // change index to 3 + EXPECT_TRUE(readResultSet->Move(RESULT_SET_INIT_POS - 2)); + CheckResultSetValue(readResultSet, OK, RESULT_SET_INIT_POS + 4); + // move -5: change index to -1 + EXPECT_TRUE(!readResultSet->Move(-5)); + CheckResultSetValue(readResultSet, NOT_FOUND, RESULT_SET_INIT_POS); + + // move INT_MIN: change index to -1 + EXPECT_TRUE(!readResultSet->Move(INT_MIN)); + CheckResultSetValue(readResultSet, NOT_FOUND, RESULT_SET_INIT_POS); + + EXPECT_TRUE(readResultSet->Move(5)); + EXPECT_TRUE(!readResultSet->Move(INT_MAX)); + CheckResultSetValue(readResultSet, NOT_FOUND, RESULT_SET_COUNT); + + /** + * @tc.steps: step4. clear the result set resource. + * @tc.expected: step4. Success. + */ + EXPECT_EQ(g_kvNbDelegatePtr->CloseResultSet(readResultSet), OK); + EXPECT_TRUE(readResultSet == nullptr); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_delegate_result_set_test"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: PutBatchVerify001 + * @tc.desc: This test case use to verify the putBatch interface function + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, PutBatchVerify001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get singleVer kvStore by GetKvStore. + * @tc.expected: step1. Get database success. + */ + const KvStoreNbDelegate::Option option = {true, true}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_PutBatchVerify_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step2. Insert 10 records into database. + * @tc.expected: step2. Insert successfully. + */ + vector entries; + for (int i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(i); + entries.push_back(entry); + } + + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entries), OK); + + for (int i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + Key key; + key.push_back(i); + Value value; + g_kvNbDelegatePtr->Get(key, value); + EXPECT_EQ(key, value); + } + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutBatch001 + * @tc.desc: Check for illegal parameters + * @tc.type: FUNC + * @tc.require: AR000DPTQ8 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerPutBatch001, TestSize.Level1) +{ + /** + * @tc.steps: step1. + * Create and construct three sets of vector , each set of three data contains records: + * (K1, V1) It is illegal for K1 to be greater than 1K, and V1 is 1K in size + * (K2, V2) K2 is legal, V2 is greater than 4M + * (K3, V3) are not legal. + */ + Key illegalKey; + DistributedDBToolsUnitTest::GetRandomKeyValue(illegalKey, DBConstant::MAX_KEY_SIZE + 1); // 1K + 1 + Value illegalValue; + DistributedDBToolsUnitTest::GetRandomKeyValue(illegalValue, DBConstant::MAX_VALUE_SIZE + 1); // 4M + 1 + vector entrysKeyIllegal = {KV_ENTRY_1, KV_ENTRY_2, {illegalKey, VALUE_3}}; + vector entrysValueIllegal = {KV_ENTRY_1, KV_ENTRY_2, {KEY_3, illegalValue}}; + vector entrysIllegal = {KV_ENTRY_1, KV_ENTRY_2, {illegalKey, illegalValue}}; + + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_SingleVerPutBatch_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps: step2. PutBatch operates on three sets of data. + * @tc.expected: step2. All three operations return INVALID_ARGS. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrysKeyIllegal), INVALID_ARGS); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrysValueIllegal), INVALID_ARGS); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrysIllegal), INVALID_ARGS); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutBatch_001"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutBatch002 + * @tc.desc: PutBatch normal insert function test. + * @tc.type: FUNC + * @tc.require: AR000DPTQ8 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerPutBatch002, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_SingleVerPutBatch_002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps: step1. + * Create and build 4 groups of vector , which are: + * Vect of empty objects; + * Vect1 of a legal Entry record; + * 128 legal Entry records Vect2; + * 129 legal Entry records Vect3; + */ + vector entrysMaxNumber; + for (size_t i = 0; i < DBConstant::MAX_BATCH_SIZE; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(i); + entrysMaxNumber.push_back(entry); + } + Key keyTemp = {'1', '1'}; + Value valueTemp; + Entry entryTemp = {keyTemp, VALUE_1}; + vector entrysOneRecord = {entryTemp}; + vector entrysOverSize = entrysMaxNumber; + entrysOverSize.push_back(entryTemp); + /** + * @tc.steps: step2. PutBatch operates on four sets of data. and use get check the result of Vect3. + * @tc.expected: step2. Returns INVALID_ARGS for 129 records, and returns OK for the rest. all get return NOT_FOUND. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrysOverSize), INVALID_ARGS); + for (size_t i = 0; i < entrysOverSize.size(); i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(entrysOverSize[i].key, valueTemp), NOT_FOUND); + } + /** + * @tc.steps: step3. Use get check the result of Vect2. + * @tc.expected: step3. Return OK and get the correct value. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrysOneRecord), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Get(keyTemp, valueTemp), OK); + EXPECT_EQ(valueTemp, VALUE_1); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrysMaxNumber), OK); + /** + * @tc.steps: step4. Use get check the result of Vect3. + * @tc.expected: step4. Return OK and get the correct value. + */ + for (size_t i = 0; i < entrysMaxNumber.size(); i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(entrysMaxNumber[i].key, valueTemp), OK); + EXPECT_EQ(valueTemp, entrysMaxNumber[i].value); + } + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutBatch_002"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutBatch003 + * @tc.desc: Check interface atomicity + * @tc.type: FUNC + * @tc.require: AR000DPTQ8 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerPutBatch003, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_SingleVerPutBatch_003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps: step1. Create and construct a set of vector with a total of 128 data, + * including one illegal data. And call PutBatch interface to insert. + */ + vector entrysMaxNumber; + for (size_t i = 0; i < DBConstant::MAX_BATCH_SIZE; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(i); + entrysMaxNumber.push_back(entry); + } + Key illegalKey; + Value valueTemp; + DistributedDBToolsUnitTest::GetRandomKeyValue(illegalKey, DBConstant::MAX_KEY_SIZE + 1); // 1K + 1 + entrysMaxNumber[0].key = illegalKey; + + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrysMaxNumber), INVALID_ARGS); + /** + * @tc.steps: step2. Use Get interface to query 128 corresponding key values. + * @tc.expected: step2. All Get interface return NOT_FOUND. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(entrysMaxNumber[0].key, valueTemp), INVALID_ARGS); + for (size_t i = 1; i < entrysMaxNumber.size(); i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(entrysMaxNumber[i].key, valueTemp), NOT_FOUND); + } + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutBatch_003"), OK); + g_kvNbDelegatePtr = nullptr; +} + +static void PreparePutBatch004(vector &entrys1, vector &entrys2, vector &entrys3) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_SingleVerPutBatch_004", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + for (int i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(i); + entrys1.push_back(entry); + } + + for (int i = 0; i < DIVIDE_BATCH_PRESET_SIZE; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(i + VALUE_OFFSET); + entrys2.push_back(entry); + } + + for (int i = DIVIDE_BATCH_PRESET_SIZE; i < BATCH_PRESET_SIZE_TEST; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(i - VALUE_OFFSET); + entrys3.push_back(entry); + } +} + +/** + * @tc.name: SingleVerPutBatch004 + * @tc.desc: Check interface data insertion and update functions. + * @tc.type: FUNC + * @tc.require: AR000DPTQ8 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerPutBatch004, TestSize.Level1) +{ + /** + * @tc.steps: step1. + * Construct three groups of three vector : + * (1) entrys1: key1 ~ 10, corresponding to Value1 ~ 10; + * (2) entrys2: key1 ~ 5, corresponding to Value6 ~ 10; + * (3) entrys3: key6 ~ 10, corresponding to Value1 ~ 5; + */ + vector entrys1; + vector entrys2; + vector entrys3; + PreparePutBatch004(entrys1, entrys2, entrys3); + /** + * @tc.steps: step2. PutBatch entrys2. + * @tc.expected: step2. PutBatch return OK. + */ + Value valueRead; + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrys2), OK); + /** + * @tc.steps: step3. Check PutBatch result. + * @tc.expected: step3. Get correct value of key1~5. Key6~10 return NOT_FOUND. + */ + for (int i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + Key keyTemp; + keyTemp.push_back(i); + if (i < DIVIDE_BATCH_PRESET_SIZE) { + Value valueTemp; + valueTemp.push_back(i + VALUE_OFFSET); + EXPECT_EQ(g_kvNbDelegatePtr->Get(keyTemp, valueRead), OK); + EXPECT_EQ(valueRead, valueTemp); + continue; + } + EXPECT_EQ(g_kvNbDelegatePtr->Get(keyTemp, valueRead), NOT_FOUND); + } + /** + * @tc.steps: step4. PutBatch entrys1. + * @tc.expected: step4. PutBatch return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrys1), OK); + /** + * @tc.steps: step5. Check PutBatch result. + * @tc.expected: step5. Update and insert value of key1~10 to value1~10. + */ + for (int i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + Key keyTemp; + keyTemp.push_back(i); + if (i < DIVIDE_BATCH_PRESET_SIZE) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(keyTemp, valueRead), OK); + EXPECT_EQ(valueRead, keyTemp); + continue; + } + EXPECT_EQ(g_kvNbDelegatePtr->Get(keyTemp, valueRead), OK); + EXPECT_EQ(valueRead, keyTemp); + } + /** + * @tc.steps: step6. PutBatch entrys3. + * @tc.expected: step6. PutBatch return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrys3), OK); + /** + * @tc.steps: step7. Check PutBatch result of key1~10. + * @tc.expected: step7. Update value of key5~10 to value1~5. + */ + for (int i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + Key keyTemp; + keyTemp.push_back(i); + if (i < DIVIDE_BATCH_PRESET_SIZE) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(keyTemp, valueRead), OK); + EXPECT_EQ(valueRead, keyTemp); + continue; + } + Value valueTemp; + valueTemp.push_back(i - VALUE_OFFSET); + EXPECT_EQ(g_kvNbDelegatePtr->Get(keyTemp, valueRead), OK); + EXPECT_EQ(valueRead, valueTemp); + } + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutBatch_004"), OK); + g_kvNbDelegatePtr = nullptr; +} + +static void CreatEntrys(int recordSize, vector &keys, vector &values, vector &entries) +{ + keys.clear(); + values.clear(); + entries.clear(); + for (int i = 0; i < recordSize; i++) { + string temp = to_string(i); + Entry entry; + Key keyTemp; + Value valueTemp; + for (auto &iter : temp) { + entry.key.push_back(iter); + entry.value.push_back(iter); + keyTemp.push_back(iter); + valueTemp.push_back(iter); + } + keys.push_back(keyTemp); + values.push_back(valueTemp); + entries.push_back(entry); + } +} + +/** + * @tc.name: SingleVerDeleteBatch001 + * @tc.desc: Check for illegal parameters. + * @tc.type: FUNC + * @tc.require: AR000DPTQ8 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerDeleteBatch001, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_SingleVerPutBatch_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps: step1. Create and construct a set of vector , containing a total of 10 data keys1 ~ 10, + * Value1 ~ 10, and call Putbatch interface to insert data. + * @tc.expected: step1. PutBatch successfully. + */ + vector entries; + vector keys; + vector values; + Value valueRead; + CreatEntrys(BATCH_PRESET_SIZE_TEST, keys, values, entries); + vector entrysBase = entries; + vector keysBase = keys; + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrysBase), OK); + /** + * @tc.steps: step2. Use Get to check data in database. + * @tc.expected: step2. Get value1~10 by key1~10 successfully. + */ + for (size_t i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(entrysBase[i].key, valueRead), OK); + } + /** + * @tc.steps: step3. Use DeleteBatch interface to transfer 10 + 119 extra keys (total 129). + * @tc.expected: step3. Return INVALID_ARGS. + */ + CreatEntrys(DBConstant::MAX_BATCH_SIZE + 1, keys, values, entries); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keys), INVALID_ARGS); + /** + * @tc.steps: step4. Use Get to check data in database. + * @tc.expected: step4. Key1~10 still in database. + */ + for (size_t i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(entrysBase[i].key, valueRead), OK); + } + /** + * @tc.steps: step5. Use the DeleteBatch interface to pass in 10 included + * keys6 ~ 10 + 123 additional key values ​​(128 in total). + * @tc.expected: step5. DeleteBatch OK. + */ + CreatEntrys(DBConstant::MAX_BATCH_SIZE + DIVIDE_BATCH_PRESET_SIZE, keys, values, entries); + keys.erase(keys.begin(), keys.begin() + DIVIDE_BATCH_PRESET_SIZE); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keys), OK); + /** + * @tc.steps: step6. Use Get to check key1~10 in database. + * @tc.expected: step6. Key1~5 in database, key6~10 have been deleted. + */ + for (size_t i = 0; i < DIVIDE_BATCH_PRESET_SIZE; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(entrysBase[i].key, valueRead), OK); + } + for (size_t i = DIVIDE_BATCH_PRESET_SIZE; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(entrysBase[i].key, valueRead), NOT_FOUND); + } + /** + * @tc.steps: step7. Repeat Putbatch key1~10, value1~10. + * @tc.expected: step7. Return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrysBase), OK); + + Key illegalKey; + DistributedDBToolsUnitTest::GetRandomKeyValue(illegalKey, DBConstant::MAX_KEY_SIZE + 1); // 1K + 1 + keysBase.push_back(illegalKey); + /** + * @tc.steps: step8. Use DeleteBatch interface to pass in 10 + 1(larger than 1K) keys. + * @tc.expected: step8. Return INVALID_ARGS. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keysBase), INVALID_ARGS); + /** + * @tc.steps: step9. Use Get to check key1~10 in database. + * @tc.expected: step9. Delete those data failed. + */ + for (size_t i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(entrysBase[i].key, valueRead), OK); + } + /** + * @tc.steps: step10. Use DeleteBatch interface to pass in 10(in database) + 1 valid keys. + * @tc.expected: step10. Delete those data successfully. + */ + keysBase.back().erase(keysBase.back().begin(), keysBase.back().begin() + 1); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keysBase), OK); + /** + * @tc.steps: step11. Check data. + * @tc.expected: step11. DeleteBatch successfully. + */ + for (size_t i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(entrysBase[i].key, valueRead), NOT_FOUND); + } + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutBatch_001"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerDeleteBatch002 + * @tc.desc: Check normal delete batch ability. + * @tc.type: FUNC + * @tc.require: AR000DPTQ8 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerDeleteBatch002, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.GetKvStore("distributed_SingleVerPutBatch_002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps: step1. Create a group of vector , containing a total of 10 data keys1 ~ 10, Value1 ~ 10, + * call the Putbatch interface to insert data. + * @tc.expected: step1. Insert to database successfully. + */ + vector entries; + vector keysBase; + vector values; + CreatEntrys(BATCH_PRESET_SIZE_TEST, keysBase, values, entries); + + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entries), OK); + /** + * @tc.steps: step2. Check data. + * @tc.expected: step2. Get key1~10 successfully. + */ + Value valueRead; + for (size_t i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(keysBase[i], valueRead), OK); + } + /** + * @tc.steps: step3. DeleteBatch key1~5. + * @tc.expected: step3. Return OK. + */ + vector keys(keysBase.begin(), keysBase.begin() + DIVIDE_BATCH_PRESET_SIZE); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keys), OK); + /** + * @tc.steps: step4. Check key1~10. + * @tc.expected: step4. Key1~5 deleted, key6~10 existed. + */ + for (size_t i = 0; i < DIVIDE_BATCH_PRESET_SIZE; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(keysBase[i], valueRead), NOT_FOUND); + } + for (size_t i = DIVIDE_BATCH_PRESET_SIZE; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(keysBase[i], valueRead), OK); + } + /** + * @tc.steps: step5. DeleteBatch key1~10. + * @tc.expected: step5. Return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keysBase), OK); + /** + * @tc.steps: step6. Check key1~10. + * @tc.expected: step6. Key1~10 deleted successfully. + */ + for (size_t i = 0; i < BATCH_PRESET_SIZE_TEST; i++) { + EXPECT_EQ(g_kvNbDelegatePtr->Get(keysBase[i], valueRead), NOT_FOUND); + } + /** + * @tc.steps: step7. DeleteBatch key1~10 once again. + * @tc.expected: step7. Return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keysBase), OK); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutBatch_002"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutBatchObserver001 + * @tc.desc: Test the observer function of PutBatch() interface. + * @tc.type: FUNC + * @tc.require: AR000DPTTA + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerPutBatchObserver001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("distributed_SingleVerPutBatchObserver_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_NATIVE, observer), OK); + /** + * @tc.steps:step3. Put batch data. + * @tc.expected: step3. Returns OK. + */ + vector entrysBase; + vector keysBase; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST + 1, entrysBase, keysBase); + + vector entries(entrysBase.begin(), entrysBase.end() - 1); + EXPECT_EQ(entries.size(), 10UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entries), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entries, observer->GetEntriesInserted())); + /** + * @tc.steps:step4. Delete the batch data. + * @tc.expected: step4. Returns OK. + */ + vector keys(keysBase.begin() + 5, keysBase.end()); + EXPECT_EQ(keys.size(), 6UL); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keys), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + vector entrysDel(entrysBase.begin() + 5, entrysBase.end() - 1); + EXPECT_EQ(entrysDel.size(), 5UL); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entrysDel, observer->GetEntriesDeleted())); + /** + * @tc.steps:step5. UnRegister the observer. + * @tc.expected: step5. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + /** + * @tc.steps:step6. Close the kv store. + * @tc.expected: step6. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutBatchObserver_001"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutBatchObserver002 + * @tc.desc: Test the observer function of PutBatch() for invalid input. + * @tc.type: FUNC + * @tc.require: AR000DPTTA + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerPutBatchObserver002, TestSize.Level4) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("distributed_SingleVerPutBatchObserver_002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_NATIVE, observer), OK); + /** + * @tc.steps:step3. Put 129 batch data. + * @tc.expected: step3. Returns INVALID_ARGS. + */ + vector entrys1; + vector keys1; + DistributedDBUnitTest::GenerateRecords(DBConstant::MAX_BATCH_SIZE + 1, entrys1, keys1); + + EXPECT_EQ(entrys1.size(), 129UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrys1), INVALID_ARGS); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(observer->GetEntriesInserted().empty()); + /** + * @tc.steps:step4. Put invalid batch data. + * @tc.expected: step4. Returns INVALID_ARGS. + */ + vector entrys2; + vector keys2; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrys2, keys2); + EXPECT_EQ(entrys2.size(), 10UL); + + vector entrysInvalid; + vector keysInvalid; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrysInvalid, keysInvalid, + DBConstant::MAX_KEY_SIZE + 10); + EXPECT_EQ(entrysInvalid.size(), 10UL); + entrys2[0].key = entrysInvalid[0].key; + + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrys2), INVALID_ARGS); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(observer->GetEntriesInserted().empty()); + /** + * @tc.steps:step5. Put MAX valid value batch data. + * @tc.expected: step5. Returns OK. + */ + vector entrys3; + vector keys3; + + DistributedDBUnitTest::GenerateRecords(DBConstant::MAX_BATCH_SIZE, entrys3, keys3); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrys3), OK); + LOGD("sleep begin"); + // sleep 20 seconds + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME * 10)); + LOGD("sleep end"); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entrys3, observer->GetEntriesInserted())); + /** + * @tc.steps:step6. UnRegister the observer. + * @tc.expected: step6. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + /** + * @tc.steps:step7. Close the kv store. + * @tc.expected: step7. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutBatchObserver_002"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutBatchObserver003 + * @tc.desc: Test the observer function of PutBatch() update function. + * @tc.type: FUNC + * @tc.require: AR000DPTTA + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerPutBatchObserver003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("distributed_SingleVerPutBatchObserver_003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_NATIVE, observer), OK); + /** + * @tc.steps:step3. Put batch data. + * @tc.expected: step3. Returns OK. + */ + vector entrysAdd; + vector keysAdd; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrysAdd, keysAdd); + + EXPECT_EQ(entrysAdd.size(), 10UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrysAdd), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entrysAdd, observer->GetEntriesInserted())); + /** + * @tc.steps:step4. Update the batch data. + * @tc.expected: step4. Returns OK. + */ + vector entrysUpdate; + vector keysUpdate; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrysUpdate, keysUpdate, DEFAULT_KEY_VALUE_SIZE, + DEFAULT_KEY_VALUE_SIZE + 10); + + EXPECT_EQ(entrysUpdate.size(), 10UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrysUpdate), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entrysUpdate, observer->GetEntriesUpdated())); + /** + * @tc.steps:step5. UnRegister the observer. + * @tc.expected: step5. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + /** + * @tc.steps:step6. Close the kv store. + * @tc.expected: step6. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutBatchObserver_003"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPutBatchObserver004 + * @tc.desc: Test the observer function of PutBatch(), same keys handle. + * @tc.type: FUNC + * @tc.require: AR000DPTTA + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerPutBatchObserver004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("distributed_SingleVerPutBatchObserver_004", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_NATIVE, observer), OK); + /** + * @tc.steps:step3. Put batch data. + * @tc.expected: step3. Returns OK. + */ + vector entrys1; + vector keys1; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrys1, keys1); + vector entrys2; + vector keys2; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrys2, keys2, DEFAULT_KEY_VALUE_SIZE, + DEFAULT_KEY_VALUE_SIZE + 10); + entrys1.insert(entrys1.end(), entrys2.begin(), entrys2.end()); + + EXPECT_EQ(entrys1.size(), 20UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrys1), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entrys2, observer->GetEntriesInserted())); + EXPECT_EQ(observer->GetEntriesUpdated().size(), 0UL); + + vector entrys3; + vector keys3; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrys3, keys3, DEFAULT_KEY_VALUE_SIZE, + DEFAULT_KEY_VALUE_SIZE + 20); + vector entrys4; + vector keys4; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entrys4, keys4, DEFAULT_KEY_VALUE_SIZE, + DEFAULT_KEY_VALUE_SIZE + 30); + entrys3.insert(entrys3.end(), entrys4.begin(), entrys4.end()); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrys3), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entrys4, observer->GetEntriesUpdated())); + EXPECT_EQ(observer->GetEntriesInserted().size(), 0UL); + + /** + * @tc.steps:step4. UnRegister the observer. + * @tc.expected: step4. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + /** + * @tc.steps:step5. Close the kv store. + * @tc.expected: step5. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerPutBatchObserver_004"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerDeleteBatchObserver001 + * @tc.desc: Test the observer function of DeleteBatch() interface. + * @tc.type: FUNC + * @tc.require: AR000DPTTA + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerDeleteBatchObserver001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("distributed_SingleVerDeleteBatchObserver_001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_NATIVE, observer), OK); + /** + * @tc.steps:step3. Put batch data. + * @tc.expected: step3. Returns OK. + */ + vector entries; + vector keys; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entries, keys); + EXPECT_EQ(entries.size(), 10UL); + + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entries), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entries, observer->GetEntriesInserted())); + /** + * @tc.steps:step4. Delete the batch data. + * @tc.expected: step4. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keys), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entries, observer->GetEntriesDeleted())); + /** + * @tc.steps:step5. UnRegister the observer. + * @tc.expected: step5. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + /** + * @tc.steps:step6. Close the kv store. + * @tc.expected: step6. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_SingleVerDeleteBatchObserver_001"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerConcurrentPut001 + * @tc.desc: Test put the data concurrently, and check the timestamp. + * @tc.type: FUNC + * @tc.require: AR000DPTTA + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerConcurrentPut001, TestSize.Level4) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("concurrentPutTest", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + std::vector entries; + for (size_t i = 0; i < CON_PUT_THREAD_NUM * PER_THREAD_PUT_NUM; i++) { + Entry entry; + DistributedDBToolsUnitTest::GetRandomKeyValue(entry.key, DEFAULT_KEY_VALUE_SIZE); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry.value); + g_entriesForConcurrency.push_back(std::move(entry)); + } + + /** + * @tc.steps:step2. Put data concurrently in 4 threads. + * @tc.expected: step2. Put OK, and the timestamp order is same with the rowid. + */ + std::thread thread1(std::bind(PutData, g_kvNbDelegatePtr, 0)); // 0th thread. + std::thread thread2(std::bind(PutData, g_kvNbDelegatePtr, 1)); // 1th thread. + std::thread thread3(std::bind(PutData, g_kvNbDelegatePtr, 2)); // 2th thread. + std::thread thread4(std::bind(PutData, g_kvNbDelegatePtr, 3)); // 3th thread. + + thread1.join(); + thread2.join(); + thread3.join(); + thread4.join(); + + EXPECT_EQ(CheckDataTimestamp("concurrentPutTest"), true); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("concurrentPutTest"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerGetLocalEntries001 + * @tc.desc: Test GetLocalEntries interface for the single ver database. + * @tc.type: FUNC + * @tc.require: AR000DPTTA + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerGetLocalEntries001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("concurrentPutTest", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Put one data whose key has prefix 'p' into the local zone. + */ + Entry entry1 = {{'p'}, {'q'}}; + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(entry1.key, entry1.value), OK); + + /** + * @tc.steps:step3. Get batch data whose key has prefix 'k' from the local zone. + * @tc.expected: step3. Get results NOT_FOUND. + */ + std::vector entries; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocalEntries({'k'}, entries), NOT_FOUND); + + /** + * @tc.steps:step4. Put two data whose key have prefix 'k' into the local zone. + */ + Entry entry2 = {{'k', '1'}, {'d'}}; + Entry entry3 = {{'k', '2'}, {'d'}}; + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(entry2.key, entry2.value), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(entry3.key, entry3.value), OK); + + /** + * @tc.steps:step5. Get batch data whose key has prefix 'k' from the local zone. + * @tc.expected: step5. Get results OK, and the entries size is 2. + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocalEntries({'k'}, entries), OK); + EXPECT_EQ(entries.size(), 2UL); + + /** + * @tc.steps:step6. Get batch data whose key has empty prefix from the local zone. + * @tc.expected: step6. Get results OK, and the entries size is 3. + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocalEntries({}, entries), OK); + EXPECT_EQ(entries.size(), 3UL); + + /** + * @tc.steps:step7. Delete one data whose key has prefix 'k' from the local zone. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocal(entry3.key), OK); + + /** + * @tc.steps:step8. Get batch data whose key has prefix 'k' from the local zone. + * @tc.expected: step8. Get results OK, and the entries size is 1. + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocalEntries({'k'}, entries), OK); + EXPECT_EQ(entries.size(), 1UL); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("concurrentPutTest"), OK); + g_kvNbDelegatePtr = nullptr; +} + +static vector PreDataForQueryByPreFixKey() +{ + vector res; + for (int i = 0; i < 5; i++) { // Random 5 for test + Key key = DistributedDBToolsUnitTest::GetRandPrefixKey({'a', 'b'}, 1024); + std::string validData = "{\"field_name1\":null, \"field_name2\":" + std::to_string(rand()) + "}"; + Value value(validData.begin(), validData.end()); + res.push_back({key, value}); + } + + for (int i = 0; i < 5; i++) { // Random 5 for test + Key key = DistributedDBToolsUnitTest::GetRandPrefixKey({'a', 'c'}, 1024); + std::string validData = "{\"field_name1\":null, \"field_name2\":" + std::to_string(rand()) + "}"; + Value value(validData.begin(), validData.end()); + res.push_back({key, value}); + } + return res; +} + +/** + * @tc.name: QueryPreFixKey002 + * @tc.desc: The query method without filtering the field can query non-schma databases + * @tc.type: FUNC + * @tc.require: AR000EPARK + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, QueryPreFixKey002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Create non-schma databases + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("QueryPreFixKey002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + vector entries = PreDataForQueryByPreFixKey(); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entries), OK); + + /** + * @tc.steps:step2. Get query object with prefixkey limit combination. + * @tc.expected: step2. Get results OK, and the entries size right. + */ + Query query = Query::Select().PrefixKey({'a', 'c'}); + std::vector entriesRes; + int errCode = g_kvNbDelegatePtr->GetEntries(query, entriesRes); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(entriesRes.size(), 5ul); + for (size_t i = 0; i < entriesRes.size(); i++) { + EXPECT_EQ(entriesRes[i].key.front(), 'a'); + EXPECT_EQ(entriesRes[i].key[1], 'c'); + } + int count = -1; + g_kvNbDelegatePtr->GetCount(query, count); + EXPECT_EQ(count, 5); + + Query query1 = Query::Select().PrefixKey({}).Limit(4, 0); + errCode = g_kvNbDelegatePtr->GetEntries(query1, entriesRes); + EXPECT_EQ(errCode, OK); + EXPECT_EQ(entriesRes.size(), 4ul); + + Query query2 = Query::Select().PrefixKey(Key(1025, 'a')); + errCode = g_kvNbDelegatePtr->GetEntries(query2, entriesRes); + EXPECT_EQ(errCode, INVALID_ARGS); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_TRUE(g_mgr.DeleteKvStore("QueryPreFixKey002") == OK); +} + +/** + * @tc.name: SingleVerGetSecurityOption001 + * @tc.desc: Test GetSecurityOption interface for the single ver database. + * @tc.type: FUNC + * @tc.require: AR000EV1G2 + * @tc.author: liuwenkai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerGetSecurityOption001, TestSize.Level1) +{ + SecurityOption savedOption; + std::shared_ptr adapter = std::make_shared(); + EXPECT_TRUE(adapter); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(adapter); + KvStoreNbDelegate::Option option = {true, false, false}; + + /** + * @tc.steps:step1. Create databases without securityOption. + * @tc.expected: step2. Returns a non-null kvstore but can not get SecurityOption. + */ + g_mgr.GetKvStore("SingleVerGetSecurityOption001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(g_kvNbDelegatePtr->GetSecurityOption(savedOption) == OK); + EXPECT_TRUE(savedOption.securityLabel == 0); + EXPECT_TRUE(savedOption.securityFlag == 0); + KvStoreNbDelegate *kvNbDelegatePtr1 = g_kvNbDelegatePtr; + + /** + * @tc.steps:step2. Create databases with new securityOption(Check ignore the new option). + * @tc.expected: step2. Returns non-null kvstore. + */ + option.secOption.securityLabel = S3; + option.secOption.securityFlag = 1; + g_mgr.GetKvStore("SingleVerGetSecurityOption001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(g_kvNbDelegatePtr->GetSecurityOption(savedOption) == OK); + EXPECT_TRUE(savedOption.securityLabel == 0); + EXPECT_TRUE(savedOption.securityFlag == 0); + + EXPECT_EQ(g_mgr.CloseKvStore(kvNbDelegatePtr1), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore("SingleVerGetSecurityOption001") == OK); +} + +/** + * @tc.name: SingleVerGetSecurityOption002 + * @tc.desc: Test GetSecurityOption interface for the single ver database. + * @tc.type: FUNC + * @tc.require: AR000EV1G2 + * @tc.author: liuwenkai + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, SingleVerGetSecurityOption002, TestSize.Level1) +{ + SecurityOption savedOption; + std::shared_ptr adapter = std::make_shared(); + EXPECT_TRUE(adapter != nullptr); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(adapter); + KvStoreNbDelegate::Option option = {true, false, false}; + + /** + * @tc.steps:step1. Create databases with securityOption. + * @tc.expected: step2. Returns a non-null kvstore and get right SecurityOption. + */ + option.secOption.securityLabel = S3; + option.secOption.securityFlag = 1; + g_mgr.GetKvStore("SingleVerGetSecurityOption002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(g_kvNbDelegatePtr->GetSecurityOption(savedOption) == OK); + EXPECT_TRUE(savedOption.securityLabel == S3); + EXPECT_TRUE(savedOption.securityFlag == 1); + KvStoreNbDelegate *kvNbDelegatePtr1 = g_kvNbDelegatePtr; + + /** + * @tc.steps:step2. Create databases without securityOption. + * @tc.expected: step2. Returns a non-null kvstore and get right SecurityOption. + */ + option.secOption.securityLabel = 0; + option.secOption.securityFlag = 0; + g_mgr.GetKvStore("SingleVerGetSecurityOption002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(g_kvNbDelegatePtr->GetSecurityOption(savedOption) == OK); + EXPECT_TRUE(savedOption.securityLabel == S3); + EXPECT_TRUE(savedOption.securityFlag == 1); + + EXPECT_EQ(g_mgr.CloseKvStore(kvNbDelegatePtr1), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore("SingleVerGetSecurityOption002") == OK); +} + +/** + * @tc.name: MaxLogSize001 + * @tc.desc: Test the pragma cmd of the max log size limit. + * @tc.type: FUNC + * @tc.require: + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, MaxLogSize001, TestSize.Level2) +{ + /** + * @tc.steps:step1. Create database. + * @tc.expected: step1. Returns a non-null kvstore. + */ + KvStoreNbDelegate::Option option; + g_mgr.GetKvStore("MaxLogSize001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Setting the max log limit for the valid value. + * @tc.expected: step2. Returns OK. + */ + uint64_t logSize = DBConstant::MAX_LOG_SIZE_HIGH; + PragmaData pragLimit = static_cast(&logSize); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(SET_MAX_LOG_LIMIT, pragLimit), OK); + + logSize = DBConstant::MAX_LOG_SIZE_LOW; + pragLimit = static_cast(&logSize); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(SET_MAX_LOG_LIMIT, pragLimit), OK); + + logSize = 10 * 1024 * 1024; // 10M + pragLimit = static_cast(&logSize); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(SET_MAX_LOG_LIMIT, pragLimit), OK); + + /** + * @tc.steps:step3. Setting the max log limit for the invalid value. + * @tc.expected: step3. Returns INLIVAD_ARGS. + */ + logSize = DBConstant::MAX_LOG_SIZE_HIGH + 1; + pragLimit = static_cast(&logSize); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(SET_MAX_LOG_LIMIT, pragLimit), INVALID_ARGS); + + logSize = DBConstant::MAX_LOG_SIZE_LOW - 1; + pragLimit = static_cast(&logSize); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(SET_MAX_LOG_LIMIT, pragLimit), INVALID_ARGS); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore("MaxLogSize001") == OK); +} + +/** + * @tc.name: ForceCheckpoint002 + * @tc.desc: Test the checkpoint of the database. + * @tc.type: FUNC + * @tc.require: + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, MaxLogSize002, TestSize.Level2) +{ + /** + * @tc.steps:step1. Create database. + * @tc.expected: step1. Returns a non-null kvstore. + */ + KvStoreNbDelegate::Option option; + g_mgr.GetKvStore("MaxLogSize002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Put the random entry into the database. + * @tc.expected: step2. Returns OK. + */ + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key, 30); // for 30B random key + DistributedDBToolsUnitTest::GetRandomKeyValue(value, 3 * 1024 * 1024); // 3M value + EXPECT_EQ(g_kvNbDelegatePtr->Put(key, value), OK); + DistributedDBToolsUnitTest::GetRandomKeyValue(key, 40); // for 40B random key + EXPECT_EQ(g_kvNbDelegatePtr->Put(key, value), OK); + DistributedDBToolsUnitTest::GetRandomKeyValue(key, 20); // for 20B random key + DistributedDBToolsUnitTest::GetRandomKeyValue(value, 1 * 1024 * 1024); // 1M value + EXPECT_EQ(g_kvNbDelegatePtr->Put(key, value), OK); + + /** + * @tc.steps:step3. Get the resultset. + * @tc.expected: step3. Returns OK. + */ + KvStoreResultSet *resultSet = nullptr; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(Key{}, resultSet), OK); + EXPECT_EQ(resultSet->GetCount(), 3); // size of all the entries is 3 + EXPECT_EQ(resultSet->MoveToFirst(), true); + + /** + * @tc.steps:step4. Put more data into the database. + * @tc.expected: step4. Returns OK. + */ + uint64_t logSize = 6 * 1024 * 1024; // 6M for initial test. + PragmaData pragLimit = static_cast(&logSize); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(SET_MAX_LOG_LIMIT, pragLimit), OK); + DistributedDBToolsUnitTest::GetRandomKeyValue(key, 10); // for 10B random key(different size) + DistributedDBToolsUnitTest::GetRandomKeyValue(value, 3 * 1024 * 1024); // 3MB + EXPECT_EQ(g_kvNbDelegatePtr->Put(key, value), OK); + DistributedDBToolsUnitTest::GetRandomKeyValue(key, 15); // for 15B random key(different size) + EXPECT_EQ(g_kvNbDelegatePtr->Put(key, value), OK); + + /** + * @tc.steps:step4. Put more data into the database while the log size is over the limit. + * @tc.expected: step4. Returns LOG_OVER_LIMITS. + */ + DistributedDBToolsUnitTest::GetRandomKeyValue(value, 25); // for 25B random key(different size) + EXPECT_EQ(g_kvNbDelegatePtr->Put(key, value), LOG_OVER_LIMITS); + EXPECT_EQ(g_kvNbDelegatePtr->Delete(key), LOG_OVER_LIMITS); + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), LOG_OVER_LIMITS); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(key, value), LOG_OVER_LIMITS); + EXPECT_EQ(g_kvNbDelegatePtr->RemoveDeviceData("deviceA"), LOG_OVER_LIMITS); + /** + * @tc.steps:step4. Change the max log size limit, and put the data. + * @tc.expected: step4. Returns OK. + */ + logSize *= 10; // 10 multiple size + pragLimit = static_cast(&logSize); + EXPECT_EQ(g_kvNbDelegatePtr->Pragma(SET_MAX_LOG_LIMIT, pragLimit), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(key, value), OK); + g_kvNbDelegatePtr->CloseResultSet(resultSet); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("MaxLogSize002"), OK); +} + +/** + * @tc.name: MaxLogCheckPoint001 + * @tc.desc: Pragma the checkpoint command. + * @tc.type: FUNC + * @tc.require: + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, MaxLogCheckPoint001, TestSize.Level2) +{ + /** + * @tc.steps:step1. Create database. + * @tc.expected: step1. Returns a non-null kvstore. + */ + KvStoreNbDelegate::Option option; + g_mgr.GetKvStore("MaxLogCheckPoint001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Put the random entry into the database. + * @tc.expected: step2. Returns OK. + */ + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key, 30); // for 30B random key(different size) + DistributedDBToolsUnitTest::GetRandomKeyValue(value, 1 * 1024 * 1024); // 1M + EXPECT_EQ(g_kvNbDelegatePtr->Put(key, value), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Delete(key), OK); + + /** + * @tc.steps:step3. Get the disk file size, execute the checkpoint and get the disk file size. + * @tc.expected: step3. Returns OK and the file size is less than the size before checkpoint. + */ + uint64_t sizeBeforeChk = 0; + g_mgr.GetKvStoreDiskSize("MaxLogCheckPoint001", sizeBeforeChk); + EXPECT_GT(sizeBeforeChk, 1 * 1024 * 1024ULL); // more than 1M + int param = 0; + PragmaData paraData = static_cast(¶m); + g_kvNbDelegatePtr->Pragma(EXEC_CHECKPOINT, paraData); + uint64_t sizeAfterChk = 0; + g_mgr.GetKvStoreDiskSize("MaxLogCheckPoint001", sizeAfterChk); + EXPECT_LT(sizeAfterChk, 100 * 1024ULL); // less than 100K + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("MaxLogCheckPoint001"), OK); +} + +/** + * @tc.name: CreateMemoryDbWithoutPath + * @tc.desc: Create memory database without path. + * @tc.type: FUNC + * @tc.require: AR000CRAKN + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, CreateMemoryDbWithoutPath, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create Memory database by GetKvStore without path. + * @tc.expected: step1. Create successfully. + */ + KvStoreDelegateManager mgr(APP_ID, USER_ID); + const KvStoreNbDelegate::Option option = {true, true}; + mgr.GetKvStore("memory_without_path", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); +} + +/** + * @tc.name: OpenStorePathCheckTest001 + * @tc.desc: Test open store with same label but different path. + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesNBDelegateTest, OpenStorePathCheckTest001, TestSize.Level1) +{ + std::string dir1 = g_testDir + "/dbDir1"; + EXPECT_EQ(OS::MakeDBDirectory(dir1), E_OK); + std::string dir2 = g_testDir + "/dbDir2"; + EXPECT_EQ(OS::MakeDBDirectory(dir2), E_OK); + + KvStoreDelegateManager mgr1(APP_ID, USER_ID); + mgr1.SetKvStoreConfig({dir1}); + + KvStoreNbDelegate *delegate1 = nullptr; + auto callback1 = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(delegate1)); + + KvStoreNbDelegate::Option option; + mgr1.GetKvStore(STORE_ID_1, option, callback1); + EXPECT_EQ(g_kvDelegateStatus, OK); + ASSERT_NE(delegate1, nullptr); + + KvStoreNbDelegate *delegate2 = nullptr; + auto callback2 = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(delegate2)); + KvStoreDelegateManager mgr2(APP_ID, USER_ID); + mgr2.SetKvStoreConfig({dir2}); + mgr2.GetKvStore(STORE_ID_1, option, callback2); + EXPECT_EQ(g_kvDelegateStatus, INVALID_ARGS); + ASSERT_EQ(delegate2, nullptr); + + mgr1.CloseKvStore(delegate1); + mgr1.DeleteKvStore(STORE_ID_1); + mgr2.CloseKvStore(delegate2); + mgr2.DeleteKvStore(STORE_ID_1); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_publish_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_publish_test.cpp new file mode 100644 index 00000000..fb6dd6a3 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_publish_test.cpp @@ -0,0 +1,792 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + const Key NULL_KEY; + const int OBSERVER_SLEEP_TIME = 100; + + // define some variables to init a KvStoreDelegateManager object. + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + string g_testDir; + KvStoreConfig g_config; + + // define the g_kvNbDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + + void KvStoreNbDelegateCallback(DBStatus statusSrc, KvStoreNbDelegate *kvStoreSrc, + DBStatus *statusDst, KvStoreNbDelegate **kvStoreDst) + { + *statusDst = statusSrc; + *kvStoreDst = kvStoreSrc; + } + + // the type of g_kvNbDelegateCallback is function + auto g_kvNbDelegateCallback = bind(&KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, &g_kvDelegateStatus, &g_kvNbDelegatePtr); + + // define parameters for conflict callback + bool g_isNeedInsertEntryInCallback = false; + int g_conflictCallbackTimes = 0; + Entry g_localEntry; + Entry g_syncEntry; + bool g_isLocalLastest = false; + bool g_isSyncNull = false; + void ResetCallbackArg(bool isLocalLastest = false) + { + g_conflictCallbackTimes = 0; + g_localEntry.key.clear(); + g_localEntry.value.clear(); + g_syncEntry.key.clear(); + g_syncEntry.value.clear(); + g_isLocalLastest = isLocalLastest; + g_isSyncNull = true; + g_isNeedInsertEntryInCallback = false; + } + + void ConflictCallback(const Entry &local, const Entry *sync, bool isLocalLastest) + { + g_conflictCallbackTimes++; + g_localEntry = local; + if (sync != nullptr) { + g_syncEntry = *sync; + g_isSyncNull = false; + } else { + g_isSyncNull = true; + } + g_isLocalLastest = isLocalLastest; + if (g_isNeedInsertEntryInCallback) { + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + } + } +} + +class DistributedDBInterfacesNBPublishTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesNBPublishTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesNBPublishTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesNBPublishTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_kvDelegateStatus = INVALID_ARGS; + g_kvNbDelegatePtr = nullptr; +} + +void DistributedDBInterfacesNBPublishTest::TearDown(void) {} + +/** + * @tc.name: SingleVerPublishKey001 + * @tc.desc: Publish nonexistent key + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBPublishTest, SingleVerPublishKey001, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_publish_SingleVerPublishKey001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_2), OK); + /** + * @tc.steps:step1. PublishLocal key2. + * @tc.expected: step1. return NOT_FOUND. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_2, true, true, nullptr), NOT_FOUND); + /** + * @tc.steps:step2. Get value of key1 and key2 both from local and sync table + * @tc.expected: step2. value of key1 and key2 are correct + */ + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), NOT_FOUND); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_2); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_2, readValue), NOT_FOUND); + /** + * @tc.steps:step3. PublishLocal key2. + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_2, true, false, nullptr), NOT_FOUND); + /** + * @tc.steps:step4. Get value of key1 and key2 both from local and sync table + * @tc.expected: step4. value of key1 and key2 are correct + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), NOT_FOUND); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_2); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_2, readValue), NOT_FOUND); + /** + * @tc.steps:step5. PublishLocal key2. + * @tc.expected: step5. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_2, false, true, nullptr), NOT_FOUND); + /** + * @tc.steps:step6. Get value of key1 and key2 both from local and sync table + * @tc.expected: step6. value of key1 and key2 are correct + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), NOT_FOUND); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_2); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_2, readValue), NOT_FOUND); + /** + * @tc.steps:step7. PublishLocal key2. + * @tc.expected: step7. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_2, false, false, nullptr), NOT_FOUND); + /** + * @tc.steps:step8. Get value of key1 and key2 both from local and sync table + * @tc.expected: step8. value of key1 and key2 are correct + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), NOT_FOUND); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_2); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_2, readValue), NOT_FOUND); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_publish_SingleVerPublishKey001"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPublishKey002 + * @tc.desc: Publish no conflict key without deleting key from local table + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBPublishTest, SingleVerPublishKey002, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_publish_SingleVerPublishKey002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_2, VALUE_2), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_3), OK); + + KvStoreObserverUnitTest *observerLocal = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observerLocal != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(NULL_KEY, OBSERVER_CHANGES_LOCAL_ONLY, observerLocal), OK); + + KvStoreObserverUnitTest *observerSync = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observerSync != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(NULL_KEY, OBSERVER_CHANGES_NATIVE, observerSync), OK); + /** + * @tc.steps:step1. PublishLocal key1. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_1, false, true, nullptr), OK); + /** + * @tc.steps:step2. Get value of key1 from local table + * @tc.expected: step2. value of key1 is value1 + */ + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + /** + * @tc.steps:step3. Get value of key2 from local table + * @tc.expected: step3. value of key2 is value2 + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_2); + /** + * @tc.steps:step4. Get value of key1 from sync table + * @tc.expected: step4. value of key1 is value1 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + /** + * @tc.steps:step5. Get value of key2 from sync table + * @tc.expected: step5. value of key2 is value3 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_3); + /** + * @tc.steps:step6. Check observer data. + * @tc.expected: step6. return OK. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + vector entriesRet = {{KEY_1, VALUE_1}}; + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entriesRet, observerSync->GetEntriesInserted())); + EXPECT_EQ(observerLocal->GetCallCount(), 0UL); + + // finilize + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observerLocal), OK); + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observerSync), OK); + delete observerLocal; + observerLocal = nullptr; + delete observerSync; + observerSync = nullptr; + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_publish_SingleVerPublishKey002"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPublishKey003 + * @tc.desc: Publish no conflict key with deleting key from local table + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBPublishTest, SingleVerPublishKey003, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_publish_SingleVerPublishKey003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_2, VALUE_2), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_3), OK); + + KvStoreObserverUnitTest *observerLocal = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observerLocal != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(NULL_KEY, OBSERVER_CHANGES_LOCAL_ONLY, observerLocal), OK); + + KvStoreObserverUnitTest *observerSync = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observerSync != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(NULL_KEY, OBSERVER_CHANGES_NATIVE, observerSync), OK); + /** + * @tc.steps:step1. PublishLocal key1. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_1, true, true, nullptr), OK); + /** + * @tc.steps:step2. Get value of key1 from local table + * @tc.expected: step2. value of key1 is value1 + */ + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), NOT_FOUND); + /** + * @tc.steps:step3. Get value of key2 from local table + * @tc.expected: step3. value of key2 is value2 + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_2); + /** + * @tc.steps:step4. Get value of key1 from sync table + * @tc.expected: step4. value of key1 is value1 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + /** + * @tc.steps:step5. Get value of key2 from sync table + * @tc.expected: step5. value of key2 is value3 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_3); + /** + * @tc.steps:step6. Check observer data. + * @tc.expected: step6. return OK. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + vector entriesRet = {{KEY_1, VALUE_1}}; + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entriesRet, observerSync->GetEntriesInserted())); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entriesRet, observerLocal->GetEntriesDeleted())); + + // finilize + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observerLocal), OK); + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observerSync), OK); + delete observerLocal; + observerLocal = nullptr; + delete observerSync; + observerSync = nullptr; + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_publish_SingleVerPublishKey003"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPublishKey004 + * @tc.desc: Publish conflict key and update timestamp + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBPublishTest, SingleVerPublishKey004, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_publish_SingleVerPublishKey004", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_2, VALUE_2), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_3), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_4), OK); + + KvStoreObserverUnitTest *observerLocal = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observerLocal != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(NULL_KEY, OBSERVER_CHANGES_LOCAL_ONLY, observerLocal), OK); + + KvStoreObserverUnitTest *observerSync = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observerSync != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(NULL_KEY, OBSERVER_CHANGES_NATIVE, observerSync), OK); + /** + * @tc.steps:step1. PublishLocal key1. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_1, false, true, nullptr), OK); + /** + * @tc.steps:step2. Get value of key1 from local table + * @tc.expected: step2. value of key1 is value1 + */ + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + /** + * @tc.steps:step3. Get value of key2 from local table + * @tc.expected: step3. value of key2 is value2 + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_2); + /** + * @tc.steps:step4. Get value of key1 from sync table + * @tc.expected: step4. value of key1 is value1 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + /** + * @tc.steps:step5. Get value of key2 from sync table + * @tc.expected: step5. value of key2 is value3 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_3); + /** + * @tc.steps:step6. Check observer data. + * @tc.expected: step6. return OK. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + vector entriesRet = {{KEY_1, VALUE_1}}; + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entriesRet, observerSync->GetEntriesUpdated())); + EXPECT_EQ(observerLocal->GetCallCount(), 0UL); + + // finilize + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observerLocal), OK); + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observerSync), OK); + delete observerLocal; + observerLocal = nullptr; + delete observerSync; + observerSync = nullptr; + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_publish_SingleVerPublishKey004"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPublishKey005 + * @tc.desc: Publish conflict key but do not update timestamp + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBPublishTest, SingleVerPublishKey005, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_publish_SingleVerPublishKey005", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_2, VALUE_2), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_3), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_4), OK); + + KvStoreObserverUnitTest *observerLocal = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observerLocal != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(NULL_KEY, OBSERVER_CHANGES_LOCAL_ONLY, observerLocal), OK); + + KvStoreObserverUnitTest *observerSync = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observerSync != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(NULL_KEY, OBSERVER_CHANGES_NATIVE, observerSync), OK); + /** + * @tc.steps:step1. PublishLocal key1. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_1, true, false, nullptr), STALE); + /** + * @tc.steps:step2. Get value of key1 from local table + * @tc.expected: step2. value of key1 is value1 + */ + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + /** + * @tc.steps:step3. Get value of key2 from local table + * @tc.expected: step3. value of key2 is value2 + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_2); + /** + * @tc.steps:step4. Get value of key1 from sync table + * @tc.expected: step4. value of key1 is value4 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_4); + /** + * @tc.steps:step5. Get value of key2 from sync table + * @tc.expected: step5. value of key2 is value3 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_2, readValue), OK); + EXPECT_EQ(readValue, VALUE_3); + /** + * @tc.steps:step6. Check observer data. + * @tc.expected: step6. return OK. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(observerSync->GetCallCount(), 0UL); + EXPECT_EQ(observerLocal->GetCallCount(), 0UL); + + // finilize + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observerLocal), OK); + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observerSync), OK); + delete observerLocal; + observerLocal = nullptr; + delete observerSync; + observerSync = nullptr; + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_publish_SingleVerPublishKey005"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPublishKey006 + * @tc.desc: Publish no conflict key and onConflict() not null + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBPublishTest, SingleVerPublishKey006, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_publish_SingleVerPublishKey006", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_1, VALUE_1), OK); + /** + * @tc.steps:step1. PublishLocal key1. + * @tc.expected: step1. return OK. + */ + ResetCallbackArg(); + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_1, true, false, ConflictCallback), OK); + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), NOT_FOUND); + /** + * @tc.steps:step2. Check onConflict callback. + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_conflictCallbackTimes, 0); + /** + * @tc.steps:step3. Get value of key1 from sync table + * @tc.expected: step3. value of key1 is value4 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_publish_SingleVerPublishKey006"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPublishKey007 + * @tc.desc: Publish conflict key and onConflict() not null + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBPublishTest, SingleVerPublishKey007, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_publish_SingleVerPublishKey007", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_2), OK); + /** + * @tc.steps:step1. PublishLocal key1. + * @tc.expected: step1. return OK. + */ + ResetCallbackArg(true); + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_1, true, false, ConflictCallback), OK); + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + /** + * @tc.steps:step2. Check onConflict callback. + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_conflictCallbackTimes, 1); + EXPECT_EQ(g_isLocalLastest, false); + Entry entryRet = {KEY_1, VALUE_1}; + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntryEqual(g_localEntry, entryRet)); + entryRet = {KEY_1, VALUE_2}; + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntryEqual(g_syncEntry, entryRet)); + /** + * @tc.steps:step3. Get value of key1 from sync table + * @tc.expected: step3. value of key1 is value4 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_2); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_publish_SingleVerPublishKey007"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPublishKey008 + * @tc.desc: Publish conflict key and onConflict() not null, need update timestamp + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBPublishTest, SingleVerPublishKey008, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_publish_SingleVerPublishKey008", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_2), OK); + /** + * @tc.steps:step1. PublishLocal key1. + * @tc.expected: step1. return OK. + */ + ResetCallbackArg(false); + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_1, true, true, ConflictCallback), OK); + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + /** + * @tc.steps:step2. Check onConflict callback. + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_conflictCallbackTimes, 1); + EXPECT_EQ(g_isLocalLastest, true); + Entry entryRet = {KEY_1, VALUE_1}; + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntryEqual(g_localEntry, entryRet)); + entryRet = {KEY_1, VALUE_2}; + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntryEqual(g_syncEntry, entryRet)); + /** + * @tc.steps:step3. Get value of key1 from sync table + * @tc.expected: step3. value of key1 is value4 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_2); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_publish_SingleVerPublishKey008"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPublishKey009 + * @tc.desc: Publish conflict key (deleted) and onConflict() not null + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBPublishTest, SingleVerPublishKey009, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_publish_SingleVerPublishKey009", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_2), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Delete(KEY_1), OK); + /** + * @tc.steps:step1. PublishLocal key1. + * @tc.expected: step1. return OK. + */ + ResetCallbackArg(true); + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_1, true, false, ConflictCallback), OK); + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + /** + * @tc.steps:step2. Check onConflict callback. + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_conflictCallbackTimes, 1); + EXPECT_EQ(g_isLocalLastest, false); + EXPECT_EQ(g_isSyncNull, true); + Entry entryRet = {KEY_1, VALUE_1}; + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntryEqual(g_localEntry, entryRet)); + /** + * @tc.steps:step3. Get value of key1 from sync table + * @tc.expected: step3. value of key1 is value4 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), NOT_FOUND); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_publish_SingleVerPublishKey009"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPublishKey010 + * @tc.desc: Publish conflict key (deleted) and onConflict() not null, need update timestamp + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBPublishTest, SingleVerPublishKey010, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_publish_SingleVerPublishKey010", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_2), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Delete(KEY_1), OK); + /** + * @tc.steps:step1. PublishLocal key1. + * @tc.expected: step1. return OK. + */ + ResetCallbackArg(false); + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_1, true, true, ConflictCallback), OK); + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + /** + * @tc.steps:step2. Check onConflict callback. + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_conflictCallbackTimes, 1); + EXPECT_EQ(g_isLocalLastest, true); + EXPECT_EQ(g_isSyncNull, true); + Entry entryRet = {KEY_1, VALUE_1}; + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntryEqual(g_localEntry, entryRet)); + /** + * @tc.steps:step3. Get value of key1 from sync table + * @tc.expected: step3. value of key1 is value4 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), NOT_FOUND); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_publish_SingleVerPublishKey010"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SingleVerPublishKey011 + * @tc.desc: Publish conflict key (deleted) and onConflict() not null, put(k1,v1) in callback method + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBPublishTest, SingleVerPublishKey011, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_publish_SingleVerPublishKey011", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_2), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Delete(KEY_1), OK); + /** + * @tc.steps:step1. PublishLocal key1. + * @tc.expected: step1. return OK. + */ + ResetCallbackArg(true); + g_isNeedInsertEntryInCallback = true; + EXPECT_EQ(g_kvNbDelegatePtr->PublishLocal(KEY_1, true, false, ConflictCallback), OK); + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + /** + * @tc.steps:step2. Check onConflict callback. + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_conflictCallbackTimes, 1); + EXPECT_EQ(g_isLocalLastest, false); + EXPECT_EQ(g_isSyncNull, true); + Entry entryRet = {KEY_1, VALUE_1}; + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntryEqual(g_localEntry, entryRet)); + /** + * @tc.steps:step3. Get value of key1 from sync table + * @tc.expected: step3. value of key1 is value4 + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), OK); + EXPECT_EQ(readValue, VALUE_1); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_publish_SingleVerPublishKey011"), OK); + g_kvNbDelegatePtr = nullptr; +} diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_transaction_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_transaction_test.cpp new file mode 100644 index 00000000..71ae8846 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_transaction_test.cpp @@ -0,0 +1,1104 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + const int BATCH_BASE_SIZE = 60; + const Key NULL_KEY; + const Key NULL_VALUE; + const int CONFLICT_ALL = 15; + const auto OLD_VALUE_TYPE = KvStoreNbConflictData::ValueType::OLD_VALUE; + const auto NEW_VALUE_TYPE = KvStoreNbConflictData::ValueType::NEW_VALUE; + + const int OBSERVER_SLEEP_TIME = 30; + + // define some variables to init a KvStoreDelegateManager object. + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + string g_testDir; + KvStoreConfig g_config; + + // define the g_kvNbDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + + struct SingleVerConflictData { + KvStoreNbConflictType type = CONFLICT_NATIVE_ALL; + Key key; + Value oldValue; + Value newValue; + bool oldIsDeleted = false; + bool newIsDeleted = false; + bool oldIsNative = false; + bool newIsNative = false; + int getoldValueErrCode = 0; + int getNewValueErrCode = 0; + bool operator==(const SingleVerConflictData &comparedData) const + { + if (this->type == comparedData.type && + this->key == comparedData.key && + this->oldValue == comparedData.oldValue && + this->newValue == comparedData.newValue && + this->oldIsDeleted == comparedData.oldIsDeleted && + this->newIsDeleted == comparedData.newIsDeleted && + this->oldIsNative == comparedData.oldIsNative && + this->newIsNative == comparedData.newIsNative && + this->getoldValueErrCode == comparedData.getoldValueErrCode && + this->getNewValueErrCode == comparedData.getNewValueErrCode) { + return true; + } + LOGD("type = %d, ctype = %d", this->type, comparedData.type); + DBCommon::PrintHexVector(this->key, __LINE__, "key"); + DBCommon::PrintHexVector(comparedData.key, __LINE__, "ckey"); + DBCommon::PrintHexVector(this->oldValue, __LINE__, "oldValue"); + DBCommon::PrintHexVector(comparedData.oldValue, __LINE__, "coldValue"); + DBCommon::PrintHexVector(this->newValue, __LINE__, "newValue"); + DBCommon::PrintHexVector(comparedData.newValue, __LINE__, "cnewValue"); + + LOGD("oldIsDeleted = %d, coldIsDeleted = %d", this->oldIsDeleted, comparedData.oldIsDeleted); + LOGD("newIsDeleted = %d, cnewIsDeleted = %d", this->newIsDeleted, comparedData.newIsDeleted); + LOGD("oldIsNative = %d, coldIsNative = %d", this->oldIsNative, comparedData.oldIsNative); + LOGD("newIsNative = %d, cnewIsNative = %d", this->newIsNative, comparedData.newIsNative); + LOGD("getoldValueErrCode = %d, cgetoldValueErrCode = %d", this->getoldValueErrCode, + comparedData.getoldValueErrCode); + LOGD("getNewValueErrCode = %d, cgetNewValueErrCode = %d", this->getNewValueErrCode, + comparedData.getNewValueErrCode); + + return false; + } + }; + std::vector g_conflictData; + + void KvStoreNbDelegateCallback(DBStatus statusSrc, KvStoreNbDelegate *kvStoreSrc, + DBStatus *statusDst, KvStoreNbDelegate **kvStoreDst) + { + *statusDst = statusSrc; + *kvStoreDst = kvStoreSrc; + } + + // the type of g_kvNbDelegateCallback is function + auto g_kvNbDelegateCallback = bind(&KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, &g_kvDelegateStatus, &g_kvNbDelegatePtr); + + void NotifierCallback(const KvStoreNbConflictData &data) + { + Key key; + Value oldValue; + Value newValue; + data.GetKey(key); + data.GetValue(OLD_VALUE_TYPE, oldValue); + LOGD("Get new value status:%d", data.GetValue(NEW_VALUE_TYPE, newValue)); + LOGD("Type:%d", data.GetType()); + DBCommon::PrintHexVector(oldValue, __LINE__); + DBCommon::PrintHexVector(newValue, __LINE__); + LOGD("Type:IsDeleted %d vs %d, IsNative %d vs %d", data.IsDeleted(OLD_VALUE_TYPE), + data.IsDeleted(NEW_VALUE_TYPE), data.IsNative(OLD_VALUE_TYPE), data.IsNative(NEW_VALUE_TYPE)); + g_conflictData.push_back({data.GetType(), key, oldValue, newValue, data.IsDeleted(OLD_VALUE_TYPE), + data.IsDeleted(NEW_VALUE_TYPE), data.IsNative(OLD_VALUE_TYPE), data.IsNative(NEW_VALUE_TYPE), + data.GetValue(OLD_VALUE_TYPE, oldValue), data.GetValue(NEW_VALUE_TYPE, newValue)}); + } +} + +class DistributedDBInterfacesNBTransactionTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesNBTransactionTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + g_mgr.SetProcessLabel("DistributedDBInterfacesNBTransactionTest", "test"); +} + +void DistributedDBInterfacesNBTransactionTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesNBTransactionTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_kvDelegateStatus = INVALID_ARGS; + g_kvNbDelegatePtr = nullptr; +} + +void DistributedDBInterfacesNBTransactionTest::TearDown(void) {} + +/** + * @tc.name: start001 + * @tc.desc: Test the nb transaction start twice. + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, start001, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_start001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + + /** + * @tc.steps:step2. Start transaction again. + * @tc.expected: step2. return DB_ERROR. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), DB_ERROR); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_start001"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: start002 + * @tc.desc: Test the nb transaction begin and end not match. + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, start002, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_start002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. Rollback transaction. + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), OK); + /** + * @tc.steps:step3. Commit transaction. + * @tc.expected: step3. return DB_ERROR. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), DB_ERROR); + /** + * @tc.steps:step4. Start transaction. + * @tc.expected: step4. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step5. Commit transaction. + * @tc.expected: step5. return DB_ERROR. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + /** + * @tc.steps:step6. Rollback transaction. + * @tc.expected: step6. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), DB_ERROR); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_start002"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: start003 + * @tc.desc: Test the nb transaction rollback automatically when db close. + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, start003, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_start003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + + /** + * @tc.steps:step2. Put (key1,value2) + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_2), OK); + /** + * @tc.steps:step3. Close db + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + /** + * @tc.steps:step4. Open db again + * @tc.expected: step4. return OK. + */ + g_mgr.GetKvStore("distributed_nb_transaction_start003", option, g_kvNbDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + /** + * @tc.steps:step5. Get key1 + * @tc.expected: step5. return OK, value of key1 is value1. + */ + Value value; + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, value), OK); + EXPECT_EQ(value, VALUE_1); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_start003"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: start004 + * @tc.desc: Test the nb operations return BUSY after transaction started. + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, start004, TestSize.Level4) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_start004", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. Local data and sync data can be simultaneously operated in transactions. + * @tc.expected: step2. From September 2020 return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY_3, VALUE_3), OK); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocal(KEY_1), OK); + + CipherPassword password; + EXPECT_EQ(g_kvNbDelegatePtr->Rekey(password), BUSY); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(KEY_3, OBSERVER_CHANGES_NATIVE, observer), BUSY); + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), NOT_FOUND); + delete observer; + observer = nullptr; + + std::string filePath = g_testDir + "test.txt"; + EXPECT_EQ(g_kvNbDelegatePtr->Export(filePath, password), BUSY); + + KvStoreResultSet *readResultSet = nullptr; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(KEY_4, readResultSet), BUSY); + EXPECT_EQ(g_kvNbDelegatePtr->CloseResultSet(readResultSet), INVALID_ARGS); + /** + * @tc.steps:step1. Commit transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_start004"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: commit001 + * @tc.desc: Test the nb transaction commit without start. + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, commit001, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_commit001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps:step1. Commit transaction. + * @tc.expected: step1. return DB_ERROR. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), DB_ERROR); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_commit001"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: commit002 + * @tc.desc: Test the nb transaction commit twice. + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, commit002, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_commit002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. Get key1 + * @tc.expected: step2. return OK, value of key1 is value1. + */ + Value value1; + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, value1), OK); + EXPECT_EQ(value1, VALUE_1); + /** + * @tc.steps:step3. Put (key2,value2) + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_2), OK); + /** + * @tc.steps:step4. Get key2 + * @tc.expected: step4. return OK, value of key2 is value2. + */ + Value value2; + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_2, value2), OK); + EXPECT_EQ(value2, VALUE_2); + /** + * @tc.steps:step5. Commit transaction. + * @tc.expected: step5. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + /** + * @tc.steps:step6. Commit transaction again. + * @tc.expected: step6. return DB_ERROR. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), DB_ERROR); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_commit002"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: commit003 + * @tc.desc: Test the entry size exceed the maximum limit in one transaction + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, commit003, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_commit003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. Put (key1,value1) + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + /** + * @tc.steps:step3. Delete key1 + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Delete(KEY_1), OK); + /** + * @tc.steps:step4. PutBatch 64 records (from key2 to key65) + * @tc.expected: step4. return OK. + */ + vector entrysBase; + vector keysBase; + DistributedDBUnitTest::GenerateRecords(BATCH_BASE_SIZE + 5, entrysBase, keysBase); + + vector entrys1(entrysBase.begin() + 1, entrysBase.end()); + EXPECT_EQ(entrys1.size(), 64UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrys1), OK); + /** + * @tc.steps:step5. DeleteBatch 63 records (from key2 to key64) + * @tc.expected: step5. return OK. + */ + vector keys1(keysBase.begin() + 1, keysBase.end() - 1); + EXPECT_EQ(keys1.size(), 63UL); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keys1), OVER_MAX_LIMITS); + /** + * @tc.steps:step6. DeleteBatch 60 records (from key1 to key60) + * @tc.expected: step6. return OK. + */ + vector keys2(keysBase.begin(), keysBase.begin() + 60); + EXPECT_EQ(keys2.size(), 60UL); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keys2), OK); + /** + * @tc.steps:step6. Commit. + * @tc.expected: step6. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + /** + * @tc.steps:step7. GetEntries. + * @tc.expected: step7. return OK. + */ + vector entriesExpect(entrysBase.begin() + 60, entrysBase.end()); + EXPECT_EQ(entriesExpect.size(), 5UL); + const Key prefix; + vector entriesRet; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(prefix, entriesRet), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntriesEqual(entriesExpect, entriesRet)); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_commit003"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: commit004 + * @tc.desc: Test the nb normal operations in one transaction + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, commit004, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_commit004", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(NULL_KEY, OBSERVER_CHANGES_NATIVE, observer), OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. Put (key1,value1) and (key2,value2) + * @tc.expected: step2. put OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_2), OK); + /** + * @tc.steps:step3. Delete key2 + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Delete(KEY_2), OK); + /** + * @tc.steps:step4. PutBatch 65 records (from key3 to key67) + * @tc.expected: step4. return OK. + */ + vector entrysBase; + vector keysBase; + DistributedDBUnitTest::GenerateRecords(BATCH_BASE_SIZE + 7, entrysBase, keysBase); + + vector entrys1(entrysBase.begin() + 2, entrysBase.end()); + EXPECT_EQ(entrys1.size(), 65UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrys1), OK); + /** + * @tc.steps:step5. DeleteBatch 60 records (from key8 to key67) + * @tc.expected: step5. return OK. + */ + vector keys(keysBase.begin() + 7, keysBase.end()); + EXPECT_EQ(keys.size(), 60UL); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keys), OK); + /** + * @tc.steps:step6. Commit. + * @tc.expected: step6. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + /** + * @tc.steps:step7. Check observer data. + * @tc.expected: step6. return OK. + */ + vector entriesRet; + Entry entry1 = {KEY_1, VALUE_1}; + entriesRet.push_back(entry1); + entriesRet.insert(entriesRet.end(), entrysBase.begin() + 2, entrysBase.begin() + 7); + EXPECT_EQ(entriesRet.size(), 6UL); + + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entriesRet, observer->GetEntriesInserted())); + /** + * @tc.steps:step8. GetEntries. + * @tc.expected: step8. return OK. + */ + const Key prefix; + vector entries; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(prefix, entries), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntriesEqual(entriesRet, entries, true)); + + // finilize + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_commit004"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: commit005 + * @tc.desc: Test the conflict data report normally in one transaction + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, commit005, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_commit005", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(g_kvNbDelegatePtr->SetConflictNotifier(CONFLICT_ALL, NotifierCallback) == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_2), OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. Put (key1,value3) and (key2,value4) + * @tc.expected: step2. put OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_3), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_4), OK); + /** + * @tc.steps:step3. Delete key2 + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Delete(KEY_2), OK); + /** + * @tc.steps:step4. put (key3 ,value5) (key3 ,value6) + * @tc.expected: step4. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_3, VALUE_5), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_3, VALUE_6), OK); + /** + * @tc.steps:step5. Commit. + * @tc.expected: step5. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + /** + * @tc.steps:step6. Check conflict report data. + * @tc.expected: step6. return OK. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_conflictData.size(), 2UL); + if (g_conflictData.size() == 2) { + SingleVerConflictData expectNotifyData1 = {KvStoreNbConflictType::CONFLICT_NATIVE_ALL, + KEY_1, VALUE_1, VALUE_3, false, false, true, true, OK, OK}; + EXPECT_EQ(g_conflictData[0], expectNotifyData1); + + SingleVerConflictData expectNotifyData2 = {KvStoreNbConflictType::CONFLICT_NATIVE_ALL, + KEY_2, VALUE_2, NULL_VALUE, false, true, true, true, OK, DB_ERROR}; + EXPECT_EQ(g_conflictData[1], expectNotifyData2); + } + + // finilize + g_conflictData.clear(); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_commit005"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: commit006 + * @tc.desc: Test the conflict data report and observer function both be normal in one transaction + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, commit006, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_commit006", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Delete(KEY_1), OK); + + EXPECT_TRUE(g_kvNbDelegatePtr->SetConflictNotifier(CONFLICT_ALL, NotifierCallback) == OK); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(observer != nullptr); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(NULL_KEY, OBSERVER_CHANGES_NATIVE, observer), OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. Put (key1,value2) + * @tc.expected: step2. put OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_2), OK); + /** + * @tc.steps:step3. Commit. + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + /** + * @tc.steps:step4. Get value of key1. + * @tc.expected: step4. return OK, value of key1 is value2. + */ + Value readValue; + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY_1, readValue), OK); + EXPECT_TRUE(readValue == VALUE_2); + /** + * @tc.steps:step5. Check observer data. + * @tc.expected: step5. return OK. + */ + vector entriesRet = {{KEY_1, VALUE_2}}; + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entriesRet, observer->GetEntriesInserted())); + /** + * @tc.steps:step6. Check conflict report data. + * @tc.expected: step6. return OK. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_conflictData.size(), 1UL); + if (g_conflictData.size() == 1) { + SingleVerConflictData expectNotifyData = {KvStoreNbConflictType::CONFLICT_NATIVE_ALL, + KEY_1, {}, VALUE_2, true, false, true, true, DB_ERROR, OK}; + EXPECT_EQ(g_conflictData[0], expectNotifyData); + } + + // finilize + g_conflictData.clear(); + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(observer), OK); + delete observer; + observer = nullptr; + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_commit006"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: rollback001 + * @tc.desc: Test the transaction rollback without start. + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, rollback001, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_rollback001", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps:step1. Transaction rollback without start. + * @tc.expected: step1. return DB_ERROR. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), DB_ERROR); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_rollback001"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: rollback002 + * @tc.desc: Test the transaction rollback twice + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, rollback002, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_rollback002", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step1. Rollback. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), OK); + /** + * @tc.steps:step1. Transaction rollback without start. + * @tc.expected: step1. return DB_ERROR. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), DB_ERROR); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_rollback002"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: rollback003 + * @tc.desc: Test the Put operation rollback + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, rollback003, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_rollback003", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. Put (key2,value2) + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_2), OK); + /** + * @tc.steps:step3. Rollback. + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), OK); + /** + * @tc.steps:step4. GetEntries. + * @tc.expected: step4. return OK. + */ + const Key prefix; + vector entries; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(prefix, entries), OK); + EXPECT_EQ(entries.size(), 1UL); + if (entries.size() > 0) { + EXPECT_EQ(entries[0].key, KEY_1); + EXPECT_EQ(entries[0].value, VALUE_1); + } + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_rollback003"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: rollback004 + * @tc.desc: Test the PutBatch operation rollback + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, rollback004, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_rollback004", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. PutBatch 10 records + * @tc.expected: step2. return OK. + */ + vector entrysBase; + vector keysBase; + DistributedDBUnitTest::GenerateRecords(10, entrysBase, keysBase); + + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrysBase), OK); + /** + * @tc.steps:step3. Rollback. + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), OK); + /** + * @tc.steps:step4. GetEntries. + * @tc.expected: step4. return OK. + */ + const Key prefix; + vector entries; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(prefix, entries), OK); + EXPECT_EQ(entries.size(), 1UL); + if (entries.size() > 0) { + EXPECT_EQ(entries[0].key, KEY_1); + EXPECT_EQ(entries[0].value, VALUE_1); + } + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_rollback004"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: rollback005 + * @tc.desc: Test the modify operation rollback + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, rollback005, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_rollback005", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. Put (key1,value2) + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_2), OK); + /** + * @tc.steps:step3. Rollback. + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), OK); + /** + * @tc.steps:step4. GetEntries. + * @tc.expected: step4. return OK. + */ + const Key prefix; + vector entries; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(prefix, entries), OK); + EXPECT_EQ(entries.size(), 1UL); + if (entries.size() > 0) { + EXPECT_EQ(entries[0].key, KEY_1); + EXPECT_EQ(entries[0].value, VALUE_1); + } + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_rollback005"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: rollback006 + * @tc.desc: Test the Delete operation rollback + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, rollback006, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_rollback006", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. Delete key1 + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Delete(KEY_1), OK); + /** + * @tc.steps:step3. Rollback. + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), OK); + /** + * @tc.steps:step4. GetEntries. + * @tc.expected: step4. return OK. + */ + const Key prefix; + vector entries; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(prefix, entries), OK); + EXPECT_EQ(entries.size(), 1UL); + if (entries.size() > 0) { + EXPECT_EQ(entries[0].key, KEY_1); + EXPECT_EQ(entries[0].value, VALUE_1); + } + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_rollback006"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: rollback007 + * @tc.desc: Test the DeleteBatch operation rollback + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, rollback007, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_rollback007", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + vector entries; + vector keys; + DistributedDBUnitTest::GenerateRecords(10, entries, keys); + + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entries), OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. DeleteBatch from key1 to key10 + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keys), OK); + /** + * @tc.steps:step3. Rollback. + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), OK); + /** + * @tc.steps:step4. GetEntries. + * @tc.expected: step4. return OK. + */ + const Key prefix; + vector entriesRet; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(prefix, entriesRet), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntriesEqual(entries, entriesRet)); + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_rollback007"), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: rollback008 + * @tc.desc: Test the multiple operations rollback + * @tc.type: FUNC + * @tc.require: AR000DPTQ9 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBInterfacesNBTransactionTest, rollback008, TestSize.Level1) +{ + const KvStoreNbDelegate::Option option = {true, false}; + g_mgr.GetKvStore("distributed_nb_transaction_rollback008", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_2, VALUE_2), OK); + /** + * @tc.steps:step1. Start transaction. + * @tc.expected: step1. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + /** + * @tc.steps:step2. Put (key3,value3) (key1,value4) + * @tc.expected: step2. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_3, VALUE_3), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_4), OK); + /** + * @tc.steps:step3. Delete key2 + * @tc.expected: step3. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Delete(KEY_2), OK); + /** + * @tc.steps:step4. PutBatch 10 records (from key3 to key12) + * @tc.expected: step4. return OK. + */ + vector entrysBase; + vector keysBase; + DistributedDBUnitTest::GenerateRecords(12, entrysBase, keysBase); + + vector entrys1(entrysBase.begin() + 2, entrysBase.end()); + EXPECT_EQ(entrys1.size(), 10UL); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entrys1), OK); + /** + * @tc.steps:step5. DeleteBatch 5 records (from key3 to key7) + * @tc.expected: step5. return OK. + */ + vector keys(keysBase.begin() + 2, keysBase.begin() + 7); + EXPECT_EQ(keys.size(), 5UL); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keys), OK); + /** + * @tc.steps:step6. Commit. + * @tc.expected: step6. return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), OK); + /** + * @tc.steps:step7. GetEntries. + * @tc.expected: step7. return OK. + */ + const Key prefix; + vector entries; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(prefix, entries), OK); + EXPECT_EQ(entries.size(), 2UL); + if (entries.size() > 1) { + EXPECT_EQ(entries[0].key, KEY_1); + EXPECT_EQ(entries[0].value, VALUE_1); + EXPECT_EQ(entries[1].key, KEY_2); + EXPECT_EQ(entries[1].value, VALUE_2); + } + + // finilize + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("distributed_nb_transaction_rollback008"), OK); + g_kvNbDelegatePtr = nullptr; +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_unpublish_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_unpublish_test.cpp new file mode 100644 index 00000000..5958850d --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_nb_unpublish_test.cpp @@ -0,0 +1,481 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_tools_unit_test.h" +#include "log_print.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + // define some variables to init a KvStoreDelegateManager object. + KvStoreDelegateManager g_mgr("app0", "user0"); + string g_testDir; + KvStoreConfig g_config; + KvStoreObserverUnitTest *g_syncObserver = nullptr; + KvStoreObserverUnitTest *g_localObserver = nullptr; + Entry g_entry1; + Entry g_entry2; + + // define the g_kvNbDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + + const int OBSERVER_SLEEP_TIME = 100; + + void KvStoreNbDelegateCallback( + DBStatus statusSrc, KvStoreNbDelegate *kvStoreSrc, DBStatus &statusDst, KvStoreNbDelegate *&kvStoreDst) + { + statusDst = statusSrc; + kvStoreDst = kvStoreSrc; + } + + // the type of g_kvNbDelegateCallback is function + auto g_kvNbDelegateCallback = bind(&KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); + + static void CheckUnpublishNotFound() + { + Value valueRead; + EXPECT_EQ(g_kvNbDelegatePtr->Get(g_entry1.key, valueRead), NOT_FOUND); + EXPECT_EQ(g_kvNbDelegatePtr->Get(g_entry2.key, valueRead), OK); + EXPECT_EQ(g_entry2.value, valueRead); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry1.key, valueRead), OK); + EXPECT_EQ(g_entry1.value, valueRead); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry2.key, valueRead), NOT_FOUND); + } + + static void RegisterObservers() + { + g_localObserver = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(g_localObserver != nullptr); + g_syncObserver = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(g_syncObserver != nullptr); + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, 3, g_syncObserver), OK); // sync data observer. + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, 4, g_localObserver), OK); // local data observer. + } +} + +class DistributedDBInterfacesNBUnpublishTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesNBUnpublishTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesNBUnpublishTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesNBUnpublishTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("unpublish_test", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entry1.key); + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entry1.value); + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entry2.key); + DistributedDBToolsUnitTest::GetRandomKeyValue(g_entry2.value); +} + +void DistributedDBInterfacesNBUnpublishTest::TearDown(void) +{ + if (g_localObserver != nullptr) { + if (g_kvNbDelegatePtr != nullptr) { + g_kvNbDelegatePtr->UnRegisterObserver(g_localObserver); + } + delete g_localObserver; + g_localObserver = nullptr; + } + + if (g_syncObserver != nullptr) { + if (g_kvNbDelegatePtr != nullptr) { + g_kvNbDelegatePtr->UnRegisterObserver(g_syncObserver); + } + delete g_syncObserver; + g_syncObserver = nullptr; + } + if (g_kvNbDelegatePtr != nullptr) { + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + g_kvNbDelegatePtr = nullptr; + } + g_mgr.DeleteKvStore("unpublish_test"); +} + +/** + * @tc.name: CombineTest001 + * @tc.desc: Test unpublish one nonexistent data. + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBUnpublishTest, SingleVerUnPublishKey001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Put [k1, v1] into the local zone, [k2, v2] into the sync zone. + * @tc.expected: step1. Get results OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(g_entry1.key, g_entry1.value), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(g_entry2.key, g_entry2.value), OK); + + /** + * @tc.steps:step2. Unpublish the k1 with para of deletePublic: true, updateTimestamp: true. + * @tc.expected: step2. Unpublish returns NOT_FOUND. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry1.key, true, true), NOT_FOUND); + + /** + * @tc.steps:step3. Check the data in the local and sync zone. + * @tc.expected: step3. Both the data are not changed. + */ + CheckUnpublishNotFound(); + + /** + * @tc.steps:step4. Unpublish the k1 with para of deletePublic: true, updateTimestamp: false. + * @tc.expected: step4. Unpublish returns NOT_FOUND. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry1.key, true, false), NOT_FOUND); + + /** + * @tc.steps:step5. Check the data in the local and sync zone. + * @tc.expected: step5. Both the data are not changed. + */ + CheckUnpublishNotFound(); + + /** + * @tc.steps:step6. Unpublish the k1 with para of deletePublic: false, updateTimestamp: true. + * @tc.expected: step6. Unpublish returns NOT_FOUND. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry1.key, false, true), NOT_FOUND); + + /** + * @tc.steps:step7. Check the data in the local and sync zone. + * @tc.expected: step7. Both the data are not changed. + */ + CheckUnpublishNotFound(); + + /** + * @tc.steps:step8. Unpublish the k1 with para of deletePublic: false, updateTimestamp: false. + * @tc.expected: step8. Unpublish returns NOT_FOUND. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry1.key, false, false), NOT_FOUND); + + /** + * @tc.steps:step9. Check the data in the local and sync zone. + * @tc.expected: step9. Both the data are not changed. + */ + CheckUnpublishNotFound(); +} + +/** + * @tc.name: SingleVerUnPublishKey002 + * @tc.desc: Test unpublish existent data(no conflict with the local data). + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBUnpublishTest, SingleVerUnPublishKey002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Put [k1, v1], [k2, v2] into the sync zone. + * @tc.expected: step1. Put returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(g_entry1.key, g_entry1.value), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(g_entry2.key, g_entry2.value), OK); + + /** + * @tc.steps:step2. Register the obsevers for the sync data and the local data. + */ + RegisterObservers(); + + /** + * @tc.steps:step3. Unpublish the k1 with para of deletePublic: false, updateTimestamp: false. + * @tc.expected: step3. Unpublish returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry1.key, false, false), OK); + + /** + * @tc.steps:step4. Get the value of k1 from the local zone. + * @tc.expected: step4. Get returns OK, and the value is v1. + */ + Value valueRead; + EXPECT_EQ(g_kvNbDelegatePtr->Get(g_entry1.key, valueRead), OK); + EXPECT_EQ(g_entry1.value, valueRead); + EXPECT_EQ(g_kvNbDelegatePtr->Get(g_entry2.key, valueRead), OK); + EXPECT_EQ(g_entry2.value, valueRead); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry1.key, valueRead), OK); + EXPECT_EQ(g_entry1.value, valueRead); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry2.key, valueRead), NOT_FOUND); + + /** + * @tc.steps:step5. Check the observer. + * @tc.expected: step5. local observer received one inserted data. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_syncObserver->GetCallCount(), 0UL); + EXPECT_EQ(g_localObserver->GetCallCount(), 1UL); + EXPECT_EQ(g_localObserver->GetEntriesInserted().size(), 1UL); + + g_localObserver->ResetToZero(); + g_syncObserver->ResetToZero(); + + /** + * @tc.steps:step6. Unpublish the k2 with para of deletePublic: true, updateTimestamp: false. + * @tc.expected: step6. Unpublish returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry2.key, true, false), OK); + + /** + * @tc.steps:step7. Get the value of k2 from the local zone and the sync zone. + * @tc.expected: step7. GetLocal returns OK, and the value is equal to v2. Get returns NOT_FOUND. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Get(g_entry2.key, valueRead), NOT_FOUND); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry2.key, valueRead), OK); + EXPECT_EQ(g_entry2.value, valueRead); + + /** + * @tc.steps:step8. Check the observer. + * @tc.expected: step8. Sync observer received 1 delete data, and the local observer received one inserted data. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_syncObserver->GetCallCount(), 1UL); + EXPECT_EQ(g_localObserver->GetCallCount(), 1UL); + EXPECT_EQ(g_localObserver->GetEntriesInserted().size(), 1UL); + EXPECT_EQ(g_syncObserver->GetEntriesDeleted().size(), 1UL); +} + +/** + * @tc.name: SingleVerUnPublishKey003 + * @tc.desc: Test unpublish one existent data(conflict with the local data). + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBUnpublishTest, SingleVerUnPublishKey003, TestSize.Level1) +{ + Value value3; + Value value4; + DistributedDBToolsUnitTest::GetRandomKeyValue(value3); + DistributedDBToolsUnitTest::GetRandomKeyValue(value4); + + /** + * @tc.steps:step1. Put [k1, v1] into the sync zone, [k1, v3][k2, v2] into the local zone, + * and put the [k2, v4] into the sync zone. + * @tc.expected: step1. Put returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(g_entry1.key, g_entry1.value), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(g_entry1.key, value3), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(g_entry2.key, g_entry2.value), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_kvNbDelegatePtr->Put(g_entry2.key, value4), OK); + + /** + * @tc.steps:step2. Register the obsevers for the sync data and the local data. + */ + RegisterObservers(); + + /** + * @tc.steps:step3. Unpublish the k2 with para of deletePublic: false, updateTimestamp: false. + * @tc.expected: step3. Unpublish returns LOCAL_DEFEAT. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry1.key, false, false), LOCAL_DEFEAT); + Value valueRead; + + /** + * @tc.steps:step4. Check the data of k1 in the local zone and the observer changes. + * @tc.expected: step4. Value of k1 is v3, and the observer has no change. + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry1.key, valueRead), OK); + EXPECT_EQ(valueRead, value3); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_localObserver->GetCallCount(), 0UL); + + /** + * @tc.steps:step5. Unpublish the k1 with para of deletePublic: false, updateTimestamp: true. + * @tc.expected: step5. Unpublish returns LOCAL_COVERED. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry1.key, false, true), LOCAL_COVERED); + + /** + * @tc.steps:step6. Check the data of k1 in the local zone and the observer changes. + * @tc.expected: step6. Value of k1 is v1, and the observer received one updated data. + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry1.key, valueRead), OK); + EXPECT_EQ(valueRead, g_entry1.value); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_localObserver->GetCallCount(), 1UL); + EXPECT_EQ(g_localObserver->GetEntriesUpdated().size(), 1UL); + g_localObserver->ResetToZero(); + + /** + * @tc.steps:step7. Unpublish the k2 with para of deletePublic: false, updateTimestamp: false. + * @tc.expected: step7. Unpublish returns LOCAL_COVERED. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry2.key, false, false), LOCAL_COVERED); + + /** + * @tc.steps:step8. Check the data of k2 in the local zone and the observer changes. + * @tc.expected: step8. Value of k2 is v2, and the observer received one updated data. + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry2.key, valueRead), OK); + EXPECT_EQ(valueRead, value4); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_localObserver->GetCallCount(), 1UL); + EXPECT_EQ(g_localObserver->GetEntriesUpdated().size(), 1UL); +} + +/** + * @tc.name: SingleVerUnPublishKey004 + * @tc.desc: Test unpublish one deleted data(no conflict with the local data). + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBUnpublishTest, SingleVerUnPublishKey004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Put [k1, v1] into the sync zone, [k2, v2] into the local zone, delete k1 from sync zone. + * @tc.expected: step1. Put returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(g_entry1.key, g_entry1.value), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(g_entry2.key, g_entry2.value), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_kvNbDelegatePtr->Delete(g_entry1.key), OK); + RegisterObservers(); + + /** + * @tc.steps:step2. Unpublish the k1 with para of deletePublic: false, updateTimestamp: false. + * @tc.expected: step2. Unpublish returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry1.key, false, false), OK); + + /** + * @tc.steps:step3. Check the observer and the data change. + * @tc.expected: step3. Observer have no changes. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_localObserver->GetCallCount(), 0UL); + EXPECT_EQ(g_syncObserver->GetCallCount(), 0UL); + Value valueRead; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry1.key, valueRead), NOT_FOUND); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry2.key, valueRead), OK); + EXPECT_EQ(valueRead, g_entry2.value); +} + +/** + * @tc.name: SingleVerUnPublishKey005 + * @tc.desc: Test unpublish one existent data(conflict with the local data). + * @tc.type: FUNC + * @tc.require: AR000DPTQ5 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBUnpublishTest, SingleVerUnPublishKey005, TestSize.Level1) +{ + Value value3; + Value value4; + DistributedDBToolsUnitTest::GetRandomKeyValue(value3); + DistributedDBToolsUnitTest::GetRandomKeyValue(value4); + + /** + * @tc.steps:step1. Put [k1, v1] [k2, v2]into the sync zone, and delete the k1 from sync zone. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(g_entry1.key, g_entry1.value), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Put(g_entry2.key, g_entry2.value), OK); + EXPECT_EQ(g_kvNbDelegatePtr->Delete(g_entry1.key), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step2. Put [k1, v3] [k2, v4]into the local zone, and delete the k2 from sync zone. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(g_entry1.key, value3), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(g_entry2.key, value4), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_kvNbDelegatePtr->Delete(g_entry2.key), OK); + + /** + * @tc.steps:step2. Register the obsevers for the sync data and the local data. + */ + RegisterObservers(); + + /** + * @tc.steps:step3. Unpublish the k1 with para of deletePublic: false, updateTimestamp: false. + * @tc.expected: step3. Unpublish returns LOCAL_DEFEAT. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry1.key, false, false), LOCAL_DEFEAT); + + /** + * @tc.steps:step4. Check the value of k1 in the local zone and the observer changes. + * @tc.expected: step4. value of k1 is still v3, and the observer has no changes. + */ + Value valueRead; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry1.key, valueRead), OK); + EXPECT_EQ(valueRead, value3); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_localObserver->GetCallCount(), 0UL); + EXPECT_EQ(g_syncObserver->GetCallCount(), 0UL); + + /** + * @tc.steps:step5. Unpublish the k2 with para of deletePublic: false, updateTimestamp: false. + * @tc.expected: step5. Unpublish returns LOCAL_DELETED. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry2.key, false, false), LOCAL_DELETED); + + /** + * @tc.steps:step6. Check the value of k2 in the local zone and the observer changes. + * @tc.expected: step6. value of k2 is not found, and the local observer has one deleted data. + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry2.key, valueRead), NOT_FOUND); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_localObserver->GetCallCount(), 1UL); + EXPECT_EQ(g_localObserver->GetEntriesDeleted().size(), 1UL); + g_localObserver->ResetToZero(); + + /** + * @tc.steps:step7. Unpublish the k1 with para of deletePublic: false, updateTimestamp: true. + * @tc.expected: step7. Unpublish returns LOCAL_DELETED. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnpublishToLocal(g_entry1.key, false, true), LOCAL_DELETED); + + /** + * @tc.steps:step8. Check the value of k1 in the local zone and the observer changes. + * @tc.expected: step8. value of k1 is not found, and the local observer has one deleted data. + */ + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(g_entry1.key, valueRead), NOT_FOUND); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_EQ(g_localObserver->GetCallCount(), 1UL); + EXPECT_EQ(g_localObserver->GetEntriesDeleted().size(), 1UL); + g_localObserver->ResetToZero(); +} diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_query_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_query_test.cpp new file mode 100644 index 00000000..c59b1713 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_query_test.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_tools_unit_test.h" +#include "get_query_info.h" +#include "log_print.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace std; + +namespace { + const std::string TEST_FIELD_NAME = "$.test"; + + bool CheckQueryContainer(Query &query, std::list &checkList) + { + const std::list queryList = GetQueryInfo::GetQueryExpression(query).GetQueryExpression(); + if (queryList.size() != checkList.size()) { + return false; + } + auto queryIter = queryList.begin(); + + for (auto checkIter = checkList.begin(); checkIter != checkList.end(); checkIter++, queryIter++) { + EXPECT_EQ(checkIter->operFlag, queryIter->operFlag); + EXPECT_EQ(checkIter->type, queryIter->type); + EXPECT_EQ(checkIter->fieldName, queryIter->fieldName); + if (checkIter->fieldValue.size() != queryIter->fieldValue.size()) { + return false; + } + for (size_t i = 0; i < checkIter->fieldValue.size(); i++) { + EXPECT_EQ(memcmp(&(checkIter->fieldValue[i]), &(queryIter->fieldValue[i]), 8), 0); // only need check 8 + EXPECT_EQ(checkIter->fieldValue[i].stringValue, queryIter->fieldValue[i].stringValue); + } + } + return true; + } + + template + std::list CreateCheckList(QueryObjType operFlag, const std::string &fieldName, const T &queryValue) + { + FieldValue fieldValue; + QueryValueType type = GetQueryValueType::GetFieldTypeAndValue(queryValue, fieldValue); + std::vector values{fieldValue}; + if (type == QueryValueType::VALUE_TYPE_BOOL) { + std::list result{{operFlag, fieldName, QueryValueType::VALUE_TYPE_BOOL, values}}; + return result; + } else if (type == QueryValueType::VALUE_TYPE_INTEGER) { + std::list result{{operFlag, fieldName, QueryValueType::VALUE_TYPE_INTEGER, values}}; + return result; + } else if (type == QueryValueType::VALUE_TYPE_LONG) { + std::list result{{operFlag, fieldName, QueryValueType::VALUE_TYPE_LONG, values}}; + return result; + } else if (type == QueryValueType::VALUE_TYPE_DOUBLE) { + std::list result{{operFlag, fieldName, QueryValueType::VALUE_TYPE_DOUBLE, values}}; + return result; + } else if (type == QueryValueType::VALUE_TYPE_STRING) { + std::list result{{operFlag, fieldName, QueryValueType::VALUE_TYPE_STRING, values}}; + return result; + } else { + std::list result{{operFlag, fieldName, QueryValueType::VALUE_TYPE_INVALID, values}}; + return result; + } + } + + void CheckQueryCompareOper() + { + Query query1 = Query::Select().NotEqualTo(TEST_FIELD_NAME, 123); // random test data + std::list result = CreateCheckList(QueryObjType::NOT_EQUALTO, TEST_FIELD_NAME, 123); // random num + EXPECT_TRUE(CheckQueryContainer(query1, result)); + + Query query2 = Query::Select().EqualTo(TEST_FIELD_NAME, true); + result.clear(); + result = CreateCheckList(QueryObjType::EQUALTO, TEST_FIELD_NAME, true); + EXPECT_TRUE(CheckQueryContainer(query2, result)); + + Query query3 = Query::Select().GreaterThan(TEST_FIELD_NAME, 0); + result.clear(); + result = CreateCheckList(QueryObjType::GREATER_THAN, TEST_FIELD_NAME, 0); + EXPECT_TRUE(CheckQueryContainer(query3, result)); + + Query query4 = Query::Select().LessThan(TEST_FIELD_NAME, INT_MAX); + result.clear(); + result = CreateCheckList(QueryObjType::LESS_THAN, TEST_FIELD_NAME, INT_MAX); + EXPECT_TRUE(CheckQueryContainer(query4, result)); + + Query query5 = Query::Select().GreaterThanOrEqualTo(TEST_FIELD_NAME, 1.56); // random test data + result.clear(); + result = CreateCheckList(QueryObjType::GREATER_THAN_OR_EQUALTO, TEST_FIELD_NAME, 1.56); // random test data + EXPECT_TRUE(CheckQueryContainer(query5, result)); + + Query query6 = Query::Select().LessThanOrEqualTo(TEST_FIELD_NAME, 100); // random test data + result.clear(); + result = CreateCheckList(QueryObjType::LESS_THAN_OR_EQUALTO, TEST_FIELD_NAME, 100); // random test data + EXPECT_TRUE(CheckQueryContainer(query6, result)); + } +} + +class DistributedDBInterfacesQueryTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesQueryTest::SetUpTestCase(void) +{ +} + +void DistributedDBInterfacesQueryTest::TearDownTestCase(void) +{ +} + +void DistributedDBInterfacesQueryTest::SetUp(void) +{ + DistributedDBUnitTest::DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBInterfacesQueryTest::TearDown(void) +{ +} + +/** + * @tc.name: Query001 + * @tc.desc: Check the legal single query operation to see if the generated container is correct + * @tc.type: FUNC + * @tc.require: AR000DR9K6 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesQueryTest, Query001, TestSize.Level1) +{ + Query query = Query::Select(); + Query queryCopy = query; + Query queryMove = std::move(query); + + CheckQueryCompareOper(); + + std::string testValue = "testValue"; + Query query7 = Query::Select().Like(TEST_FIELD_NAME, testValue); + std::list result = CreateCheckList(QueryObjType::LIKE, TEST_FIELD_NAME, testValue); + EXPECT_TRUE(CheckQueryContainer(query7, result)); + + Query query8 = Query::Select().NotLike(TEST_FIELD_NAME, "testValue"); + result.clear(); + result = CreateCheckList(QueryObjType::NOT_LIKE, TEST_FIELD_NAME, testValue); + EXPECT_TRUE(CheckQueryContainer(query8, result)); + + vector fieldValues{1, 1, 1}; + Query query9 = Query::Select().In(TEST_FIELD_NAME, fieldValues); + FieldValue fieldValue; + fieldValue.integerValue = 1; + std::vector values{fieldValue, fieldValue, fieldValue}; + std::list result1{{QueryObjType::IN, TEST_FIELD_NAME, QueryValueType::VALUE_TYPE_INTEGER, values}}; + EXPECT_TRUE(CheckQueryContainer(query9, result1)); + + Query query10 = Query::Select().NotIn(TEST_FIELD_NAME, fieldValues); + std::list result2{{QueryObjType::NOT_IN, TEST_FIELD_NAME, + QueryValueType::VALUE_TYPE_INTEGER, values}}; + EXPECT_TRUE(CheckQueryContainer(query10, result2)); + + Query query11 = Query::Select().OrderBy(TEST_FIELD_NAME, false); + result.clear(); + result = CreateCheckList(QueryObjType::ORDERBY, TEST_FIELD_NAME, false); + EXPECT_TRUE(CheckQueryContainer(query11, result)); + + Query query12 = Query::Select().Limit(1, 2); + values.pop_back(); + values.back().integerValue = 2; + std::list result3{{QueryObjType::LIMIT, string(), QueryValueType::VALUE_TYPE_INTEGER, values}}; + EXPECT_TRUE(CheckQueryContainer(query12, result3)); + + Query query13 = Query::Select().IsNull(TEST_FIELD_NAME); + std::list result4{{QueryObjType::IS_NULL, TEST_FIELD_NAME, + QueryValueType::VALUE_TYPE_NULL, std::vector()}}; + EXPECT_TRUE(CheckQueryContainer(query13, result4)); +} + +/** + * @tc.name: Query002 + * @tc.desc: Check for illegal query conditions + * @tc.type: FUNC + * @tc.require: AR000DR9K6 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesQueryTest, Query002, TestSize.Level1) +{ + float testValue = 1.1; + Query query = Query::Select().NotEqualTo(".test", testValue); + EXPECT_FALSE(GetQueryInfo::GetQueryExpression(query).GetErrFlag()); + + EXPECT_FALSE(GetQueryInfo::GetQueryExpression(Query::Select().GreaterThan(TEST_FIELD_NAME, true)).GetErrFlag()); + + EXPECT_FALSE(GetQueryInfo::GetQueryExpression(Query::Select().LessThan("$.test.12test", true)).GetErrFlag()); +} + +/** + * @tc.name: Query003 + * @tc.desc: Check combination condition + * @tc.type: FUNC + * @tc.require: AR000DR9K6 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesQueryTest, Query003, TestSize.Level1) +{ + Query query = Query::Select().EqualTo(TEST_FIELD_NAME, true).And().GreaterThan(TEST_FIELD_NAME, 1); + QueryExpression queryExpression = GetQueryInfo::GetQueryExpression(query); + EXPECT_TRUE(queryExpression.GetErrFlag()); + EXPECT_EQ(queryExpression.GetQueryExpression().size(), 3UL); + + Query query1 = Query::Select().GreaterThan(TEST_FIELD_NAME, 1).OrderBy(TEST_FIELD_NAME); + QueryExpression queryExpression1 = GetQueryInfo::GetQueryExpression(query1); + EXPECT_TRUE(queryExpression1.GetErrFlag()); + EXPECT_EQ(queryExpression1.GetQueryExpression().size(), 2UL); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_register_syncdb_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_register_syncdb_test.cpp new file mode 100644 index 00000000..9c46b69b --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_register_syncdb_test.cpp @@ -0,0 +1,1871 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "platform_specific.h" +#include "securec.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + const int OBSERVER_SLEEP_TIME = 100; + const bool LOCAL_ONLY = false; + const string STORE_ID = STORE_ID_SYNC; + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + KvStoreObserverUnitTest *g_observer = nullptr; + + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + DBStatus g_snapshotDelegateStatus = INVALID_ARGS; + KvStoreSnapshotDelegate *g_snapshotDelegatePtr = nullptr; + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + + // the type of g_snapshotDelegateCallback is function + auto g_snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_snapshotDelegateStatus), std::ref(g_snapshotDelegatePtr)); + + vector TransStrToVector(const string &input) + { + vector output(input.begin(), input.end()); + return output; + } + + void PrintfEntryList(std::list inEntryList) + { + LOGI("begin print entry list! EntryList size [%zu]", inEntryList.size()); + for (const auto &entry : inEntryList) { + string temp = DBCommon::VectorToHexString(entry.value); + + LOGI("key[%s]", DBCommon::VectorToHexString(entry.key).c_str()); + LOGI("value[%s]", temp.c_str()); + LOGI("value size[%zu]", temp.size()); + } + } + + bool TestEntryList(const list &entries, const list &expectEntries) + { + bool checkRes = true; + EXPECT_EQ(entries.size(), expectEntries.size()); + bool findEntry = false; + for (const auto &entry : entries) { + findEntry = false; + for (const auto &expectEntry : expectEntries) { + if (entry.key != expectEntry.key) { + continue; + } + if (entry.value != expectEntry.value) { + LOGE("entry[%s]:[%s]", DBCommon::VectorToHexString(entry.value).c_str(), + DBCommon::VectorToHexString(expectEntry.value).c_str()); + checkRes = false; + goto END; + } else { + findEntry = true; + break; + } + } + if (!findEntry) { + LOGE("No value can matches!"); + checkRes = false; + goto END; + } + } + END: + if (!checkRes) { + PrintfEntryList(entries); + PrintfEntryList(expectEntries); + } + return checkRes; + } + + Entry GetEntry(const string &keyStr, const string &valueStr) + { + Entry entry; + entry.key = TransStrToVector(keyStr); + entry.value = TransStrToVector(valueStr); + return entry; + } +} + +class DistributedDBInterfacesRegisterSyncDBTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesRegisterSyncDBTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + + string dir = g_testDir + "/multi_ver"; + DIR *dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } +} + +void DistributedDBInterfacesRegisterSyncDBTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesRegisterSyncDBTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /* + * Here, we create STORE_ID before test, + * and it will be closed in TearDown(). + */ + KvStoreDelegate::Option option = {true, LOCAL_ONLY}; + g_mgr.GetKvStore(STORE_ID, option, g_kvDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + + g_observer = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(g_observer != nullptr); + g_observer->ResetToZero(); +} + +void DistributedDBInterfacesRegisterSyncDBTest::TearDown(void) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + if (g_kvDelegatePtr != nullptr) { + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore(STORE_ID) == OK); + } + + if (g_observer != nullptr) { + delete g_observer; + g_observer = nullptr; + } +} + +/** + * @tc.name: RegisterObserver001 + * @tc.desc: normal register observer success. + * @tc.type: FUNC + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver001, TestSize.Level1) +{ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: RegisterObserver002 + * @tc.desc: register(null object) observer success + * @tc.type: FUNC + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver002, TestSize.Level1) +{ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(nullptr) == INVALID_ARGS); +} + +/** + * @tc.name: RegisterObserver003 + * @tc.desc: Test the new data and check the processing result of the callback function. + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test KvStoreDelegate.RegisterObserver + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + Key key; + Value value; + key.push_back('a'); + value.push_back('a'); + + /** + * @tc.steps:step2. Test g_kvDelegatePtr->Put + * @tc.expected: step2. Return OK. + */ + DBStatus status = g_kvDelegatePtr->Put(key, value); + EXPECT_TRUE(status == OK); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: RegisterObserver004 + * @tc.desc: register observer success and putbach callback + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test KvStoreDelegate.RegisterObserver + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + vector entries; + for (int i = 1; i < 11; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + } + + /** + * @tc.steps:step2. Test g_kvDelegatePtr->PutBatch + * @tc.expected: step2. Return OK. + */ + DBStatus status = g_kvDelegatePtr->PutBatch(entries); + + EXPECT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + LOGI("observer count:%lu", g_observer->GetCallCount()); + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: RegisterObserver005 + * @tc.desc: register observer success and putbach callback + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver005, TestSize.Level1) +{ + vector entries; + for (int i = 1; i < 6; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + } + DBStatus status = g_kvDelegatePtr->PutBatch(entries); + EXPECT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + /** + * @tc.steps:step1. Test KvStoreDelegate.RegisterObserver + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + entries.clear(); + for (int i = 1; i < 11; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + } + + /** + * @tc.steps:step2. Test g_kvDelegatePtr->PutBatch + * @tc.expected: step2. Return OK. + */ + status = g_kvDelegatePtr->PutBatch(entries); + EXPECT_TRUE(status == OK); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: RegisterObserver006 + * @tc.desc: register observer success and update callback + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver006, TestSize.Level1) +{ + Key key; + Value value1; + Value value2; + key.push_back(1); + value1.push_back(8); + value2.push_back(10); + DBStatus status = g_kvDelegatePtr->Put(key, value1); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step1. Test KvStoreDelegate.RegisterObserver + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + + /** + * @tc.steps:step2. Test g_kvDelegatePtr->Put(k1,v2) + * @tc.expected: step2. Return OK. + */ + status = g_kvDelegatePtr->Put(key, value2); + EXPECT_TRUE(status == OK); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: RegisterObserver007 + * @tc.desc: register observer success and delete callback + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver007, TestSize.Level1) +{ + Key key; + Value value; + key.push_back(1); + value.push_back(8); + DBStatus status = g_kvDelegatePtr->Put(key, value); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step1. Test KvStoreDelegate.RegisterObserver + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + + /** + * @tc.steps:step2. Test g_kvDelegatePtr->Delete + * @tc.expected: step2. Return OK. + */ + status = g_kvDelegatePtr->Delete(key); + EXPECT_TRUE(status == OK); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: RegisterObserver008 + * @tc.desc: register observer success and delete callback + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver008, TestSize.Level1) +{ + Key key; + key.push_back(1); + + /** + * @tc.steps:step1. Test KvStoreDelegate.RegisterObserver + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + + /** + * @tc.steps:step2. Test g_kvDelegatePtr->Delete with no value + * @tc.expected: step2. Return OK. + */ + DBStatus status = g_kvDelegatePtr->Delete(key); + EXPECT_EQ(status, OK); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Do not print log. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 0); + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: RegisterObserver009 + * @tc.desc: register observer success and deletebatch callback + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver009, TestSize.Level1) +{ + vector entries; + vector keys; + for (int i = 1; i < 6; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + keys.push_back(entry.key); + } + DBStatus status = g_kvDelegatePtr->PutBatch(entries); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step1. Test KvStoreDelegate.RegisterObserver + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + + /** + * @tc.steps:step2. Test g_kvDelegatePtr->DeleteBatch + * @tc.expected: step2. Return OK. + */ + status = g_kvDelegatePtr->DeleteBatch(keys); + EXPECT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: RegisterObserver010 + * @tc.desc: register observer success and delete callback + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver010, TestSize.Level1) +{ + vector entries; + vector keys; + for (int i = 1; i < 6; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + } + for (int i = 1; i < 11; i++) { + Key key; + key.push_back(i); + keys.push_back(key); + } + DBStatus status = g_kvDelegatePtr->PutBatch(entries); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step1. Test KvStoreDelegate.RegisterObserver + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + + /** + * @tc.steps:step2. Test g_kvDelegatePtr->DeleteBatch + * @tc.expected: step2. Return OK. + */ + status = g_kvDelegatePtr->DeleteBatch(keys); + EXPECT_TRUE(status == OK); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: RegisterObserver011 + * @tc.desc: register observer success and DeleteBatch callback + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver011, TestSize.Level1) +{ + vector keys; + for (int i = 1; i < 11; i++) { + Key key; + key.push_back(i); + keys.push_back(key); + } + + /** + * @tc.steps:step1. Test KvStoreDelegate.RegisterObserver + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + + /** + * @tc.steps:step2. Test g_kvDelegatePtr->DeleteBatch with no value + * @tc.expected: step2. Return OK. + */ + DBStatus status = g_kvDelegatePtr->DeleteBatch(keys); + EXPECT_TRUE(status == OK); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Do not print logy. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 0); + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: RegisterObserver012 + * @tc.desc: register observer success and clear callback + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver012, TestSize.Level1) +{ + vector entries; + vector keys; + for (int i = 1; i < 20; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + keys.push_back(entry.key); + } + DBStatus status = g_kvDelegatePtr->PutBatch(entries); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step1. Test KvStoreDelegate.RegisterObserver + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + + /** + * @tc.steps:step2. Test g_kvDelegatePtr->Clear + * @tc.expected: step2. Return OK. + */ + status = g_kvDelegatePtr->Clear(); + EXPECT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: RegisterObserver013 + * @tc.desc: register observer success and clear callback + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver013, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test KvStoreDelegate.RegisterObserver + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + + /** + * @tc.steps:step2. Test g_kvDelegatePtr->Clear with no key and value + * @tc.expected: step2. Return OK. + */ + DBStatus status = g_kvDelegatePtr->Clear(); + EXPECT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: RegisterObserver014 + * @tc.desc: Test the function of modifying a record, adding a record, + * deleting a record, and checking the processing result of the callback function. + * @tc.type: FUNC + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, RegisterObserver014, TestSize.Level1) +{ + Key key; + Value value; + key.push_back(1); + value.push_back(1); + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + + key.clear(); + value.clear(); + key.push_back(2); + value.push_back(2); + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step1. Test KvStoreDelegate.RegisterObserver + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + key.clear(); + value.clear(); + key.push_back(1); + value.push_back(4); + + /** + * @tc.steps:step2. Put(k1,v4) + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + key.clear(); + value.clear(); + key.push_back(3); + value.push_back(3); + + /** + * @tc.steps:step4. Put(k3,v3) + * @tc.expected: step4. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step5. Check the result of KvStoreObserver.OnChange + * @tc.expected: step5. Print log normally. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 2); + value.push_back(4); + + key.clear(); + key.push_back(2); + + /** + * @tc.steps:step6. Delete(k2) + * @tc.expected: step6. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Delete(key) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step7. Check the result of KvStoreObserver.OnChange + * @tc.expected: step7. Print log normally. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 3); + + key.clear(); + key.push_back(4); + + /** + * @tc.steps:step8. Delete(k4) + * @tc.expected: step8. Return OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Delete(key), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step9. Check the result of KvStoreObserver.OnChange + * @tc.expected: step9. Print log normally. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 3); + + /** + * @tc.steps:step10. Clear data + * @tc.expected: step10. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step11. Check the result of KvStoreObserver.OnChange + * @tc.expected: step11. Print log normally. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 4); + + /** + * @tc.steps:step12. Clear data repeat + * @tc.expected: step12. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step13. Check the result of KvStoreObserver.OnChange + * @tc.expected: step13. Print log normally. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 5); + + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: SnapshotRegisterObserver001 + * @tc.desc: register a normal observer for snapshot + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotRegisterObserver002 + * @tc.desc: register a null observer for snapshot register a null observer for snapshot + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(nullptr, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotRegisterObserver003 + * @tc.desc: register observer success and put callback + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot with + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + Key key; + Value value; + key.push_back(1); + value.push_back(1); + + /** + * @tc.steps:step2. Clear data repeat + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +static void CreatEntrysData(size_t size, uint8_t value, vector &entries) +{ + for (size_t i = 0; i < size; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back(value); + entries.push_back(entry); + } +} + +/** + * @tc.name: SnapshotRegisterObserver004 + * @tc.desc: register observer success and putBatch callback + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot with + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + vector entries; + CreatEntrysData(10, '8', entries); + + /** + * @tc.steps:step2. Put data by PutBatch + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotRegisterObserver005 + * @tc.desc: register observer success and putBatch callback + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver005, TestSize.Level1) +{ + vector entries; + CreatEntrysData(6, '8', entries); + + DBStatus status = g_kvDelegatePtr->PutBatch(entries); + EXPECT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + entries.clear(); + CreatEntrysData(10, '8', entries); + + /** + * @tc.steps:step2. Put data by PutBatch + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotRegisterObserver006 + * @tc.desc: register observer success and update callback + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver006, TestSize.Level1) +{ + Key key; + Value value; + key.push_back(1); + value.push_back(1); + + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + value.clear(); + value.push_back(2); + + /** + * @tc.steps:step2. Put data(k1,v2) + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotRegisterObserver007 + * @tc.desc: register observer success and Delete callback + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver007, TestSize.Level1) +{ + Key key; + Value value; + key.push_back(1); + value.push_back(1); + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Delete the k1,v1 + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Delete(key) == OK); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotRegisterObserver008 + * @tc.desc: register observer success and Delete null value callback + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver008, TestSize.Level1) +{ + Key key; + key.push_back(1); + + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Delete the k1, null value + * @tc.expected: step2. Return OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Delete(key), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 0); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotRegisterObserver009 + * @tc.desc: register observer success and DeleteBatch callback + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver009, TestSize.Level1) +{ + vector entries; + vector keys; + for (int i = 1; i < 11; i++) { + Entry entry; + Key key; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + keys.push_back(entry.key); + } + + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Delete with no put + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->DeleteBatch(keys) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + LOGI("observer count:%lu", g_observer->GetCallCount()); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 0); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotRegisterObserver010 + * @tc.desc: register observer success and DeleteBatch callback + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver010, TestSize.Level1) +{ + vector entries; + for (int i = 1; i < 6; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + } + + DBStatus status = g_kvDelegatePtr->PutBatch(entries); + EXPECT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + vector keys; + for (int i = 1; i < 11; i++) { + Key key; + key.push_back(i); + keys.push_back(key); + } + + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Delete the keys and value normally + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->DeleteBatch(keys) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotRegisterObserver011 + * @tc.desc: register observer success and DeleteBatch callback + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver011, TestSize.Level1) +{ + EXPECT_TRUE(g_kvDelegateStatus == OK); + + vector keys; + for (int i = 1; i < 11; i++) { + Key key; + key.push_back(i); + keys.push_back(key); + } + + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Delete with no put + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->DeleteBatch(keys) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 0); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotRegisterObserver012 + * @tc.desc: register observer success and Clear callback + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver012, TestSize.Level1) +{ + vector entries; + for (int i = 1; i < 20; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + } + DBStatus status = g_kvDelegatePtr->PutBatch(entries); + EXPECT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Clear data + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotRegisterObserver013 + * @tc.desc: register observer success and Clear callback + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver013, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Clear data + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Do not print log normally. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 1); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotRegisterObserver014 + * @tc.desc: register observer success and operate callback + * @tc.require: AR000BVDFQ AR000CQDVJ + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotRegisterObserver014, TestSize.Level1) +{ + Key key; + Value value; + key.push_back(1); + value.push_back(1); + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + key.clear(); + value.clear(); + key.push_back(2); + value.push_back(2); + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + key.clear(); + value.clear(); + key.push_back(1); + value.push_back(4); + + /** + * @tc.steps:step2. Put data(k1, v4) + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step3. Check the result of KvStoreObserver.OnChange + * @tc.expected: step3. Print log normally. + */ + LOGI("observer count:%lu", g_observer->GetCallCount()); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + key.clear(); + value.clear(); + key.push_back(3); + value.push_back(3); + + /** + * @tc.steps:step4. Put data(k3, v3) + * @tc.expected: step4. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step5. Check the result of KvStoreObserver.OnChange + * @tc.expected: step5. Print log normally. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 2); + + key.clear(); + key.push_back(2); + + /** + * @tc.steps:step6. Delete(k2) + * @tc.expected: step6. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Delete(key) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step7. Check the result of KvStoreObserver.OnChange + * @tc.expected: step7. Print log normally. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 3); + + key.clear(); + key.push_back(4); + + /** + * @tc.steps:step8.Delete(k4) + * @tc.expected: step8. Return OK. + */ + EXPECT_EQ(g_kvDelegatePtr->Delete(key), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step9. Check the result of KvStoreObserver.OnChange + * @tc.expected: step9. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 3); + + /** + * @tc.steps:step10. Clear data + * @tc.expected: step10. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step11. Check the result of KvStoreObserver.OnChange + * @tc.expected: step11. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 4); + + /** + * @tc.steps:step12. Clear data repeat + * @tc.expected: step12. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps:step13. Check the result of KvStoreObserver.OnChange + * @tc.expected: step13. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 5); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: UnRegisterObserver001 + * @tc.desc: Unregister a normal observer + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, UnRegisterObserver001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test g_kvDelegatePtr->GetKvStoreSnapshot + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + + /** + * @tc.steps:step2. Test UnRegister Observer + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); +} + +/** + * @tc.name: UnRegisterObserver002 + * @tc.desc: Unregister a null observer + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, UnRegisterObserver002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test UnRegister Observer with an null observer + * @tc.expected: step1. Return INVALID_ARGS. + */ + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(nullptr) == INVALID_ARGS); +} + +/** + * @tc.name: UnRegisterObserver003 + * @tc.desc: Unregister a unregister observer + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, UnRegisterObserver003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test UnRegister Observer with observer do not have been registered + * @tc.expected: step1. Return NOT_FOUND. + */ + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == NOT_FOUND); +} + +/** + * @tc.name: UnRegisterObserver004 + * @tc.desc: Unregister a and check callback + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, UnRegisterObserver004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Register Observer + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->RegisterObserver(g_observer) == OK); + Key key; + Value value; + key.push_back(1); + value.push_back(1); + + /** + * @tc.steps:step2. Put data(k1,v1), Check the result of KvStoreObserver.OnChange + * @tc.expected: step2. Return OK. Print log normally. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + /** + * @tc.steps:step3. Unregister Observer + * @tc.expected: step3. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->UnRegisterObserver(g_observer) == OK); + key.clear(); + value.clear(); + key.push_back(2); + value.push_back(2); + + /** + * @tc.steps:step4. Put data(k2,v2) + * @tc.expected: step4. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + + /** + * @tc.steps:step5. Check the result of KvStoreObserver.OnChange + * @tc.expected: step5. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + vector entries; + vector keys; + for (int i = 11; i < 21; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + keys.push_back(entry.key); + } + + /** + * @tc.steps:step6. PutBatch 10 data + * @tc.expected: step6. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + + /** + * @tc.steps:step7. Check the result of KvStoreObserver.OnChange + * @tc.expected: step7. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + key.clear(); + value.clear(); + key.push_back(1); + value.push_back(2); + + /** + * @tc.steps:step8. Put data(k1, v2) + * @tc.expected: step8. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + + /** + * @tc.steps:step9. Check the result of KvStoreObserver.OnChange + * @tc.expected: step9. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + /** + * @tc.steps:step10. Delete data(k1) + * @tc.expected: step10. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Delete(key) == OK); + + /** + * @tc.steps:step11. Check the result of KvStoreObserver.OnChange + * @tc.expected: step11. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + /** + * @tc.steps:step12. Delete data(k1) + * @tc.expected: step12. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Delete(key) == OK); + + /** + * @tc.steps:step11. Check the result of KvStoreObserver.OnChange + * @tc.expected: step11. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + /** + * @tc.steps:step12. DeleteBatch data + * @tc.expected: step12. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->DeleteBatch(keys) == OK); + + /** + * @tc.steps:step13. Check the result of KvStoreObserver.OnChange + * @tc.expected: step13. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + /** + * @tc.steps:step14. Clear all data + * @tc.expected: step14. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + + /** + * @tc.steps:step15. Check the result of KvStoreObserver.OnChange + * @tc.expected: step15. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + /** + * @tc.steps:step16. Clear all data repeat + * @tc.expected: step16. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + + /** + * @tc.steps:step17. Check the result of KvStoreObserver.OnChange + * @tc.expected: step17. Do not print log. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 1); +} + +/** + * @tc.name: SnapshotUnRegisterObserver001 + * @tc.desc: Unregister a snapshot observer + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotUnRegisterObserver001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get KvStore Snapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotUnRegisterObserver002 + * @tc.desc: Unregister a null snapshot observer + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotUnRegisterObserver002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get KvStore Snapshot with snapshot is null + * @tc.expected: step1. Return ERROR. + */ + KvStoreSnapshotDelegate *snapshot = nullptr; + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshot) == DB_ERROR); + g_snapshotDelegatePtr = nullptr; +} + +/** + * @tc.name: SnapshotUnRegisterObserver003 + * @tc.desc: Unregister a unregister snapshot observer + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotUnRegisterObserver003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get KvStore Snapshot with snapshot is not registered + * @tc.expected: step1. Return OK. + */ + KvStoreSnapshotDelegate *snapshot = new (std::nothrow) KvStoreSnapshotDelegateImpl(nullptr, nullptr); + ASSERT_TRUE(snapshot != nullptr); + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(snapshot) == OK); + g_snapshotDelegatePtr = nullptr; +} + +static void SnapshotUnRegisterObserver004Inner() +{ + /** + * @tc.steps:step1. Get KvStore Snapshot + * @tc.expected: step1. Return OK. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + Key key; + Value value; + key.push_back(1); // random key + value.push_back(1); // random value + + /** + * @tc.steps:step2. Put data(k1,v1), Check the result of KvStoreObserver.OnChange + * @tc.expected: step2. Return OK. Print log normally. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + /** + * @tc.steps:step3. Release KvStore Snapshot + * @tc.expected: step3. Return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; + key.clear(); + value.clear(); + key.push_back(2); // random key + value.push_back(2); // random value + + /** + * @tc.steps:step4/5. Put data(k2,v2), Check the result of KvStoreObserver.OnChange + * @tc.expected: step4/5. Return OK. Do not print log. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(g_observer->GetCallCount() == 1); +} + +/** + * @tc.name: SnapshotUnRegisterObserver004 + * @tc.desc: Check a unregister snapshot observer + * @tc.require: AR000BVDFP AR000CQDVI + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, SnapshotUnRegisterObserver004, TestSize.Level1) +{ + SnapshotUnRegisterObserver004Inner(); + + vector entries; + vector keys; + for (int i = 11; i < 21; i++) { + Entry entry; + entry.key.push_back(i); + entry.value.push_back('8'); + entries.push_back(entry); + keys.push_back(entry.key); + } + + /** + * @tc.steps:step6/7. PutBatch 10 data, Check the result of KvStoreObserver.OnChange + * @tc.expected: step6/7. Return OK. Do not print log. + */ + EXPECT_TRUE(g_kvDelegatePtr->PutBatch(entries) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + Key key; + Value value; + key.push_back(1); + value.push_back(2); + + /** + * @tc.steps:step8/9. Put data(k1,v2), Check the result of KvStoreObserver.OnChange + * @tc.expected: step8/9. Return OK. Do not print log. + */ + EXPECT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + /** + * @tc.steps:step10/11. Delete(k1), Check the result of KvStoreObserver.OnChange + * @tc.expected: step10/11. Return OK. Do not print log. + */ + EXPECT_TRUE(g_kvDelegatePtr->Delete(key) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + /** + * @tc.steps:step12/13. Delete a not exist key, Check the result of KvStoreObserver.OnChange + * @tc.expected: step12/13. Return OK. Do not print log. + */ + EXPECT_TRUE(g_kvDelegatePtr->Delete(key) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + /** + * @tc.steps:step14/15. DeleteBatch, Check the result of KvStoreObserver.OnChange + * @tc.expected: step14/15. Return OK. Do not print log. + */ + EXPECT_TRUE(g_kvDelegatePtr->DeleteBatch(keys) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + /** + * @tc.steps:step14/15. Clear all data, Check the result of KvStoreObserver.OnChange + * @tc.expected: step14/15. Return OK. Do not print log. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(g_observer->GetCallCount() == 1); + + /** + * @tc.steps:step14/15. Clear all data repeat, Check the result of KvStoreObserver.OnChange + * @tc.expected: step14/15. Return OK. Do not print log. + */ + EXPECT_TRUE(g_kvDelegatePtr->Clear() == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(g_observer->GetCallCount() == 1); +} + +static void CheckObserverCallback(const Entry &entryB) +{ + /** + * @tc.steps: step3. Start a transaction. Insert [keyA, valueD], [keyC, valueC] + * through the Put interface of the delegate, and delete the keyB data + * through the Delete interface of the delegate. Ending a transaction + * @tc.expected: step3. Obtain [keyC, valueC] + * from the GetEntriesInserted of the callback data, + * obtain [keyA, valueD] from the GetEntriesUpdated, + * and obtain [keyB, valueB] through the GetEntriesDeleted. + */ + EXPECT_EQ(g_kvDelegatePtr->StartTransaction(), OK); + Entry entryC = GetEntry("key_C", ""); + DistributedDBToolsUnitTest::GetRandomKeyValue(entryC.value, 10 * 1024); // 30K + Entry entryE = GetEntry("key_E", ""); + DistributedDBToolsUnitTest::GetRandomKeyValue(entryE.value, 200 * 1024); // 200K, over the slice threshold. + Entry entryF = GetEntry("key_F", ""); + DistributedDBToolsUnitTest::GetRandomKeyValue(entryF.value, 100); // 100 + EXPECT_EQ(g_kvDelegatePtr->Put(entryE.key, entryE.value), OK); + EXPECT_EQ(g_kvDelegatePtr->Put(entryF.key, entryF.value), OK); + EXPECT_EQ(g_kvDelegatePtr->Put(entryC.key, entryC.value), OK); + Entry entryD = GetEntry("key_A", ""); + DistributedDBToolsUnitTest::GetRandomKeyValue(entryD.value, 100 * 1024); // 100k + EXPECT_EQ(g_kvDelegatePtr->Put(entryD.key, entryD.value), OK); + EXPECT_EQ(g_kvDelegatePtr->Delete(entryB.key), OK); + EXPECT_EQ(g_kvDelegatePtr->Commit(), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME * 10)); + + /** + * @tc.steps: step4. Check whether the observer callback is triggered + * and check the data obtained from the survey. + */ + EXPECT_TRUE(g_observer->GetCallCount() == 1); + std::list inserted = g_observer->GetEntriesInserted(); + std::list updated = g_observer->GetEntriesUpdated(); + std::list deleted = g_observer->GetEntriesDeleted(); + LOGI("insert size[%zu], updated size[%zu], deleted size[%zu]", inserted.size(), updated.size(), deleted.size()); + + std::list expectedInserted; + std::list expectedUpdated; + std::list expectedDeleted; + expectedInserted.push_back(entryC); + expectedInserted.push_back(entryE); + expectedInserted.push_back(entryF); + expectedUpdated.push_back(entryD); + expectedDeleted.push_back(entryB); + EXPECT_TRUE(TestEntryList(inserted, expectedInserted)); + EXPECT_TRUE(TestEntryList(updated, expectedUpdated)); + EXPECT_TRUE(TestEntryList(deleted, expectedDeleted)); +} + +/** + * @tc.name: GetObserverData001 + * @tc.desc: Test whether the data change notification can obtain these changes + * when the database is added, deleted, or modified. + * @tc.type: FUNC + * @tc.require: AR000BVDFR AR000CQDVK + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, GetObserverData001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Insert the data of [keyA, valueA], [keyB, valueB] through the Put interface of the delegate. + */ + Entry entryA = GetEntry("key_A", "value_A"); + EXPECT_EQ(g_kvDelegatePtr->Put(entryA.key, entryA.value), OK); + Entry entryB = GetEntry("key_B", "value_B"); + EXPECT_EQ(g_kvDelegatePtr->Put(entryB.key, entryB.value), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps: step2. Register an observer through the RegisterObserver interface of the delegate. + * @tc.expected: step2. Returns a non-empty snapshot. + */ + EXPECT_EQ(g_kvDelegatePtr->RegisterObserver(g_observer), OK); + + CheckObserverCallback(entryB); + + EXPECT_EQ(g_kvDelegatePtr->UnRegisterObserver(g_observer), OK); +} + +/** + * @tc.name: GetSnapshotObserverData001 + * @tc.desc: Test whether a data notification is sent + * when the value of observer is not empty + * when a snapshot is obtained and the database data changes. + * @tc.type: FUNC + * @tc.require: AR000C06UT AR000CQDTG + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesRegisterSyncDBTest, GetSnapshotObserverData001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Insert the data of [keyA, valueA], [keyB, valueB] through the Put interface of the delegate. + */ + Entry entryA = GetEntry("key_A", "value_A"); + EXPECT_EQ(g_kvDelegatePtr->Put(entryA.key, entryA.value), OK); + Entry entryB = GetEntry("key_B", "value_B"); + EXPECT_EQ(g_kvDelegatePtr->Put(entryB.key, entryB.value), OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps: step2. Obtain the snapshot object snapshotA + * through the GetKvStoreSnapshot interface of the delegate and transfer the non-null observer. + * @tc.expected: step2. Returns a non-empty snapshot. + */ + g_kvDelegatePtr->GetKvStoreSnapshot(g_observer, g_snapshotDelegateCallback); + EXPECT_EQ(g_kvDelegateStatus, OK); + + CheckObserverCallback(entryB); + + EXPECT_EQ(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr), OK); + g_snapshotDelegatePtr = nullptr; +} diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_relational_sync_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_relational_sync_test.cpp new file mode 100644 index 00000000..dda185f9 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_relational_sync_test.cpp @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" +#include "relational_store_manager.h" +#include "virtual_communicator_aggregator.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + constexpr const char* DB_SUFFIX = ".db"; + constexpr const char* STORE_ID = "Relational_Store_ID"; + const std::string DEVICE_A = "DEVICE_A"; + std::string g_testDir; + std::string g_dbDir; + DistributedDB::RelationalStoreManager g_mgr(APP_ID, USER_ID); + VirtualCommunicatorAggregator* g_communicatorAggregator = nullptr; + + const std::string NORMAL_CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS sync_data(" \ + "key BLOB NOT NULL UNIQUE," \ + "value BLOB," \ + "timestamp INT NOT NULL," \ + "flag INT NOT NULL," \ + "device BLOB," \ + "ori_device BLOB," \ + "hash_key BLOB PRIMARY KEY NOT NULL," \ + "w_timestamp INT," \ + "UNIQUE(device, ori_device));" \ + "CREATE INDEX key_index ON sync_data (key, flag);"; + + const std::string EMPTY_COLUMN_TYPE_CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS student(" \ + "id INTEGER NOT NULL UNIQUE," \ + "name TEXT," \ + "field_1);"; +} + +class DistributedDBInterfacesRelationalSyncTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp() override; + void TearDown() override; +protected: + sqlite3 *db = nullptr; + RelationalStoreDelegate *delegate = nullptr; +}; + +void DistributedDBInterfacesRelationalSyncTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + LOGD("Test dir is %s", g_testDir.c_str()); + g_dbDir = g_testDir + "/"; + + g_communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator(); + ASSERT_TRUE(g_communicatorAggregator != nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(g_communicatorAggregator); +} + +void DistributedDBInterfacesRelationalSyncTest::TearDownTestCase(void) +{ + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); +} + +void DistributedDBInterfacesRelationalSyncTest::SetUp() +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + + db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + RelationalTestUtils::CreateDeviceTable(db, "sync_data", DEVICE_A); + + DBStatus status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + + status = delegate->CreateDistributedTable("sync_data"); + EXPECT_EQ(status, OK); +} + +void DistributedDBInterfacesRelationalSyncTest::TearDown() +{ + g_mgr.CloseStore(delegate); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir); +} + +/** + * @tc.name: RelationalSyncTest001 + * @tc.desc: Test with sync interface, table is not a distributed table + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalSyncTest, RelationalSyncTest001, TestSize.Level1) +{ + std::vector devices = {DEVICE_A}; + Query query = Query::Select("sync_datb"); + int errCode = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + + EXPECT_EQ(errCode, DISTRIBUTED_SCHEMA_NOT_FOUND); +} + +/** + * @tc.name: RelationalSyncTest002 + * @tc.desc: Test with sync interface, query is not support + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalSyncTest, RelationalSyncTest002, TestSize.Level1) +{ + std::vector devices = {DEVICE_A}; + Query query = Query::Select("sync_data").Like("value", "abc"); + int errCode = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + + EXPECT_EQ(errCode, NOT_SUPPORT); +} + +/** + * @tc.name: RelationalSyncTest003 + * @tc.desc: Test with sync interface, query is invalid format + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalSyncTest, RelationalSyncTest003, TestSize.Level1) +{ + std::vector devices = {DEVICE_A}; + Query query = Query::Select("sync_data").And().Or().EqualTo("flag", 2); + int errCode = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + + EXPECT_EQ(errCode, INVALID_QUERY_FORMAT); +} + +/** + * @tc.name: RelationalSyncTest004 + * @tc.desc: Test with sync interface, query use invalid field + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalSyncTest, RelationalSyncTest004, TestSize.Level1) +{ + std::vector devices = {DEVICE_A}; + Query query = Query::Select("sync_data").EqualTo("fleg", 2); + int errCode = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + + EXPECT_EQ(errCode, INVALID_QUERY_FIELD); +} + +/** + * @tc.name: RelationalSyncTest005 + * @tc.desc: Test with sync interface, query table has been modified + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalSyncTest, RelationalSyncTest005, TestSize.Level1) +{ + std::string modifySql = "ALTER TABLE sync_data ADD COLUMN add_field INTEGER;"; + EXPECT_EQ(RelationalTestUtils::ExecSql(db, modifySql), SQLITE_OK); + + std::vector devices = {DEVICE_A}; + Query query = Query::Select("sync_data"); + int errCode = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + + EXPECT_EQ(errCode, DISTRIBUTED_SCHEMA_CHANGED); +} + +/** + * @tc.name: RelationalSyncTest006 + * @tc.desc: Test with sync interface, query is not set table name + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalSyncTest, RelationalSyncTest006, TestSize.Level1) +{ + std::vector devices = {DEVICE_A}; + Query query = Query::Select(); + int errCode = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + + EXPECT_EQ(errCode, NOT_SUPPORT); +} + +/** + * @tc.name: RelationalSyncTest007 + * @tc.desc: Test with sync interface, distributed table has empty column type + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalSyncTest, RelationalSyncTest007, TestSize.Level1) +{ + EXPECT_EQ(RelationalTestUtils::ExecSql(db, EMPTY_COLUMN_TYPE_CREATE_TABLE_SQL), SQLITE_OK); + RelationalTestUtils::CreateDeviceTable(db, "student", DEVICE_A); + + DBStatus status = delegate->CreateDistributedTable("student"); + EXPECT_EQ(status, OK); + + std::vector devices = {DEVICE_A}; + Query query = Query::Select("student"); + int errCode = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + + EXPECT_EQ(errCode, OK); +} + +/** + * @tc.name: RelationalSyncTest008 + * @tc.desc: Test sync with rebuilt table + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalSyncTest, RelationalSyncTest008, TestSize.Level1) +{ + /** + * @tc.steps:step1. Drop sync_data + * @tc.expected: step1. ok + */ + std::string dropSql = "DROP TABLE IF EXISTS sync_data;"; + EXPECT_EQ(RelationalTestUtils::ExecSql(db, dropSql), SQLITE_OK); + + /** + * @tc.steps:step2. sync with sync_data + * @tc.expected: step2. return INVALID_QUERY_FORMAT + */ + std::vector devices = {DEVICE_A}; + Query query = Query::Select("sync_data"); + int errCode = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + EXPECT_EQ(errCode, DISTRIBUTED_SCHEMA_CHANGED); + + /** + * @tc.steps:step3. recreate sync_data + * @tc.expected: step3. ok + */ + EXPECT_EQ(RelationalTestUtils::ExecSql(db, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + DBStatus status = delegate->CreateDistributedTable("sync_data"); + EXPECT_EQ(status, OK); + + /** + * @tc.steps:step4. Check trigger + * @tc.expected: step4. trigger exists + */ + bool result = false; + std::string checkSql = "select * from sqlite_master where type = 'trigger' and tbl_name = 'sync_data';"; + EXPECT_EQ(RelationalTestUtils::CheckSqlResult(db, checkSql, result), E_OK); + EXPECT_EQ(result, true); + + /** + * @tc.steps:step5. sync with sync_data + * @tc.expected: step5. ok + */ + errCode = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + + EXPECT_EQ(errCode, OK); +} + +/** + * @tc.name: RelationalSyncTest009 + * @tc.desc: Test sync with invalid query + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalSyncTest, RelationalSyncTest009, TestSize.Level1) +{ + EXPECT_EQ(RelationalTestUtils::ExecSql(db, EMPTY_COLUMN_TYPE_CREATE_TABLE_SQL), SQLITE_OK); + RelationalTestUtils::CreateDeviceTable(db, "student", DEVICE_A); + + DBStatus status = delegate->CreateDistributedTable("student"); + EXPECT_EQ(status, OK); + + std::vector devices = {DEVICE_A}; + Query query = Query::Select("student").EqualTo("$id", 123); + status = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + EXPECT_EQ(status, INVALID_QUERY_FORMAT); + + query = Query::Select("student").EqualTo("A$id", 123); + status = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + EXPECT_EQ(status, INVALID_QUERY_FORMAT); + + query = Query::Select("student").EqualTo("$.id", 123); + status = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + + EXPECT_EQ(status, OK); +} + +/** + * @tc.name: RelationalSyncTest010 + * @tc.desc: Test sync with shcema changed + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalSyncTest, RelationalSyncTest010, TestSize.Level1) +{ + std::vector devices = {DEVICE_A}; + Query query = Query::Select("sync_data"); + int errCode = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + EXPECT_EQ(errCode, OK); + + std::string modifySql = "DROP TABLE IF EXISTS sync_data;"; + EXPECT_EQ(RelationalTestUtils::ExecSql(db, modifySql), SQLITE_OK); + + errCode = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + EXPECT_EQ(errCode, DISTRIBUTED_SCHEMA_CHANGED); + + errCode = delegate->Sync(devices, SyncMode::SYNC_MODE_PUSH_ONLY, query, + [&devices](const std::map> &devicesMap) { + EXPECT_EQ(devicesMap.size(), devices.size()); + }, true); + EXPECT_EQ(errCode, DISTRIBUTED_SCHEMA_CHANGED); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_relational_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_relational_test.cpp new file mode 100644 index 00000000..824f9be1 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_relational_test.cpp @@ -0,0 +1,784 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" +#include "platform_specific.h" +#include "relational_store_manager.h" +#include "relational_store_sqlite_ext.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + constexpr const char* DB_SUFFIX = ".db"; + constexpr const char* STORE_ID = "Relational_Store_ID"; + std::string g_testDir; + std::string g_dbDir; + DistributedDB::RelationalStoreManager g_mgr(APP_ID, USER_ID); + + const std::string NORMAL_CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS sync_data(" \ + "key BLOB NOT NULL UNIQUE," \ + "value BLOB," \ + "timestamp INT NOT NULL," \ + "flag INT NOT NULL," \ + "device BLOB," \ + "ori_device BLOB," \ + "hash_key BLOB PRIMARY KEY NOT NULL," \ + "w_timestamp INT," \ + "UNIQUE(device, ori_device));" \ + "CREATE INDEX key_index ON sync_data (key, flag);"; + + const std::string CREATE_TABLE_SQL_NO_PRIMARY_KEY = "CREATE TABLE IF NOT EXISTS sync_data(" \ + "key BLOB NOT NULL UNIQUE," \ + "value BLOB," \ + "timestamp INT NOT NULL," \ + "flag INT NOT NULL," \ + "device BLOB," \ + "ori_device BLOB," \ + "hash_key BLOB NOT NULL," \ + "w_timestamp INT," \ + "UNIQUE(device, ori_device));" \ + "CREATE INDEX key_index ON sync_data (key, flag);"; + + const std::string UNSUPPORTED_FIELD_TABLE_SQL = "CREATE TABLE IF NOT EXISTS test('$.ID' INT, val BLOB);"; + + const std::string COMPOSITE_PRIMARY_KEY_TABLE_SQL = R"(CREATE TABLE workers ( + worker_id INTEGER, + last_name VARCHAR NOT NULL, + first_name VARCHAR, + join_date DATE, + PRIMARY KEY (last_name, first_name) + );)"; + + const std::string INSERT_SYNC_DATA_SQL = "INSERT OR REPLACE INTO sync_data (key, timestamp, flag, hash_key) " + "VALUES('KEY', 123456789, 1, 'HASH_KEY');"; +} + +class DistributedDBInterfacesRelationalTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesRelationalTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + LOGD("Test dir is %s", g_testDir.c_str()); + g_dbDir = g_testDir + "/"; + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir); +} + +void DistributedDBInterfacesRelationalTest::TearDownTestCase(void) +{ +} + +void DistributedDBInterfacesRelationalTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBInterfacesRelationalTest::TearDown(void) +{ + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir); +} + +/** + * @tc.name: RelationalStoreTest001 + * @tc.desc: Test open store and create distributed db + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalStoreTest001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare db file + * @tc.expected: step1. Return OK. + */ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + RelationalTestUtils::CreateDeviceTable(db, "sync_data", "DEVICE_A"); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); + + /** + * @tc.steps:step2. open relational store, create distributed table, close store + * @tc.expected: step2. Return OK. + */ + RelationalStoreDelegate *delegate = nullptr; + DBStatus status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + + status = delegate->CreateDistributedTable("sync_data"); + EXPECT_EQ(status, OK); + + // test create same table again + status = delegate->CreateDistributedTable("sync_data"); + EXPECT_EQ(status, OK); + + status = g_mgr.CloseStore(delegate); + EXPECT_EQ(status, OK); + + /** + * @tc.steps:step3. drop sync_data table + * @tc.expected: step3. Return OK. + */ + db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "drop table sync_data;"), SQLITE_OK); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); + + /** + * @tc.steps:step4. open again, check auxiliary should be delete + * @tc.expected: step4. Return OK. + */ + delegate = nullptr; + status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + status = g_mgr.CloseStore(delegate); + EXPECT_EQ(status, OK); +} + +/** + * @tc.name: RelationalStoreTest002 + * @tc.desc: Test open store with invalid path or store ID + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalStoreTest002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare db file + * @tc.expected: step1. Return OK. + */ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); + + /** + * @tc.steps:step2. Test open store with invalid path or store ID + * @tc.expected: step2. open store failed. + */ + RelationalStoreDelegate *delegate = nullptr; + + // test open store with path not exist + DBStatus status = g_mgr.OpenStore(g_dbDir + "tmp/" + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_NE(status, OK); + ASSERT_EQ(delegate, nullptr); + + // test open store with empty store_id + status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, {}, {}, delegate); + EXPECT_NE(status, OK); + ASSERT_EQ(delegate, nullptr); + + // test open store with path has invalid character + status = g_mgr.OpenStore(g_dbDir + "t&m$p/" + STORE_ID + DB_SUFFIX, {}, {}, delegate); + EXPECT_NE(status, OK); + ASSERT_EQ(delegate, nullptr); + + // test open store with store_id has invalid character + status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, "Relation@al_S$tore_ID", {}, delegate); + EXPECT_NE(status, OK); + ASSERT_EQ(delegate, nullptr); + + // test open store with store_id length over MAX_STORE_ID_LENGTH + status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, + std::string(DBConstant::MAX_STORE_ID_LENGTH + 1, 'a'), {}, delegate); + EXPECT_NE(status, OK); + ASSERT_EQ(delegate, nullptr); +} + +/** + * @tc.name: RelationalStoreTest003 + * @tc.desc: Test open store with journal_mode is not WAL + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalStoreTest003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare db file with string is not WAL + * @tc.expected: step1. Return OK. + */ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=PERSIST;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); + + /** + * @tc.steps:step2. Test open store + * @tc.expected: step2. Open store failed. + */ + RelationalStoreDelegate *delegate = nullptr; + + // test open store with journal mode is not WAL + DBStatus status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_NE(status, OK); + ASSERT_EQ(delegate, nullptr); +} + +/** + * @tc.name: RelationalStoreTest004 + * @tc.desc: Test create distributed table with over limit + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalStoreTest004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare db file with multiple tables + * @tc.expected: step1. Return OK. + */ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + const int tableCount = DBConstant::MAX_DISTRIBUTED_TABLE_COUNT + 10; // 10: additional size for test abnormal scene + for (int i=0; iCreateDistributedTable("TEST_" + std::to_string(i)), OK); + } else { + EXPECT_NE(delegate->CreateDistributedTable("TEST_" + std::to_string(i)), OK); + } + } + + /** + * @tc.steps:step3. Close store + * @tc.expected: step3. Return OK. + */ + status = g_mgr.CloseStore(delegate); + EXPECT_EQ(status, OK); +} + +/** + * @tc.name: RelationalStoreTest005 + * @tc.desc: Test create distributed table with invalid table name + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalStoreTest005, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare db file + * @tc.expected: step1. Return OK. + */ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); + + /** + * @tc.steps:step2. Open store + * @tc.expected: step2. return OK + */ + RelationalStoreDelegate *delegate = nullptr; + DBStatus status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + + /** + * @tc.steps:step3. Create distributed table with invalid table name + * @tc.expected: step3. Create distributed table failed. + */ + EXPECT_NE(delegate->CreateDistributedTable(DBConstant::SYSTEM_TABLE_PREFIX + "_tmp"), OK); + + EXPECT_EQ(delegate->CreateDistributedTable("Handle-J@^."), INVALID_ARGS); + + /** + * @tc.steps:step4. Close store + * @tc.expected: step4. Return OK. + */ + status = g_mgr.CloseStore(delegate); + EXPECT_EQ(status, OK); +} + +/** + * @tc.name: RelationalStoreTest006 + * @tc.desc: Test create distributed table with non primary key schema + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalStoreTest006, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare db file + * @tc.expected: step1. Return OK. + */ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, CREATE_TABLE_SQL_NO_PRIMARY_KEY), SQLITE_OK); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); + + /** + * @tc.steps:step2. Open store + * @tc.expected: step2. return OK + */ + RelationalStoreDelegate *delegate = nullptr; + DBStatus status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + + /** + * @tc.steps:step3. Create distributed table with invalid table name + * @tc.expected: step3. Create distributed table failed. + */ + EXPECT_EQ(delegate->CreateDistributedTable("sync_data"), OK); + + /** + * @tc.steps:step4. Close store + * @tc.expected: step4. Return OK. + */ + status = g_mgr.CloseStore(delegate); + EXPECT_EQ(status, OK); + delegate = nullptr; + + status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + + status = g_mgr.CloseStore(delegate); + EXPECT_EQ(status, OK); +} + +/** + * @tc.name: RelationalStoreTest007 + * @tc.desc: Test create distributed table with table has invalid field name + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalStoreTest007, TestSize.Level1) +{ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, UNSUPPORTED_FIELD_TABLE_SQL), SQLITE_OK); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); + + RelationalStoreDelegate *delegate = nullptr; + DBStatus status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + + EXPECT_EQ(delegate->CreateDistributedTable("test"), NOT_SUPPORT); + status = g_mgr.CloseStore(delegate); + EXPECT_EQ(status, OK); +} + +/** + * @tc.name: RelationalStoreTest008 + * @tc.desc: Test create distributed table with table has composite primary key + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalStoreTest008, TestSize.Level1) +{ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, COMPOSITE_PRIMARY_KEY_TABLE_SQL), SQLITE_OK); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); + + RelationalStoreDelegate *delegate = nullptr; + DBStatus status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + + EXPECT_EQ(delegate->CreateDistributedTable("workers"), NOT_SUPPORT); + status = g_mgr.CloseStore(delegate); + EXPECT_EQ(status, OK); +} + +namespace { +void TableModifyTest(const std::string &modifySql, DBStatus expect) +{ + /** + * @tc.steps:step1. Prepare db file + * @tc.expected: step1. Return OK. + */ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + + RelationalTestUtils::CreateDeviceTable(db, "sync_data", "DEVICE_A"); + RelationalTestUtils::CreateDeviceTable(db, "sync_data", "DEVICE_B"); + RelationalTestUtils::CreateDeviceTable(db, "sync_data", "DEVICE_C"); + + /** + * @tc.steps:step2. Open store + * @tc.expected: step2. return OK + */ + RelationalStoreDelegate *delegate = nullptr; + DBStatus status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + + /** + * @tc.steps:step3. Create distributed table + * @tc.expected: step3. Create distributed table OK. + */ + EXPECT_EQ(delegate->CreateDistributedTable("sync_data"), OK); + + /** + * @tc.steps:step4. Upgrade table with modifySql + * @tc.expected: step4. return OK + */ + EXPECT_EQ(RelationalTestUtils::ExecSql(db, modifySql), SQLITE_OK); + + /** + * @tc.steps:step5. Create distributed table again + * @tc.expected: step5. Create distributed table return expect. + */ + EXPECT_EQ(delegate->CreateDistributedTable("sync_data"), expect); + + /** + * @tc.steps:step6. Close store + * @tc.expected: step6 Return OK. + */ + status = g_mgr.CloseStore(delegate); + EXPECT_EQ(status, OK); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); +} +} + +/** + * @tc.name: RelationalTableModifyTest001 + * @tc.desc: Test modify distributed table with compatible upgrade + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalTableModifyTest001, TestSize.Level1) +{ + TableModifyTest("ALTER TABLE sync_data ADD COLUMN add_field INTEGER NOT NULL DEFAULT 123;", OK); +} + +/** + * @tc.name: RelationalTableModifyTest002 + * @tc.desc: Test modify distributed table with incompatible upgrade + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalTableModifyTest002, TestSize.Level1) +{ + TableModifyTest("ALTER TABLE sync_data ADD COLUMN add_field INTEGER NOT NULL;", SCHEMA_MISMATCH); +} + +/** + * @tc.name: RelationalTableModifyTest003 + * @tc.desc: Test modify distributed table with incompatible upgrade + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalTableModifyTest003, TestSize.Level1) +{ + TableModifyTest("ALTER TABLE sync_data DROP COLUMN w_timestamp;", SCHEMA_MISMATCH); +} + +/** + * @tc.name: RelationalTableModifyTest004 + * @tc.desc: Test upgrade distributed table with device table exists + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalTableModifyTest004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare db file + * @tc.expected: step1. Return OK. + */ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + RelationalTestUtils::CreateDeviceTable(db, "sync_data", "DEVICE_A"); + RelationalTestUtils::CreateDeviceTable(db, "sync_data", "DEVICE_B"); + RelationalTestUtils::CreateDeviceTable(db, "sync_data", "DEVICE_C"); + + /** + * @tc.steps:step2. Open store + * @tc.expected: step2. return OK + */ + RelationalStoreDelegate *delegate = nullptr; + DBStatus status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + + /** + * @tc.steps:step3. Create distributed table + * @tc.expected: step3. Create distributed table OK. + */ + EXPECT_EQ(delegate->CreateDistributedTable("sync_data"), OK); + + /** + * @tc.steps:step4. Upgrade table + * @tc.expected: step4. return OK + */ + std::string modifySql = "ALTER TABLE sync_data ADD COLUMN add_field INTEGER;"; + EXPECT_EQ(RelationalTestUtils::ExecSql(db, modifySql), SQLITE_OK); + std::string indexSql = "CREATE INDEX add_index ON sync_data (add_field);"; + EXPECT_EQ(RelationalTestUtils::ExecSql(db, indexSql), SQLITE_OK); + std::string deleteIndexSql = "DROP INDEX IF EXISTS key_index"; + EXPECT_EQ(RelationalTestUtils::ExecSql(db, deleteIndexSql), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, INSERT_SYNC_DATA_SQL), SQLITE_OK); + + /** + * @tc.steps:step5. Create distributed table again + * @tc.expected: step5. Create distributed table return expect. + */ + EXPECT_EQ(delegate->CreateDistributedTable("sync_data"), OK); + + /** + * @tc.steps:step6. Close store + * @tc.expected: step6 Return OK. + */ + status = g_mgr.CloseStore(delegate); + EXPECT_EQ(status, OK); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); +} + +/** + * @tc.name: RelationalTableModifyTest005 + * @tc.desc: Test modify distributed table with compatible upgrade + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalTableModifyTest005, TestSize.Level1) +{ + TableModifyTest("ALTER TABLE sync_data ADD COLUMN add_field STRING NOT NULL DEFAULT 'asdf';", OK); +} + +/** + * @tc.name: RelationalRemoveDeviceDataTest001 + * @tc.desc: Test remove device data + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalRemoveDeviceDataTest001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare db file + * @tc.expected: step1. Return OK. + */ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + RelationalTestUtils::CreateDeviceTable(db, "sync_data", "DEVICE_A"); + RelationalTestUtils::CreateDeviceTable(db, "sync_data", "DEVICE_B"); + RelationalTestUtils::CreateDeviceTable(db, "sync_data", "DEVICE_C"); + + /** + * @tc.steps:step2. Open store + * @tc.expected: step2. return OK + */ + RelationalStoreDelegate *delegate = nullptr; + DBStatus status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + + /** + * @tc.steps:step3. Remove device data + * @tc.expected: step3. ok + */ + EXPECT_EQ(delegate->RemoveDeviceData("DEVICE_A"), OK); + EXPECT_EQ(delegate->RemoveDeviceData("DEVICE_B"), OK); + EXPECT_EQ(delegate->RemoveDeviceData("DEVICE_C", "sync_data"), OK); + + /** + * @tc.steps:step4. Remove device data with invalid args + * @tc.expected: step4. invalid + */ + EXPECT_EQ(delegate->RemoveDeviceData(""), INVALID_ARGS); + EXPECT_EQ(delegate->RemoveDeviceData("DEVICE_A", "Handle-J@^."), INVALID_ARGS); + + /** + * @tc.steps:step5. Close store + * @tc.expected: step5 Return OK. + */ + status = g_mgr.CloseStore(delegate); + EXPECT_EQ(status, OK); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); +} + +/** + * @tc.name: RelationalOpenStorePathCheckTest001 + * @tc.desc: Test open store with same label but different path. + * @tc.type: FUNC + * @tc.require: AR000GK58F + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalOpenStorePathCheckTest001, TestSize.Level1) +{ + std::string dir1 = g_dbDir + "dbDir1"; + EXPECT_EQ(OS::MakeDBDirectory(dir1), E_OK); + sqlite3 *db1 = RelationalTestUtils::CreateDataBase(dir1 + STORE_ID + DB_SUFFIX); + ASSERT_NE(db1, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db1, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db1, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + EXPECT_EQ(sqlite3_close_v2(db1), SQLITE_OK); + + std::string dir2 = g_dbDir + "dbDir2"; + EXPECT_EQ(OS::MakeDBDirectory(dir2), E_OK); + sqlite3 *db2 = RelationalTestUtils::CreateDataBase(dir2 + STORE_ID + DB_SUFFIX); + ASSERT_NE(db2, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db2, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db2, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + EXPECT_EQ(sqlite3_close_v2(db2), SQLITE_OK); + + DBStatus status = OK; + RelationalStoreDelegate *delegate1 = nullptr; + status = g_mgr.OpenStore(dir1 + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate1); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate1, nullptr); + + RelationalStoreDelegate *delegate2 = nullptr; + status = g_mgr.OpenStore(dir2 + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate2); + EXPECT_EQ(status, INVALID_ARGS); + ASSERT_EQ(delegate2, nullptr); + + status = g_mgr.CloseStore(delegate1); + EXPECT_EQ(status, OK); + + status = g_mgr.CloseStore(delegate2); + EXPECT_EQ(status, INVALID_ARGS); +} + +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalOpenStorePressureTest001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare db file + * @tc.expected: step1. Return OK. + */ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); + + DBStatus status = OK; + for (int i = 0; i < 1000; i++) { + RelationalStoreDelegate *delegate = nullptr; + status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + + status = g_mgr.CloseStore(delegate); + EXPECT_EQ(status, OK); + delegate = nullptr; + } +} + +HWTEST_F(DistributedDBInterfacesRelationalTest, RelationalOpenStorePressureTest002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Prepare db file + * @tc.expected: step1. Return OK. + */ + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_dbDir + STORE_ID + DB_SUFFIX); + ASSERT_NE(db, nullptr); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, "PRAGMA journal_mode=WAL;"), SQLITE_OK); + EXPECT_EQ(RelationalTestUtils::ExecSql(db, NORMAL_CREATE_TABLE_SQL), SQLITE_OK); + EXPECT_EQ(sqlite3_close_v2(db), SQLITE_OK); + + std::queue delegateQueue; + std::mutex queueLock; + std::random_device rd; + default_random_engine e(rd()); + uniform_int_distribution u(0, 9); + + std::thread openStoreThread([&, this]() { + for (int i = 0; i < 1000; i++) { + LOGD("++++> open store delegate: %d", i); + RelationalStoreDelegate *delegate = nullptr; + DBStatus status = g_mgr.OpenStore(g_dbDir + STORE_ID + DB_SUFFIX, STORE_ID, {}, delegate); + EXPECT_EQ(status, OK); + ASSERT_NE(delegate, nullptr); + { + std::lock_guard lock(queueLock); + delegateQueue.push(delegate); + } + LOGD("++++< open store delegate: %d", i); + } + }); + + int cnt = 0; + while (cnt < 1000) { + RelationalStoreDelegate *delegate = nullptr; + { + std::lock_guard lock(queueLock); + if (delegateQueue.empty()) { + std::this_thread::sleep_for(std::chrono::microseconds(100)); + continue; + } + delegate = delegateQueue.front(); + delegateQueue.pop(); + } + LOGD("++++> close store delegate: %d", cnt); + DBStatus status = g_mgr.CloseStore(delegate); + LOGD("++++< close store delegate: %d", cnt); + EXPECT_EQ(status, OK); + delegate = nullptr; + cnt++; + std::this_thread::sleep_for(std::chrono::microseconds(100 * u(e))); + } + openStoreThread.join(); +} diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_resultset_performance.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_resultset_performance.cpp new file mode 100644 index 00000000..0ded0a66 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_resultset_performance.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_tools_unit_test.h" +#include "kv_store_delegate_manager.h" +#include "kv_store_nb_delegate.h" +#include "log_print.h" +#include "store_types.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + // define some variables to init a KvStoreDelegateManager object. + KvStoreDelegateManager g_mgr("app0", "user0"); + string g_testDir; + KvStoreConfig g_config; + Key g_keyPrefix = {'A', 'B', 'C'}; + + const int BASE_NUMBER = 100000; + const int INSERT_NUMBER = 100; + const int ENTRY_VALUE_SIZE = 3000; + const int BATCH_ENTRY_NUMBER = 100; + + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + + void KvStoreNbDelegateCallback(DBStatus statusSrc, KvStoreNbDelegate* kvStoreSrc, + DBStatus* statusDst, KvStoreNbDelegate** kvStoreDst) + { + *statusDst = statusSrc; + *kvStoreDst = kvStoreSrc; + } + + // the type of g_kvNbDelegateCallback is function + auto g_kvNbDelegateCallback = bind(&KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, &g_kvDelegateStatus, &g_kvNbDelegatePtr); + + void InitResultSet() + { + Key testKey; + Value testValue; + for (int i = BASE_NUMBER; i < BASE_NUMBER + INSERT_NUMBER; i++) { + testKey.clear(); + testValue.clear(); + testKey = g_keyPrefix; + std::string strIndex = std::to_string(i); + testKey.insert(testKey.end(), strIndex.begin(), strIndex.end()); + + DistributedDBToolsUnitTest::GetRandomKeyValue(testValue, ENTRY_VALUE_SIZE); + if ((i % BATCH_ENTRY_NUMBER) == 0) { + g_kvNbDelegatePtr->StartTransaction(); + } + EXPECT_EQ(g_kvNbDelegatePtr->Put(testKey, testValue), OK); + if (((i + 1) % BATCH_ENTRY_NUMBER) == 0) { + g_kvNbDelegatePtr->Commit(); + } + } + + std::this_thread::sleep_for(std::chrono::seconds(2)); // sleep 2 s for the cache. + } +} +class DistributedDBInterfacesNBResultsetPerfTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesNBResultsetPerfTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesNBResultsetPerfTest::TearDownTestCase(void) +{ +} + +void DistributedDBInterfacesNBResultsetPerfTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_kvDelegateStatus = INVALID_ARGS; + g_kvNbDelegatePtr = nullptr; + g_kvDelegatePtr = nullptr; +} + +void DistributedDBInterfacesNBResultsetPerfTest::TearDown(void) +{ + if (g_kvDelegatePtr != nullptr) { + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + g_kvNbDelegatePtr = nullptr; + } +} + +/** + * @tc.name: ResultSetPerfTest001 + * @tc.desc: Test the NbDelegate for result set function. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBInterfacesNBResultsetPerfTest, ResultSetPerfTest001, TestSize.Level4) +{ + /** + * @tc.steps: step1. initialize result set. + * @tc.expected: step1. Success. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore("resultset_perf_test", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + InitResultSet(); + + /** + * @tc.steps: step2. get entries using result set. + * @tc.expected: step2. Success. + */ + LOGI("######## Before get resultset"); + KvStoreResultSet *readResultSet = nullptr; + Key keyGet = g_keyPrefix; + keyGet.push_back('1'); + + int offset = 40; // offset 40 + LOGI("######## Query resultSet"); + Query query = Query::Select().PrefixKey(keyGet).Limit(50, offset); // limit 50 + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(query, readResultSet), OK); + ASSERT_TRUE(readResultSet != nullptr); + LOGI("######## After get resultset"); + int totalCount = readResultSet->GetCount(); + EXPECT_EQ(totalCount, 50); // limit 50 + LOGI("######## After get count:%d", totalCount); + + readResultSet->MoveToPosition(0); + LOGI("######## After move to next"); + EXPECT_EQ(g_kvNbDelegatePtr->CloseResultSet(readResultSet), OK); + EXPECT_TRUE(readResultSet == nullptr); + + std::this_thread::sleep_for(std::chrono::seconds(5)); // sleep 5 s + LOGI("######## Plain resultSet"); + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(keyGet, readResultSet), OK); + ASSERT_TRUE(readResultSet != nullptr); + LOGI("######## After get resultset"); + totalCount = readResultSet->GetCount(); + EXPECT_EQ(totalCount, INSERT_NUMBER); + LOGI("######## After get count:%d", totalCount); + + readResultSet->MoveToPosition(offset); + LOGI("######## After move to next"); + EXPECT_EQ(g_kvNbDelegatePtr->CloseResultSet(readResultSet), OK); + EXPECT_TRUE(readResultSet == nullptr); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore("resultset_perf_test"), OK); + g_kvNbDelegatePtr = nullptr; +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_schema_database_upgrade_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_schema_database_upgrade_test.cpp new file mode 100644 index 00000000..c0dd0630 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_schema_database_upgrade_test.cpp @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_JSON +#include +#include "distributeddb_tools_unit_test.h" +#include "kv_store_delegate_manager.h" +#include "schema_constant.h" +#include "schema_object.h" +#include "schema_utils.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { + const std::string APP_ID = "SCHEMA"; + const std::string USER_ID = "UPGRADE"; + std::string g_testDir; + KvStoreDelegateManager g_manager(APP_ID, USER_ID); + + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvDelegatePtr = nullptr; + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + std::placeholders::_1, std::placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + + std::string g_baseSchema; + DBStatus g_expectError = SCHEMA_VIOLATE_VALUE; + + std::string StringEraser(const std::string &oriString, const std::string &toErase) + { + std::string resStr = oriString; + auto iter = std::search(resStr.begin(), resStr.end(), toErase.begin(), toErase.end()); + resStr.erase(iter, iter + toErase.size()); + return resStr; + } + std::string StringReplacer(const std::string &oriString, const std::string &toErase, const std::string &toRepalce) + { + std::string resStr = oriString; + auto iter = std::search(resStr.begin(), resStr.end(), toErase.begin(), toErase.end()); + resStr.replace(iter, iter + toErase.size(), toRepalce); + return resStr; + } + + const std::string SCHEMA_INC_FIELD = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_DEFINE\":{" + "\"field_1\":\"LONG, NOT NULL, DEFAULT 100\"," + "\"field_2\":{" + "\"field_3\":\"STRING, DEFAULT 'OpenHarmony'\"," + "\"field_4\":\"INTEGER\"" + "}" + "}," + "\"SCHEMA_INDEXES\":[\"field_1\", [\"field_2.field_3\", \"field_2.field_4\"]]}"; + const std::string SCHEMA_BASE = StringReplacer(StringEraser(SCHEMA_INC_FIELD, ",\"field_4\":\"INTEGER\""), + "[\"field_2.field_3\", \"field_2.field_4\"]", "\"field_2.field_3\""); + const std::string SCHEMA_INC_FIELD_NOTNULL = StringReplacer(SCHEMA_INC_FIELD, "\"INTEGER\"", + "\"INTEGER, NOT NULL\""); + const std::string SCHEMA_INC_FIELD_DEFAULT = StringReplacer(SCHEMA_INC_FIELD, "\"INTEGER\"", + "\"INTEGER, DEFAULT 88\""); + const std::string SCHEMA_INC_FIELD_NOTNULL_DEFAULT = StringReplacer(SCHEMA_INC_FIELD, "\"INTEGER\"", + "\"INTEGER, NOT NULL, DEFAULT 88\""); + + const std::string VALUE_BASE_LACK = "{\"field_1\":LONG_VAL}"; + const std::string VALUE_BASE = "{\"field_1\":LONG_VAL,\"field_2\":{\"field_3\":STR_VAL}}"; + const std::string VALUE_FIELD_FULL = "{\"field_1\":LONG_VAL,\"field_2\":{\"field_3\":STR_VAL,\"field_4\":INT_VAL}}"; + + std::string SchemaSwitchMode(const std::string &oriSchemaStr) + { + std::string resStr = oriSchemaStr; + auto iterStrict = std::search(resStr.begin(), resStr.end(), SchemaConstant::KEYWORD_MODE_STRICT.begin(), + SchemaConstant::KEYWORD_MODE_STRICT.end()); + auto iterCompatible = std::search(resStr.begin(), resStr.end(), SchemaConstant::KEYWORD_MODE_COMPATIBLE.begin(), + SchemaConstant::KEYWORD_MODE_COMPATIBLE.end()); + if (iterStrict != resStr.end()) { + resStr.replace(iterStrict, iterStrict + SchemaConstant::KEYWORD_MODE_STRICT.size(), + SchemaConstant::KEYWORD_MODE_COMPATIBLE.begin(), SchemaConstant::KEYWORD_MODE_COMPATIBLE.end()); + return resStr; + } + if (iterCompatible != resStr.end()) { + resStr.replace(iterCompatible, iterCompatible + SchemaConstant::KEYWORD_MODE_COMPATIBLE.size(), + SchemaConstant::KEYWORD_MODE_STRICT.begin(), SchemaConstant::KEYWORD_MODE_STRICT.end()); + return resStr; + } + return oriSchemaStr; + } + bool SchemaChecker(const std::string schema) + { + SchemaObject schemaObj; + return (schemaObj.ParseFromSchemaString(schema) == E_OK); + } + std::vector ToVec(const std::string &inStr) + { + std::vector outVec(inStr.begin(), inStr.end()); + return outVec; + } + std::string ToStr(const std::vector &inVec) + { + std::string outStr(inVec.begin(), inVec.end()); + return outStr; + } + + const std::map> VALUE_MAP { + {"LACK", ToVec(StringReplacer(VALUE_BASE_LACK, "LONG_VAL", "1"))}, + {"BASE", ToVec(StringReplacer(StringReplacer(VALUE_BASE, "LONG_VAL", "2"), "STR_VAL", "\"OS\""))}, + {"FULL", ToVec(StringReplacer(StringReplacer(StringReplacer(VALUE_FIELD_FULL, "LONG_VAL", "3"), "STR_VAL", + "\"TEST\""), "INT_VAL", "33"))}, + {"BASE_WRONG_TYPE", ToVec(StringReplacer(StringReplacer(VALUE_BASE, "LONG_VAL", "2"), "STR_VAL", "10086"))}, + {"FULL_NULL", ToVec(StringReplacer(StringReplacer(StringReplacer(VALUE_FIELD_FULL, "LONG_VAL", "3"), + "STR_VAL", "\"TEST\""), "INT_VAL", "null"))}, + {"FULL_WRONG_TYPE", ToVec(StringReplacer(StringReplacer(StringReplacer(VALUE_FIELD_FULL, "LONG_VAL", "3"), + "STR_VAL", "\"TEST\""), "INT_VAL", "\"UT\""))}, + }; + + // Key begin from "KEY_1" + void InsertPresetEntry(KvStoreNbDelegate &delegate, const std::vector &selection) + { + int count = 0; + for (const auto &eachSel : selection) { + ASSERT_NE(VALUE_MAP.count(eachSel), 0ul); + DBStatus ret = delegate.Put(ToVec(std::string("KEY_") + std::to_string(++count)), VALUE_MAP.at(eachSel)); + ASSERT_EQ(ret, OK); + } + } +} + +class DistributedDBInterfacesSchemaDatabaseUpgradeTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesSchemaDatabaseUpgradeTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + KvStoreConfig config{g_testDir}; + g_manager.SetKvStoreConfig(config); + ASSERT_EQ(SchemaChecker(SCHEMA_BASE), true); + ASSERT_EQ(SchemaChecker(SCHEMA_INC_FIELD), true); + ASSERT_EQ(SchemaChecker(SCHEMA_INC_FIELD_NOTNULL), true); + ASSERT_EQ(SchemaChecker(SCHEMA_INC_FIELD_DEFAULT), true); + ASSERT_EQ(SchemaChecker(SCHEMA_INC_FIELD_NOTNULL_DEFAULT), true); +} + +void DistributedDBInterfacesSchemaDatabaseUpgradeTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("[TestSchemaUpgrade] Remove test directory error."); + } +} + +void DistributedDBInterfacesSchemaDatabaseUpgradeTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_kvDelegateStatus = INVALID_ARGS; + g_kvDelegatePtr = nullptr; +} + +void DistributedDBInterfacesSchemaDatabaseUpgradeTest::TearDown(void) +{ + if (g_kvDelegatePtr != nullptr) { + ASSERT_EQ(g_manager.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + } +} + +/** + * @tc.name: UpgradeFromKv001 + * @tc.desc: Schema database upgrade from kv database, exist value match compatible schema(mismatch strict schema) + * @tc.type: FUNC + * @tc.require: AR000F3OPD + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBInterfacesSchemaDatabaseUpgradeTest, UpgradeFromKv001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Prepare kv database with value match compatible schema(mismatch strict schema) then close + * @tc.expected: step1. E_OK + */ + std::string storeId = "UpgradeFromKv001"; + KvStoreNbDelegate::Option option; + g_manager.GetKvStore(storeId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + ASSERT_EQ(g_kvDelegateStatus, OK); + + InsertPresetEntry(*g_kvDelegatePtr, std::vector{"LACK", "BASE", "LACK", "BASE", "FULL"}); + DBStatus ret = g_kvDelegatePtr->Delete(ToVec("KEY_4")); + ASSERT_EQ(ret, OK); + ASSERT_EQ(g_manager.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + + /** + * @tc.steps: step2. Upgrade to schema(strict) database + * @tc.expected: step2. SCHEMA_VIOLATE_VALUE + */ + option.schema = SchemaSwitchMode(SCHEMA_BASE); + g_manager.GetKvStore(storeId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + ASSERT_EQ(g_kvDelegateStatus, SCHEMA_VIOLATE_VALUE); + + /** + * @tc.steps: step3. Upgrade to schema(compatible) database + * @tc.expected: step3. E_OK + */ + option.schema = SCHEMA_BASE; + g_manager.GetKvStore(storeId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + ASSERT_EQ(g_kvDelegateStatus, OK); + + /** + * @tc.steps: step4. Query field_2.field_3 equal OpenHarmony + * @tc.expected: step4. E_OK, KEY_1 + */ + Query query = Query::Select().EqualTo("field_2.field_3", "OpenHarmony"); + std::vector entries; + EXPECT_EQ(g_kvDelegatePtr->GetEntries(query, entries), OK); + ASSERT_EQ(entries.size(), 2ul); + EXPECT_EQ(ToStr(entries[0].key), std::string("KEY_1")); + EXPECT_EQ(ToStr(entries[1].key), std::string("KEY_3")); + std::string valStr = ToStr(entries[0].value); + std::string defaultVal = "OpenHarmony"; + auto iter = std::search(valStr.begin(), valStr.end(), defaultVal.begin(), defaultVal.end()); + EXPECT_TRUE(iter != valStr.end()); +} + +/** + * @tc.name: UpgradeFromKv002 + * @tc.desc: Schema database upgrade from kv database, exist value mismatch compatible schema + * @tc.type: FUNC + * @tc.require: AR000F3OPD + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBInterfacesSchemaDatabaseUpgradeTest, UpgradeFromKv002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Prepare kv database with value mismatch compatible schema then close + * @tc.expected: step1. E_OK + */ + std::string storeId = "UpgradeFromKv002"; + KvStoreNbDelegate::Option option; + g_manager.GetKvStore(storeId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + ASSERT_EQ(g_kvDelegateStatus, OK); + + InsertPresetEntry(*g_kvDelegatePtr, std::vector{"BASE_WRONG_TYPE"}); + ASSERT_EQ(g_manager.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + + /** + * @tc.steps: step2. Upgrade to schema(compatible) database + * @tc.expected: step2. SCHEMA_VIOLATE_VALUE + */ + option.schema = SCHEMA_BASE; + g_manager.GetKvStore(storeId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + ASSERT_EQ(g_kvDelegateStatus, SCHEMA_VIOLATE_VALUE); +} + +namespace { +void TestUpgradeFromSchema(const std::string &storeId, const std::vector &selection, + const std::string &newSchema, bool expectMatch, uint32_t expectCount) +{ + LOGI("[TestUpgradeFromSchema] StoreId=%s", storeId.c_str()); + /** + * @tc.steps: step1. Prepare kv database with value then close + * @tc.expected: step1. E_OK + */ + KvStoreNbDelegate::Option option; + option.schema = g_baseSchema; + g_manager.GetKvStore(storeId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + ASSERT_EQ(g_kvDelegateStatus, OK); + + InsertPresetEntry(*g_kvDelegatePtr, selection); + ASSERT_EQ(g_manager.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + + /** + * @tc.steps: step2. Upgrade to schema database + * @tc.expected: step2. OK or SCHEMA_VIOLATE_VALUE + */ + option.schema = newSchema; + g_manager.GetKvStore(storeId, option, g_kvDelegateCallback); + if (expectMatch) { + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + ASSERT_EQ(g_kvDelegateStatus, OK); + } else { + ASSERT_TRUE(g_kvDelegatePtr == nullptr); + ASSERT_EQ(g_kvDelegateStatus, g_expectError); + } + + /** + * @tc.steps: step3. Query field_2.field_3 + * @tc.expected: step3. E_OK + */ + if (expectMatch) { + Query query = Query::Select().EqualTo("field_2.field_4", 88); // 88 is the default value in the testcase. + std::vector entries; + if (expectCount == 0) { + EXPECT_EQ(g_kvDelegatePtr->GetEntries(query, entries), NOT_FOUND); + } else { + ASSERT_EQ(g_kvDelegatePtr->GetEntries(query, entries), OK); + EXPECT_EQ(entries.size(), expectCount); + } + ASSERT_EQ(g_manager.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + } +} +} + +/** + * @tc.name: UpgradeFromSchema001 + * @tc.desc: Schema database upgrade from kv database, exist value match new schema + * @tc.type: FUNC + * @tc.require: AR000F3OPD + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBInterfacesSchemaDatabaseUpgradeTest, UpgradeFromSchema001, TestSize.Level1) +{ + g_baseSchema = SCHEMA_BASE; + g_expectError = SCHEMA_VIOLATE_VALUE; + TestUpgradeFromSchema("UpgradeFromSchema001_1", std::vector{"LACK", "BASE", "FULL", "FULL_NULL"}, + SCHEMA_INC_FIELD, true, 0); + TestUpgradeFromSchema("UpgradeFromSchema001_2", std::vector{"LACK", "BASE", "FULL", "FULL_NULL"}, + SCHEMA_INC_FIELD_DEFAULT, true, 2); + TestUpgradeFromSchema("UpgradeFromSchema001_3", std::vector{"LACK", "BASE", "FULL"}, + SCHEMA_INC_FIELD_NOTNULL_DEFAULT, true, 2); +} + +/** + * @tc.name: UpgradeFromSchema002 + * @tc.desc: Schema database upgrade from kv database, exist value mismatch new schema + * @tc.type: FUNC + * @tc.require: AR000F3OPD + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBInterfacesSchemaDatabaseUpgradeTest, UpgradeFromSchema002, TestSize.Level1) +{ + g_baseSchema = SCHEMA_BASE; + g_expectError = SCHEMA_VIOLATE_VALUE; + TestUpgradeFromSchema("UpgradeFromSchema002_1", std::vector{"LACK", "BASE", "FULL", "FULL_WRONG_TYPE"}, + SCHEMA_INC_FIELD, false, 0); + TestUpgradeFromSchema("UpgradeFromSchema002_2", std::vector{"LACK", "BASE", "FULL", "FULL_WRONG_TYPE"}, + SCHEMA_INC_FIELD_DEFAULT, false, 0); + TestUpgradeFromSchema("UpgradeFromSchema002_3", std::vector{"LACK", "BASE", "FULL", "FULL_NULL"}, + SCHEMA_INC_FIELD_NOTNULL_DEFAULT, false, 0); +} + +/** + * @tc.name: UpgradeFromSchema003 + * @tc.desc: Schema database upgrade from kv database, new schema incompatible with old schema + * @tc.type: FUNC + * @tc.require: AR000F3OPD + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBInterfacesSchemaDatabaseUpgradeTest, UpgradeFromSchema003, TestSize.Level1) +{ + // Compatible schema can increase field, but must not be null without default. + g_baseSchema = SCHEMA_BASE; + g_expectError = SCHEMA_MISMATCH; + TestUpgradeFromSchema("UpgradeFromSchema003_1", std::vector{"LACK", "BASE", "FULL", "FULL_NULL"}, + SCHEMA_INC_FIELD_NOTNULL, false, 0); + // Strict schema can not incrase field + g_baseSchema = SchemaSwitchMode(SCHEMA_BASE); + TestUpgradeFromSchema("UpgradeFromSchema003_2", std::vector{"LACK", "BASE"}, + SchemaSwitchMode(SCHEMA_INC_FIELD), false, 0); +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_single_version_result_set_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_single_version_result_set_test.cpp new file mode 100644 index 00000000..a4bd3364 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_single_version_result_set_test.cpp @@ -0,0 +1,534 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_constant.h" +#include "db_types.h" +#include "distributeddb_data_generate_unit_test.h" +#include "ikvdb_raw_cursor.h" +#include "kv_store_nb_delegate_impl.h" +#include "kvdb_manager.h" +#include "platform_specific.h" +#include "result_entries_window.h" +#include "sqlite_single_ver_forward_cursor.h" +#include "sqlite_single_ver_natural_store.h" +#include "sqlite_single_ver_natural_store_connection.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + string g_identifier; + IKvDBRawCursor *g_rawCursor = nullptr; + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + DBStatus g_kvDelegateStatus = INVALID_ARGS; + SQLiteSingleVerNaturalStore *g_store = nullptr; + DistributedDB::SQLiteSingleVerNaturalStoreConnection *g_connection = nullptr; + const string STORE_ID = STORE_ID_SYNC; + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); + const int TIME_LAG = 100; + const int INITIAL_POSITION = 0; + const int SECOND_POSITION = 1; + const int TOTAL_COUNT = 3; + const Key KEY_PREFIX = {'K'}; + const Key LOCAL_KEY_1 = {'K', '1'}; + const Key LOCAL_KEY_2 = {'K', '2'}; + const Key LOCAL_KEY_3 = {'K', '3'}; + const Key LOCAL_KEY_4 = {'K', '4'}; +} + +class DistributedDBInterfacesSingleVersionResultSetTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesSingleVersionResultSetTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + std::string origIdentifier = USER_ID + "-" + APP_ID + "-" + STORE_ID; + std::string identifier = DBCommon::TransferHashString(origIdentifier); + g_identifier = DBCommon::TransferStringToHex(identifier); + string dir = g_testDir + g_identifier + "/" + DBConstant::SINGLE_SUB_DIR; + DIR *dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } +} + +void DistributedDBInterfacesSingleVersionResultSetTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir + STORE_ID + "/" + DBConstant::SINGLE_SUB_DIR) != 0) { + LOGE("rm test db files error!"); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); +} + +void DistributedDBInterfacesSingleVersionResultSetTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + KvStoreNbDelegate::Option delegateOption = {true}; + g_mgr.GetKvStore(STORE_ID, delegateOption, g_kvNbDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + + KvDBProperties property; + property.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + property.SetStringProp(KvDBProperties::STORE_ID, STORE_ID); + property.SetStringProp(KvDBProperties::IDENTIFIER_DIR, g_identifier); + property.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + + g_store = new (std::nothrow) SQLiteSingleVerNaturalStore; + ASSERT_NE(g_store, nullptr); + ASSERT_EQ(g_store->Open(property), E_OK); + + int errCode = E_OK; + g_connection = static_cast(g_store->GetDBConnection(errCode)); + ASSERT_NE(g_connection, nullptr); + g_store->DecObjRef(g_store); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps:step1. Put 3 data items. + * @tc.expected: step1. + */ + IOption option; + option.dataType = IOption::SYNC_DATA; + g_connection->Clear(option); + ASSERT_EQ(g_connection->Put(option, LOCAL_KEY_1, VALUE_1), OK); + ASSERT_EQ(g_connection->Put(option, LOCAL_KEY_2, VALUE_2), OK); + ASSERT_EQ(g_connection->Put(option, LOCAL_KEY_3, VALUE_3), OK); + + EXPECT_EQ(errCode, E_OK); + g_rawCursor = new (std::nothrow) SQLiteSingleVerForwardCursor(g_store, KEY_PREFIX); + ASSERT_NE(g_rawCursor, nullptr); +} + +void DistributedDBInterfacesSingleVersionResultSetTest::TearDown(void) +{ + if (g_connection != nullptr) { + g_connection->Close(); + g_connection = nullptr; + } + + g_store = nullptr; + + if (g_kvNbDelegatePtr != nullptr) { + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore(STORE_ID) == OK); + } + + if (g_rawCursor != nullptr) { + delete g_rawCursor; + g_rawCursor = nullptr; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); +} + +/** + * @tc.name: SingleVersionResultSetTest001 + * @tc.desc: CursorWindow Class: Return error when the window size too large. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be INT_MAX, which is larger than the upper limit. + * @tc.expected: step1. Expect return -E_INVALID_ARGS. + */ + ResultEntriesWindow resultWindow; + double scale = 1; + int64_t windoweSize = 0x100000000L; // 4G + EXPECT_EQ(resultWindow.Init(g_rawCursor, windoweSize, scale), -E_INVALID_ARGS); +} + +/** + * @tc.name: SingleVersionResultSetTest002 + * @tc.desc: CursorWindow Class: Return error when the window size is negative. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be -1. + * @tc.expected: step1. Expect return -E_INVALID_ARGS. + */ + ResultEntriesWindow resultWindow; + double scale = 1; + int windowSize = -1; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), -E_INVALID_ARGS); +} + +/** + * @tc.name: SingleVersionResultSetTest003 + * @tc.desc: CursorWindow Class: Return error when the window size is zero. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 0. + * @tc.expected: step1. Expect return -E_INVALID_ARGS. + */ + ResultEntriesWindow resultWindow; + double scale = 1; + int windowSize = 0; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), -E_INVALID_ARGS); +} + +/** + * @tc.name: SingleVersionResultSetTest004 + * @tc.desc: CursorWindow Class: Return OK when the window size is positive. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 100, which is smaller than the upper limit. + * @tc.expected: step1. Expect return OK. + */ + ResultEntriesWindow resultWindow; + double scale = 1; + int windowSize = 100; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), E_OK); +} + +/** + * @tc.name: SingleVersionResultSetTest005 + * @tc.desc: CursorWindow Class: Return -E_INVALID_ARGS when the window scale is negative. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest005, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 100, and window scale to be negative (-1). + * @tc.expected: step1. Expect return -E_INVALID_ARGS. + */ + ResultEntriesWindow resultWindow; + double scale = -1; + int windowSize = 100; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), -E_INVALID_ARGS); +} + +/** + * @tc.name: SingleVersionResultSetTest006 + * @tc.desc: CursorWindow Class: Return -E_INVALID_ARGS when the window scale is larger than 1. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest006, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 100, and window scale to be 2. + * @tc.expected: step1. Expect return -E_INVALID_ARGS. + */ + ResultEntriesWindow resultWindow; + double scale = 2; + int windowSize = 100; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), -E_INVALID_ARGS); +} + +/** + * @tc.name: SingleVersionResultSetTest007 + * @tc.desc: CursorWindow Class: Return -E_INVALID_ARGS when the window scale 0. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest007, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 100, and window scale to be 0. + * @tc.expected: step1. Expect return -E_INVALID_ARGS. + */ + ResultEntriesWindow resultWindow; + double scale = 0; + int windowSize = 100; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), -E_INVALID_ARGS); +} + +/** + * @tc.name: SingleVersionResultSetTest008 + * @tc.desc: CursorWindow Class: Return OK when the window scale is between 0 and 1. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest008, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 100, and window scale to be 0.5. + * @tc.expected: step1. Expect return OK. + */ + ResultEntriesWindow resultWindow; + double scale = 0.5; + int windowSize = 100; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), E_OK); +} + +/** + * @tc.name: SingleVersionResultSetTest009 + * @tc.desc: CursorWindow Class: Return -E_INVALID_ARGS when the g_rawCursor is nulSSSlptr. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest009, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 100, and window scale to be 1 and resultWindow be null pointer. + * @tc.expected: step1. Expect return -E_INVALID_ARGS. + */ + IKvDBRawCursor *rawCursor = nullptr; + ResultEntriesWindow resultWindow; + double scale = 1; + int windowSize = 100; + EXPECT_EQ(resultWindow.Init(rawCursor, windowSize, scale), -E_INVALID_ARGS); +} + +/** + * @tc.name: SingleVersionResultSetTest010 + * @tc.desc: CursorWindow Class: Check if get total count is feasible. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest010, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 100, and window scale to be 1 and resultWindow be null pointer. + * @tc.expected: step1. Expect return OK. + */ + ResultEntriesWindow resultWindow; + double scale = 1; + int windowSize = 100; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), E_OK); + + /** + * @tc.steps:step2. Get the total count. + * @tc.expected: step2. Expect return 3. + */ + EXPECT_EQ(resultWindow.GetTotalCount(), TOTAL_COUNT); +} + +/** + * @tc.name: SingleVersionResultSetTest011 + * @tc.desc: CursorWindow Class: Check if get total count is feasible and the inserted items after + * creating ResultEntriesWindow have not been counted. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest011, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 100, and window scale to be 1 and resultWindow be null pointer. + * @tc.expected: step1. Expect return OK. + */ + ResultEntriesWindow resultWindow; + double scale = 1; + int windowSize = 100; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), E_OK); + + /** + * @tc.steps:step2. Get the total count. + * @tc.expected: step2. Expect return 3. + */ + EXPECT_EQ(resultWindow.GetTotalCount(), TOTAL_COUNT); + + /** + * @tc.steps:step3. Put one more item + * @tc.expected: step3. + */ + IOption option; + option.dataType = IOption::SYNC_DATA; + ASSERT_EQ(g_connection->Put(option, LOCAL_KEY_4, VALUE_4), OK); + + /** + * @tc.steps:step4. Get the total count. + * @tc.expected: step4. Expect return 3. + */ + EXPECT_EQ(resultWindow.GetTotalCount(), TOTAL_COUNT); +} + +/** + * @tc.name: SingleVersionResultSetTest012 + * @tc.desc: CursorWindow Class: Check if current position after initialization is at 0. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest012, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 100, and window scale to be 1 and resultWindow be null pointer. + * @tc.expected: step1. Expect return OK. + */ + ResultEntriesWindow resultWindow; + double scale = 1; + int windowSize = 100; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), E_OK); + + /** + * @tc.steps:step2. Get initial position. + * @tc.expected: step2. Expect return INITIAL_POSITION (which is 0). + */ + EXPECT_EQ(resultWindow.GetCurrentPosition(), INITIAL_POSITION); + + /** + * @tc.steps:step3. Get entry . + * @tc.expected: step3. Expect return E_OK. + */ + Entry entry; + EXPECT_EQ(resultWindow.GetEntry(entry), E_OK); + EXPECT_EQ(entry.key, LOCAL_KEY_1); + EXPECT_EQ(entry.value, VALUE_1); +} + +/** + * @tc.name: SingleVersionResultSetTest013 + * @tc.desc: CursorWindow Class: Check if current position after move is at the right place+. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest013, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 100, and window scale to be 1 and resultWindow be null pointer. + * @tc.expected: step1. Expect return OK. + */ + ResultEntriesWindow resultWindow; + double scale = 1; + int windowSize = 100; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), E_OK); + + /** + * @tc.steps:step2. move to second position. + * @tc.expected: step2. Expect return SECOND_POSITION (which is 2). + */ + EXPECT_EQ(resultWindow.MoveToPosition(SECOND_POSITION), true); + EXPECT_EQ(resultWindow.GetCurrentPosition(), SECOND_POSITION); + + /** + * @tc.steps:step3. Get entry . + * @tc.expected: step3. Expect return OK and entry corresponds to the right item. + */ + Entry entry; + EXPECT_EQ(resultWindow.GetEntry(entry), E_OK); + EXPECT_EQ(entry.key, LOCAL_KEY_2); + EXPECT_EQ(entry.value, VALUE_2); +} + +/** + * @tc.name: SingleVersionResultSetTest014 + * @tc.desc: CursorWindow Class: Move to negative position and the position bounces back to zero. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest014, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 100, and window scale to be 1 and resultWindow be null pointer. + * @tc.expected: step1. Expect return OK. + */ + ResultEntriesWindow resultWindow; + double scale = 1; + int windowSize = 100; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), E_OK); + + /** + * @tc.steps:step2. move to second position. + * @tc.expected: step2. Expect return false and initial position. + */ + int negativePosition = -2; + EXPECT_EQ(resultWindow.MoveToPosition(negativePosition), false); + EXPECT_EQ(resultWindow.GetCurrentPosition(), INITIAL_POSITION); + + /** + * @tc.steps:step3. Get entry . + * @tc.expected: step3. Expect return E_OK. + */ + Entry entry; + EXPECT_EQ(resultWindow.GetEntry(entry), E_OK); + EXPECT_EQ(entry.key, LOCAL_KEY_1); + EXPECT_EQ(entry.value, VALUE_1); +} + +/** + * @tc.name: SingleVersionResultSetTest015 + * @tc.desc: CursorWindow Class: Move to position larger than N and the position bounces back to original position. + * @tc.type: FUNC + * @tc.require: AR000D08KT + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBInterfacesSingleVersionResultSetTest, SingleVersionResultSetTest015, TestSize.Level1) +{ + /** + * @tc.steps:step1. Let the WindowSize be 100, and window scale to be 1 and resultWindow be null pointer. + * @tc.expected: step1. Expect return OK. + */ + ResultEntriesWindow resultWindow; + double scale = 1; + int windowSize = 100; + EXPECT_EQ(resultWindow.Init(g_rawCursor, windowSize, scale), E_OK); + + /** + * @tc.steps:step2. move to second position. + * @tc.expected: step2. Expect return false and move to total count. + */ + int largePosition = TOTAL_COUNT + 1; + EXPECT_EQ(resultWindow.MoveToPosition(largePosition), false); + EXPECT_EQ(resultWindow.GetCurrentPosition(), INITIAL_POSITION); + + /** + * @tc.steps:step3. Get entry . + * @tc.expected: step3. Expect return VALUE_1. + */ + Entry entry; + EXPECT_EQ(resultWindow.GetEntry(entry), E_OK); + EXPECT_EQ(entry.key, LOCAL_KEY_1); + EXPECT_EQ(entry.value, VALUE_1); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_space_management_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_space_management_test.cpp new file mode 100644 index 00000000..0a837ade --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_space_management_test.cpp @@ -0,0 +1,530 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_constant.h" +#include "db_common.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + // define some variables to init a KvStoreDelegateManager object. + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + string g_testDir; + KvStoreConfig g_config; + + // define the g_kvNbDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + KvStoreNbDelegate::Option g_nbOption; + + // the type of g_kvNbDelegateCallback is function + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); + + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + + std::string g_storeId; + std::string g_identifier; + vector g_singleVerFileNames; + vector g_commitLogFileNames; + vector g_metaStorageFileNames; + vector g_multiVerDataFileNames; + vector g_ValueStorageFileNames; + + void GetRealFileUrl() + { + std::string origIdentifier = USER_ID + "-" + APP_ID + "-" + g_storeId; + std::string hashIdentifier = DBCommon::TransferHashString(origIdentifier); + g_identifier = DBCommon::TransferStringToHex(hashIdentifier); + + g_singleVerFileNames = { + g_testDir + "/" + g_identifier + "/single_ver/main/gen_natural_store.db", + g_testDir + "/" + g_identifier + "/single_ver/main/gen_natural_store.db-shm", + g_testDir + "/" + g_identifier + "/single_ver/main/gen_natural_store.db-wal"}; + g_commitLogFileNames = { + g_testDir + "/" + g_identifier + "/multi_ver/commit_logs.db", + g_testDir + "/" + g_identifier + "/multi_ver/commit_logs.db-shm", + g_testDir + "/" + g_identifier + "/multi_ver/commit_logs.db-wal"}; + g_metaStorageFileNames = { + g_testDir + "/" + g_identifier + "/multi_ver/meta_storage.db", + g_testDir + "/" + g_identifier + "/multi_ver/meta_storage.db-shm", + g_testDir + "/" + g_identifier + "/multi_ver/meta_storage.db-wal"}; + g_multiVerDataFileNames = { + g_testDir + "/" + g_identifier + "/multi_ver/multi_ver_data.db", + g_testDir + "/" + g_identifier + "/multi_ver/multi_ver_data.db-shm", + g_testDir + "/" + g_identifier + "/multi_ver/multi_ver_data.db-wal"}; + g_ValueStorageFileNames = { + g_testDir + "/" + g_identifier + "/multi_ver/value_storage.db", + g_testDir + "/" + g_identifier + "/multi_ver/value_storage.db-shm", + g_testDir + "/" + g_identifier + "/multi_ver/value_storage.db-wal"}; + } + + vector GetMultiVerFilelist() + { + vector multiFileNames; + for (const auto &iter : g_commitLogFileNames) { + multiFileNames.push_back(iter); + } + for (const auto &iter : g_metaStorageFileNames) { + multiFileNames.push_back(iter); + } + for (const auto &iter : g_multiVerDataFileNames) { + multiFileNames.push_back(iter); + } + for (const auto &iter : g_ValueStorageFileNames) { + multiFileNames.push_back(iter); + } + return multiFileNames; + } +} + +class DistributedDBInterfacesSpaceManagementTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesSpaceManagementTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesSpaceManagementTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesSpaceManagementTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_kvDelegateStatus = INVALID_ARGS; + g_kvNbDelegatePtr = nullptr; + g_kvDelegatePtr = nullptr; +} + +void DistributedDBInterfacesSpaceManagementTest::TearDown(void) {} + +// use another way calculate small file size(2G) +static uint64_t CheckRealFileSize(const vector &fileNames) +{ + uint64_t size = 0; + for (const auto &file : fileNames) { + FILE *fileHandle = nullptr; + fileHandle = fopen(file.c_str(), "rb"); + if (fileHandle == nullptr) { + LOGE("Open file[%s] fail[%d]", file.c_str(), errno); + continue; + } + (void)fseek(fileHandle, 0, SEEK_END); + long fileSize = ftell(fileHandle); + LOGD("CheckRealFileSize:FileName[%s],size[%ld]", file.c_str(), fileSize); + size += static_cast(fileSize); // file is less than 16M. + (void)fclose(fileHandle); + } + return size; +} + +/** + * @tc.name: GetKvStoreDiskSize001 + * @tc.desc: ROM space occupied by applications in the distributed database can be calculated. + * @tc.type: FUNC + * @tc.require: AR000CQDTD + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesSpaceManagementTest, GetKvStoreDiskSize001, TestSize.Level1) +{ + g_storeId = "distributed_GetKvStoreDiskSize_001"; + GetRealFileUrl(); + + g_mgr.GetKvStore(g_storeId, g_nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + /** + * @tc.steps: step1/2. Get Db size by GetKvStoreDiskSize. + * @tc.expected: step1/2. Return right size and ok. + */ + uint64_t localDbSize = 0; + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(g_storeId, localDbSize), OK); + EXPECT_EQ(CheckRealFileSize(g_singleVerFileNames), localDbSize); + + /** + * @tc.steps: step3. Reopen Db. + */ + g_mgr.GetKvStore(g_storeId, g_nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step4. Put some Key Value to change Db size. + */ + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key, DBConstant::MAX_KEY_SIZE); + DistributedDBToolsUnitTest::GetRandomKeyValue(value, DBConstant::MAX_VALUE_SIZE); + EXPECT_EQ(g_kvNbDelegatePtr->Put(key, value), OK); + + /** + * @tc.steps: step5/6. Get Db size by GetKvStoreDiskSize. + * @tc.expected: step5/6. Return right size and ok. + */ + localDbSize = 0; + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(g_storeId, localDbSize), OK); + EXPECT_EQ(CheckRealFileSize(g_singleVerFileNames), localDbSize); + + /** + * @tc.steps: step7. Close and Delete Db. + * @tc.expected: step7. Successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(g_storeId), OK); +} + +/** + * @tc.name: GetKvStoreDiskSize002 + * @tc.desc: Obtain the size of the opened database. + * @tc.type: FUNC + * @tc.require: AR000CQDTD + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesSpaceManagementTest, GetKvStoreDiskSize002, TestSize.Level2) +{ + g_storeId = "distributed_GetKvStoreDiskSize_002"; + GetRealFileUrl(); + + KvStoreDelegate::Option option; + g_mgr.GetKvStore(g_storeId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + g_mgr.GetKvStore(g_storeId, g_nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step1/2. Get Db size by GetKvStoreDiskSize. + * @tc.expected: step1/2. Return right size and ok. + */ + uint64_t singleAndMultiDbSize = 0; + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(g_storeId, singleAndMultiDbSize), OK); + uint64_t dbSizeForCheck = CheckRealFileSize(g_singleVerFileNames) + CheckRealFileSize(GetMultiVerFilelist()); + EXPECT_EQ(dbSizeForCheck, singleAndMultiDbSize); + + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key, DBConstant::MAX_KEY_SIZE); + DistributedDBToolsUnitTest::GetRandomKeyValue(value, DBConstant::MAX_VALUE_SIZE); + + EXPECT_EQ(g_kvNbDelegatePtr->Put(key, value), OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + /** + * @tc.steps: step3/4. Reopen Db and Put some Key Value to change Db size. + */ + g_mgr.GetKvStore(g_storeId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_kvDelegatePtr->Put(key, value), OK); + + /** + * @tc.steps: step5/6. Get Db size by GetKvStoreDiskSize. + * @tc.expected: step5/6. Return right size and ok. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // for vacuum + singleAndMultiDbSize = 0; + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(g_storeId, singleAndMultiDbSize), OK); + ASSERT_TRUE(dbSizeForCheck != singleAndMultiDbSize); + dbSizeForCheck = CheckRealFileSize(g_singleVerFileNames) + CheckRealFileSize(GetMultiVerFilelist()); + EXPECT_EQ(dbSizeForCheck, singleAndMultiDbSize); + LOGE("single:%" PRIu64 ",mul:%" PRIu64, CheckRealFileSize(g_singleVerFileNames), + CheckRealFileSize(GetMultiVerFilelist())); + + /** + * @tc.steps: step7. Close and Delete Db. + * @tc.expected: step7. Successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(g_storeId), OK); +} + +// The file will be deleted after the test, it only for test, no security impact on permissions +static void CreateFile(const std::string &fileUrl, uint64_t fileSize) +{ + ofstream mcfile; + mcfile.open(fileUrl); + if (!mcfile.is_open()) { + return; + } + std::string fileContext; + fileContext.resize(fileSize, 'X'); + mcfile << fileContext; + mcfile.close(); + return; +} + +static void DeleteFile(const std::string &fileUrl) +{ + std::ifstream walFile(fileUrl); + if (walFile) { + int result = remove(fileUrl.c_str()); + if (result < 0) { + LOGE("failed to delete the file[%s]:%d", fileUrl.c_str(), errno); + } + } + return; +} + +/** + * @tc.name: GetKvStoreDiskSize003 + * @tc.desc: Verification exception parameters + * @tc.type: FUNC + * @tc.require: AR000CQDTD + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesSpaceManagementTest, GetKvStoreDiskSize003, TestSize.Level1) +{ + g_storeId = "distributed_GetKvStoreDiskSize_003"; + GetRealFileUrl(); + KvStoreNbDelegate::Option nbOption; + g_mgr.GetKvStore(g_storeId, nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + KvStoreDelegate::Option option; + g_mgr.GetKvStore(g_storeId, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step1. Use an anomalous length of storeId by GetKvStoreDiskSize to get size. + * @tc.expected: step1. Return 0 size and INVALID_ARGS. + */ + uint64_t dbSize = 0; + std::string exceptStoreId; + exceptStoreId.clear(); + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(exceptStoreId, dbSize), INVALID_ARGS); + EXPECT_EQ(dbSize, 0ull); + + exceptStoreId.resize(129, 'X'); + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(exceptStoreId, dbSize), INVALID_ARGS); + EXPECT_EQ(dbSize, 0ull); + + /** + * @tc.steps: step2. Use a valid but not exist storeId to GetKvStoreDiskSize. + * @tc.expected: step2. Return 0 size and NOT_FOUND. + */ + exceptStoreId.resize(128, 'X'); + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(exceptStoreId, dbSize), NOT_FOUND); + EXPECT_EQ(dbSize, 0ull); + + /** + * @tc.steps: step3/4. Use right storeId to GetKvStoreDiskSize. + * @tc.expected: step3/4. Return right size and OK. + */ + uint64_t singleAndMultiDbSize = 0; + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(g_storeId, singleAndMultiDbSize), OK); + uint64_t dbSizeForCheck = CheckRealFileSize(g_singleVerFileNames) + CheckRealFileSize(GetMultiVerFilelist()); + EXPECT_EQ(dbSizeForCheck, singleAndMultiDbSize); + + /** + * @tc.steps: step5. Create irrelevant files. + */ + CreateFile(g_testDir + "/" + g_storeId + "/" + DBConstant::MULTI_SUB_DIR + "/test.txt", 1024 * 1024); + + /** + * @tc.steps: step6/7/8. Get Db size by GetKvStoreDiskSize. + * @tc.expected: step6/7/8. Return right size and ok. + */ + singleAndMultiDbSize = 0; + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(g_storeId, singleAndMultiDbSize), OK); + EXPECT_EQ(dbSizeForCheck, singleAndMultiDbSize); + + DeleteFile(g_testDir + "/" + g_storeId + "/" + DBConstant::MULTI_SUB_DIR + "/test.txt"); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(g_storeId), OK); +} + +/** + * @tc.name: GetKvStoreDiskSize004 + * @tc.desc: Calculate memory database size + * @tc.type: FUNC + * @tc.require: AR000CQDTD + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesSpaceManagementTest, GetKvStoreDiskSize004, TestSize.Level1) +{ + g_storeId = "distributed_GetKvStoreDiskSize_004"; + GetRealFileUrl(); + + KvStoreNbDelegate::Option nbOption; + g_mgr.GetKvStore(g_storeId, nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + uint64_t singleVerRealSize = CheckRealFileSize(g_singleVerFileNames); + + /** + * @tc.steps: step1/2. Get Db size by GetKvStoreDiskSize. + * @tc.expected: step1/2. Return right size and ok. + */ + uint64_t singleVerDbSize = 0; + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(g_storeId, singleVerDbSize), OK); + EXPECT_EQ(singleVerDbSize, singleVerRealSize); + + /** + * @tc.steps: step3. Use the same storeId create memoryDb. + */ + nbOption = {true, true}; + g_mgr.GetKvStore(g_storeId, nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps: step4/5. Get Db size by GetKvStoreDiskSize. + * @tc.expected: step4/5. Return 0 size and ok. + */ + singleVerDbSize = 0; + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(g_storeId, singleVerDbSize), OK); + EXPECT_EQ(singleVerDbSize, 0ull); + + /** + * @tc.steps: step6. Close memoryDb. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + /** + * @tc.steps: step7. Get Db size by GetKvStoreDiskSize. + * @tc.expected: step7. Return right size and ok. + */ + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(g_storeId, singleVerDbSize), OK); + EXPECT_EQ(singleVerDbSize, singleVerRealSize); + + EXPECT_EQ(g_mgr.DeleteKvStore(g_storeId), OK); +} + +/** + * @tc.name: DeleteDbByStoreId001 + * @tc.desc: Delete database by storeId. + * @tc.type: FUNC + * @tc.require: AR000CQDTD + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesSpaceManagementTest, DeleteDbByStoreId001, TestSize.Level1) +{ + std::string storeId1 = "distributed_DeleteDbByStoreId001"; + KvStoreNbDelegate::Option nbOption; + g_mgr.GetKvStore(storeId1, nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + KvStoreDelegate::Option option; + g_mgr.GetKvStore(storeId1, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + option.localOnly = true; + g_mgr.GetKvStore(storeId1, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + std::string storeId2 = "distributed_DeleteDbByStoreId002"; + + g_mgr.GetKvStore(storeId2, nbOption, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + option.localOnly = false; + g_mgr.GetKvStore(storeId2, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + option.localOnly = true; + g_mgr.GetKvStore(storeId2, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + + uint64_t store1DbSize = 0; + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(storeId1, store1DbSize), OK); + EXPECT_NE(store1DbSize, 0ull); + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(storeId2, store1DbSize), OK); + EXPECT_NE(store1DbSize, 0ull); + + /** + * @tc.steps: step1. Delete database by storeId 1. + */ + EXPECT_EQ(g_mgr.DeleteKvStore(storeId1), OK); + + /** + * @tc.steps: step2. Use storeId 1 to get Db size by GetKvStoreDiskSize. + * @tc.expected: step2. Return 0 size and ok. + */ + store1DbSize = 0; + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(storeId1, store1DbSize), NOT_FOUND); + EXPECT_EQ(store1DbSize, 0ull); + + /** + * @tc.steps: step3. Use storeId 2 to get Db size by GetKvStoreDiskSize. + * @tc.expected: step3. Return right size and ok. + */ + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(storeId2, store1DbSize), OK); + EXPECT_NE(store1DbSize, 0ull); +} + +/** + * @tc.name: DeleteDbByStoreId002 + * @tc.desc: Delete database by not exist storeId. + * @tc.type: FUNC + * @tc.require: AR000CQDTD + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBInterfacesSpaceManagementTest, DeleteDbByStoreId002, TestSize.Level1) +{ + std::string storeId1 = "distributed_DeleteDbByStoreId001"; + + uint64_t store1DbSize = 0; + EXPECT_EQ(g_mgr.GetKvStoreDiskSize(storeId1, store1DbSize), NOT_FOUND); + EXPECT_EQ(store1DbSize, 0ull); + + /** + * @tc.steps: step1. Delete database by not exist storeId 1. + * @tc.expected: step3. Return NOT_FOUND. + */ + EXPECT_EQ(g_mgr.DeleteKvStore(storeId1), NOT_FOUND); +} diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_optimization_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_optimization_test.cpp new file mode 100644 index 00000000..3f8e6078 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_optimization_test.cpp @@ -0,0 +1,811 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_constant.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + KvStoreConfig g_config; + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + + const int OBSERVER_SLEEP_TIME = 100; + const int BATCH_PRESET_SIZE_TEST = 10; + const int DIVIDE_BATCH_PRESET_SIZE = 5; + + const Key KEY1{'k', 'e', 'y', '1'}; + const Key KEY2{'k', 'e', 'y', '2'}; + const Value VALUE1{'v', 'a', 'l', 'u', 'e', '1'}; + const Value VALUE2{'v', 'a', 'l', 'u', 'e', '2'}; + + // the type of g_kvNbDelegateCallback is function + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); +} + +class DistributedDBInterfacesTransactionOptimizationTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesTransactionOptimizationTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesTransactionOptimizationTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesTransactionOptimizationTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_kvDelegateStatus = INVALID_ARGS; + g_kvNbDelegatePtr = nullptr; +} + +void DistributedDBInterfacesTransactionOptimizationTest::TearDown(void) +{ + if (g_kvNbDelegatePtr != nullptr) { + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + g_kvNbDelegatePtr = nullptr; + } +} + +/** + * @tc.name: BatchOperationsOfSyncAndLocal001 + * @tc.desc: Verify the batch put and query functions of the sync and local data in the same transaction. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesTransactionOptimizationTest, SyncAndLocalBatchOperations001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + std::string storeId("SyncAndLocalBatchOperations001"); + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Starting a transaction. + * @tc.expected: step2. The transaction is started successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + + vector entries; + vector keys; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entries, keys); + EXPECT_TRUE(entries.size() == BATCH_PRESET_SIZE_TEST); + + vector localEntrys; + vector localKeys; + DistributedDBUnitTest::GenerateRecords(DIVIDE_BATCH_PRESET_SIZE, localEntrys, localKeys); + EXPECT_TRUE(localEntrys.size() == DIVIDE_BATCH_PRESET_SIZE); + + /** + * @tc.steps:step3. Put batch data. + * @tc.expected: step3. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entries), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(localEntrys), OK); + + Key keyPrefix; + std::vector getSyncEntries; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(keyPrefix, getSyncEntries), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntriesEqual(entries, getSyncEntries, true)); + + std::vector getLocalEntries; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocalEntries(keyPrefix, getLocalEntries), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntriesEqual(localEntrys, getLocalEntries, true)); + + /** + * @tc.steps:step4. Commit a transaction. + * @tc.expected: step4. Transaction submitted successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + + /** + * @tc.steps:step5. GetEntries after the transaction is submitted. + * @tc.expected: step5. GetEntries return OK and the geted data is correct. + */ + getSyncEntries.clear(); + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(keyPrefix, getSyncEntries), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntriesEqual(entries, getSyncEntries, true)); + + getLocalEntries.clear(); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocalEntries(keyPrefix, getLocalEntries), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsEntriesEqual(localEntrys, getLocalEntries, true)); + + /** + * @tc.steps:step6. Close the kv store. + * @tc.expected: step6. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SyncAndLocalSingleOperations001 + * @tc.desc: Verify the single put and query functions of the sync and local data in the same transaction. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesTransactionOptimizationTest, SyncAndLocalSingleOperations001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + std::string storeId("SyncAndLocalSingleOperations001"); + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Starting a transaction. + * @tc.expected: step2. The transaction is started successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + + /** + * @tc.steps:step3. Put and Get single data. + * @tc.expected: step3. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY1, VALUE1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY2, VALUE2), OK); + + Value getSyncValue; + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY1, getSyncValue), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsValueEqual(VALUE1, getSyncValue)); + + Value getLocalValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY2, getLocalValue), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsValueEqual(VALUE2, getLocalValue)); + + /** + * @tc.steps:step4. Commit a transaction. + * @tc.expected: step4. Transaction submitted successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + + /** + * @tc.steps:step5. Get after the transaction is submitted. + * @tc.expected: step5. Get return OK and the geted data is correct. + */ + getSyncValue.clear(); + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY1, getSyncValue), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsValueEqual(VALUE1, getSyncValue)); + + getLocalValue.clear(); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY2, getLocalValue), OK); + EXPECT_TRUE(DistributedDBToolsUnitTest::IsValueEqual(VALUE2, getLocalValue)); + + /** + * @tc.steps:step6. Close the kv store. + * @tc.expected: step6. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: DeleteInTransaction001 + * @tc.desc: Verify that the sync and local functions can be deleted in the same transaction. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesTransactionOptimizationTest, DeleteInTransaction001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + std::string storeId("DeleteInTransaction001"); + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Starting a transaction. + * @tc.expected: step2. The transaction is started successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + + /** + * @tc.steps:step3. Put and Get single data. + * @tc.expected: step3. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY1, VALUE1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY2, VALUE2), OK); + + /** + * @tc.steps:step4 Delete before the transaction is submitted. + * @tc.expected: step4. Delete return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Delete(KEY1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocal(KEY2), OK); + + /** + * @tc.steps:step5 Commit a transaction. + * @tc.expected: step5 Transaction submitted successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + + /** + * @tc.steps:step6 Get after the transaction is submitted. + * @tc.expected: step6 Get return NOT_FOUND and the geted data is correct. + */ + Value getSyncValue; + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY1, getSyncValue), NOT_FOUND); + Value getLocalValue; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY2, getLocalValue), NOT_FOUND); + + /** + * @tc.steps:step7 Close the kv store. + * @tc.expected: step7 Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: DeleteBatchInTransaction001 + * @tc.desc: Local data does not check readOnly. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesTransactionOptimizationTest, DeleteBatchInTransaction001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + std::string storeId("DeleteBatchInTransaction001"); + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Starting a Transaction. + * @tc.expected: step2. The transaction is started successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + + vector entries; + vector keys; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, entries, keys); + EXPECT_TRUE(entries.size() == BATCH_PRESET_SIZE_TEST); + + vector localEntrys; + vector localKeys; + DistributedDBUnitTest::GenerateRecords(DIVIDE_BATCH_PRESET_SIZE, localEntrys, localKeys); + EXPECT_TRUE(localEntrys.size() == DIVIDE_BATCH_PRESET_SIZE); + + /** + * @tc.steps:step3. Put batch data. + * @tc.expected: step3. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(entries), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(localEntrys), OK); + + /** + * @tc.steps:step4 DeleteBatch before the transaction is submitted. + * @tc.expected: step4. Delete return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(keys), OK); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(localKeys), OK); + + /** + * @tc.steps:step5 Commit a transaction. + * @tc.expected: step5 Transaction submitted successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + + /** + * @tc.steps:step6 GetEntries after the transaction is submitted. + * @tc.expected: step6 GetEntries return NOT_FOUND and the geted data is correct. + */ + Key keyPrefix; + std::vector getSyncEntries; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(keyPrefix, getSyncEntries), NOT_FOUND); + std::vector getLocalEntries; + EXPECT_EQ(g_kvNbDelegatePtr->GetLocalEntries(keyPrefix, getLocalEntries), NOT_FOUND); + + /** + * @tc.steps:step7 Close the kv store. + * @tc.expected: step7 Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SyncAndLocalObserver001 + * @tc.desc: Verify the observer functions of the sync and local data in the same transaction. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesTransactionOptimizationTest, SyncAndLocalObserver001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + std::string storeId("SyncAndLocalObserver001"); + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *syncObserver = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(syncObserver != nullptr); + KvStoreObserverUnitTest *localObserver = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(localObserver != nullptr); + + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_NATIVE, syncObserver), OK); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_LOCAL_ONLY, localObserver), OK); + + /** + * @tc.steps:step3. Starting a Transaction. + * @tc.expected: step3. The transaction is started successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + + /** + * @tc.steps:step4. Put batch data. + * @tc.expected: step4. Returns OK. + */ + vector syncKeys; + vector syncEntries; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, syncEntries, syncKeys); + EXPECT_TRUE(syncEntries.size() == BATCH_PRESET_SIZE_TEST); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(syncEntries), OK); + + vector localKeys; + vector localEntries; + DistributedDBUnitTest::GenerateRecords(DIVIDE_BATCH_PRESET_SIZE, localEntries, localKeys); + EXPECT_TRUE(localEntries.size() == DIVIDE_BATCH_PRESET_SIZE); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(localEntries), OK); + + /** + * @tc.steps:step5. Commit a transaction. + * @tc.expected: step5. Transaction submitted successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + + /** + * @tc.steps:step6. Check changed data. + * @tc.expected: step6. The inserted data is the same as the written data. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(syncEntries, syncObserver->GetEntriesInserted())); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(localEntries, localObserver->GetEntriesInserted())); + + /** + * @tc.steps:step7. UnRegister the observer. + * @tc.expected: step7. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(syncObserver), OK); + delete syncObserver; + syncObserver = nullptr; + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(localObserver), OK); + delete localObserver; + localObserver = nullptr; + + /** + * @tc.steps:step8. Close the kv store. + * @tc.expected: step8. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: OnlyDeleteInTransaction001 + * @tc.desc: Verify the observer functions of delete operation in the transaction. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesTransactionOptimizationTest, OnlyDeleteInTransaction001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + std::string storeId("OnlyDeleteInTransaction001"); + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + /** + * @tc.steps:step2. Put batch data. + * @tc.expected: step2. Returns OK. + */ + vector syncKeys; + vector syncEntries; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, syncEntries, syncKeys); + EXPECT_TRUE(syncEntries.size() == BATCH_PRESET_SIZE_TEST); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(syncEntries), OK); + + vector localKeys; + vector localEntries; + DistributedDBUnitTest::GenerateRecords(DIVIDE_BATCH_PRESET_SIZE, localEntries, localKeys); + EXPECT_TRUE(localEntries.size() == DIVIDE_BATCH_PRESET_SIZE); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(localEntries), OK); + + KvStoreObserverUnitTest *syncObserver = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(syncObserver != nullptr); + KvStoreObserverUnitTest *localObserver = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(localObserver != nullptr); + + /** + * @tc.steps:step3. Register the non-null observer for the special key. + * @tc.expected: step3. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_NATIVE, syncObserver), OK); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_LOCAL_ONLY, localObserver), OK); + + /** + * @tc.steps:step4. Starting a Transaction. + * @tc.expected: step4. The transaction is started successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + + /** + * @tc.steps:step5 DeleteBatch before the transaction is submitted. + * @tc.expected: step5. Delete return OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->DeleteBatch(syncKeys), OK); + EXPECT_EQ(g_kvNbDelegatePtr->DeleteLocalBatch(localKeys), OK); + + /** + * @tc.steps:step6. Commit a transaction. + * @tc.expected: step6. Transaction submitted successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + + /** + * @tc.steps:step7. Check changed data. + * @tc.expected: step7. The inserted data is the same as the written data. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(syncEntries, syncObserver->GetEntriesDeleted())); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(localEntries, localObserver->GetEntriesDeleted())); + + /** + * @tc.steps:step8. UnRegister the observer. + * @tc.expected: step8. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(syncObserver), OK); + delete syncObserver; + syncObserver = nullptr; + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(localObserver), OK); + delete localObserver; + localObserver = nullptr; + + /** + * @tc.steps:step9. Close the kv store. + * @tc.expected: step9. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: SyncAndLocalObserver002 + * @tc.desc: Verify the observer functions of the sync and local data in the same transaction. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesTransactionOptimizationTest, SyncAndLocalObserver002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + std::string storeId("SyncAndLocalObserver002"); + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *syncObserver = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(syncObserver != nullptr); + KvStoreObserverUnitTest *localObserver = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(localObserver != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_NATIVE, syncObserver), OK); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_LOCAL_ONLY, localObserver), OK); + + /** + * @tc.steps:step3. Starting a Transaction. + * @tc.expected: step3. The transaction is started successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + + /** + * @tc.steps:step4. Put data. + * @tc.expected: step4. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY1, VALUE1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY2, VALUE2), OK); + + /** + * @tc.steps:step5. Commit a transaction. + * @tc.expected: step5. Transaction submitted successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Commit(), OK); + + std::vector syncEntries; + Entry syncEntry{KEY1, VALUE1}; + syncEntries.emplace_back(syncEntry); + std::vector localEntries; + Entry localEntry{KEY2, VALUE2}; + localEntries.emplace_back(localEntry); + + /** + * @tc.steps:step6. Check changed data. + * @tc.expected: step6. The inserted data is the same as the written data. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(syncEntries, syncObserver->GetEntriesInserted())); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(localEntries, localObserver->GetEntriesInserted())); + + /** + * @tc.steps:step7. UnRegister the observer. + * @tc.expected: step7. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(syncObserver), OK); + delete syncObserver; + syncObserver = nullptr; + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(localObserver), OK); + delete localObserver; + localObserver = nullptr; + + /** + * @tc.steps:step8. Close the kv store. + * @tc.expected: step8. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: PutRollback001 + * @tc.desc: Verify that a transaction can be rolled back after data is put. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesTransactionOptimizationTest, PutRollback001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + std::string storeId("PutRollback001"); + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *syncObserver = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(syncObserver != nullptr); + KvStoreObserverUnitTest *localObserver = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(localObserver != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_NATIVE, syncObserver), OK); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_LOCAL_ONLY, localObserver), OK); + + /** + * @tc.steps:step3. Starting a Transaction. + * @tc.expected: step3. The transaction is started successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + + /** + * @tc.steps:step3. Put data. + * @tc.expected: step3. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY1, VALUE1), OK); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocal(KEY2, VALUE2), OK); + + /** + * @tc.steps:step3. Transaction rollback. + * @tc.expected: step3. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), OK); + + /** + * @tc.steps:step4. After the rollback, query the database and observe the changed data. + * @tc.expected: step4. Get return NOT_FOUND. The changed data is empty. + */ + Value value; + EXPECT_EQ(g_kvNbDelegatePtr->Get(KEY1, value), NOT_FOUND); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocal(KEY2, value), NOT_FOUND); + + std::vector empty; + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(empty, syncObserver->GetEntriesInserted())); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(empty, localObserver->GetEntriesInserted())); + + /** + * @tc.steps:step5. UnRegister the observer. + * @tc.expected: step5. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(syncObserver), OK); + delete syncObserver; + syncObserver = nullptr; + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(localObserver), OK); + delete localObserver; + localObserver = nullptr; + + /** + * @tc.steps:step6. Close the kv store. + * @tc.expected: step6. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); + g_kvNbDelegatePtr = nullptr; +} + +/** + * @tc.name: PutBatchRollback001 + * @tc.desc: Verify that a transaction can be rolled back after data is put. + * @tc.type: FUNC + * @tc.require: AR000EPAS8 + * @tc.author: changguicai + */ +HWTEST_F(DistributedDBInterfacesTransactionOptimizationTest, PutBatchRollback001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + std::string storeId("OptimizeObserver008"); + KvStoreNbDelegate::Option option = {true, false, false}; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + + KvStoreObserverUnitTest *syncObserver = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(syncObserver != nullptr); + KvStoreObserverUnitTest *localObserver = new (std::nothrow) KvStoreObserverUnitTest; + ASSERT_TRUE(localObserver != nullptr); + /** + * @tc.steps:step2. Register the non-null observer for the special key. + * @tc.expected: step2. Register results OK. + */ + Key key; + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_NATIVE, syncObserver), OK); + EXPECT_EQ(g_kvNbDelegatePtr->RegisterObserver(key, OBSERVER_CHANGES_LOCAL_ONLY, localObserver), OK); + + /** + * @tc.steps:step3. Starting a Transaction. + * @tc.expected: step3. The transaction is started successfully. + */ + EXPECT_EQ(g_kvNbDelegatePtr->StartTransaction(), OK); + + /** + * @tc.steps:step3. Put batch data. + * @tc.expected: step3. Returns OK. + */ + std::vector syncKeys; + std::vector syncEntries; + DistributedDBUnitTest::GenerateRecords(BATCH_PRESET_SIZE_TEST, syncEntries, syncKeys); + EXPECT_TRUE(syncEntries.size() == BATCH_PRESET_SIZE_TEST); + EXPECT_EQ(g_kvNbDelegatePtr->PutBatch(syncEntries), OK); + + std::vector localKeys; + std::vector localEntries; + DistributedDBUnitTest::GenerateRecords(DIVIDE_BATCH_PRESET_SIZE, localEntries, localKeys); + EXPECT_TRUE(localEntries.size() == DIVIDE_BATCH_PRESET_SIZE); + EXPECT_EQ(g_kvNbDelegatePtr->PutLocalBatch(localEntries), OK); + + /** + * @tc.steps:step3. Transaction rollback. + * @tc.expected: step3. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->Rollback(), OK); + + /** + * @tc.steps:step4. After the rollback, query the database and observe the changed data. + * @tc.expected: step4. Get return NOT_FOUND. The changed data is empty. + */ + Key keyPrefix; + std::vector entries; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(keyPrefix, entries), NOT_FOUND); + EXPECT_EQ(g_kvNbDelegatePtr->GetLocalEntries(keyPrefix, entries), NOT_FOUND); + + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entries, syncObserver->GetEntriesInserted())); + EXPECT_TRUE(DistributedDBToolsUnitTest::CheckObserverResult(entries, localObserver->GetEntriesInserted())); + + /** + * @tc.steps:step5. UnRegister the observer. + * @tc.expected: step5. Returns OK. + */ + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(syncObserver), OK); + delete syncObserver; + syncObserver = nullptr; + EXPECT_EQ(g_kvNbDelegatePtr->UnRegisterObserver(localObserver), OK); + delete localObserver; + localObserver = nullptr; + + /** + * @tc.steps:step6. Close the kv store. + * @tc.expected: step6. Results OK and delete successfully. + */ + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + EXPECT_EQ(g_mgr.DeleteKvStore(storeId), OK); + g_kvNbDelegatePtr = nullptr; +} + diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_syncdb_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_syncdb_test.cpp new file mode 100644 index 00000000..1b8779f8 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_syncdb_test.cpp @@ -0,0 +1,643 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_data_generate_unit_test.h" +#include "distributeddb_interfaces_transaction_testcase.h" +#include "distributeddb_tools_unit_test.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + const bool LOCAL_ONLY = false; + const string STORE_ID = STORE_ID_SYNC; + + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + + // define the g_snapshotDelegateCallback, used to get some information when open a kv snapshot. + DBStatus g_snapshotDelegateStatus = INVALID_ARGS; + KvStoreSnapshotDelegate *g_snapshotDelegatePtr = nullptr; + // the type of g_snapshotDelegateCallback is function + auto g_snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_snapshotDelegateStatus), std::ref(g_snapshotDelegatePtr)); +} + +class DistributedDBInterfacesTransactionSyncDBTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesTransactionSyncDBTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesTransactionSyncDBTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesTransactionSyncDBTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /* + * Here, we create STORE_ID before test, + * and it will be closed in TearDown(). + */ + KvStoreDelegate::Option option = {true, LOCAL_ONLY}; + g_mgr.GetKvStore(STORE_ID, option, g_kvDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); +} + +void DistributedDBInterfacesTransactionSyncDBTest::TearDown(void) +{ + if (g_kvDelegatePtr != nullptr && g_snapshotDelegatePtr != nullptr) { + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; + } + + if (g_kvDelegatePtr != nullptr) { + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID), OK); + } +} + +/** + * @tc.name: StartTransaction001 + * @tc.desc: Test that can't call StartTransaction interface repeatedly. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, StartTransaction001, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface the 1st time. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. call StartTransaction interface the 2nd time. + * @tc.expected: step2. call failed and return ERROR. + */ + DistributedDBInterfacesTransactionTestCase::StartTransaction001(g_kvDelegatePtr); +} + +/** + * @tc.name: StartTransaction002 + * @tc.desc: Test that call StartTransaction and commit interface normally. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, StartTransaction002, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. call commit interface. + * @tc.expected: step2. call succeed. + */ + DistributedDBInterfacesTransactionTestCase::StartTransaction002(g_kvDelegatePtr); +} + +/** + * @tc.name: StartTransaction003 + * @tc.desc: Test that call StartTransaction and rolback interface normally. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, StartTransaction003, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. call rollback interface. + * @tc.expected: step2. call succeed. + */ + DistributedDBInterfacesTransactionTestCase::StartTransaction003(g_kvDelegatePtr); +} + +/** + * @tc.name: StartTransaction004 + * @tc.desc: Test that call StartTransaction and rolback interface normally. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, StartTransaction004, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. put (k1, v1) to data base. + * @tc.expected: step2. put succeed. + */ + /** + * @tc.steps:step3. close data base. + * @tc.expected: step3. close succeed. + */ + /** + * @tc.steps:step4. use GetKvStore interface to open db. + * @tc.expected: step4. open succeed. + */ + /** + * @tc.steps:step5. use snapshot interface to check the value of k1. + * @tc.expected: step5. can't get the record of k1. + */ + DistributedDBInterfacesTransactionTestCase::StartTransaction004(g_kvDelegatePtr, STORE_ID, LOCAL_ONLY, + g_mgr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: Commit001 + * @tc.desc: Test that can't commit Transaction before it start. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, Commit001, TestSize.Level1) +{ + /** + * @tc.steps:step1. commit Transaction without start it. + * @tc.expected: step1. commit failed and returned ERROR. + */ + DistributedDBInterfacesTransactionTestCase::Commit001(g_kvDelegatePtr); +} + +/** + * @tc.name: Commit002 + * @tc.desc: Test that can't commit Transaction repeatedly even if it start normally. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, Commit002, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. call commit interface the 1st time. + * @tc.expected: step2. call succeed. + */ + /** + * @tc.steps:step3. call commit interface the 2nd time. + * @tc.expected: step3. call failed and returned ERROR. + */ + DistributedDBInterfacesTransactionTestCase::Commit002(g_kvDelegatePtr); +} + +/** + * @tc.name: Commit003 + * @tc.desc: Test that can commit Transaction after put record. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, Commit003, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. put (k1, v1) to db. + * @tc.expected: step2. put succeed. + */ + /** + * @tc.steps:step3. call commit interface. + * @tc.expected: step3. call succeed. + */ + /** + * @tc.steps:step4. use snapshot interface to check if (k1, v1) is put succeed. + * @tc.expected: step4. can find (k1, v1) from db. + */ + DistributedDBInterfacesTransactionTestCase::Commit003(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: Commit004 + * @tc.desc: Test that can commit Transaction after update record. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, Commit004, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. update value = v2 where key = k1. + * @tc.expected: step2. update succeed. + */ + /** + * @tc.steps:step3. call commit interface. + * @tc.expected: step3. commit succeed. + */ + /** + * @tc.steps:step4. use snapshot interface to check if (k1, v2) is update succeed. + * @tc.expected: step4. the value is v2 where key = k1 in the db. + */ + DistributedDBInterfacesTransactionTestCase::Commit004(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: Commit005 + * @tc.desc: Test that can commit Transaction after delete record. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, Commit005, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. delete record from db where key = k1. + * @tc.expected: step2. delete succeed. + */ + /** + * @tc.steps:step3. call commit interface. + * @tc.expected: step3. commit succeed. + */ + /** + * @tc.steps:step4. use snapshot interface to check if (k1, v1) is delete succeed. + * @tc.expected: step4. can't find (k1, v1) in the db. + */ + DistributedDBInterfacesTransactionTestCase::Commit005(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: Commit006 + * @tc.desc: Test that can commit Transaction after clear all the records. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, Commit006, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. clear all the records from db. + * @tc.expected: step2. clear succeed. + */ + /** + * @tc.steps:step3. call commit interface. + * @tc.expected: step3. commit succeed. + */ + /** + * @tc.steps:step4. use snapshot interface to check if there are any data in db. + * @tc.expected: step4. can't find any data in db. + */ + DistributedDBInterfacesTransactionTestCase::Commit006(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: Commit007 + * @tc.desc: Test that can commit Transaction after delete and update db. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, Commit007, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. delete record from db where key = k1. + * @tc.expected: step2. delete succeed. + */ + /** + * @tc.steps:step3. put (k2, v1) to db. + * @tc.expected: step3. put succeed. + */ + /** + * @tc.steps:step4. call commit interface. + * @tc.expected: step4. commit succeed. + */ + /** + * @tc.steps:step5. use snapshot interface to check the data in db. + * @tc.expected: step5. can't find (k1, v1) but can find (k2, v1) in db. + */ + DistributedDBInterfacesTransactionTestCase::Commit007(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: Commit008 + * @tc.desc: Test that can commit Transaction after clear and new add records. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, Commit008, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. clear all the records from db. + * @tc.expected: step2. clear succeed. + */ + /** + * @tc.steps:step3. put (k3, v3) to db. + * @tc.expected: step3. put succeed. + */ + /** + * @tc.steps:step4. call commit interface. + * @tc.expected: step4. commit succeed. + */ + /** + * @tc.steps:step5. use snapshot interface to check the data in db. + * @tc.expected: step5. can only find (k3, v3) in db. + */ + DistributedDBInterfacesTransactionTestCase::Commit008(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: RollBack001 + * @tc.desc: Test if new commit records and logs generated + * when a transaction rollback-ed + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, RollBack001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test g_kvDelegatePtr->Rollback + * @tc.expected: step1. Return ERROR. + */ + DistributedDBInterfacesTransactionTestCase::RollBack001(g_kvDelegatePtr); +} + +/** +* @tc.name: RollBack002 +* @tc.desc: rollback a transaction two times +* @tc.type: FUNC +* @tc.require: AR000BVRNM AR000CQDTQ +* @tc.author: huangnaigu +*/ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, RollBack002, TestSize.Level1) +{ + /** + * @tc.steps:step1. start a transaction + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. rollback the transaction + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. rollback the transaction the second time + * @tc.expected: step3. Return ERROR. + */ + DistributedDBInterfacesTransactionTestCase::RollBack002(g_kvDelegatePtr); +} + +/** + * @tc.name: RollBack003 + * @tc.desc: insert a data and rollback + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, RollBack003, TestSize.Level1) +{ + /** + * @tc.steps:step1. start a transaction + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. Put (k1,v1) + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. rollback a transaction + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. check if (k1,v1) exists + * @tc.expected: step4. Return NOT_FOUND. + */ + DistributedDBInterfacesTransactionTestCase::RollBack003(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: RollBack004 + * @tc.desc: update a data and rollback + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, RollBack004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Put (k1,v1) + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. Update (k1,v1) to (k1,v2) in the transaction + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. rollback the transaction + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps:step5. check the value of k1 is v1 + * @tc.expected: step5. verification is OK . + */ + DistributedDBInterfacesTransactionTestCase::RollBack004(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: RollBack005 + * @tc.desc: delete an exist data and rollback + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, RollBack005, TestSize.Level1) +{ + /** + * @tc.steps:step1. Put (k1,v1) + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. Delete (k1,v1) in the transaction + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. rollback the transaction + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps:step5. check the value of k1 is v1 + * @tc.expected: step5. verification is OK . + */ + DistributedDBInterfacesTransactionTestCase::RollBack005(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: RollBack006 + * @tc.desc: clear db and rollback + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, RollBack006, TestSize.Level1) +{ + /** + * @tc.steps:step1. PutBatch records: (k1,v1), (k2,v2) + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. Clear all records in the transaction + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. rollback the transaction + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps:step5. check if there are 2 records in the db + * @tc.expected: step5. verification is OK . + */ + DistributedDBInterfacesTransactionTestCase::RollBack006(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: RollBack007 + * @tc.desc: delete a exist data and update a data and rollback + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, RollBack007, TestSize.Level1) +{ + /** + * @tc.steps:step1. PutBatch records: (k1,v1), (k2,v2) + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. Delete (k1,v1) in the transaction + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. Update (k2,v2) to (k2,v1) in the transaction + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps:step5. rollback the transaction + * @tc.expected: step5. Return OK. + */ + /** + * @tc.steps:step6. check if (k1,v1),(k2,v2) exist and no more records in the db + * @tc.expected: step6. verification is OK . + */ + DistributedDBInterfacesTransactionTestCase::RollBack007(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: RollBack008 + * @tc.desc: clear db and insert a data and rollback + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionSyncDBTest, RollBack008, TestSize.Level1) +{ + /** + * @tc.steps:step1. PutBatch records: (k1,v1), (k2,v2) + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. Clear all records in the transaction + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. Put (012, ABC) in the transaction + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps:step5. rollback the transaction + * @tc.expected: step5. Return OK. + */ + /** + * @tc.steps:step6. check if (k1,v1),(k2,v2) exist and no more records in the db + * @tc.expected: step6. verification is OK . + */ + DistributedDBInterfacesTransactionTestCase::RollBack008(g_kvDelegatePtr, g_snapshotDelegatePtr); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_test.cpp new file mode 100644 index 00000000..3dc576de --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_test.cpp @@ -0,0 +1,675 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_data_generate_unit_test.h" +#include "distributeddb_interfaces_transaction_testcase.h" +#include "distributeddb_tools_unit_test.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + const bool LOCAL_ONLY = true; + const string STORE_ID = STORE_ID_LOCAL; + + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + + // define the g_snapshotDelegateCallback, used to get some information when open a kv snapshot. + DBStatus g_snapshotDelegateStatus = INVALID_ARGS; + KvStoreSnapshotDelegate *g_snapshotDelegatePtr = nullptr; + // the type of g_snapshotDelegateCallback is function + auto g_snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_snapshotDelegateStatus), std::ref(g_snapshotDelegatePtr)); +} + +class DistributedDBInterfacesTransactionTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBInterfacesTransactionTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); +} + +void DistributedDBInterfacesTransactionTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBInterfacesTransactionTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /* + * Here, we create STORE_ID before test, + * and it will be closed in TearDown(). + */ + KvStoreDelegate::Option option = {true, LOCAL_ONLY}; + g_mgr.GetKvStore(STORE_ID, option, g_kvDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); +} + +void DistributedDBInterfacesTransactionTest::TearDown(void) +{ + if (g_kvDelegatePtr != nullptr && g_snapshotDelegatePtr != nullptr) { + EXPECT_TRUE(g_kvDelegatePtr->ReleaseKvStoreSnapshot(g_snapshotDelegatePtr) == OK); + g_snapshotDelegatePtr = nullptr; + } + + if (g_kvDelegatePtr != nullptr) { + EXPECT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + EXPECT_EQ(g_mgr.DeleteKvStore(STORE_ID), OK); + } +} + +/** + * @tc.name: StartTransaction001 + * @tc.desc: Test that can't call StartTransaction interface repeatedly. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, StartTransaction001, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface the 1st time. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. call StartTransaction interface the 2nd time. + * @tc.expected: step2. call failed and return ERROR. + */ + DistributedDBInterfacesTransactionTestCase::StartTransaction001(g_kvDelegatePtr); +} + +/** + * @tc.name: StartTransaction002 + * @tc.desc: Test that call StartTransaction and commit interface normally. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, StartTransaction002, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. call commit interface. + * @tc.expected: step2. call succeed. + */ + DistributedDBInterfacesTransactionTestCase::StartTransaction002(g_kvDelegatePtr); +} + +/** + * @tc.name: StartTransaction003 + * @tc.desc: Test that call StartTransaction and rolback interface normally. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, StartTransaction003, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. call rollback interface. + * @tc.expected: step2. call succeed. + */ + DistributedDBInterfacesTransactionTestCase::StartTransaction003(g_kvDelegatePtr); +} + +/** + * @tc.name: StartTransaction004 + * @tc.desc: Test that call StartTransaction and rolback interface normally. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, StartTransaction004, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. put (k1, v1) to data base. + * @tc.expected: step2. put succeed. + */ + /** + * @tc.steps:step3. close data base. + * @tc.expected: step3. close succeed. + */ + /** + * @tc.steps:step4. use GetKvStore interface to open db. + * @tc.expected: step4. open succeed. + */ + /** + * @tc.steps:step5. use snapshot interface to check the value of k1. + * @tc.expected: step5. can't get the record of k1. + */ + DistributedDBInterfacesTransactionTestCase::StartTransaction004(g_kvDelegatePtr, STORE_ID, LOCAL_ONLY, + g_mgr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: StartTransaction005 + * @tc.desc: Test that can't call StartTransaction interface repeatedly for different kv store. + * @tc.type: FUNC + * @tc.require: AR000BVRNK AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, StartTransaction005, TestSize.Level3) +{ + /** + * @tc.steps:step1. call StartTransaction interface the 1st time. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. call StartTransaction interface the 2nd time using another . + * @tc.expected: step2. call failed. + */ + /** + * @tc.steps:step4. call commit interface the 1st time. + * @tc.expected: step4. call failed. + */ + /** + * @tc.steps:step5. call commit interface the 2nd time. + * @tc.expected: step5. call failed. + */ + DistributedDBInterfacesTransactionTestCase::StartTransaction005(g_kvDelegatePtr, STORE_ID, LOCAL_ONLY, g_mgr); +} + +/** + * @tc.name: Commit001 + * @tc.desc: Test that can't commit Transaction before it start. + * @tc.type: FUNC + * @tc.require: AR000CQDTO AR000CQDTP + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Commit001, TestSize.Level1) +{ + /** + * @tc.steps:step1. commit Transaction without start it. + * @tc.expected: step1. commit failed and returned ERROR. + */ + DistributedDBInterfacesTransactionTestCase::Commit001(g_kvDelegatePtr); +} + +/** + * @tc.name: Commit002 + * @tc.desc: Test that can't commit Transaction repeatedly even if it start normally. + * @tc.type: FUNC + * @tc.require: AR000CQDTO AR000CQDTP + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Commit002, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. call commit interface the 1st time. + * @tc.expected: step2. call succeed. + */ + /** + * @tc.steps:step3. call commit interface the 2nd time. + * @tc.expected: step3. call failed and returned ERROR. + */ + DistributedDBInterfacesTransactionTestCase::Commit002(g_kvDelegatePtr); +} + +/** + * @tc.name: Commit003 + * @tc.desc: Test that can commit Transaction after put record. + * @tc.type: FUNC + * @tc.require: AR000CQDTO AR000CQDTP + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Commit003, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. put (k1, v1) to db. + * @tc.expected: step2. put succeed. + */ + /** + * @tc.steps:step3. call commit interface. + * @tc.expected: step3. call succeed. + */ + /** + * @tc.steps:step4. use snapshot interface to check if (k1, v1) is put succeed. + * @tc.expected: step4. can find (k1, v1) from db. + */ + DistributedDBInterfacesTransactionTestCase::Commit003(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: Commit004 + * @tc.desc: Test that can commit Transaction after update record. + * @tc.type: FUNC + * @tc.require: AR000CQDTO AR000CQDTP + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Commit004, TestSize.Level1) +{ + /** + * @tc.steps:step1. put one data. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. call StartTransaction interface. + * @tc.expected: step2. call succeed. + */ + /** + * @tc.steps:step3. update the data to another value. + * @tc.expected: step3. call succeed. + */ + /** + * @tc.steps:step4. call commit interface. + * @tc.expected: step4. call succeed. + */ + /** + * @tc.steps:step5. use snapshot interface to check the updated data. + * @tc.expected: step5. the value is updated. + */ + DistributedDBInterfacesTransactionTestCase::Commit004(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: Commit005 + * @tc.desc: Test that can commit Transaction after delete record. + * @tc.type: FUNC + * @tc.require: AR000CQDTO AR000CQDTP + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Commit005, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. delete record from db where key = k1. + * @tc.expected: step2. delete succeed. + */ + /** + * @tc.steps:step3. call commit interface. + * @tc.expected: step3. commit succeed. + */ + /** + * @tc.steps:step4. use snapshot interface to check if (k1, v1) is delete succeed. + * @tc.expected: step4. can't find (k1, v1) in the db. + */ + DistributedDBInterfacesTransactionTestCase::Commit005(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: Commit006 + * @tc.desc: Test that can commit Transaction after clear all the records. + * @tc.type: FUNC + * @tc.require: AR000CQDTO + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Commit006, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. clear all the records from db. + * @tc.expected: step2. clear succeed. + */ + /** + * @tc.steps:step3. call commit interface. + * @tc.expected: step3. commit succeed. + */ + /** + * @tc.steps:step4. use snapshot interface to check if there are any data in db. + * @tc.expected: step4. can't find any data in db. + */ + DistributedDBInterfacesTransactionTestCase::Commit006(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: Commit007 + * @tc.desc: Test that can commit Transaction after delete and update db. + * @tc.type: FUNC + * @tc.require: AR000CQDTO AR000CQDTP + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Commit007, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. delete record from db where key = k1. + * @tc.expected: step2. delete succeed. + */ + /** + * @tc.steps:step3. put (k2, v1) to db. + * @tc.expected: step3. put succeed. + */ + /** + * @tc.steps:step4. call commit interface. + * @tc.expected: step4. commit succeed. + */ + /** + * @tc.steps:step5. use snapshot interface to check the data in db. + * @tc.expected: step5. can't find (k1, v1) but can find (k2, v1) in db. + */ + DistributedDBInterfacesTransactionTestCase::Commit007(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: Commit008 + * @tc.desc: Test that can commit Transaction after clear and new add records. + * @tc.type: FUNC + * @tc.require: AR000CQDTO AR000CQDTP + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Commit008, TestSize.Level1) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + /** + * @tc.steps:step2. clear all the records from db. + * @tc.expected: step2. clear succeed. + */ + /** + * @tc.steps:step3. put (k3, v3) to db. + * @tc.expected: step3. put succeed. + */ + /** + * @tc.steps:step4. call commit interface. + * @tc.expected: step4. commit succeed. + */ + /** + * @tc.steps:step5. use snapshot interface to check the data in db. + * @tc.expected: step5. can only find (k3, v3) in db. + */ + DistributedDBInterfacesTransactionTestCase::Commit008(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: RollBack001 + * @tc.desc: Test if new commit records and logs generated + * when a transaction rollback-ed + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Rollback001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Test g_kvDelegatePtr->Rollback + * @tc.expected: step1. Return ERROR. + */ + DistributedDBInterfacesTransactionTestCase::RollBack001(g_kvDelegatePtr); +} + +/** +* @tc.name: RollBack002 +* @tc.desc: rollback a transaction two times +* @tc.type: FUNC +* @tc.require: AR000BVRNM AR000CQDTQ +* @tc.author: huangnaigu +*/ +HWTEST_F(DistributedDBInterfacesTransactionTest, Rollback002, TestSize.Level1) +{ + /** + * @tc.steps:step1. start a transaction + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. rollback the transaction + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. rollback the transaction the second time + * @tc.expected: step3. Return ERROR. + */ + DistributedDBInterfacesTransactionTestCase::RollBack002(g_kvDelegatePtr); +} + +/** + * @tc.name: RollBack003 + * @tc.desc: insert a data and rollback + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Rollback003, TestSize.Level1) +{ + /** + * @tc.steps:step1. start a transaction + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. Put (k1,v1) + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. rollback a transaction + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. check if (k1,v1) exists + * @tc.expected: step4. Return NOT_FOUND. + */ + DistributedDBInterfacesTransactionTestCase::RollBack003(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: RollBack004 + * @tc.desc: update a data and rollback + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Rollback004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Put (k1,v1) + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. Update (k1,v1) to (k1,v2) in the transaction + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. rollback the transaction + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps:step5. check the value of k1 is v1 + * @tc.expected: step5. verification is OK . + */ + DistributedDBInterfacesTransactionTestCase::RollBack004(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: RollBack005 + * @tc.desc: delete a exist data and rollback + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Rollback005, TestSize.Level1) +{ + /** + * @tc.steps:step1. Put (k1,v1) + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. Delete (k1,v1) in the transaction + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. rollback the transaction + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps:step5. check the value of k1 is v1 + * @tc.expected: step5. verification is OK . + */ + DistributedDBInterfacesTransactionTestCase::RollBack005(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: RollBack006 + * @tc.desc: clear db and rollback + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Rollback006, TestSize.Level1) +{ + /** + * @tc.steps:step1. PutBatch records: (k1,v1), (k2,v2) + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. Clear all records in the transaction + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. rollback the transaction + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps:step5. check if there are 2 records in the db + * @tc.expected: step5. verification is OK . + */ + DistributedDBInterfacesTransactionTestCase::RollBack006(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: RollBack007 + * @tc.desc: delete a exist data and update a data and rollback + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Rollback007, TestSize.Level1) +{ + /** + * @tc.steps:step1. PutBatch records: (k1,v1), (k2,v2) + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. Delete (k1,v1) in the transaction + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. Update (k2,v2) to (k2,v1) in the transaction + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps:step5. rollback the transaction + * @tc.expected: step5. Return OK. + */ + /** + * @tc.steps:step6. check if (k1,v1),(k2,v2) exist and no more records in the db + * @tc.expected: step6. verification is OK . + */ + DistributedDBInterfacesTransactionTestCase::RollBack007(g_kvDelegatePtr, g_snapshotDelegatePtr); +} + +/** + * @tc.name: RollBack008 + * @tc.desc: clear db and insert a data and rollback + * @tc.type: FUNC + * @tc.require: AR000BVRNM AR000CQDTQ + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBInterfacesTransactionTest, Rollback008, TestSize.Level1) +{ + /** + * @tc.steps:step1. PutBatch records: (k1,v1), (k2,v2) + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. Clear all records in the transaction + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. Put (012, ABC) in the transaction + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps:step5. rollback the transaction + * @tc.expected: step5. Return OK. + */ + /** + * @tc.steps:step6. check if (k1,v1),(k2,v2) exist and no more records in the db + * @tc.expected: step6. verification is OK . + */ + DistributedDBInterfacesTransactionTestCase::RollBack008(g_kvDelegatePtr, g_snapshotDelegatePtr); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_testcase.cpp b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_testcase.cpp new file mode 100644 index 00000000..ff6786cf --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_testcase.cpp @@ -0,0 +1,720 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_interfaces_transaction_testcase.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +void DistributedDBInterfacesTransactionTestCase::StartTransaction001(KvStoreDelegate *&kvDelegatePtr) +{ + /** + * @tc.steps:step1. call StartTransaction interface the 1st time. + * @tc.expected: step1. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step2. call StartTransaction interface the 2nd time. + * @tc.expected: step2. call failed and return ERROR. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == DB_ERROR); + EXPECT_EQ(kvDelegatePtr->Commit(), OK); +} + +void DistributedDBInterfacesTransactionTestCase::StartTransaction002(KvStoreDelegate *&kvDelegatePtr) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step2. call commit interface. + * @tc.expected: step2. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Commit() == OK); +} + +void DistributedDBInterfacesTransactionTestCase::StartTransaction003(KvStoreDelegate *&kvDelegatePtr) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step2. call rollback interface. + * @tc.expected: step2. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Rollback() == OK); +} + +static void GetSnapshotUnitTest(KvStoreDelegate *&kvDelegatePtr, KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus snapshotDelegateStatus = INVALID_ARGS; + auto snapshotDelegateCallback = bind(&DistributedDBToolsUnitTest::SnapshotDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(snapshotDelegateStatus), std::ref(snapshotDelegatePtr)); + + kvDelegatePtr->GetKvStoreSnapshot(nullptr, snapshotDelegateCallback); + EXPECT_TRUE(snapshotDelegateStatus == OK); + ASSERT_TRUE(snapshotDelegatePtr != nullptr); +} + +void DistributedDBInterfacesTransactionTestCase::StartTransaction004(KvStoreDelegate *&kvDelegatePtr, + const string &storeId, bool localOnly, KvStoreDelegateManager &mgr, KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus kvDelegateStatus = INVALID_ARGS; + auto kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(kvDelegateStatus), std::ref(kvDelegatePtr)); + + DBStatus valueStatus = INVALID_ARGS; + Value value; + auto valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(valueStatus), std::ref(value)); + + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step2. put (k1, v1) to data base. + * @tc.expected: step2. put succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Put(KEY_1, VALUE_1) == OK); + /** + * @tc.steps:step3. close data base. + * @tc.expected: step3. close succeed. + */ + EXPECT_EQ(mgr.CloseKvStore(kvDelegatePtr), OK); + kvDelegatePtr = nullptr; + + /** + * @tc.steps:step4. use GetKvStore interface to open db. + * @tc.expected: step4. open succeed. + */ + KvStoreDelegate::Option option = {true, localOnly}; + mgr.GetKvStore(storeId, option, kvDelegateCallback); + EXPECT_EQ(kvDelegateStatus, OK); + ASSERT_TRUE(kvDelegatePtr != nullptr); + + /** + * @tc.steps:step5. use snapshot interface to check the value of k1. + * @tc.expected: step5. can't get the record of k1. + */ + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->Get(KEY_1, valueCallback); + EXPECT_TRUE(valueStatus == NOT_FOUND); + EXPECT_TRUE(value.size() == 0); +} + +void DistributedDBInterfacesTransactionTestCase::StartTransaction005(KvStoreDelegate *&kvDelegatePtr, + const string &storeId, bool localOnly, KvStoreDelegateManager &mgr) +{ + DBStatus kvDelegateStatus = INVALID_ARGS; + auto kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(kvDelegateStatus), std::ref(kvDelegatePtr)); + + /** + * @tc.steps:step1. call StartTransaction interface the 1st time. + * @tc.expected: step1. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + KvStoreDelegate *temp = kvDelegatePtr; + temp->Put(KEY_1, VALUE_1); + + KvStoreDelegate::Option option = {true, localOnly}; + mgr.GetKvStore(storeId, option, kvDelegateCallback); + EXPECT_TRUE(kvDelegateStatus == OK); + ASSERT_TRUE(kvDelegatePtr != nullptr); + /** + * @tc.steps:step2. call StartTransaction interface the 2nd time using another . + * @tc.expected: step2. call failed. + */ + EXPECT_NE(kvDelegatePtr->StartTransaction(), OK); + + kvDelegatePtr->Put(KEY_2, VALUE_2); + /** + * @tc.steps:step4. call commit interface the 1st time. + * @tc.expected: step4. call failed. + */ + EXPECT_EQ(temp->Commit(), OK); + EXPECT_EQ(mgr.CloseKvStore(temp), OK); + temp = nullptr; + /** + * @tc.steps:step5. call commit interface the 2nd time. + * @tc.expected: step5. call failed. + */ + EXPECT_NE(kvDelegatePtr->Commit(), OK); +} + +void DistributedDBInterfacesTransactionTestCase::Commit001(KvStoreDelegate *&kvDelegatePtr) +{ + /** + * @tc.steps:step1. commit Transaction without start it. + * @tc.expected: step1. commit failed and returned ERROR. + */ + EXPECT_TRUE(kvDelegatePtr->Commit() == DB_ERROR); +} + +void DistributedDBInterfacesTransactionTestCase::Commit002(KvStoreDelegate *&kvDelegatePtr) +{ + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step2. call commit interface the 1st time. + * @tc.expected: step2. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Commit() == OK); + /** + * @tc.steps:step3. call commit interface the 2nd time. + * @tc.expected: step3. call failed and returned ERROR. + */ + EXPECT_TRUE(kvDelegatePtr->Commit() == DB_ERROR); +} + +void DistributedDBInterfacesTransactionTestCase::Commit003(KvStoreDelegate *&kvDelegatePtr, + KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus valueStatus = INVALID_ARGS; + Value value; + auto valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(valueStatus), std::ref(value)); + + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step2. put (k1, v1) to db. + * @tc.expected: step2. put succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Put(KEY_1, VALUE_1) == OK); + /** + * @tc.steps:step3. call commit interface. + * @tc.expected: step3. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Commit() == OK); + + /** + * @tc.steps:step4. use snapshot interface to check if (k1, v1) is put succeed. + * @tc.expected: step4. can find (k1, v1) from db. + */ + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->Get(KEY_1, valueCallback); + EXPECT_TRUE(valueStatus == OK); + ASSERT_TRUE(value.size() > 0); + EXPECT_TRUE(value.front() == VALUE_1.front()); +} + +void DistributedDBInterfacesTransactionTestCase::Commit004(KvStoreDelegate *&kvDelegatePtr, + KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus valueStatus = INVALID_ARGS; + Value value; + auto valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(valueStatus), std::ref(value)); + /** + * @tc.steps:step1. put one data. + * @tc.expected: step1. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Put(KEY_1, VALUE_1) == OK); + /** + * @tc.steps:step2. call StartTransaction interface. + * @tc.expected: step2. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step3. update the data to another value. + * @tc.expected: step3. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Put(KEY_1, VALUE_2) == OK); + /** + * @tc.steps:step4. call commit interface. + * @tc.expected: step4. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Commit() == OK); + + /** + * @tc.steps:step5. use snapshot interface to check the updated data. + * @tc.expected: step5. the value is updated. + */ + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->Get(KEY_1, valueCallback); + EXPECT_TRUE(valueStatus == OK); + ASSERT_TRUE(value.size() > 0); + EXPECT_TRUE(value.front() == VALUE_2.front()); +} + +void DistributedDBInterfacesTransactionTestCase::Commit005(KvStoreDelegate *&kvDelegatePtr, + KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus valueStatus = INVALID_ARGS; + Value value; + auto valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(valueStatus), std::ref(value)); + + EXPECT_TRUE(kvDelegatePtr->Put(KEY_1, VALUE_1) == OK); + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step2. delete record from db where key = k1. + * @tc.expected: step2. delete succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Delete(KEY_1) == OK); + /** + * @tc.steps:step3. call commit interface. + * @tc.expected: step3. commit succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Commit() == OK); + + /** + * @tc.steps:step4. use snapshot interface to check if (k1, v1) is delete succeed. + * @tc.expected: step4. can't find (k1, v1) in the db. + */ + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->Get(KEY_1, valueCallback); + EXPECT_TRUE(valueStatus == NOT_FOUND); +} + +void DistributedDBInterfacesTransactionTestCase::Commit006(KvStoreDelegate *&kvDelegatePtr, + KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus entryVectorStatus = INVALID_ARGS; + unsigned long matchSize = 0; + std::vector entriesVector; + auto entryVectorCallback = bind(&DistributedDBToolsUnitTest::EntryVectorCallback, placeholders::_1, + placeholders::_2, std::ref(entryVectorStatus), std::ref(matchSize), std::ref(entriesVector)); + + EXPECT_TRUE(kvDelegatePtr->PutBatch(ENTRY_VECTOR) == OK); + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step2. clear all the records from db. + * @tc.expected: step2. clear succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Clear() == OK); + /** + * @tc.steps:step3. call commit interface. + * @tc.expected: step3. commit succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Commit() == OK); + + /** + * @tc.steps:step4. use snapshot interface to check if there are any data in db. + * @tc.expected: step4. can't find any data in db. + */ + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->GetEntries(NULL_KEY_1, entryVectorCallback); + EXPECT_TRUE(entryVectorStatus == NOT_FOUND); +} + +void DistributedDBInterfacesTransactionTestCase::Commit007(KvStoreDelegate *&kvDelegatePtr, + KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus valueStatus = INVALID_ARGS; + Value value; + auto valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(valueStatus), std::ref(value)); + + EXPECT_TRUE(kvDelegatePtr->PutBatch(ENTRY_VECTOR) == OK); + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step2. delete record from db where key = k1. + * @tc.expected: step2. delete succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Delete(KEY_1) == OK); + /** + * @tc.steps:step3. put (k2, v1) to db. + * @tc.expected: step3. put succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Put(KEY_2, VALUE_1) == OK); + /** + * @tc.steps:step4. call commit interface. + * @tc.expected: step4. commit succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Commit() == OK); + + /** + * @tc.steps:step5. use snapshot interface to check the data in db. + * @tc.expected: step5. can't find (k1, v1) but can find (k2, v1) in db. + */ + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->Get(KEY_1, valueCallback); + EXPECT_TRUE(valueStatus == NOT_FOUND); + snapshotDelegatePtr->Get(KEY_2, valueCallback); + EXPECT_TRUE(valueStatus == OK); + ASSERT_TRUE(value.size() > 0); + EXPECT_TRUE(value.front() == VALUE_1.front()); +} + +void DistributedDBInterfacesTransactionTestCase::Commit008(KvStoreDelegate *&kvDelegatePtr, + KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus entryVectorStatus = INVALID_ARGS; + unsigned long matchSizeCallback = 0; + std::vector entriesVector; + auto entryVectorCallback = bind(&DistributedDBToolsUnitTest::EntryVectorCallback, placeholders::_1, + placeholders::_2, std::ref(entryVectorStatus), std::ref(matchSizeCallback), std::ref(entriesVector)); + + EXPECT_TRUE(kvDelegatePtr->PutBatch(ENTRY_VECTOR) == OK); + /** + * @tc.steps:step1. call StartTransaction interface. + * @tc.expected: step1. call succeed. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step2. clear all the records from db. + * @tc.expected: step2. clear succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Clear() == OK); + /** + * @tc.steps:step3. put (k3, v3) to db. + * @tc.expected: step3. put succeed. + */ + Entry entry; + GenerateEntry(1, 3, entry); + EXPECT_TRUE(kvDelegatePtr->Put(entry.key, entry.value) == OK); + /** + * @tc.steps:step4. call commit interface. + * @tc.expected: step4. commit succeed. + */ + EXPECT_TRUE(kvDelegatePtr->Commit() == OK); + + /** + * @tc.steps:step5. use snapshot interface to check the data in db. + * @tc.expected: step5. can only find (k3, v3) in db. + */ + unsigned long matchSize = 1; + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->GetEntries(NULL_KEY_1, entryVectorCallback); + EXPECT_TRUE(entryVectorStatus == OK); + ASSERT_TRUE(matchSizeCallback == matchSize); +} + +void DistributedDBInterfacesTransactionTestCase::RollBack001(KvStoreDelegate *&kvDelegatePtr) +{ + /** + * @tc.steps:step1. Test g_kvDelegatePtr->Rollback + * @tc.expected: step1. Return ERROR. + */ + EXPECT_TRUE(kvDelegatePtr->Rollback() == DB_ERROR); +} + +void DistributedDBInterfacesTransactionTestCase::RollBack002(KvStoreDelegate *&kvDelegatePtr) +{ + /** + * @tc.steps:step1. start a transaction + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step2. rollback the transaction + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Rollback() == OK); + /** + * @tc.steps:step3. rollback the transaction the second time + * @tc.expected: step3. Return ERROR. + */ + EXPECT_TRUE(kvDelegatePtr->Rollback() == DB_ERROR); +} + +void DistributedDBInterfacesTransactionTestCase::RollBack003(KvStoreDelegate *&kvDelegatePtr, + KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus valueStatus = INVALID_ARGS; + Value value; + auto valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(valueStatus), std::ref(value)); + /** + * @tc.steps:step1. start a transaction + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step2. Put (k1,v1) + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Put(KEY_1, VALUE_1) == OK); + /** + * @tc.steps:step3. rollback a transaction + * @tc.expected: step3. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Rollback() == OK); + + /** + * @tc.steps:step4. check if (k1,v1) exists + * @tc.expected: step4. Return NOT_FOUND. + */ + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->Get(KEY_1, valueCallback); + EXPECT_TRUE(valueStatus == NOT_FOUND); +} + +void DistributedDBInterfacesTransactionTestCase::RollBack004(KvStoreDelegate *&kvDelegatePtr, + KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus valueStatus = INVALID_ARGS; + Value value; + auto valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(valueStatus), std::ref(value)); + /** + * @tc.steps:step1. Put (k1,v1) + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Put(KEY_1, VALUE_1) == OK); + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step3. Update (k1,v1) to (k1,v2) in the transaction + * @tc.expected: step3. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Put(KEY_1, VALUE_2) == OK); + /** + * @tc.steps:step4. rollback the transaction + * @tc.expected: step4. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Rollback() == OK); + + /** + * @tc.steps:step5. check the value of k1 is v1 + * @tc.expected: step5. verification is OK . + */ + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->Get(KEY_1, valueCallback); + EXPECT_TRUE(valueStatus == OK); + ASSERT_TRUE(value.size() > 0); + EXPECT_TRUE(value.front() == VALUE_1.front()); +} + +void DistributedDBInterfacesTransactionTestCase::RollBack005(KvStoreDelegate *&kvDelegatePtr, + KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus valueStatus = INVALID_ARGS; + Value value; + auto valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(valueStatus), std::ref(value)); + /** + * @tc.steps:step1. Put (k1,v1) + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Put(KEY_1, VALUE_1) == OK); + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step3. Delete (k1,v1) in the transaction + * @tc.expected: step3. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Delete(KEY_1) == OK); + /** + * @tc.steps:step4. rollback the transaction + * @tc.expected: step4. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Rollback() == OK); + + /** + * @tc.steps:step5. check the value of k1 is v1 + * @tc.expected: step5. verification is OK . + */ + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->Get(KEY_1, valueCallback); + EXPECT_TRUE(valueStatus == OK); + ASSERT_TRUE(value.size() > 0); + EXPECT_TRUE(value.front() == VALUE_1.front()); +} + +void DistributedDBInterfacesTransactionTestCase::RollBack006(KvStoreDelegate *&kvDelegatePtr, + KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus entryVectorStatus = INVALID_ARGS; + unsigned long matchSizeCallback = 0; + std::vector entriesVector; + auto entryVectorCallback = bind(&DistributedDBToolsUnitTest::EntryVectorCallback, placeholders::_1, + placeholders::_2, std::ref(entryVectorStatus), std::ref(matchSizeCallback), std::ref(entriesVector)); + /** + * @tc.steps:step1. PutBatch records: (k1,v1), (k2,v2) + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->PutBatch(ENTRY_VECTOR) == OK); + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step3. Clear all records in the transaction + * @tc.expected: step3. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Clear() == OK); + /** + * @tc.steps:step4. rollback the transaction + * @tc.expected: step4. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Rollback() == OK); + + /** + * @tc.steps:step5. check if there are 2 records in the db + * @tc.expected: step5. verification is OK . + */ + unsigned long matchSize = 2; + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->GetEntries(NULL_KEY_1, entryVectorCallback); + EXPECT_TRUE(entryVectorStatus == OK); + ASSERT_TRUE(matchSizeCallback == matchSize); +} + +void DistributedDBInterfacesTransactionTestCase::RollBack007(KvStoreDelegate *&kvDelegatePtr, + KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus valueStatus = INVALID_ARGS; + Value value; + auto valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(valueStatus), std::ref(value)); + + DBStatus entryVectorStatus = INVALID_ARGS; + unsigned long matchSizeCallback = 0; + std::vector entriesVector; + auto entryVectorCallback = bind(&DistributedDBToolsUnitTest::EntryVectorCallback, placeholders::_1, + placeholders::_2, std::ref(entryVectorStatus), std::ref(matchSizeCallback), std::ref(entriesVector)); + + /** + * @tc.steps:step1. PutBatch records: (k1,v1), (k2,v2) + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->PutBatch(ENTRY_VECTOR) == OK); + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step3. Delete (k1,v1) in the transaction + * @tc.expected: step3. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Delete(KEY_1) == OK); + /** + * @tc.steps:step4. Update (k2,v2) to (k2,v1) in the transaction + * @tc.expected: step4. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Put(KEY_2, VALUE_1) == OK); + /** + * @tc.steps:step5. rollback the transaction + * @tc.expected: step5. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Rollback() == OK); + + /** + * @tc.steps:step6. check if (k1,v1),(k2,v2) exist and no more records in the db + * @tc.expected: step6. verification is OK . + */ + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->Get(KEY_1, valueCallback); + EXPECT_TRUE(valueStatus == OK); + ASSERT_TRUE(value.size() > 0); + EXPECT_TRUE(value.front() == VALUE_1.front()); + snapshotDelegatePtr->Get(KEY_2, valueCallback); + EXPECT_TRUE(valueStatus == OK); + ASSERT_TRUE(value.size() > 0); + EXPECT_TRUE(value.front() == VALUE_2.front()); + + unsigned long matchSize = 2; + snapshotDelegatePtr->GetEntries(NULL_KEY_1, entryVectorCallback); + EXPECT_TRUE(entryVectorStatus == OK); + ASSERT_TRUE(matchSizeCallback == matchSize); +} + +void DistributedDBInterfacesTransactionTestCase::RollBack008(KvStoreDelegate *&kvDelegatePtr, + KvStoreSnapshotDelegate *&snapshotDelegatePtr) +{ + DBStatus valueStatus = INVALID_ARGS; + Value value; + auto valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(valueStatus), std::ref(value)); + DBStatus entryVectorStatus = INVALID_ARGS; + unsigned long matchSizeCallback = 0; + std::vector entriesVector; + auto entryVectorCallback = bind(&DistributedDBToolsUnitTest::EntryVectorCallback, placeholders::_1, + placeholders::_2, std::ref(entryVectorStatus), std::ref(matchSizeCallback), std::ref(entriesVector)); + + /** + * @tc.steps:step1. PutBatch records: (k1,v1), (k2,v2) + * @tc.expected: step1. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->PutBatch(ENTRY_VECTOR) == OK); + /** + * @tc.steps:step2. start a transaction + * @tc.expected: step2. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->StartTransaction() == OK); + /** + * @tc.steps:step3. Clear all records in the transaction + * @tc.expected: step3. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Clear() == OK); + /** + * @tc.steps:step4. Put (012, ABC) in the transaction + * @tc.expected: step4. Return OK. + */ + Entry entry; + GenerateEntry(1, 3, entry); + EXPECT_TRUE(kvDelegatePtr->Put(entry.key, entry.value) == OK); + /** + * @tc.steps:step5. rollback the transaction + * @tc.expected: step5. Return OK. + */ + EXPECT_TRUE(kvDelegatePtr->Rollback() == OK); + + /** + * @tc.steps:step6. check if (k1,v1),(k2,v2) exist and no more records in the db + * @tc.expected: step6. verification is OK . + */ + GetSnapshotUnitTest(kvDelegatePtr, snapshotDelegatePtr); + snapshotDelegatePtr->Get(KEY_1, valueCallback); + EXPECT_TRUE(valueStatus == OK); + ASSERT_TRUE(value.size() > 0); + EXPECT_TRUE(value.front() == VALUE_1.front()); + snapshotDelegatePtr->Get(KEY_2, valueCallback); + EXPECT_TRUE(valueStatus == OK); + ASSERT_TRUE(value.size() > 0); + EXPECT_TRUE(value.front() == VALUE_2.front()); + + unsigned long matchSize = 2; + snapshotDelegatePtr->GetEntries(NULL_KEY_1, entryVectorCallback); + EXPECT_TRUE(entryVectorStatus == OK); + ASSERT_TRUE(matchSizeCallback == matchSize); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_testcase.h b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_testcase.h new file mode 100644 index 00000000..73181f1a --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/distributeddb_interfaces_transaction_testcase.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDB_INTERFACES_TRANSACTION_TESTCASE_H +#define DISTRIBUTEDDB_INTERFACES_TRANSACTION_TESTCASE_H + +#include +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" + +class DistributedDBInterfacesTransactionTestCase final { +public: + DistributedDBInterfacesTransactionTestCase() {}; + ~DistributedDBInterfacesTransactionTestCase() {}; + + static void StartTransaction001(DistributedDB::KvStoreDelegate *&kvDelegatePtr); + + static void StartTransaction002(DistributedDB::KvStoreDelegate *&kvDelegatePtr); + + static void StartTransaction003(DistributedDB::KvStoreDelegate *&kvDelegatePtr); + + static void StartTransaction004(DistributedDB::KvStoreDelegate *&kvDelegatePtr, const std::string &storeId, + bool localOnly, DistributedDB::KvStoreDelegateManager &mgr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); + + static void StartTransaction005(DistributedDB::KvStoreDelegate *&kvDelegatePtr, const std::string &storeId, + bool localOnly, DistributedDB::KvStoreDelegateManager &mgr); + + static void Commit001(DistributedDB::KvStoreDelegate *&kvDelegatePtr); + + static void Commit002(DistributedDB::KvStoreDelegate *&kvDelegatePtr); + + static void Commit003(DistributedDB::KvStoreDelegate *&kvDelegatePtr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); + + static void Commit004(DistributedDB::KvStoreDelegate *&kvDelegatePtr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); + + static void Commit005(DistributedDB::KvStoreDelegate *&kvDelegatePtr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); + + static void Commit006(DistributedDB::KvStoreDelegate *&kvDelegatePtr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); + + static void Commit007(DistributedDB::KvStoreDelegate *&kvDelegatePtr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); + + static void Commit008(DistributedDB::KvStoreDelegate *&kvDelegatePtr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); + + static void RollBack001(DistributedDB::KvStoreDelegate *&kvDelegatePtr); + + static void RollBack002(DistributedDB::KvStoreDelegate *&kvDelegatePtr); + + static void RollBack003(DistributedDB::KvStoreDelegate *&kvDelegatePtr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); + + static void RollBack004(DistributedDB::KvStoreDelegate *&kvDelegatePtr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); + + static void RollBack005(DistributedDB::KvStoreDelegate *&kvDelegatePtr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); + + static void RollBack006(DistributedDB::KvStoreDelegate *&kvDelegatePtr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); + + static void RollBack007(DistributedDB::KvStoreDelegate *&kvDelegatePtr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); + + static void RollBack008(DistributedDB::KvStoreDelegate *&kvDelegatePtr, + DistributedDB::KvStoreSnapshotDelegate *&snapshotDelegatePtr); +}; +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/interfaces/process_system_api_adapter_impl.cpp b/mock/distributeddb/test/unittest/common/interfaces/process_system_api_adapter_impl.cpp new file mode 100644 index 00000000..82180154 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/process_system_api_adapter_impl.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "process_system_api_adapter_impl.h" + +#include +#include + +#include "distributeddb_tools_unit_test.h" +#include "distributeddb_data_generate_unit_test.h" +#include "log_print.h" +#include "platform_specific.h" + +using namespace DistributedDBUnitTest; + +namespace DistributedDB { +namespace { + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + std::placeholders::_1, std::placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); +} + +ProcessSystemApiAdapterImpl::ProcessSystemApiAdapterImpl() + : callback_(nullptr), + isLocked_(false), + createDb_(false) +{ +} + +ProcessSystemApiAdapterImpl::~ProcessSystemApiAdapterImpl() +{ + callback_ = nullptr; +} + +DBStatus ProcessSystemApiAdapterImpl::RegOnAccessControlledEvent(const OnAccessControlledEvent &callback) +{ + callback_ = callback; + return OK; +} + +bool ProcessSystemApiAdapterImpl::IsAccessControlled() const +{ + return isLocked_; +} + +DBStatus ProcessSystemApiAdapterImpl::SetSecurityOption(const std::string &filePath, const SecurityOption &option) +{ + bool isExisted = OS::CheckPathExistence(filePath); + if (!isExisted) { + LOGE("SetSecurityOption to unexistence dir![%s]", filePath.c_str()); + return NOT_FOUND; + } + + std::string dirName; + struct dirent *direntPtr = nullptr; + DIR *dirPtr = opendir(filePath.c_str()); + if (dirPtr == nullptr) { + LOGD("set path secOpt![%s] [%d] [%d]", filePath.c_str(), option.securityFlag, option.securityLabel); + pathSecOptDic_[filePath] = option; + return OK; + } + + while (true) { + direntPtr = readdir(dirPtr); + // condition to exit the loop + if (direntPtr == nullptr) { + break; + } + // only remove all *.db files + std::string str(direntPtr->d_name); + if (str == "." || str == "..") { + continue; + } + dirName.clear(); + dirName.append(filePath).append("/").append(str); + if (direntPtr->d_type == DT_DIR) { + SetSecurityOption(dirName, option); + std::lock_guard lock(adapterlock_); + pathSecOptDic_[dirName] = option; + LOGD("set path secOpt![%s] [%d] [%d]", dirName.c_str(), option.securityFlag, option.securityLabel); + } else { + std::lock_guard lock(adapterlock_); + pathSecOptDic_[dirName] = option; + LOGD("set path secOpt![%s] [%d] [%d]", dirName.c_str(), option.securityFlag, option.securityLabel); + continue; + } + } + closedir(dirPtr); + pathSecOptDic_[filePath] = option; + return OK; +} + +DBStatus ProcessSystemApiAdapterImpl::GetSecurityOption(const std::string &filePath, SecurityOption &option) const +{ + std::map temp = pathSecOptDic_; // For const interface only for test + if (temp.find(filePath) == temp.end()) { + LOGE("[ProcessSystemApiAdapterImpl]::[GetSecurityOption] path [%s] not set secOpt!", filePath.c_str()); + option.securityLabel = NOT_SET; + option.securityFlag = 0; + return OK; + } + LOGD("[AdapterImpl] Get path secOpt![%s] [%d] [%d]", filePath.c_str(), option.securityFlag, option.securityLabel); + option = temp[filePath]; + return OK; +} + +bool ProcessSystemApiAdapterImpl::CheckDeviceSecurityAbility(const std::string &devId, + const SecurityOption &option) const +{ + LOGI("CheckDeviceSecurityAbility!!"); + if (createDb_) { // for close kvstore will close virtual communicator + KvStoreConfig config; + DistributedDBToolsUnitTest::TestDirInit(config.dataDir); + + g_mgr.SetKvStoreConfig(config); + + KvStoreNbDelegate::Option dbOption = {true, false, false}; + g_mgr.GetKvStore("CheckDeviceSecurityAbilityMeta", dbOption, g_kvNbDelegateCallback); + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + } + return true; +} + +void ProcessSystemApiAdapterImpl::SetLockStatus(bool isLock) +{ + std::lock_guard lock(adapterlock_); + if (callback_) { + callback_(isLock); + } + isLocked_ = isLock; +} + +void ProcessSystemApiAdapterImpl::SetNeedCreateDb(bool isCreate) +{ + std::lock_guard lock(adapterlock_); + createDb_ = isCreate; +} + +void ProcessSystemApiAdapterImpl::ResetSecOptDic() +{ + pathSecOptDic_.clear(); +} + +void ProcessSystemApiAdapterImpl::ResetAdapter() +{ + ResetSecOptDic(); + SetLockStatus(false); + g_mgr.DeleteKvStore("CheckDeviceSecurityAbilityMeta"); +} +}; diff --git a/mock/distributeddb/test/unittest/common/interfaces/process_system_api_adapter_impl.h b/mock/distributeddb/test/unittest/common/interfaces/process_system_api_adapter_impl.h new file mode 100644 index 00000000..474e71d5 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/process_system_api_adapter_impl.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 PROCESS_SYSTEM_API_ADAPTER_H +#define PROCESS_SYSTEM_API_ADAPTER_H + +#include +#include +#include + +#include "iprocess_system_api_adapter.h" +#include "store_types.h" + +namespace DistributedDB { +class ProcessSystemApiAdapterImpl : public IProcessSystemApiAdapter { +public: + ProcessSystemApiAdapterImpl(); + ~ProcessSystemApiAdapterImpl() override; + DBStatus RegOnAccessControlledEvent(const OnAccessControlledEvent &callback) override; + bool IsAccessControlled() const override; + DBStatus SetSecurityOption(const std::string &filePath, const SecurityOption &option) override; + DBStatus GetSecurityOption(const std::string &filePath, SecurityOption &option) const override; + bool CheckDeviceSecurityAbility(const std::string &devId, const SecurityOption &option) const override; + void SetLockStatus(bool isLock); + void SetNeedCreateDb(bool isCreate); + void ResetSecOptDic(); + void ResetAdapter(); + +private: + mutable std::mutex adapterlock_; + OnAccessControlledEvent callback_; + std::map pathSecOptDic_; + bool isLocked_; + bool createDb_; +}; +} // namespace DistributedDB + +#endif diff --git a/mock/distributeddb/test/unittest/common/interfaces/runtime_context_process_system_api_adapter_impl_test.cpp b/mock/distributeddb/test/unittest/common/interfaces/runtime_context_process_system_api_adapter_impl_test.cpp new file mode 100644 index 00000000..373f325d --- /dev/null +++ b/mock/distributeddb/test/unittest/common/interfaces/runtime_context_process_system_api_adapter_impl_test.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_errno.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "iprocess_system_api_adapter.h" +#include "log_print.h" +#include "process_system_api_adapter_impl.h" +#include "runtime_context.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + const std::string DATA_FILE_PATH = "/data/test/"; + SecurityOption g_option = {0, 0}; + const std::string DEV_ID = "devId"; + std::shared_ptr g_adapter; + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + string g_testDir; + KvStoreConfig g_config; + + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); +} + +class RuntimeContextProcessSystemApiAdapterImplTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); +}; + +void RuntimeContextProcessSystemApiAdapterImplTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Get an adapter + */ + g_adapter = std::make_shared(); + EXPECT_TRUE(g_adapter != nullptr); + DistributedDBToolsUnitTest::TestDirInit(g_testDir); +} + +void RuntimeContextProcessSystemApiAdapterImplTest::TearDownTestCase(void) +{ + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir); +} + +void RuntimeContextProcessSystemApiAdapterImplTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_adapter->ResetAdapter(); +} + +/** + * @tc.name: SetSecurityOption001 + * @tc.desc: Set SecurityOption. + * @tc.type: FUNC + * @tc.require: AR000EV1G2 + */ +HWTEST_F(RuntimeContextProcessSystemApiAdapterImplTest, SetSecurityOption001, TestSize.Level1) +{ + /** + * @tc.steps: step1. call SetSecurityOption to set SecurityOption before set g_adapter + * @tc.expected: step1. function return E_NOT_SUPPORT + */ + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); + int errCode = RuntimeContext::GetInstance()->SetSecurityOption(DATA_FILE_PATH, g_option); + EXPECT_TRUE(errCode == -E_NOT_SUPPORT); + + /** + * @tc.steps: step2. call SetSecurityOption to set SecurityOption after set g_adapter + * @tc.expected: step2. function return E_OK + */ + EXPECT_TRUE(g_adapter != nullptr); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(g_adapter); + errCode = RuntimeContext::GetInstance()->SetSecurityOption(DATA_FILE_PATH, g_option); + EXPECT_EQ(errCode, E_OK); +} + +/** + * @tc.name: GetSecurityOption001 + * @tc.desc: Get SecurityOption. + * @tc.type: FUNC + * @tc.require: AR000EV1G2 + */ +HWTEST_F(RuntimeContextProcessSystemApiAdapterImplTest, GetSecurityOption001, TestSize.Level1) +{ + /** + * @tc.steps: step1. call GetSecurityOption to get SecurityOption before set g_adapter + * @tc.expected: step1. function return E_NOT_SUPPORT + */ + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); + int errCode = RuntimeContext::GetInstance()->GetSecurityOption(DATA_FILE_PATH, g_option); + EXPECT_TRUE(errCode == -E_NOT_SUPPORT); + + /** + * @tc.steps: step2. call GetSecurityOption to get SecurityOption after set g_adapter + * @tc.expected: step2. function return E_OK + */ + EXPECT_TRUE(g_adapter != nullptr); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(g_adapter); + errCode = RuntimeContext::GetInstance()->GetSecurityOption(DATA_FILE_PATH, g_option); + EXPECT_TRUE(errCode == E_OK); +} + +/** + * @tc.name: RegisterLockStatusLister001 + * @tc.desc: Register a listener. + * @tc.type: FUNC + * @tc.require: AR000EV1G2 + */ +HWTEST_F(RuntimeContextProcessSystemApiAdapterImplTest, RegisterLockStatusLister001, TestSize.Level1) +{ + int errCode = E_OK; + bool lockStatus = false; + auto onEventFunction1 = [&lockStatus](void *isLock) { + LOGI("lock status 1 changed %d", *(static_cast(isLock))); + lockStatus = *(static_cast(isLock)); + }; + + auto onEventFunction2 = [&lockStatus](void *isLock) { + LOGI("lock status 2 changed %d", *(static_cast(isLock))); + lockStatus = *(static_cast(isLock)); + }; + /** + * @tc.steps: step1. call RegisterLockStatusLister to register a listener before set adapter + * @tc.expected: step1. function return ok + */ + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); + NotificationChain::Listener *listener = + RuntimeContext::GetInstance()->RegisterLockStatusLister(onEventFunction1, errCode); + EXPECT_NE(listener, nullptr); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. call RegisterLockStatusLister to register a listener after set g_adapter + * @tc.expected: step2. function return a not null listener + */ + EXPECT_TRUE(g_adapter != nullptr); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(g_adapter); + + auto listener1 = RuntimeContext::GetInstance()->RegisterLockStatusLister(onEventFunction1, errCode); + EXPECT_TRUE(errCode == E_OK); + EXPECT_NE(listener1, nullptr); + listener1->Drop(); + + /** + * @tc.steps: step3. call SetLockStatus to change lock status + * @tc.expected: step3. the listener's callback should be called + */ + g_adapter->SetLockStatus(false); + EXPECT_TRUE(!lockStatus); + + /** + * @tc.steps: step4. call RegisterLockStatusLister to register another listener after set g_adapter + * @tc.expected: step4. function return a not null listener + */ + listener->Drop(); + listener = RuntimeContext::GetInstance()->RegisterLockStatusLister(onEventFunction2, errCode); + EXPECT_NE(listener, nullptr); + listener->Drop(); +} + +/** + * @tc.name: IsAccessControlled001 + * @tc.desc: Get Access Lock Status. + * @tc.type: FUNC + * @tc.require: AR000EV1G2 + */ +HWTEST_F(RuntimeContextProcessSystemApiAdapterImplTest, IsAccessControlled001, TestSize.Level1) +{ + /** + * @tc.steps: step1. call IsAccessControlled to get Access lock status before set g_adapter + * @tc.expected: step1. function return true + */ + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); + bool isLocked = RuntimeContext::GetInstance()->IsAccessControlled(); + EXPECT_FALSE(isLocked); + + /** + * @tc.steps: step2. IsAccessControlled to get Access lock status after set g_adapter + * @tc.expected: step2. function return false + */ + EXPECT_TRUE(g_adapter != nullptr); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(g_adapter); + isLocked = RuntimeContext::GetInstance()->IsAccessControlled(); + EXPECT_TRUE(!isLocked); +} + +/** + * @tc.name: CheckDeviceSecurityAbility001 + * @tc.desc: Check device security ability. + * @tc.type: FUNC + * @tc.require: AR000EV1G2 + */ +HWTEST_F(RuntimeContextProcessSystemApiAdapterImplTest, CheckDeviceSecurityAbility001, TestSize.Level1) +{ + /** + * @tc.steps: step1. call CheckDeviceSecurityAbility to check device security ability before set g_adapter + * @tc.expected: step1. function return true + */ + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); + bool isSupported = RuntimeContext::GetInstance()->CheckDeviceSecurityAbility(DEV_ID, g_option); + EXPECT_TRUE(isSupported); + + /** + * @tc.steps: step2. IsAccessControlled to check device security ability after set g_adapter + * @tc.expected: step2. function return true + */ + EXPECT_TRUE(g_adapter != nullptr); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(g_adapter); + isSupported = RuntimeContext::GetInstance()->CheckDeviceSecurityAbility(DEV_ID, g_option); + EXPECT_TRUE(isSupported); +} + +namespace { +void FuncCheckDeviceSecurityAbility() +{ + RuntimeContext::GetInstance()->CheckDeviceSecurityAbility("", SecurityOption()); + return; +} +} + +/** + * @tc.name: CheckDeviceSecurityAbility002 + * @tc.desc: Check device security ability with getkvstore frequency. + * @tc.type: FUNC + * @tc.require: AR000EV1G2 + */ +HWTEST_F(RuntimeContextProcessSystemApiAdapterImplTest, CheckDeviceSecurityAbility002, TestSize.Level1) +{ + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(g_adapter); + g_adapter->SetNeedCreateDb(true); + + const std::string storeId = "CheckDeviceSecurityAbility002"; + std::thread t1(FuncCheckDeviceSecurityAbility); + std::thread t2([&]() { + for (int i = 0; i < 100; i++) { // open close 100 times + LOGI("open store!!"); + KvStoreNbDelegate::Option option1 = {true, false, false}; + g_mgr.GetKvStore(storeId, option1, g_kvNbDelegateCallback); + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + } + }); + std::thread t3(FuncCheckDeviceSecurityAbility); + + t1.join(); + t2.join(); + t3.join(); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_data_transformer_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_data_transformer_test.cpp new file mode 100644 index 00000000..b3c6b353 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_data_transformer_test.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "data_transformer.h" + +using namespace testing::ext; +using namespace DistributedDB; +namespace { +const int ONE_HUNDERED = 100; +const char DEFAULT_CHAR = 'D'; +const std::string DEFAULT_TEXT = "This is a text"; +const std::vector DEFAULT_BLOB(ONE_HUNDERED, DEFAULT_CHAR); +void SetNull(DataValue &dataValue) +{ + dataValue.ResetValue(); +} + +void SetInt64(DataValue &dataValue) +{ + dataValue = INT64_MAX; +} + +void SetDouble(DataValue &dataValue) +{ + dataValue = 1.0; +} + +void SetText(DataValue &dataValue) +{ + dataValue.SetText(DEFAULT_TEXT); +} + +void SetBlob(DataValue &dataValue) +{ + Blob blob; + blob.WriteBlob(DEFAULT_BLOB.data(), DEFAULT_BLOB.size()); + dataValue.SetBlob(blob); +} + +std::map g_typeMapFunction = { + {StorageType::STORAGE_TYPE_NULL, &SetNull}, + {StorageType::STORAGE_TYPE_INTEGER, &SetInt64}, + {StorageType::STORAGE_TYPE_REAL, &SetDouble}, + {StorageType::STORAGE_TYPE_TEXT, &SetText}, + {StorageType::STORAGE_TYPE_BLOB, &SetBlob} +}; + +void GenerateRowData(const std::vector &fieldInfoList, RowData &rowData) +{ + for (auto &item: fieldInfoList) { + DataValue dataValue; + StorageType type = StorageType::STORAGE_TYPE_NULL; + if (g_typeMapFunction.find(item.GetStorageType()) != g_typeMapFunction.end()) { + type = item.GetStorageType(); + } + g_typeMapFunction[type](dataValue); + rowData.push_back(std::move(dataValue)); + } +} + +void GenerateTableDataWithLog(const std::vector &fieldInfoList, TableDataWithLog &tableDataWithLog) +{ + tableDataWithLog.tableName = DEFAULT_TEXT; + for (int i = 0; i < ONE_HUNDERED; i++) { + RowDataWithLog rowDataWithLog; + GenerateRowData(fieldInfoList, rowDataWithLog.rowData); + LogInfo logInfo; + // choose first element as primary key + logInfo.dataKey = i; + tableDataWithLog.dataList.push_back(std::move(rowDataWithLog)); + } +} + +bool Equal(const LogInfo &origin, const LogInfo &target) +{ + if (origin.dataKey != target.dataKey) { + return false; + } + if (origin.device != target.device) { + return false; + } + if (origin.flag != target.flag) { + return false; + } + if (origin.hashKey != target.hashKey) { + return false; + } + if (origin.originDev != target.originDev) { + return false; + } + if (origin.timestamp != target.timestamp) { + return false; + } + if (origin.wTimestamp != target.wTimestamp) { + return false; + } + return true; +} + +bool Equal(const RowDataWithLog &origin, const OptRowDataWithLog &target) +{ + if (!Equal(origin.logInfo, target.logInfo)) { + return false; + } + if (origin.rowData.size() != target.optionalData.size()) { + return false; + } + for (uint32_t i = 0; i < origin.rowData.size(); i++) { + const auto &originData = origin.rowData[i]; + const auto &targetData = target.optionalData[i]; + if (originData != targetData) { + LOGD("VALUE NOT EQUAL!"); + return false; + } + } + return true; +} + +bool Equal(TableDataWithLog origin, OptTableDataWithLog target) +{ + if (origin.dataList.size() != target.dataList.size()) { + return false; + } + for (uint32_t i = 0; i < origin.dataList.size(); i++) { + RowDataWithLog originData = origin.dataList[i]; + OptRowDataWithLog targetData = target.dataList[i]; + if (!Equal(originData, targetData)) { + return false; + } + } + return true; +} +} + +class DistributedDBDataTransformerTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBDataTransformerTest::SetUpTestCase(void) +{ +} + +void DistributedDBDataTransformerTest::TearDownTestCase(void) +{ +} + +void DistributedDBDataTransformerTest::SetUp(void) +{ +} + +void DistributedDBDataTransformerTest::TearDown(void) +{ +} + +/** + * @tc.name: DataTransformerCheck001 + * @tc.desc: To test transformer work correctly when data contains nullptr, bool, string, double, int64, uint8_t*. + * @tc.type: Func + * @tc.require: + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBDataTransformerTest, DataTransformerCheck001, TestSize.Level1) +{ + /** + * @tc.steps: step1. generate the fieldInfoList which contains nullptr, bool, string, double, int64, uint8_t*. + */ + std::vector fieldInfoList; + int count = 0; + for (const auto &item : g_typeMapFunction) { + FieldInfo fieldInfo; + fieldInfo.SetStorageType(item.first); + fieldInfo.SetFieldName(std::to_string(count++)); + fieldInfoList.push_back(fieldInfo); + } + + /** + * @tc.steps: step2. generate an originData by fieldInfoLiist. + */ + TableDataWithLog originData; + GenerateTableDataWithLog(fieldInfoList, originData); + + /** + * @tc.steps: step3. transform originData to KV data and transform back to relationData. + * @tc.expected: get ok and value has no change + */ + std::vector dataItemList; + EXPECT_EQ(DataTransformer::TransformTableData(originData, fieldInfoList, dataItemList), E_OK); + + OptTableDataWithLog targetData; + EXPECT_EQ(DataTransformer::TransformDataItem(dataItemList, fieldInfoList, fieldInfoList, targetData), E_OK); + + EXPECT_TRUE(Equal(originData, targetData)); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_file_package_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_file_package_test.cpp new file mode 100644 index 00000000..6a4d6cde --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_file_package_test.cpp @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_errno.h" +#include "distributeddb_tools_unit_test.h" +#include "package_file.h" +#include "platform_specific.h" +#include "securec.h" +#include "value_hash_calc.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testPath; + string g_sourcePath = "/source/"; + string g_packageResultPath = "/package_result/"; + string g_unpackResultPath = "/unpack_result/"; + FileInfo g_fileInfo; + const string PACKAGE_RESULT_FILE_NAME = "package_result.dat"; + const string NON_EXIST_PATH = "/nonexist/"; + const string FILE_NAME_1 = "file1.txt"; + const string FILE_NAME_2 = "file2.dat"; + const string FILE_CONTENT_1 = "Hello world."; + const int FILE_CONTENT_2_LEN = 4; + const char FILE_CONTENT_2[FILE_CONTENT_2_LEN] = {0x5B, 0x3A, 0x29, 0x3E}; + const int BUFFER_SIZE = 4096; + const int DEVICE_ID_LEN = 32; + const vector DIVICE_ID = {'a', 'e', 'i', 'o', 'u'}; + + void RemovePath(const string &path) + { + list files; + int errCode = OS::GetFileAttrFromPath(path, files); + ASSERT_EQ(errCode, E_OK); + string fileName; + for (auto file : files) { + fileName = path + "/" + file.fileName; + switch (file.fileType) { + case OS::FILE: + (void)remove(fileName.c_str()); + break; + case OS::PATH: + if (file.fileName != "." && file.fileName != "..") { + RemovePath(fileName); + } + break; + default: + break; + } + } + (void)OS::RemoveDBDirectory(path); + } + + bool CompareFileName(const OS::FileAttr &file1, const OS::FileAttr &file2) + { + return file1.fileName <= file2.fileName; + } + + void ComparePath(const string &path1, const string &path2) + { + list files1; + int errCode = OS::GetFileAttrFromPath(path1, files1); + ASSERT_EQ(errCode, E_OK); + files1.sort(CompareFileName); + list files2; + errCode = OS::GetFileAttrFromPath(path2, files2); + ASSERT_EQ(errCode, E_OK); + files2.sort(CompareFileName); + ASSERT_EQ(files1.size(), files2.size()); + auto fileIter1 = files1.begin(); + auto fileIter2 = files2.begin(); + vector buffer1(BUFFER_SIZE, 0); + vector buffer2(BUFFER_SIZE, 0); + string bufferStr1; + string bufferStr2; + for (; fileIter1 != files1.end() && fileIter2 != files2.end(); fileIter1++, fileIter2++) { + ASSERT_STREQ(fileIter1->fileName.c_str(), fileIter2->fileName.c_str()); + ASSERT_EQ(fileIter1->fileType, fileIter2->fileType); + ASSERT_EQ(fileIter1->fileLen, fileIter2->fileLen); + if (fileIter1->fileType != OS::FILE) { + continue; + } + ifstream file1(path1 + fileIter1->fileName, ios::out | ios::binary); + ASSERT_TRUE(file1.is_open()); + ifstream file2(path2 + fileIter2->fileName, ios::out | ios::binary); + ASSERT_TRUE(file2.is_open()); + buffer1.assign(BUFFER_SIZE, 0); + buffer2.assign(BUFFER_SIZE, 0); + for (file1.read(buffer1.data(), BUFFER_SIZE), file2.read(buffer2.data(), BUFFER_SIZE); + !(file1.eof() || file2.eof()); + file1.read(buffer1.data(), BUFFER_SIZE), file2.read(buffer2.data(), BUFFER_SIZE)) { + bufferStr1.assign(buffer1.begin(), buffer1.end()); + bufferStr2.assign(buffer2.begin(), buffer2.end()); + ASSERT_STREQ(bufferStr1.c_str(), bufferStr2.c_str()); + } + file1.close(); + file2.close(); + bufferStr1.assign(buffer1.begin(), buffer1.end()); + bufferStr2.assign(buffer2.begin(), buffer2.end()); + ASSERT_STREQ(bufferStr1.c_str(), bufferStr2.c_str()); + } + } +} + +class DistributedDBFilePackageTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBFilePackageTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testPath); + g_sourcePath = g_testPath + g_sourcePath; + g_packageResultPath = g_testPath + g_packageResultPath; + g_unpackResultPath = g_testPath + g_unpackResultPath; + (void)OS::MakeDBDirectory(g_sourcePath); + ofstream file1(g_sourcePath + FILE_NAME_1, ios::out | ios::binary | ios::trunc); + ASSERT_TRUE(file1.is_open()); + file1.write(FILE_CONTENT_1.c_str(), FILE_CONTENT_1.size()); + file1.close(); + ofstream file2(g_sourcePath + FILE_NAME_2, ios::out | ios::binary | ios::trunc); + ASSERT_TRUE(file2.is_open()); + file2.write(FILE_CONTENT_2, FILE_CONTENT_2_LEN); + file2.close(); + g_fileInfo.dbType = 1; + ValueHashCalc calc; + int errCode = calc.Initialize(); + ASSERT_EQ(errCode, E_OK); + errCode = calc.Update(DIVICE_ID); + ASSERT_EQ(errCode, E_OK); + vector deviceIDVec; + errCode = calc.GetResult(deviceIDVec); + ASSERT_EQ(errCode, E_OK); + g_fileInfo.deviceID.resize(DEVICE_ID_LEN); + g_fileInfo.deviceID.assign(deviceIDVec.begin(), deviceIDVec.end()); +} + +void DistributedDBFilePackageTest::TearDownTestCase(void) +{ + RemovePath(g_testPath); +} + +void DistributedDBFilePackageTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + (void)OS::MakeDBDirectory(g_packageResultPath); + (void)OS::MakeDBDirectory(g_unpackResultPath); +} + +void DistributedDBFilePackageTest::TearDown(void) +{ + RemovePath(g_packageResultPath); + RemovePath(g_unpackResultPath); +} + +/** + * @tc.name: PackageFileTest001 + * @tc.desc: Test file package and unpack functions. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBFilePackageTest, PackageFileTest001, TestSize.Level1) +{ + int errCode = PackageFile::PackageFiles(g_sourcePath, g_packageResultPath + PACKAGE_RESULT_FILE_NAME, g_fileInfo); + ASSERT_EQ(errCode, E_OK); + FileInfo fileInfo; + errCode = PackageFile::UnpackFile(g_packageResultPath + PACKAGE_RESULT_FILE_NAME, g_unpackResultPath, fileInfo); + ASSERT_EQ(errCode, E_OK); + ComparePath(g_sourcePath, g_unpackResultPath); + ASSERT_EQ(fileInfo.dbType, g_fileInfo.dbType); + ASSERT_EQ(fileInfo.deviceID == g_fileInfo.deviceID, true); + return; +} + +/** + * @tc.name: PackageFileTest002 + * @tc.desc: Test file package if source path is not exist. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBFilePackageTest, PackageFileTest002, TestSize.Level1) +{ + int errCode = PackageFile::PackageFiles(g_sourcePath + NON_EXIST_PATH, + g_packageResultPath + PACKAGE_RESULT_FILE_NAME, g_fileInfo); + ASSERT_EQ(errCode, -E_INVALID_PATH); + return; +} + +/** + * @tc.name: PackageFileTest003 + * @tc.desc: Test file package if result path is not exist. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBFilePackageTest, PackageFileTest003, TestSize.Level1) +{ + int errCode = PackageFile::PackageFiles(g_sourcePath, + g_packageResultPath + NON_EXIST_PATH + PACKAGE_RESULT_FILE_NAME, g_fileInfo); + ASSERT_EQ(errCode, -E_INVALID_PATH); + return; +} + +/** + * @tc.name: PackageFileTest004 + * @tc.desc: Test file package if source path is empty. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBFilePackageTest, PackageFileTest004, TestSize.Level1) +{ + // Clear source files. + RemovePath(g_sourcePath); + (void)OS::MakeDBDirectory(g_sourcePath); + // Test function. + int errCode = PackageFile::PackageFiles(g_sourcePath, g_packageResultPath + PACKAGE_RESULT_FILE_NAME, g_fileInfo); + ASSERT_EQ(errCode, -E_EMPTY_PATH); + // Create source files again. + ofstream file1(g_sourcePath + FILE_NAME_1, ios::out | ios::binary | ios::trunc); + ASSERT_TRUE(file1.is_open()); + file1.write(FILE_CONTENT_1.c_str(), FILE_CONTENT_1.size()); + file1.close(); + ofstream file2(g_sourcePath + FILE_NAME_2, ios::out | ios::binary | ios::trunc); + ASSERT_TRUE(file2.is_open()); + file2.write(FILE_CONTENT_2, 4); + file2.close(); + return; +} + +/** + * @tc.name: PackageFileTest005 + * @tc.desc: Test file unpack if source file is not exist. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBFilePackageTest, PackageFileTest005, TestSize.Level1) +{ + FileInfo fileInfo; + int errCode = PackageFile::UnpackFile(g_packageResultPath + PACKAGE_RESULT_FILE_NAME, g_unpackResultPath, fileInfo); + ASSERT_EQ(errCode, -E_INVALID_PATH); + return; +} + +/** + * @tc.name: PackageFileTest006 + * @tc.desc: Test file unpack if result path is not exist. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBFilePackageTest, PackageFileTest006, TestSize.Level1) +{ + int errCode = PackageFile::PackageFiles(g_sourcePath, g_packageResultPath + PACKAGE_RESULT_FILE_NAME, g_fileInfo); + ASSERT_EQ(errCode, E_OK); + FileInfo fileInfo; + errCode = PackageFile::UnpackFile(g_packageResultPath + PACKAGE_RESULT_FILE_NAME, + g_unpackResultPath + NON_EXIST_PATH, fileInfo); + ASSERT_EQ(errCode, -E_INVALID_PATH); + return; +} + +/** + * @tc.name: PackageFileTest007 + * @tc.desc: Test file unpack if magic check failed. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBFilePackageTest, PackageFileTest007, TestSize.Level1) +{ + int errCode = PackageFile::PackageFiles(g_sourcePath, g_packageResultPath + PACKAGE_RESULT_FILE_NAME, g_fileInfo); + ASSERT_EQ(errCode, E_OK); + // Change package file header. + const string REPLACE_FILE_HEADER = "test"; + fstream file(g_packageResultPath + PACKAGE_RESULT_FILE_NAME, ios::in | ios::out | ios::binary); + ASSERT_TRUE(file.is_open()); + file.seekp(0, ios_base::beg); + file.write(REPLACE_FILE_HEADER.c_str(), REPLACE_FILE_HEADER.size()); + file.close(); + // Unpack file. + FileInfo fileInfo; + errCode = PackageFile::UnpackFile(g_packageResultPath + PACKAGE_RESULT_FILE_NAME, g_unpackResultPath, fileInfo); + ASSERT_EQ(errCode, -E_INVALID_FILE); + return; +} + +/** + * @tc.name: PackageFileTest008 + * @tc.desc: Test file unpack if checksum check failed. + * @tc.type: FUNC + * @tc.require: AR000D4879 + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBFilePackageTest, PackageFileTest008, TestSize.Level1) +{ + int errCode = PackageFile::PackageFiles(g_sourcePath, g_packageResultPath + PACKAGE_RESULT_FILE_NAME, g_fileInfo); + ASSERT_EQ(errCode, E_OK); + // Rewrite package file without file tail. + ifstream fileIn(g_packageResultPath + PACKAGE_RESULT_FILE_NAME, ios::in | ios::binary); + ASSERT_TRUE(fileIn.is_open()); + fileIn.seekg(0, ios_base::end); + int fileLen = fileIn.tellg(); + fileIn.seekg(0, ios_base::beg); + const int CUT_TAIL = 3; + int bufferLen = fileLen > BUFFER_SIZE ? BUFFER_SIZE : fileLen - CUT_TAIL; + vector buffer(bufferLen, 0); + fileIn.read(buffer.data(), buffer.size()); + fileIn.close(); + ofstream fileOut(g_packageResultPath + PACKAGE_RESULT_FILE_NAME, ios::out | ios::binary | ios::trunc); + ASSERT_TRUE(fileOut.is_open()); + fileOut.write(buffer.data(), buffer.size()); + fileOut.close(); + // Unpack file. + FileInfo fileInfo; + errCode = PackageFile::UnpackFile(g_packageResultPath + PACKAGE_RESULT_FILE_NAME, g_unpackResultPath, fileInfo); + ASSERT_EQ(errCode, -E_INVALID_FILE); + return; +} + diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_multi_ver_vacuum_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_multi_ver_vacuum_test.cpp new file mode 100644 index 00000000..0e0618a8 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_multi_ver_vacuum_test.cpp @@ -0,0 +1,753 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_errno.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" +#include "multi_ver_vacuum.h" +#include "multi_ver_vacuum_executor_stub.h" + +using namespace std; +using namespace testing::ext; +using namespace DistributedDB; + +namespace { + using Predication = std::function; + // repeatInterval in millisecond + bool RepeatCheckAsyncResult(const Predication &inPred, uint8_t repeatLimit, uint32_t repeatInterval) + { + uint8_t limit = repeatLimit; + while (limit != 0) { + if (inPred()) { + return true; + } + if (--limit == 0) { + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(repeatInterval)); + } + return false; + } + + const string DB_IDENTITY_A = "DATABASE_A"; + const string DB_IDENTITY_B = "DATABASE_B"; + const string DB_IDENTITY_C = "DATABASE_C"; + + bool CheckVacuumTaskStatus(const MultiVerVacuum &inVacuum, const string &inDbIdentifier, + VacuumTaskStatus expectStatus, uint8_t repeatLimit = 5, uint32_t repeatInterval = 100) // 5 times, 100 ms + { + return RepeatCheckAsyncResult([&inVacuum, &inDbIdentifier, expectStatus]()->bool { + VacuumTaskStatus outStatus = VacuumTaskStatus::RUN_WAIT; + int errCode = inVacuum.QueryStatus(inDbIdentifier, outStatus); + return errCode == E_OK && outStatus == expectStatus; + }, repeatLimit, repeatInterval); + } +} + +class DistributedDBMultiVerVacuumTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBMultiVerVacuumTest::SetUpTestCase(void) +{ + MultiVerVacuum::Enable(true); // Make sure functionality is enabled. +} + +void DistributedDBMultiVerVacuumTest::TearDownTestCase(void) +{ +} + +void DistributedDBMultiVerVacuumTest::SetUp() +{ + DistributedDBUnitTest::DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBMultiVerVacuumTest::TearDown() +{ +} + +/** + * @tc.name: SingleTaskNormalStatusSwitch001 + * @tc.desc: Test status switch for single task under normal operation + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerVacuumTest, SingleTaskNormalStatusSwitch001, TestSize.Level2) +{ + // Preset + MultiVerVacuum vacuum; + MultiVerVacuumExecutorStub databaseA(DbScale {1, 1, 2, 2}, 100); // 1, 2 For Scale, 100 For TimeCost, 1.7s in Total + + /** + * @tc.steps: step1. launch dbTaskA for databaseA + * @tc.expected: step1. dbTaskA RUN_NING + */ + int errCode = vacuum.Launch(DB_IDENTITY_A, &databaseA); + EXPECT_EQ(errCode, E_OK); + bool stepOne = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepOne, true); + + /** + * @tc.steps: step2. pause dbTaskA + * @tc.expected: step2. dbTaskA PAUSE_DONE + */ + errCode = vacuum.Pause(DB_IDENTITY_A); + EXPECT_EQ(errCode, E_OK); + bool stepTwo = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::PAUSE_DONE, 1); // only 1 time + EXPECT_EQ(stepTwo, true); + + /** + * @tc.steps: step3. pause dbTaskA again + * @tc.expected: step3. dbTaskA PAUSE_DONE + */ + errCode = vacuum.Pause(DB_IDENTITY_A); + EXPECT_EQ(errCode, E_OK); + bool stepThree = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::PAUSE_DONE, 1); // only 1 time + EXPECT_EQ(stepThree, true); + + /** + * @tc.steps: step4. continue dbTaskA with autoRelaunch false + * @tc.expected: step4. dbTaskA PAUSE_DONE + */ + errCode = vacuum.Continue(DB_IDENTITY_A, false); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 100 ms + bool stepFour = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::PAUSE_DONE); + EXPECT_EQ(stepFour, true); + + /** + * @tc.steps: step5. continue dbTaskA with autoRelaunch false again + * @tc.expected: step5. dbTaskA RUN_NING + */ + errCode = vacuum.Continue(DB_IDENTITY_A, false); + EXPECT_EQ(errCode, E_OK); + bool stepFive = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepFive, true); + + /** + * @tc.steps: step6. wait for some time + * @tc.expected: step6. dbTaskA FINISH + */ + bool stepSix = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::FINISH, 3, 1000); // 3 time, 1000 ms + EXPECT_EQ(stepSix, true); +} + +/** + * @tc.name: SingleTaskNormalStatusSwitch002 + * @tc.desc: Test status switch for single task under normal operation + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerVacuumTest, SingleTaskNormalStatusSwitch002, TestSize.Level2) +{ + // Preset + MultiVerVacuum vacuum; + MultiVerVacuumExecutorStub databaseB(DbScale {1, 1, 2, 2}, 100); // 1, 2 For Scale, 100 For TimeCost, 1.7s in Total + + /** + * @tc.steps: step1. launch dbTaskB for databaseB, then wait for some time + * @tc.expected: step1. dbTaskB FINISH + */ + int errCode = vacuum.Launch(DB_IDENTITY_B, &databaseB); + EXPECT_EQ(errCode, E_OK); + bool stepOne = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::FINISH, 3, 1000); // 3 time, 1000 ms + EXPECT_EQ(stepOne, true); + + /** + * @tc.steps: step2. pause dbTaskB + * @tc.expected: step2. dbTaskB FINISH + */ + errCode = vacuum.Pause(DB_IDENTITY_B); + EXPECT_EQ(errCode, E_OK); + bool stepTwo = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::FINISH, 1); // only 1 time + EXPECT_EQ(stepTwo, true); + + /** + * @tc.steps: step3. continue dbTaskB with autoRelaunch false + * @tc.expected: step3. dbTaskB FINISH + */ + errCode = vacuum.Continue(DB_IDENTITY_B, false); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 100 ms + bool stepThree = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::FINISH, 1); // only 1 time + EXPECT_EQ(stepThree, true); + + /** + * @tc.steps: step4. pause dbTaskB again + * @tc.expected: step4. dbTaskB FINISH + */ + errCode = vacuum.Pause(DB_IDENTITY_B); + EXPECT_EQ(errCode, E_OK); + bool stepFour = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::FINISH, 1); // only 1 time + EXPECT_EQ(stepFour, true); + + /** + * @tc.steps: step5. continue dbTaskB again with autoRelaunch true + * @tc.expected: step5. dbTaskB RUN_NING + */ + errCode = vacuum.Continue(DB_IDENTITY_B, true); + EXPECT_EQ(errCode, E_OK); + bool stepFive = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepFive, true); + + /** + * @tc.steps: step6. wait for some time + * @tc.expected: step6. dbTaskB FINISH + */ + bool stepSix = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::FINISH, 3, 1000); // 3 time, 1000 ms + EXPECT_EQ(stepSix, true); +} + +/** + * @tc.name: SingleTaskNormalStatusSwitch003 + * @tc.desc: Test status switch for single task under normal operation + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerVacuumTest, SingleTaskNormalStatusSwitch003, TestSize.Level2) +{ + // Preset + MultiVerVacuum vacuum; + MultiVerVacuumExecutorStub databaseC(DbScale {1, 1, 2, 2}, 100); // 1, 2 For Scale, 100 For TimeCost, 1.7s in Total + + /** + * @tc.steps: step1. launch dbTaskC for databaseC, then wait for some time + * @tc.expected: step1. dbTaskC FINISH + */ + int errCode = vacuum.Launch(DB_IDENTITY_C, &databaseC); + EXPECT_EQ(errCode, E_OK); + bool stepOne = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::FINISH, 3, 1000); // 3 time, 1000 ms + EXPECT_EQ(stepOne, true); + + /** + * @tc.steps: step2. AutoRelaunch dbTaskC + * @tc.expected: step2. dbTaskC RUN_NING + */ + errCode = vacuum.AutoRelaunchOnce(DB_IDENTITY_C); + EXPECT_EQ(errCode, E_OK); + bool stepTwo = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepTwo, true); + + /** + * @tc.steps: step3. Abort dbTaskC + * @tc.expected: step3. dbTaskC ABORT_DONE + */ + errCode = vacuum.Abort(DB_IDENTITY_C); + EXPECT_EQ(errCode, E_OK); + bool stepThree = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::ABORT_DONE, 1); // only 1 time + EXPECT_EQ(stepThree, true); + + /** + * @tc.steps: step4. launch dbTaskC again + * @tc.expected: step4. dbTaskC RUN_NING + */ + errCode = vacuum.Launch(DB_IDENTITY_C, &databaseC); + EXPECT_EQ(errCode, E_OK); + bool stepFour = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepFour, true); + + /** + * @tc.steps: step5. wait for some time + * @tc.expected: step5. dbTaskC FINISH + */ + bool stepFive = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::FINISH, 3, 1000); // 3 time, 1000 ms + EXPECT_EQ(stepFive, true); + + /** + * @tc.steps: step6. Abort dbTaskC again + * @tc.expected: step6. dbTaskC ABORT_DONE + */ + errCode = vacuum.Abort(DB_IDENTITY_C); + EXPECT_EQ(errCode, E_OK); + bool stepSix = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::ABORT_DONE, 1); // only 1 time + EXPECT_EQ(stepSix, true); +} + +/** + * @tc.name: SingleTaskNormalStatusSwitch004 + * @tc.desc: Test status switch for single task under normal operation + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerVacuumTest, SingleTaskNormalStatusSwitch004, TestSize.Level2) +{ + // Preset + MultiVerVacuum vacuum; + MultiVerVacuumExecutorStub databaseA(DbScale {1, 1, 2, 2}, 100); // 1, 2 For Scale, 100 For TimeCost, 1.7s in Total + + /** + * @tc.steps: step1. launch dbTaskA for databaseA, then wait for some time + * @tc.expected: step1. dbTaskA FINISH + */ + int errCode = vacuum.Launch(DB_IDENTITY_A, &databaseA); + EXPECT_EQ(errCode, E_OK); + bool stepOne = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::FINISH, 3, 1000); // 3 time, 1000 ms + EXPECT_EQ(stepOne, true); + + /** + * @tc.steps: step2. pause dbTaskA + * @tc.expected: step2. dbTaskA FINISH + */ + errCode = vacuum.Pause(DB_IDENTITY_A); + EXPECT_EQ(errCode, E_OK); + bool stepTwo = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::FINISH, 1); // only 1 time + EXPECT_EQ(stepTwo, true); + + /** + * @tc.steps: step3. AutoRelaunch dbTaskA + * @tc.expected: step3. dbTaskA FINISH + */ + errCode = vacuum.AutoRelaunchOnce(DB_IDENTITY_A); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 100 ms + bool stepThree = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::FINISH, 1); // only 1 time + EXPECT_EQ(stepThree, true); + + /** + * @tc.steps: step4. continue dbTaskA with autoRelaunch false + * @tc.expected: step4. dbTaskA RUN_NING + */ + errCode = vacuum.Continue(DB_IDENTITY_A, false); + EXPECT_EQ(errCode, E_OK); + bool stepFour = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepFour, true); + + /** + * @tc.steps: step5. wait for some time + * @tc.expected: step5. dbTaskA FINISH + */ + bool stepFive = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::FINISH, 3, 1000); // 3 time, 1000 ms + EXPECT_EQ(stepFive, true); +} + +/** + * @tc.name: SingleTaskAbnormalStatusSwitch001 + * @tc.desc: Test status switch for single task under abnormal operation + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerVacuumTest, SingleTaskAbnormalStatusSwitch001, TestSize.Level2) +{ + // Preset + MultiVerVacuum vacuum; + MultiVerVacuumExecutorStub databaseB(DbScale {1, 1, 2, 2}, 100); // 1, 2 For Scale, 100 For TimeCost, 1.7s in Total + + /** + * @tc.steps: step1. launch dbTaskB for databaseB + * @tc.expected: step1. dbTaskB RUN_NING + */ + int errCode = vacuum.Launch(DB_IDENTITY_B, &databaseB); + EXPECT_EQ(errCode, E_OK); + bool stepOne = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepOne, true); + + /** + * @tc.steps: step2. pause dbTaskB + * @tc.expected: step2. dbTaskB PAUSE_DONE + */ + errCode = vacuum.Pause(DB_IDENTITY_B); + EXPECT_EQ(errCode, E_OK); + bool stepTwo = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::PAUSE_DONE, 1); // only 1 time + EXPECT_EQ(stepTwo, true); + + /** + * @tc.steps: step3. abort dbTaskB + * @tc.expected: step3. dbTaskB ABORT_DONE + */ + errCode = vacuum.Abort(DB_IDENTITY_B); + EXPECT_EQ(errCode, E_OK); + bool stepThree = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::ABORT_DONE, 1); // only 1 time + EXPECT_EQ(stepThree, true); + + /** + * @tc.steps: step4. launch dbTaskB again + * @tc.expected: step4. dbTaskB RUN_NING + */ + errCode = vacuum.Launch(DB_IDENTITY_B, &databaseB); + EXPECT_EQ(errCode, E_OK); + bool stepFour = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepFour, true); + + /** + * @tc.steps: step5. pause dbTaskB again + * @tc.expected: step5. dbTaskB PAUSE_DONE + */ + errCode = vacuum.Pause(DB_IDENTITY_B); + EXPECT_EQ(errCode, E_OK); + bool stepFive = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::PAUSE_DONE, 1); // only 1 time + EXPECT_EQ(stepFive, true); + + /** + * @tc.steps: step6. continue dbTaskA with autoRelaunch false + * @tc.expected: step6. dbTaskB RUN_NING + */ + errCode = vacuum.Continue(DB_IDENTITY_B, false); + EXPECT_EQ(errCode, E_OK); + bool stepSix = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepSix, true); + + /** + * @tc.steps: step7. wait for some time + * @tc.expected: step7. dbTaskB FINISH + */ + bool stepSeven = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::FINISH, 3, 1000); // 3 time, 1000 ms + EXPECT_EQ(stepSeven, true); +} + +namespace { + bool ConcurrentPauseThenCheckResult(MultiVerVacuum &vacuum, const std::string &dbIdentifier) + { + int retForThreadA = E_OK; + int retForThreadB = E_OK; + int isQuitThreadA = false; + int isQuitThreadB = false; + std::thread threadA([&vacuum, &dbIdentifier, &retForThreadA, &isQuitThreadA]() { + LOGI("[ConcurrentPauseThenCheckResult] ThreadA Enter Do Pause."); + retForThreadA = vacuum.Pause(dbIdentifier); + isQuitThreadA = true; + LOGI("[ConcurrentPauseThenCheckResult] ThreadA Exit Do Pause."); + }); + std::thread threadB([&vacuum, &dbIdentifier, &retForThreadB, &isQuitThreadB]() { + LOGI("[ConcurrentPauseThenCheckResult] ThreadB Enter Do Pause."); + retForThreadB = vacuum.Pause(dbIdentifier); + isQuitThreadB = true; + LOGI("[ConcurrentPauseThenCheckResult] ThreadB Exit Do Pause."); + }); + threadA.detach(); + threadB.detach(); + bool result = RepeatCheckAsyncResult([&retForThreadA, &isQuitThreadA, &retForThreadB, &isQuitThreadB]()->bool { + LOGI("[ConcurrentPauseThenCheckResult] Check."); + return retForThreadA == E_OK && retForThreadB == E_OK && isQuitThreadA == true && isQuitThreadB == true; + }, 6, 500); // 6 time, 500 ms + if (!result) { + LOGE("[ConcurrentPauseThenCheckResult] RepeatCheckAsyncResult Fail."); + return false; + } + return CheckVacuumTaskStatus(vacuum, dbIdentifier, VacuumTaskStatus::PAUSE_DONE, 1); // only 1 time + } + + bool ConcurrentPauseAndAbortThenCheckResult(MultiVerVacuum &vacuum, const std::string &dbIdentifier) + { + int retForThreadA = E_OK; + int retForThreadB = E_OK; + int isQuitThreadA = false; + int isQuitThreadB = false; + std::thread threadA([&vacuum, &dbIdentifier, &retForThreadA, &isQuitThreadA]() { + LOGI("[ConcurrentPauseAndAbortThenCheckResult] ThreadA Enter Do Pause."); + retForThreadA = vacuum.Pause(dbIdentifier); + isQuitThreadA = true; + LOGI("[ConcurrentPauseAndAbortThenCheckResult] ThreadA Exit Do Pause."); + }); + std::thread threadB([&vacuum, &dbIdentifier, &retForThreadB, &isQuitThreadB]() { + LOGI("[ConcurrentPauseAndAbortThenCheckResult] ThreadB Enter Do Abort."); + retForThreadB = vacuum.Abort(dbIdentifier); + isQuitThreadB = true; + LOGI("[ConcurrentPauseAndAbortThenCheckResult] ThreadB Exit Do Abort."); + }); + threadA.detach(); + threadB.detach(); + bool result = RepeatCheckAsyncResult([&retForThreadA, &isQuitThreadA, &retForThreadB, &isQuitThreadB]()->bool { + LOGI("[ConcurrentPauseAndAbortThenCheckResult] Check."); // Pause May Fail if Abort First + return retForThreadB == E_OK && isQuitThreadA == true && isQuitThreadB == true; + }, 6, 500); // 6 time, 500 ms + if (!result) { + LOGE("[ConcurrentPauseAndAbortThenCheckResult] RepeatCheckAsyncResult Fail."); + return false; + } + return CheckVacuumTaskStatus(vacuum, dbIdentifier, VacuumTaskStatus::ABORT_DONE, 1); // only 1 time + } +} + +/** + * @tc.name: SingleTaskConcurrentStatusSwitch001 + * @tc.desc: Test status switch for single task under Concurrent operation + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerVacuumTest, SingleTaskConcurrentStatusSwitch001, TestSize.Level2) +{ + // Preset + MultiVerVacuum vacuum; + MultiVerVacuumExecutorStub databaseC(DbScale {1, 1, 1, 1}, 1000); // 1 For Scale, 1000 For TimeCost, 11s in Total + + /** + * @tc.steps: step1. launch dbTaskC for databaseC, databaseC is timecost + * @tc.expected: step1. dbTaskC FINISH + */ + int errCode = vacuum.Launch(DB_IDENTITY_C, &databaseC); + EXPECT_EQ(errCode, E_OK); + bool stepOne = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepOne, true); + + /** + * @tc.steps: step2. Concurrently pause dbTaskC in two thread + * @tc.expected: step2. thread can quit and dbTaskC PAUSE_DONE + */ + bool stepTwo = ConcurrentPauseThenCheckResult(vacuum, DB_IDENTITY_C); + EXPECT_EQ(stepTwo, true); + + /** + * @tc.steps: step3. continue dbTaskC with autoRelaunch false + * @tc.expected: step3. dbTaskC PAUSE_DONE + */ + errCode = vacuum.Continue(DB_IDENTITY_C, false); + EXPECT_EQ(errCode, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 100 ms + bool stepThree = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::PAUSE_DONE, 1); // only 1 time + EXPECT_EQ(stepThree, true); + + /** + * @tc.steps: step4. continue dbTaskC with autoRelaunch false again + * @tc.expected: step4. dbTaskC RUN_NING + */ + errCode = vacuum.Continue(DB_IDENTITY_C, false); + EXPECT_EQ(errCode, E_OK); + bool stepFour = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepFour, true); + + /** + * @tc.steps: step5. Concurrently pause and abort dbTaskC in two thread + * @tc.expected: step5. thread can quit and dbTaskC ABORT_DONE + */ + bool stepFive = ConcurrentPauseAndAbortThenCheckResult(vacuum, DB_IDENTITY_C); + EXPECT_EQ(stepFive, true); +} + +/** + * @tc.name: SingleTaskWriteHandleOccupy001 + * @tc.desc: Test write handle occupy for single task under normal operation + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerVacuumTest, SingleTaskWriteHandleOccupy001, TestSize.Level2) +{ + // Preset + MultiVerVacuum vacuum; + MultiVerVacuumExecutorStub databaseA(DbScale {1, 1, 2, 2}, 100); // 1, 2 For Scale, 100 For TimeCost, 1.7s in Total + + /** + * @tc.steps: step1. launch dbTaskA for databaseA + * @tc.expected: step1. dbTaskA RUN_NING + */ + int errCode = vacuum.Launch(DB_IDENTITY_A, &databaseA); + EXPECT_EQ(errCode, E_OK); + bool stepOne = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepOne, true); + stepOne = RepeatCheckAsyncResult([&databaseA]()->bool { + return databaseA.IsTransactionOccupied() == true; + }, 5, 100); // 5 times, 100 ms + EXPECT_EQ(stepOne, true); + + /** + * @tc.steps: step2. pause dbTaskA + * @tc.expected: step2. dbTaskA PAUSE_DONE + */ + errCode = vacuum.Pause(DB_IDENTITY_A); + EXPECT_EQ(errCode, E_OK); + bool stepTwo = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::PAUSE_DONE, 1); // only 1 time + EXPECT_EQ(stepTwo, true); + EXPECT_EQ(databaseA.IsTransactionOccupied(), false); + + /** + * @tc.steps: step3. Continue dbTaskA + * @tc.expected: step3. dbTaskA RUN_NING + */ + errCode = vacuum.Continue(DB_IDENTITY_A, false); + EXPECT_EQ(errCode, E_OK); + bool stepThree = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepThree, true); + stepThree = RepeatCheckAsyncResult([&databaseA]()->bool { + return databaseA.IsTransactionOccupied() == true; + }, 5, 100); // 5 times, 100 ms + EXPECT_EQ(stepThree, true); + + /** + * @tc.steps: step4. Abort dbTaskA + * @tc.expected: step4. dbTaskA ABORT_DONE + */ + errCode = vacuum.Abort(DB_IDENTITY_A); + EXPECT_EQ(errCode, E_OK); + bool stepFour = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::ABORT_DONE, 1); // only 1 time + EXPECT_EQ(stepFour, true); + EXPECT_EQ(databaseA.IsTransactionOccupied(), false); +} + +/** + * @tc.name: MultipleTaskNormalStatusSwitch001 + * @tc.desc: Test status switch for multiple task under normal operation + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerVacuumTest, MultipleTaskNormalStatusSwitch001, TestSize.Level1) +{ + // Preset + MultiVerVacuum vacuum; + MultiVerVacuumExecutorStub databaseA(DbScale {1, 1, 2, 2}, 100); // 1, 2 For Scale, 100 For TimeCost, 1.7s in Total + MultiVerVacuumExecutorStub databaseB(DbScale {1, 1, 2, 2}, 100); // 1, 2 For Scale, 100 For TimeCost, 1.7s in Total + + /** + * @tc.steps: step1. launch dbTaskA for databaseA and dbTaskB for databaseB + * @tc.expected: step1. dbTaskA RUN_NING and dbTaskB RUN_WAIT + */ + int errCode = vacuum.Launch(DB_IDENTITY_A, &databaseA); + EXPECT_EQ(errCode, E_OK); + errCode = vacuum.Launch(DB_IDENTITY_B, &databaseB); + EXPECT_EQ(errCode, E_OK); + bool stepOne = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepOne, true); + stepOne = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::RUN_WAIT); + EXPECT_EQ(stepOne, true); + + /** + * @tc.steps: step2. pause dbTaskB + * @tc.expected: step2. dbTaskA RUN_NING and dbTaskB PAUSE_DONE + */ + errCode = vacuum.Pause(DB_IDENTITY_B); + EXPECT_EQ(errCode, E_OK); + bool stepTwo = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::RUN_NING, 1); // only 1 time + EXPECT_EQ(stepTwo, true); + stepTwo = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::PAUSE_DONE, 1); // only 1 time + EXPECT_EQ(stepTwo, true); + /** + * @tc.steps: step3. continue dbTaskB with autoRelaunch false + * @tc.expected: step3. dbTaskA RUN_NING and dbTaskB RUN_WAIT + */ + errCode = vacuum.Continue(DB_IDENTITY_B, false); + EXPECT_EQ(errCode, E_OK); + bool stepThree = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::RUN_NING, 1); // only 1 time + EXPECT_EQ(stepThree, true); + stepThree = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::RUN_WAIT, 1); // only 1 time + EXPECT_EQ(stepThree, true); + + /** + * @tc.steps: step4. Abort dbTaskA + * @tc.expected: step4. dbTaskA ABORT_DONE and dbTaskB RUN_NING + */ + errCode = vacuum.Abort(DB_IDENTITY_A); + EXPECT_EQ(errCode, E_OK); + bool stepFour = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::ABORT_DONE, 1); // only 1 time + EXPECT_EQ(stepFour, true); + stepFour = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepFour, true); + + /** + * @tc.steps: step5. Abort dbTaskB + * @tc.expected: step5. dbTaskA ABORT_DONE and dbTaskB ABORT_DONE + */ + errCode = vacuum.Abort(DB_IDENTITY_B); + EXPECT_EQ(errCode, E_OK); + bool stepFive = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::ABORT_DONE, 1); // only 1 time + EXPECT_EQ(stepFive, true); + stepFive = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::ABORT_DONE, 1); // only 1 time + EXPECT_EQ(stepFive, true); +} + +/** + * @tc.name: MultipleTaskNormalStatusSwitch002 + * @tc.desc: Test status switch for multiple task under normal operation + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerVacuumTest, MultipleTaskNormalStatusSwitch002, TestSize.Level2) +{ + // Preset + MultiVerVacuum vacuum; + MultiVerVacuumExecutorStub databaseA(DbScale {1, 1, 1, 1}, 30); // 1 For Scale, 30 For TimeCost, 330ms in Total + MultiVerVacuumExecutorStub databaseB(DbScale {1, 1, 2, 2}, 100); // 1, 2 For Scale, 100 For TimeCost, 1.7s in Total + MultiVerVacuumExecutorStub databaseC(DbScale {1, 1, 2, 2}, 100); // 1, 2 For Scale, 100 For TimeCost, 1.7s in Total + + /** + * @tc.steps: step1. launch dbTaskA,B,C for databaseA,B,C and wait dbTaskA,B FINISH + * @tc.expected: step1. dbTaskA FINISH and dbTaskB FINISH and dbTaskC RUN_NING + */ + int errCode = vacuum.Launch(DB_IDENTITY_A, &databaseA); + EXPECT_EQ(errCode, E_OK); + errCode = vacuum.Launch(DB_IDENTITY_B, &databaseB); + EXPECT_EQ(errCode, E_OK); + errCode = vacuum.Launch(DB_IDENTITY_C, &databaseC); + EXPECT_EQ(errCode, E_OK); + bool stepOne = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::FINISH, 3, 1000); // 3 time, 1000 ms + EXPECT_EQ(stepOne, true); + stepOne = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::FINISH, 3, 1000); // 3 time, 1000 ms + EXPECT_EQ(stepOne, true); + stepOne = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepOne, true); + + /** + * @tc.steps: step2. abnormal operation, Launch dbTaskB again without abort dbTaskB + * @tc.expected: step2. dbTaskA FINISH and dbTaskB RUN_WAIT and dbTaskC RUN_NING + */ + errCode = vacuum.Launch(DB_IDENTITY_B, &databaseB); + EXPECT_EQ(errCode, E_OK); + bool stepTwo = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::FINISH, 1); // only 1 time + EXPECT_EQ(stepTwo, true); + stepTwo = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::RUN_WAIT, 1); // only 1 time + EXPECT_EQ(stepTwo, true); + stepTwo = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::RUN_NING, 1); // only 1 time + EXPECT_EQ(stepTwo, true); + + /** + * @tc.steps: step3. AutoRelaunch dbTaskA + * @tc.expected: step3. dbTaskA RUN_WAIT and dbTaskB RUN_WAIT and dbTaskC RUN_NING + */ + errCode = vacuum.AutoRelaunchOnce(DB_IDENTITY_A); + EXPECT_EQ(errCode, E_OK); + bool stepThree = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::RUN_WAIT, 1); // only 1 time + EXPECT_EQ(stepThree, true); + stepThree = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::RUN_WAIT, 1); // only 1 time + EXPECT_EQ(stepThree, true); + stepThree = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::RUN_NING, 1); // only 1 time + EXPECT_EQ(stepThree, true); + + /** + * @tc.steps: step4. wait dbTaskC FINISH + * @tc.expected: step4. dbTaskA RUN_WAIT and dbTaskB RUN_NING and dbTaskC FINISH + */ + bool stepFour = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::FINISH, 3, 1000); // 3 time, 1000 ms + EXPECT_EQ(stepFour, true); + stepFour = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::RUN_WAIT, 1); // only 1 time + EXPECT_EQ(stepFour, true); + stepFour = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::RUN_NING); + EXPECT_EQ(stepFour, true); + + /** + * @tc.steps: step5. Abort dbTaskB and dbTaskB + * @tc.expected: step5. dbTaskA ABORT_DONE and dbTaskB ABORT_DONE and dbTaskC FINISH + */ + vacuum.Abort(DB_IDENTITY_A); + vacuum.Abort(DB_IDENTITY_B); + bool stepFive = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_A, VacuumTaskStatus::ABORT_DONE, 1); // only 1 time + EXPECT_EQ(stepFive, true); + stepFive = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_B, VacuumTaskStatus::ABORT_DONE, 1); // only 1 time + EXPECT_EQ(stepFive, true); + stepFive = CheckVacuumTaskStatus(vacuum, DB_IDENTITY_C, VacuumTaskStatus::FINISH, 1); // only 1 time + EXPECT_EQ(stepFive, true); +} diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_query_object_helper_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_query_object_helper_test.cpp new file mode 100644 index 00000000..eabfbabd --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_query_object_helper_test.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_errno.h" +#include "get_query_info.h" +#include "log_print.h" +#include "query_object.h" + +using namespace testing::ext; +using namespace DistributedDB; + +namespace { +const std::string VALID_SCHEMA_FULL_DEFINE = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":{" + "\"field_name3\":\"INTEGER, NOT NULL\"," + "\"field_name4\":\"LONG, DEFAULT 100\"," + "\"field_name5\":\"DOUBLE, NOT NULL, DEFAULT 3.14\"," + "\"field_name6\":\"STRING, NOT NULL, DEFAULT '3.1415'\"," + "\"field_name7\":[]," + "\"field_name8\":{}" + "}" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\", \"$.field_name2.field_name6\"]}"; +const std::string TEST_FIELD_NAME = "$.field_name2.field_name6"; + +static void GetQuerySql(const Query &query) +{ + QueryObject queryObj(query); + + SchemaObject schema; + schema.ParseFromSchemaString(VALID_SCHEMA_FULL_DEFINE); + queryObj.SetSchema(schema); + + int errCode = E_OK; + SqliteQueryHelper helper = queryObj.GetQueryHelper(errCode); + ASSERT_EQ(errCode, E_OK); + EXPECT_EQ(errCode, E_OK); + std::string sql; + helper.GetQuerySql(sql, false); + LOGD("[UNITTEST][sql] = [%s]", sql.c_str()); +} +} + +class DistributedDBQueryObjectHelperTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBQueryObjectHelperTest::SetUpTestCase(void) +{ +} + +void DistributedDBQueryObjectHelperTest::TearDownTestCase(void) +{ +} + +void DistributedDBQueryObjectHelperTest::SetUp(void) +{ +} + +void DistributedDBQueryObjectHelperTest::TearDown(void) +{ +} + +/** + * @tc.name: Query001 + * @tc.desc: Check the legal single query operation to see if the generated container is correct + * @tc.type: FUNC + * @tc.require: AR000DR9K6 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBQueryObjectHelperTest, Query001, TestSize.Level1) +{ + Query query1 = Query::Select().NotEqualTo(TEST_FIELD_NAME, 123); // random test data + GetQuerySql(query1); + + Query query2 = Query::Select().EqualTo(TEST_FIELD_NAME, true); + GetQuerySql(query2); + + Query query3 = Query::Select().GreaterThan(TEST_FIELD_NAME, 0); + GetQuerySql(query3); + + Query query4 = Query::Select().LessThan(TEST_FIELD_NAME, INT_MAX); + GetQuerySql(query4); + + Query query5 = Query::Select().GreaterThanOrEqualTo(TEST_FIELD_NAME, 1.56); // random test data + GetQuerySql(query5); + + Query query6 = Query::Select().LessThanOrEqualTo(TEST_FIELD_NAME, 100); // random test data + GetQuerySql(query6); + + std::string testValue = "employee.sun.yong"; + Query query7 = Query::Select().Like(TEST_FIELD_NAME, testValue); + GetQuerySql(query7); + + Query query8 = Query::Select().NotLike(TEST_FIELD_NAME, "testValue"); + GetQuerySql(query8); + + std::vector fieldValues{1, 1, 1}; + Query query9 = Query::Select().In(TEST_FIELD_NAME, fieldValues); + GetQuerySql(query9); + + Query query10 = Query::Select().NotIn(TEST_FIELD_NAME, fieldValues); + GetQuerySql(query10); + + Query query11 = Query::Select().OrderBy(TEST_FIELD_NAME, false); + GetQuerySql(query11); + + Query query12 = Query::Select().Limit(1, 2); + GetQuerySql(query12); + + Query query13 = Query::Select().IsNull(TEST_FIELD_NAME); + GetQuerySql(query13); +} + +/** + * @tc.name: Query002 + * @tc.desc: Check for illegal query conditions can not get helper transfer to sql + * @tc.type: FUNC + * @tc.require: AR000DR9K6 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBQueryObjectHelperTest, Query002, TestSize.Level1) +{ + float testValue = 1.1; + Query query = Query::Select().NotEqualTo(".test", testValue); + QueryObject queryObj(query); + SchemaObject schema; + schema.ParseFromSchemaString(VALID_SCHEMA_FULL_DEFINE); + queryObj.SetSchema(schema); + int errCode = E_OK; + SqliteQueryHelper helper = queryObj.GetQueryHelper(errCode); // invalid field name + EXPECT_NE(errCode, E_OK); + + Query query1 = Query::Select().GreaterThan(TEST_FIELD_NAME, true); // bool compare + QueryObject queryObj1(query1); + queryObj1.SetSchema(schema); + SqliteQueryHelper helper1 = queryObj1.GetQueryHelper(errCode); + EXPECT_NE(errCode, E_OK); + + Query query2 = Query::Select().LessThan("$.field_name2.field_name4", true); + QueryObject queryObj2(query2); + queryObj2.SetSchema(schema); + SqliteQueryHelper helper2 = queryObj2.GetQueryHelper(errCode); + EXPECT_NE(errCode, E_OK); +} + +/** + * @tc.name: Query003 + * @tc.desc: Check combination condition transfer to sql + * @tc.type: FUNC + * @tc.require: AR000DR9K6 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBQueryObjectHelperTest, Query003, TestSize.Level1) +{ + Query query = Query::Select().EqualTo(TEST_FIELD_NAME, true).And().GreaterThan(TEST_FIELD_NAME, 1); + GetQuerySql(query); + + Query query1 = Query::Select().GreaterThan(TEST_FIELD_NAME, 1).OrderBy(TEST_FIELD_NAME); + GetQuerySql(query1); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_relational_get_data_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_relational_get_data_test.cpp new file mode 100644 index 00000000..30bf7339 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_relational_get_data_test.cpp @@ -0,0 +1,1475 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include + +#include "data_transformer.h" +#include "db_common.h" +#include "db_constant.h" +#include "db_errno.h" +#include "db_types.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "generic_single_ver_kv_entry.h" +#include "kvdb_properties.h" +#include "log_print.h" +#include "relational_schema_object.h" +#include "relational_store_delegate.h" +#include "relational_store_instance.h" +#include "relational_store_manager.h" +#include "relational_store_sqlite_ext.h" +#include "relational_sync_able_storage.h" +#include "sqlite_relational_store.h" +#include "sqlite_utils.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { +string g_testDir; +string g_storePath; +string g_storeID = "dftStoreID"; +const string g_tableName { "data" }; +DistributedDB::RelationalStoreManager g_mgr(APP_ID, USER_ID); +RelationalStoreDelegate *g_delegate = nullptr; +IRelationalStore *g_store = nullptr; + +void CreateDBAndTable() +{ + sqlite3 *db = nullptr; + int errCode = sqlite3_open(g_storePath.c_str(), &db); + if (errCode != SQLITE_OK) { + LOGE("open db failed:%d", errCode); + sqlite3_close(db); + return; + } + + const string sql = + "PRAGMA journal_mode=WAL;" + "CREATE TABLE " + g_tableName + "(key INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, value INTEGER);"; + char *zErrMsg = nullptr; + errCode = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &zErrMsg); + if (errCode != SQLITE_OK) { + LOGE("sql error:%s", zErrMsg); + sqlite3_free(zErrMsg); + } + sqlite3_close(db); +} + +int AddOrUpdateRecord(int64_t key, int64_t value) +{ + sqlite3 *db = nullptr; + int errCode = sqlite3_open(g_storePath.c_str(), &db); + if (errCode == SQLITE_OK) { + const string sql = + "INSERT OR REPLACE INTO " + g_tableName + " VALUES(" + to_string(key) + "," + to_string(value) + ");"; + errCode = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr); + } + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + sqlite3_close(db); + return errCode; +} + +int GetLogData(int key, uint64_t &flag, Timestamp ×tamp, const DeviceID &device = "") +{ + string tableName = g_tableName; + if (!device.empty()) { + } + const string sql = "SELECT timestamp, flag \ + FROM " + g_tableName + " as a, " + DBConstant::RELATIONAL_PREFIX + g_tableName + "_log as b \ + WHERE a.key=? AND a.rowid=b.data_key;"; + + sqlite3 *db = nullptr; + sqlite3_stmt *statement = nullptr; + int errCode = sqlite3_open(g_storePath.c_str(), &db); + if (errCode != SQLITE_OK) { + LOGE("open db failed:%d", errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + goto END; + } + errCode = SQLiteUtils::GetStatement(db, sql, statement); + if (errCode != E_OK) { + goto END; + } + errCode = SQLiteUtils::BindInt64ToStatement(statement, 1, key); // 1 means key's index + if (errCode != E_OK) { + goto END; + } + errCode = SQLiteUtils::StepWithRetry(statement, false); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + timestamp = static_cast(sqlite3_column_int64(statement, 0)); + flag = static_cast(sqlite3_column_int64(statement, 1)); + errCode = E_OK; + } else if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + errCode = -E_NOT_FOUND; + } + +END: + SQLiteUtils::ResetStatement(statement, true, errCode); + sqlite3_close(db); + return errCode; +} + +void InitStoreProp(const std::string &storePath, const std::string &appId, const std::string &userId, + RelationalDBProperties &properties) +{ + properties.SetStringProp(RelationalDBProperties::DATA_DIR, storePath); + properties.SetStringProp(RelationalDBProperties::APP_ID, appId); + properties.SetStringProp(RelationalDBProperties::USER_ID, userId); + properties.SetStringProp(RelationalDBProperties::STORE_ID, g_storeID); + std::string identifier = userId + "-" + appId + "-" + g_storeID; + std::string hashIdentifier = DBCommon::TransferHashString(identifier); + properties.SetStringProp(RelationalDBProperties::IDENTIFIER_DATA, hashIdentifier); +} + +const RelationalSyncAbleStorage *GetRelationalStore() +{ + RelationalDBProperties properties; + InitStoreProp(g_storePath, APP_ID, USER_ID, properties); + int errCode = E_OK; + g_store = RelationalStoreInstance::GetDataBase(properties, errCode); + if (g_store == nullptr) { + LOGE("Get db failed:%d", errCode); + return nullptr; + } + return static_cast(g_store)->GetStorageEngine(); +} + +int GetCount(sqlite3 *db, const string &sql, size_t &count) +{ + sqlite3_stmt *stmt = nullptr; + int errCode = SQLiteUtils::GetStatement(db, sql, stmt); + if (errCode != E_OK) { + return errCode; + } + errCode = SQLiteUtils::StepWithRetry(stmt, false); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + count = static_cast(sqlite3_column_int64(stmt, 0)); + errCode = E_OK; + } + SQLiteUtils::ResetStatement(stmt, true, errCode); + return errCode; +} + +void ExpectCount(sqlite3 *db, const string &sql, size_t expectCount) +{ + size_t count = 0; + ASSERT_EQ(GetCount(db, sql, count), E_OK); + EXPECT_EQ(count, expectCount); +} + +std::string GetOneText(sqlite3 *db, const string &sql) +{ + std::string result; + sqlite3_stmt *stmt = nullptr; + int errCode = SQLiteUtils::GetStatement(db, sql, stmt); + if (errCode != E_OK) { + return result; + } + errCode = SQLiteUtils::StepWithRetry(stmt, false); + if (errCode == SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)) { + SQLiteUtils::GetColumnTextValue(stmt, 0, result); + } + SQLiteUtils::ResetStatement(stmt, true, errCode); + return result; +} + +int PutBatchData(uint32_t totalCount, uint32_t valueSize) +{ + sqlite3 *db = nullptr; + sqlite3_stmt *stmt = nullptr; + const string sql = "INSERT INTO " + g_tableName + " VALUES(?,?);"; + int errCode = sqlite3_open(g_storePath.c_str(), &db); + if (errCode != SQLITE_OK) { + goto ERROR; + } + EXPECT_EQ(sqlite3_exec(db, "BEGIN IMMEDIATE TRANSACTION", nullptr, nullptr, nullptr), SQLITE_OK); + errCode = SQLiteUtils::GetStatement(db, sql, stmt); + if (errCode != E_OK) { + goto ERROR; + } + for (uint32_t i = 0; i < totalCount; i++) { + errCode = SQLiteUtils::BindBlobToStatement(stmt, 2, Value(valueSize, 'a'), false); // 2 means value index + if (errCode != E_OK) { + break; + } + errCode = SQLiteUtils::StepWithRetry(stmt); + if (errCode != SQLiteUtils::MapSQLiteErrno(SQLITE_DONE)) { + break; + } + errCode = E_OK; + SQLiteUtils::ResetStatement(stmt, false, errCode); + } + +ERROR: + if (errCode == E_OK) { + EXPECT_EQ(sqlite3_exec(db, "COMMIT TRANSACTION", nullptr, nullptr, nullptr), SQLITE_OK); + } else { + EXPECT_EQ(sqlite3_exec(db, "ROLLBACK TRANSACTION", nullptr, nullptr, nullptr), SQLITE_OK); + } + SQLiteUtils::ResetStatement(stmt, true, errCode); + errCode = SQLiteUtils::MapSQLiteErrno(errCode); + sqlite3_close(db); + return errCode; +} + +void ExecSqlAndAssertOK(sqlite3 *db, const std::string &sql) +{ + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); +} + +void ExecSqlAndAssertOK(sqlite3 *db, const initializer_list &sqlList) +{ + for (const auto &sql : sqlList) { + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + } +} + +void ExpectMissQueryCnt(const std::vector &entries, size_t expectCount) +{ + size_t count = 0; + for (auto iter = entries.begin(); iter != entries.end(); ++iter) { + if (((*iter)->GetFlag() & DataItem::REMOTE_DEVICE_DATA_MISS_QUERY) == 0) { + count++; + } + auto nextOne = std::next(iter, 1); + if (nextOne != entries.end()) { + EXPECT_LT((*iter)->GetTimestamp(), (*nextOne)->GetTimestamp()); + } + } + EXPECT_EQ(count, expectCount); +}; +} + +class DistributedDBRelationalGetDataTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBRelationalGetDataTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_storePath = g_testDir + "/getDataTest.db"; + LOGI("The test db is:%s", g_testDir.c_str()); +} + +void DistributedDBRelationalGetDataTest::TearDownTestCase(void) +{} + +void DistributedDBRelationalGetDataTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + CreateDBAndTable(); +} + +void DistributedDBRelationalGetDataTest::TearDown(void) +{ + if (g_delegate != nullptr) { + EXPECT_EQ(g_mgr.CloseStore(g_delegate), DBStatus::OK); + g_delegate = nullptr; + } + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error."); + } + return; +} + +/** + * @tc.name: LogTbl1 + * @tc.desc: When put sync data to relational store, trigger generate log. + * @tc.type: FUNC + * @tc.require: AR000GK58G + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, LogTbl1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + + /** + * @tc.steps: step1. Put data. + * @tc.expected: Succeed, return OK. + */ + int insertKey = 1; + int insertValue = 1; + EXPECT_EQ(AddOrUpdateRecord(insertKey, insertValue), E_OK); + + /** + * @tc.steps: step2. Check log record. + * @tc.expected: Record exists. + */ + uint64_t flag = 0; + Timestamp timestamp1 = 0; + EXPECT_EQ(GetLogData(insertKey, flag, timestamp1), E_OK); + EXPECT_EQ(flag, DataItem::LOCAL_FLAG); + EXPECT_NE(timestamp1, 0ULL); +} + +/** + * @tc.name: GetSyncData1 + * @tc.desc: GetSyncData interface + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, GetSyncData1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + + /** + * @tc.steps: step1. Put 500 records. + * @tc.expected: Succeed, return OK. + */ + const size_t RECORD_COUNT = 500; + for (size_t i = 0; i < RECORD_COUNT; ++i) { + EXPECT_EQ(AddOrUpdateRecord(i, i), E_OK); + } + + /** + * @tc.steps: step2. Get all data. + * @tc.expected: Succeed and the count is right. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + QueryObject query(Query::Select(g_tableName)); + std::vector entries; + DataSizeSpecInfo sizeInfo {MTU_SIZE, 50}; + + int errCode = store->GetSyncData(query, SyncTimeRange {}, sizeInfo, token, entries); + auto count = entries.size(); + SingleVerKvEntry::Release(entries); + EXPECT_EQ(errCode, -E_UNFINISHED); + while (token != nullptr) { + errCode = store->GetSyncDataNext(entries, token, sizeInfo); + count += entries.size(); + SingleVerKvEntry::Release(entries); + EXPECT_TRUE(errCode == E_OK || errCode == -E_UNFINISHED); + } + EXPECT_EQ(count, RECORD_COUNT); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: GetSyncData2 + * @tc.desc: GetSyncData interface. For overlarge data(over 4M), ignore it. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, GetSyncData2, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + + /** + * @tc.steps: step1. Put 10 records.(1M + 2M + 3M + 4M + 5M) * 2. + * @tc.expected: Succeed, return OK. + */ + for (int i = 1; i <= 5; ++i) { + EXPECT_EQ(PutBatchData(1, i * 1024 * 1024), E_OK); // 1024*1024 equals 1M. + } + for (int i = 1; i <= 5; ++i) { + EXPECT_EQ(PutBatchData(1, i * 1024 * 1024), E_OK); // 1024*1024 equals 1M. + } + + /** + * @tc.steps: step2. Get all data. + * @tc.expected: Succeed and the count is 6. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + QueryObject query(Query::Select(g_tableName)); + std::vector entries; + + const size_t EXPECT_COUNT = 6; // expect 6 records. + DataSizeSpecInfo sizeInfo; + sizeInfo.blockSize = 100 * 1024 * 1024; // permit 100M. + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, sizeInfo, token, entries), E_OK); + EXPECT_EQ(entries.size(), EXPECT_COUNT); + SingleVerKvEntry::Release(entries); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: GetSyncData3 + * @tc.desc: GetSyncData interface. For deleted data. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, GetSyncData3, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + + /** + * @tc.steps: step1. Create distributed table "dataPlus". + * @tc.expected: Succeed, return OK. + */ + const string tableName = g_tableName + "Plus"; + std::string sql = "CREATE TABLE " + tableName + "(key INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, value INTEGER);"; + sqlite3 *db = nullptr; + ASSERT_EQ(sqlite3_open(g_storePath.c_str(), &db), SQLITE_OK); + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + ASSERT_EQ(g_delegate->CreateDistributedTable(tableName), DBStatus::OK); + + /** + * @tc.steps: step2. Put 5 records with different type into "dataPlus" table. Put 5 records into "data" table. + * @tc.expected: Succeed, return OK. + */ + const size_t RECORD_COUNT = 5; // 5 records + ExecSqlAndAssertOK(db, {"INSERT INTO " + tableName + " VALUES(NULL, 1);", + "INSERT INTO " + tableName + " VALUES(NULL, 0.01);", + "INSERT INTO " + tableName + " VALUES(NULL, NULL);", + "INSERT INTO " + tableName + " VALUES(NULL, 'This is a text.');", + "INSERT INTO " + tableName + " VALUES(NULL, x'0123456789');"}); + + /** + * @tc.steps: step3. Get all data from "dataPlus" table. + * @tc.expected: Succeed and the count is right. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + QueryObject query(Query::Select(tableName)); + std::vector entries; + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(entries.size(), RECORD_COUNT); + + /** + * @tc.steps: step4. Put data into "data" table from deviceA and deviceB + * @tc.expected: Succeed, return OK. + */ + QueryObject gQuery(Query::Select(g_tableName)); + DeviceID deviceA = "deviceA"; + ASSERT_EQ(E_OK, SQLiteUtils::CreateSameStuTable(db, store->GetSchemaInfo().GetTable(g_tableName), + DBCommon::GetDistributedTableName(deviceA, g_tableName))); + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(gQuery, entries, deviceA), E_OK); + + DeviceID deviceB = "deviceB"; + auto rEntries = std::vector(entries.rbegin(), entries.rend()); + ASSERT_EQ(E_OK, SQLiteUtils::CreateSameStuTable(db, store->GetSchemaInfo().GetTable(g_tableName), + DBCommon::GetDistributedTableName(deviceB, g_tableName))); + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(gQuery, rEntries, deviceB), E_OK); + rEntries.clear(); + SingleVerKvEntry::Release(entries); + + /** + * @tc.steps: step5. Delete 2 "dataPlus" data from deviceA. + * @tc.expected: Succeed. + */ + ExecSqlAndAssertOK(db, "DELETE FROM " + tableName + " WHERE rowid<=2;"); + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(entries.size(), RECORD_COUNT); + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(gQuery, entries, deviceA), E_OK); + SingleVerKvEntry::Release(entries); + + /** + * @tc.steps: step6. Check data. + * @tc.expected: 2 data in the from deviceA are deleted and all data from deviceB are not deleted. + */ + ExpectCount(db, "SELECT count(*) FROM " + DBConstant::RELATIONAL_PREFIX + g_tableName + + "_log WHERE flag&0x01=0x01;", 2U); // 2 deleted log + ExpectCount(db, "SELECT count(*) FROM " + DBConstant::RELATIONAL_PREFIX + g_tableName + "_" + + DBCommon::TransferStringToHex(DBCommon::TransferHashString(deviceA)) + ";", 3U); // 3 records in A + ExpectCount(db, "SELECT count(*) FROM " + DBConstant::RELATIONAL_PREFIX + g_tableName + "_" + + DBCommon::TransferStringToHex(DBCommon::TransferHashString(deviceB)) + ";", RECORD_COUNT); // 5 records in B + + sqlite3_close(db); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: GetQuerySyncData1 + * @tc.desc: GetSyncData interface. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, GetQuerySyncData1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + + /** + * @tc.steps: step1. Put 100 records. + * @tc.expected: Succeed, return OK. + */ + const size_t RECORD_COUNT = 100; // 100 records. + for (size_t i = 0; i < RECORD_COUNT; ++i) { + EXPECT_EQ(AddOrUpdateRecord(i, i), E_OK); + } + + /** + * @tc.steps: step2. Get data limit 80, offset 30. + * @tc.expected: Get 70 records. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + const unsigned int LIMIT = 80; // limit as 80. + const unsigned int OFFSET = 30; // offset as 30. + const unsigned int EXPECT_COUNT = RECORD_COUNT - OFFSET; // expect 70 records. + QueryObject query(Query::Select(g_tableName).Limit(LIMIT, OFFSET)); + std::vector entries; + + int errCode = store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries); + EXPECT_EQ(entries.size(), EXPECT_COUNT); + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(token, nullptr); + SingleVerKvEntry::Release(entries); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: GetQuerySyncData2 + * @tc.desc: GetSyncData interface. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, GetQuerySyncData2, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + + /** + * @tc.steps: step1. Put 100 records. + * @tc.expected: Succeed, return OK. + */ + const size_t RECORD_COUNT = 100; // 100 records. + for (size_t i = 0; i < RECORD_COUNT; ++i) { + EXPECT_EQ(AddOrUpdateRecord(i, i), E_OK); + } + + /** + * @tc.steps: step2. Get record whose key is not equal to 10 and value is not equal to 20, order by key desc. + * @tc.expected: Succeed, Get 98 records. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + + Query query = Query::Select(g_tableName).NotEqualTo("key", 10).And().NotEqualTo("value", 20).OrderBy("key", false); + QueryObject queryObj(query); + queryObj.SetSchema(store->GetSchemaInfo()); + + std::vector entries; + EXPECT_EQ(store->GetSyncData(queryObj, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(token, nullptr); + size_t expectCount = 98; // expect 98 records. + EXPECT_EQ(entries.size(), expectCount); + for (auto iter = entries.begin(); iter != entries.end(); ++iter) { + auto nextOne = std::next(iter, 1); + if (nextOne != entries.end()) { + EXPECT_LT((*iter)->GetTimestamp(), (*nextOne)->GetTimestamp()); + } + } + SingleVerKvEntry::Release(entries); + + /** + * @tc.steps: step3. Get record whose key is equal to 10 or value is equal to 20, order by key asc. + * @tc.expected: Succeed, Get 98 records. + */ + query = Query::Select(g_tableName).EqualTo("key", 10).Or().EqualTo("value", 20).OrderBy("key", true); + queryObj = QueryObject(query); + queryObj.SetSchema(store->GetSchemaInfo()); + + EXPECT_EQ(store->GetSyncData(queryObj, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(token, nullptr); + expectCount = 2; // expect 2 records. + EXPECT_EQ(entries.size(), expectCount); + for (auto iter = entries.begin(); iter != entries.end(); ++iter) { + auto nextOne = std::next(iter, 1); + if (nextOne != entries.end()) { + EXPECT_LT((*iter)->GetTimestamp(), (*nextOne)->GetTimestamp()); + } + } + SingleVerKvEntry::Release(entries); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: GetIncorrectTypeData1 + * @tc.desc: GetSyncData and PutSyncDataWithQuery interface. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, GetIncorrectTypeData1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + + /** + * @tc.steps: step1. Create 2 index for table "data". + * @tc.expected: Succeed, return OK. + */ + sqlite3 *db = nullptr; + ASSERT_EQ(sqlite3_open(g_storePath.c_str(), &db), SQLITE_OK); + + ExecSqlAndAssertOK(db, {"CREATE INDEX index1 ON " + g_tableName + "(value);", + "CREATE UNIQUE INDEX index2 ON " + g_tableName + "(value,key);"}); + + /** + * @tc.steps: step2. Create distributed table "dataPlus". + * @tc.expected: Succeed, return OK. + */ + const string tableName = g_tableName + "Plus"; + string sql = "CREATE TABLE " + tableName + "(key INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, value INTEGER);"; + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + ASSERT_EQ(g_delegate->CreateDistributedTable(tableName), DBStatus::OK); + + /** + * @tc.steps: step3. Put 5 records with different type into "dataPlus" table. + * @tc.expected: Succeed, return OK. + */ + const size_t RECORD_COUNT = 5; // 5 sqls + ExecSqlAndAssertOK(db, {"INSERT INTO " + tableName + " VALUES(NULL, 1);", + "INSERT INTO " + tableName + " VALUES(NULL, 0.01);", + "INSERT INTO " + tableName + " VALUES(NULL, NULL);", + "INSERT INTO " + tableName + " VALUES(NULL, 'This is a text.');", + "INSERT INTO " + tableName + " VALUES(NULL, x'0123456789');"}); + + /** + * @tc.steps: step4. Get all data from "dataPlus" table. + * @tc.expected: Succeed and the count is right. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + QueryObject query(Query::Select(tableName)); + std::vector entries; + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(entries.size(), RECORD_COUNT); + + /** + * @tc.steps: step5. Put data into "data" table from deviceA. + * @tc.expected: Succeed, return OK. + */ + QueryObject queryPlus(Query::Select(g_tableName)); + const DeviceID deviceID = "deviceA"; + ASSERT_EQ(E_OK, SQLiteUtils::CreateSameStuTable(db, store->GetSchemaInfo().GetTable(g_tableName), + DBCommon::GetDistributedTableName(deviceID, g_tableName))); + ASSERT_EQ(E_OK, SQLiteUtils::CloneIndexes(db, g_tableName, + DBCommon::GetDistributedTableName(deviceID, g_tableName))); + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(queryPlus, entries, deviceID), E_OK); + SingleVerKvEntry::Release(entries); + + /** + * @tc.steps: step6. Check data. + * @tc.expected: All data in the two tables are same. + */ + ExpectCount(db, "SELECT count(*) FROM " + tableName + " as a, " + DBConstant::RELATIONAL_PREFIX + g_tableName + + "_" + DBCommon::TransferStringToHex(DBCommon::TransferHashString(deviceID)) + " as b " + "WHERE a.key=b.key AND (a.value=b.value OR (a.value is NULL AND b.value is NULL));", RECORD_COUNT); + + /** + * @tc.steps: step7. Check index. + * @tc.expected: 2 index for deviceA's data table exists. + */ + ExpectCount(db, + "SELECT count(*) FROM sqlite_master WHERE type='index' AND tbl_name='" + DBConstant::RELATIONAL_PREFIX + + g_tableName + "_" + DBCommon::TransferStringToHex(DBCommon::TransferHashString(deviceID)) + "'", 2U); // 2 index + sqlite3_close(db); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: UpdateData1 + * @tc.desc: UpdateData succeed when the table has primary key. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, UpdateData1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + + /** + * @tc.steps: step1. Create distributed table "dataPlus". + * @tc.expected: Succeed, return OK. + */ + const string tableName = g_tableName + "Plus"; + std::string sql = "CREATE TABLE " + tableName + "(key INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, value INTEGER);"; + sqlite3 *db = nullptr; + ASSERT_EQ(sqlite3_open(g_storePath.c_str(), &db), SQLITE_OK); + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + ASSERT_EQ(g_delegate->CreateDistributedTable(tableName), DBStatus::OK); + + /** + * @tc.steps: step2. Put 5 records with different type into "dataPlus" table. + * @tc.expected: Succeed, return OK. + */ + vector sqls = { + "INSERT INTO " + tableName + " VALUES(NULL, 1);", + "INSERT INTO " + tableName + " VALUES(NULL, 0.01);", + "INSERT INTO " + tableName + " VALUES(NULL, NULL);", + "INSERT INTO " + tableName + " VALUES(NULL, 'This is a text.');", + "INSERT INTO " + tableName + " VALUES(NULL, x'0123456789');", + }; + const size_t RECORD_COUNT = sqls.size(); + for (const auto &sql : sqls) { + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + } + + /** + * @tc.steps: step3. Get all data from "dataPlus" table. + * @tc.expected: Succeed and the count is right. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + QueryObject query(Query::Select(tableName)); + std::vector entries; + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(entries.size(), RECORD_COUNT); + + /** + * @tc.steps: step4. Put data into "data" table from deviceA for 10 times. + * @tc.expected: Succeed, return OK. + */ + query = QueryObject(Query::Select(g_tableName)); + const DeviceID deviceID = "deviceA"; + ASSERT_EQ(E_OK, SQLiteUtils::CreateSameStuTable(db, store->GetSchemaInfo().GetTable(g_tableName), + DBCommon::GetDistributedTableName(deviceID, g_tableName))); + for (uint32_t i = 0; i < 10; ++i) { // 10 for test. + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(query, entries, deviceID), E_OK); + } + SingleVerKvEntry::Release(entries); + + /** + * @tc.steps: step5. Check data. + * @tc.expected: There is 5 data in table. + */ + sql = "SELECT count(*) FROM " + DBConstant::RELATIONAL_PREFIX + g_tableName + "_" + + DBCommon::TransferStringToHex(DBCommon::TransferHashString(deviceID)) + ";"; + size_t count = 0; + EXPECT_EQ(GetCount(db, sql, count), E_OK); + EXPECT_EQ(count, RECORD_COUNT); + + sql = "SELECT count(*) FROM " + DBConstant::RELATIONAL_PREFIX + g_tableName + "_log;"; + count = 0; + EXPECT_EQ(GetCount(db, sql, count), E_OK); + EXPECT_EQ(count, RECORD_COUNT); + + sqlite3_close(db); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: UpdateDataWithMulDevData1 + * @tc.desc: UpdateData succeed when there is multiple devices data exists. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, UpdateDataWithMulDevData1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + /** + * @tc.steps: step1. Create distributed table "dataPlus". + * @tc.expected: Succeed, return OK. + */ + const string tableName = g_tableName + "Plus"; + std::string sql = "CREATE TABLE " + tableName + "(key INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, value INTEGER);"; + sqlite3 *db = nullptr; + ASSERT_EQ(sqlite3_open(g_storePath.c_str(), &db), SQLITE_OK); + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + ASSERT_EQ(g_delegate->CreateDistributedTable(tableName), DBStatus::OK); + /** + * @tc.steps: step2. Put k1v1 into "dataPlus" table. + * @tc.expected: Succeed, return OK. + */ + sql = "INSERT INTO " + tableName + " VALUES(1, 1);"; // k1v1 + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + /** + * @tc.steps: step3. Get k1v1 from "dataPlus" table. + * @tc.expected: Succeed and the count is right. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + QueryObject query(Query::Select(tableName)); + std::vector entries; + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + /** + * @tc.steps: step4. Put k1v1 into "data" table from deviceA. + * @tc.expected: Succeed, return OK. + */ + query = QueryObject(Query::Select(g_tableName)); + const DeviceID deviceID = "deviceA"; + ASSERT_EQ(E_OK, SQLiteUtils::CreateSameStuTable(db, store->GetSchemaInfo().GetTable(g_tableName), + DBCommon::GetDistributedTableName(deviceID, g_tableName))); + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(query, entries, deviceID), E_OK); + SingleVerKvEntry::Release(entries); + /** + * @tc.steps: step4. Put k1v1 into "data" table. + * @tc.expected: Succeed, return OK. + */ + EXPECT_EQ(AddOrUpdateRecord(1, 1), E_OK); // k1v1 + /** + * @tc.steps: step5. Change k1v1 to k1v2 + * @tc.expected: Succeed, return OK. + */ + sql = "UPDATE " + g_tableName + " SET value=2 WHERE key=1;"; // k1v1 + EXPECT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); // change k1v1 to k1v2 + + sqlite3_close(db); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: MissQuery1 + * @tc.desc: Check REMOTE_DEVICE_DATA_MISS_QUERY flag succeed. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, MissQuery1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + /** + * @tc.steps: step1. Create distributed table "dataPlus". + * @tc.expected: Succeed, return OK. + */ + const string tableName = g_tableName + "Plus"; + std::string sql = "CREATE TABLE " + tableName + "(key INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, value INTEGER);"; + sqlite3 *db = nullptr; + ASSERT_EQ(sqlite3_open(g_storePath.c_str(), &db), SQLITE_OK); + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + ASSERT_EQ(g_delegate->CreateDistributedTable(tableName), DBStatus::OK); + + /** + * @tc.steps: step2. Put 5 records with different type into "dataPlus" table. + * @tc.expected: Succeed, return OK. + */ + ExecSqlAndAssertOK(db, {"INSERT INTO " + tableName + " VALUES(NULL, 1);", + "INSERT INTO " + tableName + " VALUES(NULL, 2);", "INSERT INTO " + tableName + " VALUES(NULL, 3);", + "INSERT INTO " + tableName + " VALUES(NULL, 4);", "INSERT INTO " + tableName + " VALUES(NULL, 5);"}); + + /** + * @tc.steps: step3. Get all data from "dataPlus" table. + * @tc.expected: Succeed and the count is right. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + SyncTimeRange timeRange; + QueryObject query(Query::Select(tableName).EqualTo("value", 2).Or().EqualTo("value", 3).Or().EqualTo("value", 4)); + std::vector entries; + EXPECT_EQ(store->GetSyncData(query, timeRange, DataSizeSpecInfo {}, token, entries), E_OK); + timeRange.lastQueryTime = (*(entries.rbegin()))->GetTimestamp(); + EXPECT_EQ(entries.size(), 3U); // 3 for test + + /** + * @tc.steps: step4. Put data into "data" table from deviceA for 10 times. + * @tc.expected: Succeed, return OK. + */ + query = QueryObject(Query::Select(g_tableName)); + const DeviceID deviceID = "deviceA"; + ASSERT_EQ(E_OK, SQLiteUtils::CreateSameStuTable(db, store->GetSchemaInfo().GetTable(g_tableName), + DBCommon::GetDistributedTableName(deviceID, g_tableName))); + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(query, entries, deviceID), E_OK); + SingleVerKvEntry::Release(entries); + + /** + * @tc.steps: step5. Check data. + * @tc.expected: There is 3 data in table. + */ + std::string getDataSql = "SELECT count(*) FROM " + DBConstant::RELATIONAL_PREFIX + g_tableName + "_" + + DBCommon::TransferStringToHex(DBCommon::TransferHashString(deviceID)) + ";"; + ExpectCount(db, getDataSql, 3); // 2,3,4 + + std::string getLogSql = "SELECT count(*) FROM " + DBConstant::RELATIONAL_PREFIX + g_tableName + "_log;"; + ExpectCount(db, getLogSql, 3); // 2,3,4 + + /** + * @tc.steps: step6. Update data. k2v2 to k2v102, k3v3 to k3v103, k4v4 to k4v104. + * @tc.expected: Update succeed. + */ + ExecSqlAndAssertOK(db, {"INSERT OR REPLACE INTO " + tableName + " VALUES(2, 102);", + "UPDATE " + tableName + " SET value=103 WHERE value=3;", + "DELETE FROM " + tableName + " WHERE key=4;", + "INSERT INTO " + tableName + " VALUES(4, 104);"}); + + /** + * @tc.steps: step7. Get all data from "dataPlus" table. + * @tc.expected: Succeed and the count is right. + */ + query = QueryObject(Query::Select(tableName).EqualTo("value", 2).Or().EqualTo("value", 3).Or().EqualTo("value", 4)); + EXPECT_EQ(store->GetSyncData(query, timeRange, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(entries.size(), 3U); // 3 miss query data. + + /** + * @tc.steps: step8. Put data into "data" table from deviceA for 10 times. + * @tc.expected: Succeed, return OK. + */ + query = QueryObject(Query::Select(g_tableName)); + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(query, entries, deviceID), E_OK); + SingleVerKvEntry::Release(entries); + + /** + * @tc.steps: step9. Check data. + * @tc.expected: There is 0 data in table. + */ + ExpectCount(db, getDataSql, 0U); // 0 data exists + ExpectCount(db, getLogSql, 0U); // 0 data exists + + sqlite3_close(db); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: CompatibleData1 + * @tc.desc: Check compatibility. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, CompatibleData1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + /** + * @tc.steps: step1. Create distributed table "dataPlus". + * @tc.expected: Succeed, return OK. + */ + const string tableName = g_tableName + "Plus"; + std::string sql = "CREATE TABLE " + tableName + "(key INTEGER, value INTEGER NOT NULL, \ + extra_field TEXT NOT NULL DEFAULT 'default_value');"; + sqlite3 *db = nullptr; + ASSERT_EQ(sqlite3_open(g_storePath.c_str(), &db), SQLITE_OK); + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + ASSERT_EQ(g_delegate->CreateDistributedTable(tableName), DBStatus::OK); + /** + * @tc.steps: step2. Put 1 record into data and dataPlus table. + * @tc.expected: Succeed, return OK. + */ + ASSERT_EQ(AddOrUpdateRecord(1, 101), E_OK); + sql = "INSERT INTO " + tableName + " VALUES(2, 102, 'f3');"; // k2v102 + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + /** + * @tc.steps: step3. Get all data from "data" table. + * @tc.expected: Succeed and the count is right. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + QueryObject query(Query::Select(g_tableName)); + std::vector entries; + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(entries.size(), 1UL); + /** + * @tc.steps: step4. Put data into "data_plus" table from deviceA. + * @tc.expected: Succeed, return OK. + */ + QueryObject queryPlus(Query::Select(tableName)); + const DeviceID deviceID = "deviceA"; + ASSERT_EQ(E_OK, SQLiteUtils::CreateSameStuTable(db, store->GetSchemaInfo().GetTable(tableName), + DBCommon::GetDistributedTableName(deviceID, tableName))); + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(queryPlus, entries, deviceID), E_OK); + SingleVerKvEntry::Release(entries); + /** + * @tc.steps: step4. Get all data from "dataPlus" table. + * @tc.expected: Succeed and the count is right. + */ + EXPECT_EQ(store->GetSyncData(queryPlus, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(entries.size(), 1UL); + /** + * @tc.steps: step5. Put data into "data" table from deviceA. + * @tc.expected: Succeed, return OK. + */ + ASSERT_EQ(E_OK, SQLiteUtils::CreateSameStuTable(db, store->GetSchemaInfo().GetTable(g_tableName), + DBCommon::GetDistributedTableName(deviceID, g_tableName))); + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(query, entries, deviceID), E_OK); + SingleVerKvEntry::Release(entries); + /** + * @tc.steps: step6. Check data. + * @tc.expected: All data in the two tables are same. + */ + sql = "SELECT count(*) FROM " + g_tableName + " as a," + DBConstant::RELATIONAL_PREFIX + tableName + "_" + + DBCommon::TransferStringToHex(DBCommon::TransferHashString(deviceID)) + " as b " + + "WHERE a.key=b.key AND a.value=b.value;"; + size_t count = 0; + EXPECT_EQ(GetCount(db, sql, count), E_OK); + EXPECT_EQ(count, 1UL); + sql = "SELECT count(*) FROM " + tableName + " as a," + DBConstant::RELATIONAL_PREFIX + g_tableName + "_" + + DBCommon::TransferStringToHex(DBCommon::TransferHashString(deviceID)) + " as b " + + "WHERE a.key=b.key AND a.value=b.value;"; + count = 0; + EXPECT_EQ(GetCount(db, sql, count), E_OK); + EXPECT_EQ(count, 1UL); + sqlite3_close(db); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: GetDataSortByTime1 + * @tc.desc: All query get data sort by time asc. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, GetDataSortByTime1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + /** + * @tc.steps: step2. Add 3 record into data. k1v105, k2v104, k3v103, timestamp desc. + * @tc.expected: Succeed, return OK. + */ + sqlite3 *db = nullptr; + ASSERT_EQ(sqlite3_open(g_storePath.c_str(), &db), SQLITE_OK); + std::string sql = "INSERT INTO " + g_tableName + " VALUES(1, 101);"; // k1v101 + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + sql = "INSERT INTO " + g_tableName + " VALUES(2, 102);"; // k2v102 + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + sql = "INSERT INTO " + g_tableName + " VALUES(3, 103);"; // k3v103 + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + sql = "UPDATE " + g_tableName + " SET value=104 WHERE key=2;"; // k2v104 + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + sql = "UPDATE " + g_tableName + " SET value=105 WHERE key=1;"; // k1v105 + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + /** + * @tc.steps: step3. Get all data from "data" table by all query. + * @tc.expected: Succeed and the count is right. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + QueryObject query(Query::Select(g_tableName)); + std::vector entries; + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + ExpectMissQueryCnt(entries, 3UL); // 3 data + SingleVerKvEntry::Release(entries); + + query = QueryObject(Query::Select(g_tableName).EqualTo("key", 1).Or().EqualTo("key", 3)); + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + ExpectMissQueryCnt(entries, 2UL); // 2 data + SingleVerKvEntry::Release(entries); + + query = QueryObject(Query::Select(g_tableName).OrderBy("key", false)); + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + ExpectMissQueryCnt(entries, 3UL); // 3 data + SingleVerKvEntry::Release(entries); + + query = QueryObject(Query::Select(g_tableName).OrderBy("value", false)); + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + ExpectMissQueryCnt(entries, 3UL); // 3 data + SingleVerKvEntry::Release(entries); + + query = QueryObject(Query::Select(g_tableName).Limit(2)); + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + ExpectMissQueryCnt(entries, 2UL); // 2 data + SingleVerKvEntry::Release(entries); + + sqlite3_close(db); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: SameFieldWithLogTable1 + * @tc.desc: Get query data OK when the table has same field with log table. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, SameFieldWithLogTable1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + /** + * @tc.steps: step1. Create distributed table "dataPlus". + * @tc.expected: Succeed, return OK. + */ + const string tableName = g_tableName + "Plus"; + std::string sql = "CREATE TABLE " + tableName + "(key INTEGER, flag INTEGER NOT NULL, \ + device TEXT NOT NULL DEFAULT 'default_value');"; + sqlite3 *db = nullptr; + ASSERT_EQ(sqlite3_open(g_storePath.c_str(), &db), SQLITE_OK); + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + ASSERT_EQ(g_delegate->CreateDistributedTable(tableName), DBStatus::OK); + /** + * @tc.steps: step2. Put 1 record into dataPlus table. + * @tc.expected: Succeed, return OK. + */ + sql = "INSERT INTO " + tableName + " VALUES(1, 101, 'f3');"; // k1v101 + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + /** + * @tc.steps: step3. Get all data from dataPlus table. + * @tc.expected: Succeed and the count is right. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + QueryObject query(Query::Select(tableName).EqualTo("flag", 101).OrderBy("device", false)); + std::vector entries; + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(entries.size(), 1UL); + SingleVerKvEntry::Release(entries); + sqlite3_close(db); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: CompatibleData2 + * @tc.desc: Check compatibility. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, CompatibleData2, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + + sqlite3 *db = nullptr; + ASSERT_EQ(sqlite3_open(g_storePath.c_str(), &db), SQLITE_OK); + + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + + /** + * @tc.steps: step1. Create distributed table from deviceA. + * @tc.expected: Succeed, return OK. + */ + const DeviceID deviceID = "deviceA"; + ASSERT_EQ(E_OK, SQLiteUtils::CreateSameStuTable(db, store->GetSchemaInfo().GetTable(g_tableName), + DBCommon::GetDistributedTableName(deviceID, g_tableName))); + + /** + * @tc.steps: step2. Alter "data" table and create distributed table again. + * @tc.expected: Succeed. + */ + std::string sql = "ALTER TABLE " + g_tableName + " ADD COLUMN integer_type INTEGER DEFAULT 123 not null;" + "ALTER TABLE " + g_tableName + " ADD COLUMN text_type TEXT DEFAULT 'high_version' not null;" + "ALTER TABLE " + g_tableName + " ADD COLUMN real_type REAL DEFAULT 123.123456 not null;" + "ALTER TABLE " + g_tableName + " ADD COLUMN blob_type BLOB DEFAULT 123 not null;"; + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + + /** + * @tc.steps: step3. Check deviceA's distributed table. + * @tc.expected: The create sql is correct. + */ + std::string expectSql = "CREATE TABLE naturalbase_rdb_aux_data_" + "265a9c8c3c690cdfdac72acfe7a50f748811802635d987bb7d69dc602ed3794f(key integer NOT NULL PRIMARY KEY," + "value integer, integer_type integer NOT NULL DEFAULT 123, text_type text NOT NULL DEFAULT 'high_version', " + "real_type real NOT NULL DEFAULT 123.123456, blob_type blob NOT NULL DEFAULT 123)"; + sql = "SELECT sql FROM sqlite_master WHERE tbl_name='" + DBConstant::RELATIONAL_PREFIX + g_tableName + "_" + + DBCommon::TransferStringToHex(DBCommon::TransferHashString(deviceID)) + "';"; + EXPECT_EQ(GetOneText(db, sql), expectSql); + + sqlite3_close(db); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: PutSyncDataConflictDataTest001 + * @tc.desc: Check put with conflict sync data. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lianhuix + */ +HWTEST_F(DistributedDBRelationalGetDataTest, PutSyncDataConflictDataTest001, TestSize.Level1) +{ + const DeviceID deviceID_A = "deviceA"; + const DeviceID deviceID_B = "deviceB"; + sqlite3 *db = RelationalTestUtils::CreateDataBase(g_storePath); + RelationalTestUtils::CreateDeviceTable(db, g_tableName, deviceID_B); + + DBStatus status = g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate); + EXPECT_EQ(status, DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + EXPECT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + + auto store = const_cast(GetRelationalStore()); + ASSERT_NE(store, nullptr); + + RelationalTestUtils::ExecSql(db, "INSERT OR REPLACE INTO " + g_tableName + " (key,value) VALUES (1001,'VAL_1');"); + RelationalTestUtils::ExecSql(db, "INSERT OR REPLACE INTO " + g_tableName + " (key,value) VALUES (1002,'VAL_2');"); + RelationalTestUtils::ExecSql(db, "INSERT OR REPLACE INTO " + g_tableName + " (key,value) VALUES (1003,'VAL_3');"); + + DataSizeSpecInfo sizeInfo {MTU_SIZE, 50}; + ContinueToken token = nullptr; + QueryObject query(Query::Select(g_tableName)); + std::vector entries; + int errCode = store->GetSyncData(query, {}, sizeInfo, token, entries); + EXPECT_EQ(errCode, E_OK); + + errCode = store->PutSyncDataWithQuery(query, entries, deviceID_B); + EXPECT_EQ(errCode, E_OK); + GenericSingleVerKvEntry::Release(entries); + + QueryObject query2(Query::Select(g_tableName).EqualTo("key", 1001)); + std::vector entries2; + store->GetSyncData(query2, {}, sizeInfo, token, entries2); + + errCode = store->PutSyncDataWithQuery(query, entries2, deviceID_B); + EXPECT_EQ(errCode, E_OK); + GenericSingleVerKvEntry::Release(entries2); + + RefObject::DecObjRef(g_store); + + std::string deviceTable = DBCommon::GetDistributedTableName(deviceID_B, g_tableName); + EXPECT_EQ(RelationalTestUtils::CheckTableRecords(db, deviceTable), 3); + sqlite3_close_v2(db); +} + +/** + * @tc.name: SaveNonexistDevdata1 + * @tc.desc: Save non-exist device data and check errCode. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, SaveNonexistDevdata1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + /** + * @tc.steps: step1. Create distributed table "dataPlus". + * @tc.expected: Succeed, return OK. + */ + const string tableName = g_tableName + "Plus"; + std::string sql = "CREATE TABLE " + tableName + "(key INTEGER, value INTEGER NOT NULL, \ + extra_field TEXT NOT NULL DEFAULT 'default_value');"; + sqlite3 *db = nullptr; + ASSERT_EQ(sqlite3_open(g_storePath.c_str(), &db), SQLITE_OK); + ASSERT_EQ(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + ASSERT_EQ(g_delegate->CreateDistributedTable(tableName), DBStatus::OK); + /** + * @tc.steps: step2. Put 1 record into data table. + * @tc.expected: Succeed, return OK. + */ + ASSERT_EQ(AddOrUpdateRecord(1, 101), E_OK); + + /** + * @tc.steps: step3. Get all data from "data" table. + * @tc.expected: Succeed and the count is right. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + ContinueToken token = nullptr; + QueryObject query(Query::Select(g_tableName)); + std::vector entries; + EXPECT_EQ(store->GetSyncData(query, SyncTimeRange {}, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(entries.size(), 1UL); + + /** + * @tc.steps: step4. Put data into "data_plus" table from deviceA and deviceA does not exist. + * @tc.expected: Succeed, return OK. + */ + query = QueryObject(Query::Select(tableName)); + const DeviceID deviceID = "deviceA"; + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(query, entries, deviceID), + -1); // -1 means error + SingleVerKvEntry::Release(entries); + + sqlite3_close(db); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: GetMaxTimestamp1 + * @tc.desc: Get max timestamp. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, GetMaxTimestamp1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + /** + * @tc.steps: step1. Create distributed table "dataPlus". + * @tc.expected: Succeed, return OK. + */ + sqlite3 *db = nullptr; + ASSERT_EQ(sqlite3_open(g_storePath.c_str(), &db), SQLITE_OK); + const string tableName = g_tableName + "Plus"; + ExecSqlAndAssertOK(db, "CREATE TABLE " + tableName + "(key INTEGER, value INTEGER NOT NULL, \ + extra_field TEXT NOT NULL DEFAULT 'default_value');"); + ASSERT_EQ(g_delegate->CreateDistributedTable(tableName), DBStatus::OK); + + /** + * @tc.steps: step2. Get max timestamp when no data exists. + * @tc.expected: Succeed and the time is 0; + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + + Timestamp time1 = 0; + store->GetMaxTimestamp(time1); + EXPECT_EQ(time1, 0ull); + + /** + * @tc.steps: step3. Put 1 record into data table and get max timestamp. + * @tc.expected: Succeed and the time is updated. + */ + ASSERT_EQ(AddOrUpdateRecord(1, 101), E_OK); + Timestamp time2 = 0; + store->GetMaxTimestamp(time2); + EXPECT_GT(time2, time1); + + /** + * @tc.steps: step4. Put 1 record into data table and get max timestamp. + * @tc.expected: Succeed and the time is updated. + */ + ASSERT_EQ(AddOrUpdateRecord(2, 102), E_OK); + Timestamp time3 = 0; + store->GetMaxTimestamp(time3); + EXPECT_GT(time3, time2); + + /** + * @tc.steps: step5. Put 1 record into data table and get the max timestamp of data table. + * @tc.expected: Succeed and the time is equals to max timestamp in DB. + */ + Timestamp time4 = 0; + store->GetMaxTimestamp(g_tableName, time4); + EXPECT_EQ(time4, time3); + + /** + * @tc.steps: step6. Put 1 record into data table and get the max timestamp of dataPlus table. + * @tc.expected: Succeed and the time is 0. + */ + Timestamp time5 = 0; + store->GetMaxTimestamp(tableName, time5); + EXPECT_EQ(time5, 0ull); + + sqlite3_close(db); + RefObject::DecObjRef(g_store); +} + +/** + * @tc.name: NoPkData1 + * @tc.desc: For no pk data. + * @tc.type: FUNC + * @tc.require: AR000GK58H + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBRelationalGetDataTest, NoPkData1, TestSize.Level1) +{ + ASSERT_EQ(g_mgr.OpenStore(g_storePath, g_storeID, RelationalStoreDelegate::Option {}, g_delegate), DBStatus::OK); + ASSERT_NE(g_delegate, nullptr); + + sqlite3 *db = nullptr; + ASSERT_EQ(sqlite3_open(g_storePath.c_str(), &db), SQLITE_OK); + ExecSqlAndAssertOK(db, "DROP TABLE IF EXISTS " + g_tableName + "; \ + CREATE TABLE " + g_tableName + "(key INTEGER NOT NULL, value INTEGER);"); + ASSERT_EQ(g_delegate->CreateDistributedTable(g_tableName), DBStatus::OK); + + /** + * @tc.steps: step1. Create distributed table "dataPlus". + * @tc.expected: Succeed, return OK. + */ + const string tableName = g_tableName + "Plus"; + ExecSqlAndAssertOK(db, "CREATE TABLE " + tableName + "(key INTEGER NOT NULL, value INTEGER);"); + ASSERT_EQ(g_delegate->CreateDistributedTable(tableName), DBStatus::OK); + + /** + * @tc.steps: step2. Put 2 data into "data" table. + * @tc.expected: Succeed. + */ + ASSERT_EQ(AddOrUpdateRecord(1, 1), E_OK); + ASSERT_EQ(AddOrUpdateRecord(2, 2), E_OK); + + /** + * @tc.steps: step3. Get data from "data" table. + * @tc.expected: Succeed. + */ + auto store = GetRelationalStore(); + ASSERT_NE(store, nullptr); + + ContinueToken token = nullptr; + QueryObject query(Query::Select(g_tableName)); + std::vector entries; + EXPECT_EQ(store->GetSyncData(query, {}, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(entries.size(), 2U); // expect 2 data. + + /** + * @tc.steps: step4. Put data into "data" table from deviceA. + * @tc.expected: Succeed, return OK. + */ + QueryObject queryPlus(Query::Select(tableName)); + const DeviceID deviceID = "deviceA"; + ASSERT_EQ(E_OK, SQLiteUtils::CreateSameStuTable(db, store->GetSchemaInfo().GetTable(tableName), + DBCommon::GetDistributedTableName(deviceID, tableName))); + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(queryPlus, entries, deviceID), E_OK); + SingleVerKvEntry::Release(entries); + + /** + * @tc.steps: step5. Changet data in "data" table. + * @tc.expected: Succeed. + */ + ExecSqlAndAssertOK(db, {"UPDATE " + g_tableName + " SET value=101 WHERE key=1;", + "DELETE FROM " + g_tableName + " WHERE key=2;", + "INSERT INTO " + g_tableName + " VALUES(2, 102);"}); + + /** + * @tc.steps: step6. Get data from "data" table. + * @tc.expected: Succeed. + */ + EXPECT_EQ(store->GetSyncData(query, {}, DataSizeSpecInfo {}, token, entries), E_OK); + EXPECT_EQ(entries.size(), 2U); // expect 2 data. + + /** + * @tc.steps: step7. Put data into "data" table from deviceA. + * @tc.expected: Succeed, return OK. + */ + EXPECT_EQ(const_cast(store)->PutSyncDataWithQuery(queryPlus, entries, deviceID), E_OK); + SingleVerKvEntry::Release(entries); + + /** + * @tc.steps: step8. Check data. + * @tc.expected: There is 2 data. + */ + std::string sql = "SELECT count(*) FROM " + DBConstant::RELATIONAL_PREFIX + tableName + "_" + + DBCommon::TransferStringToHex(DBCommon::TransferHashString(deviceID)) + ";"; + size_t count = 0; + EXPECT_EQ(GetCount(db, sql, count), E_OK); + EXPECT_EQ(count, 2U); // expect 2 data. + + sqlite3_close(db); + RefObject::DecObjRef(g_store); +} +#endif diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_sqlite_register_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_sqlite_register_test.cpp new file mode 100644 index 00000000..6219f1ed --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_sqlite_register_test.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_tools_unit_test.h" +#include "platform_specific.h" +#include "sqlite_import.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + char *g_errMsg = nullptr; + sqlite3 *g_sqliteDb = nullptr; + + const char * const DB_NAME = "test.db"; + const char * const SQL_HASH = "SELECT CALC_HASH_KEY(KEY) FROM ADDRESS_TEST"; +#ifndef OMIT_JSON + const char * const SQL_JSON_RIGHT = "SELECT * FROM ADDRESS_TEST \ + WHERE JSON_EXTRACT_BY_PATH(VALUE, '$.population', 0) > 800000"; + const char * const SQL_JSON_WRONG_PATH = "SELECT * FROM ADDRESS_TEST \ + WHERE JSON_EXTRACT_BY_PATH(VALUE, '.populationWrong', 0) > 800000"; + const char * const SQL_JSON_WRONG_ARGS = "SELECT * FROM ADDRESS_TEST \ + WHERE JSON_EXTRACT_BY_PATH(VALUE, '$.population') > 800000"; +#endif + const char * const SQL_CREATE_TABLE = "CREATE TABLE IF NOT EXISTS ADDRESS_TEST(" \ + "KEY BLOB NOT NULL PRIMARY KEY," \ + "VALUE BLOB NOT NULL);"; + + const char * const SQL_INSERT = "INSERT INTO ADDRESS_TEST (KEY, VALUE)" \ + "VALUES ('1', '{\"province\":\"hunan\", \"city\":\"shaoyang\", \"population\":1000000}');" \ + "INSERT INTO ADDRESS_TEST (KEY, VALUE)" \ + "VALUES ('12', '{\"province\":\"jiangsu\", \"city\":\"nanjing\", \"population\":3500000}');" \ + "INSERT INTO ADDRESS_TEST (KEY, VALUE)" \ + "VALUES ('123', '{\"province\":\"guangdong\", \"city\":\"shenzhen\", \"population\":700000}');"; + +#ifndef OMIT_JSON + int Callback(void *data, int argc, char **argv, char **azColName) + { + for (int i = 0; i < argc; i++) { + LOGI("%s = %s", azColName[i], argv[i] ? argv[i] : "NULL"); + } + return 0; + } +#endif +} + +class DistributedDBSqliteRegisterTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBSqliteRegisterTest::SetUpTestCase(void) +{ + DistributedDB::OS::RemoveFile(DB_NAME); + int errCode = sqlite3_open(DB_NAME, &g_sqliteDb); + ASSERT_EQ(errCode, SQLITE_OK); + errCode = SQLiteUtils::RegisterJsonFunctions(g_sqliteDb); + ASSERT_EQ(errCode, SQLITE_OK); + + errCode = sqlite3_exec(g_sqliteDb, SQL_CREATE_TABLE, nullptr, nullptr, &g_errMsg); + if (errCode != SQLITE_OK) { + if (g_errMsg != nullptr) { + LOGE("SQL error: %s\n", g_errMsg); + sqlite3_free(g_errMsg); + g_errMsg = nullptr; + } + } + ASSERT_EQ(errCode, SQLITE_OK); + + errCode = sqlite3_exec(g_sqliteDb, SQL_INSERT, nullptr, nullptr, &g_errMsg); + if (errCode != SQLITE_OK) { + if (g_errMsg != nullptr) { + LOGE("SQL error: %s\n", g_errMsg); + sqlite3_free(g_errMsg); + g_errMsg = nullptr; + } + } + ASSERT_EQ(errCode, SQLITE_OK); +} + +void DistributedDBSqliteRegisterTest::TearDownTestCase(void) +{ + if (g_sqliteDb != nullptr) { + sqlite3_close(g_sqliteDb); + } + if (g_errMsg != nullptr) { + sqlite3_free(g_errMsg); + g_errMsg = nullptr; + } +} + +void DistributedDBSqliteRegisterTest::SetUp() +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBSqliteRegisterTest::TearDown() +{ +} +#ifndef OMIT_JSON +/** + * @tc.name: JsonExtract001 + * @tc.desc: test json_extract_by_path function in sqlite + * @tc.type: FUNC + * @tc.require: AR000DR9K7 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBSqliteRegisterTest, JsonExtract001, TestSize.Level1) +{ + ASSERT_NE(g_sqliteDb, nullptr); + int errCode = sqlite3_exec(g_sqliteDb, SQL_JSON_RIGHT, Callback, nullptr, &g_errMsg); + if (errCode != SQLITE_OK) { + if (g_errMsg != nullptr) { + LOGE("SQL error: %s\n", g_errMsg); + sqlite3_free(g_errMsg); + g_errMsg = nullptr; + } + } + ASSERT_EQ(errCode, SQLITE_OK); +} + +/** + * @tc.name: JsonExtract002 + * @tc.desc: test json_extract_by_path function in sqlite + * @tc.type: FUNC + * @tc.require: AR000DR9K7 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBSqliteRegisterTest, JsonExtract002, TestSize.Level1) +{ + ASSERT_NE(g_sqliteDb, nullptr); + if (g_sqliteDb == nullptr) { + LOGE("Sqlite DB not exists."); + return; + } + int errCode = sqlite3_exec(g_sqliteDb, SQL_JSON_WRONG_PATH, Callback, nullptr, &g_errMsg); + if (errCode != SQLITE_OK) { + if (g_errMsg != nullptr) { + LOGE("SQL error: %s\n", g_errMsg); + sqlite3_free(g_errMsg); + g_errMsg = nullptr; + } + } + ASSERT_TRUE(errCode != SQLITE_OK); +} + +/** + * @tc.name: JsonExtract003 + * @tc.desc: test json_extract_by_path function in sqlite + * @tc.type: FUNC + * @tc.require: AR000DR9K7 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBSqliteRegisterTest, JsonExtract003, TestSize.Level1) +{ + ASSERT_NE(g_sqliteDb, nullptr); + int errCode = sqlite3_exec(g_sqliteDb, SQL_JSON_WRONG_ARGS, Callback, nullptr, &g_errMsg); + if (errCode != SQLITE_OK) { + if (g_errMsg != nullptr) { + LOGE("SQL error: %s\n", g_errMsg); + sqlite3_free(g_errMsg); + g_errMsg = nullptr; + } + } + ASSERT_NE(errCode, SQLITE_OK); +} +#endif +/** + * @tc.name: CalcHashValue001 + * @tc.desc: test calc_hash_key function in sqlite + * @tc.type: FUNC + * @tc.require: AR000DR9K7 + * @tc.author: yiguang + */ +HWTEST_F(DistributedDBSqliteRegisterTest, CalcHashValue001, TestSize.Level1) +{ + ASSERT_NE(g_sqliteDb, nullptr); + int errCode = sqlite3_exec(g_sqliteDb, SQL_HASH, nullptr, nullptr, &g_errMsg); + if (errCode != SQLITE_OK) { + if (g_errMsg != nullptr) { + LOGE("SQL error: %s\n", g_errMsg); + sqlite3_free(g_errMsg); + g_errMsg = nullptr; + } + } + ASSERT_EQ(errCode, SQLITE_OK); +} diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_commit_storage_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_commit_storage_test.cpp new file mode 100644 index 00000000..615b8eb5 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_commit_storage_test.cpp @@ -0,0 +1,839 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_errno.h" +#include "default_factory.h" +#include "distributeddb_tools_unit_test.h" +#include "ikvdb_factory.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + IKvDBCommitStorage::Property g_prop; + IKvDBCommitStorage *g_commitStorage = nullptr; + bool g_createFactory = false; + Version g_defaultCommitVer1 = 1; + Version g_defaultCommitVer2 = 2; + Version g_defaultCommitVer3 = 3; + Version g_defaultCommitVer4 = 4; + Version g_defaultCommitVer5 = 5; + Version g_defaultCommitVer6 = 6; + Version g_defaultCommitVer7 = 7; + Version g_defaultCommitVer8 = 8; + Version g_defaultCommitVer9 = 9; + Version g_defaultCommitVer10 = 10; + Version g_defaultCommitVer11 = 11; + Version g_defaultCommitVer12 = 12; + string g_defaultCommitID0 = ""; + string g_defaultCommitID1 = "commit_ID_1"; + string g_defaultCommitID2 = "commit_ID_2"; + string g_defaultCommitID3 = "commit_ID_3"; + string g_defaultCommitID4 = "commit_ID_4"; + string g_defaultCommitID5 = "commit_ID_5"; + string g_defaultCommitID6 = "commit_ID_6"; + string g_defaultCommitID7 = "commit_ID_7"; + string g_defaultCommitID8 = "commit_ID_8"; + string g_defaultCommitID9 = "commit_ID_9"; + string g_defaultCommitID10 = "commit_ID_10"; + string g_defaultCommitID11 = "commit_ID_11"; + string g_defaultCommitID12 = "commit_ID_12"; + string g_defaultCommitID13 = "commit_ID_13"; + string g_defaultCommitID14 = "commit_ID_14"; + DeviceID g_localDevice = "local"; + DeviceID g_remoteDeviceA = "remote_device_A"; + DeviceID g_remoteDeviceB = "remote_device_B"; + DeviceID g_remoteDeviceC = "remote_device_C"; + DeviceID g_remoteDeviceD = "remote_device_D"; + const Timestamp TIME_STAMP1 = 100; + const Timestamp TIME_STAMP2 = 200; + const Timestamp TIME_STAMP3 = 300; + const Timestamp TIME_STAMP4 = 400; + const Timestamp TIME_STAMP5 = 500; + const Timestamp TIME_STAMP6 = 600; + const Timestamp TIME_STAMP7 = 700; + const Timestamp TIME_STAMP8 = 800; + const Timestamp TIME_STAMP9 = 900; + const Timestamp TIME_STAMP10 = 1000; + const Timestamp TIME_STAMP11 = 1100; + const Timestamp TIME_STAMP12 = 1200; + + struct CommitInfo { + Version version; + string commitID; + string leftCommitID; + string rightCommitID; + Timestamp timestamp; + bool localFlag; + DeviceID deviceInfo; + }; + + string TransCommitIDToStr(const CommitID &inputCommitID) + { + string commitIDStr = ""; + if (inputCommitID.size() != 0) { + commitIDStr.resize(inputCommitID.size()); + commitIDStr.assign(inputCommitID.begin(), inputCommitID.end()); + } + return commitIDStr; + } + + void CompareCommitWithExpectation(const IKvDBCommit *commit, const CommitInfo &commitInfo) + { + Version versionInfo = commit->GetCommitVersion(); + ASSERT_EQ(versionInfo, commitInfo.version); + CommitID commitID = commit->GetCommitId(); + string commitIDStr = TransCommitIDToStr(commitID); + ASSERT_STREQ(commitIDStr.c_str(), commitInfo.commitID.c_str()); + CommitID leftCommitID = commit->GetLeftParentId(); + string leftCommitIDStr = TransCommitIDToStr(leftCommitID); + ASSERT_STREQ(leftCommitIDStr.c_str(), commitInfo.leftCommitID.c_str()); + CommitID rightCommitID = commit->GetRightParentId(); + string rightCommitIDStr = TransCommitIDToStr(rightCommitID); + ASSERT_STREQ(rightCommitIDStr.c_str(), commitInfo.rightCommitID.c_str()); + Timestamp timestamp = commit->GetTimestamp(); + ASSERT_EQ(timestamp, commitInfo.timestamp); + bool localFlag = commit->GetLocalFlag(); + ASSERT_EQ(localFlag, commitInfo.localFlag); + DeviceID deviceInfo = commit->GetDeviceInfo(); + ASSERT_EQ(deviceInfo == commitInfo.deviceInfo, true); + } + + void TestLatestCommitOfDevice(const std::map &latestCommits, + const DeviceID &deviceInfo, const CommitInfo &expectCommitInfo) + { + auto latestCommit = latestCommits.find(deviceInfo); + ASSERT_EQ(latestCommit != latestCommits.end(), true); + CompareCommitWithExpectation(latestCommit->second, expectCommitInfo); + } + + CommitID TransStrToCommitID(const string &commitIDStr) + { + CommitID commitID; + commitID.resize(commitIDStr.size()); + if (commitIDStr.size() != 0) { + commitID.assign(commitIDStr.begin(), commitIDStr.end()); + } + return commitID; + } + + void InsertCommitToCommitStorage(const CommitInfo &commitInfo, int expectedResult) + { + int result; + IKvDBCommit *commit = g_commitStorage->AllocCommit(result); + ASSERT_EQ(result, E_OK); + ASSERT_NE(commit, nullptr); + if (result != E_OK || commit == nullptr) { + return; + } + CommitID commitID = TransStrToCommitID(commitInfo.commitID); + CommitID leftCommitID = TransStrToCommitID(commitInfo.leftCommitID); + CommitID rightCommitID = TransStrToCommitID(commitInfo.rightCommitID); + commit->SetCommitVersion(commitInfo.version); + commit->SetLeftParentId(leftCommitID); + commit->SetRightParentId(rightCommitID); + commit->SetCommitId(commitID); + commit->SetTimestamp(commitInfo.timestamp); + commit->SetLocalFlag(commitInfo.localFlag); + commit->SetDeviceInfo(commitInfo.deviceInfo); + result = g_commitStorage->AddCommit(*commit, false); + ASSERT_EQ(result, expectedResult); + g_commitStorage->ReleaseCommit(commit); + commit = nullptr; + } + + void DeleteCommit(const string &inputCommitID, int expectedResult) + { + CommitID commitID = TransStrToCommitID(inputCommitID); + int result = g_commitStorage->RemoveCommit(commitID); + ASSERT_EQ(result, expectedResult); + } + + void TestCommit(const CommitInfo &commitInfo, int expectedResult) + { + int result; + CommitID inputCommitID = TransStrToCommitID(commitInfo.commitID); + IKvDBCommit *commit = g_commitStorage->GetCommit(inputCommitID, result); + ASSERT_EQ(result, expectedResult); + if (expectedResult == E_OK) { + ASSERT_NE(commit, nullptr); + } else { + ASSERT_EQ(commit, nullptr); + } + if (result != E_OK || commit == nullptr) { + return; + } + CompareCommitWithExpectation(commit, commitInfo); + g_commitStorage->ReleaseCommit(commit); + commit = nullptr; + } + + void SetCommitStorageHeader(const string &inputHeader, int expectedResult) + { + CommitID header = TransStrToCommitID(inputHeader); + int result = g_commitStorage->SetHeader(header); + ASSERT_EQ(result, expectedResult); + } + + void TestCommitStorageHeader(const string &expectedHeader) + { + int errCode = E_OK; + CommitID header = g_commitStorage->GetHeader(errCode); + string headerStr = TransCommitIDToStr(header); + ASSERT_STREQ(headerStr.c_str(), expectedHeader.c_str()); + } + + /* + * commit tree is as below: + * L A B C D + * 1 2 4 8 d + * |/|/| | + * 3 / | 9 + * |X| |/| + * 5 6 a e + * |/|/ + * 7 b + * |/ + * c + */ + void PrepareCommitTree() + { + CommitInfo commitInfo1 = {g_defaultCommitVer1, g_defaultCommitID1, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP1, true, g_localDevice}; + CommitInfo commitInfo2 = {g_defaultCommitVer2, g_defaultCommitID2, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP2, false, g_remoteDeviceA}; + CommitInfo commitInfo3 = {g_defaultCommitVer3, g_defaultCommitID3, g_defaultCommitID1, g_defaultCommitID2, + TIME_STAMP3, true, g_localDevice}; + CommitInfo commitInfo4 = {g_defaultCommitVer4, g_defaultCommitID4, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP4, false, g_remoteDeviceB}; + CommitInfo commitInfo5 = {g_defaultCommitVer5, g_defaultCommitID5, g_defaultCommitID3, g_defaultCommitID4, + TIME_STAMP5, true, g_localDevice}; + CommitInfo commitInfo6 = {g_defaultCommitVer6, g_defaultCommitID6, g_defaultCommitID2, g_defaultCommitID3, + TIME_STAMP6, false, g_remoteDeviceA}; + CommitInfo commitInfo7 = {g_defaultCommitVer7, g_defaultCommitID7, g_defaultCommitID5, g_defaultCommitID6, + TIME_STAMP7, true, g_localDevice}; + CommitInfo commitInfo8 = {g_defaultCommitVer8, g_defaultCommitID8, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP8, false, g_remoteDeviceC}; + CommitInfo commitInfo9 = {g_defaultCommitVer9, g_defaultCommitID9, g_defaultCommitID8, g_defaultCommitID0, + TIME_STAMP9, false, g_remoteDeviceC}; + CommitInfo commitInfo10 = {g_defaultCommitVer10, g_defaultCommitID10, g_defaultCommitID4, g_defaultCommitID9, + TIME_STAMP10, false, g_remoteDeviceB}; + CommitInfo commitInfo11 = {g_defaultCommitVer11, g_defaultCommitID11, g_defaultCommitID6, g_defaultCommitID10, + TIME_STAMP11, false, g_remoteDeviceA}; + CommitInfo commitInfo12 = {g_defaultCommitVer12, g_defaultCommitID12, g_defaultCommitID7, g_defaultCommitID11, + TIME_STAMP12, true, g_localDevice}; + InsertCommitToCommitStorage(commitInfo1, E_OK); + InsertCommitToCommitStorage(commitInfo2, E_OK); + InsertCommitToCommitStorage(commitInfo3, E_OK); + InsertCommitToCommitStorage(commitInfo4, E_OK); + InsertCommitToCommitStorage(commitInfo5, E_OK); + InsertCommitToCommitStorage(commitInfo6, E_OK); + InsertCommitToCommitStorage(commitInfo7, E_OK); + InsertCommitToCommitStorage(commitInfo8, E_OK); + InsertCommitToCommitStorage(commitInfo9, E_OK); + InsertCommitToCommitStorage(commitInfo10, E_OK); + InsertCommitToCommitStorage(commitInfo11, E_OK); + InsertCommitToCommitStorage(commitInfo12, E_OK); + SetCommitStorageHeader(g_defaultCommitID12, E_OK); + } +} + +class DistributedDBStorageCommitStorageTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBStorageCommitStorageTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_prop.path); + g_prop.isNeedCreate = true; + if (IKvDBFactory::GetCurrent() == nullptr) { + IKvDBFactory *factory = new (std::nothrow) DefaultFactory(); + ASSERT_NE(factory, nullptr); + if (factory == nullptr) { + LOGE("failed to new DefaultFactory!"); + return; + } + IKvDBFactory::Register(factory); + g_createFactory = true; + } +} + +void DistributedDBStorageCommitStorageTest::TearDownTestCase(void) +{ + if (g_createFactory) { + if (IKvDBFactory::GetCurrent() != nullptr) { + delete IKvDBFactory::GetCurrent(); + IKvDBFactory::Register(nullptr); + } + } + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_prop.path) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBStorageCommitStorageTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + IKvDBFactory *factory = IKvDBFactory::GetCurrent(); + ASSERT_NE(factory, nullptr); + if (factory == nullptr) { + LOGE("failed to get DefaultFactory!"); + return; + } + int result; + g_commitStorage = factory->CreateMultiVerCommitStorage(result); + ASSERT_EQ(result, E_OK); + ASSERT_NE(g_commitStorage, nullptr); + if (g_commitStorage == nullptr) { + return; + } + + int errCode = g_commitStorage->Open(g_prop); + ASSERT_EQ(errCode, E_OK); + if (errCode != E_OK) { + delete g_commitStorage; + g_commitStorage = nullptr; + return; + } +} + +void DistributedDBStorageCommitStorageTest::TearDown(void) +{ + if (g_commitStorage != nullptr) { + (void)g_commitStorage->Remove(g_prop); + delete g_commitStorage; + g_commitStorage = nullptr; + } +} + +/** + * @tc.name: MultiVerCommitStorage001 + * @tc.desc: Open a commit storage when it has been opened. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage001, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Open commit log database + * @tc.expected: step1/2. Return OK. + */ + int result = g_commitStorage->Open(g_prop); + ASSERT_EQ(result, E_OK); + return; +} + +/** + * @tc.name: MultiVerCommitStorage002 + * @tc.desc: Remove a commit storage database, then try to add, delete and query commit, set and get header. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage002, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Remove commit log database + * @tc.expected: step1/2. Return OK. + */ + int result = g_commitStorage->Remove(g_prop); + ASSERT_EQ(result, E_OK); + if (result != E_OK) { + return; + } + CommitInfo commitInfo = {g_defaultCommitVer1, g_defaultCommitID1, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP1, true, g_localDevice}; + + /** + * @tc.steps:step3/4. Insert recording commit log database + * @tc.expected: step3/4. Return E_INVALID_DB. + */ + InsertCommitToCommitStorage(commitInfo, -E_INVALID_DB); + + /** + * @tc.steps:step5/6. Delete commit History to commit log database + * @tc.expected: step5/6. Return E_INVALID_DB. + */ + DeleteCommit(g_defaultCommitID1, -E_INVALID_DB); + + /** + * @tc.steps:step7/8. Cheeck commit History to commit log database + * @tc.expected: step7/8. Return E_INVALID_DB. + */ + TestCommit(commitInfo, -E_INVALID_DB); + + /** + * @tc.steps:step9/10. Cheeck change commit header + * @tc.expected: step7/10. Return E_INVALID_DB. + */ + SetCommitStorageHeader(g_defaultCommitID1, -E_INVALID_DB); + + /** + * @tc.steps:step11/12. Cheeck query commit header + * @tc.expected: step11/12. Return failed. + */ + TestCommitStorageHeader(g_defaultCommitID0); +} + +/** + * @tc.name: MultiVerCommitStorage003 + * @tc.desc: Insert a commit to commit storage, and get it. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage003, TestSize.Level1) +{ + CommitInfo commitInfo = {g_defaultCommitVer1, g_defaultCommitID1, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP1, true, g_localDevice}; + + /** + * @tc.steps:step1. Insert recording commit log database + * @tc.expected: step1. Return E_OK. + */ + InsertCommitToCommitStorage(commitInfo, E_OK); + + /** + * @tc.steps:step2. Cheeck commit History to commit log database + * @tc.expected: step2. Return E_OK. + */ + TestCommit(commitInfo, E_OK); + return; +} + +/** + * @tc.name: MultiVerCommitStorage004 + * @tc.desc: Set header of commit storage, and get it. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage004, TestSize.Level1) +{ + CommitInfo commitInfo = {g_defaultCommitVer1, g_defaultCommitID1, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP1, true, g_localDevice}; + + /** + * @tc.steps:step1. Insert recording commit log database + * @tc.expected: step1. Return E_OK. + */ + InsertCommitToCommitStorage(commitInfo, E_OK); + + /** + * @tc.steps:step2. Cheeck change commit header + * @tc.expected: step2. Return E_OK. + */ + SetCommitStorageHeader(g_defaultCommitID1, E_OK); + + /** + * @tc.steps:step3. Cheeck query commit header + * @tc.expected: step3. Return success. + */ + TestCommitStorageHeader(g_defaultCommitID1); + return; +} + +/** + * @tc.name: MultiVerCommitStorage005 + * @tc.desc: Delete the header commit, test if it can be get, and get the new header. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage005, TestSize.Level1) +{ + CommitInfo commitInfo1 = {g_defaultCommitVer1, g_defaultCommitID1, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP1, true, g_localDevice}; + + /** + * @tc.steps:step1. Insert recording commitInfo1 commit log database + * @tc.expected: step1. Return E_OK. + */ + InsertCommitToCommitStorage(commitInfo1, E_OK); + CommitInfo commitInfo2 = {g_defaultCommitVer2, g_defaultCommitID2, g_defaultCommitID1, g_defaultCommitID0, + TIME_STAMP2, true, g_localDevice}; + + /** + * @tc.steps:step2. Insert recording commitInfo2 commit log database + * @tc.expected: step2. Return E_OK. + */ + InsertCommitToCommitStorage(commitInfo2, E_OK); + + /** + * @tc.steps:step3. Cheeck change commit header + * @tc.expected: step3. Return E_OK. + */ + SetCommitStorageHeader(g_defaultCommitID2, E_OK); + + /** + * @tc.steps:step4. Delete commit History to commit log database + * @tc.expected: step4. Return E_INVALID_DB. + */ + DeleteCommit(g_defaultCommitID2, E_OK); + + /** + * @tc.steps:step5. Cheeck query commit header + * @tc.expected: step5. Return success. + */ + TestCommitStorageHeader(g_defaultCommitID1); + + /** + * @tc.steps:step6. Cheeck commit History to commit log database + * @tc.expected: step6. Return E_OK. + */ + TestCommit(commitInfo1, E_OK); + + /** + * @tc.steps:step7. Cheeck commit History to commit log database + * @tc.expected: step7. Return E_OK. + */ + TestCommit(commitInfo2, -E_NOT_FOUND); + return; +} + +/** + * @tc.name: MultiVerCommitStorage006 + * @tc.desc: Add commit with empty commit ID, and it will not be added. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage006, TestSize.Level1) +{ + CommitInfo commitInfo = {g_defaultCommitVer1, g_defaultCommitID0, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP1, true, g_localDevice}; + + /** + * @tc.steps:step1/2. Insert commit ID is null to commit log database + * @tc.expected: step1/2. Return E_UNEXPECTED_DATA. + */ + InsertCommitToCommitStorage(commitInfo, -E_UNEXPECTED_DATA); +} + +/** + * @tc.name: MultiVerCommitStorage008 + * @tc.desc: Add commit with the same commit ID as its left parent, and it will not be added. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage008, TestSize.Level1) +{ + CommitInfo commitInfo1 = {g_defaultCommitVer1, g_defaultCommitID1, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP1, true, g_localDevice}; + + /** + * @tc.steps:step1. Insert a recording to commit log database + * @tc.expected: step1. Return E_OK. + */ + InsertCommitToCommitStorage(commitInfo1, E_OK); + CommitInfo commitInfo2 = {g_defaultCommitVer1, g_defaultCommitID1, g_defaultCommitID1, g_defaultCommitID0, + TIME_STAMP2, true, g_localDevice}; + + /** + * @tc.steps:step2. Add commit with the same commit ID as its right parent + * @tc.expected: step2. Return E_UNEXPECTED_DATA. + */ + InsertCommitToCommitStorage(commitInfo2, -E_UNEXPECTED_DATA); + + /** + * @tc.steps:step3. Cheeck commit History to commit log database + * @tc.expected: step3. Return E_OK. + */ + TestCommit(commitInfo1, E_OK); +} + +/** + * @tc.name: MultiVerCommitStorage009 + * @tc.desc: Add commit with the same commit ID as its right parent, and it will not be added. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage009, TestSize.Level1) +{ + CommitInfo commitInfo1 = {g_defaultCommitVer1, g_defaultCommitID1, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP1, true, g_localDevice}; + + /** + * @tc.steps:step1. Insert a recording to commit log database + * @tc.expected: step1. Return E_OK. + */ + InsertCommitToCommitStorage(commitInfo1, E_OK); + CommitInfo commitInfo2 = {g_defaultCommitVer1, g_defaultCommitID1, g_defaultCommitID0, g_defaultCommitID1, + TIME_STAMP2, true, g_localDevice}; + + /** + * @tc.steps:step2. Add commit with the same commit ID as its right parent + * @tc.expected: step2. Return E_UNEXPECTED_DATA. + */ + InsertCommitToCommitStorage(commitInfo2, -E_UNEXPECTED_DATA); + + /** + * @tc.steps:step3. Cheeck commit History to commit log database + * @tc.expected: step3. Return E_OK. + */ + TestCommit(commitInfo1, E_OK); +} + +/** + * @tc.name: MultiVerCommitStorage010 + * @tc.desc: Add commit whose left parent and right parent is the same, and it will not be added. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage010, TestSize.Level1) +{ + CommitInfo commitInfo1 = {g_defaultCommitVer1, g_defaultCommitID1, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP1, true, g_localDevice}; + + /** + * @tc.steps:step1. Insert a recording to commit log database + * @tc.expected: step1. Return E_OK. + */ + InsertCommitToCommitStorage(commitInfo1, E_OK); + CommitInfo commitInfo2 = {g_defaultCommitVer1, g_defaultCommitID2, g_defaultCommitID1, g_defaultCommitID1, + TIME_STAMP2, true, g_localDevice}; + + /** + * @tc.steps:step2. Add commit whose left parent and right parent is the same + * @tc.expected: step2. Return E_UNEXPECTED_DATA. + */ + InsertCommitToCommitStorage(commitInfo2, -E_UNEXPECTED_DATA); + + /** + * @tc.steps:step3. Cheeck commit History of commitInfo1 to commit log database + * @tc.expected: step3. Return E_OK. + */ + TestCommit(commitInfo1, E_OK); + + /** + * @tc.steps:step3. Cheeck commit History of commitInfo2 to commit log database + * @tc.expected: step3. Return E_NOT_FOUND. + */ + TestCommit(commitInfo2, -E_NOT_FOUND); +} + +/** + * @tc.name: MultiVerCommitStorage011 + * @tc.desc: Add commit with a non exist left parent, and it will not be added. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage011, TestSize.Level1) +{ + CommitInfo commitInfo = {g_defaultCommitVer1, g_defaultCommitID2, g_defaultCommitID1, g_defaultCommitID0, + TIME_STAMP1, true, g_localDevice}; + + /** + * @tc.steps:step1. Add commit with a non exist left parent + * @tc.expected: step1. Return E_NOT_FOUND. + */ + InsertCommitToCommitStorage(commitInfo, -E_NOT_FOUND); + + /** + * @tc.steps:step2. Cheeck commit History to commit log database + * @tc.expected: step2. Return E_NOT_FOUND. + */ + TestCommit(commitInfo, -E_NOT_FOUND); +} + +/** + * @tc.name: MultiVerCommitStorage012 + * @tc.desc: Add commit with a non exist right parent, and it will not be added. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage012, TestSize.Level1) +{ + CommitInfo commitInfo = {g_defaultCommitVer1, g_defaultCommitID2, g_defaultCommitID0, g_defaultCommitID1, + TIME_STAMP1, true, g_localDevice}; + + /** + * @tc.steps:step1. Add commit with a non exist right parent + * @tc.expected: step1. Return E_NOT_FOUND. + */ + InsertCommitToCommitStorage(commitInfo, -E_NOT_FOUND); + + /** + * @tc.steps:step2. Cheeck commit History to commit log database + * @tc.expected: step2. Return E_NOT_FOUND. + */ + TestCommit(commitInfo, -E_NOT_FOUND); +} + +/** + * @tc.name: MultiVerCommitStorage013 + * @tc.desc: Delete a commit which is not header, and it will not be deleted. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage013, TestSize.Level1) +{ + CommitInfo commitInfo1 = {g_defaultCommitVer1, g_defaultCommitID1, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP1, true, g_localDevice}; + + /** + * @tc.steps:step1. Insert a recording to commit log database + */ + InsertCommitToCommitStorage(commitInfo1, E_OK); + CommitInfo commitInfo2 = {g_defaultCommitVer2, g_defaultCommitID2, g_defaultCommitID1, g_defaultCommitID0, + TIME_STAMP2, true, g_localDevice}; + + /** + * @tc.steps:step2. Insert a left parent to commit log database + */ + InsertCommitToCommitStorage(commitInfo2, E_OK); + + /** + * @tc.steps:step3. Set g_defaultCommitID2 is parent to commit log database + */ + SetCommitStorageHeader(g_defaultCommitID2, E_OK); + + /** + * @tc.steps:step4. Delete g_defaultCommitID1 + * @tc.expected: step4. Return E_UNEXPECTED_DATA. + */ + DeleteCommit(g_defaultCommitID1, -E_UNEXPECTED_DATA); + + /** + * @tc.steps:step5. Cheeck commit header is same as g_defaultCommitID2 + */ + TestCommitStorageHeader(g_defaultCommitID2); + + TestCommit(commitInfo1, E_OK); + TestCommit(commitInfo2, E_OK); + return; +} + +/** + * @tc.name: MultiVerCommitStorage014 + * @tc.desc: Set unexist commit to header, and it will not success. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage014, TestSize.Level1) +{ + SetCommitStorageHeader(g_defaultCommitID2, -E_NOT_FOUND); + TestCommitStorageHeader(g_defaultCommitID0); +} + +/** + * @tc.name: MultiVerCommitStorage015 + * @tc.desc: SDetermine whether commit exists + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage015, TestSize.Level1) +{ + PrepareCommitTree(); + int errCode = E_OK; + EXPECT_EQ(g_commitStorage->CommitExist(TransStrToCommitID(g_defaultCommitID7), errCode), true); + EXPECT_EQ(g_commitStorage->CommitExist(TransStrToCommitID(g_defaultCommitID13), errCode), false); +} + +/** + * @tc.name: MultiVerCommitStorage016 + * @tc.desc: Get latest commit of each device from commit storage + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage016, TestSize.Level1) +{ + PrepareCommitTree(); + std::map latestCommits; + int result = g_commitStorage->GetLatestCommits(latestCommits); + EXPECT_EQ(result, E_OK); + CommitInfo commitInfoLocal = {g_defaultCommitVer12, g_defaultCommitID12, g_defaultCommitID7, + g_defaultCommitID11, TIME_STAMP12, true, g_localDevice}; + CommitInfo commitInfoA = {g_defaultCommitVer11, g_defaultCommitID11, g_defaultCommitID6, + g_defaultCommitID10, TIME_STAMP11, false, g_remoteDeviceA}; + CommitInfo commitInfoB = {g_defaultCommitVer10, g_defaultCommitID10, g_defaultCommitID4, + g_defaultCommitID9, TIME_STAMP10, false, g_remoteDeviceB}; + CommitInfo commitInfoC = {g_defaultCommitVer9, g_defaultCommitID9, g_defaultCommitID8, + g_defaultCommitID0, TIME_STAMP9, false, g_remoteDeviceC}; + TestLatestCommitOfDevice(latestCommits, g_localDevice, commitInfoLocal); + TestLatestCommitOfDevice(latestCommits, g_remoteDeviceA, commitInfoA); + TestLatestCommitOfDevice(latestCommits, g_remoteDeviceB, commitInfoB); + TestLatestCommitOfDevice(latestCommits, g_remoteDeviceC, commitInfoC); + for (auto latestCommit : latestCommits) { + g_commitStorage->ReleaseCommit(latestCommit.second); + latestCommit.second = nullptr; + } +} + +/** + * @tc.name: MultiVerCommitStorage017 + * @tc.desc: Get commit tree from commit storage by latest commits + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageCommitStorageTest, MultiVerCommitStorage017, TestSize.Level1) +{ + PrepareCommitTree(); + map latestCommits; + latestCommits.insert(make_pair(g_localDevice, TransStrToCommitID(g_defaultCommitID3))); + latestCommits.insert(make_pair(g_remoteDeviceA, TransStrToCommitID(g_defaultCommitID2))); + latestCommits.insert(make_pair(g_remoteDeviceC, TransStrToCommitID(g_defaultCommitID14))); + latestCommits.insert(make_pair(g_remoteDeviceD, TransStrToCommitID(g_defaultCommitID13))); + list commits; + int result = g_commitStorage->GetCommitTree(latestCommits, commits); + EXPECT_EQ(result, E_OK); + vector commitsVector(commits.begin(), commits.end()); + LOGD("Commits.size%zu", commits.size()); + ASSERT_GT(commits.size(), 6UL); + CommitInfo commitInfo4 = {g_defaultCommitVer4, g_defaultCommitID4, g_defaultCommitID0, g_defaultCommitID0, + TIME_STAMP4, false, g_remoteDeviceB}; + CommitInfo commitInfo5 = {g_defaultCommitVer5, g_defaultCommitID5, g_defaultCommitID3, g_defaultCommitID4, + TIME_STAMP5, true, g_localDevice}; + CommitInfo commitInfo6 = {g_defaultCommitVer6, g_defaultCommitID6, g_defaultCommitID2, g_defaultCommitID3, + TIME_STAMP6, false, g_remoteDeviceA}; + CommitInfo commitInfo7 = {g_defaultCommitVer7, g_defaultCommitID7, g_defaultCommitID5, g_defaultCommitID6, + TIME_STAMP7, true, g_localDevice}; + CommitInfo commitInfo10 = {g_defaultCommitVer10, g_defaultCommitID10, g_defaultCommitID4, g_defaultCommitID9, + TIME_STAMP10, false, g_remoteDeviceB}; + CommitInfo commitInfo11 = {g_defaultCommitVer11, g_defaultCommitID11, g_defaultCommitID6, g_defaultCommitID10, + TIME_STAMP11, false, g_remoteDeviceA}; + CommitInfo commitInfo12 = {g_defaultCommitVer12, g_defaultCommitID12, g_defaultCommitID7, g_defaultCommitID11, + TIME_STAMP12, true, g_localDevice}; + CompareCommitWithExpectation(commitsVector[0], commitInfo4); + CompareCommitWithExpectation(commitsVector[1], commitInfo5); + CompareCommitWithExpectation(commitsVector[2], commitInfo6); + CompareCommitWithExpectation(commitsVector[3], commitInfo7); + CompareCommitWithExpectation(commitsVector[4], commitInfo10); + CompareCommitWithExpectation(commitsVector[5], commitInfo11); + CompareCommitWithExpectation(commitsVector[6], commitInfo12); + for (auto commit : commits) { + g_commitStorage->ReleaseCommit(commit); + commit = nullptr; + } +} diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_data_operation_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_data_operation_test.cpp new file mode 100644 index 00000000..832cc326 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_data_operation_test.cpp @@ -0,0 +1,642 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_constant.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "kvdb_manager.h" +#include "multi_ver_natural_store_transfer_data.h" +#include "sqlite_local_kvdb_connection.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + SQLiteLocalKvDBConnection *g_connection = nullptr; +} + +class DistributedDBStorageDataOperationTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBStorageDataOperationTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); +} + +void DistributedDBStorageDataOperationTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBStorageDataOperationTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + KvDBProperties properties; + properties.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + properties.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::LOCAL_TYPE); + properties.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + properties.SetStringProp(KvDBProperties::STORE_ID, "test"); + properties.SetStringProp(KvDBProperties::IDENTIFIER_DIR, "test"); + + int errCode = E_OK; + g_connection = static_cast(KvDBManager::GetDatabaseConnection(properties, errCode)); + EXPECT_EQ(errCode, E_OK); +} + +void DistributedDBStorageDataOperationTest::TearDown(void) +{ + if (g_connection != nullptr) { + g_connection->Close(); + g_connection = nullptr; + } + return; +} + +/** + * @tc.name: Insert001 + * @tc.desc: Insert a record into a distributed db + * @tc.type: FUNC + * @tc.require: AR000CQDV8 AR000CQDVB + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageDataOperationTest, Insert001, TestSize.Level1) +{ + EXPECT_NE(g_connection, nullptr); + if (g_connection == nullptr) { + return; + } + + Key key(3, 'w'); + Value value; + value.assign(8, 87); + IOption option; + + /** + * @tc.steps:step1. Put a kv into database + * @tc.expected: step1. Return OK. + */ + int errCode = g_connection->Put(option, key, value); + EXPECT_EQ(errCode, E_OK); + + Value valueRead; + valueRead.clear(); + + /** + * @tc.steps:step2. Get k from database + * @tc.expected: step2. Return OK. The size is right. + */ + errCode = g_connection->Get(option, key, valueRead); + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(valueRead.size(), 8UL); + + for (auto iter = valueRead.begin(); iter != valueRead.end(); iter++) { + EXPECT_EQ(*iter, 87); + } +} + +/** + * @tc.name: InsertBatch001 + * @tc.desc: Insert some records into a distributed db + * @tc.type: FUNC + * @tc.require: AR000CQDV9 AR000CQDVE + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageDataOperationTest, InsertBatch001, TestSize.Level1) +{ + EXPECT_NE(g_connection, nullptr); + if (g_connection == nullptr) { + return; + } + + Key key(3, 'w'); + Value value; + value.assign(8, 87); + IOption option; + + Entry entry; + entry.key = key; + entry.value = value; + + std::vector entries; + entries.push_back(entry); + + entry.key.push_back('q'); + entry.value.assign(6, 76); + entries.push_back(entry); + + /** + * @tc.steps:step1. PutBatch series kv into database + * @tc.expected: step1. Return OK. + */ + int errCode = g_connection->PutBatch(option, entries); + EXPECT_EQ(errCode, E_OK); + + std::vector entriesRead; + Key keyRead(3, 'w'); + entriesRead.clear(); + + /** + * @tc.steps:step2. Get k from database by GetEntries + * @tc.expected: step2. Return OK. The size is right. + */ + errCode = g_connection->GetEntries(option, keyRead, entriesRead); + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(entriesRead.size(), 2UL); + + if (entriesRead.size() > 2) { + EXPECT_EQ(entriesRead[0].value.size(), 8UL); + EXPECT_EQ(entriesRead[1].value.size(), 6UL); + } +} + +/** + * @tc.name: Clear001 + * @tc.desc: Clear some records from a distributed db + * @tc.type: FUNC + * @tc.require: AR000BVTO6 AR000CQDVA + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageDataOperationTest, Clear001, TestSize.Level1) +{ + EXPECT_NE(g_connection, nullptr); + if (g_connection == nullptr) { + return; + } + + Key key(3, 'w'); + Value value; + value.assign(8, 87); + IOption option; + + Entry entry; + entry.key = key; + entry.value = value; + + std::vector entries; + entries.push_back(entry); + + entry.key.push_back('q'); + entry.value.assign(6, 76); + entries.push_back(entry); + + /** + * @tc.steps:step1. PutBatch series kv into database + * @tc.expected: step1. Return OK. + */ + int errCode = g_connection->PutBatch(option, entries); + EXPECT_EQ(errCode, E_OK); + + std::vector entriesRead; + Key keyRead(3, 'w'); + entriesRead.clear(); + + /** + * @tc.steps:step2. Get k from database by GetEntries + * @tc.expected: step2. Return OK. The size is right. + */ + errCode = g_connection->GetEntries(option, keyRead, entriesRead); + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(entriesRead.size(), 2UL); + + if (entriesRead.size() > 2) { + EXPECT_EQ(entriesRead[0].value.size(), 8UL); + EXPECT_EQ(entriesRead[1].value.size(), 6UL); + } + + /** + * @tc.steps:step3. Clear all data from database + * @tc.expected: step3. Return OK. + */ + errCode = g_connection->Clear(option); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps:step2. Get k from database by GetEntries + * @tc.expected: step2. Return E_NOT_FOUND. The result size is 0. + */ + entriesRead.clear(); + errCode = g_connection->GetEntries(option, keyRead, entriesRead); + EXPECT_EQ(errCode, -E_NOT_FOUND); + EXPECT_EQ(entriesRead.size(), 0UL); +} + +/** + * @tc.name: Delete001 + * @tc.desc: Delete a record from a distributed db + * @tc.type: FUNC + * @tc.require: AR000CQDVF AR000CQDVB + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageDataOperationTest, Delete001, TestSize.Level1) +{ + EXPECT_NE(g_connection, nullptr); + if (g_connection == nullptr) { + return; + } + + Key key(3, 'w'); + Value value; + value.assign(8, 87); + IOption option; + + Entry entry; + entry.key = key; + entry.value = value; + + std::vector entries; + entries.push_back(entry); + + entry.key.push_back('q'); + entry.value.assign(6, 76); + entries.push_back(entry); + + /** + * @tc.steps:step1. PutBatch series kv into database + * @tc.expected: step1. Return OK. + */ + int errCode = g_connection->PutBatch(option, entries); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps:step2. Get k from database by GetEntries + * @tc.expected: step2. Return OK. The size is right. + */ + Value valueRead; + errCode = g_connection->Get(option, entry.key, valueRead); + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(valueRead.size(), 6UL); + + std::vector entriesRead; + Key keyRead(3, 'w'); + entriesRead.clear(); + + /** + * @tc.steps:step3. Get k from database by GetEntries + * @tc.expected: step3. Return E_OK. The result size is right. + */ + errCode = g_connection->GetEntries(option, keyRead, entriesRead); + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(entriesRead.size(), 2UL); + + if (entriesRead.size() > 2) { + EXPECT_EQ(entriesRead[0].value.size(), 8UL); + EXPECT_EQ(entriesRead[1].value.size(), 6UL); + } + + /** + * @tc.steps:step3. Delete k from database + * @tc.expected: step3. Return E_OK. + */ + errCode = g_connection->Delete(option, key); + EXPECT_EQ(errCode, E_OK); + + entriesRead.clear(); + + /** + * @tc.steps:step3. Get k from database by GetEntries + * @tc.expected: step3. Return E_OK. The result size is reduction 1. + */ + errCode = g_connection->GetEntries(option, keyRead, entriesRead); + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(entriesRead.size(), 1UL); +} + +/** + * @tc.name: DeleteBatch001 + * @tc.desc: Delete some records from a distributed db + * @tc.type: FUNC + * @tc.require: AR000CQDVG AR000CQDVB + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageDataOperationTest, DeleteBatch001, TestSize.Level1) +{ + EXPECT_NE(g_connection, nullptr); + if (g_connection == nullptr) { + return; + } + + Key key(3, 'w'); + Value value; + value.assign(8, 87); + IOption option; + + Entry entry; + entry.key = key; + entry.value = value; + + std::vector entries; + entries.push_back(entry); + + entry.key.push_back('q'); + entry.value.assign(6, 76); + entries.push_back(entry); + + /** + * @tc.steps:step1. PutBatch series kv into database + * @tc.expected: step1. Return OK. + */ + int errCode = g_connection->PutBatch(option, entries); + EXPECT_EQ(errCode, E_OK); + + std::vector entriesRead; + Key keyRead(3, 'w'); + entriesRead.clear(); + + /** + * @tc.steps:step2. Get k from database by GetEntries + * @tc.expected: step2. Return E_OK. The result size is right. + */ + errCode = g_connection->GetEntries(option, keyRead, entriesRead); + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(entriesRead.size(), 2UL); + + if (entriesRead.size() > 2) { + EXPECT_EQ(entriesRead[0].value.size(), 8UL); + EXPECT_EQ(entriesRead[1].value.size(), 6UL); + } + + std::vector keys; + Key keyTmp = key; + + keys.push_back(keyTmp); + keyTmp.push_back('q'); + keys.push_back(keyTmp); + + /** + * @tc.steps:step3. DeleteBatch keys from database by DeleteBatch + * @tc.expected: step3. Return E_OK. + */ + errCode = g_connection->DeleteBatch(option, keys); + EXPECT_EQ(errCode, E_OK); + + entriesRead.clear(); + + /** + * @tc.steps:step3. Get k from database by GetEntries + * @tc.expected: step3. Return E_OK. The result size is 0. + */ + errCode = g_connection->GetEntries(option, keyRead, entriesRead); + EXPECT_EQ(errCode, -E_NOT_FOUND); + EXPECT_EQ(entriesRead.size(), 0UL); +} + +static void CheckSplitData(const Value &oriValue, const uint32_t numBlock, + std::map &valueDic, Value &savedValue) +{ + MultiVerValueObject valueObject; + MultiVerNaturalStoreTransferData transferData; + std::vector partValues; + int errCode = transferData.SegmentAndTransferValueToHash(oriValue, partValues); + // Default threshold + if (oriValue.size() <= DistributedDB::DBConstant::MAX_VALUE_SIZE) { + valueObject.SetFlag(0); + valueObject.SetValue(oriValue); + valueObject.GetSerialData(savedValue); + EXPECT_EQ(errCode, -E_UNEXPECTED_DATA); + EXPECT_EQ(partValues.size(), numBlock); + return; + } + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(partValues.size(), numBlock); + + valueObject.SetFlag(MultiVerValueObject::HASH_FLAG); + std::vector hashValues; + ValueSliceHash hashValue; + for (const auto &value : partValues) { + errCode = DBCommon::CalcValueHash(value, hashValue); + EXPECT_EQ(errCode, E_OK); + + // prepare for recover + valueDic[hashValue] = value; + hashValues.push_back(std::move(hashValue)); + } + + valueObject.SetValueHash(hashValues); + valueObject.GetSerialData(savedValue); + + return; +} + +static void CheckRecoverData(const Value &savedValue, std::map &valueDic, + Value &checkValue) +{ + Value value; + MultiVerValueObject valueObject; + EXPECT_EQ(valueObject.DeSerialData(savedValue), E_OK); + if (!valueObject.IsHash()) { + EXPECT_EQ(valueObject.GetValue(value), E_OK); + } + + std::vector sliceHashVect; + EXPECT_EQ(valueObject.GetValueHash(sliceHashVect), E_OK); + + value.reserve(valueObject.GetDataLength()); + for (const auto &item : sliceHashVect) { + Value itemValue = valueDic[item]; + value.insert(value.end(), itemValue.begin(), itemValue.end()); + } + + EXPECT_EQ(value, checkValue); + return; +} + +/** + * @tc.name: BlockDataIndex001 + * @tc.desc: Determine the block threshold of the database. + * @tc.type: FUNC + * @tc.require: AR000CQDTT SR000CQDTR + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageDataOperationTest, BlockDataIndex001, TestSize.Level1) +{ + /** + * @tc.steps:step1/2/3. Put 100B 1K 100k size of unique value into database + */ + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(value1, 100); // 100B + Value value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, 1024); // 1K + Value value3; + DistributedDBToolsUnitTest::GetRandomKeyValue(value3, 1024 * 100); // 100K + + IOption option; + option.dataType = IOption::SYNC_DATA; + int errCode = g_connection->Put(option, KEY_1, value1); + EXPECT_EQ(errCode, E_OK); + errCode = g_connection->Put(option, KEY_2, value2); + EXPECT_EQ(errCode, E_OK); + errCode = g_connection->Put(option, KEY_3, value3); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps:step4. Check split status + * @tc.expected: step4. Value1 not cut, value2 cut into 1 block, value3 cut into 2 blocks. + */ + std::map valueDic; + Value savedValue1; + CheckSplitData(value1, 0ul, valueDic, savedValue1); + Value savedValue2; + CheckSplitData(value2, 0ul, valueDic, savedValue2); + Value savedValue3; + CheckSplitData(value3, 0ul, valueDic, savedValue3); + + /** + * @tc.steps:step5. Get the original before key + * @tc.expected: step5. Return the right original value. + */ + Value valueRead; + valueRead.clear(); + errCode = g_connection->Get(option, KEY_1, valueRead); + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(value1, valueRead); + valueRead.clear(); + errCode = g_connection->Get(option, KEY_2, valueRead); + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(value2, valueRead); + valueRead.clear(); + errCode = g_connection->Get(option, KEY_3, valueRead); + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(value3, valueRead); +} + +/** + * @tc.name: CutValueIntoBlock001 + * @tc.desc: Database block size test + * @tc.type: FUNC + * @tc.require: AR000CQDTS AR000CQDTU + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageDataOperationTest, CutValueIntoBlock001, TestSize.Level1) +{ + /** + * @tc.steps:step1/2/3/4. Put 100B 1K 100k 64k size of unique value into database + */ + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(value1, 100); // 100B + Value value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, 1024); // 1K + Value value3; + DistributedDBToolsUnitTest::GetRandomKeyValue(value3, 1024 * 100); // 100k + Value value4; + DistributedDBToolsUnitTest::GetRandomKeyValue(value4, 1024 * 64); // 64K + + /** + * @tc.steps:step4. Split and check repeat value block. + * @tc.expected: step4. No repeat block. + */ + IOption option; + option.dataType = IOption::SYNC_DATA; + int errCode = g_connection->Put(option, KEY_1, value1); + EXPECT_EQ(errCode, E_OK); + errCode = g_connection->Put(option, KEY_2, value2); + EXPECT_EQ(errCode, E_OK); + errCode = g_connection->Put(option, KEY_3, value3); + EXPECT_EQ(errCode, E_OK); + errCode = g_connection->Put(option, KEY_4, value4); + EXPECT_EQ(errCode, E_OK); + + std::map valueDic; + Value savedValue; + CheckSplitData(value1, 0ul, valueDic, savedValue); + CheckRecoverData(savedValue, valueDic, value1); + + valueDic.clear(); + savedValue.clear(); + CheckSplitData(value2, 0ul, valueDic, savedValue); + CheckRecoverData(savedValue, valueDic, value2); + + valueDic.clear(); + savedValue.clear(); + CheckSplitData(value3, 0ul, valueDic, savedValue); + CheckRecoverData(savedValue, valueDic, value3); + EXPECT_EQ(valueDic.size(), 0ul); + + valueDic.clear(); + savedValue.clear(); + CheckSplitData(value4, 0ul, valueDic, savedValue); + CheckRecoverData(savedValue, valueDic, value4); +} + +/** + * @tc.name: CutValueIntoBlock002 + * @tc.desc: Block data index + * @tc.type: FUNC + * @tc.require: AR000CQDTT AR000CQDTV + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageDataOperationTest, CutValueIntoBlock002, TestSize.Level1) +{ + /** + * @tc.steps:step1/2/3. Put 64k 100k 200k size of value into database(some blocks are repeated). + */ + Value valueTemp; + DistributedDBToolsUnitTest::GetRandomKeyValue(valueTemp, 1024 * 36); // 36K add 64K equal 100K + + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(value1, 1024 * 64); // 64K + Value value2; + value2.insert(value2.end(), value1.begin(), value1.end()); + value2.insert(value2.end(), valueTemp.begin(), valueTemp.end()); + + Value value3; + // repeat twice value1 in front of value3 + for (int i = 0; i < 2; i++) { + value3.insert(value3.end(), value1.begin(), value1.end()); + } + value3.insert(value3.end(), valueTemp.begin(), valueTemp.end()); + value3.insert(value3.end(), valueTemp.begin(), valueTemp.end()); + + IOption option; + option.dataType = IOption::SYNC_DATA; + int errCode = g_connection->Put(option, KEY_1, value1); + EXPECT_EQ(errCode, E_OK); + errCode = g_connection->Put(option, KEY_2, value2); + EXPECT_EQ(errCode, E_OK); + errCode = g_connection->Put(option, KEY_3, value3); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps:step4. Split and check repeat value block. + * @tc.expected: step4. Duplicate blocks are eliminated + */ + std::map valueDic; + Value savedValue; + CheckSplitData(value3, 0ul, valueDic, savedValue); + CheckRecoverData(savedValue, valueDic, value3); + EXPECT_EQ(valueDic.size(), 0ul); + + savedValue.clear(); + CheckSplitData(value1, 0ul, valueDic, savedValue); + CheckRecoverData(savedValue, valueDic, value1); + EXPECT_EQ(valueDic.size(), 0ul); + + savedValue.clear(); + CheckSplitData(value2, 0ul, valueDic, savedValue); + CheckRecoverData(savedValue, valueDic, value2); + EXPECT_EQ(valueDic.size(), 0ul); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_encrypt_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_encrypt_test.cpp new file mode 100644 index 00000000..ca927e8b --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_encrypt_test.cpp @@ -0,0 +1,1397 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 + +#include "db_types.h" +#include "log_print.h" +#include "securec.h" +#include "sqlite_import.h" + +#ifndef OMIT_ENCRYPT +using namespace testing::ext; +using namespace DistributedDB; +using namespace std; +using namespace std::placeholders; + +namespace { + sqlite3 *g_db = nullptr; + const string STORE_ID = "test"; + const string STORE_ID2 = "test2"; + const string STORE_ID3 = "test3"; + const int PASSWD_TEST_SIZE = 16; + char g_oldPasswd[PASSWD_TEST_SIZE + 1] = {0}; + char g_newPasswd[PASSWD_TEST_SIZE + 1] = {0}; + char g_diffPasswd[PASSWD_TEST_SIZE + 1] = {0}; + const string ALG1 = "'aes-256-gcm'"; + const string ALG2 = "'aes-256-cbc'"; + const string ALG3 = "'ABCDEG'"; + const int ITERATION = 64000; + const int ITERATION2 = 1000; + const std::string CREATE_SQL = "CREATE TABLE IF NOT EXISTS data(key TEXT PRIMARY KEY, value TEXT);"; + const int SLEEP_TIME = 1; + + const std::vector KEY_1 = {'A'}; + const std::vector VALUE_1 = {'1'}; + const std::vector VALUE_2 = {'2'}; +#ifndef USE_SQLITE_CODEC_CIPHER + const std::string PRAGMA_CIPHER = "PRAGMA cipher="; + const std::string PRAGMA_KDF_ITER = "PRAGMA kdf_iter="; + const std::string EXPORT_STRING = "sqlcipher_export"; +#else + const std::string PRAGMA_CIPHER = "PRAGMA codec_cipher="; + const std::string PRAGMA_KDF_ITER = "PRAGMA codec_kdf_iter="; + const std::string EXPORT_STRING = "export_database"; +#endif + + int Callback(void *data, int argc, char **argv, char **azColName) + { + vector *value = static_cast *>(data); + value->push_back(*argv[0]); + return 0; + } + + int Open(sqlite3 *&db, const string &storeID) + { + std::string uri = "file:" + storeID + ".db"; + sqlite3 *dbTemp = nullptr; + int errCode = sqlite3_open_v2(uri.c_str(), &dbTemp, + SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); + if (errCode != SQLITE_OK) { + if (dbTemp != nullptr) { + (void)sqlite3_close_v2(dbTemp); + dbTemp = nullptr; + } + return errCode; + } + db = dbTemp; + + return errCode; + } + + int CreateTable() + { + char *zErrMsg = nullptr; + int errCode = sqlite3_exec(g_db, CREATE_SQL.c_str(), nullptr, nullptr, &zErrMsg); + if (errCode != SQLITE_OK && zErrMsg != nullptr) { + LOGE(" [SQLITE]: %s", zErrMsg); + sqlite3_free(zErrMsg); + return errCode; + } + + return errCode; + } + + int SetEncryptParam(const char *passwd, int iterNumber, const string &algName) + { + char *zErrMsg = nullptr; + int errCode = sqlite3_key(g_db, static_cast(passwd), strlen(passwd)); + if (errCode != SQLITE_OK && zErrMsg != nullptr) { + LOGE(" [SQLITE]: %s", zErrMsg); + sqlite3_free(zErrMsg); + return errCode; + } + + errCode = sqlite3_exec(g_db, (PRAGMA_KDF_ITER + to_string(iterNumber)).c_str(), nullptr, nullptr, + &zErrMsg); + if (errCode != SQLITE_OK && zErrMsg != nullptr) { + LOGE(" [SQLITE]: %s", zErrMsg); + sqlite3_free(zErrMsg); + return errCode; + } + + errCode = sqlite3_exec(g_db, (PRAGMA_CIPHER + algName + ";").c_str(), nullptr, nullptr, + &zErrMsg); + if (errCode != SQLITE_OK && zErrMsg != nullptr) { + LOGE(" [SQLITE]: %s", zErrMsg); + sqlite3_free(zErrMsg); + return errCode; + } + + return errCode; + } + + int OpenWithKey(const char *passwd, int iterNumber, const string &algName, bool isEncrypted) + { + int errCode = Open(g_db, STORE_ID); + if (errCode != SQLITE_OK) { + return errCode; + } + if (isEncrypted) { + errCode = SetEncryptParam(passwd, iterNumber, algName); + if (errCode != SQLITE_OK) { + return errCode; + } + } + + errCode = CreateTable(); + if (errCode != SQLITE_OK) { + return errCode; + } + errCode = sqlite3_close(g_db); + if (errCode != SQLITE_OK) { + return errCode; + } + + errCode = Open(g_db, STORE_ID); + if (errCode != SQLITE_OK) { + return errCode; + } + + return errCode; + } + + int InputPasswd(const char *passwd, int iterNumber, const string &algName) + { + int errCode = SetEncryptParam(passwd, iterNumber, algName); + if (errCode != SQLITE_OK) { + return errCode; + } + + return errCode; + } + + int Reconnect(const char *passwd, int iterNumber, const string &algName) + { + int errCode = Open(g_db, STORE_ID); + if (errCode != SQLITE_OK) { + return errCode; + } + errCode = InputPasswd(passwd, iterNumber, algName); + if (errCode != SQLITE_OK) { + return errCode; + } + errCode = InputPasswd(passwd, iterNumber, algName); + if (errCode != SQLITE_OK) { + return errCode; + } + + return errCode; + } + + int PutValue(const Key &key, const Value &value) + { + char *zErrMsg = nullptr; + string keyStr(key.begin(), key.end()); + string valueStr(value.begin(), value.end()); + int errCode = sqlite3_exec(g_db, ("INSERT OR REPLACE INTO data VALUES('" + keyStr + "','" + valueStr + + "');").c_str(), nullptr, nullptr, &zErrMsg); + if (errCode != SQLITE_OK && zErrMsg != nullptr) { + LOGE(" [SQLITE]: %s", zErrMsg); + sqlite3_free(zErrMsg); + return errCode; + } + return errCode; + } + + int DeleteValue(const Key &key) + { + char *zErrMsg = nullptr; + string keyStr(key.begin(), key.end()); + int errCode = sqlite3_exec(g_db, ("DELETE FROM data WHERE key='" + keyStr + "';").c_str(), nullptr, + nullptr, &zErrMsg); + if (errCode != SQLITE_OK && zErrMsg != nullptr) { + LOGE(" [SQLITE]: %s", zErrMsg); + sqlite3_free(zErrMsg); + return errCode; + } + return errCode; + } + + int UpdateValue(const Key &key, const Value &value) + { + char *zErrMsg = nullptr; + string keyStr(key.begin(), key.end()); + string valueStr(value.begin(), value.end()); + int errCode = sqlite3_exec(g_db, ("INSERT OR REPLACE INTO data VALUES('" + keyStr + "','" + valueStr + + "');").c_str(), nullptr, nullptr, &zErrMsg); + if (errCode != SQLITE_OK && zErrMsg != nullptr) { + LOGE(" [SQLITE]: %s", zErrMsg); + sqlite3_free(zErrMsg); + return errCode; + } + return errCode; + } + + int GetValue(const Key &key, Value &value) + { + char *zErrMsg = nullptr; + string keyStr(key.begin(), key.end()); + int errCode = sqlite3_exec(g_db, ("SELECT value from data WHERE key='" + keyStr + "';").c_str(), + Callback, static_cast(&value), &zErrMsg); + if (errCode != SQLITE_OK && zErrMsg != nullptr) { + LOGE(" [SQLITE]: %s", zErrMsg); + sqlite3_free(zErrMsg); + return errCode; + } + return errCode; + } + + int Export(const string &dbName) + { + char *zErrMsg = nullptr; + int errCode = sqlite3_exec(g_db, ("SELECT " + EXPORT_STRING + "('" + dbName + "');").c_str(), nullptr, nullptr, + &zErrMsg); + if (errCode != SQLITE_OK && zErrMsg != nullptr) { + LOGE(" [SQLITE]: %s", zErrMsg); + sqlite3_free(zErrMsg); + return errCode; + } + return errCode; + } + + int Attach(const string &dbName) + { + char *zErrMsg = nullptr; + int errCode = sqlite3_exec(g_db, ("attach '" + dbName + ".db' as " + dbName + " key '';").c_str(), + nullptr, nullptr, &zErrMsg); + if (errCode != SQLITE_OK && zErrMsg != nullptr) { + LOGE(" [SQLITE]: %s", zErrMsg); + sqlite3_free(zErrMsg); + return errCode; + } + return errCode; + } + + int AttachWithKey(const string &dbName, const char *passwd) + { + char *zErrMsg = nullptr; + int errCode = sqlite3_exec(g_db, ("attach '" + dbName + ".db' as " + dbName + " key '" + passwd + "';").c_str(), + nullptr, nullptr, &zErrMsg); + if (errCode != SQLITE_OK && zErrMsg != nullptr) { + LOGE(" [SQLITE]: %s", zErrMsg); + sqlite3_free(zErrMsg); + return errCode; + } + return errCode; + } + + int MultipleOperation(Value &valueGet, const Value &valueUpdate) + { + int errCode = PutValue(KEY_1, VALUE_1); + if (errCode != SQLITE_OK) { + return errCode; + } + errCode = UpdateValue(KEY_1, valueUpdate); + if (errCode != SQLITE_OK) { + return errCode; + } + GetValue(KEY_1, valueGet); + errCode = DeleteValue(KEY_1); + if (errCode != SQLITE_OK) { + return errCode; + } + return errCode; + } +} + +class DistributedDBStorageEncryptTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBStorageEncryptTest::SetUpTestCase(void) +{ + unsigned char initialByte = 0; + RAND_bytes(&initialByte, 1); + for (int i = 0; i < PASSWD_TEST_SIZE; i++) { + initialByte %= 20; // keep the number < 20, so 'A' + 20 is the maximum alphabet. + g_oldPasswd[i] = ('A' + initialByte++); + g_newPasswd[i] = ('A' + initialByte++); + g_diffPasswd[i] = ('A' + initialByte++); + } +} + +void DistributedDBStorageEncryptTest::TearDownTestCase(void) +{ +} + +void DistributedDBStorageEncryptTest::SetUp(void) +{ + testing::UnitTest *test = testing::UnitTest::GetInstance(); + ASSERT_NE(test, nullptr); + const testing::TestInfo *testInfo = test->current_test_info(); + ASSERT_NE(testInfo, nullptr); + LOGI("Start unit test: %s.%s", testInfo->test_case_name(), testInfo->name()); + /** + * @tc.Clean DB files created from every test case. + */ + if (remove((STORE_ID + ".db").c_str()) != 0) { + LOGE("remove db failed, errno:%d", errno); + } + if (remove((STORE_ID2 + ".db").c_str()) != 0) { + LOGE("remove db failed, errno:%d", errno); + } +} + +void DistributedDBStorageEncryptTest::TearDown(void) +{ + /** + * @tc.make sure g_db is nullptr and is closed. + */ + if (g_db != nullptr) { + g_db = nullptr; + } + /** + * @tc.Clean DB files created from every test case. + */ + if (remove((STORE_ID + ".db").c_str()) != 0) { + LOGE("remove db failed, errno:%d", errno); + } + if (remove((STORE_ID2 + ".db").c_str()) != 0) { + LOGE("remove db failed, errno:%d", errno); + } + /** + * @tc.Wait a number of SLEEP_TIME until remove done. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_TIME)); +} + +/** + * @tc.name: EncryptTest001 + * @tc.desc: Check if opening database possible without encryption + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database without being encrypted. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, false), SQLITE_OK); + + /** + * @tc.steps:step2/5. Add, Update, Get and Delete the data. + * @tc.expected: step2/5. Return SQLITE_OK. + */ + Value valueGet; + Value valueUpdate = VALUE_2; + EXPECT_EQ(MultipleOperation(valueGet, valueUpdate), SQLITE_OK); + EXPECT_EQ(valueGet, valueUpdate); + + /** + * @tc.steps:step6. Close DB. + * @tc.expected: step6. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest002 + * @tc.desc: Check if it is possible to open nonencrypted database with password. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a nonencrypted DB. + * @tc.expected: step1. Return 0. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, false), SQLITE_OK); + + /** + * @tc.steps:step2. Set the key to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3/6. Add, Update, Get and Delete the data. + * @tc.expected: step3/6. Return NOT_EQUAL_OK values. + */ + Value valueGet; + Value valueUpdate = { VALUE_2 }; + EXPECT_NE(MultipleOperation(valueGet, valueUpdate), SQLITE_OK); + EXPECT_NE(valueGet, valueUpdate); + + /** + * @tc.steps:step7. Close DB. + * @tc.expected: step7. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest003 + * @tc.desc: Check if deciphering an encrypted database possible with wrong password. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open an encrypted DB with password g_oldPasswd. + * @tc.expected: step1. Return 0. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_diffPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_diffPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3/6. Add, Update, Get and Delete the data. + * @tc.expected: step3/6. Return NOT_EQUAL_OK values. + */ + Value valueGet; + Value valueUpdate = { VALUE_2 }; + EXPECT_NE(MultipleOperation(valueGet, valueUpdate), SQLITE_OK); + EXPECT_NE(valueGet, valueUpdate); + + /** + * @tc.steps:step7. Close DB. + * @tc.expected: step7. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest004 + * @tc.desc: Check if deciphering an encrypted database possible with correct password. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open an encrypted DB with password g_oldPasswd. + * @tc.expected: step1. Return 0. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3/6. Add, Update, Get and Delete the data. + * @tc.expected: step3/6. Return SQLITE_OK values. + */ + Value valueGet; + Value valueUpdate = { VALUE_2 }; + EXPECT_EQ(MultipleOperation(valueGet, valueUpdate), SQLITE_OK); + EXPECT_EQ(valueGet, valueUpdate); + + /** + * @tc.steps:step7. Close DB. + * @tc.expected: step7. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} +#ifdef USE_SQLITE_CODEC_CIPHER +/** + * @tc.name: EncryptTest005 + * @tc.desc: Check if rekeying possible with wrong password. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest005, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open an encrypted DB with password g_oldPasswd. + * @tc.expected: step1. Return 0. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_diffPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_diffPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. Reset the key by invoking the sqlite3_rekey() with the password as g_newPasswd. + * @tc.expected: step3. Return SQLITE_ERROR values. + */ + EXPECT_EQ(sqlite3_rekey(g_db, static_cast(g_newPasswd), strlen(g_newPasswd)), SQLITE_ERROR); + + /** + * @tc.steps:step4. Close DB. + * @tc.expected: step4. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} +#endif +/** + * @tc.name: EncryptTest006 + * @tc.desc: Check if rekeying possible with correct password. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest006, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open an encrypted DB with password g_oldPasswd. + * @tc.expected: step1. Return 0. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. Reset the key by invoking the sqlite3_rekey() with the password as g_newPasswd. + * @tc.expected: step3. Return SQLITE_OK values. + */ + EXPECT_EQ(sqlite3_rekey(g_db, static_cast(g_newPasswd), strlen(g_newPasswd)), SQLITE_OK); + + /** + * @tc.steps:step4. Close DB. + * @tc.expected: step4. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest007 + * @tc.desc: Check if manipulating data possible after rekeying before disconnecting with DB. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest007, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open an encrypted DB with password g_oldPasswd. + * @tc.expected: step1. Return 0. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. Reset the key by invoking the sqlite3_rekey() with the password as g_newPasswd. + * @tc.expected: step3. Return SQLITE_OK values. + */ + EXPECT_EQ(sqlite3_rekey(g_db, static_cast(g_newPasswd), strlen(g_newPasswd)), SQLITE_OK); + + /** + * @tc.steps:step4/7. Add, Update, Get and Delete the data. + * @tc.expected: step4/7. Return SQLITE_OK values. + */ + Value valueGet; + Value valueUpdate = { VALUE_2 }; + EXPECT_EQ(MultipleOperation(valueGet, valueUpdate), SQLITE_OK); + EXPECT_EQ(valueGet, valueUpdate); + + /** + * @tc.steps:step8. Close DB. + * @tc.expected: step8. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest008 + * @tc.desc: Check if manipulating data possible after rekeying and reconnection with a wrong password. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest008, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open an encrypted DB with password g_oldPasswd. + * @tc.expected: step1. Return 0. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. Reset the key by invoking the sqlite3_rekey() with the password as g_newPasswd. + * @tc.expected: step3. Return SQLITE_OK values. + */ + EXPECT_EQ(sqlite3_rekey(g_db, static_cast(g_newPasswd), strlen(g_newPasswd)), SQLITE_OK); + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); + + /** + * @tc.steps:step4. Open DB with the original password 'g_oldPasswd'. + * @tc.expected: step4. Return SQLITE_OK values. + */ + EXPECT_EQ(Reconnect(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step5/8. Add, Update, Get and Delete the data. + * @tc.expected: step5/8. Return NOT_OK values. + */ + Value valueGet; + Value valueUpdate = { VALUE_2 }; + EXPECT_NE(MultipleOperation(valueGet, valueUpdate), SQLITE_OK); + EXPECT_NE(valueGet, valueUpdate); + + /** + * @tc.steps:step9. Close DB. + * @tc.expected: step9. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest009 + * @tc.desc: Check if manipulating data possible after rekeying and reconnection with a correct password. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng +*/ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest009, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open an encrypted DB with password g_oldPasswd. + * @tc.expected: step1. Return 0. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. Reset the key by invoking the sqlite3_rekey() with the password as g_newPasswd. + * @tc.expected: step3. Return SQLITE_OK values. + */ + EXPECT_EQ(sqlite3_rekey(g_db, static_cast(g_newPasswd), strlen(g_newPasswd)), SQLITE_OK); + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); + + /** + * @tc.steps:step4. Open DB with the new password 'g_newPasswd'. + * @tc.expected: step4. Return SQLITE_OK values. + */ + EXPECT_EQ(Reconnect(g_newPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step5/8. Add, Update, Get and Delete the data. + * @tc.expected: step5/8. Return SQLITE_OK values. + */ + Value valueGet; + Value valueUpdate = { VALUE_2 }; + EXPECT_EQ(MultipleOperation(valueGet, valueUpdate), SQLITE_OK); + EXPECT_EQ(valueGet, valueUpdate); + + /** + * @tc.steps:step9. Close DB. + * @tc.expected: step9. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest010 + * @tc.desc: Export DB when there is no encryption. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng +*/ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest010, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database without being encrypted. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, false), SQLITE_OK); + + /** + * @tc.steps:step2. Attach DB. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(Attach(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step3. export DB. + * @tc.expected: step3. Return SQLITE_OK. + */ + EXPECT_EQ(Export(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step4. Close DB. + * @tc.expected: step4. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest011 + * @tc.desc: Export DB when there is no encryption but decipherment is attempted. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest011, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database without being encrypted. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, false), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. Attach DB. + * @tc.expected: step3. Return is not SQLITE_OK. + */ + EXPECT_NE(Attach(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step4. export DB. + * @tc.expected: step4. Return NOT_OK. + */ + EXPECT_NE(Export(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step5. Close DB. + * @tc.expected: step5. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest012 + * @tc.desc: Export DB when there is encryption but password is wrong. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest012, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database with password g_oldPasswd. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_diffPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_diffPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. Attach DB. + * @tc.expected: step3. Return NOT_SQLITE_OK. + */ + EXPECT_NE(Attach(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step4. export DB. + * @tc.expected: step4. Return NOT_OK. + */ + EXPECT_NE(Export(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step5. Close DB. + * @tc.expected: step5. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest013 + * @tc.desc: Export DB when there is encryption and password matches. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest013, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database with password g_oldPasswd. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_diffPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. Attach DB. + * @tc.expected: step3. Return SQLITE_OK. + */ + EXPECT_EQ(Attach(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step4. export DB. + * @tc.expected: step4. Return SQLITE_OK. + */ + EXPECT_EQ(Export(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step5. Close DB. + * @tc.expected: step5. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest014 + * @tc.desc: Attach DB files when there is no encryption. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest014, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database without being encrypted. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, false), SQLITE_OK); + + /** + * @tc.steps:step2. attach DB file STORE_ID2. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(Attach(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step3. Close DB. + * @tc.expected: step3. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest015 + * @tc.desc: Attach DB files when there is no encryption but decipherment is attempted. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest015, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database without being encrypted. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, false), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. attach DB file STORE_ID2. + * @tc.expected: step3. Return is not SQLITE_OK. + */ + EXPECT_NE(Attach(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step4. Close DB. + * @tc.expected: step4. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest016 + * @tc.desc: Attach DB files when there is encryption but password dismatches. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest016, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database with password g_oldPasswd. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_diffPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_diffPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. attach DB file STORE_ID2. + * @tc.expected: step3. Return NOT_SQLITE_OK. + */ + EXPECT_NE(Attach(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step4. Close DB. + * @tc.expected: step4. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest017 + * @tc.desc: Attach DB files when there is encryption and password matches. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest017, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database with password g_oldPasswd. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. attach DB file STORE_ID2. + * @tc.expected: step3. Return SQLITE_OK. + */ + EXPECT_EQ(Attach(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step4. Close DB. + * @tc.expected: step4. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest018 + * @tc.desc: Export attached DB file failed if the file does not exist. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest018, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database with password g_oldPasswd. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. attach DB file STORE_ID2. + * @tc.expected: step3. Return SQLITE_OK. + */ + EXPECT_EQ(Attach(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step3. export DB. + * @tc.expected: step3. Return NOT_OK. + */ + EXPECT_NE(Export(STORE_ID3), SQLITE_OK); + + /** + * @tc.steps:step4. Close DB. + * @tc.expected: step4. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest019 + * @tc.desc: Export attached DB file succeeded if the file exists. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng +*/ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest019, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database with password g_oldPasswd. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. attach DB file STORE_ID2. + * @tc.expected: step3. Return SQLITE_OK. + */ + EXPECT_EQ(Attach(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step4. export DB. + * @tc.expected: step4. Return NOT_OK. + */ + EXPECT_EQ(Export(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step5. Close DB. + * @tc.expected: step5. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest020 + * @tc.desc: Failed to manipulate the data if the parameter of number of iteration dismatches. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest020, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database with password g_oldPasswd and choose not to save password. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION2, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3/6. Add, Update, Get and Delete the data. + * @tc.expected: step3/6. Return SQLITE_OK values. + */ + Value valueGet; + Value valueUpdate = { VALUE_2 }; + EXPECT_NE(MultipleOperation(valueGet, valueUpdate), SQLITE_OK); + EXPECT_NE(valueGet, valueUpdate); + + /** + * @tc.steps:step7. Close DB. + * @tc.expected: step7. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest021 + * @tc.desc: Succeeded to manipulate the data if the parameter of number of iteration matches. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest021, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database with password g_oldPasswd and choose not to save password. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3/6. Add, Update, Get and Delete the data. + * @tc.expected: step3/6. Return SQLITE_OK values. + */ + Value valueGet; + Value valueUpdate = { VALUE_2 }; + EXPECT_EQ(MultipleOperation(valueGet, valueUpdate), SQLITE_OK); + EXPECT_EQ(valueGet, valueUpdate); + + /** + * @tc.steps:step7. Close DB. + * @tc.expected: step7. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest022 + * @tc.desc: Failed to manipulate the data if the parameter of encryption algorithm dismatches. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest022, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database with password g_oldPasswd and choose not to save password. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG2), SQLITE_OK); + + /** + * @tc.steps:step3/6. Add, Update, Get and Delete the data. + * @tc.expected: step3/6. Return SQLITE_OK values. + */ + Value valueGet; + Value valueUpdate = { VALUE_2 }; + EXPECT_NE(MultipleOperation(valueGet, valueUpdate), SQLITE_OK); + EXPECT_NE(valueGet, valueUpdate); + + /** + * @tc.steps:step7. Close DB. + * @tc.expected: step7. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest023 + * @tc.desc: Succeeded to manipulate the data if the parameter of encryption algorithm matches. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest023, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database with password g_oldPasswd and choose not to save password. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3/6. Add, Update, Get and Delete the data. + * @tc.expected: step3/6. Return SQLITE_OK values. + */ + Value valueGet; + Value valueUpdate = { VALUE_2 }; + EXPECT_EQ(MultipleOperation(valueGet, valueUpdate), SQLITE_OK); + EXPECT_EQ(valueGet, valueUpdate); + + /** + * @tc.steps:step7. Close DB. + * @tc.expected: step7. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest024 + * @tc.desc: Export attached DB (no password) file and check the context. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest024, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database with password g_oldPasswd. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG1, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG1), SQLITE_OK); + + /** + * @tc.steps:step3. Put key into DB + * @tc.expected: step3. Return SQLITE_OK. + */ + EXPECT_EQ(PutValue(KEY_1, VALUE_1), SQLITE_OK); + + /** + * @tc.steps:step4. attach DB file STORE_ID2. + * @tc.expected: step4. Return SQLITE_OK. + */ + EXPECT_EQ(Attach(STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step5. Export DB. + * @tc.expected: step5. Return SQLITE_OK. + */ + EXPECT_EQ(Export(STORE_ID2), SQLITE_OK); + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); + + /** + * @tc.steps:step6. Open exported DB. + * @tc.expected: step6. Return SQLITE_OK. + */ + EXPECT_EQ(Open(g_db, STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step7. Get Value from exported DB and the value shall be the same as the original one. + * @tc.expected: step7. Return SQLITE_OK. + */ + Value valueGet; + GetValue(KEY_1, valueGet); + EXPECT_EQ(valueGet, VALUE_1); + + /** + * @tc.steps:step8. Close DB. + * @tc.expected: step8. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest025 + * @tc.desc: Export attached DB (password) file and check the context. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest025, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open a database without password. + * @tc.expected: step1. Return SQLITE_OK. + */ + EXPECT_EQ(Open(g_db, STORE_ID), SQLITE_OK); + + sqlite3_exec(g_db, ("PRAGMA cipher_default_attach_kdf_iter=5000;"), nullptr, nullptr, nullptr); + sqlite3_exec(g_db, ("PRAGMA cipher_default_attach_cipher='aes-256-gcm';"), nullptr, nullptr, nullptr); + + EXPECT_EQ(CreateTable(), SQLITE_OK); + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); + EXPECT_EQ(Open(g_db, STORE_ID), SQLITE_OK); + EXPECT_EQ(PutValue(KEY_1, VALUE_1), SQLITE_OK); + + /** + * @tc.steps:step2. attach DB file STORE_ID2 with password. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(AttachWithKey(STORE_ID2, g_oldPasswd), SQLITE_OK); + + /** + * @tc.steps:step3. export DB. + * @tc.expected: step3. Return SQLITE_OK. + */ + EXPECT_EQ(Export(STORE_ID2), SQLITE_OK); + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); + + /** + * @tc.steps:step4. Open exported DB. + * @tc.expected: step4. Return SQLITE_OK. + */ + EXPECT_EQ(Open(g_db, STORE_ID2), SQLITE_OK); + + /** + * @tc.steps:step5. Input password to g_oldPasswd. + * @tc.expected: step5. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_key(g_db, static_cast(g_oldPasswd), strlen(g_oldPasswd)), SQLITE_OK); + + EXPECT_EQ(sqlite3_exec(g_db, (PRAGMA_CIPHER + "'aes-256-gcm';").c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + EXPECT_EQ(sqlite3_exec(g_db, (PRAGMA_KDF_ITER + "5000;").c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + + /** + * @tc.steps:step6. Get Value from exported DB and the value shall be the same as the original one. + * @tc.expected: step6. Return SQLITE_OK. + */ + Value valueGet; + GetValue(KEY_1, valueGet); + EXPECT_EQ(valueGet, VALUE_1); + + /** + * @tc.steps:step6. Close DB. + * @tc.expected: step6. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} + +/** + * @tc.name: EncryptTest026 + * @tc.desc: Check if deciphering with a non-existing algorithm can be detected. + * @tc.type: FUNC + * @tc.require: AR000CQDT6 + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageEncryptTest, EncryptTest026, TestSize.Level1) +{ + /** + * @tc.steps:step1. Open an encrypted DB with password g_oldPasswd. + * @tc.expected: step1. Return 0. + */ + EXPECT_EQ(OpenWithKey(g_oldPasswd, ITERATION, ALG3, true), SQLITE_OK); + + /** + * @tc.steps:step2. Set password to g_oldPasswd. + * @tc.expected: step2. Return SQLITE_OK. + */ + EXPECT_EQ(InputPasswd(g_oldPasswd, ITERATION, ALG3), SQLITE_OK); + + /** + * @tc.steps:step3/6. Add, Update, Get and Delete the data. + * @tc.expected: step3/6. Return SQLITE_OK values. + */ + Value valueGet; + Value valueUpdate = { VALUE_2 }; + EXPECT_EQ(MultipleOperation(valueGet, valueUpdate), SQLITE_OK); + EXPECT_EQ(valueGet, valueUpdate); + + /** + * @tc.steps:step7. Close DB. + * @tc.expected: step7. Return SQLITE_OK. + */ + EXPECT_EQ(sqlite3_close(g_db), SQLITE_OK); +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_index_optimize_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_index_optimize_test.cpp new file mode 100644 index 00000000..77805452 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_index_optimize_test.cpp @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_JSON +#include + +#include "db_common.h" +#include "db_constant.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" +#include "platform_specific.h" +#include "sqlite_import.h" +#include "store_types.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { + std::string g_testDir; + std::string g_identifier; + + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + DBStatus g_kvNbDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + std::placeholders::_1, std::placeholders::_2, std::ref(g_kvNbDelegateStatus), std::ref(g_kvNbDelegatePtr)); + + const std::string BASE_SCHEMA_STRING = "{\"SCHEMA_VERSION\" : \"1.0\"," + "\"SCHEMA_MODE\" : \"COMPATIBLE\"," + "\"SCHEMA_DEFINE\" : {" + "\"name\" : \"STRING\"," + "\"id\" : \"INTEGER\"," + "\"father\" : {" + "\"name\" : \"STRING\"," + "\"id\" : \"INTEGER\"" + "}," + "\"phone\" : \"INTEGER\"" + "}," + "\"SCHEMA_INDEXES\" : "; + + const std::string JSON_VALUE ="{\"name\":\"Tom\"," + "\"id\":10," + "\"father\":{\"name\":\"Jim\", \"id\":20}," + "\"phone\":20}"; + + void GenerateSchemaString(std::string &schema, const std::string &indexString) + { + schema = BASE_SCHEMA_STRING + indexString + "}"; + } + + std::string GetKvStoreDirectory(const std::string &userId, const std::string &appId, const std::string &storeId) + { + string identifier = DBCommon::GenerateIdentifierId(storeId, appId, userId); + string hashIdentifierName = DBCommon::TransferHashString(identifier); + string identifierName = DBCommon::TransferStringToHex(hashIdentifierName); + string filePath = g_testDir + "/" + identifierName + "/" + DBConstant::SINGLE_SUB_DIR + "/main/"; + filePath += DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + return filePath; + } + + bool CheckIndexFromDbFile(const::std::string &filePath, const std::string &indexName) + { + sqlite3 *db = nullptr; + if (sqlite3_open_v2(filePath.c_str(), &db, SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE, nullptr) != SQLITE_OK) { + LOGD("DB open failed %s", filePath.c_str()); + if (db != nullptr) { + (void)sqlite3_close_v2(db); + } + return false; + } + + std::string querySQL = "select sql from sqlite_master where name = '" + indexName + "'"; + int errCode = sqlite3_exec(db, querySQL.c_str(), nullptr, nullptr, nullptr); + (void)sqlite3_close_v2(db); + if (errCode == SQLITE_OK) { + return true; + } + return false; + } +} + +class DistributedDBStorageIndexOptimizeTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBStorageIndexOptimizeTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + std::string origIdentifier = USER_ID + "-" + APP_ID + "-" + STORE_ID_1; + std::string identifier = DBCommon::TransferHashString(origIdentifier); + g_identifier = DBCommon::TransferStringToHex(identifier); + std::string dir = g_testDir + g_identifier + "/" + DBConstant::SINGLE_SUB_DIR; + DIR *dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } + + KvStoreConfig config; + config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(config); +} + +void DistributedDBStorageIndexOptimizeTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBStorageIndexOptimizeTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBStorageIndexOptimizeTest::TearDown(void) +{ +} + +/** + * @tc.name: ParseAndCheckUnionIndex001 + * @tc.desc: Test the Json union index parse and check function Open function + * @tc.type: FUNC + * @tc.require: AR000F3OPD + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBStorageIndexOptimizeTest, ParseAndCheckUnionIndex001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create a correct shema string include a correct union index. + */ + std::string schema1; + GenerateSchemaString(schema1, "[[\"name\", \"father.name\", \"father.id\", \"id\", \"phone\"]]"); + + /** + * @tc.steps: step2. Call SchemaObject.ParseFromSchemaString to parse the string. + * @tc.expected: step2. Expect return E_OK. + */ + SchemaObject so1; + EXPECT_EQ(so1.ParseFromSchemaString(schema1), E_OK); + + /** + * @tc.steps: step3. Create a correct shema string include a single index and a union index + */ + std::string schema2; + GenerateSchemaString(schema2, "[[\"name\", \"father.name\", \"father.id\", \"id\", \"phone\"], \"id\"]"); + + /** + * @tc.steps: step4. Call SchemaObject.ParseFromSchemaString to parse the string. + * @tc.expected: step4. Expect return E_OK. + */ + SchemaObject so2; + EXPECT_EQ(so2.ParseFromSchemaString(schema2), E_OK); + + /** + * @tc.steps: step5. Create a shema string include a single index and a union index, and the two index has + the same sort column. + */ + std::string schema3; + GenerateSchemaString(schema3, "[[\"name\", \"father.name\", \"father.id\", \"id\", \"phone\"], \"name\"]"); + + /** + * @tc.steps: step6. Call SchemaObject.ParseFromSchemaString to parse the string. + * @tc.expected: step6. Expect return E_SCHEMA_PARSE_FAIL. + */ + SchemaObject so3; + EXPECT_EQ(so3.ParseFromSchemaString(schema3), -E_SCHEMA_PARSE_FAIL); + + /** + * @tc.steps: step7. Create a shema string include a single index with a not exist column + */ + std::string schema4; + GenerateSchemaString(schema4, "[[\"name\", \"father.name\", \"father.id\", \"id\", \"tel\"]]"); + + /** + * @tc.steps: step8. Call SchemaObject.ParseFromSchemaString to parse the string. + * @tc.expected: step8. Expect return -E_SCHEMA_PARSE_FAIL. + */ + SchemaObject so4; + EXPECT_EQ(so4.ParseFromSchemaString(schema4), -E_SCHEMA_PARSE_FAIL); + + /** + * @tc.steps: step9. Create a shema string include a single index with all columns not exists + */ + std::string schema5; + GenerateSchemaString(schema5, "[[\"name1\", \"father.name2\", \"father1.id\", \"id2\", \"tel\"]]"); + + /** + * @tc.steps: step10. Call SchemaObject.ParseFromSchemaString to parse the string. + * @tc.expected: step10. Expect return -E_SCHEMA_PARSE_FAIL. + */ + SchemaObject so5; + EXPECT_EQ(so5.ParseFromSchemaString(schema5), -E_SCHEMA_PARSE_FAIL); +} + +/** + * @tc.name: UnionIndexCreatTest001 + * @tc.desc: Test the Json uoin index create function + * @tc.type: FUNC + * @tc.require: AR000F3OPD + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBStorageIndexOptimizeTest, UnionIndexCreatTest001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create a correct shema string include a correct union index. + */ + std::string schema; + GenerateSchemaString(schema, "[[\"name\", \"father.name\"]]"); + + /** + * @tc.steps: step2. Create a kvStore with the schema string. + */ + KvStoreNbDelegate::Option option; + option.schema = schema; + g_mgr.GetKvStore(STORE_ID_1, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + g_mgr.CloseKvStore(g_kvNbDelegatePtr); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(CheckIndexFromDbFile(GetKvStoreDirectory(USER_ID, APP_ID, STORE_ID_1), "$.name")); +} + +/** + * @tc.name: SuggestIndexTest001 + * @tc.desc: Test the Suggest index verify function + * @tc.type: FUNC + * @tc.require: AR000F3OPE + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBStorageIndexOptimizeTest, SuggestIndexTest001, TestSize.Level1) +{ + std::string schema; + GenerateSchemaString(schema, "[\"name\", \"id\"]"); + SchemaObject schemaObject1; + ASSERT_EQ(schemaObject1.ParseFromSchemaString(schema), E_OK); + + /** + * @tc.steps: step1. Create a Query and call GreaterThan().SuggestIndex(), then check the query1 + * @tc.expected: step1. query1 is valid. + */ + Query query1 = Query::Select().GreaterThan("id", 1).SuggestIndex("id"); + QueryObject queryObject1(query1); + queryObject1.SetSchema(schemaObject1); + queryObject1.Init(); + EXPECT_TRUE(queryObject1.IsValid()); + + /** + * @tc.steps: step2. Create a Query and call SuggestIndex().SuggestIndex(), then check the query2 + * @tc.expected: step2. query1 is invalid. + */ + SchemaObject schemaObject2; + ASSERT_EQ(schemaObject2.ParseFromSchemaString(schema), E_OK); + Query query2 = Query::Select().SuggestIndex("id").SuggestIndex("id"); + QueryObject queryObject2(query2); + queryObject2.SetSchema(schemaObject2); + queryObject2.Init(); + EXPECT_FALSE(queryObject2.IsValid()); + + /** + * @tc.steps: step3. Create a Query and call SuggestIndex().GreaterThan(), then check the query3 + * @tc.expected: step4. query3 is invalid. + */ + SchemaObject schemaObject3; + ASSERT_EQ(schemaObject3.ParseFromSchemaString(schema), E_OK); + Query query3 = Query::Select().SuggestIndex("id").GreaterThan("id", 1); + QueryObject queryObject3(query3); + queryObject3.SetSchema(schemaObject3); + queryObject3.Init(); + EXPECT_FALSE(queryObject3.IsValid()); +} + +/** + * @tc.name: SuggestIndexTest002 + * @tc.desc: Test the Query parse sql the SuggestIndex + * @tc.type: FUNC + * @tc.require: AR000F3OPE + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBStorageIndexOptimizeTest, SuggestIndexTest002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create a schema include index name, id, phone. + */ + std::string schema; + GenerateSchemaString(schema, "[\"name\", \"id\", \"phone\"]"); + + /** + * @tc.steps: step2. Create Query,call GreaterThan("id").GreaterThan("phone").SuggestIndex("id") + */ + SchemaObject schemaObject1; + ASSERT_EQ(schemaObject1.ParseFromSchemaString(schema), E_OK); + Query query1 = Query::Select().GreaterThan("id", 1).And().GreaterThan("phone", 1).SuggestIndex("id"); + + /** + * @tc.steps: step3. Create QueryObject with query1 and call GetQuerySql to check the sql + * @tc.expected: step3. the sql contains "INDEXED BY $.id". + */ + QueryObject queryObject1(query1); + queryObject1.SetSchema(schemaObject1); + int errCode = E_OK; + SqliteQueryHelper helper = queryObject1.GetQueryHelper(errCode); + ASSERT_EQ(errCode, E_OK); + std::string sql1; + ASSERT_EQ(helper.GetQuerySql(sql1, false), E_OK); + size_t pos = sql1.find("INDEXED BY '$.id'", 0); + ASSERT_TRUE(pos != std::string::npos); + + SqliteQueryHelper helper1 = queryObject1.GetQueryHelper(errCode); + ASSERT_EQ(errCode, E_OK); + /** + * @tc.steps: step4. Create a kvStore with the schema string. + */ + KvStoreNbDelegate::Option option; + option.schema = schema; + g_mgr.GetKvStore(STORE_ID_1, option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + + /** + * @tc.steps: step5. put a valid value + */ + Value value(JSON_VALUE.begin(), JSON_VALUE.end()); + ASSERT_EQ(g_kvNbDelegatePtr->Put(KEY_1, value), OK); + std::vector entries; + + /** + * @tc.steps: step6. GetEntries with the query1 + * @tc.expected: step6. GetEntries return OK, and the out value is the given value. + */ + ASSERT_EQ(errCode, E_OK); + ASSERT_EQ(g_kvNbDelegatePtr->GetEntries(query1, entries), OK); + EXPECT_TRUE(value == entries[0].value); + ASSERT_EQ(errCode, E_OK); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + SqliteQueryHelper helper2 = queryObject1.GetQueryHelper(errCode); + ASSERT_EQ(errCode, E_OK); + /** + * @tc.steps: step7. Create Query,call GreaterThan("id").SuggestIndex("car") + */ + SchemaObject schemaObject3; + ASSERT_EQ(schemaObject3.ParseFromSchemaString(schema), E_OK); + Query query3 = Query::Select().GreaterThan("id", 1).SuggestIndex("car"); + + /** + * @tc.steps: step8. Create QueryObject with query3 and call GetQuerySql to check the sql + * @tc.expected: step8. the sql not contains "INDEXED BY $.car". + */ + QueryObject queryObject3(query3); + queryObject3.SetSchema(schemaObject3); + SqliteQueryHelper helper3 = queryObject1.GetQueryHelper(errCode); + ASSERT_EQ(errCode, E_OK); + std::string sql3; + ASSERT_EQ(helper3.GetQuerySql(sql3, false), E_OK); + pos = sql3.find("INDEXED BY '$.car'", 0); + ASSERT_TRUE(pos == std::string::npos); +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_memory_single_ver_naturall_store_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_memory_single_ver_naturall_store_test.cpp new file mode 100644 index 00000000..3d7eb017 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_memory_single_ver_naturall_store_test.cpp @@ -0,0 +1,1040 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_constant.h" +#include "distributeddb_storage_single_ver_natural_store_testcase.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + DistributedDB::KvStoreConfig g_config; + std::string g_testDir; + const std::string MEM_URL = "file:31?mode=memory&cache=shared"; + DistributedDB::SQLiteSingleVerNaturalStore *g_store = nullptr; + DistributedDB::SQLiteSingleVerNaturalStoreConnection *g_connection = nullptr; +} + +class DistributedDBStorageMemorySingleVerNaturalStoreTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBStorageMemorySingleVerNaturalStoreTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + LOGD("Test dir is %s", g_testDir.c_str()); + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir + "/TestGeneralNB/" + DBConstant::SINGLE_SUB_DIR); +} + +void DistributedDBStorageMemorySingleVerNaturalStoreTest::TearDownTestCase(void) {} + +void DistributedDBStorageMemorySingleVerNaturalStoreTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + KvDBProperties property; + property.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + property.SetStringProp(KvDBProperties::STORE_ID, "TestGeneralNB"); + property.SetStringProp(KvDBProperties::IDENTIFIER_DIR, "31"); + property.SetBoolProp(KvDBProperties::MEMORY_MODE, true); + property.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + g_store = new (std::nothrow) SQLiteSingleVerNaturalStore; + ASSERT_NE(g_store, nullptr); + ASSERT_EQ(g_store->Open(property), E_OK); + + int erroCode = E_OK; + g_connection = static_cast(g_store->GetDBConnection(erroCode)); + ASSERT_NE(g_connection, nullptr); + g_store->DecObjRef(g_store); + EXPECT_EQ(erroCode, E_OK); +} + +void DistributedDBStorageMemorySingleVerNaturalStoreTest::TearDown(void) +{ + if (g_connection != nullptr) { + g_connection->Close(); + } + + g_store = nullptr; + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir + "/TestGeneralNB/" + DBConstant::SINGLE_SUB_DIR); +} + +/** + * @tc.name: GetSyncData001 + * @tc.desc: To test the function of querying the data in the time stamp range in the database. + * @tc.type: FUNC + * @tc.require: AR000CRAKO + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, GetSyncData001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Obtain the data within the time stamp range + * through the GetSyncData(A, C) interface of the NaturalStore, where AB + * @tc.expected: step1. The value of GetSyncData is E_INVALID_ARG. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData003(g_store, g_connection); +} + +/** + * @tc.name: GetSyncData004 + * @tc.desc: To the test database Subcon reading, a large number of data records exist in the time stamp range. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, GetSyncData004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Obtain the data within the time stamp range + * through the GetSyncData(A, B) interface of the NaturalStore. + * @tc.expected: step1. Return E_GET_UNFINISHED. + */ + /** + * @tc.steps:step2. Continue to obtain data through the GetSyncDataNext() interface + * of the NaturalStore until the E_GET_FINISHED message is returned. + * @tc.expected: step2. When the GetSyncDataNext returns E_GET_FINISHED, + * the total number of obtained data is the number of inserted data and the data is consistent. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData004(g_store, g_connection); +} + +/** + * @tc.name: GetSyncData005 + * @tc.desc: In the test database, if a large number of data records exist + * in the time stamp range, a packet is read successfully. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, GetSyncData005, TestSize.Level1) +{ + /** + * @tc.steps:step1. Obtain the data within the time stamp range + * through the GetSyncData(A, B) interface of the NaturalStore. + * @tc.expected: step1. The total size of all data in OK, dataItems is 99K. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData005(g_store, g_connection); +} + +/** + * @tc.name: GetSyncData006 + * @tc.desc: To test the function of reading data when the time stamp range in the database + * is greater than the value of blockSize. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, GetSyncData006, TestSize.Level1) +{ + /** + * @tc.steps:step1. Use the GetSyncData(A, B) interface of the NaturalStore + * and set blockSize to 50 kb to obtain the data within the time stamp range. + * @tc.expected: step1. The system returns E_GET_FINISHED. The size of the obtained data is 1 kb. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData006(g_store, g_connection); +} + +/** + * @tc.name: PutSyncData001 + * @tc.desc: To test the function of synchronizing the new data of the remote device that synchronizes the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, PutSyncData001, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Set Ioption to synchronous data and insert a (key1, value1) data record by put interface. + */ + /** + * @tc.steps:step3. Insert a (key1, value2!=value1, timestamp, false) data record + * through the PutSyncData interface. The value of timestamp is less than or equal + * to the value of timestamp. For Compare the timestamp to determine whether to synchronization data. + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. The Ioption is set to synchronize data + * through the Get interface to obtain the value data of the key1. + * @tc.expected: step4. Return OK.The obtained value is value1. + */ + /** + * @tc.steps:step5. Insert a (key1, value3!=value1, timestamp, false) data record + * through the PutSyncData interface of the NaturalStore. The value of timestamp + * is greater than that of timestamp inserted in 2. + * @tc.expected: step5. Return OK. + */ + /** + * @tc.steps:step6. The Ioption is set to synchronize data through the Get interface + * to obtain the value data of the key1. + * @tc.expected: step6. Return OK. + */ + /** + * @tc.steps:step7. Insert a (key2, value4) data record through the PutSyncData interface. + * @tc.expected: step7. Return OK. + */ + /** + * @tc.steps:step8. The Ioption is set to synchronize data + * through the Get interface to obtain the value data of the key2. + * @tc.expected: step8. Returns OK, and the obtained data is value4. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::PutSyncData001(g_store, g_connection); +} + +/** + * @tc.name: PutSyncData002 + * @tc.desc: To test the function of synchronizing data from the remote device + * to the local device after the data is deleted from the remote device. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, PutSyncData002, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Set Ioption to synchronous data and insert a (key1, value1) data record by put interface. + */ + /** + * @tc.steps:step3. Insert a (key1, value2!=value1, timestamp, false) data record + * through the PutSyncData interface. The value of timestamp is less than or equal + * to the value of timestamp. For Compare the timestamp to determine whether delete data. + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. The Ioption is set to synchronize data + * through the Get interface to obtain the value data of the key1. + * @tc.expected: step4. Return OK.The obtained value is value1. + */ + /** + * @tc.steps:step5. Insert a (key1, value3!=value1, timestamp, false) data record + * through the PutSyncData interfac. The value of timestamp + * is greater than that of timestamp inserted in step2. + * @tc.expected: step5. Return OK. + */ + /** + * @tc.steps:step6. The Ioption is set to synchronize data through the Get interface + * to obtain the value data of the key1. + * @tc.expected: step6. Return E_NOT_FOUND. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::PutSyncData002(g_store, g_connection); +} + +/** + * @tc.name: PutSyncData003 + * @tc.desc: To test the function of synchronizing the mixed data of the added + * and deleted data from the remote device to the local device. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, PutSyncData003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Insert a data record (key1,value1 is not null) and (key2, value2 is not null) + * through the PutSyncData interface. + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. Set Ioption as the synchronization data to obtain the data of key1 and key2. + * @tc.expected: step2. The Get interface returns OK. The value of key1 is value1, + * and the value of key2 is value2. + */ + /** + * @tc.steps:step3. Insert a (key3, value3) and delete the data of the (key1, value1). + * @tc.expected: step3. The PutSyncData returns OK. + */ + /** + * @tc.steps:step4. Set Ioption to the synchronization data and obtain the data of key1, key2, and key3. + * @tc.expected: step4. Get key1 returns E_NOT_FOUND,Get key2. + * The value of OK,value is value2, the value of Get key3 is OK, + * and the value of value is value3. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::PutSyncData003(g_store, g_connection); +} + +/** + * @tc.name: PutMetaData001 + * @tc.desc: Test metadata insertion and modification. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, PutMetaData001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Run the PutMetaData command to insert a non-empty key1 non-empty value1 data record. + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. Run the PutMetaData command to insert a non-empty key1 non-empty value1 data record. + * @tc.expected: step2. The obtained value is the same as the value of value1. + */ + /** + * @tc.steps:step3. The key value is key1, the value is not empty, + * and the value of value2 is different from the value of value1 through the PutMetaData interface. + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. Run the GetMetaData command to obtain the value of key1 + * and check whether the value is the same as the value of value2. + * @tc.expected: step4. The obtained value is the same as the value of value2. + */ + /** + * @tc.steps:step5. Use PutMetaData to insert a record whose key is empty and value is not empty. + * @tc.expected: step5. Return E_INVALID_ARGS. + */ + /** + * @tc.steps:step6. Use PutMetaData in NaturalStore to insert data whose key2(!=key1) + * is not empty and value is empty. + * @tc.expected: step6. Return OK. + */ + /** + * @tc.steps:step7. Obtain the value of key2 and check whether the value is empty. + * @tc.expected: step7. The obtained value is empty. + */ + /** + * @tc.steps:step8. Insert the data whose key size is 1024 and value size is 4Mb + * through PutMetaData of NaturalStore. + * @tc.expected: step8. Return OK. + */ + /** + * @tc.steps:step9/10. Insert data items whose key size is greater than 1 kb + * or value size greater than 4Mb through PutMetaData of NaturalStore. + * @tc.expected: step9/10. Return E_INVALID_ARGS. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::PutMetaData001(g_store, g_connection); +} + +/** + * @tc.name: GetMetaData001 + * @tc.desc: To test the function of reading the metadata of a key in the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, GetMetaData001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Run the PutMetaData command to insert a non-empty key1 non-empty value1 data record. + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. Run the PutMetaData command to insert a non-empty key1 non-empty value1 data record. + * @tc.expected: step2. The obtained value is the same as the value of value1. + */ + /** + * @tc.steps:step3. The key value is key1, the value is not empty, + * and the value of value2 is different from the value of value1 through the PutMetaData interface. + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. Run the GetMetaData command to obtain the value of key1 + * and check whether the value is the same as the value of value2. + * @tc.expected: step4. The obtained value is the same as the value of value2. + */ + /** + * @tc.steps:step5. Use PutMetaData to insert a record whose key is empty and value is not empty. + * @tc.expected: step5. Return E_INVALID_ARGS. + */ + /** + * @tc.steps:step6. Use PutMetaData in NaturalStore to insert data whose key2(!=key1) + * is not empty and value is empty. + * @tc.expected: step6. Return OK. + */ + /** + * @tc.steps:step7. Obtain the value of key2 and check whether the value is empty. + * @tc.expected: step7. The obtained value is empty. + */ + /** + * @tc.steps:step8. Insert the data whose key size is 1024 and value size is 4Mb + * through PutMetaData of NaturalStore. + * @tc.expected: step8. Return OK. + */ + /** + * @tc.steps:step9/10. Insert data items whose key size is greater than 1 kb + * or value size greater than 4Mb through PutMetaData of NaturalStore. + * @tc.expected: step9/10. Return E_INVALID_ARGS. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetMetaData001(g_store, g_connection); +} + +/** + * @tc.name: GetCurrentMaxTimestamp001 + * @tc.desc: To test the function of obtaining the maximum timestamp when a record exists in the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, GetCurrentMaxTimestamp001, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Insert a data record into the synchronization database. + */ + /** + * @tc.steps:step3. The current maximum timestamp is A. + */ + /** + * @tc.steps:step4. Insert a data record into the synchronization database. + */ + /** + * @tc.steps:step5. Obtain the maximum timestamp B and check whether B>=A exists. + * @tc.expected: step5. The obtained timestamp is B>=A. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetCurrentMaxTimestamp001(g_store, g_connection); +} + +/** + * @tc.name: GetCurrentMaxTimestamp002 + * @tc.desc: Obtain the maximum timestamp when no record exists in the test record library. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, GetCurrentMaxTimestamp002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Obtains the maximum timestamp in the current database record. + * @tc.expected: step1. Return timestamp is 0. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetCurrentMaxTimestamp002(g_store); +} + +/** + * @tc.name: LocalDatabaseOperate001 + * @tc.desc: Test the function of inserting data in the local database of the NaturalStore. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, LocalDatabaseOperate001, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Set Ioption to the local data and insert a record of key1 and value1. + * @tc.expected: step1/2. Return OK. + */ + /** + * @tc.steps: step3. Set Ioption to the local data and obtain the value of key1. + * Check whether the value is the same as the value of value1. + * @tc.expected: step3. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step4. Ioption Set this parameter to the local data. Insert key1. + * The value cannot be empty. value2(!=value1) + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps: step5. Set Ioption to the local data, GetMetaData to obtain the value of key1, + * and check whether the value is the same as the value of value2. + * @tc.expected: step5. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step6. The Ioption parameter is set to the local data. + * The data record whose key is empty and value is not empty is inserted. + * @tc.expected: step6. Return E_INVALID_DATA. + */ + /** + * @tc.steps: step7. Set Ioption to the local data, insert data + * whose key2(!=key1) is not empty, and value is empty. + * @tc.expected: step7. Return OK. + */ + /** + * @tc.steps: step8. Set option to local data, obtain the value of key2, + * and check whether the value is empty. + * @tc.expected: step8. Return OK, value is empty. + */ + /** + * @tc.steps: step9. Ioption Set the local data. + * Insert the data whose key size is 1024 and value size is 4Mb. + * @tc.expected: step9. Return OK. + */ + /** + * @tc.steps: step10/11. Set Ioption to the local data and insert data items + * whose value is greater than 4Mb or key is bigger than 1Kb + * @tc.expected: step10/11. Return E_INVALID_ARGS. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::LocalDatabaseOperate001(g_store, g_connection); +} + +/** + * @tc.name: LocalDatabaseOperate002 + * @tc.desc: Test the function of deleting data from the local database of the NaturalStore. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, LocalDatabaseOperate002, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Set Ioption to the local data and insert a record of key1 and value1. + * @tc.expected: step1/2. Return OK. + */ + /** + * @tc.steps: step3. Set Ioption to the local data and obtain the value of key1. + * Check whether the value is the same as the value of value1. + * @tc.expected: step3. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step4. Ioption Set this parameter to the local data. Insert key1. + * The value cannot be empty. value2(!=value1) + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps: step5. Set Ioption to the local data, GetMetaData to obtain the value of key1, + * and check whether the value is the same as the value of value2. + * @tc.expected: step5. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step6. The Ioption parameter is set to the local data. + * The data record whose key is empty and value is not empty is inserted. + * @tc.expected: step6. Return E_INVALID_DATA. + */ + /** + * @tc.steps: step7. Set Ioption to the local data, insert data + * whose key2(!=key1) is not empty, and value is empty. + * @tc.expected: step7. Return OK. + */ + /** + * @tc.steps: step8. Set option to local data, obtain the value of key2, + * and check whether the value is empty. + * @tc.expected: step8. Return OK, value is empty. + */ + /** + * @tc.steps: step9. Ioption Set the local data. + * Insert the data whose key size is 1024 and value size is 4Mb. + * @tc.expected: step9. Return OK. + */ + /** + * @tc.steps: step10/11. Set Ioption to the local data and insert data items + * whose value is greater than 4Mb or key is bigger than 1Kb + * @tc.expected: step10/11. Return E_INVALID_ARGS. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::LocalDatabaseOperate002(g_store, g_connection); +} + +/** + * @tc.name: LocalDatabaseOperate003 + * @tc.desc: To test the function of reading data from the local database of the NaturalStore. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, LocalDatabaseOperate003, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Set Ioption to the local data and insert a record of key1 and value1. + * @tc.expected: step1/2. Return OK. + */ + /** + * @tc.steps: step3. Set Ioption to the local data and obtain the value of key1. + * Check whether the value is the same as the value of value1. + * @tc.expected: step3. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step4. Ioption Set this parameter to the local data. Insert key1. + * The value cannot be empty. value2(!=value1) + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps: step5. Set Ioption to the local data, GetMetaData to obtain the value of key1, + * and check whether the value is the same as the value of value2. + * @tc.expected: step5. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step6. The Ioption parameter is set to the local data. + * The data record whose key is empty and value is not empty is inserted. + * @tc.expected: step6. Return E_INVALID_DATA. + */ + /** + * @tc.steps: step7. Set Ioption to the local data, insert data + * whose key2(!=key1) is not empty, and value is empty. + * @tc.expected: step7. Return OK. + */ + /** + * @tc.steps: step8. Set option to local data, obtain the value of key2, + * and check whether the value is empty. + * @tc.expected: step8. Return OK, value is empty. + */ + /** + * @tc.steps: step9. Ioption Set the local data. + * Insert the data whose key size is 1024 and value size is 4Mb. + * @tc.expected: step9. Return OK. + */ + /** + * @tc.steps: step10/11. Set Ioption to the local data and insert data items + * whose value is greater than 4Mb or key is bigger than 1Kb + * @tc.expected: step10/11. Return E_INVALID_ARGS. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::LocalDatabaseOperate003(g_store, g_connection); +} + +/** + * @tc.name: SyncDatabaseOperate001 + * @tc.desc: To test the function of inserting data of the local device in the synchronization database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, SyncDatabaseOperate001, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Set Ioption to the local data and insert a record of key1 and value1. + * @tc.expected: step1/2. Return OK. + */ + /** + * @tc.steps: step3. Set Ioption to the local data and obtain the value of key1. + * Check whether the value is the same as the value of value1. + * @tc.expected: step3. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step4. Ioption Set this parameter to the local data. Insert key1. + * The value cannot be empty. value2(!=value1) + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps: step5. Set Ioption to the local data, GetMetaData to obtain the value of key1, + * and check whether the value is the same as the value of value2. + * @tc.expected: step5. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step6. The Ioption parameter is set to the local data. + * The data record whose key is empty and value is not empty is inserted. + * @tc.expected: step6. Return E_INVALID_DATA. + */ + /** + * @tc.steps: step7. Set Ioption to the local data, insert data + * whose key2(!=key1) is not empty, and value is empty. + * @tc.expected: step7. Return OK. + */ + /** + * @tc.steps: step8. Set option to local data, obtain the value of key2, + * and check whether the value is empty. + * @tc.expected: step8. Return OK, value is empty. + */ + /** + * @tc.steps: step9. Ioption Set the local data. + * Insert the data whose key size is 1024 and value size is 4Mb. + * @tc.expected: step9. Return OK. + */ + /** + * @tc.steps: step10/11. Set Ioption to the local data and insert data items + * whose value is greater than 4Mb or key is bigger than 1Kb + * @tc.expected: step10/11. Return E_INVALID_ARGS. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate001(g_store, g_connection); +} + +/** + * @tc.name: SyncDatabaseOperate002 + * @tc.desc: test the put operation after data synced from other devices. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, SyncDatabaseOperate002, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Add a remote synchronization data record. (key1, value1). + */ + /** + * @tc.steps: step3. Ioption is set to synchronous data. Obtains the value data of the key1. + * @tc.expected: step3. Return OK. The value is the same as the value of value1. + */ + /** + * @tc.steps: step4. Ioption Set the data to be synchronized and insert the data of key1,value2. + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps: step3. Ioption is set to synchronous data. Obtains the value data of the key1. + * @tc.expected: step3. Return OK. The value is the same as the value of value2. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate002(g_store, g_connection); +} + +/** + * @tc.name: SyncDatabaseOperate003 + * @tc.desc: test the delete operation in sync database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, SyncDatabaseOperate003, TestSize.Level1) +{ + /** + * @tc.steps: step2. Set Ioption to the local data and delete the data whose key is key1 (empty). + * @tc.expected: step2. Return E_INVALID_ARGS. + */ + /** + * @tc.steps: step3. Set Ioption to the local data, insert non-null key1, and non-null value1 data. + * @tc.expected: step3. Return E_OK. + */ + /** + * @tc.steps: step4. Set Ioption to the local data, obtain the value of key1, + * and check whether the value is the same as that of value1. + * @tc.expected: step4. Return E_OK. The obtained value is the same as the value of value1. + */ + /** + * @tc.steps: step5. Set Ioption to the local data and delete the data whose key is key1. + * @tc.expected: step5. Return E_OK. + */ + /** + * @tc.steps: step5. Set Ioption to the local data and obtain the value of Key1. + * @tc.expected: step5. Return E_NOT_FOUND. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate003(g_store, g_connection); +} + +/** + * @tc.name: SyncDatabaseOperate004 + * @tc.desc: test the delete for the data from other devices in sync database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, SyncDatabaseOperate004, TestSize.Level1) +{ + /** + * @tc.steps: step2. The Ioption parameter is set to synchronize data to obtain the value data of the key1. + * @tc.expected: step2. Return OK. The value is the same as the value of value1. + */ + /** + * @tc.steps: step3. The Ioption parameter is set to synchronize data, and the key1 data is deleted. + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps: step4. The Ioption parameter is set to synchronize data to obtain the value data of the key1. + * @tc.expected: step4. Return E_NOT_FOUND. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate004(g_store, g_connection); +} + +/** + * @tc.name: SyncDatabaseOperate005 + * @tc.desc: test the reading for sync database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, SyncDatabaseOperate005, TestSize.Level1) +{ + /** + * @tc.steps: step2. Set Ioption to the local data and delete the data whose key is key1 (empty). + * @tc.expected: step2. Return E_INVALID_ARGS. + */ + /** + * @tc.steps: step3. Set Ioption to the local data, insert non-null key1, and non-null value1 data. + * @tc.expected: step3. Return E_OK. + */ + /** + * @tc.steps: step4. Set Ioption to the local data, obtain the value of key1, + * and check whether the value is the same as that of value1. + * @tc.expected: step4. Return E_OK. The obtained value is the same as the value of value1. + */ + /** + * @tc.steps: step5. Set Ioption to the local data and obtain the value data of Key1. + * Check whether the value is the same as the value of value2. + * @tc.expected: step4. Return E_OK, and the value is the same as the value of value2. + */ + /** + * @tc.steps: step5. The Ioption is set to the local. + * The data of the key1 and value2(!=value1) is inserted. + * @tc.expected: step4. Return E_OK. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate005(g_store, g_connection); +} + +/** + * @tc.name: SyncDatabaseOperate006 + * @tc.desc: test the get entries for sync database + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, SyncDatabaseOperate006, TestSize.Level1) +{ + /** + * @tc.steps: step2/3/4. Set Ioption to synchronous data. + * Insert the data of key=keyPrefix + 'a', value1. + * Insert the data of key=keyPrefix + 'c', value2. + * Insert the data of key length=keyPrefix length - 1, value3. + * @tc.expected: step2/3/4. Return E_NOT_FOUND. + */ + /** + * @tc.steps: step5. Obtain all data whose prefixKey is keyPrefix. + * @tc.expected: step5. Return OK. The number of obtained data records is 2. + */ + /** + * @tc.steps: step6. Obtain all data whose prefixKey is empty. + * @tc.expected: step6. Return OK. The number of obtained data records is 3. + */ + /** + * @tc.steps: step7. Obtain all data whose prefixKey is keyPrefix. + * @tc.expected: step7. Return E_NOT_SUPPORT. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate006(g_store, g_connection); +} + +/** + * @tc.name: ClearRemoteData001 + * @tc.desc: test the clear data synced from the remote by device. + * @tc.type: FUNC + * @tc.require: AR000CIFDA AR000CQS3T + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, ClearRemoteData001, TestSize.Level1) +{ + /** + * @tc.steps: step1. New data is inserted to the B end of the device. [keyB, valueB]. + */ + /** + * @tc.steps: step2. The device pulls the data of the device B, and the device inserts the [keyA, valueA]. + */ + /** + * @tc.steps: step3. The device obtains the data of keyA and valueB. + * @tc.expected: step3. Obtain [keyA, valueA] and [keyB, valueB]. + */ + /** + * @tc.steps: step4.Invoke the interface for clearing the synchronization data of the B device. + */ + /** + * @tc.steps: step5. The device obtains the data of keyA and valueB. + * @tc.expected: step5. The value of [keyA, valueA] is obtained, + * and the value of NOT_FOUND is obtained by querying keyB. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::ClearRemoteData001(g_store, g_connection); +} + +/** + * @tc.name: DeleteUserKeyValue001 + * @tc.desc: When a user deletes a data record, the system clears the user record. + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, DeleteUserKeyValue001, TestSize.Level1) +{ + /** + * @tc.steps: step1. delete K1. + * @tc.expected: step1. delete K1 successfully. + */ + /** + * @tc.steps: step2. Real query by sqlite3. + * @tc.expected: step2. Find KEY_1, not find K2. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::MemoryDbDeleteUserKeyValue001(g_store, g_connection, MEM_URL); +} + +/** + * @tc.name: DeleteUserKeyValue002 + * @tc.desc: After the synchronization library data is deleted locally, add the same key data locally. + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, DeleteUserKeyValue002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Delete key1 data via Delete interface. + * @tc.expected: step1. Delete successfully. + */ + /** + * @tc.steps: step2. New data from key1, value3 via Put interface. + * @tc.expected: step2. New data from key1, value3 via Put interface successfully. + */ + /** + * @tc.steps: step3. Query key1 data via Get interface. + * @tc.expected: step3. Query key1 data via Get interface successfully, get value3 by key1. + */ + /** + * @tc.steps: step4. Query key1 real data by sqlite3. + * @tc.expected: step4. Two records were found. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue002(g_store, g_connection, MEM_URL); +} + +/** + * @tc.name: DeleteUserKeyValue003 + * @tc.desc: After the synchronization database data is deleted locally, the same key data is added from the remote end. + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, DeleteUserKeyValue003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Delete data by key1. + * @tc.expected: step1. Delete successfully. + */ + /** + * @tc.steps: step2. Get data by key1. + * @tc.expected: step1. Key1 not exist in database. + */ + /** + * @tc.steps: step3. Get a new data from remote device B , key1, value3, + * with a smaller timestamp than the current timestamp. + */ + /** + * @tc.steps: step4. Get data by key1. + * @tc.expected: step4. Key1 not exist in database. + */ + /** + * @tc.steps: step5. Get a new data from remote device C , key1, value4, + * and the timestamp is larger than the current timestamp. + */ + /** + * @tc.steps: step6. Get data by key1. + * @tc.expected: step6. Key1 not exist in database. + */ + /** + * @tc.steps: step7. Get real data by key1. + * @tc.expected: step7. Get 1 record. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue003(g_store, g_connection, MEM_URL); +} + +/** + * @tc.name: DeleteUserKeyValue004 + * @tc.desc: Changes in key after remote delete data syncs to local + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, DeleteUserKeyValue004, TestSize.Level1) +{ + /** + * @tc.steps: step1 2 3. Synchronize data to another device B; delete key1 data from device B; + * pull the action of key1 to local. + */ + /** + * @tc.steps: step4. Close database. + */ + /** + * @tc.steps: step5 6. Get real data by key1;and get the number of records. + * @tc.expected: step5 6. Not exist key1 real data in database;Get 1 record. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::MemoryDbDeleteUserKeyValue004(g_store, g_connection, MEM_URL); +} + +/** + * @tc.name: DeleteUserKeyValue005 + * @tc.desc: New unified key data locally after remote delete data syncs to local + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, DeleteUserKeyValue005, TestSize.Level1) +{ + /** + * @tc.steps: step1 2 3. Synchronize data to another device B; delete key1 data from device B; + * pull the action of key1 to local. + */ + /** + * @tc.steps: step4. Put K1 V1 to database. + * @tc.expected: step4. Put successfully. + */ + /** + * @tc.steps: step5. Close database. + */ + /** + * @tc.steps: step6 7. Get real data by key1;and get the number of records. + * @tc.expected: step6 7. Not exist key1 real data in database;Get 2 record. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::MemoryDbDeleteUserKeyValue005(g_store, g_connection, MEM_URL); +} + +/** + * @tc.name: DeleteUserKeyValue006 + * @tc.desc: After the remote delete data is synced to the local, + * the same key data is added from the remote other devices + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageMemorySingleVerNaturalStoreTest, DeleteUserKeyValue006, TestSize.Level1) +{ + /** + * @tc.steps: step1. Remote device B sync deletes data key1 and pushes to local. + */ + /** + * @tc.steps: step2. Get key1 from database. + * @tc.expected: step2. Not exist key1. + */ + /** + * @tc.steps: step3. Remote device C syncs new data (key1, value2), + * timestamp is less than delete timestamp, to local. + */ + /** + * @tc.steps: step4. Get key1 from database. + * @tc.expected: step4. Not exist key1. + */ + /** + * @tc.steps: step5. Remote device C syncs new data (key1, value2), + * timestamp is bigger than delete timestamp, to local. + */ + /** + * @tc.steps: step6. Get key1 from database. + * @tc.expected: step6. Exist key1. + */ + /** + * @tc.steps: step7. Get real data from database. + * @tc.expected: step7. Get 1 record. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue006(g_store, g_connection, MEM_URL); +} + diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_query_sync_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_query_sync_test.cpp new file mode 100644 index 00000000..c8791c19 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_query_sync_test.cpp @@ -0,0 +1,1233 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_errno.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "generic_single_ver_kv_entry.h" +#include "kvdb_manager.h" +#include "query_sync_object.h" +#include "sqlite_single_ver_continue_token.h" +#include "sqlite_single_ver_natural_store.h" +#include "sqlite_single_ver_natural_store_connection.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + DistributedDB::KvStoreConfig g_config; + std::string g_testDir; + DistributedDB::SQLiteSingleVerNaturalStore *g_store = nullptr; + DistributedDB::SQLiteSingleVerNaturalStore *g_schemaStore = nullptr; + DistributedDB::SQLiteSingleVerNaturalStoreConnection *g_connection = nullptr; + DistributedDB::SQLiteSingleVerNaturalStoreConnection *g_schemaConnect = nullptr; + + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); + + const std::string REMOTE_DEVICE_ID = "remote_device_id"; + const Key PREFIX_KEY = { 'k' }; + const Key KEY1 = { 'k', '1' }; + const Key KEY2 = { 'k', '2' }; + const Key KEY3 = { 'k', '3' }; + const Value VALUE1 = { 'v', '1' }; + const Value VALUE2 = { 'v', '2' }; + const Value VALUE3 = { 'v', '3' }; + + void ReleaseKvEntries(std::vector &entries) + { + for (auto &itemEntry : entries) { + delete itemEntry; + itemEntry = nullptr; + } + entries.clear(); + } + + const string SCHEMA_STRING = + "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":\"BOOL\"," + "\"field_name3\":\"INTEGER, NOT NULL\"," + "\"field_name4\":\"LONG, DEFAULT 100\"," + "\"field_name5\":\"DOUBLE, NOT NULL, DEFAULT 3.14\"," + "\"field_name6\":\"STRING, NOT NULL, DEFAULT '3.1415'\"," + "\"field_name7\":\"LONG, DEFAULT 100\"," + "\"field_name8\":\"LONG, DEFAULT 100\"," + "\"field_name9\":\"LONG, DEFAULT 100\"," + "\"field_name10\":\"LONG, DEFAULT 100\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\", \"$.field_name2\"]}"; + + const std::string SCHEMA_VALUE1 = + "{\"field_name1\":true," + "\"field_name2\":false," + "\"field_name3\":10," + "\"field_name4\":20," + "\"field_name5\":3.14," + "\"field_name6\":\"3.1415\"," + "\"field_name7\":100," + "\"field_name8\":100," + "\"field_name9\":100," + "\"field_name10\":100}"; +} + +class DistributedDBStorageQuerySyncTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBStorageQuerySyncTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + LOGD("Test dir is %s", g_testDir.c_str()); + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir + "/TestQuerySync/" + DBConstant::SINGLE_SUB_DIR); + + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + // Create schema database + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_STRING; + g_mgr.GetKvStore("QuerySyncSchema", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + Value value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end()); + g_kvNbDelegatePtr->Put(KEY_1, value); + + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); +} + +void DistributedDBStorageQuerySyncTest::TearDownTestCase(void) +{ + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir); +} + +void DistributedDBStorageQuerySyncTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + KvDBProperties property; + property.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + property.SetStringProp(KvDBProperties::STORE_ID, "31"); + property.SetStringProp(KvDBProperties::IDENTIFIER_DIR, "TestQuerySync"); + property.SetBoolProp(KvDBProperties::MEMORY_MODE, false); + property.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + property.SetIntProp(KvDBProperties::CONFLICT_RESOLVE_POLICY, ConflictResolvePolicy::DEVICE_COLLABORATION); + g_store = new (std::nothrow) SQLiteSingleVerNaturalStore; + ASSERT_NE(g_store, nullptr); + ASSERT_EQ(g_store->Open(property), E_OK); + + int erroCode = E_OK; + g_connection = static_cast(g_store->GetDBConnection(erroCode)); + ASSERT_NE(g_connection, nullptr); + g_store->DecObjRef(g_store); + EXPECT_EQ(erroCode, E_OK); + + std::string oriIdentifier = USER_ID + "-" + APP_ID + "-" + "QuerySyncSchema"; + std::string identifier = DBCommon::TransferHashString(oriIdentifier); + property.SetStringProp(KvDBProperties::IDENTIFIER_DATA, identifier); + std::string identifierHex = DBCommon::TransferStringToHex(identifier); + property.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + property.SetStringProp(KvDBProperties::STORE_ID, "QuerySyncSchema"); + property.SetStringProp(KvDBProperties::IDENTIFIER_DIR, identifierHex); + + g_schemaStore = new (std::nothrow) SQLiteSingleVerNaturalStore; + ASSERT_NE(g_schemaStore, nullptr); + ASSERT_EQ(g_schemaStore->Open(property), E_OK); + g_schemaConnect = static_cast(g_schemaStore->GetDBConnection(erroCode)); + ASSERT_NE(g_schemaConnect, nullptr); + + std::vector entries; + IOption option; + option.dataType = IOption::SYNC_DATA; + g_schemaConnect->GetEntries(option, Query::Select(), entries); + ASSERT_FALSE(entries.empty()); + + g_schemaStore->DecObjRef(g_schemaStore); +} + +void DistributedDBStorageQuerySyncTest::TearDown(void) +{ + if (g_connection != nullptr) { + g_connection->Close(); + } + + if (g_schemaConnect != nullptr) { + g_schemaConnect->Close(); + } + + g_store = nullptr; + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir + "/TestQuerySync/" + DBConstant::SINGLE_SUB_DIR); +} + +/** + * @tc.name: GetSyncData001 + * @tc.desc: To test the function of querying the data in the time stamp range in the database. + * @tc.type: FUNC + * @tc.require: AR000CRAKO + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, GetQuerySyncData001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Obtain the data within the time stamp range + * through the GetSyncData(A, C) interface of the NaturalStore, where APut(option, key, value), E_OK); + + Query query = Query::Select().PrefixKey(key); + QueryObject queryObj(query); + DataSizeSpecInfo specInfo = {4 * 1024 * 1024, DBConstant::MAX_HPMODE_PACK_ITEM_SIZE}; + std::vector entries; + ContinueToken token = nullptr; + EXPECT_EQ(g_store->GetSyncData(queryObj, SyncTimeRange{}, specInfo, token, entries), E_OK); + EXPECT_EQ(entries.size(), 1UL); + ReleaseKvEntries(entries); + + Key keyOther = key; + keyOther.push_back('1'); + g_store->ReleaseContinueToken(token); + EXPECT_EQ(g_connection->Put(option, keyOther, value), E_OK); + EXPECT_EQ(g_store->GetSyncData(queryObj, SyncTimeRange{}, specInfo, token, entries), E_OK); + EXPECT_EQ(entries.size(), 2UL); + ReleaseKvEntries(entries); + g_store->ReleaseContinueToken(token); +} + +/** + * @tc.name: GetQuerySyncData002 + * @tc.desc: To test GetSyncData function is available and check the boundary value. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, GetQuerySyncData002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put k1 k2. k1's timestamp is 0 and k2's timestamp is INT64_MAX-1. + * @tc.expected: step1. Put k1 k2 successfully. + */ + DataItem data1{KEY1, VALUE1, 0, DataItem::LOCAL_FLAG, REMOTE_DEVICE_ID}; + DataItem data2{KEY2, VALUE2, INT64_MAX - 1, DataItem::LOCAL_FLAG, REMOTE_DEVICE_ID}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vector{data1, data2}, REMOTE_DEVICE_ID), E_OK); + + /** + * @tc.steps: step2. Get k1 k2. SyncTimeRange is default(all time range). + * @tc.expected: step2. Get k1 k2 successfully. + */ + Query query = Query::Select().PrefixKey(PREFIX_KEY); + QueryObject queryObj(query); + DataSizeSpecInfo specInfo = {4 * 1024 * 1024, DBConstant::MAX_HPMODE_PACK_ITEM_SIZE}; + std::vector entries; + ContinueToken token = nullptr; + EXPECT_EQ(g_store->GetSyncData(queryObj, SyncTimeRange{}, specInfo, token, entries), E_OK); + EXPECT_EQ(entries.size(), 2UL); + ReleaseKvEntries(entries); + g_store->ReleaseContinueToken(token); + + /** + * @tc.steps: step3. Put k3. k3's timestamp t3 is random. + * @tc.expected: step3. Put k3. + */ + auto time3 = static_cast(DistributedDBToolsUnitTest::GetRandInt64(0, g_store->GetCurrentTimestamp())); + DataItem data3{KEY3, VALUE3, time3, DataItem::LOCAL_FLAG, REMOTE_DEVICE_ID}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vector{data3}, REMOTE_DEVICE_ID), E_OK); + + /** + * @tc.steps: step4. Get k3. SyncTimeRange is between t3 and t3 + 1. + * @tc.expected: step4. Get k3 successfully. + */ + EXPECT_EQ(g_store->GetSyncData(queryObj, SyncTimeRange{time3, 0, time3 + 1, 0}, + specInfo, token, entries), E_OK); + EXPECT_EQ(entries.size(), 1UL); + ReleaseKvEntries(entries); + g_store->ReleaseContinueToken(token); + + /** + * @tc.steps: step5. Delete k1 k3. + * @tc.expected: step5. Delete k1 k3 successfully. + */ + IOption option{ IOption::SYNC_DATA }; + Timestamp deleteBeginTime = g_store->GetCurrentTimestamp(); + g_connection->DeleteBatch(option, vector{KEY1, KEY3}); + + /** + * @tc.steps: step6. Get deleted data. + * @tc.expected: step6. Get k1 k3. + */ + Timestamp deleteEndTime = g_store->GetCurrentTimestamp(); + EXPECT_EQ(g_store->GetSyncData(queryObj, SyncTimeRange{0, deleteBeginTime, 0, deleteEndTime}, specInfo, token, + entries), E_OK); + EXPECT_EQ(entries.size(), 2UL); + ReleaseKvEntries(entries); + g_store->ReleaseContinueToken(token); +} + +/** + * @tc.name: GetQuerySyncData004 + * @tc.desc: To test GetSyncDataNext function is available and check the boundary value. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, GetQuerySyncData004, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put k1 k2. k1's timestamp is 0 and k2's timestamp is INT64_MAX-1. + * @tc.expected: step1. Put k1 k2 successfully. + */ + DataItem data1{KEY1, VALUE1, 0, DataItem::LOCAL_FLAG, REMOTE_DEVICE_ID}; + DataItem data2{KEY2, VALUE2, INT64_MAX - 1, DataItem::LOCAL_FLAG, REMOTE_DEVICE_ID}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vector{data1, data2}, REMOTE_DEVICE_ID), E_OK); + + /** + * @tc.steps: step2. Get k1 k2. SyncTimeRange is default(all time range). + * @tc.expected: step2. Get k1 k2 successfully. + */ + Query query = Query::Select().PrefixKey(PREFIX_KEY); + QueryObject queryObj(query); + DataSizeSpecInfo specInfo = {4 * 1024 * 1024, DBConstant::MAX_HPMODE_PACK_ITEM_SIZE}; + std::vector entries; + ContinueToken token = new (std::nothrow) SQLiteSingleVerContinueToken{SyncTimeRange{}, queryObj}; + EXPECT_EQ(g_store->GetSyncDataNext(entries, token, specInfo), E_OK); + EXPECT_EQ(entries.size(), 2UL); + ReleaseKvEntries(entries); + g_store->ReleaseContinueToken(token); + + /** + * @tc.steps: step3. Put k3. k3's timestamp t3 is random. + * @tc.expected: step3. Put k3. + */ + auto time3 = static_cast(DistributedDBToolsUnitTest::GetRandInt64(0, g_store->GetCurrentTimestamp())); + DataItem data3{KEY3, VALUE3, time3, DataItem::LOCAL_FLAG, REMOTE_DEVICE_ID}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vector{data3}, REMOTE_DEVICE_ID), E_OK); + + /** + * @tc.steps: step4. Get k3. SyncTimeRange is between t3 and t3 + 1. + * @tc.expected: step4. Get k3 successfully. + */ + token = new (std::nothrow) SQLiteSingleVerContinueToken{SyncTimeRange{time3, 0, time3 + 1}, queryObj}; + EXPECT_EQ(g_store->GetSyncDataNext(entries, token, specInfo), E_OK); + EXPECT_EQ(entries.size(), 1UL); + ReleaseKvEntries(entries); + g_store->ReleaseContinueToken(token); + + /** + * @tc.steps: step5. Delete k1 k3. + * @tc.expected: step5. Delete k1 k3 successfully. + */ + IOption option{ IOption::SYNC_DATA }; + Timestamp deleteBeginTime = g_store->GetCurrentTimestamp(); + g_connection->DeleteBatch(option, vector{KEY1, KEY3}); + + /** + * @tc.steps: step6. Get deleted data. + * @tc.expected: step6. Get k1 k3. + */ + Timestamp deleteEndTime = g_store->GetCurrentTimestamp(); + token = new (std::nothrow) SQLiteSingleVerContinueToken{SyncTimeRange{0, deleteBeginTime, 0, deleteEndTime}, + queryObj}; + EXPECT_EQ(g_store->GetSyncDataNext(entries, token, specInfo), E_OK); + EXPECT_EQ(entries.size(), 2UL); + ReleaseKvEntries(entries); + g_store->ReleaseContinueToken(token); +} + +/** + * @tc.name: GetQuerySyncData006 + * @tc.desc: To test if parameter is invalid, GetSyncData function return an E_INVALID_ARGS code. If no data found, + GetSyncData will return E_OK but entries will be empty. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, GetQuerySyncData006, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get sync data when no data exists in DB. + * @tc.expected: step1. GetSyncData return E_OK and entries is empty. + */ + Query query = Query::Select().PrefixKey(PREFIX_KEY); + QueryObject queryObj(query); + DataSizeSpecInfo specInfo = {4 * 1024 * 1024, DBConstant::MAX_HPMODE_PACK_ITEM_SIZE}; + std::vector entries; + ContinueToken token = nullptr; + EXPECT_EQ(g_store->GetSyncData(queryObj, SyncTimeRange{}, specInfo, token, entries), E_OK); + EXPECT_TRUE(entries.empty()); + ReleaseKvEntries(entries); + g_store->ReleaseContinueToken(token); + + /** + * @tc.steps: step2. Get sync data with invalid SyncTimeRange(beginTime is greater than endTime). + * @tc.expected: step2. GetSyncData return E_INVALID_ARGS. + */ + auto time = static_cast(DistributedDBToolsUnitTest::GetRandInt64(0, INT64_MAX)); + EXPECT_EQ(g_store->GetSyncData(queryObj, SyncTimeRange{time, 0, 0}, specInfo, token, entries), + -E_INVALID_ARGS); +} + +/** + * @tc.name: GetQuerySyncData006 + * @tc.desc: To test QUERY_SYNC_THRESHOLD works. When all query data is found in one get sync data operation, + if the size of query data is greater than QUERY_SYNC_THRESHOLD*MAX_ITEM_SIZE , will not get deleted data next. + Otherwise, will get deleted data next. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, GetQuerySyncData008, TestSize.Level1) +{ + const size_t maxItemSize = 200; + const float querySyncThreshold = 0.50; + + Key key; + Value value; + string str; + IOption option{ IOption::SYNC_DATA }; + + /** + * @tc.steps: step1. Put MAX_ITEM_SIZE / 2 + 1 entries from k0 to k100. + * @tc.expected: step1. Put data successfully. + */ + for (unsigned i = 0; i <= maxItemSize * querySyncThreshold; i++) { + str = "k" + to_string(i); + key = Key(str.begin(), str.end()); + str[0] = 'v'; + value = Value(str.begin(), str.end()); + EXPECT_EQ(g_connection->Put(option, key, value), E_OK); + } + + DataItem item{key, value}; + auto oneBlockSize = SQLiteSingleVerStorageExecutor::GetDataItemSerialSize(item, Parcel::GetAppendedLen()); + + /** + * @tc.steps: step2. Delete k0. + * @tc.expected: step2. Delete k0 successfully. + */ + str = "k0"; + g_connection->Delete(option, Key(str.begin(), str.end())); + + /** + * @tc.steps: step3. Get sync data when 100 query data and 1 deleted data exists in DB. + * @tc.expected: step3. Get 100 query data and no deleted data. + */ + Query query = Query::Select().PrefixKey(PREFIX_KEY); + QueryObject queryObj(query); + + DataSizeSpecInfo specInfo = {static_cast((maxItemSize) * oneBlockSize), maxItemSize}; + std::vector entries; + ContinueToken token = nullptr; + EXPECT_EQ(g_store->GetSyncData(queryObj, SyncTimeRange{}, specInfo, token, entries), -E_UNFINISHED); + EXPECT_EQ(entries.size(), 100UL); + ReleaseKvEntries(entries); + g_store->ReleaseContinueToken(token); + + /** + * @tc.steps: step4. Delete k1. + * @tc.expected: step4. Delete k1 successfully. + */ + str = "k1"; + g_connection->Delete(option, Key(str.begin(), str.end())); + + /** + * @tc.steps: step5. Get sync data when 99 query data and 2 deleted data exists in DB. + * @tc.expected: step5. Get 99 query data and 2 deleted data. + */ + EXPECT_EQ(g_store->GetSyncData(queryObj, SyncTimeRange{}, specInfo, token, entries), E_OK); + EXPECT_EQ(entries.size(), 101UL); + ReleaseKvEntries(entries); + g_store->ReleaseContinueToken(token); +} + +/** + * @tc.name: GetQuerySyncData009 + * @tc.desc: To test GetSyncData and GetSyncDataNext function works with large amounts of data. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, GetQuerySyncData009, TestSize.Level2) +{ + /** + * @tc.steps: step1. Put 500 entries from k0 to k499. + * @tc.expected: step1. Put data successfully. + */ + Key key; + Value value; + string str; + IOption option{ IOption::SYNC_DATA }; + const uint64_t totalSize = 500; // 500 data in DB. + for (unsigned i = 0; i < totalSize; i++) { + str = "k" + to_string(i); + key = Key(str.begin(), str.end()); + str[0] = 'v'; + value = Value(str.begin(), str.end()); + EXPECT_EQ(g_connection->Put(option, key, value), E_OK); + } + + /** + * @tc.steps: step2. Delete 150 entries from k150 to k299. + * @tc.expected: step2. Delete data successfully. + */ + for (unsigned i = 150; i < 300; i++) { + str = "k" + to_string(i); + g_connection->Delete(option, Key(str.begin(), str.end())); + } + + /** + * @tc.steps: step3. Get all sync data; + * @tc.expected: step3. Get 500 data. + */ + Query query = Query::Select().PrefixKey(PREFIX_KEY); + QueryObject queryObj(query); + + uint64_t getSize = 0; + std::vector entries; + const size_t maxItemSize = 100; // Get 100 data at most in one GetSyncData operation. + DataSizeSpecInfo specInfo = {MTU_SIZE, maxItemSize}; + ContinueToken token = nullptr; + g_store->GetSyncData(queryObj, SyncTimeRange{}, specInfo, token, entries); + getSize += entries.size(); + ReleaseKvEntries(entries); + + while (token != nullptr) { + g_store->GetSyncDataNext(entries, token, specInfo); + getSize += entries.size(); + ReleaseKvEntries(entries); + } + + EXPECT_EQ(getSize, totalSize); +} + +/** + * @tc.name: GetQuerySyncData010 + * @tc.desc: To test GetSyncData when Query with limit. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, GetQuerySyncData010, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put 100 entries from k100 to k1. + * @tc.expected: step1. Put data successfully. + */ + Key key; + Value value; + string str; + IOption option{ IOption::SYNC_DATA }; + const uint64_t totalSize = 100; // 100 data in DB. + for (unsigned i = totalSize; i > 0; i--) { + str = "k" + to_string(i); + key = Key(str.begin(), str.end()); + str[0] = 'v'; + value = Value(str.begin(), str.end()); + EXPECT_EQ(g_connection->Put(option, key, value), E_OK); + } + + /** + * @tc.steps: step3. Get half of sync data; + * @tc.expected: step3. Get half of sync data successfully. + */ + Query query = Query::Select().PrefixKey(PREFIX_KEY).Limit(totalSize / 2); + QueryObject queryObj(query); + + uint64_t getSize = 0; + std::vector entries; + const size_t maxItemSize = 10; // Get 10 data at most in one GetSyncData operation. + DataSizeSpecInfo specInfo = {MTU_SIZE, maxItemSize}; + ContinueToken token = nullptr; + g_store->GetSyncData(queryObj, SyncTimeRange{}, specInfo, token, entries); + getSize += entries.size(); + ReleaseKvEntries(entries); + + while (token != nullptr) { + g_store->GetSyncDataNext(entries, token, specInfo); + getSize += entries.size(); + ReleaseKvEntries(entries); + } + + EXPECT_EQ(getSize, totalSize / 2); +} + +/** + * @tc.name: GetQueryID001 + * @tc.desc: To test the function of generating query identity. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, GetQueryID001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get illegal query object, get this object identify + * @tc.expected: step1. GetIdentify will get empty string + */ + Query errQuery = Query::Select().GreaterThan("$.test", true); + QuerySyncObject querySync(errQuery); + EXPECT_EQ(querySync.GetIdentify().empty(), true); + + /** + * @tc.steps:step2. use illegal query object to serialized + * @tc.expected: step2. SerializeData will not return E_OK + */ + vector buffer(querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT), 0); + Parcel writeParcel(buffer.data(), querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + Parcel readParcel(buffer.data(), querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + EXPECT_NE(querySync.SerializeData(writeParcel, SOFTWARE_VERSION_CURRENT), E_OK); +} + +/** + * @tc.name: GetQueryID002 + * @tc.desc: To test the function of generating query identity. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, GetQueryID002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get empty condition query object, get this object identify + * @tc.expected: step1. GetIdentify result not change + */ + Query query1 = Query::Select(); + + QuerySyncObject querySync(query1); + EXPECT_EQ(querySync.GetIdentify().empty(), false); + // same object identify is same + EXPECT_EQ(querySync.GetIdentify(), querySync.GetIdentify()); + + IOption option; + option.dataType = IOption::SYNC_DATA; + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + EXPECT_EQ(g_connection->Put(option, key, value), E_OK); + EXPECT_EQ(g_connection->Put(option, KEY_1, VALUE_1), E_OK); + EXPECT_EQ(g_connection->Put(option, KEY_2, VALUE_2), E_OK); + + /** + * @tc.steps:step2. Get prefix key condition query object, get this object identify + * @tc.expected: step2. GetIdentify result not same as other condition query object + */ + Query query2 = Query::Select().PrefixKey(key); + QuerySyncObject querySync1(query2); + EXPECT_EQ(querySync1.GetIdentify().empty(), false); + // same object identify is not same + EXPECT_NE(querySync.GetIdentify(), querySync1.GetIdentify()); + + /** + * @tc.steps:step3. empty condition query object can serialized and deserialized normally + * @tc.expected: step3. after deserialized, can get all key value in database + */ + vector buffer(querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT), 0); + Parcel writeParcel(buffer.data(), querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + Parcel readParcel(buffer.data(), querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + EXPECT_EQ(querySync.SerializeData(writeParcel, SOFTWARE_VERSION_CURRENT), E_OK); + + QuerySyncObject queryObj2; + EXPECT_EQ(QuerySyncObject::DeSerializeData(readParcel, queryObj2), E_OK); + LOGD("Query obj after serialize!"); + + DataSizeSpecInfo specInfo = {4 * 1024 * 1024, DBConstant::MAX_HPMODE_PACK_ITEM_SIZE}; + std::vector entries; + ContinueToken token = nullptr; + EXPECT_EQ(g_store->GetSyncData(queryObj2, SyncTimeRange{}, specInfo, token, entries), E_OK); + EXPECT_EQ(entries.size(), 3UL); + SingleVerKvEntry::Release(entries); +} + +/** + * @tc.name: GetQueryID003 + * @tc.desc: To test the function of generating query identity ignore limit, orderby, suggestion. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, GetQueryID003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Get empty condition query object, get this object identify + */ + Query query1 = Query::Select().PrefixKey({}); + QuerySyncObject querySync1(query1); + std::string id1 = querySync1.GetIdentify(); + EXPECT_EQ(id1.empty(), false); + + /** + * @tc.steps:step2. Get limit condition query object, get this object identify + * @tc.expected: step2. GetIdentify result same as no contain limit condition + */ + Query query2 = query1.Limit(1, 1); + QuerySyncObject querySync2(query2); + std::string id2 = querySync2.GetIdentify(); + EXPECT_EQ(id2, id1); + + /** + * @tc.steps:step3. Get orderby condition query object, get this object identify + * @tc.expected: step3. GetIdentify result same as no contain orderby condition + */ + Query query3 = query2.OrderBy("$.test"); + QuerySyncObject querySync3(query3); + std::string id3 = querySync3.GetIdentify(); + EXPECT_EQ(id2, id3); +} + +/** + * @tc.name: Serialize001 + * @tc.desc: To test the function of querying the data after serialized and deserialized. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, Serialize001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Put K1V1 K2V2 and rand KV for query + */ + IOption option; + option.dataType = IOption::SYNC_DATA; + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + EXPECT_EQ(g_connection->Put(option, key, value), E_OK); + EXPECT_EQ(g_connection->Put(option, KEY_1, VALUE_1), E_OK); + EXPECT_EQ(g_connection->Put(option, KEY_2, VALUE_2), E_OK); + + /** + * @tc.steps:step2. Put K1V1 K2V2 and rand KV for query + * @tc.expected: step2. GetIdentify result same as no contain limit condition + */ + Query query = Query::Select().PrefixKey(key); + QueryObject queryObj(query); + DataSizeSpecInfo specInfo = {4 * 1024 * 1024, DBConstant::MAX_HPMODE_PACK_ITEM_SIZE}; + std::vector entries; + ContinueToken token = nullptr; + LOGD("Ori query obj!"); + EXPECT_EQ(g_store->GetSyncData(queryObj, SyncTimeRange{}, specInfo, token, entries), E_OK); + EXPECT_EQ(entries.size(), 1UL); + SingleVerKvEntry::Release(entries); + + /** + * @tc.steps:step3. query result after serialized and deserialized + * @tc.expected: step3. Get same result + */ + QuerySyncObject querySync(query); + vector buffer(querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT), 0); + Parcel writeParcel(buffer.data(), querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + Parcel readParcel(buffer.data(), querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + EXPECT_EQ(querySync.SerializeData(writeParcel, SOFTWARE_VERSION_CURRENT), E_OK); + + QuerySyncObject queryObj1; + EXPECT_EQ(QuerySyncObject::DeSerializeData(readParcel, queryObj1), E_OK); + + LOGD("Query obj after serialize!"); + EXPECT_EQ(g_store->GetSyncData(queryObj1, SyncTimeRange{}, specInfo, token, entries), E_OK); + EXPECT_EQ(entries.size(), 1UL); + SingleVerKvEntry::Release(entries); + + std::string id = querySync.GetIdentify().c_str(); + EXPECT_EQ(id.size(), 64u); + LOGD("query identify [%s] [%zu]", id.c_str(), id.size()); +} + +/** + * @tc.name: Serialize002 + * @tc.desc: To test the function of serialized illegal query object. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, Serialize002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Serialized illegal query object + * @tc.expected: step1. return not E_OK + */ + Query query = Query::Select().PrefixKey({}).GreaterThan("$.test", true); // bool can not compare + QuerySyncObject querySync(query); + vector buffer(querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT), 0); + Parcel writeParcel(buffer.data(), querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + EXPECT_NE(querySync.SerializeData(writeParcel, SOFTWARE_VERSION_CURRENT), E_OK); +} + +/** + * @tc.name: DeSerialize001 + * @tc.desc: To test the function of deserialized illegal query object. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, DeSerialize001, TestSize.Level1) +{ + /** + * @tc.steps:step1. deserialized empty content query object + * @tc.expected: step1. return not E_OK + */ + QuerySyncObject querySync; + vector buffer(querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT), 0); + Parcel readParcel(buffer.data(), querySync.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + + QuerySyncObject queryObj; + EXPECT_NE(QuerySyncObject::DeSerializeData(readParcel, queryObj), E_OK); + + /** + * @tc.steps:step2. deserialized empty parcel + * @tc.expected: step2. return not E_OK + */ + buffer.resize(0); + Parcel readParcel1(buffer.data(), 0); + EXPECT_NE(QuerySyncObject::DeSerializeData(readParcel1, queryObj), E_OK); + + /** + * @tc.steps:step3. deserialized error size parcel + * @tc.expected: step3. return not E_OK + */ + uint8_t simSize = 0; + RAND_bytes(&simSize, 1); + buffer.resize(simSize); + Parcel readParcel2(buffer.data(), simSize); + EXPECT_NE(QuerySyncObject::DeSerializeData(readParcel2, queryObj), E_OK); +} + +/** + * @tc.name: SameQueryObjectIdInDiffVer001 + * @tc.desc: Same query object have same id in different version. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, SameQueryObjectIdInDiffVer001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Record the fixed id of the query object of the current version, + * and keep it unchanged in subsequent versions + * @tc.expected: step1. Never change in diff version + */ + Query query1 = Query::Select().PrefixKey({}); + QuerySyncObject querySync1(query1); + EXPECT_EQ(querySync1.GetIdentify(), "A9AB721457C4CA98726EECC7CB16F94E31B9752BEE6D08569CFE797B4A64A304"); + + Query query2 = Query::Select(); + QuerySyncObject querySync2(query2); + EXPECT_EQ(querySync2.GetIdentify(), "AF5570F5A1810B7AF78CAF4BC70A660F0DF51E42BAF91D4DE5B2328DE0E83DFC"); + + Query query3 = Query::Select().NotLike("$.test", "testValue"); + QuerySyncObject querySync3(query3); + EXPECT_EQ(querySync3.GetIdentify(), "F2BAC2B53FE81F9928E5F8DCDF502F2419E8CEB5DFC157EEBDDB955A66C0148B"); + + vector fieldValues{1, 1, 1}; + Query query4 = Query::Select().In("$.test", fieldValues); + QuerySyncObject querySync4(query4); + EXPECT_EQ(querySync4.GetIdentify(), "EEAECCD0E1A7217574ED3092C8DAA39469388FA1B8B7B210185B4257B785FE4D"); + + Query query5 = Query::Select().OrderBy("$.test.test_child", false); + QuerySyncObject querySync5(query5); + EXPECT_EQ(querySync5.GetIdentify(), "AF5570F5A1810B7AF78CAF4BC70A660F0DF51E42BAF91D4DE5B2328DE0E83DFC"); + + Query query6 = Query::Select().Limit(1, 2); + QuerySyncObject querySync6(query6); + EXPECT_EQ(querySync6.GetIdentify(), "AF5570F5A1810B7AF78CAF4BC70A660F0DF51E42BAF91D4DE5B2328DE0E83DFC"); + + Query query7 = Query::Select().IsNull("$.test.test_child"); + QuerySyncObject querySync7(query7); + EXPECT_EQ(querySync7.GetIdentify(), "762AB5FDF9B1433D6F398269D4DDD6DE6444953F515E87C6796654180A7FF422"); + + Query query8 = Query::Select().EqualTo("$.test.test_child", true).And().GreaterThan("$.test.test_child", 1); + QuerySyncObject querySync8(query8); + EXPECT_EQ(querySync8.GetIdentify(), "B97FBFFBC690DAF25031FD4EE8ADC92F4698B9E81FD4877CD54EDEA122F6A6E0"); + + Query query9 = Query::Select().GreaterThan("$.test", 1).OrderBy("$.test"); + QuerySyncObject querySync9(query9); + EXPECT_EQ(querySync9.GetIdentify(), "77480E3EE04EB1500BB2F1A31704EE5676DC81F088A7A300F6D30E3FABA7D0A3"); + + Query query = Query::Select().GreaterThan("$.test1", 1).OrderBy("$.test1"); + QuerySyncObject querySync(query); + EXPECT_EQ(querySync.GetIdentify(), "170F5137C0BB49011D7415F706BD96B86F5FAFADA356374981362B1E177263B9"); +} + +/** + * @tc.name: querySyncByField + * @tc.desc: Test for illegal query conditions, use GetSyncData + * @tc.type: FUNC + * @tc.require: + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, querySyncByField, TestSize.Level1) +{ + Query queryInvalidField = Query::Select().EqualTo("$.field_name11", 1); + Query queryInvalidCombine = Query::Select().EqualTo("$.field_name3", 1).BeginGroup(); + Query queryAll = Query::Select(); + Query queryPrefixKeyLimit = Query::Select().PrefixKey({}).Limit(1, 0); + + DataSizeSpecInfo specInfo = {4 * 1024 * 1024, DBConstant::MAX_HPMODE_PACK_ITEM_SIZE}; + std::vector entries; + ContinueToken token = nullptr; + + QueryObject queryObj(queryInvalidCombine); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObj, SyncTimeRange{}, specInfo, token, entries), -E_INVALID_QUERY_FORMAT); + + QueryObject queryObj2(queryAll); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObj2, SyncTimeRange{}, specInfo, token, entries), E_OK); + EXPECT_EQ(entries.size(), 1UL); + ReleaseKvEntries(entries); + + QueryObject queryObj1(queryInvalidField); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObj1, SyncTimeRange{}, specInfo, token, entries), -E_INVALID_QUERY_FIELD); + + QueryObject queryObj3(queryPrefixKeyLimit); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObj3, SyncTimeRange{}, specInfo, token, entries), E_OK); + ReleaseKvEntries(entries); +} + +/** + * @tc.name: IsQueryOnlyByKey + * @tc.desc: The test can correctly determine whether the value is used for query + * @tc.type: FUNC + * @tc.require: + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, IsQueryOnlyByKey, TestSize.Level1) +{ + Query queryAll = Query::Select(); + Query queryPrefixKeyLimit = Query::Select().PrefixKey({}).Limit(1, 0); + Query queryPrefix = Query::Select().PrefixKey({}); + Query queryPrefixKeyLimitIndex = Query::Select().PrefixKey({}).Limit(1, 0).SuggestIndex("$.field_name3"); + Query queryPrefixKeyLimitEQ = Query::Select().PrefixKey({}).Limit(1, 0).EqualTo("$.field_name3", 1); + Query queryEQ = Query::Select().EqualTo("$.field_name3", 1); + Query queryLimitEQ = Query::Select().Limit(1, 0).EqualTo("$.field_name3", 1); + + QueryObject queryObj(queryAll); + EXPECT_TRUE(queryObj.IsQueryOnlyByKey()); + + QueryObject queryObj1(queryPrefixKeyLimit); + EXPECT_TRUE(queryObj1.IsQueryOnlyByKey()); + + QueryObject queryObj2(queryPrefix); + EXPECT_TRUE(queryObj2.IsQueryOnlyByKey()); + + QueryObject queryObj3(queryPrefixKeyLimitIndex); + EXPECT_FALSE(queryObj3.IsQueryOnlyByKey()); + + QueryObject queryObj4(queryPrefixKeyLimitEQ); + EXPECT_FALSE(queryObj4.IsQueryOnlyByKey()); + + QueryObject queryObj5(queryEQ); + EXPECT_FALSE(queryObj5.IsQueryOnlyByKey()); + + QueryObject queryObj6(queryLimitEQ); + EXPECT_FALSE(queryObj6.IsQueryOnlyByKey()); +} + +/** + * @tc.name: MultiQueryParcel + * @tc.desc: Mix multiple conditions for simultaneous query can be serialize + * @tc.type: FUNC + * @tc.require: + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, MultiQueryParcel, TestSize.Level1) +{ + Query queryInvalidField = Query::Select().LessThan("$.field_name1", 1); + Query queryInvalidCombine = Query::Select().EqualTo("$.field_name3", 1).BeginGroup(); + Query queryPrefixKeyLimit = Query::Select().PrefixKey({}).Limit(1, 0); + + DataSizeSpecInfo specInfo = {4 * 1024 * 1024, DBConstant::MAX_HPMODE_PACK_ITEM_SIZE}; + std::vector entries; + ContinueToken token = nullptr; + + QuerySyncObject querySyncObj(queryInvalidField); + vector buffer(querySyncObj.CalculateParcelLen(SOFTWARE_VERSION_CURRENT), 0); + Parcel writeParcel(buffer.data(), querySyncObj.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + Parcel readParcel(buffer.data(), querySyncObj.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + EXPECT_EQ(querySyncObj.SerializeData(writeParcel, SOFTWARE_VERSION_CURRENT), E_OK); + QuerySyncObject queryObjAfterSer; + EXPECT_EQ(QuerySyncObject::DeSerializeData(readParcel, queryObjAfterSer), E_OK); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObjAfterSer, SyncTimeRange{}, specInfo, token, entries), + -E_INVALID_QUERY_FORMAT); + + QuerySyncObject querySyncObj1(queryInvalidCombine); + vector buffer1(querySyncObj1.CalculateParcelLen(SOFTWARE_VERSION_CURRENT), 0); + Parcel writeParcel1(buffer1.data(), querySyncObj1.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + Parcel readParcel1(buffer1.data(), querySyncObj1.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + EXPECT_EQ(querySyncObj1.SerializeData(writeParcel1, SOFTWARE_VERSION_CURRENT), E_OK); + QuerySyncObject queryObjAfterSer1; + EXPECT_EQ(QuerySyncObject::DeSerializeData(readParcel1, queryObjAfterSer1), E_OK); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObjAfterSer1, SyncTimeRange{}, specInfo, token, entries), + -E_INVALID_QUERY_FORMAT); + + QuerySyncObject querySyncObj2(queryPrefixKeyLimit); + vector buffer2(querySyncObj2.CalculateParcelLen(SOFTWARE_VERSION_CURRENT), 0); + Parcel writeParcel2(buffer2.data(), querySyncObj2.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + Parcel readParcel2(buffer2.data(), querySyncObj2.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + EXPECT_EQ(querySyncObj2.SerializeData(writeParcel2, SOFTWARE_VERSION_CURRENT), E_OK); + QuerySyncObject queryObjAfterSer2; + EXPECT_EQ(QuerySyncObject::DeSerializeData(readParcel2, queryObjAfterSer2), E_OK); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObjAfterSer2, SyncTimeRange{}, specInfo, token, entries), + E_OK); + EXPECT_FALSE(entries.empty()); + ReleaseKvEntries(entries); +} + + +/** + * @tc.name: QueryParcel001 + * @tc.desc: Query object should has same attribute(Limit, OrderBy) after deserialized + * @tc.type: FUNC + * @tc.require: + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, QueryParcel001, TestSize.Level1) +{ + Query query = Query::Select().OrderBy("$.field_name1").Limit(10, 5); + + QuerySyncObject querySyncObj(query); + vector buffer(querySyncObj.CalculateParcelLen(SOFTWARE_VERSION_CURRENT), 0); + Parcel writeParcel(buffer.data(), querySyncObj.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + Parcel readParcel(buffer.data(), querySyncObj.CalculateParcelLen(SOFTWARE_VERSION_CURRENT)); + EXPECT_EQ(querySyncObj.SerializeData(writeParcel, SOFTWARE_VERSION_CURRENT), E_OK); + QuerySyncObject queryObjAfterSer; + EXPECT_EQ(QuerySyncObject::DeSerializeData(readParcel, queryObjAfterSer), E_OK); + EXPECT_EQ(queryObjAfterSer.HasLimit(), true); + int limit = 0; + int offset = 0; + queryObjAfterSer.GetLimitVal(limit, offset); + EXPECT_EQ(limit, 10); + EXPECT_EQ(offset, 5); + EXPECT_EQ(queryObjAfterSer.HasOrderBy(), true); +} + +/** + * @tc.name: MultiQueryGetSyncData001 + * @tc.desc: Mix multiple conditions for simultaneous query + * @tc.type: FUNC + * @tc.require: + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, MultiQueryGetSyncData001, TestSize.Level1) +{ + Query query = Query::Select(); + Query query1 = Query::Select().EqualTo("$.field_name1", true); + Query query2 = Query::Select().BeginGroup().GreaterThan("$.field_name3", 1).EndGroup(); + Query query3 = Query::Select().Like("field_name7", ""); + Query query4 = Query::Select().PrefixKey({}).OrderBy("$.field_name6"); + Query query5 = Query::Select().PrefixKey({}).IsNull("field_name10"); + + DataSizeSpecInfo specInfo = {4 * 1024 * 1024, DBConstant::MAX_HPMODE_PACK_ITEM_SIZE}; + std::vector entries; + ContinueToken token = nullptr; + + QueryObject queryObj(query); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObj, SyncTimeRange{}, specInfo, token, entries), E_OK); + ReleaseKvEntries(entries); + + QueryObject queryObj1(query1); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObj1, SyncTimeRange{}, specInfo, token, entries), E_OK); + EXPECT_EQ(entries.size(), 1UL); + ReleaseKvEntries(entries); + + QueryObject queryObj2(query2); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObj2, SyncTimeRange{}, specInfo, token, entries), E_OK); + ReleaseKvEntries(entries); + + QueryObject queryObj3(query3); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObj3, SyncTimeRange{}, specInfo, token, entries), E_OK); + ReleaseKvEntries(entries); + + QueryObject queryObj4(query4); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObj4, SyncTimeRange{}, specInfo, token, entries), E_OK); + ReleaseKvEntries(entries); + + QueryObject queryObj5(query5); + EXPECT_EQ(g_schemaStore->GetSyncData(queryObj5, SyncTimeRange{}, specInfo, token, entries), E_OK); + ReleaseKvEntries(entries); +} + +/** + * @tc.name: QueryPredicateValidation001 + * @tc.desc: check query object is query only by key and has orderBy or not + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, QueryPredicateValidation001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Create a query object with prefixKey only, check it's predicate + * @tc.expected: step1. check IsQueryOnlyByKey true; check HasOrderBy false + */ + Query query1 = Query::Select().PrefixKey({}); + QuerySyncObject querySync1(query1); + EXPECT_EQ(querySync1.IsQueryOnlyByKey(), true); + EXPECT_EQ(querySync1.HasOrderBy(), false); + + /** + * @tc.steps:step2. Create a query object with prefixKey and equalTo, check it's predicate + * @tc.expected: step2. check IsQueryOnlyByKey false; check HasOrderBy false + */ + Query query2 = Query::Select().PrefixKey({}).EqualTo("$.testField", 0); + QuerySyncObject querySync2(query2); + EXPECT_EQ(querySync2.IsQueryOnlyByKey(), false); + EXPECT_EQ(querySync2.HasOrderBy(), false); + + /** + * @tc.steps:step3. Create a query object with orderBy only, check it's predicate + * @tc.expected: step3. check IsQueryOnlyByKey false; check HasOrderBy true + */ + Query query3 = Query::Select().OrderBy("$.testField"); + QuerySyncObject querySync3(query3); + EXPECT_EQ(querySync3.IsQueryOnlyByKey(), false); + EXPECT_EQ(querySync3.HasOrderBy(), true); +} + +/** + * @tc.name: RelationalQuerySyncTest001 + * @tc.desc: Test querySyncObject serialize with table name is specified + * @tc.type: FUNC + * @tc.require: + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, RelationalQuerySyncTest001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Create a query object with table name is specified + * @tc.expected: ok + */ + Query query1 = Query::Select("Relational_table").EqualTo("field1", "abc"); + QuerySyncObject obj1(query1); + + /** + * @tc.steps:step2. Serialize the object + * @tc.expected: ok + */ + uint32_t buffLen = obj1.CalculateParcelLen(SOFTWARE_VERSION_CURRENT); + vector buffer(buffLen, 0); + Parcel writeParcel(buffer.data(), buffLen); + EXPECT_EQ(obj1.SerializeData(writeParcel, SOFTWARE_VERSION_CURRENT), E_OK); + + /** + * @tc.steps:step3. DeSerialize the data + * @tc.expected: ok, And the queryId is same + */ + Parcel readParcel(buffer.data(), buffLen); + QuerySyncObject queryObj2; + EXPECT_EQ(QuerySyncObject::DeSerializeData(readParcel, queryObj2), E_OK); + EXPECT_EQ(obj1.GetIdentify(), queryObj2.GetIdentify()); +} + +/** + * @tc.name: RelationalQuerySyncTest002 + * @tc.desc: Test querySyncObject with different table name has different identity + * @tc.type: FUNC + * @tc.require: + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, RelationalQuerySyncTest002, TestSize.Level1) +{ + Query query1 = Query::Select("Relational_table1").EqualTo("field1", "abc"); + QuerySyncObject obj1(query1); + + Query query2 = Query::Select("Relational_table2").EqualTo("field1", "abc"); + QuerySyncObject obj2(query2); + + /** + * @tc.steps:step1. check object identity + * @tc.expected: identity should be different. + */ + EXPECT_NE(obj1.GetIdentify(), obj2.GetIdentify()); +} + +/** + * @tc.name: SerializeAndDeserializeForVer1 + * @tc.desc: Test querySyncObject serialization and deserialization. + * @tc.type: FUNC + * @tc.require: AR000GOHO7 + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, SerializeAndDeserializeForVer1, TestSize.Level1) +{ + Query qeury1 = Query::Select("table1").EqualTo("field1", "abc").InKeys({KEY_1, KEY_2, KEY_3}); + QuerySyncObject obj1(qeury1); + + /** + * @tc.steps:step1. Serialize obj1. + * @tc.expected: Serialize successfully. + */ + auto len = obj1.CalculateParcelLen(SOFTWARE_VERSION_CURRENT); + std::vector buffer(len); + Parcel parcel1(buffer.data(), buffer.size()); + obj1.SerializeData(parcel1, SOFTWARE_VERSION_CURRENT); + ASSERT_EQ(parcel1.IsError(), false); + + /** + * @tc.steps:step2. Deserialize obj1. + * @tc.expected: Deserialize successfully. + */ + QuerySyncObject obj2; + Parcel parcel2(buffer.data(), buffer.size()); + ASSERT_EQ(parcel2.IsError(), false); + + /** + * @tc.steps:step3. check object identity + * @tc.expected: identity should be the same. + */ + EXPECT_NE(obj1.GetIdentify(), obj2.GetIdentify()); +} + +/** + * @tc.name: MultiInkeys1 + * @tc.desc: Test the rc when multiple inkeys exists. + * @tc.type: FUNC + * @tc.require: AR000GOHO7 + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBStorageQuerySyncTest, MultiInkeys1, TestSize.Level1) +{ + /** + * @tc.steps:step1. Create an invalid query, with multiple inkeys. + */ + Query query = Query::Select().InKeys({KEY_1, KEY_2}).InKeys({KEY_3}); + + /** + * @tc.steps:step2. Get data. + * @tc.expected: Return INVALID_QUERY_FORMAT. + */ + std::vector entries; + IOption option; + option.dataType = IOption::SYNC_DATA; + EXPECT_EQ(g_schemaConnect->GetEntries(option, query, entries), -E_INVALID_QUERY_FORMAT); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_register_conflict_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_register_conflict_test.cpp new file mode 100644 index 00000000..a7d5f622 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_register_conflict_test.cpp @@ -0,0 +1,836 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_constant.h" +#include "db_errno.h" +#include "distributeddb_data_generate_unit_test.h" +#include "kv_store_nb_conflict_data.h" +#include "kv_store_nb_delegate_impl.h" +#include "kvdb_conflict_entry.h" +#include "platform_specific.h" +#include "sqlite_single_ver_natural_store.h" +#include "sqlite_single_ver_natural_store_connection.h" +#include "time_helper.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + const string STORE_ID = STORE_ID_SYNC; + std::string g_identifier; + const int CONFLICT_ALL = 15; + const int TIME_LAG = 100; + const int DATA_TIME_LAG = 1000; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + DBStatus g_kvDelegateStatus = INVALID_ARGS; + SQLiteSingleVerNaturalStore *g_store = nullptr; + + const Value DEFT_VALUE; + DistributedDB::SQLiteSingleVerNaturalStoreConnection *g_connection = nullptr; + const auto OLD_VALUE_TYPE = KvStoreNbConflictData::ValueType::OLD_VALUE; + const auto NEW_VALUE_TYPE = KvStoreNbConflictData::ValueType::NEW_VALUE; + std::list g_conflictDataList; + std::vector g_conflictDataConnect; + struct SingleVerConflictData { + KvStoreNbConflictType type = CONFLICT_NATIVE_ALL; + Key key; + Value oldValue; + Value newValue; + bool oldIsDeleted = false; + bool newIsDeleted = false; + bool oldIsNative = false; + bool newIsNative = false; + int getoldValueErrCode = 0; + int getNewValueErrCode = 0; + bool operator==(const SingleVerConflictData &comparedData) const + { + if (this->type == comparedData.type && + this->key == comparedData.key && + this->oldValue == comparedData.oldValue && + this->newValue == comparedData.newValue && + this->oldIsDeleted == comparedData.oldIsDeleted && + this->newIsDeleted == comparedData.newIsDeleted && + this->oldIsNative == comparedData.oldIsNative && + this->newIsNative == comparedData.newIsNative && + this->getoldValueErrCode == comparedData.getoldValueErrCode && + this->getNewValueErrCode == comparedData.getNewValueErrCode) { + return true; + } + LOGD("type = %d, ctype = %d", this->type, comparedData.type); + DBCommon::PrintHexVector(this->key, __LINE__, "key"); + DBCommon::PrintHexVector(comparedData.key, __LINE__, "ckey"); + DBCommon::PrintHexVector(this->oldValue, __LINE__, "value"); + DBCommon::PrintHexVector(comparedData.oldValue, __LINE__, "oldValue"); + DBCommon::PrintHexVector(this->newValue, __LINE__, "oldvalue"); + DBCommon::PrintHexVector(comparedData.newValue, __LINE__, "newValue"); + + LOGD("oldIsDeleted = %d, coldIsDeleted = %d", this->oldIsDeleted, comparedData.oldIsDeleted); + LOGD("newIsDeleted = %d, cnewIsDeleted = %d", this->newIsDeleted, comparedData.newIsDeleted); + LOGD("oldIsNative = %d, coldIsNative = %d", this->oldIsNative, comparedData.oldIsNative); + LOGD("newIsNative = %d, cnewIsNative = %d", this->newIsNative, comparedData.newIsNative); + LOGD("getoldValueErrCode = %d, cgetoldValueErrCode = %d", this->getoldValueErrCode, + comparedData.getoldValueErrCode); + LOGD("getNewValueErrCode = %d, cgetNewValueErrCode = %d", this->getNewValueErrCode, + comparedData.getNewValueErrCode); + + return false; + } + }; + std::vector g_conflictData; + + void NotifierConnectCallback(const KvDBCommitNotifyData &data) + { + int errCode; + g_conflictDataList = data.GetCommitConflicts(errCode); + for (const auto &element : g_conflictDataList) { + g_conflictDataConnect.push_back(element); + } + } + + void NotifierCallback(const KvStoreNbConflictData &data) + { + Key key; + Value oldValue; + Value newValue; + data.GetKey(key); + data.GetValue(OLD_VALUE_TYPE, oldValue); + LOGD("Get new value status:%d", data.GetValue(NEW_VALUE_TYPE, newValue)); + LOGD("Type:%d", data.GetType()); + DBCommon::PrintHexVector(oldValue, __LINE__); + DBCommon::PrintHexVector(newValue, __LINE__); + LOGD("Type:IsDeleted %d vs %d, IsNative %d vs %d", data.IsDeleted(OLD_VALUE_TYPE), + data.IsDeleted(NEW_VALUE_TYPE), data.IsNative(OLD_VALUE_TYPE), data.IsNative(NEW_VALUE_TYPE)); + g_conflictData.push_back({data.GetType(), key, oldValue, newValue, data.IsDeleted(OLD_VALUE_TYPE), + data.IsDeleted(NEW_VALUE_TYPE), data.IsNative(OLD_VALUE_TYPE), data.IsNative(NEW_VALUE_TYPE), + data.GetValue(OLD_VALUE_TYPE, oldValue), data.GetValue(NEW_VALUE_TYPE, newValue)}); + } + + // the type of g_kvDelegateCallback is function + auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); +} + +class DistributedDBStorageRegisterConflictTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBStorageRegisterConflictTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + + std::string origIdentifier = USER_ID + "-" + APP_ID + "-" + STORE_ID; + std::string identifier = DBCommon::TransferHashString(origIdentifier); + g_identifier = DBCommon::TransferStringToHex(identifier); + + string dir = g_testDir + "/" + g_identifier + "/" + DBConstant::SINGLE_SUB_DIR; + DIR *dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } +} + +void DistributedDBStorageRegisterConflictTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir + "/" + g_identifier + "/" + + DBConstant::SINGLE_SUB_DIR) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBStorageRegisterConflictTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /* + * Here, we create STORE_ID before test, + * and it will be closed in TearDown(). + */ + KvStoreNbDelegate::Option option = {true}; + g_mgr.GetKvStore(STORE_ID, option, g_kvNbDelegateCallback); + EXPECT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + + KvDBProperties property; + property.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + property.SetStringProp(KvDBProperties::STORE_ID, STORE_ID); + property.SetStringProp(KvDBProperties::IDENTIFIER_DIR, g_identifier); + property.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + g_store = new (std::nothrow) SQLiteSingleVerNaturalStore; + ASSERT_NE(g_store, nullptr); + ASSERT_EQ(g_store->Open(property), E_OK); + + int erroCode = E_OK; + g_connection = static_cast(g_store->GetDBConnection(erroCode)); + ASSERT_NE(g_connection, nullptr); + g_store->DecObjRef(g_store); + EXPECT_EQ(erroCode, E_OK); + + g_conflictData.clear(); + g_conflictDataConnect.clear(); +} + +void DistributedDBStorageRegisterConflictTest::TearDown(void) +{ + if (g_connection != nullptr) { + g_connection->Close(); + } + + g_store = nullptr; + + if (g_kvNbDelegatePtr != nullptr) { + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore(STORE_ID) == OK); + } +} + +static bool CheckNewConflictData(KvDBConflictEntry ¬ifyData, KvDBConflictEntry &expectNotifyData) +{ + if (notifyData.newData.value != expectNotifyData.newData.value) { + LOGD("New Data value ERROR! Actual vs Expected"); + DBCommon::PrintHexVector(notifyData.newData.value); + DBCommon::PrintHexVector(expectNotifyData.newData.value); + return false; + } + + if (notifyData.oldData.isDeleted != expectNotifyData.oldData.isDeleted) { + LOGD("Old Data IsDeleted ERROR! Actual %d vs Expected %d", notifyData.oldData.isDeleted, + expectNotifyData.oldData.isDeleted); + return false; + } + if (notifyData.oldData.isLocal != expectNotifyData.oldData.isLocal) { + LOGD("Old Data IsLocal ERROR! Actual %d vs Expected %d", + notifyData.oldData.isLocal, expectNotifyData.oldData.isLocal); + return false; + } + + if (notifyData.oldData.value != expectNotifyData.oldData.value) { + LOGD("Old Data value ERROR! Actualvs Expected"); + DBCommon::PrintHexVector(notifyData.oldData.value); + DBCommon::PrintHexVector(expectNotifyData.oldData.value); + return false; + } + + return true; +} + +static bool CheckOldConflictData(KvDBConflictEntry ¬ifyData, KvDBConflictEntry &expectNotifyData) +{ + if (notifyData.type != expectNotifyData.type) { + LOGD("Conflict Type ERROR! Actual %d vs Expected %d", notifyData.type, expectNotifyData.type); + return false; + } + + if (notifyData.key != expectNotifyData.key) { + LOGD("key not match"); + return false; + } + + if (notifyData.newData.isDeleted != expectNotifyData.newData.isDeleted) { + LOGD("New Data IsDeleted ERROR! Actual %d vs Expected %d", notifyData.newData.isDeleted, + expectNotifyData.newData.isDeleted); + return false; + } + + if (notifyData.newData.isLocal != expectNotifyData.newData.isLocal) { + LOGD("New Data IsLocal ERROR! Actual %d vs Expected %d", notifyData.newData.isLocal, + expectNotifyData.newData.isLocal); + return false; + } + + return true; +} + +static void SyncPutConflictData(int deltaTime) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + + Timestamp timeEnd = TimeHelper::GetSysCurrentTime(); + std::vector vect; + vect.push_back({KEY_1, VALUE_1, timeEnd, 0, DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceB"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + ASSERT_EQ(g_connection->SetConflictNotifier(CONFLICT_ALL, NotifierConnectCallback), E_OK); + EXPECT_EQ(g_conflictDataConnect.empty(), true); + + vect.clear(); + vect.push_back({KEY_1, VALUE_2, timeEnd + deltaTime, 0, DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceC"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + ASSERT_EQ(g_conflictDataConnect.empty(), false); + + KvDBConflictEntry expectNotifyData1 = {KvStoreNbConflictType::CONFLICT_FOREIGN_KEY_ONLY, KEY_1, + {VALUE_2, false, false}, {VALUE_1, false, true}}; + KvDBConflictEntry expectNotifyData2 = {KvStoreNbConflictType::CONFLICT_FOREIGN_KEY_ONLY, KEY_1, + {VALUE_1, false, true}, {VALUE_2, false, false}}; + if (deltaTime > 0) { + EXPECT_EQ(CheckOldConflictData(g_conflictDataConnect.front(), expectNotifyData2), true); + EXPECT_EQ(CheckNewConflictData(g_conflictDataConnect.front(), expectNotifyData2), true); + } else { + EXPECT_EQ(CheckOldConflictData(g_conflictDataConnect.front(), expectNotifyData1), true); + EXPECT_EQ(CheckNewConflictData(g_conflictDataConnect.front(), expectNotifyData1), true); + } +} + +static void SyncDeleteConflictData(const int deltaTime) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + + Timestamp time = TimeHelper::GetSysCurrentTime(); + + std::vector vect; + vect.push_back({KEY_1, VALUE_1, time, 0, DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceB"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + ASSERT_EQ(g_connection->SetConflictNotifier(CONFLICT_ALL, NotifierConnectCallback), E_OK); + EXPECT_EQ(g_conflictDataConnect.empty(), true); + + vect.clear(); + std::vector hashKey; + DistributedDBToolsUnitTest::CalcHash(KEY_1, hashKey); + vect.push_back({hashKey, DEFT_VALUE, time + deltaTime, 1, DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceC"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); +} + +static void SyncPutFromDiffDevConflictData(const int deltaTime) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + + Timestamp time = TimeHelper::GetSysCurrentTime(); + + std::vector vect; + vect.push_back({KEY_1, VALUE_1, time, 0, DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceB"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + g_connection->SetConflictNotifier(CONFLICT_ALL, NotifierConnectCallback); + + vect.clear(); + vect.push_back({KEY_1, VALUE_2, time + deltaTime, 0, DBCommon::TransferHashString("deviceC")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceC"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); +} + +static void SyncDeleteFromDiffDevConflictData(const int deltaTime) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + + Timestamp time = TimeHelper::GetSysCurrentTime(); + + std::vector vect; + vect.push_back({KEY_1, VALUE_1, time, 0, DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceB"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + + ASSERT_EQ(g_connection->SetConflictNotifier(CONFLICT_ALL, NotifierConnectCallback), E_OK); + EXPECT_EQ(g_conflictDataConnect.empty(), true); + + vect.clear(); + std::vector hashKey; + DistributedDBToolsUnitTest::CalcHash(KEY_1, hashKey); + vect.push_back({hashKey, DEFT_VALUE, time + deltaTime, 1, DBCommon::TransferHashString("deviceC")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceC"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); +} + +/** + * @tc.name: ConflictNotificationTest001 + * @tc.desc: Put a non-conflict key and expect no conflict being triggered. + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Setup conflict notifier. + * @tc.expected: step1. Expect setup success + */ + EXPECT_TRUE(g_kvNbDelegatePtr->SetConflictNotifier(CONFLICT_ALL, NotifierCallback) == OK); + /** + * @tc.steps:step2. Put a key into the database. + * @tc.expected: step2. Return no conflict. + */ + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + EXPECT_EQ(g_conflictData.empty(), true); +} + +/** + * @tc.name: ConflictNotificationTest002 + * @tc.desc: Put a native conflict key and expect native conflict being triggered. + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest002, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Put a kv data into database with KEY_1, VALUE_1 and setup conflict notifier. + * @tc.expected: step1/2. setup success. + */ + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + EXPECT_TRUE(g_kvNbDelegatePtr->SetConflictNotifier(CONFLICT_ALL, NotifierCallback) == OK); + /** + * @tc.steps:step3. Put another kv data into database with the same key KEY_1. + * @tc.expected: step3. Expect to trigger a conflict. Return a SingleVerConflictData with { + * CONFLICT_NATIVE_ALL, KEY_1, VALUE_1, VALUE_2, false, false, true, true} + */ + EXPECT_EQ(g_kvNbDelegatePtr->Put(KEY_1, VALUE_2), E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + ASSERT_FALSE(g_conflictData.empty()); + SingleVerConflictData expectNotifyData = {KvStoreNbConflictType::CONFLICT_NATIVE_ALL, + KEY_1, VALUE_1, VALUE_2, false, false, true, true}; + EXPECT_EQ(g_conflictData.front() == expectNotifyData, true); +} + +/** + * @tc.name: ConflictNotificationTest003 + * @tc.desc: Put a data then delete it. Expect native conflict being triggered. + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest003, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Put a kv data into database with KEY_1, VALUE_1 and setup conflict notifier. + * @tc.expected: step1/2. setup success. + */ + g_kvNbDelegatePtr->Put(KEY_1, VALUE_1); + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + EXPECT_TRUE(g_kvNbDelegatePtr->SetConflictNotifier(CONFLICT_ALL, NotifierCallback) == OK); + /** + * @tc.steps:step3. Delete KEY_1. + * @tc.expected: step3. Expect Delete action triggers a conflict. Return a SingleVerConflictData with { + * KvStoreNbConflictType::CONFLICT_NATIVE_ALL, KEY_1, VALUE_1, DEFT_VALUE, false, true, true, true, OK, ERROR}; + */ + g_kvNbDelegatePtr->Delete(KEY_1); + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + EXPECT_EQ(g_conflictData.empty(), false); + SingleVerConflictData expectNotifyData = {KvStoreNbConflictType::CONFLICT_NATIVE_ALL, + KEY_1, VALUE_1, DEFT_VALUE, false, true, true, true, OK, DB_ERROR}; + EXPECT_EQ(g_conflictData.front() == expectNotifyData, true); +} + +/** + * @tc.name: ConflictNotificationTest004 + * @tc.desc: Sync a data then put a data with the same key. Expect native conflict being triggered. + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest004, TestSize.Level1) +{ + Timestamp time = TimeHelper::GetSysCurrentTime(); + /** + * @tc.steps:step1/2. Sync a kv data into database with KEY_1, VALUE_1 and setup conflict notifier. + * @tc.expected: step1/2. setup conflict notifier success. + */ + std::vector vect; + vect.push_back({KEY_1, VALUE_1, time, 0, DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceB"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + EXPECT_TRUE(g_kvNbDelegatePtr->SetConflictNotifier(CONFLICT_ALL, NotifierCallback) == OK); + /** + * @tc.steps:step3. Put a kv data with the same key but different value KEY_1, VALUE_2. + * @tc.expected: step3. Expect Put action triggers a conflict, which is of type SingleVerConflictData, + * {KvStoreNbConflictType::CONFLICT_NATIVE_ALL, KEY_1, VALUE_1, VALUE_2, false, false, true, true, OK, OK}; + */ + g_kvNbDelegatePtr->Put(KEY_1, VALUE_2); + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + EXPECT_EQ(g_conflictData.empty(), false); + SingleVerConflictData expectNotifyData = {KvStoreNbConflictType::CONFLICT_NATIVE_ALL, + KEY_1, VALUE_1, VALUE_2, false, false, true, true, OK, OK}; + EXPECT_EQ(g_conflictData.front() == expectNotifyData, true); +} + +/** + * @tc.name: ConflictNotificationTest005 + * @tc.desc: Get a Sync data then delete it. Expect to see native conflict. + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest005, TestSize.Level1) +{ + Timestamp time = TimeHelper::GetSysCurrentTime(); + /** + * @tc.steps:step1/2. Sync a kv data into database with KEY_1, VALUE_1 and setup conflict notifier. + * @tc.expected: step1/2. setup conflict notifier success. + */ + std::vector vect; + vect.push_back({KEY_1, VALUE_1, time, 0, DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceB"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + /** + * @tc.steps:step3. Delete the synchronized data. + * @tc.expected: step3. Expect Delete action triggers a conflict, which is of type SingleVerConflictData, + * {KvStoreNbConflictType::CONFLICT_NATIVE_ALL, KEY_1, VALUE_1, DEFT_VALUE, false, true, true, true, OK, ERROR}; + */ + EXPECT_TRUE(g_kvNbDelegatePtr->SetConflictNotifier(CONFLICT_ALL, NotifierCallback) == OK); + g_kvNbDelegatePtr->Delete(KEY_1); + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + SingleVerConflictData expectNotifyData = {KvStoreNbConflictType::CONFLICT_NATIVE_ALL, + KEY_1, VALUE_1, DEFT_VALUE, false, true, true, true, OK, DB_ERROR}; + EXPECT_EQ(g_conflictData.front() == expectNotifyData, true); +} + +/** + * @tc.name: ConflictNotificationTest006 + * @tc.desc: Get a sync data without local key that conflicts with it. Expect to see no conflict. + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest006, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Sync a kv data into database with KEY_1, VALUE_1 and setup conflict notifier. + * @tc.expected: step1/2. setup conflict notifier success and no conflict being triggered. + */ + EXPECT_TRUE(g_kvNbDelegatePtr->SetConflictNotifier(CONFLICT_ALL, NotifierCallback) == OK); + Timestamp time = TimeHelper::GetSysCurrentTime(); + std::vector vect; + vect.push_back({KEY_1, VALUE_1, time, 1, DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceB"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + EXPECT_EQ(g_conflictData.empty(), true); +} + +/** + * @tc.name: ConflictNotificationTest007 + * @tc.desc: Sync-sync data conflict. Expect to see foreign conflict and the winner has larger time tag. + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest007, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Sync a kv data into database, and sync another with the same key and same origin, with a time + * lag DATA_TIME_LAG us. + * @tc.expected: step1/2. Expect to see conflict with Foreign key only conflict: + * {CONFLICT_FOREIGN_KEY_ONLY, KEY_1.key, {VALUE_1, false, true}, {VALUE_2, false, false}} + */ + SyncPutConflictData(DATA_TIME_LAG); +} + +/** + * @tc.name: ConflictNotificationTest008 + * @tc.desc: Sync-sync data conflict. Expect to see foreign conflict and the winner has larger time tag. + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest008, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Sync a kv data into database, and sync another with the same key and same origin, with + * time advanced DATA_TIME_LAG us. + * @tc.expected: step1/2. Expect to see conflict: + * {CONFLICT_FOREIGN_KEY_ONLY, KEY_1.key, {VALUE_1, false, true}, {VALUE_2, false, false}} + */ + SyncPutConflictData(-DATA_TIME_LAG); +} + +/** + * @tc.name: ConflictNotificationTest009 + * @tc.desc: Sync a data to the device. Sync another data with the same key and the time tag +DATA_TIME_LAG us. + * Expect to see native conflict and the first data being deleted. + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest009, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Sync a kv data into database, and sync another deleted data (null data) with the same key + * and same origin, with a time lag DATA_TIME_LAG us. + * @tc.expected: step1/2. Expect the deleted data triggers conflict: + * {CONFLICT_FOREIGN_KEY_ONLY, KEY_1.key, {VALUE_1, false, true}, {DEFT_VALUE.value, true, false}} + */ + SyncDeleteConflictData(DATA_TIME_LAG); + ASSERT_FALSE(g_conflictDataConnect.empty()); + KvDBConflictEntry expectNotifyData = {KvStoreNbConflictType::CONFLICT_FOREIGN_KEY_ONLY, KEY_1, + {VALUE_1, false, true}, {DEFT_VALUE, true, false}}; + EXPECT_EQ(CheckOldConflictData(g_conflictDataConnect.front(), expectNotifyData), true); + EXPECT_EQ(CheckNewConflictData(g_conflictDataConnect.front(), expectNotifyData), true); +} + +/** + * @tc.name: ConflictNotificationTest010 + * @tc.desc: Sync a data to the device. Sync another data with the same key and the time tag -DATA_TIME_LAG us. + * Expect to see native conflict and the second data being deleted. + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest010, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Sync a kv data into database, and sync another deleted data (null data) with the same key + * and same origin, with a time lag DATA_TIME_LAG us. + * @tc.expected: step1/2. Expect the deleted data triggers conflict: + * {CONFLICT_FOREIGN_KEY_ONLY, KEY_1.key, {DEFT_VALUE.value, true, false}, {VALUE_1, false, true}} + */ + SyncDeleteConflictData(-DATA_TIME_LAG); + EXPECT_EQ(g_conflictDataConnect.empty(), false); + KvDBConflictEntry expectNotifyData = {KvStoreNbConflictType::CONFLICT_FOREIGN_KEY_ONLY, KEY_1, + {DEFT_VALUE, true, false}, {VALUE_1, false, true}}; + EXPECT_EQ(CheckOldConflictData(g_conflictDataConnect.front(), expectNotifyData), true); + EXPECT_EQ(CheckNewConflictData(g_conflictDataConnect.front(), expectNotifyData), true); +} + +/** + * @tc.name: ConflictNotificationTest011 + * @tc.desc: Sync-sync multi-origin conflict. + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest011, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Sync a kv data into database, and sync another with the same key + * but origin differs from the previous, with a time lag DATA_TIME_LAG us. + * @tc.expected: step1/2. Expect the Put Action triggers conflict: + * {CONFLICT_FOREIGN_KEY_ORIG, KEY_1.key, {VALUE_1, false, true}, {VALUE_2, false, false}} + */ + SyncPutFromDiffDevConflictData(DATA_TIME_LAG); + EXPECT_EQ(g_conflictDataConnect.empty(), false); + KvDBConflictEntry expectNotifyData = {KvStoreNbConflictType::CONFLICT_FOREIGN_KEY_ORIG, KEY_1, + {VALUE_1, false, true}, {VALUE_2, false, false}}; + EXPECT_EQ(CheckOldConflictData(g_conflictDataConnect.front(), expectNotifyData), true); + EXPECT_EQ(CheckNewConflictData(g_conflictDataConnect.front(), expectNotifyData), true); +} + +/** + * @tc.name: ConflictNotificationTest012 + * @tc.desc: Sync-sync multi-origin conflict. + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest012, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Sync a kv data into database, and sync another with the same key + * but origin differs from the previous, with a time advanced DATA_TIME_LAG us. + * @tc.expected: step1/2. Expect the Put Action triggers conflict: + * {CONFLICT_FOREIGN_KEY_ORIG, KEY_1.key, {VALUE_1, false, true}, {VALUE_2, false, false}} + */ + SyncPutFromDiffDevConflictData(-DATA_TIME_LAG); + KvDBConflictEntry expectNotifyData = {KvStoreNbConflictType::CONFLICT_FOREIGN_KEY_ORIG, KEY_1, + {VALUE_2, false, false}, {VALUE_1, false, true}}; + EXPECT_EQ(CheckOldConflictData(g_conflictDataConnect.front(), expectNotifyData), true); + EXPECT_EQ(CheckNewConflictData(g_conflictDataConnect.front(), expectNotifyData), true); +} + +/** + * @tc.name: ConflictNotificationTest013 + * @tc.desc: Sync-sync multi-origin conflict with deleted data + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest013, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Sync a kv data into database, and sync another with the same key + * but origin differs from the previous, with a time lag DATA_TIME_LAG us. + * @tc.expected: step1/2. Expect the deleted data triggers conflict: + * {CONFLICT_FOREIGN_KEY_ORIG, KEY_1.key, {VALUE_1, false, true}, {VALUE_2, false, false}} + */ + SyncDeleteFromDiffDevConflictData(DATA_TIME_LAG); + EXPECT_EQ(g_conflictDataConnect.empty(), false); + KvDBConflictEntry expectNotifyData = {KvStoreNbConflictType::CONFLICT_FOREIGN_KEY_ORIG, KEY_1, + {VALUE_1, false, true}, {DEFT_VALUE, true, false}}; + EXPECT_EQ(CheckOldConflictData(g_conflictDataConnect.front(), expectNotifyData), true); + EXPECT_EQ(CheckNewConflictData(g_conflictDataConnect.front(), expectNotifyData), true); +} + +/** + * @tc.name: ConflictNotificationTest014 + * @tc.desc: Sync-sync multi-origin conflict with deleted data + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: maokeheng + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest014, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Sync a kv data into database, and sync another with the same key + * but origin differs from the previous, with a time advanced DATA_TIME_LAG us. + * @tc.expected: step1/2. Expect the deleted data triggers conflict: + * {CONFLICT_FOREIGN_KEY_ORIG, KEY_1.key, {VALUE_1, false, true}, {VALUE_2, false, false}} + */ + SyncDeleteFromDiffDevConflictData(-DATA_TIME_LAG); + KvDBConflictEntry expectNotifyData = {KvStoreNbConflictType::CONFLICT_FOREIGN_KEY_ORIG, KEY_1, + {DEFT_VALUE, true, false}, {VALUE_1, false, true}}; + EXPECT_EQ(CheckOldConflictData(g_conflictDataConnect.front(), expectNotifyData), true); + EXPECT_EQ(CheckNewConflictData(g_conflictDataConnect.front(), expectNotifyData), true); +} + +/** + * @tc.name: ConflictNotificationTest015 + * @tc.desc: put same record for conflict notification function + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: wumin + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest015, TestSize.Level1) +{ + Timestamp timeEnd = TimeHelper::GetSysCurrentTime(); + + std::vector vect; + vect.push_back({KEY_1, VALUE_1, timeEnd, 0, "deviceB", 0, "deviceB"}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceB"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + ASSERT_EQ(g_connection->SetConflictNotifier(CONFLICT_ALL, NotifierConnectCallback), E_OK); + EXPECT_EQ(g_conflictDataConnect.empty(), true); + + vect.clear(); + vect.push_back({KEY_1, VALUE_1, timeEnd + DATA_TIME_LAG, 0, "deviceB", 0, "deviceB"}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceB"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + ASSERT_EQ(g_conflictDataConnect.empty(), true); +} + +/** + * @tc.name: ConflictNotificationTest016 + * @tc.desc: put record for conflict notification function + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: wumin + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest016, TestSize.Level1) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + Timestamp timeEnd = TimeHelper::GetSysCurrentTime(); + + std::vector vect; + vect.push_back({KEY_1, VALUE_1, timeEnd, 0, DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceB"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + ASSERT_EQ(g_connection->SetConflictNotifier(CONFLICT_ALL, NotifierConnectCallback), E_OK); + EXPECT_EQ(g_conflictDataConnect.empty(), true); + + vect.clear(); + vect.push_back({KEY_1, VALUE_2, timeEnd, 0, DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceB"), E_OK); + + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + + ASSERT_EQ(g_conflictDataConnect.empty(), false); + KvDBConflictEntry expectNotifyData = {KvStoreNbConflictType::CONFLICT_FOREIGN_KEY_ONLY, KEY_1, + {VALUE_1, false, true}, {VALUE_2, false, false}}; + + EXPECT_EQ(CheckOldConflictData(g_conflictDataConnect.front(), expectNotifyData), true); + EXPECT_EQ(CheckNewConflictData(g_conflictDataConnect.front(), expectNotifyData), true); +} + +namespace { + void GetNewConflictStore() + { + if (g_connection != nullptr) { + g_connection->Close(); + } + if (g_kvNbDelegatePtr != nullptr) { + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + EXPECT_TRUE(g_mgr.DeleteKvStore(STORE_ID) == OK); + } + KvDBProperties property; + property.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + property.SetStringProp(KvDBProperties::STORE_ID, STORE_ID); + property.SetStringProp(KvDBProperties::IDENTIFIER_DIR, g_identifier); + property.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + property.SetIntProp(KvDBProperties::CONFLICT_RESOLVE_POLICY, DENY_OTHER_DEV_AMEND_CUR_DEV_DATA); + g_store = new (std::nothrow) SQLiteSingleVerNaturalStore; + ASSERT_NE(g_store, nullptr); + ASSERT_EQ(g_store->Open(property), E_OK); + + int erroCode = E_OK; + g_connection = static_cast(g_store->GetDBConnection(erroCode)); + ASSERT_NE(g_connection, nullptr); + g_store->DecObjRef(g_store); + EXPECT_EQ(erroCode, E_OK); + } +} + +/** + * @tc.name: ConflictNotificationTest017 + * @tc.desc: put record for conflict notification function + * @tc.type: FUNC + * @tc.require: AR000CQS3U + * @tc.author: wumin + */ +HWTEST_F(DistributedDBStorageRegisterConflictTest, ConflictNotificationTest017, TestSize.Level1) +{ + GetNewConflictStore(); + IOption option; + option.dataType = IOption::SYNC_DATA; + std::vector vect; + g_connection->Put(option, KEY_1, VALUE_1); + ASSERT_EQ(g_connection->SetConflictNotifier(CONFLICT_ALL, NotifierConnectCallback), E_OK); + + Timestamp timeEnd = TimeHelper::GetSysCurrentTime(); + static const uint32_t addTimestamp = 10000; + vect.push_back({KEY_1, VALUE_2, timeEnd + addTimestamp, 0, "", timeEnd + addTimestamp, + DBCommon::TransferHashString("deviceB")}); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(g_store, vect, "deviceB"), E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(TIME_LAG)); + EXPECT_EQ(g_conflictDataConnect.empty(), true); + Value readValue; + EXPECT_EQ(g_connection->Get(option, KEY_1, readValue), E_OK); + EXPECT_EQ(VALUE_1, readValue); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_register_observer_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_register_observer_test.cpp new file mode 100644 index 00000000..a4f8bdea --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_register_observer_test.cpp @@ -0,0 +1,828 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_constant.h" +#include "db_errno.h" +#include "default_factory.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "ikvdb_factory.h" +#include "log_print.h" +#include "sqlite_single_ver_natural_store.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + + SQLiteSingleVerNaturalStore *g_singleVerNaturaStore = nullptr; + IKvDBConnection *g_singleVerNaturaStoreConnection = nullptr; + bool g_createFactory = false; + const int OBSERVER_SLEEP_TIME = 80; + + Key g_emptyKey; + string g_keyStr1 = "key_1"; + string g_keyStr2 = "key_2"; + string g_keyStr3 = "key_3"; + string g_keyStr4 = "key_4"; + string g_keyStr5 = "key_5"; + string g_keyStr6 = "key_6"; + + string g_valueStr1 = "value_1"; + string g_valueStr2 = "value_2"; + string g_valueStr3 = "value_3"; + string g_valueStr4 = "value_4"; + string g_valueStr5 = "value_5"; + string g_valueStr6 = "value_6"; + string g_oldValueStr3 = "old_value_3"; + string g_oldValueStr4 = "old_value_4"; + + list g_emptyEntries; + Entry g_entry0; + Entry g_entry1; + Entry g_entry2; + Entry g_entry3; + Entry g_entry4; + Entry g_entry5; + Entry g_entry6; + Entry g_oldEntry3; + Entry g_oldEntry4; + + bool g_testFuncCalled = false; + list g_insertedEntries; + list g_updatedEntries; + list g_deletedEntries; + + Entry TransferStrToKyEntry(const string &key, const string &value) + { + Entry entry; + entry.key.resize(key.size()); + entry.key.assign(key.begin(), key.end()); + entry.value.resize(value.size()); + entry.value.assign(value.begin(), value.end()); + return entry; + } + + void TestFunc(const KvDBCommitNotifyData &data) + { + g_testFuncCalled = true; + int errCode; + g_insertedEntries = data.GetInsertedEntries(errCode); + ASSERT_EQ(errCode, E_OK); + g_updatedEntries = data.GetUpdatedEntries(errCode); + ASSERT_EQ(errCode, E_OK); + g_deletedEntries = data.GetDeletedEntries(errCode); + ASSERT_EQ(errCode, E_OK); + LOGI("Insert:%zu, update:%zu, delete:%zu", g_insertedEntries.size(), g_updatedEntries.size(), + g_deletedEntries.size()); + return; + } + + bool CompairEntryList(const list &entryList1, const list &entryList2) + { + bool result = true; + EXPECT_EQ(entryList1.size(), entryList2.size()); + if (entryList1.size() != entryList2.size()) { + return false; + } + for (const auto &entry1 : entryList1) { + result = false; + for (const auto &entry2 : entryList2) { + if (entry1.key != entry2.key) { + continue; + } + if (entry1.value == entry2.value) { + result = true; + break; + } + cout << "entry1.key: "; + for (const auto &character : entry1.key) { + cout << character; + } + cout << endl; + cout << "entry2.key: "; + for (const auto &character : entry2.key) { + cout << character; + } + cout << endl; + cout << "entry1.value: "; + for (const auto &character : entry1.value) { + cout << character; + } + cout << endl; + cout << "entry2.value: "; + for (const auto &character : entry2.value) { + cout << character; + } + cout << endl; + break; + } + } + return result; + } + + void TestAndClearCallbackResult(bool isCallbackCalled, const list &expectedInsertedEntries, + const list &expectedUpdatedEntries, const list &expectedDeletedEntries) + { + EXPECT_EQ(g_testFuncCalled, isCallbackCalled); + if (g_testFuncCalled) { + EXPECT_EQ(CompairEntryList(g_insertedEntries, expectedInsertedEntries), true); + EXPECT_EQ(CompairEntryList(g_updatedEntries, expectedUpdatedEntries), true); + EXPECT_EQ(CompairEntryList(g_deletedEntries, expectedDeletedEntries), true); + } + // clear result + g_testFuncCalled = false; + g_insertedEntries.resize(0); + g_updatedEntries.resize(0); + g_deletedEntries.resize(0); + } + + void PreDataforOperation(const Entry &entry, bool isLocalPutRegisted, bool isPutRegisted, list &entries) + { + IOption opt; + entries.push_back(entry); + // test local insert + opt.dataType = IOption::LOCAL_DATA; + g_singleVerNaturaStoreConnection->Put(opt, entry.key, entry.value); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + TestAndClearCallbackResult(isLocalPutRegisted, entries, g_emptyEntries, g_emptyEntries); + // test local update + g_singleVerNaturaStoreConnection->Put(opt, entry.key, entry.value); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + TestAndClearCallbackResult(isLocalPutRegisted, g_emptyEntries, entries, g_emptyEntries); + // test local delete + g_singleVerNaturaStoreConnection->Delete(opt, entry.key); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + TestAndClearCallbackResult(isLocalPutRegisted, g_emptyEntries, g_emptyEntries, entries); + + // test insert + opt.dataType = IOption::SYNC_DATA; + g_singleVerNaturaStoreConnection->Put(opt, entry.key, entry.value); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + TestAndClearCallbackResult(isPutRegisted, entries, g_emptyEntries, g_emptyEntries); + + // test update + g_singleVerNaturaStoreConnection->Put(opt, entry.key, entry.value); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + TestAndClearCallbackResult(isPutRegisted, g_emptyEntries, entries, g_emptyEntries); + // test delete + g_singleVerNaturaStoreConnection->Delete(opt, entry.key); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + TestAndClearCallbackResult(isPutRegisted, g_emptyEntries, g_emptyEntries, entries); + } + + void TestForOperation(const Entry &entry, bool isLocalPutRegisted, bool isPutRegisted, bool isSyncRegisted) + { + list entries; + entries.push_back(entry); + + // test sync insert + Timestamp time; + g_singleVerNaturaStore->GetMaxTimestamp(time); + + DataItem dataItem; + dataItem.key = entry.key; + dataItem.value = entry.value; + dataItem.timestamp = time + 1; + dataItem.writeTimestamp = dataItem.timestamp; + dataItem.flag = 0; + vector insertDataItems; + insertDataItems.push_back(dataItem); + int result = DistributedDBToolsUnitTest::PutSyncDataTest(g_singleVerNaturaStore, insertDataItems, "deviceB"); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + ASSERT_EQ(result, E_OK); + TestAndClearCallbackResult(isSyncRegisted, entries, g_emptyEntries, g_emptyEntries); + // test sync update + vector updateDataItems; + dataItem.timestamp++; + dataItem.writeTimestamp = dataItem.timestamp; + updateDataItems.push_back(dataItem); + result = DistributedDBToolsUnitTest::PutSyncDataTest(g_singleVerNaturaStore, updateDataItems, "deviceB"); + + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + ASSERT_EQ(result, E_OK); + TestAndClearCallbackResult(isSyncRegisted, g_emptyEntries, entries, g_emptyEntries); + // test sync delete + vector deleteDataItems; + DataItem dataItem1 = dataItem; + dataItem1.timestamp++; + dataItem1.writeTimestamp = dataItem1.timestamp; + dataItem1.flag = 1; + DistributedDBToolsUnitTest::CalcHash(dataItem.key, dataItem1.key); + deleteDataItems.push_back(dataItem1); + result = DistributedDBToolsUnitTest::PutSyncDataTest(g_singleVerNaturaStore, deleteDataItems, "deviceB"); + + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + ASSERT_EQ(result, E_OK); + TestAndClearCallbackResult(isSyncRegisted, g_emptyEntries, g_emptyEntries, entries); + } +} + +class DistributedDBStorageRegisterObserverTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBStorageRegisterObserverTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + if (IKvDBFactory::GetCurrent() == nullptr) { + IKvDBFactory *factory = new (std::nothrow) DefaultFactory(); + ASSERT_NE(factory, nullptr); + if (factory == nullptr) { + LOGE("failed to new DefaultFactory!"); + return; + } + IKvDBFactory::Register(factory); + g_createFactory = true; + } + // prepare test entries + g_entry1 = TransferStrToKyEntry(g_keyStr1, g_valueStr1); + g_entry2 = TransferStrToKyEntry(g_keyStr2, g_valueStr2); + g_entry3 = TransferStrToKyEntry(g_keyStr3, g_valueStr3); + g_entry4 = TransferStrToKyEntry(g_keyStr4, g_valueStr4); + g_entry5 = TransferStrToKyEntry(g_keyStr5, g_valueStr5); + g_entry6 = TransferStrToKyEntry(g_keyStr6, g_valueStr6); + g_oldEntry3 = TransferStrToKyEntry(g_keyStr3, g_oldValueStr3); + g_oldEntry4 = TransferStrToKyEntry(g_keyStr4, g_oldValueStr4); +} + +void DistributedDBStorageRegisterObserverTest::TearDownTestCase(void) +{ + if (g_createFactory) { + if (IKvDBFactory::GetCurrent() != nullptr) { + delete IKvDBFactory::GetCurrent(); + IKvDBFactory::Register(nullptr); + } + } + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBStorageRegisterObserverTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + IKvDBFactory *factory = IKvDBFactory::GetCurrent(); + ASSERT_NE(factory, nullptr); + if (factory == nullptr) { + LOGE("failed to get DefaultFactory!"); + return; + } + + g_singleVerNaturaStore = new (std::nothrow) SQLiteSingleVerNaturalStore(); + ASSERT_NE(g_singleVerNaturaStore, nullptr); + if (g_singleVerNaturaStore == nullptr) { + return; + } + + KvDBProperties property; + property.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + property.SetStringProp(KvDBProperties::STORE_ID, "TestGeneralNB"); + property.SetStringProp(KvDBProperties::IDENTIFIER_DIR, "TestGeneralNB"); + property.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + int errCode = g_singleVerNaturaStore->Open(property); + ASSERT_EQ(errCode, E_OK); + if (errCode != E_OK) { + g_singleVerNaturaStore = nullptr; + return; + } + + g_singleVerNaturaStoreConnection = g_singleVerNaturaStore->GetDBConnection(errCode); + ASSERT_EQ(errCode, E_OK); + ASSERT_NE(g_singleVerNaturaStoreConnection, nullptr); +} + +void DistributedDBStorageRegisterObserverTest::TearDown(void) +{ + if (g_singleVerNaturaStoreConnection != nullptr) { + g_singleVerNaturaStoreConnection->Close(); + } + std::string identifierName; + g_singleVerNaturaStore->DecObjRef(g_singleVerNaturaStore); + identifierName = DBCommon::TransferStringToHex("TestGeneralNB"); + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir + "/" + identifierName + "/" + DBConstant::SINGLE_SUB_DIR); +} + +/** + * @tc.name: RegisterObserver001 + * @tc.desc: Register a NULL pointer as an observer + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver001, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Register a null pointer to subscribe to the database. + * Check whether the registration is successful. + * @tc.expected: step1/2. Returns INVALID_ARGS. + */ + int result; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_PUT_EVENT), g_entry1.key, nullptr, result); + EXPECT_EQ(result, -E_INVALID_ARGS); + EXPECT_EQ(handle, nullptr); + + /** + * @tc.steps: step3/4. UnRegister a null pointer to subscribe to the database. + * Check whether the unregistration is successful. + * @tc.expected: step3/4. Returns INVALID_ARGS. + */ + result = g_singleVerNaturaStoreConnection->UnRegisterObserver(nullptr); + EXPECT_EQ(result, -E_INVALID_ARGS); + return; +} + +/** + * @tc.name: RegisterObserver002 + * @tc.desc: Register an observer for the local database change of a specified key + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver002, TestSize.Level1) +{ + int result; + /** + * @tc.steps: step1/2. Register a null pointer to subscribe to the database. + * Check whether the registration is successful. + * @tc.expected: step1/2. Returns INVALID_ARGS. + */ + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_LOCAL_PUT_EVENT), g_entry1.key, TestFunc, result); + EXPECT_EQ(result, E_OK); + EXPECT_NE(handle, nullptr); + + /** + * @tc.steps: step3/4/5/6. Register an observer for the local database change of a specified key + */ + TestForOperation(g_entry1, true, false, false); + TestForOperation(g_entry2, false, false, false); + + /** + * @tc.steps: step7/8. UnRegister the subscribe to the database. + * Check whether the unregistration is successful. + * @tc.expected: step7/8. Returns E_OK. + */ + result = g_singleVerNaturaStoreConnection->UnRegisterObserver(handle); + EXPECT_EQ(result, E_OK); + + /** + * @tc.steps: step9. Repeat step3/5 + * @tc.expected: step9. No callback. + */ + TestForOperation(g_entry1, false, false, false); + TestForOperation(g_entry2, false, false, false); + return; +} + +/** + * @tc.name: RegisterObserver003 + * @tc.desc: Register an observer for the local sync database change of a specified key. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver003, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Register a null pointer to subscribe to the database. + * Check whether the registration is successful. + * @tc.expected: step1/2. Returns INVALID_ARGS. + */ + int result; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_PUT_EVENT), g_entry1.key, TestFunc, result); + EXPECT_EQ(result, E_OK); + EXPECT_NE(handle, nullptr); + + /** + * @tc.steps: step3/4/5/6. Register an observer for the local sync database change of a specified key. + */ + TestForOperation(g_entry1, false, true, false); + TestForOperation(g_entry2, false, false, false); + + /** + * @tc.steps: step7/8. UnRegister the subscribe to the database. + * Check whether the unregistration is successful. + * @tc.expected: step7/8. Returns E_OK. + */ + result = g_singleVerNaturaStoreConnection->UnRegisterObserver(handle); + EXPECT_EQ(result, E_OK); + + /** + * @tc.steps: step9. Repeat step3/5 + * @tc.expected: step9. No callback. + */ + TestForOperation(g_entry1, false, false, false); + TestForOperation(g_entry2, false, false, false); + return; +} + +/** + * @tc.name: RegisterObserver004 + * @tc.desc: Register an observer for the remote sync database change of a specified key. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver004, TestSize.Level1) +{ + int result; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_SYNC_EVENT), g_entry1.key, TestFunc, result); + EXPECT_EQ(result, E_OK); + EXPECT_NE(handle, nullptr); + TestForOperation(g_entry1, false, false, true); + list entries1; + PreDataforOperation(g_entry1, false, false, entries1); + TestForOperation(g_entry2, false, false, false); + list entries2; + PreDataforOperation(g_entry2, false, false, entries2); + result = g_singleVerNaturaStoreConnection->UnRegisterObserver(handle); + EXPECT_EQ(result, E_OK); + TestForOperation(g_entry1, false, false, false); + TestForOperation(g_entry2, false, false, false); + return; +} + +/** + * @tc.name: RegisterObserver005 + * @tc.desc: Register an observer for the sync database change of a specified key. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver005, TestSize.Level1) +{ + int result; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_PUT_EVENT) | + static_cast(SQLITE_GENERAL_NS_SYNC_EVENT), g_entry1.key, TestFunc, result); + EXPECT_EQ(result, E_OK); + EXPECT_NE(handle, nullptr); + TestForOperation(g_entry1, false, true, true); + TestForOperation(g_entry2, false, false, false); + result = g_singleVerNaturaStoreConnection->UnRegisterObserver(handle); + EXPECT_EQ(result, E_OK); + TestForOperation(g_entry1, false, false, false); + TestForOperation(g_entry2, false, false, false); + return; +} + +/** + * @tc.name: RegisterObserver006 + * @tc.desc: Register an observer for the local database change of any key. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver006, TestSize.Level1) +{ + int result; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_LOCAL_PUT_EVENT), g_emptyKey, TestFunc, result); + EXPECT_EQ(result, E_OK); + EXPECT_NE(handle, nullptr); + TestForOperation(g_entry1, true, false, false); + TestForOperation(g_entry2, true, false, false); + result = g_singleVerNaturaStoreConnection->UnRegisterObserver(handle); + EXPECT_EQ(result, E_OK); + TestForOperation(g_entry1, false, false, false); + TestForOperation(g_entry2, false, false, false); + return; +} + +/** + * @tc.name: RegisterObserver007 + * @tc.desc: Register an observer for the local sync database change of any key. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver007, TestSize.Level1) +{ + int result; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_PUT_EVENT), g_emptyKey, TestFunc, result); + EXPECT_EQ(result, E_OK); + EXPECT_NE(handle, nullptr); + TestForOperation(g_entry1, false, true, false); + TestForOperation(g_entry2, false, true, false); + result = g_singleVerNaturaStoreConnection->UnRegisterObserver(handle); + EXPECT_EQ(result, E_OK); + TestForOperation(g_entry1, false, false, false); + TestForOperation(g_entry2, false, false, false); + return; +} + +/** + * @tc.name: RegisterObserver008 + * @tc.desc: Register an observer for the remote sync database change of any key. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver008, TestSize.Level1) +{ + int result; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_SYNC_EVENT), g_emptyKey, TestFunc, result); + EXPECT_EQ(result, E_OK); + EXPECT_NE(handle, nullptr); + TestForOperation(g_entry1, false, false, true); + TestForOperation(g_entry2, false, false, true); + result = g_singleVerNaturaStoreConnection->UnRegisterObserver(handle); + EXPECT_EQ(result, E_OK); + TestForOperation(g_entry1, false, false, false); + TestForOperation(g_entry2, false, false, false); + return; +} + +/** + * @tc.name: RegisterObserver009 + * @tc.desc: Register an observer for the sync database change of any key. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver009, TestSize.Level1) +{ + int result; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_PUT_EVENT) | + static_cast(SQLITE_GENERAL_NS_SYNC_EVENT), g_emptyKey, TestFunc, result); + EXPECT_EQ(result, E_OK); + EXPECT_NE(handle, nullptr); + TestForOperation(g_entry1, false, true, true); + TestForOperation(g_entry2, false, true, true); + result = g_singleVerNaturaStoreConnection->UnRegisterObserver(handle); + EXPECT_EQ(result, E_OK); + TestForOperation(g_entry1, false, false, false); + TestForOperation(g_entry2, false, false, false); + return; +} + +/** + * @tc.name: RegisterObserver010 + * @tc.desc: Register an observer for the local sync database change and the local database change of a specified key. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver010, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Register an observer for the local sync database change + * and the local database change of a specified key. Check register result. + * @tc.expected: step1/2. Returns E_NOT_SUPPORT. + */ + int result; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_PUT_EVENT) | + static_cast(SQLITE_GENERAL_NS_LOCAL_PUT_EVENT), g_entry1.key, TestFunc, result); + EXPECT_EQ(result, -E_NOT_SUPPORT); + EXPECT_EQ(handle, nullptr); + return; +} + +/** + * @tc.name: RegisterObserver011 + * @tc.desc: Register an observer for the remote sync database change and the local database change of a specified key + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver011, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Register an observer for the remote sync database change + * and the local database change of a specified key. Check register result. + * @tc.expected: step1/2. Returns E_NOT_SUPPORT. + */ + int result; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_SYNC_EVENT) | + static_cast(SQLITE_GENERAL_NS_LOCAL_PUT_EVENT), g_entry1.key, TestFunc, result); + EXPECT_EQ(result, -E_NOT_SUPPORT); + EXPECT_EQ(handle, nullptr); + return; +} + +/** + * @tc.name: RegisterObserver012 + * @tc.desc: Register an observer for the local sync database change and the local database change of any key. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver012, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Register an observer for the local sync database change + * and the local database change of any key. Check register result. + * @tc.expected: step1/2. Returns E_NOT_SUPPORT. + */ + int result; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_PUT_EVENT) | + static_cast(SQLITE_GENERAL_NS_LOCAL_PUT_EVENT), g_emptyKey, TestFunc, result); + EXPECT_EQ(result, -E_NOT_SUPPORT); + EXPECT_EQ(handle, nullptr); + return; +} + +/** + * @tc.name: RegisterObserver013 + * @tc.desc: Register an observer for the remote sync database change and the local database change of any key. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver013, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Register an observer for the remote sync database change + * and the local database change of any key. Check register result. + * @tc.expected: step1/2. Returns E_NOT_SUPPORT. + */ + int result; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_SYNC_EVENT) | + static_cast(SQLITE_GENERAL_NS_LOCAL_PUT_EVENT), g_emptyKey, TestFunc, result); + EXPECT_EQ(result, -E_NOT_SUPPORT); + EXPECT_EQ(handle, nullptr); + return; +} + +static void PreSyncDataForRegisterObserver014(Timestamp time, vector &dataItems) +{ + // sync data + DataItem dataItem = {g_entry1.key, g_entry1.value, .timestamp = ++time, .flag = 1}; + dataItem.writeTimestamp = dataItem.timestamp; + DistributedDBToolsUnitTest::CalcHash(g_entry1.key, dataItem.key); + dataItems.push_back(dataItem); + + DistributedDBToolsUnitTest::CalcHash(g_entry2.key, dataItem.key); + dataItem.value = g_entry2.value; + dataItems.push_back(dataItem); + + dataItem.key = g_entry3.key; + dataItem.value = g_entry3.value; + dataItem.flag = 0; + dataItems.push_back(dataItem); + + dataItem.key = g_entry4.key; + dataItem.value = g_entry4.value; + dataItems.push_back(dataItem); + + dataItem.key = g_entry5.key; + dataItem.value = g_entry5.value; + dataItems.push_back(dataItem); + + dataItem.key = g_entry6.key; + dataItem.value = g_entry6.value; + dataItems.push_back(dataItem); +} + +/** + * @tc.name: RegisterObserver014 + * @tc.desc: Sync multiple records to the sync database + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver014, TestSize.Level1) +{ + /** + * @tc.steps: step1. Write the per data records to the synchronization database by Put. + */ + IOption opt = {.dataType = IOption::SYNC_DATA}; + g_singleVerNaturaStoreConnection->Put(opt, g_entry1.key, g_entry1.value); + g_singleVerNaturaStoreConnection->Put(opt, g_entry2.key, g_entry2.value); + g_singleVerNaturaStoreConnection->Put(opt, g_oldEntry3.key, g_oldEntry3.value); + g_singleVerNaturaStoreConnection->Put(opt, g_oldEntry4.key, g_oldEntry4.value); + // get max time + Timestamp time; + g_singleVerNaturaStore->GetMaxTimestamp(time); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + /** + * @tc.steps: step2. Register the observer to the sync database + * from the remote end without specifying the key. + */ + int result = E_OK; + KvDBObserverHandle* handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_SYNC_EVENT), g_emptyKey, TestFunc, result); + EXPECT_EQ(result, E_OK); + EXPECT_NE(handle, nullptr); + + // sync data + vector dataItems; + PreSyncDataForRegisterObserver014(time, dataItems); + + /** + * @tc.steps: step3. A batch write operation by PutSyncData. + * The key1 and key2 records are deleted, and the key3 and key4 records are recorded. + */ + result = DistributedDBToolsUnitTest::PutSyncDataTest(g_singleVerNaturaStore, dataItems, "deviceB"); + + ASSERT_EQ(result, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME)); + + // test result + list deletedEntries; + deletedEntries.push_back(g_entry1); + deletedEntries.push_back(g_entry2); + list updatedEntries; + updatedEntries.push_back(g_entry3); + updatedEntries.push_back(g_entry4); + list insertedEntries; + insertedEntries.push_back(g_entry5); + insertedEntries.push_back(g_entry6); + + /** + * @tc.steps: step4. Callback is triggered, the Put and Delete data is obtained from the observer. + * @tc.expected: step4. The data is consistent with the data to be written. + */ + TestAndClearCallbackResult(true, insertedEntries, updatedEntries, deletedEntries); + + /** + * @tc.steps: step3. unregister observer + */ + result = g_singleVerNaturaStoreConnection->UnRegisterObserver(handle); + EXPECT_EQ(result, E_OK); + return; +} +/** + * @tc.name: RegisterObserver015 + * @tc.desc: Sync multiple records to the sync database, and remove them. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: liujialei + */ +HWTEST_F(DistributedDBStorageRegisterObserverTest, RegisterObserver015, TestSize.Level1) +{ + /** + * @tc.steps: step1. Generate the random entry. + */ + vector dataItems; + static const unsigned long number = 2; // 2 entries + for (unsigned long i = 0; i < number; i++) { + DataItem item; + DistributedDBToolsUnitTest::GetRandomKeyValue(item.key, DBConstant::MAX_KEY_SIZE); + DistributedDBToolsUnitTest::GetRandomKeyValue(item.value, DBConstant::MAX_VALUE_SIZE); + dataItems.push_back(std::move(item)); + } + /** + * @tc.steps: step2. Put the entries through the syncer interface. + */ + int result = DistributedDBToolsUnitTest::PutSyncDataTest(g_singleVerNaturaStore, dataItems, "deviceB"); + dataItems.clear(); + dataItems.shrink_to_fit(); + ASSERT_EQ(result, E_OK); + /** + * @tc.steps: step3. Register the observer. + */ + KvDBObserverHandle *handle = g_singleVerNaturaStoreConnection->RegisterObserver( + static_cast(SQLITE_GENERAL_NS_SYNC_EVENT), g_emptyKey, TestFunc, result); + EXPECT_EQ(result, E_OK); + ASSERT_NE(handle, nullptr); + /** + * @tc.steps: step4. Remove the data from "deviceB". + * @tc.expected: step4. Return E_OK and the observer data has delete entries. + */ + g_deletedEntries.clear(); + result = g_singleVerNaturaStore->RemoveDeviceData("deviceB", true); + ASSERT_EQ(result, E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(OBSERVER_SLEEP_TIME * number)); + ASSERT_NE(g_deletedEntries.empty(), true); + /** + * @tc.steps: step5. unregister observer + */ + result = g_singleVerNaturaStoreConnection->UnRegisterObserver(handle); + EXPECT_EQ(result, E_OK); + return; +} diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_resultset_and_json_optimize.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_resultset_and_json_optimize.cpp new file mode 100644 index 00000000..4ee0ecbb --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_resultset_and_json_optimize.cpp @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 OMIT_JSON +#include + +#include "db_common.h" +#include "db_constant.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "kvdb_manager.h" +#include "log_print.h" +#include "platform_specific.h" +#include "res_finalizer.h" +#include "sqlite_single_ver_natural_store_connection.h" +#include "sqlite_single_ver_result_set.h" +#include "sqlite_utils.h" +#include "store_types.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { + const int INSERT_NUMBER = 10; + const Key EMPTY_KEY; + const SQLiteSingleVerResultSet::Option OPTION = {ResultSetCacheMode::CACHE_ENTRY_ID_ONLY, 1}; + + string g_testDir; + string g_identifier; + SQLiteSingleVerNaturalStore *g_store = nullptr; + SQLiteSingleVerNaturalStoreConnection *g_connection = nullptr; + KvDBProperties g_Property; + const string STORE_ID = STORE_ID_SYNC; +} +class DistributedDBStorageResultAndJsonOptimizeTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBStorageResultAndJsonOptimizeTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + std::string origIdentifier = USER_ID + "-" + APP_ID + "-" + STORE_ID; + std::string identifier = DBCommon::TransferHashString(origIdentifier); + g_identifier = DBCommon::TransferStringToHex(identifier); + std::string dir = g_testDir + g_identifier + "/" + DBConstant::SINGLE_SUB_DIR; + DIR *dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } + g_Property.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + g_Property.SetStringProp(KvDBProperties::STORE_ID, STORE_ID); + g_Property.SetStringProp(KvDBProperties::IDENTIFIER_DIR, g_identifier); + g_Property.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); +} + +void DistributedDBStorageResultAndJsonOptimizeTest::TearDownTestCase(void) +{ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +void DistributedDBStorageResultAndJsonOptimizeTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: 1. Create a SQLiteSingleVerNaturalStore. + * 2. Set the ResultSet cache mode to CACHE_ENTRY_ID_ONLY. + * 3. Put 10 records. + */ + g_store = new (std::nothrow) SQLiteSingleVerNaturalStore; + ASSERT_NE(g_store, nullptr); + ASSERT_EQ(g_store->Open(g_Property), E_OK); + + int errCode = E_OK; + g_connection = static_cast(g_store->GetDBConnection(errCode)); + ASSERT_NE(g_connection, nullptr); + g_store->DecObjRef(g_store); + EXPECT_EQ(errCode, E_OK); + + IOption option; + option.dataType = IOption::SYNC_DATA; + g_connection->Clear(option); + Key insertKey; + ASSERT_EQ(g_connection->StartTransaction(), E_OK); + for (int i = 1; i < INSERT_NUMBER + 1; i++) { + insertKey.clear(); + insertKey.push_back(i); + ASSERT_EQ(g_connection->Put(option, insertKey, VALUE_1), OK); + } + ASSERT_EQ(g_connection->Commit(), E_OK); +} + +void DistributedDBStorageResultAndJsonOptimizeTest::TearDown(void) +{ + /** + * @tc.teardown: Release the SQLiteSingleVerNaturalStore. + */ + if (g_connection != nullptr) { + g_connection->Close(); + g_connection = nullptr; + } + + g_store = nullptr; + KvDBManager::RemoveDatabase(g_Property); +} + +/** + * @tc.name: ResultSetOpen001 + * @tc.desc: Test the SQLiteSingleVerResultSet Open function + * @tc.type: FUNC + * @tc.require: AR000F3OP0 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBStorageResultAndJsonOptimizeTest, ResultSetOpen001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create a SQLiteSingleVerResultSet. + */ + std::unique_ptr resultSet1 = + std::make_unique(g_store, EMPTY_KEY, OPTION); + + /** + * @tc.steps: step2. Call SQLiteSingleVerResultSet.Open with parameter true. + * @tc.expected: step2. Expect return E_OK. + */ + EXPECT_EQ(resultSet1->Open(true), E_OK); + + /** + * @tc.steps: step3. Create a SQLiteSingleVerResultSet. + */ + std::unique_ptr resultSet2 = + std::make_unique(g_store, EMPTY_KEY, OPTION); + + /** + * @tc.steps: step4. Call SQLiteSingleVerResultSet.Open with parameter false. + * @tc.expected: step4. Expect return E_OK. + */ + EXPECT_EQ(resultSet2->Open(false), E_OK); + + /** + * @tc.steps: step5. Close all ResultSet. + */ + resultSet1->Close(); + resultSet2->Close(); +} + +/** + * @tc.name: ResultSetGetCount001 + * @tc.desc: Test the SQLiteSingleVerResultSet GetCount function. + * @tc.type: FUNC + * @tc.require: AR000F3OP0 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBStorageResultAndJsonOptimizeTest, ResultSetGetCount001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create a SQLiteSingleVerResultSet. + */ + std::unique_ptr resultSet = + std::make_unique(g_store, EMPTY_KEY, OPTION); + + /** + * @tc.steps: step2. Call SQLiteSingleVerResultSet.Open + * @tc.expected: step2. Expect return E_OK.Gits + */ + EXPECT_EQ(resultSet->Open(false), E_OK); + + /** + * @tc.steps: step2. Call SQLiteSingleVerResultSet.GetCount + * @tc.expected: step2. Expect return INSERT_NUMBER. + */ + EXPECT_EQ(resultSet->GetCount(), INSERT_NUMBER); + + /** + * @tc.steps: step3. Close the ResultSet. + */ + resultSet->Close(); +} + +/** + * @tc.name: ResultSetMoveTo001 + * @tc.desc: Test the SQLiteSingleVerResultSet MoveTo And GetPosition function. + * @tc.type: FUNC + * @tc.require: AR000F3OP0 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBStorageResultAndJsonOptimizeTest, ResultSetMoveTo001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create a SQLiteSingleVerResultSet. + */ + std::unique_ptr resultSet = + std::make_unique(g_store, EMPTY_KEY, OPTION); + + /** + * @tc.steps: step2. Call SQLiteSingleVerResultSet.Open. + * @tc.expected: step2. Expect return E_OK. + */ + EXPECT_EQ(resultSet->Open(false), E_OK); + + /** + * @tc.steps: step3. Call SQLiteSingleVerResultSet MoveTo INSERT_NUMBER - 1 + * @tc.expected: step3. Expect return E_OK. + */ + EXPECT_EQ(resultSet->MoveTo(INSERT_NUMBER - 1), E_OK); + + /** + * @tc.steps: step4. Call SQLiteSingleVerResultSet GetPosition + * @tc.expected: step5. Expect return INSERT_NUMBER - 1. + */ + EXPECT_EQ(resultSet->GetPosition(), INSERT_NUMBER - 1); + + /** + * @tc.steps: step5. Call SQLiteSingleVerResultSet MoveTo INSERT_NUMBER + * @tc.expected: step5. Expect return -E_INVALID_ARGS. + */ + EXPECT_EQ(resultSet->MoveTo(INSERT_NUMBER), -E_INVALID_ARGS); + + /** + * @tc.steps: step6. Call SQLiteSingleVerResultSet GetPosition + * @tc.expected: step6. Expect return INSERT_NUMBER. + */ + EXPECT_EQ(resultSet->GetPosition(), INSERT_NUMBER); + + /** + * @tc.steps: step7. Call SQLiteSingleVerResultSet MoveTo -1 + * @tc.expected: step7. Expect return E_INVALID_ARGS. + */ + EXPECT_EQ(resultSet->MoveTo(-1), -E_INVALID_ARGS); + + /** + * @tc.steps: step8. Call SQLiteSingleVerResultSet GetPosition + * @tc.expected: step8. Expect return 0. + */ + EXPECT_EQ(resultSet->GetPosition(), -1); + + /** + * @tc.steps: step9. Call SQLiteSingleVerResultSet MoveTo 0 + * @tc.expected: step9. Expect return E_OK. + */ + EXPECT_EQ(resultSet->MoveTo(0), E_OK); + + /** + * @tc.steps: step10. Call SQLiteSingleVerResultSet GetPosition + * @tc.expected: step10. Expect return 0. + */ + EXPECT_EQ(resultSet->GetPosition(), 0); + + /** + * @tc.steps: step11. Close the ResultSet. + */ + resultSet->Close(); +} + +/** + * @tc.name: ResultSetGetEntry001 + * @tc.desc: Test the SQLiteSingleVerResultSet GetEntry function. + * @tc.type: FUNC + * @tc.require: AR000F3OP0 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBStorageResultAndJsonOptimizeTest, ResultSetGetEntry001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create a SQLiteSingleVerResultSet. + */ + std::unique_ptr resultSet = + std::make_unique(g_store, EMPTY_KEY, OPTION); + + /** + * @tc.steps: step2. Call SQLiteSingleVerResultSet.Open + * @tc.expected: step2. Expect return E_OK. + */ + EXPECT_EQ(resultSet->Open(false), E_OK); + + /** + * @tc.steps: step2. Call SQLiteSingleVerResultSet MoveTo 0 And GetEntry + * @tc.expected: step2. Expect return E_OK. + */ + Entry entry; + ASSERT_EQ(resultSet->MoveTo(0), E_OK); + EXPECT_EQ(resultSet->GetEntry(entry), E_OK); + + /** + * @tc.expected: step2. Expect return Key == { 1 }, value == VALUE_1. + */ + const Key key = { 1 }; + EXPECT_EQ(entry.key, key); + EXPECT_EQ(entry.value, VALUE_1); + + /** + * @tc.steps: step3. Close the ResultSet. + */ + resultSet->Close(); +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_single_ver_natural_store_testcase.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_single_ver_natural_store_testcase.cpp new file mode 100644 index 00000000..4ca1b1b4 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_single_ver_natural_store_testcase.cpp @@ -0,0 +1,1902 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_storage_single_ver_natural_store_testcase.h" + +#include "generic_single_ver_kv_entry.h" +#include "time_helper.h" + +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { + const int MAX_TEST_KEY_SIZE = 1024; + const int MAX_TEST_VAL_SIZE = 4194304; + + // select result index for the item for sync database + const int SYNC_RES_KEY_INDEX = 0; + const int SYNC_RES_VAL_INDEX = 1; + const int SYNC_RES_TIME_INDEX = 2; + const int SYNC_RES_FLAG_INDEX = 3; + const int SYNC_RES_HASH_KEY_INDEX = 6; + + const std::string SYNC_DATA_DEFAULT_SQL = "select * from SYNC_DATA;"; +} + +/** + * @tc.name: GetSyncData001 + * @tc.desc: To test the function of querying the data in the time stamp range in the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData001(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + /** + * @tc.steps:step1. Obtain the data within the time stamp range + * through the GetSyncData(A, C) interface of the NaturalStore, where AGetMaxTimestamp(timeBegin); + Key key1; + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(key1); + DistributedDBToolsUnitTest::GetRandomKeyValue(value1); + + EXPECT_EQ(connection->Put(option, key1, value1), E_OK); + Timestamp timeEnd; + store->GetMaxTimestamp(timeEnd); + EXPECT_GT(timeEnd, timeBegin); + + std::vector vect; + ContinueToken token = nullptr; + SyncInputArg inputArg(timeBegin, timeEnd + 1, 1024); // no more than 1024 + EXPECT_EQ(DistributedDBToolsUnitTest::GetSyncDataTest(inputArg, store, vect, token), E_OK); + + EXPECT_EQ(token, nullptr); + DataItem item = {key1, value1, 0, 0}; + EXPECT_EQ(DistributedDBToolsUnitTest::IsItemValueExist(item, vect), true); +} + +/** + * @tc.name: GetSyncData002 + * @tc.desc: Test the function that the database does not query the data in the time stamp range. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData002(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + /** + * @tc.steps:step1. Obtain the data within the time stamp range + * through the GetSyncData(A, B) interface of the NaturalStore, + * where APut(option, key, value), E_OK); + Timestamp timestamp; + store->GetMaxTimestamp(timestamp); + + std::vector vect; + ContinueToken token = nullptr; + SyncInputArg inputArg(timestamp + 1, timestamp + 1000, 1024); // no more than 1024 + EXPECT_EQ(DistributedDBToolsUnitTest::GetSyncDataTest(inputArg, store, vect, token), E_OK); + + EXPECT_EQ(token, nullptr); + EXPECT_EQ(vect.size(), 0UL); +} + +/** + * @tc.name: GetSyncData003 + * @tc.desc: To test the function of querying data when the timestamp range + * in the data obtaining interface is invalid. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData003(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + /** + * @tc.steps:step1. Obtain the data within the time stamp range + * through the GetSyncData(A, B) interface of the NaturalStore, where A>B + * @tc.expected: step1. The value of GetSyncData is E_INVALID_ARG. + */ + IOption option; + option.dataType = IOption::SYNC_DATA; + Timestamp timeBegin = 1000; // random + Timestamp timeEnd = 700; // random + std::vector vect; + ContinueToken token = nullptr; + SyncInputArg inputArg1(timeBegin, timeEnd, MAX_TEST_VAL_SIZE); + EXPECT_EQ(DistributedDBToolsUnitTest::GetSyncDataTest(inputArg1, store, vect, token), -E_INVALID_ARGS); + + timeEnd = timeBegin; + SyncInputArg inputArg2(timeBegin, timeEnd, MAX_TEST_VAL_SIZE); + EXPECT_EQ(DistributedDBToolsUnitTest::GetSyncDataTest(inputArg2, store, vect, token), -E_INVALID_ARGS); + + store->GetMaxTimestamp(timeBegin); + Key key1; + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(key1); + DistributedDBToolsUnitTest::GetRandomKeyValue(value1); + + EXPECT_EQ(connection->Put(option, key1, value1), E_OK); + store->GetMaxTimestamp(timeEnd); + + SyncInputArg inputArg3(timeEnd, timeBegin, MAX_TEST_VAL_SIZE); + EXPECT_EQ(DistributedDBToolsUnitTest::GetSyncDataTest(inputArg3, store, vect, token), -E_INVALID_ARGS); + + EXPECT_EQ(token, nullptr); +} + +/** + * @tc.name: GetSyncData004 + * @tc.desc: To the test database Subcon reading, a large number of data records exist in the time stamp range. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData004(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + Key key; + Value value; + IOption option; + option.dataType = IOption::SYNC_DATA; + // The test assumes that there are ten data records + for (int i = 0; i < 10; i++) { + DistributedDBToolsUnitTest::GetRandomKeyValue(key, 100 + i); // random size + DistributedDBToolsUnitTest::GetRandomKeyValue(value, 9900 + i); // random size + EXPECT_EQ(connection->Put(option, key, value), E_OK); + } + + Timestamp timestamp = 0; + store->GetMaxTimestamp(timestamp); + + /** + * @tc.steps:step1. Obtain the data within the time stamp range + * through the GetSyncData(A, B) interface of the NaturalStore. + * @tc.expected: step1. Return E_GET_UNFINISHED. + */ + ContinueToken token = nullptr; + std::vector dataItems; + SyncInputArg inputArg(0, timestamp + 1, 30 * 1024); // 30k per block + EXPECT_EQ(DistributedDBToolsUnitTest::GetSyncDataTest(inputArg, store, dataItems, token), -E_UNFINISHED); + + EXPECT_NE(token, nullptr); + std::size_t countNum = dataItems.size(); + int count = 1; + do { + /** + * @tc.steps:step2. Continue to obtain data through the GetSyncDataNext() interface + * of the NaturalStore until the E_GET_FINISHED message is returned. + * @tc.expected: step2. When the GetSyncDataNext returns E_GET_FINISHED, + * the total number of obtained data is the number of inserted data and the data is consistent. + */ + dataItems.clear(); + int errCode = DistributedDBToolsUnitTest::GetSyncDataNextTest(store, 30 * 1024, dataItems, token); // 30k block + + countNum += dataItems.size(); + count++; + if (errCode == -E_UNFINISHED) { + continue; + } else if (errCode == -E_FINISHED || errCode == E_OK) { + break; + } else { + count = 0; + break; + } + } while (true); + EXPECT_EQ(token, nullptr); + EXPECT_EQ(countNum, 10UL); // 10 entries + EXPECT_EQ(count, 4); // 4 blocks +} + +/** + * @tc.name: GetSyncData005 + * @tc.desc: In the test database, if a large number of data records exist + * in the time stamp range, a packet is read successfully. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData005(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + Key key; + Value value; + IOption option; + option.dataType = IOption::SYNC_DATA; + for (int i = 0; i < 10; i++) { // 10 entries + DistributedDBToolsUnitTest::GetRandomKeyValue(key, 100 + i); // about 100 byte + DistributedDBToolsUnitTest::GetRandomKeyValue(value, 9900 + i); // about 9900 byte + EXPECT_EQ(connection->Put(option, key, value), E_OK); + } + + Timestamp timestamp = 0; + store->GetMaxTimestamp(timestamp); + + ContinueToken token = nullptr; + std::vector dataItems; + /** + * @tc.steps:step1. Obtain the data within the time stamp range + * through the GetSyncData(A, B) interface of the NaturalStore. + * @tc.expected: step1. The total size of all data in OK, dataItems is 99K. + */ + SyncInputArg inputArg(0, timestamp + 1, 100 * 1024); // for 100k + EXPECT_EQ(DistributedDBToolsUnitTest::GetSyncDataTest(inputArg, store, dataItems, token), E_OK); + + EXPECT_EQ(token, nullptr); + EXPECT_EQ(dataItems.size(), 10UL); +} + +/** + * @tc.name: GetSyncData006 + * @tc.desc: To test the function of reading data when the time stamp range in the database + * is greater than the value of blockSize. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData006(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key, MAX_TEST_KEY_SIZE); + DistributedDBToolsUnitTest::GetRandomKeyValue(value, MAX_TEST_VAL_SIZE); + + IOption option; + option.dataType = IOption::SYNC_DATA; + EXPECT_EQ(connection->Put(option, key, value), E_OK); + Timestamp timestamp = 0; + store->GetMaxTimestamp(timestamp); + + ContinueToken token = nullptr; + std::vector dataItems; + + /** + * @tc.steps:step1. Use the GetSyncData(A, B) interface of the NaturalStore + * and set blockSize to 50 kb to obtain the data within the time stamp range. + * @tc.expected: step1. The system returns E_GET_FINISHED. The size of the obtained data is 1 kb. + */ + SyncInputArg inputArg(0, timestamp + 1, 1000); // get size for 1k + EXPECT_EQ(DistributedDBToolsUnitTest::GetSyncDataTest(inputArg, store, dataItems, token), E_OK); + + EXPECT_EQ(token, nullptr); + DataItem item = {key, value, 0, 0}; + EXPECT_EQ(DistributedDBToolsUnitTest::IsItemValueExist(item, dataItems), true); +} + +/** + * @tc.name: PutSyncData001 + * @tc.desc: To test the function of synchronizing the new data of the remote device that synchronizes the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::PutSyncData001(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + Timestamp timeBegin; + store->GetMaxTimestamp(timeBegin); + Key key1; + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(key1, 13); // random size + DistributedDBToolsUnitTest::GetRandomKeyValue(value1, 20); // random size + + /** + * @tc.steps:step1/2. Set Ioption to synchronous data and insert a (key1, value1) data record by put interface. + */ + EXPECT_EQ(connection->Put(option, key1, value1), E_OK); + Timestamp timeEnd; + store->GetMaxTimestamp(timeEnd); + EXPECT_GT(timeEnd, timeBegin); + + DataItem item1; + std::vector vect; + item1.key = key1; + DistributedDBToolsUnitTest::GetRandomKeyValue(item1.value, 18); // random size + item1.timestamp = timeBegin; + item1.writeTimestamp = item1.timestamp; + item1.flag = 0; + vect.push_back(item1); + + /** + * @tc.steps:step3. Insert a (key1, value2!=value1, timestamp, false) data record + * through the PutSyncData interface. The value of timestamp is less than or equal + * to the value of timestamp. For Compare the timestamp to determine whether to synchronization data. + * @tc.expected: step3. Return OK. + */ + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps:step4. The Ioption is set to synchronize data + * through the Get interface to obtain the value data of the key1. + * @tc.expected: step4. Return OK.The obtained value is value1. + */ + Value valueRead; + EXPECT_EQ(connection->Get(option, key1, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value1), true); + + item1.timestamp = timeEnd + 1; + item1.writeTimestamp = item1.timestamp; + vect.clear(); + vect.push_back(item1); + + /** + * @tc.steps:step5. Insert a (key1, value3!=value1, timestamp, false) data record + * through the PutSyncData interface of the NaturalStore. The value of timestamp + * is greater than that of timestamp inserted in 2. + * @tc.expected: step5. Return OK. + */ + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps:step6. The Ioption is set to synchronize data through the Get interface + * to obtain the value data of the key1. + * @tc.expected: step6. Return OK. + */ + EXPECT_EQ(connection->Get(option, key1, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(item1.value, valueRead), true); + + DistributedDBToolsUnitTest::GetRandomKeyValue(item1.key, 35); // random size + DistributedDBToolsUnitTest::GetRandomKeyValue(item1.value, 47); // random size + vect.clear(); + vect.push_back(item1); + + /** + * @tc.steps:step7. Insert a (key2, value4) data record through the PutSyncData interface. + * @tc.expected: step7. Return OK. + */ + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps:step8. The Ioption is set to synchronize data + * through the Get interface to obtain the value data of the key2. + * @tc.expected: step8. Returns OK, and the obtained data is value4. + */ + EXPECT_EQ(connection->Get(option, item1.key, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(item1.value, valueRead), true); +} + +/** + * @tc.name: PutSyncData002 + * @tc.desc: To test the function of synchronizing data from the remote device + * to the local device after the data is deleted from the remote device. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::PutSyncData002(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + Timestamp timeBegin; + store->GetMaxTimestamp(timeBegin); + Key key1; + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(key1, 37); // random size + DistributedDBToolsUnitTest::GetRandomKeyValue(value1, 19); // random size + + /** + * @tc.steps:step1/2. Set Ioption to synchronous data and insert a (key1, value1) data record by put interface. + */ + EXPECT_EQ(connection->Put(option, key1, value1), E_OK); + Timestamp timeEnd; + store->GetMaxTimestamp(timeEnd); + EXPECT_GT(timeEnd, timeBegin); + + DataItem item1; + std::vector vect; + item1.key = key1; + DistributedDBToolsUnitTest::GetRandomKeyValue(item1.value, 18); // random size + item1.timestamp = timeBegin; + item1.writeTimestamp = item1.timestamp; + item1.flag = 1; + DistributedDBToolsUnitTest::CalcHash(key1, item1.key); + vect.push_back(item1); + /** + * @tc.steps:step3. Insert a (key1, value2!=value1, timestamp, false) data record + * through the PutSyncData interface. The value of timestamp is less than or equal + * to the value of timestamp. For Compare the timestamp to determine whether delete data. + * @tc.expected: step3. Return OK. + */ + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps:step4. The Ioption is set to synchronize data + * through the Get interface to obtain the value data of the key1. + * @tc.expected: step4. Return OK.The obtained value is value1. + */ + Value valueRead; + EXPECT_EQ(connection->Get(option, key1, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value1), true); + + item1.timestamp = timeEnd + 1; + item1.writeTimestamp = item1.timestamp; + vect.clear(); + vect.push_back(item1); + + /** + * @tc.steps:step5. Insert a (key1, value3!=value1, timestamp, false) data record + * through the PutSyncData interfac. The value of timestamp + * is greater than that of timestamp inserted in step2. + * @tc.expected: step5. Return OK. + */ + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps:step6. The Ioption is set to synchronize data through the Get interface + * to obtain the value data of the key1. + * @tc.expected: step6. Return E_NOT_FOUND. + */ + EXPECT_EQ(connection->Get(option, key1, valueRead), -E_NOT_FOUND); + + // put remote deleted data which not existed locally. + DistributedDBToolsUnitTest::GetRandomKeyValue(item1.key, 35); // random size + DistributedDBToolsUnitTest::GetRandomKeyValue(item1.value, 47); // random size + vect.clear(); + vect.push_back(item1); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + EXPECT_EQ(connection->Get(option, item1.key, valueRead), -E_NOT_FOUND); +} + +/** + * @tc.name: PutSyncData003 + * @tc.desc: To test the function of synchronizing the mixed data of the added + * and deleted data from the remote device to the local device. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::PutSyncData003(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + Timestamp timeBegin; + store->GetMaxTimestamp(timeBegin); + DataItem dataItem1; + DataItem dataItem2; + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem1.key, 23); // random size + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem2.key, 15); // random size + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem1.value); + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem2.value); + dataItem1.timestamp = timeBegin + 1; // ensure bigger timestamp + dataItem1.writeTimestamp = dataItem1.timestamp; + dataItem2.timestamp = timeBegin + 2; // ensure bigger timestamp + dataItem2.writeTimestamp = dataItem2.timestamp; + dataItem1.flag = dataItem2.flag = 0; + + /** + * @tc.steps:step1. Insert a data record (key1,value1 is not null) and (key2, value2 is not null) + * through the PutSyncData interface. + * @tc.expected: step1. Return OK. + */ + std::vector vect = {dataItem1, dataItem2}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps:step2. Set Ioption as the synchronization data to obtain the data of key1 and key2. + * @tc.expected: step2. The Get interface returns OK. The value of key1 is value1, + * and the value of key2 is value2. + */ + Value valueRead1, valueRead2; + EXPECT_EQ(connection->Get(option, dataItem1.key, valueRead1), E_OK); + EXPECT_EQ(connection->Get(option, dataItem2.key, valueRead2), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead1, dataItem1.value), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead2, dataItem2.value), true); + + /** + * @tc.steps:step3. Insert a (key3, value3) and delete the data of the (key1, value1). + * @tc.expected: step3. The PutSyncData returns OK. + */ + DataItem dataItem3 = dataItem1; + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem3.key, 38); // random size + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem3.value, 27); // random size + + DataItem dataItem4 = dataItem1; + dataItem4.flag = 1; + dataItem4.timestamp += 1; + dataItem4.writeTimestamp = dataItem4.timestamp; + DistributedDBToolsUnitTest::CalcHash(dataItem1.key, dataItem4.key); + vect = {dataItem4, dataItem3}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps:step4. Set Ioption to the synchronization data and obtain the data of key1, key2, and key3. + * @tc.expected: step4. Get key1 returns E_NOT_FOUND,Get key2. + * The value of OK,value is value2, the value of Get key3 is OK, + * and the value of value is value3. + */ + valueRead1.clear(); + valueRead2.clear(); + Value valueRead3; + EXPECT_EQ(connection->Get(option, dataItem1.key, valueRead1), -E_NOT_FOUND); + EXPECT_EQ(connection->Get(option, dataItem2.key, valueRead2), E_OK); + EXPECT_EQ(connection->Get(option, dataItem3.key, valueRead3), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead2, dataItem2.value), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead3, dataItem3.value), true); +} + +/** + * @tc.name: PutMetaData001 + * @tc.desc: Test metadata insertion and modification. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::PutMetaData001(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + TestMetaDataPutAndGet(store, connection); +} + +/** + * @tc.name: GetMetaData001 + * @tc.desc: To test the function of reading the metadata of a key in the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::GetMetaData001(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + /** + * @tc.steps:step1. Use GetMetaData in NaturalStore to obtain the value of key1. + * Check whether the value is the same as the value of value1. + * @tc.expected: step1. Return OK, and the value is the same as the value of value1. + */ + TestMetaDataPutAndGet(store, connection); +} + +/** + * @tc.name: DeleteMetaData001 + * @tc.desc: To test the function of deleting the metadata with prefix key in the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteMetaData001(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + /** + * @tc.steps:step1. Put 2 mete data with prefix key 'a', 2 meta data with prefix key 'b'. + And delete meta data with prefix key 'b'. + * @tc.expected: step1. Get all meta data and will get 2 data with prefix key 'a'. + */ + TestMetaDataDeleteByPrefixKey(store, connection); +} + +/** + * @tc.name: GetCurrentMaxTimestamp001 + * @tc.desc: To test the function of obtaining the maximum timestamp when a record exists in the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::GetCurrentMaxTimestamp001(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + Key key1; + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(key1); + DistributedDBToolsUnitTest::GetRandomKeyValue(value1); + Timestamp timeBegin = 0; + Timestamp timeMiddle = 0; + Timestamp timeEnd = 0; + + /** + * @tc.steps:step1/2. Insert a data record into the synchronization database. + */ + store->GetMaxTimestamp(timeBegin); + IOption option; + option.dataType = IOption::SYNC_DATA; + EXPECT_EQ(connection->Put(option, key1, value1), E_OK); + + /** + * @tc.steps:step3. The current maximum timestamp is A. + */ + store->GetMaxTimestamp(timeMiddle); + EXPECT_GT(timeMiddle, timeBegin); + + /** + * @tc.steps:step4. Insert a data record into the synchronization database. + */ + EXPECT_EQ(connection->Put(option, key1, value1), E_OK); + + /** + * @tc.steps:step5. Obtain the maximum timestamp B and check whether B>=A exists. + * @tc.expected: step5. The obtained timestamp is B>=A. + */ + store->GetMaxTimestamp(timeEnd); + EXPECT_GT(timeEnd, timeMiddle); +} + +/** + * @tc.name: GetCurrentMaxTimestamp002 + * @tc.desc: Obtain the maximum timestamp when no record exists in the test record library. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::GetCurrentMaxTimestamp002(SQLiteSingleVerNaturalStore *&store) +{ + /** + * @tc.steps:step1. Obtains the maximum timestamp in the current database record. + * @tc.expected: step1. Return timestamp is 0. + */ + Timestamp timestamp = 10; // non-zero + store->GetMaxTimestamp(timestamp); + EXPECT_EQ(timestamp, 0UL); +} + +/** + * @tc.name: LocalDatabaseOperate001 + * @tc.desc: Test the function of inserting data in the local database of the NaturalStore. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::LocalDatabaseOperate001(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::LOCAL_DATA; + DataBaseCommonPutOperate(store, connection, option); +} + +/** + * @tc.name: LocalDatabaseOperate002 + * @tc.desc: Test the function of deleting data from the local database of the NaturalStore. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::LocalDatabaseOperate002(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::LOCAL_DATA; + DataBaseCommonDeleteOperate(store, connection, option); +} + +/** + * @tc.name: LocalDatabaseOperate003 + * @tc.desc: To test the function of reading data from the local database of the NaturalStore. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::LocalDatabaseOperate003(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::LOCAL_DATA; + DataBaseCommonGetOperate(store, connection, option); +} + +/** + * @tc.name: SyncDatabaseOperate001 + * @tc.desc: To test the function of inserting data of the local device in the synchronization database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate001(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + DataBaseCommonPutOperate(store, connection, option); +} + +/** + * @tc.name: SyncDatabaseOperate002 + * @tc.desc: test the put operation after data synced from other devices. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate002(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + DataItem dataItem1; + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem1.key); + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem1.value); + dataItem1.timestamp = 1001; // 1001 as random timestamp + dataItem1.writeTimestamp = dataItem1.timestamp; + dataItem1.flag = 0; + + /** + * @tc.steps: step1/2. Add a remote synchronization data record. (key1, value1). + */ + std::vector vect = {dataItem1}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps: step3. Ioption is set to synchronous data. Obtains the value data of the key1. + * @tc.expected: step3. Return OK. The value is the same as the value of value1. + */ + Value valueRead; + EXPECT_EQ(connection->Get(option, dataItem1.key, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, dataItem1.value), true); + + Value value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, dataItem1.value.size() + 1); + + /** + * @tc.steps: step4. Ioption Set the data to be synchronized and insert the data of key1,value2. + * @tc.expected: step4. Return OK. + */ + EXPECT_EQ(connection->Put(option, dataItem1.key, value2), E_OK); + EXPECT_EQ(connection->Get(option, dataItem1.key, valueRead), E_OK); + + /** + * @tc.steps: step3. Ioption is set to synchronous data. Obtains the value data of the key1. + * @tc.expected: step3. Return OK. The value is the same as the value of value2. + */ + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value2), true); +} + +/** + * @tc.name: SyncDatabaseOperate003 + * @tc.desc: test the delete operation in sync database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate003(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + DataBaseCommonDeleteOperate(store, connection, option); +} + +/** + * @tc.name: SyncDatabaseOperate004 + * @tc.desc: test the delete for the data from other devices in sync database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate004(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + DataItem dataItem1; + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem1.key); + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem1.value); + dataItem1.timestamp = 1997; // 1997 as random timestamp + dataItem1.writeTimestamp = dataItem1.timestamp; + dataItem1.flag = 0; + + std::vector vect = {dataItem1}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps: step2. The Ioption parameter is set to synchronize data to obtain the value data of the key1. + * @tc.expected: step2. Return OK. The value is the same as the value of value1. + */ + Value valueRead; + EXPECT_EQ(connection->Get(option, dataItem1.key, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, dataItem1.value), true); + + Key key2; + DistributedDBToolsUnitTest::GetRandomKeyValue(key2); + EXPECT_EQ(connection->Delete(option, key2), E_OK); + + /** + * @tc.steps: step3. The Ioption parameter is set to synchronize data, and the key1 data is deleted. + * @tc.expected: step3. Return OK. + */ + EXPECT_EQ(connection->Delete(option, dataItem1.key), E_OK); + + /** + * @tc.steps: step4. The Ioption parameter is set to synchronize data to obtain the value data of the key1. + * @tc.expected: step4. Return E_NOT_FOUND. + */ + EXPECT_EQ(connection->Get(option, dataItem1.key, valueRead), -E_NOT_FOUND); +} + +/** + * @tc.name: SyncDatabaseOperate005 + * @tc.desc: test the reading for sync database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate005(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + DataBaseCommonGetOperate(store, connection, option); +} + +/** + * @tc.name: SyncDatabaseOperate006 + * @tc.desc: test the get entries for sync database + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate006(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + Key key1, key2, key3; + Value value1, value2, value3; + + /** + * @tc.steps: step2/3/4. Set Ioption to synchronous data. + * Insert the data of key=keyPrefix + 'a', value1. + * Insert the data of key=keyPrefix + 'c', value2. + * Insert the data of key length=keyPrefix length - 1, value3. + * @tc.expected: step2/3/4. Return E_NOT_FOUND. + */ + DistributedDBToolsUnitTest::GetRandomKeyValue(key1, 30); // 30 as random size + key3 = key2 = key1; + key2.push_back('C'); + key3.pop_back(); + DistributedDBToolsUnitTest::GetRandomKeyValue(value1, 84); // 84 as random size + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, 101); // 101 as random size + DistributedDBToolsUnitTest::GetRandomKeyValue(value3, 37); // 37 as random size + EXPECT_EQ(connection->Put(option, key1, value1), E_OK); + EXPECT_EQ(connection->Put(option, key2, value2), E_OK); + EXPECT_EQ(connection->Put(option, key3, value3), E_OK); + + /** + * @tc.steps: step5. Obtain all data whose prefixKey is keyPrefix. + * @tc.expected: step5. Return OK. The number of obtained data records is 2. + */ + std::vector entriesRead; + EXPECT_EQ(connection->GetEntries(option, key1, entriesRead), E_OK); + EXPECT_EQ(entriesRead.size(), 2UL); + + /** + * @tc.steps: step6. Obtain all data whose prefixKey is empty. + * @tc.expected: step6. Return OK. The number of obtained data records is 3. + */ + entriesRead.clear(); + Key emptyKey; + EXPECT_EQ(connection->GetEntries(option, emptyKey, entriesRead), E_OK); + EXPECT_EQ(entriesRead.size(), 3UL); + + /** + * @tc.steps: step7. Obtain all data whose prefixKey is keyPrefix. + * @tc.expected: step7. Return E_NOT_SUPPORT. + */ + option.dataType = IOption::LOCAL_DATA; + EXPECT_EQ(connection->GetEntries(option, emptyKey, entriesRead), -E_NOT_FOUND); +} + +/** + * @tc.name: ClearRemoteData001 + * @tc.desc: test the clear data synced from the remote by device. + * @tc.type: FUNC + * @tc.require: AR000CIFDA AR000CQS3T + * @tc.author: wangbingquan + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::ClearRemoteData001(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + DataItem dataItem1; + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem1.key); + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem1.value); + dataItem1.timestamp = 1997; // 1997 as random timestamp + dataItem1.writeTimestamp = dataItem1.timestamp; + dataItem1.flag = 0; + + DataItem dataItem2; + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem2.key, dataItem1.key.size() + 1); + DistributedDBToolsUnitTest::GetRandomKeyValue(dataItem2.value); + dataItem2.timestamp = 2019; // 2019 as random timestamp + dataItem2.writeTimestamp = dataItem2.timestamp; + dataItem2.flag = 0; + + /** + * @tc.steps: step1. New data is inserted to the B end of the device. [keyB, valueB]. + */ + std::vector vect = {dataItem1}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceA"), E_OK); + + /** + * @tc.steps: step2. The device pulls the data of the device B, and the device inserts the [keyA, valueA]. + */ + vect.clear(); + vect.push_back(dataItem2); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps: step3. The device obtains the data of keyA and valueB. + * @tc.expected: step3. Obtain [keyA, valueA] and [keyB, valueB]. + */ + Value valueRead; + EXPECT_EQ(connection->Get(option, dataItem1.key, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, dataItem1.value), true); + EXPECT_EQ(connection->Get(option, dataItem2.key, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, dataItem2.value), true); + + /** + * @tc.steps: step4.Invoke the interface for clearing the synchronization data of the B device. + */ + EXPECT_EQ(store->RemoveDeviceData("deviceA", false), E_OK); + + /** + * @tc.steps: step5. The device obtains the data of keyA and valueB. + * @tc.expected: step5. The value of [keyA, valueA] is obtained, + * and the value of NOT_FOUND is obtained by querying keyB. + */ + EXPECT_EQ(connection->Get(option, dataItem1.key, valueRead), -E_NOT_FOUND); + EXPECT_EQ(connection->Get(option, dataItem2.key, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, dataItem2.value), true); + + EXPECT_EQ(store->RemoveDeviceData("deviceB", false), E_OK); + EXPECT_EQ(connection->Get(option, dataItem2.key, valueRead), -E_NOT_FOUND); +} + +/** + * @tc.name: DeleteUserKeyValue001 + * @tc.desc: When a user deletes a data record, the system clears the user record. + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue001(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &url) +{ + // StoreID::TestGeneralNB + IOption option; + option.dataType = IOption::SYNC_DATA; + + // per-set data + EXPECT_EQ(connection->Put(option, KEY_1, VALUE_1), E_OK); + EXPECT_EQ(connection->Put(option, KEY_2, VALUE_2), E_OK); + + /** + * @tc.steps: step1. delete K1. + * @tc.expected: step1. delete K1 successfully. + */ + EXPECT_EQ(connection->Delete(option, KEY_1), E_OK); + + // Close database + connection->Close(); + connection = nullptr; + store = nullptr; + + /** + * @tc.steps: step2. Real query by sqlite3. + * @tc.expected: step2. Find KEY_1, not find K2. + */ + std::vector vecSyncData; + int numSelect = GetRawSyncData(url, SYNC_DATA_DEFAULT_SQL, vecSyncData); + + bool isFound = false; + EXPECT_EQ(numSelect, 2); // 2 as entry size + isFound = IsSqlinteExistKey(vecSyncData, KEY_1); + EXPECT_EQ(isFound, false); + isFound = IsSqlinteExistKey(vecSyncData, KEY_2); + EXPECT_EQ(isFound, true); +} + +/** + * @tc.name: MemoryDbDeleteUserKeyValue001 + * @tc.desc: When a user deletes a data record, the system clears the user record. + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::MemoryDbDeleteUserKeyValue001( + SQLiteSingleVerNaturalStore *&store, SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &url) +{ + // StoreID::TestGeneralNB + IOption option; + option.dataType = IOption::SYNC_DATA; + + // per-set data + EXPECT_EQ(connection->Put(option, KEY_1, VALUE_1), E_OK); + EXPECT_EQ(connection->Put(option, KEY_2, VALUE_2), E_OK); + + /** + * @tc.steps: step1. delete K1. + * @tc.expected: step1. delete K1 successfully. + */ + EXPECT_EQ(connection->Delete(option, KEY_1), E_OK); + + /** + * @tc.steps: step3. Real query by sqlite3. + * @tc.expected: step3. Find KEY_1, not find K2. + */ + std::vector vecSyncData; + int numSelect = GetRawSyncData(url, SYNC_DATA_DEFAULT_SQL, vecSyncData); + + bool isFound = false; + EXPECT_EQ(numSelect, 2); // 2 as entry size + isFound = IsSqlinteExistKey(vecSyncData, KEY_1); + EXPECT_EQ(isFound, false); + isFound = IsSqlinteExistKey(vecSyncData, KEY_2); + EXPECT_EQ(isFound, true); + + // Close database + connection->Close(); + connection = nullptr; + store = nullptr; +} + +/** + * @tc.name: DeleteUserKeyValue002 + * @tc.desc: After the synchronization library data is deleted locally, add the same key data locally. + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue002(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &url) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + + // pre-set data + EXPECT_EQ(connection->Put(option, KEY_1, VALUE_1), E_OK); + EXPECT_EQ(connection->Put(option, KEY_2, VALUE_2), E_OK); + + /** + * @tc.steps: step1. Delete key1 data via Delete interface. + * @tc.expected: step1. Delete successfully. + */ + EXPECT_EQ(connection->Delete(option, KEY_1), E_OK); + + /** + * @tc.steps: step2. New data from key1, value3 via Put interface. + * @tc.expected: step2. New data from key1, value3 via Put interface successfully. + */ + EXPECT_EQ(connection->Put(option, KEY_1, VALUE_3), E_OK); + + /** + * @tc.steps: step3. Query key1 data via Get interface. + * @tc.expected: step3. Query key1 data via Get interface successfully, get value3 by key1. + */ + Value valueRead; + EXPECT_EQ(connection->Get(option, KEY_1, valueRead), E_OK); + EXPECT_EQ(valueRead, VALUE_3); + + /** + * @tc.steps: step4. Query key1 real data by sqlite3. + * @tc.expected: step4. Two records were found. + */ + std::vector vecSyncData; + int numSelect = GetRawSyncData(url, SYNC_DATA_DEFAULT_SQL, vecSyncData); + + EXPECT_EQ(numSelect, 2); // 2 as entry size +} + +/** + * @tc.name: DeleteUserKeyValue003 + * @tc.desc: After the synchronization database data is deleted locally, the same key data is added from the remote end. + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue003(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &url) +{ + IOption option; + option.dataType = IOption::SYNC_DATA; + + // ready data + EXPECT_EQ(connection->Put(option, KEY_1, VALUE_1), E_OK); + + /** + * @tc.steps: step1. Delete data by key1. + * @tc.expected: step1. Delete successfully. + */ + EXPECT_EQ(connection->Delete(option, KEY_1), E_OK); + + /** + * @tc.steps: step2. Get data by key1. + * @tc.expected: step1. Key1 not exist in database. + */ + Value valueRead; + EXPECT_NE(connection->Get(option, KEY_1, valueRead), E_OK); + + Timestamp timestamp = 0; + store->GetMaxTimestamp(timestamp); + + DataItem dataItem1; + dataItem1.key = KV_ENTRY_1.key; + dataItem1.value = KV_ENTRY_3.value; + dataItem1.timestamp = timestamp - 100UL; // less than current timestamp + dataItem1.writeTimestamp = dataItem1.timestamp; + dataItem1.flag = 0; + + DataItem dataItem2; + dataItem2.key = KV_ENTRY_1.key; + dataItem2.value = KV_ENTRY_4.value; + dataItem2.timestamp = timestamp + 100UL; // bigger than current timestamp + dataItem2.writeTimestamp = dataItem2.timestamp; + dataItem2.flag = 0; + std::vector vect = {dataItem1}; + + /** + * @tc.steps: step3. Get a new data from remote device B , key1, value3, + * with a smaller timestamp than the current timestamp. + */ + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps: step4. Get data by key1. + * @tc.expected: step4. Key1 not exist in database. + */ + EXPECT_NE(connection->Get(option, KEY_1, valueRead), E_OK); + + /** + * @tc.steps: step5. Get a new data from remote device C , key1, value4, + * and the timestamp is larger than the current timestamp. + */ + vect.clear(); + vect.push_back(dataItem2); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceC"), E_OK); + + /** + * @tc.steps: step6. Get data by key1. + * @tc.expected: step6. Key1 not exist in database. + */ + EXPECT_EQ(connection->Get(option, KEY_1, valueRead), E_OK); + + /** + * @tc.steps: step7. Get real data by key1. + * @tc.expected: step7. Get 1 record. + */ + std::vector vecSyncData; + int numSelect = GetRawSyncData(url, SYNC_DATA_DEFAULT_SQL, vecSyncData); + + EXPECT_EQ(numSelect, 1); +} + +/** + * @tc.name: DeleteUserKeyValue004 + * @tc.desc: Changes in key after remote delete data syncs to local + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue004(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &url) +{ + // pre-set data + IOption option; + option.dataType = IOption::SYNC_DATA; + EXPECT_EQ(connection->Put(option, KEY_1, VALUE_1), E_OK); + + DataItem dataItem1; + dataItem1.key = KV_ENTRY_1.key; + dataItem1.value = KV_ENTRY_1.value; + store->GetMaxTimestamp(dataItem1.timestamp); + dataItem1.flag = 1; + dataItem1.timestamp += 1; + dataItem1.writeTimestamp = dataItem1.timestamp; + /** + * @tc.steps: step1 2 3. Synchronize data to another device B; delete key1 data from device B; + * pull the action of key1 to local. + */ + DistributedDBToolsUnitTest::CalcHash(KV_ENTRY_1.key, dataItem1.key); + std::vector vect = {dataItem1}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps: step4. Close database. + */ + connection->Close(); + connection = nullptr; + store = nullptr; + + /** + * @tc.steps: step5 6. Get real data by key1;and get the number of records. + * @tc.expected: step5 6. Not exist key1 real data in database;Get 1 record. + */ + std::vector vecSyncData; + int numSelect = GetRawSyncData(url, SYNC_DATA_DEFAULT_SQL, vecSyncData); + + EXPECT_EQ(numSelect, 1); + bool isFound = IsSqlinteExistKey(vecSyncData, KEY_1); + EXPECT_EQ(isFound, false); +} + +/** + * @tc.name: MemoryDbDeleteUserKeyValue004 + * @tc.desc: Changes in key after remote delete data syncs to local + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::MemoryDbDeleteUserKeyValue004( + SQLiteSingleVerNaturalStore *&store, SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &url) +{ + // pre-set data + IOption option; + option.dataType = IOption::SYNC_DATA; + EXPECT_EQ(connection->Put(option, KEY_1, VALUE_1), E_OK); + + DataItem dataItem1; + dataItem1.key = KV_ENTRY_1.key; + dataItem1.value = KV_ENTRY_1.value; + store->GetMaxTimestamp(dataItem1.timestamp); + dataItem1.flag = 1; + dataItem1.timestamp += 1; + dataItem1.writeTimestamp = dataItem1.timestamp; + /** + * @tc.steps: step1 2 3. Synchronize data to another device B; delete key1 data from device B; + * pull the action of key1 to local. + */ + DistributedDBToolsUnitTest::CalcHash(KV_ENTRY_1.key, dataItem1.key); + std::vector vect = {dataItem1}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps: step4 5. Get real data by key1;and get the number of records. + * @tc.expected: step 4 5. Not exist key1 real data in database;Get 1 record. + */ + std::vector vecSyncData; + int numSelect = GetRawSyncData(url, SYNC_DATA_DEFAULT_SQL, vecSyncData); + + EXPECT_EQ(numSelect, 1); + bool isFound = IsSqlinteExistKey(vecSyncData, KEY_1); + EXPECT_EQ(isFound, false); + + connection->Close(); + connection = nullptr; + store = nullptr; +} + +/** + * @tc.name: DeleteUserKeyValue005 + * @tc.desc: New unified key data locally after remote delete data syncs to local + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue005(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &url) +{ + // pre-set data + IOption option; + option.dataType = IOption::SYNC_DATA; + EXPECT_EQ(connection->Put(option, KEY_1, VALUE_1), E_OK); + EXPECT_EQ(connection->Put(option, KEY_2, VALUE_2), E_OK); + + DataItem dataItem1; + dataItem1.key = KV_ENTRY_1.key; + dataItem1.value = KV_ENTRY_1.value; + store->GetMaxTimestamp(dataItem1.timestamp); + dataItem1.timestamp = dataItem1.timestamp + 10UL; + dataItem1.writeTimestamp = dataItem1.timestamp; + dataItem1.flag = 1; + + /** + * @tc.steps: step1 2 3. Synchronize data to another device B; delete key1 data from device B; + * pull the action of key1 to local. + */ + DistributedDBToolsUnitTest::CalcHash(KV_ENTRY_1.key, dataItem1.key); + std::vector vect = {dataItem1}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps: step4. Put K1 V1 to database. + * @tc.expected: step4. Put successfully. + */ + EXPECT_EQ(connection->Put(option, KEY_1, VALUE_1), E_OK); + + /** + * @tc.steps: step5. Close database. + */ + connection->Close(); + connection = nullptr; + store = nullptr; + + /** + * @tc.steps: step6 7. Get real data by key1;and get the number of records. + * @tc.expected: step6 7. Not exist key1 real data in database;Get 2 record. + */ + std::vector vecSyncData; + int numSelect = GetRawSyncData(url, SYNC_DATA_DEFAULT_SQL, vecSyncData); + + EXPECT_EQ(numSelect, 2); // 2 as entry size + bool isFound = IsSqlinteExistKey(vecSyncData, KEY_1); + EXPECT_EQ(isFound, true); +} + +/** + * @tc.name: MemoryDbDeleteUserKeyValue005 + * @tc.desc: New unified key data locally after remote delete data syncs to local + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::MemoryDbDeleteUserKeyValue005( + SQLiteSingleVerNaturalStore *&store, SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &url) +{ + // pre-set data + IOption option; + option.dataType = IOption::SYNC_DATA; + EXPECT_EQ(connection->Put(option, KEY_1, VALUE_1), E_OK); + EXPECT_EQ(connection->Put(option, KEY_2, VALUE_2), E_OK); + + DataItem dataItem1; + dataItem1.key = KV_ENTRY_1.key; + dataItem1.value = KV_ENTRY_1.value; + store->GetMaxTimestamp(dataItem1.timestamp); + dataItem1.timestamp = TimeHelper::GetSysCurrentTime(); + dataItem1.writeTimestamp = dataItem1.timestamp; + dataItem1.flag = 1; + + /** + * @tc.steps: step1 2 3. Synchronize data to another device B; delete key1 data from device B; + * pull the action of key1 to local. + */ + DistributedDBToolsUnitTest::CalcHash(KV_ENTRY_1.key, dataItem1.key); + std::vector vect = {dataItem1}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps: step4. Put K1 V1 to database. + * @tc.expected: step4. Put successfully. + */ + EXPECT_EQ(connection->Put(option, KEY_1, VALUE_1), E_OK); + + /** + * @tc.steps: step5 6. Get real data by key1;and get the number of records. + * @tc.expected: step5 6. Not exist key1 real data in database;Get 2 record. + */ + std::vector vecSyncData; + int numSelect = GetRawSyncData(url, SYNC_DATA_DEFAULT_SQL, vecSyncData); + + EXPECT_EQ(numSelect, 2); // 2 as entry size + bool isFound = IsSqlinteExistKey(vecSyncData, KEY_1); + EXPECT_EQ(isFound, true); + + connection->Close(); + connection = nullptr; + store = nullptr; +} + +/** + * @tc.name: DeleteUserKeyValue006 + * @tc.desc: After the remote delete data is synced to the local, + * the same key data is added from the remote other devices + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +void DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue006(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &url) +{ + // pre-set data + IOption option; + option.dataType = IOption::SYNC_DATA; + EXPECT_EQ(connection->Put(option, KEY_1, VALUE_1), E_OK); + + DataItem dataItem1; + dataItem1.key = KV_ENTRY_1.key; + dataItem1.value = KV_ENTRY_1.value; + store->GetMaxTimestamp(dataItem1.timestamp); + dataItem1.timestamp = dataItem1.timestamp + 10UL; + dataItem1.writeTimestamp = dataItem1.timestamp; + dataItem1.flag = 1; + + /** + * @tc.steps: step1. Remote device B sync deletes data key1 and pushes to local. + */ + DistributedDBToolsUnitTest::CalcHash(KV_ENTRY_1.key, dataItem1.key); + std::vector vect = {dataItem1}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceB"), E_OK); + + /** + * @tc.steps: step2. Get key1 from database. + * @tc.expected: step2. Not exist key1. + */ + Value valueRead; + EXPECT_NE(connection->Get(option, KEY_1, valueRead), E_OK); + + dataItem1.key = KV_ENTRY_1.key; + dataItem1.flag = 0; + dataItem1.value = KV_ENTRY_2.value; + dataItem1.timestamp = dataItem1.timestamp - 100UL; // less than current timestamp + dataItem1.writeTimestamp = dataItem1.timestamp; + /** + * @tc.steps: step3. Remote device C syncs new data (key1, value2), + * timestamp is less than delete timestamp, to local. + */ + vect = {dataItem1}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceC"), E_OK); + + /** + * @tc.steps: step4. Get key1 from database. + * @tc.expected: step4. Not exist key1. + */ + EXPECT_NE(connection->Get(option, KEY_1, valueRead), E_OK); + + dataItem1.value = KV_ENTRY_3.value; + dataItem1.timestamp = dataItem1.timestamp + 200UL; // bigger than current timestamp + dataItem1.writeTimestamp = dataItem1.timestamp; + /** + * @tc.steps: step5. Remote device C syncs new data (key1, value2), + * timestamp is bigger than delete timestamp, to local. + */ + vect = {dataItem1}; + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, vect, "deviceD"), E_OK); + + /** + * @tc.steps: step6. Get key1 from database. + * @tc.expected: step6. Exist key1. + */ + EXPECT_EQ(connection->Get(option, KEY_1, valueRead), E_OK); + EXPECT_EQ(valueRead, VALUE_3); + + /** + * @tc.steps: step7. Get real data from database. + * @tc.expected: step7. Get 1 record. + */ + std::vector vecSyncData; + int numSelect = GetRawSyncData(url, SYNC_DATA_DEFAULT_SQL, vecSyncData); + + EXPECT_EQ(numSelect, 1); +} + +// private Begin +void DistributedDBStorageSingleVerNaturalStoreTestCase::CreateMemDb(SQLiteSingleVerNaturalStoreConnection *&connection, + int &errCode) +{ + // pre-Set close other db + if (connection != nullptr) { + connection->Close(); + connection = nullptr; + } + + KvDBProperties property; + property.SetStringProp(KvDBProperties::STORE_ID, "TestGeneralNB"); + property.SetStringProp(KvDBProperties::IDENTIFIER_DIR, "TestGeneralNB"); + property.SetBoolProp(KvDBProperties::MEMORY_MODE, true); + + SQLiteSingleVerNaturalStore *memoryStore = nullptr; + memoryStore = new (std::nothrow) SQLiteSingleVerNaturalStore; + ASSERT_NE(memoryStore, nullptr); + + errCode = memoryStore->Open(property); + if (errCode != E_OK) { + return; + } + + connection = static_cast(memoryStore->GetDBConnection(errCode)); + ASSERT_NE(connection, nullptr); + memoryStore->DecObjRef(memoryStore); +} + +// param [in] dbName:Database name,strSql: The sql statement executed,[out],vecSyncData:SYNC_DATA table data +// Real query sync-DATA table data via sqlite. return query data row number +int DistributedDBStorageSingleVerNaturalStoreTestCase::GetRawSyncData(const std::string &dbName, + const std::string &strSql, std::vector &vecSyncData) +{ + uint64_t flag = SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE; + flag |= SQLITE_OPEN_CREATE; + + sqlite3* db = nullptr; + int nResult = sqlite3_open_v2(dbName.c_str(), &db, flag, nullptr); + if (nResult != SQLITE_OK) { + return -nResult; + } + + sqlite3_stmt *statement = nullptr; + + nResult = sqlite3_prepare(db, strSql.c_str(), -1, &statement, NULL); + if (nResult != SQLITE_OK) { + (void)sqlite3_close_v2(db); + return -1; + } + + while (sqlite3_step(statement) == SQLITE_ROW) { + SyncData stuSyncData; + const uint8_t *blobValue = static_cast(sqlite3_column_blob(statement, SYNC_RES_KEY_INDEX)); + int valueLength = sqlite3_column_bytes(statement, SYNC_RES_KEY_INDEX); + if (blobValue == nullptr) { + stuSyncData.key.clear(); + } else { + stuSyncData.key.resize(valueLength); + stuSyncData.key.assign(blobValue, blobValue + valueLength); + } + + blobValue = static_cast(sqlite3_column_blob(statement, SYNC_RES_HASH_KEY_INDEX)); + valueLength = sqlite3_column_bytes(statement, SYNC_RES_HASH_KEY_INDEX); + stuSyncData.hashKey.resize(valueLength); + stuSyncData.hashKey.assign(blobValue, blobValue + valueLength); + + blobValue = static_cast(sqlite3_column_blob(statement, SYNC_RES_VAL_INDEX)); + valueLength = sqlite3_column_bytes(statement, SYNC_RES_VAL_INDEX); + if (blobValue == nullptr) { + stuSyncData.value.clear(); + } else { + stuSyncData.value.resize(valueLength); + stuSyncData.value.assign(blobValue, blobValue + valueLength); + } + + stuSyncData.timestamp = static_cast(sqlite3_column_int64(statement, SYNC_RES_TIME_INDEX)); + stuSyncData.flag = sqlite3_column_int64(statement, SYNC_RES_FLAG_INDEX); + vecSyncData.push_back(stuSyncData); + } + + sqlite3_finalize(statement); + statement = nullptr; + (void)sqlite3_close_v2(db); + return static_cast(vecSyncData.size()); +} + +// @Real query sync-DATA table by key, judge is exist. +bool DistributedDBStorageSingleVerNaturalStoreTestCase::IsSqlinteExistKey(const std::vector &vecSyncData, + const std::vector &key) +{ + for (const auto &iter : vecSyncData) { + if (key == iter.key) { + return true; + } + } + return false; +} + +void DistributedDBStorageSingleVerNaturalStoreTestCase::TestMetaDataPutAndGet(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection) +{ + Key key1; + Value value1; + Key emptyKey; + Value emptyValue; + DistributedDBToolsUnitTest::GetRandomKeyValue(key1); + DistributedDBToolsUnitTest::GetRandomKeyValue(value1); + + /** + * @tc.steps:step1. Run the PutMetaData command to insert a non-empty key1 non-empty value1 data record. + * @tc.expected: step1. Return OK. + */ + EXPECT_EQ(store->PutMetaData(key1, value1), E_OK); + + /** + * @tc.steps:step2. Run the PutMetaData command to insert a non-empty key1 non-empty value1 data record. + * @tc.expected: step2. The obtained value is the same as the value of value1. + */ + Value valueRead; + EXPECT_EQ(store->GetMetaData(key1, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value1), true); + Value value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, static_cast(value1.size() + 3)); // 3 as random size + + /** + * @tc.steps:step3. The key value is key1, the value is not empty, + * and the value of value2 is different from the value of value1 through the PutMetaData interface. + * @tc.expected: step3. Return OK. + */ + EXPECT_EQ(store->PutMetaData(key1, value2), E_OK); + + /** + * @tc.steps:step4. Run the GetMetaData command to obtain the value of key1 + * and check whether the value is the same as the value of value2. + * @tc.expected: step4. The obtained value is the same as the value of value2. + */ + EXPECT_EQ(store->GetMetaData(key1, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value2), true); + + /** + * @tc.steps:step5. Use PutMetaData to insert a record whose key is empty and value is not empty. + * @tc.expected: step5. Return E_INVALID_ARGS. + */ + EXPECT_EQ(store->PutMetaData(emptyKey, value1), -E_INVALID_ARGS); + + /** + * @tc.steps:step6. Use PutMetaData in NaturalStore to insert data whose key2(!=key1) + * is not empty and value is empty. + * @tc.expected: step6. Return OK. + */ + Key key2; + DistributedDBToolsUnitTest::GetRandomKeyValue(key2, static_cast(key1.size() + 1)); + EXPECT_EQ(store->PutMetaData(key2, emptyValue), E_OK); + + /** + * @tc.steps:step7. Obtain the value of key2 and check whether the value is empty. + * @tc.expected: step7. The obtained value is empty. + */ + EXPECT_EQ(store->GetMetaData(key2, valueRead), E_OK); + EXPECT_EQ(valueRead.empty(), true); + + /** + * @tc.steps:step8. Insert the data whose key size is 1024 and value size is 4Mb + * through PutMetaData of NaturalStore. + * @tc.expected: step8. Return OK. + */ + Key sizeKey; + Value sizeValue; + DistributedDBToolsUnitTest::GetRandomKeyValue(sizeKey, MAX_TEST_KEY_SIZE); + DistributedDBToolsUnitTest::GetRandomKeyValue(sizeValue, MAX_TEST_VAL_SIZE); + EXPECT_EQ(store->PutMetaData(sizeKey, sizeValue), E_OK); + EXPECT_EQ(store->GetMetaData(sizeKey, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, sizeValue), true); + + /** + * @tc.steps:step9/10. Insert data items whose key size is greater than 1 kb + * or value size greater than 4Mb through PutMetaData of NaturalStore. + * @tc.expected: step9/10. Return E_INVALID_ARGS. + */ + sizeKey.push_back(249); // 249 as random size + EXPECT_EQ(store->PutMetaData(sizeKey, sizeValue), -E_INVALID_ARGS); + sizeKey.pop_back(); + sizeValue.push_back(174); // 174 as random size + EXPECT_EQ(store->PutMetaData(sizeKey, sizeValue), -E_INVALID_ARGS); + + /** + * @tc.steps:step11. Delete key1 and key2 successfully. + * @tc.expected: step11. Cannot find key1 and key2 in DB anymore. + */ + EXPECT_EQ(store->DeleteMetaData(std::vector {key1, key2}), E_OK); + EXPECT_EQ(store->GetMetaData(key1, valueRead), -E_NOT_FOUND); + EXPECT_EQ(store->GetMetaData(key2, valueRead), -E_NOT_FOUND); +} + +void DistributedDBStorageSingleVerNaturalStoreTestCase::DataBaseCommonPutOperate(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection, IOption option) +{ + Key key1; + Value value1; + + /** + * @tc.steps: step1/2. Set Ioption to the local data and insert a record of key1 and value1. + * @tc.expected: step1/2. Return OK. + */ + DistributedDBToolsUnitTest::GetRandomKeyValue(key1); + DistributedDBToolsUnitTest::GetRandomKeyValue(value1); + EXPECT_EQ(connection->Put(option, key1, value1), E_OK); + + /** + * @tc.steps: step3. Set Ioption to the local data and obtain the value of key1. + * Check whether the value is the same as the value of value1. + * @tc.expected: step3. The obtained value and value2 are the same. + */ + Value valueRead; + EXPECT_EQ(connection->Get(option, key1, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value1), true); + Value value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, static_cast(value1.size() + 3)); // 3 more for diff + + /** + * @tc.steps: step4. Ioption Set this parameter to the local data. Insert key1. + * The value cannot be empty. value2(!=value1) + * @tc.expected: step4. Return OK. + */ + EXPECT_EQ(connection->Put(option, key1, value2), E_OK); + + /** + * @tc.steps: step5. Set Ioption to the local data, GetMetaData to obtain the value of key1, + * and check whether the value is the same as the value of value2. + * @tc.expected: step5. The obtained value and value2 are the same. + */ + EXPECT_EQ(connection->Get(option, key1, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value2), true); + + /** + * @tc.steps: step6. The Ioption parameter is set to the local data. + * The data record whose key is empty and value is not empty is inserted. + * @tc.expected: step6. Return E_INVALID_DATA. + */ + Key emptyKey; + Value emptyValue; + EXPECT_EQ(connection->Put(option, emptyKey, value1), -E_INVALID_ARGS); + + /** + * @tc.steps: step7. Set Ioption to the local data, insert data + * whose key2(!=key1) is not empty, and value is empty. + * @tc.expected: step7. Return OK. + */ + Key key2; + DistributedDBToolsUnitTest::GetRandomKeyValue(key2, static_cast(key1.size() + 1)); + EXPECT_EQ(connection->Put(option, key2, emptyValue), E_OK); + + /** + * @tc.steps: step8. Set option to local data, obtain the value of key2, + * and check whether the value is empty. + * @tc.expected: step8. Return OK, value is empty. + */ + EXPECT_EQ(connection->Get(option, key2, valueRead), E_OK); + EXPECT_EQ(valueRead.empty(), true); + + /** + * @tc.steps: step9. Ioption Set the local data. + * Insert the data whose key size is 1024 and value size is 4Mb. + * @tc.expected: step9. Return OK. + */ + Key sizeKey; + Value sizeValue; + DistributedDBToolsUnitTest::GetRandomKeyValue(sizeKey, MAX_TEST_KEY_SIZE); + DistributedDBToolsUnitTest::GetRandomKeyValue(sizeValue, MAX_TEST_VAL_SIZE); + EXPECT_EQ(connection->Put(option, sizeKey, sizeValue), E_OK); + EXPECT_EQ(connection->Get(option, sizeKey, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, sizeValue), true); + + /** + * @tc.steps: step10/11. Set Ioption to the local data and insert data items + * whose value is greater than 4Mb or key is bigger than 1Kb + * @tc.expected: step10/11. Return E_INVALID_ARGS. + */ + sizeKey.push_back(std::rand()); // random size + EXPECT_EQ(connection->Put(option, sizeKey, sizeValue), -E_INVALID_ARGS); + sizeKey.pop_back(); + sizeValue.push_back(174); // 174 as random size + EXPECT_EQ(connection->Put(option, sizeKey, sizeValue), -E_INVALID_ARGS); +} + +void DistributedDBStorageSingleVerNaturalStoreTestCase::DataBaseCommonDeleteOperate(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection, IOption option) +{ + /** + * @tc.steps: step2. Set Ioption to the local data and delete the data whose key is key1 (empty). + * @tc.expected: step2. Return E_INVALID_ARGS. + */ + Key key1; + EXPECT_EQ(connection->Delete(option, key1), -E_INVALID_ARGS); + DistributedDBToolsUnitTest::GetRandomKeyValue(key1, MAX_TEST_KEY_SIZE + 1); + EXPECT_EQ(connection->Delete(option, key1), -E_INVALID_ARGS); + DistributedDBToolsUnitTest::GetRandomKeyValue(key1); + EXPECT_EQ(connection->Delete(option, key1), E_OK); + + /** + * @tc.steps: step3. Set Ioption to the local data, insert non-null key1, and non-null value1 data. + * @tc.expected: step3. Return E_OK. + */ + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(value1); + EXPECT_EQ(connection->Put(option, key1, value1), E_OK); + + /** + * @tc.steps: step4. Set Ioption to the local data, obtain the value of key1, + * and check whether the value is the same as that of value1. + * @tc.expected: step4. Return E_OK. The obtained value is the same as the value of value1. + */ + Value valueRead; + EXPECT_EQ(connection->Get(option, key1, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value1), true); + + /** + * @tc.steps: step5. Set Ioption to the local data and delete the data whose key is key1. + * @tc.expected: step5. Return E_OK. + */ + EXPECT_EQ(connection->Delete(option, key1), E_OK); + + /** + * @tc.steps: step5. Set Ioption to the local data and obtain the value of Key1. + * @tc.expected: step5. Return E_NOT_FOUND. + */ + EXPECT_EQ(connection->Get(option, key1, valueRead), -E_NOT_FOUND); +} + +void DistributedDBStorageSingleVerNaturalStoreTestCase::DataBaseCommonGetOperate(SQLiteSingleVerNaturalStore *&store, + SQLiteSingleVerNaturalStoreConnection *&connection, IOption option) +{ + /** + * @tc.steps: step2. Set Ioption to the local data and delete the data whose key is key1 (empty). + * @tc.expected: step2. Return E_INVALID_ARGS. + */ + Key key1; + Value valueRead; + // empty key + EXPECT_EQ(connection->Get(option, key1, valueRead), -E_INVALID_ARGS); + + // invalid key + DistributedDBToolsUnitTest::GetRandomKeyValue(key1, MAX_TEST_KEY_SIZE + 1); + EXPECT_EQ(connection->Get(option, key1, valueRead), -E_INVALID_ARGS); + + // non-exist key + DistributedDBToolsUnitTest::GetRandomKeyValue(key1, MAX_TEST_KEY_SIZE); + EXPECT_EQ(connection->Get(option, key1, valueRead), -E_NOT_FOUND); + + /** + * @tc.steps: step3. Set Ioption to the local data, insert non-null key1, and non-null value1 data. + * @tc.expected: step3. Return E_OK. + */ + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(value1); + EXPECT_EQ(connection->Put(option, key1, value1), E_OK); + + /** + * @tc.steps: step4. Set Ioption to the local data, obtain the value of key1, + * and check whether the value is the same as that of value1. + * @tc.expected: step4. Return E_OK. The obtained value is the same as the value of value1. + */ + EXPECT_EQ(connection->Get(option, key1, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value1), true); + + Key key2; + DistributedDBToolsUnitTest::GetRandomKeyValue(key2); + EXPECT_EQ(connection->Get(option, key2, valueRead), -E_NOT_FOUND); + + /** + * @tc.steps: step5. Set Ioption to the local data and obtain the value data of Key1. + * Check whether the value is the same as the value of value2. + * @tc.expected: step4. Return E_OK, and the value is the same as the value of value2. + */ + Value value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, value1.size() + 1); + EXPECT_EQ(connection->Put(option, key1, value2), E_OK); + + /** + * @tc.steps: step5. The Ioption is set to the local. + * The data of the key1 and value2(!=value1) is inserted. + * @tc.expected: step4. Return E_OK. + */ + EXPECT_EQ(connection->Get(option, key1, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value2), true); +} + +void DistributedDBStorageSingleVerNaturalStoreTestCase::TestMetaDataDeleteByPrefixKey( + SQLiteSingleVerNaturalStore *&store, SQLiteSingleVerNaturalStoreConnection *&connection) +{ + /** + * @tc.steps:step1. Put a1, b1, a2, b2. + * @tc.expected: step1. Return OK. + */ + ASSERT_EQ(store->PutMetaData(Key {'a', '1'}, Value {'a', '1'}), E_OK); + ASSERT_EQ(store->PutMetaData(Key {'b', '1'}, Value {'b', '1'}), E_OK); + ASSERT_EQ(store->PutMetaData(Key {'a', '2'}, Value {'a', '2'}), E_OK); + ASSERT_EQ(store->PutMetaData(Key {'b', '2'}, Value {'b', '2'}), E_OK); + + /** + * @tc.steps:step2. Delete meta data with prefix key 'b'. + * @tc.expected: step2. Return OK. + */ + ASSERT_EQ(store->DeleteMetaDataByPrefixKey(Key {'b'}), E_OK); + ASSERT_EQ(store->DeleteMetaDataByPrefixKey(Key {'c'}), E_OK); + + /** + * @tc.steps:step3. Get a1, b1, a2, b2. + * @tc.expected: step3. Get a1, a2 successfully, and get b1, b2 failed. + */ + Value value; + EXPECT_EQ(store->GetMetaData(Key {'a', '1'}, value), E_OK); + EXPECT_EQ(store->GetMetaData(Key {'a', '2'}, value), E_OK); + EXPECT_EQ(store->GetMetaData(Key {'b', '1'}, value), -E_NOT_FOUND); + EXPECT_EQ(store->GetMetaData(Key {'b', '2'}, value), -E_NOT_FOUND); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_single_ver_natural_store_testcase.h b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_single_ver_natural_store_testcase.h new file mode 100644 index 00000000..148fd14d --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_single_ver_natural_store_testcase.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_errno.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "multi_ver_natural_store.h" +#include "sqlite_local_kvdb.h" +#include "sqlite_single_ver_natural_store.h" +#include "sqlite_single_ver_natural_store_connection.h" +#include "sqlite_utils.h" + +#ifndef DISTRIBUTEDDB_STORAGE_SINGLE_VER_NATURAL_STORE_TESTCASE_H +#define DISTRIBUTEDDB_STORAGE_SINGLE_VER_NATURAL_STORE_TESTCASE_H +struct SyncData { + std::vector hashKey; + std::vector key; + std::vector value; + uint64_t timestamp; + uint64_t flag; + std::string deviceInfo; +}; + +class DistributedDBStorageSingleVerNaturalStoreTestCase final { +public: + DistributedDBStorageSingleVerNaturalStoreTestCase() {}; + ~DistributedDBStorageSingleVerNaturalStoreTestCase() {}; + + static void GetSyncData001(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void GetSyncData002(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void GetSyncData003(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void GetSyncData004(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void GetSyncData005(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void GetSyncData006(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void PutSyncData001(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void PutSyncData002(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void PutSyncData003(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void PutMetaData001(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void GetMetaData001(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void DeleteMetaData001(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void GetCurrentMaxTimestamp001(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void GetCurrentMaxTimestamp002(DistributedDB::SQLiteSingleVerNaturalStore *&store); + + static void LocalDatabaseOperate001(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void LocalDatabaseOperate002(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void LocalDatabaseOperate003(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void SyncDatabaseOperate001(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void SyncDatabaseOperate002(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void SyncDatabaseOperate003(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void SyncDatabaseOperate004(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void SyncDatabaseOperate005(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void SyncDatabaseOperate006(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void ClearRemoteData001(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void DeleteUserKeyValue001(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &dbName); + + static void MemoryDbDeleteUserKeyValue001(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &dbName); + + static void DeleteUserKeyValue002(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &dbName); + + static void DeleteUserKeyValue003(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &dbName); + + static void DeleteUserKeyValue004(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &dbName); + + static void MemoryDbDeleteUserKeyValue004(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &dbName); + + static void DeleteUserKeyValue005(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &dbName); + + static void MemoryDbDeleteUserKeyValue005(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &dbName); + + static void DeleteUserKeyValue006(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, const std::string &dbName); + static int GetRawSyncData(const std::string &dbName, const std::string &strSql, std::vector &vecSyncData); + +private: + static void CreateMemDb(DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, int &errCode); + + static bool IsSqlinteExistKey(const std::vector &vecSyncData, const std::vector &key); + + static void TestMetaDataPutAndGet(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void TestMetaDataDeleteByPrefixKey(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection); + + static void DataBaseCommonPutOperate(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, DistributedDB::IOption option); + + static void DataBaseCommonDeleteOperate(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, DistributedDB::IOption option); + + static void DataBaseCommonGetOperate(DistributedDB::SQLiteSingleVerNaturalStore *&store, + DistributedDB::SQLiteSingleVerNaturalStoreConnection *&connection, DistributedDB::IOption option); +}; +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_single_ver_upgrade_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_single_ver_upgrade_test.cpp new file mode 100644 index 00000000..b5b311ce --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_single_ver_upgrade_test.cpp @@ -0,0 +1,588 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_constant.h" +#include "db_errno.h" +#include "distributeddb_tools_unit_test.h" +#include "iprocess_system_api_adapter.h" +#include "kv_store_delegate_manager.h" +#include "kv_store_nb_delegate.h" +#include "log_print.h" +#include "platform_specific.h" +#include "process_system_api_adapter_impl.h" +#include "runtime_context.h" +#include "sqlite_single_ver_natural_store.h" +#include "sqlite_utils.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + // define some variables to init a KvStoreDelegateManager object. + KvStoreDelegateManager g_mgr("app0", "user0"); + enum ForkConcurrentStatus : int { + NOT_RUN = 0, + RUNNING, + FINISHED, + }; + string g_testDir; + KvStoreConfig g_config; + string g_identifier; + string g_databaseName; + string g_newDatabaseName; + string g_localdatabaseName; + Value g_origValue = {'c', 'e'}; + string g_flag = "2"; + CipherPassword g_passwd; + int g_forkconcurrent = ForkConcurrentStatus::NOT_RUN; + static std::shared_ptr g_adapter; + string g_maindbPath; + string g_metadbPath; + string g_cachedbPath; + DBStatus g_valueStatus = INVALID_ARGS; + Value g_value; + auto g_valueCallback = bind(&DistributedDBToolsUnitTest::ValueCallback, + placeholders::_1, placeholders::_2, std::ref(g_valueStatus), std::ref(g_value)); + + // define the g_kvNbDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; + const std::vector ORIG_DATABASE_V1 = { + "CREATE TABLE IF NOT EXISTS local_data(key BLOB PRIMARY KEY NOT NULL, value BLOB);", + "CREATE TABLE IF NOT EXISTS sync_data(key BLOB NOT NULL, value BLOB, timestamp INT NOT NULL," \ + "flag INT NOT NULL, device BLOB, ori_device BLOB, hash_key BLOB PRIMARY KEY NOT NULL);", + "CREATE TABLE IF NOT EXISTS meta_data(key BLOB PRIMARY KEY NOT NULL, value BLOB);", + "CREATE INDEX IF NOT EXISTS key_index ON sync_data (key);", + "CREATE INDEX IF NOT EXISTS time_index ON sync_data (timestamp);", + "CREATE INDEX IF NOT EXISTS dev_index ON sync_data (device);", + "PRAGMA user_version=101;" + }; + + const std::vector ORIG_DATABASE_V2 = { + "CREATE TABLE IF NOT EXISTS local_data(key BLOB PRIMARY KEY NOT NULL," \ + "value BLOB, timestamp INT, hash_key BLOB);", + "CREATE TABLE IF NOT EXISTS sync_data(key BLOB NOT NULL, value BLOB, timestamp INT NOT NULL," \ + "flag INT NOT NULL, device BLOB, ori_device BLOB, hash_key BLOB PRIMARY KEY NOT NULL, w_timestamp INT);", + "CREATE TABLE IF NOT EXISTS meta_data(key BLOB PRIMARY KEY NOT NULL, value BLOB);", + "CREATE INDEX IF NOT EXISTS key_index ON sync_data (key, flag);", + "CREATE INDEX IF NOT EXISTS time_index ON sync_data (timestamp);", + "CREATE INDEX IF NOT EXISTS dev_index ON sync_data (device);", + "CREATE INDEX IF NOT EXISTS local_hashkey_index ON local_data (hash_key);", + "PRAGMA user_version=102;" + }; + + const std::vector ORIG_DATABASE_V3 = { + "CREATE TABLE IF NOT EXISTS local_data(key BLOB PRIMARY KEY NOT NULL, value BLOB, " \ + "timestamp INT, hash_key BLOB);", + "CREATE TABLE IF NOT EXISTS sync_data(key BLOB NOT NULL, value BLOB, timestamp INT NOT NULL," \ + "flag INT NOT NULL, device BLOB, ori_device BLOB, hash_key BLOB PRIMARY KEY NOT NULL, w_timestamp INT);", + "CREATE INDEX IF NOT EXISTS key_index ON sync_data (key, flag);", + "CREATE INDEX IF NOT EXISTS time_index ON sync_data (timestamp);", + "CREATE INDEX IF NOT EXISTS dev_index ON sync_data (device);", + "CREATE INDEX IF NOT EXISTS local_hashkey_index ON local_data (hash_key);", + "PRAGMA user_version=103;" + }; + + const std::vector INSERT_DATA_V1 = { + "INSERT INTO sync_data VALUES('ab', 'cd', 100, 2, '', '', 'efdef');" \ + }; + + const std::string INSERT_LOCAL_DATA_V1 = { + "INSERT INTO local_data VALUES(?, 'ce');" + }; + + const std::vector INSERT_DATA_V2 = { + "INSERT INTO sync_data VALUES('ab', 'cd', 100, " + g_flag + ", '', '', 'efdef', 100);" + }; + + const std::string INSERT_LOCAL_DATA_V2 = { + "INSERT INTO local_data VALUES(?, 'ce',3169633545069981070,'efdef');" + }; + + const std::string INSERT_META_DATA_V2 = { + "INSERT INTO meta_data VALUES('ab', 'ce');" + }; + + const std::string CHECK_V1_SYNC_UPGRADE = + "SELECT w_timestamp, timestamp FROM sync_data;"; + + const std::string CHECK_V2_SYNC_UPGRADE = + "SELECT flag FROM sync_data;"; + + const std::string CHECK_V1_LOCAL_UPGRADE = + "SELECT timestamp, hash_key FROM local_data;"; + + void KvStoreNbDelegateCallback( + DBStatus statusSrc, KvStoreNbDelegate *kvStoreSrc, DBStatus &statusDst, KvStoreNbDelegate *&kvStoreDst) + { + statusDst = statusSrc; + kvStoreDst = kvStoreSrc; + } + + auto g_kvNbDelegateCallback = bind(&KvStoreNbDelegateCallback, placeholders::_1, + placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); + + void CreateDatabase(const std::vector &insertSqls, const std::string &insertLocalDataSql, + const OpenDbProperties &property) + { + sqlite3 *db = nullptr; + EXPECT_EQ(SQLiteUtils::OpenDatabase(property, db), E_OK); + ASSERT_NE(db, nullptr); + + for (const auto &item : insertSqls) { + ASSERT_EQ(SQLiteUtils::ExecuteRawSQL(db, item), E_OK); + } + sqlite3_stmt *statement = nullptr; + ASSERT_EQ(SQLiteUtils::GetStatement(db, insertLocalDataSql, statement), E_OK); + ASSERT_NE(statement, nullptr); + EXPECT_EQ(SQLiteUtils::BindBlobToStatement(statement, 1, g_origValue, false), E_OK); + EXPECT_EQ(SQLiteUtils::StepWithRetry(statement, false), -SQLITE_DONE); + EXPECT_EQ(sqlite3_finalize(statement), SQLITE_OK); + + (void)sqlite3_close_v2(db); + } + + void CheckSyncDataV1ToV2(sqlite3 *db) + { + sqlite3_stmt *statement = nullptr; + ASSERT_EQ(SQLiteUtils::GetStatement(db, CHECK_V1_SYNC_UPGRADE, statement), E_OK); + ASSERT_NE(statement, nullptr); + ASSERT_EQ(SQLiteUtils::StepWithRetry(statement), SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)); + ASSERT_EQ(sqlite3_column_int64(statement, 0), sqlite3_column_int64(statement, 1)); + ASSERT_EQ(sqlite3_finalize(statement), SQLITE_OK); + } + + void CheckSyncDataV2ToV3(sqlite3 *db) + { + sqlite3_stmt *statement = nullptr; + ASSERT_EQ(SQLiteUtils::GetStatement(db, CHECK_V2_SYNC_UPGRADE, statement), E_OK); + ASSERT_NE(statement, nullptr); + ASSERT_EQ(SQLiteUtils::StepWithRetry(statement), SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)); + long int targetFlagValue = sqlite3_column_int64(statement, 0); + ASSERT_EQ(targetFlagValue, stol(g_flag)); + ASSERT_EQ(sqlite3_finalize(statement), SQLITE_OK); + } + + void CheckLocalDataV1ToV2(sqlite3 *db) + { + sqlite3_stmt *statement = nullptr; + ASSERT_EQ(SQLiteUtils::GetStatement(db, CHECK_V1_LOCAL_UPGRADE, statement), E_OK); + ASSERT_NE(statement, nullptr); + ASSERT_EQ(SQLiteUtils::StepWithRetry(statement), SQLiteUtils::MapSQLiteErrno(SQLITE_ROW)); + Timestamp stamp = static_cast(sqlite3_column_int64(statement, 0)); + EXPECT_NE(stamp, 0UL); + + Value readHashValue; + Value calcValue; + EXPECT_EQ(DBCommon::CalcValueHash(g_origValue, calcValue), E_OK); + ASSERT_EQ(SQLiteUtils::GetColumnBlobValue(statement, 1, readHashValue), E_OK); + EXPECT_EQ(readHashValue, calcValue); + ASSERT_EQ(sqlite3_finalize(statement), SQLITE_OK); + } + + void CheckDirectoryV2ToV3(bool expectedValue, bool expecteMetaDbExist) + { + std::string identifier = DBCommon::TransferStringToHex(g_identifier); + std::string newDatabaseName = g_testDir + "/" + identifier + "/" + DBConstant::SINGLE_SUB_DIR + "/" + + DBConstant::MAINDB_DIR + "/" + DBConstant::SINGLE_VER_DATA_STORE + ".db"; + std::string newMetadatabaseName = g_testDir + "/" + identifier + "/" + DBConstant::SINGLE_SUB_DIR + "/" + + DBConstant::METADB_DIR + "/" + DBConstant::SINGLE_VER_META_STORE + ".db"; + std::string newCacheDirectory = g_testDir + "/" + identifier + "/" + DBConstant::SINGLE_SUB_DIR + "/" + + DBConstant::CACHEDB_DIR + "/"; + EXPECT_EQ(OS::CheckPathExistence(newDatabaseName), expectedValue); + EXPECT_EQ(OS::CheckPathExistence(newMetadatabaseName), expecteMetaDbExist); + EXPECT_EQ(OS::CheckPathExistence(newCacheDirectory), expectedValue); + } + + void CheckVersionV3(sqlite3 *db) + { + int version = SOFTWARE_VERSION_BASE; + SQLiteUtils::GetVersion(db, version); + EXPECT_EQ(version, SINGLE_VER_STORE_VERSION_CURRENT); + } + + void CheckSecOpt(const SecurityOption ¤tSecOpt) + { + SecurityOption checkSecOpt; + SecurityOption currentMetaSecOpt {SecurityLabel::S2, SecurityFlag::ECE}; + int errCode = RuntimeContext::GetInstance()->GetSecurityOption(g_maindbPath, checkSecOpt); + EXPECT_TRUE(currentSecOpt == checkSecOpt); + EXPECT_TRUE(errCode == E_OK); + if (OS::CheckPathExistence(g_cachedbPath)) { + errCode = RuntimeContext::GetInstance()->GetSecurityOption(g_cachedbPath, checkSecOpt); + EXPECT_TRUE(currentSecOpt == checkSecOpt); + EXPECT_TRUE(errCode == E_OK); + } + if (OS::CheckPathExistence(g_metadbPath)) { + errCode = RuntimeContext::GetInstance()->GetSecurityOption(g_metadbPath, checkSecOpt); + EXPECT_TRUE(currentMetaSecOpt == checkSecOpt); + EXPECT_TRUE(errCode == E_OK); + } + } + + void GetKvStoreProcess(const KvStoreNbDelegate::Option &option, bool putCheck, bool secOptCheck, + const SecurityOption &secopt) + { + Key keyTmp = {'1'}; + Value valueRead; + Value value = {'7'}; + g_mgr.GetKvStore("TestUpgradeNb", option, g_kvNbDelegateCallback); + ASSERT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + if (secOptCheck) { + CheckSecOpt(secopt); + } + if (putCheck) { + EXPECT_TRUE(g_kvNbDelegatePtr->Put(keyTmp, value) == OK); + EXPECT_TRUE(g_kvNbDelegatePtr->Get(keyTmp, valueRead) == OK); + } + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + g_kvNbDelegatePtr = nullptr; + } +} + +class DistributedDBStorageSingleVerUpgradeTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBStorageSingleVerUpgradeTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + + std::string oriIdentifier = "user0-app0-TestUpgradeNb"; + g_identifier = DBCommon::TransferHashString(oriIdentifier); + std::string identifier = DBCommon::TransferStringToHex(g_identifier); + g_databaseName = "/" + identifier + "/" + DBConstant::SINGLE_SUB_DIR + "/" + + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + g_newDatabaseName = "/" + identifier + "/" + DBConstant::SINGLE_SUB_DIR + "/" + DBConstant::MAINDB_DIR + "/" + + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + g_localdatabaseName = "/" + identifier + "/" + DBConstant::LOCAL_SUB_DIR + "/" + + DBConstant::LOCAL_DATABASE_NAME + DBConstant::SQLITE_DB_EXTENSION; + const int passwdLen = 5; + const int passwdVal = 1; + vector passwdBuffer1(passwdLen, passwdVal); + int errCode = g_passwd.SetValue(passwdBuffer1.data(), passwdBuffer1.size()); + ASSERT_EQ(errCode, CipherPassword::ErrorCode::OK); + g_adapter = std::make_shared(); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(g_adapter); + g_maindbPath = g_testDir + "/" + identifier + "/" + DBConstant::SINGLE_SUB_DIR + "/" + DBConstant::MAINDB_DIR + + "/" + DBConstant::SINGLE_VER_DATA_STORE + DBConstant::SQLITE_DB_EXTENSION; + g_metadbPath = g_testDir + "/" + identifier + "/" + DBConstant::SINGLE_SUB_DIR + "/" + DBConstant::METADB_DIR + + "/" + DBConstant::SINGLE_VER_META_STORE + DBConstant::SQLITE_DB_EXTENSION; + g_cachedbPath = g_testDir + "/" + identifier + "/" + DBConstant::SINGLE_SUB_DIR + "/" + DBConstant::CACHEDB_DIR + + "/" + DBConstant::SINGLE_VER_CACHE_STORE + DBConstant::SQLITE_DB_EXTENSION; +} + +void DistributedDBStorageSingleVerUpgradeTest::TearDownTestCase(void) +{ + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); +} + +void DistributedDBStorageSingleVerUpgradeTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + std::string identifier = DBCommon::TransferStringToHex(g_identifier); + DBCommon::CreateDirectory(g_testDir + "/" + identifier); + DBCommon::CreateDirectory(g_testDir + "/" + identifier + "/" + DBConstant::SINGLE_SUB_DIR); + DBCommon::CreateDirectory(g_testDir + "/" + identifier + "/" + DBConstant::LOCAL_SUB_DIR); +} + +void DistributedDBStorageSingleVerUpgradeTest::TearDown(void) +{ + while (g_forkconcurrent == ForkConcurrentStatus::RUNNING) { + sleep(1); + } + g_adapter->ResetSecOptDic(); + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } +} + +/** + * @tc.name: UpgradeTest001 + * @tc.desc: Test the NbDelegate upgrade from the old version V1. + * @tc.type: FUNC + * @tc.require: AR000DPTQ7 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSingleVerUpgradeTest, UpgradeTest001, TestSize.Level2) +{ + /** + * @tc.steps:step1. create old version V1 db. + */ + std::string dbPath = g_testDir + g_databaseName; + OpenDbProperties property = {dbPath, true, false, ORIG_DATABASE_V1}; + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(g_adapter); + SecurityOption secopt{SecurityLabel::S3, SecurityFlag::SECE}; + CreateDatabase(INSERT_DATA_V1, INSERT_LOCAL_DATA_V1, property); + bool isDatabaseExists = OS::CheckPathExistence(dbPath); + EXPECT_EQ(isDatabaseExists, true); + /** + * @tc.steps:step2. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + option.secOption = secopt; + GetKvStoreProcess(option, true, true, SecurityOption()); + + sqlite3 *db = nullptr; + dbPath = g_testDir + g_newDatabaseName; + property = {dbPath, true, false}; + EXPECT_EQ(SQLiteUtils::OpenDatabase(property, db), E_OK); + ASSERT_NE(db, nullptr); + /** + * @tc.steps:step3. check result is ok. + * @tc.expected: dir is ok,version is ok. + */ + CheckLocalDataV1ToV2(db); + CheckSyncDataV1ToV2(db); + CheckDirectoryV2ToV3(true, false); + CheckVersionV3(db); + (void)sqlite3_close_v2(db); + EXPECT_EQ(g_mgr.DeleteKvStore("TestUpgradeNb"), OK); +} + +/** + * @tc.name: UpgradeTest002 + * @tc.desc: Test the NbDelegate upgrade from the old version V2. + * @tc.type: FUNC + * @tc.require: AR000DPTQ7 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBStorageSingleVerUpgradeTest, UpgradeTest002, TestSize.Level2) +{ + /** + * @tc.steps:step1. create old version V2 db. + */ + std::string dbPath = g_testDir + g_databaseName; + OpenDbProperties property = {dbPath, true, false, ORIG_DATABASE_V2}; + CreateDatabase(INSERT_DATA_V2, INSERT_LOCAL_DATA_V2, property); + SecurityOption secopt{SecurityLabel::S3, SecurityFlag::SECE}; + bool isDatabaseExists = OS::CheckPathExistence(dbPath); + EXPECT_EQ(isDatabaseExists, true); + /** + * @tc.steps:step2. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + option.secOption = secopt; + GetKvStoreProcess(option, true, true, SecurityOption()); + + sqlite3 *db = nullptr; + dbPath = g_testDir + g_newDatabaseName; + property = {dbPath, true, false}; + EXPECT_EQ(SQLiteUtils::OpenDatabase(property, db), E_OK); + ASSERT_NE(db, nullptr); + /** + * @tc.steps:step3. check result is ok. + * @tc.expected: dir is ok,version is ok. + */ + CheckDirectoryV2ToV3(true, false); + CheckVersionV3(db); + (void)sqlite3_close_v2(db); + GetKvStoreProcess(option, false, true, SecurityOption()); + EXPECT_EQ(g_mgr.DeleteKvStore("TestUpgradeNb"), OK); +} +#ifndef OMIT_JSON +/** + * @tc.name: UpgradeTest003 + * @tc.desc: Test the NbDelegate upgrade from the old version V2 with schema. + * @tc.type: FUNC + * @tc.require: AR000DPTQ7 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBStorageSingleVerUpgradeTest, UpgradeTest003, TestSize.Level2) +{ + /** + * @tc.steps:step1. create old version V2 db. + */ + std::string dbPath = g_testDir + g_databaseName; + OpenDbProperties property = {dbPath, true, false, ORIG_DATABASE_V2}; + std::string val = "{\"field_name1\":true, \"field_name2\":{\"field_name3\":1, \"field_name4\":1, \"field_name5\":1,\ + \"field_name6\":\"1\", \"field_name7\":null, \"field_name8\":null}}"; + std::string insertValueSql = "INSERT INTO sync_data VALUES('ab', '"; + insertValueSql += val; + insertValueSql += "', 100, " + g_flag + ", '', '', 'efdef', 100);"; + CreateDatabase(std::vector {insertValueSql}, INSERT_LOCAL_DATA_V2, property); + SecurityOption secopt{SecurityLabel::S3, SecurityFlag::SECE}; + bool isDatabaseExists = OS::CheckPathExistence(dbPath); + EXPECT_EQ(isDatabaseExists, true); + /** + * @tc.steps:step2. Get the nb delegate. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + option.secOption = secopt; + option.schema = "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":{" + "\"field_name3\":\"INTEGER, NOT NULL\"," + "\"field_name4\":\"LONG, DEFAULT 100\"," + "\"field_name5\":\"DOUBLE, NOT NULL, DEFAULT 3.14\"," + "\"field_name6\":\"STRING, NOT NULL, DEFAULT '3.1415'\"," + "\"field_name7\":[]," + "\"field_name8\":{}" + "}" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\", \"$.field_name2.field_name6\"]}"; + GetKvStoreProcess(option, false, true, SecurityOption()); + + sqlite3 *db = nullptr; + dbPath = g_testDir + g_newDatabaseName; + property = {dbPath, true, false}; + EXPECT_EQ(SQLiteUtils::OpenDatabase(property, db), E_OK); + ASSERT_NE(db, nullptr); + /** + * @tc.steps:step3. check result is ok. + * @tc.expected: dir is ok,version is ok. + */ + CheckDirectoryV2ToV3(true, false); + CheckVersionV3(db); + (void)sqlite3_close_v2(db); + + GetKvStoreProcess(option, false, true, SecurityOption()); + EXPECT_EQ(g_mgr.DeleteKvStore("TestUpgradeNb"), OK); +} +#endif +/** + * @tc.name: UpgradeTest004 + * @tc.desc: Test the NbDelegate upgrade from the old version V2 while secOption from NOT_SET to S3SECE. + * @tc.type: FUNC + * @tc.require: AR000DPTQ7 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBStorageSingleVerUpgradeTest, UpgradeTest004, TestSize.Level2) +{ + /** + * @tc.steps:step1. create old version V2 db. + */ + std::string dbPath = g_testDir + g_databaseName; + OpenDbProperties property = {dbPath, true, false, ORIG_DATABASE_V2}; + CreateDatabase(INSERT_DATA_V2, INSERT_LOCAL_DATA_V2, property); + SecurityOption secopt{SecurityLabel::S3, SecurityFlag::SECE}; + SecurityOption checkSecOpt; + bool isDatabaseExists = OS::CheckPathExistence(dbPath); + EXPECT_EQ(isDatabaseExists, true); + /** + * @tc.steps:step2. Get the nb delegate without secoption and Get the nb delegate again with secoption. + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + GetKvStoreProcess(option, false, true, SecurityOption()); + RuntimeContext::GetInstance()->GetSecurityOption(g_maindbPath, checkSecOpt); + EXPECT_TRUE(checkSecOpt.securityLabel == NOT_SET); + + option.secOption = secopt; + GetKvStoreProcess(option, true, true, SecurityOption()); + + sqlite3 *db = nullptr; + dbPath = g_testDir + g_newDatabaseName; + property = {dbPath, true, false}; + EXPECT_EQ(SQLiteUtils::OpenDatabase(property, db), E_OK); + ASSERT_NE(db, nullptr); + /** + * @tc.steps:step3. check result is ok. + * @tc.expected: dir is ok,version is ok. + */ + CheckDirectoryV2ToV3(true, false); + CheckVersionV3(db); + (void)sqlite3_close_v2(db); + + GetKvStoreProcess(option, false, false, secopt); + EXPECT_EQ(g_mgr.DeleteKvStore("TestUpgradeNb"), OK); +} + +HWTEST_F(DistributedDBStorageSingleVerUpgradeTest, UpgradeTest005, TestSize.Level2) +{ + /** + * @tc.steps:step1. create old version V2 db. + */ + std::string dbPath = g_testDir + g_databaseName; + OpenDbProperties property = {dbPath, true, false, ORIG_DATABASE_V2}; + CreateDatabase(INSERT_DATA_V2, INSERT_LOCAL_DATA_V2, property); + SecurityOption secopt = {SecurityLabel::S2, SecurityFlag::ECE}; + bool isDatabaseExists = OS::CheckPathExistence(dbPath); + EXPECT_EQ(isDatabaseExists, true); + /** + * @tc.steps:step2. Get the nb delegate while not sprite meta_db scene + * @tc.expected: step1. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + option.secOption = secopt; + GetKvStoreProcess(option, true, true, SecurityOption()); + + sqlite3 *db = nullptr; + dbPath = g_testDir + g_newDatabaseName; + property = {dbPath, true, false}; + EXPECT_EQ(SQLiteUtils::OpenDatabase(property, db), E_OK); + ASSERT_NE(db, nullptr); + /** + * @tc.steps:step3. check result is ok. + * @tc.expected: dir is ok,version is ok. + */ + CheckSyncDataV2ToV3(db); + CheckDirectoryV2ToV3(true, false); + CheckVersionV3(db); + (void)sqlite3_close_v2(db); + EXPECT_EQ(g_mgr.DeleteKvStore("TestUpgradeNb"), OK); +} + +HWTEST_F(DistributedDBStorageSingleVerUpgradeTest, UpgradeTest006, TestSize.Level2) +{ + /** + * @tc.steps:step1. create old version V2 db. + */ + std::string dbPath = g_testDir + g_databaseName; + OpenDbProperties property = {dbPath, true, false, ORIG_DATABASE_V2}; + CreateDatabase(INSERT_DATA_V2, INSERT_LOCAL_DATA_V2, property); + SecurityOption secopt = {SecurityLabel::S3, SecurityFlag::ECE}; + bool isDatabaseExists = OS::CheckPathExistence(dbPath); + EXPECT_EQ(isDatabaseExists, true); + /** + * @tc.steps:step2. Get the nb delegate while not sprite meta_db scene + * @tc.expected: step2. Get results OK and non-null delegate. + */ + KvStoreNbDelegate::Option option = {true, false, false}; + option.secOption = secopt; + GetKvStoreProcess(option, true, true, SecurityOption()); + + sqlite3 *db = nullptr; + dbPath = g_testDir + g_newDatabaseName; + property = {dbPath, true, false}; + EXPECT_EQ(SQLiteUtils::OpenDatabase(property, db), E_OK); + ASSERT_NE(db, nullptr); + /** + * @tc.steps:step3. check result is ok. + * @tc.expected: dir is ok,version is ok. + */ + CheckSyncDataV2ToV3(db); + CheckDirectoryV2ToV3(true, false); + CheckVersionV3(db); + (void)sqlite3_close_v2(db); + EXPECT_EQ(g_mgr.DeleteKvStore("TestUpgradeNb"), OK); +} diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_sqlite_single_ver_natural_store_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_sqlite_single_ver_natural_store_test.cpp new file mode 100644 index 00000000..51150eab --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_sqlite_single_ver_natural_store_test.cpp @@ -0,0 +1,1081 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_constant.h" +#include "db_common.h" +#include "distributeddb_storage_single_ver_natural_store_testcase.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + DistributedDB::KvStoreConfig g_config; + + std::string g_testDir; + std::string g_databaseName; + std::string g_identifier; + + DistributedDB::SQLiteSingleVerNaturalStore *g_store = nullptr; + DistributedDB::SQLiteSingleVerNaturalStoreConnection *g_connection = nullptr; +} + +class DistributedDBStorageSQLiteSingleVerNaturalStoreTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBStorageSQLiteSingleVerNaturalStoreTest::SetUpTestCase(void) +{} + +void DistributedDBStorageSQLiteSingleVerNaturalStoreTest::TearDownTestCase(void) {} + +void DistributedDBStorageSQLiteSingleVerNaturalStoreTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + LOGD("DistributedDBStorageSQLiteSingleVerNaturalStoreTest dir is %s", g_testDir.c_str()); + std::string oriIdentifier = APP_ID + "-" + USER_ID + "-" + "TestGeneralNB"; + std::string identifier = DBCommon::TransferHashString(oriIdentifier); + std::string g_identifier = DBCommon::TransferStringToHex(identifier); + + g_databaseName = "/" + g_identifier + "/" + DBConstant::SINGLE_SUB_DIR + "/" + DBConstant::MAINDB_DIR + "/" + + DBConstant::SINGLE_VER_DATA_STORE + ".db"; + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir + "/" + g_identifier + "/" + DBConstant::SINGLE_SUB_DIR); + KvDBProperties property; + property.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + property.SetStringProp(KvDBProperties::STORE_ID, "TestGeneralNB"); + property.SetStringProp(KvDBProperties::IDENTIFIER_DIR, g_identifier); + property.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + + g_store = new (std::nothrow) SQLiteSingleVerNaturalStore; + ASSERT_NE(g_store, nullptr); + ASSERT_EQ(g_store->Open(property), E_OK); + + int erroCode = E_OK; + g_connection = static_cast(g_store->GetDBConnection(erroCode)); + ASSERT_NE(g_connection, nullptr); + g_store->DecObjRef(g_store); + EXPECT_EQ(erroCode, E_OK); +} + +void DistributedDBStorageSQLiteSingleVerNaturalStoreTest::TearDown(void) +{ + if (g_connection != nullptr) { + g_connection->Close(); + } + + g_store = nullptr; + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir + "/" + g_identifier + "/" + + DBConstant::SINGLE_SUB_DIR); +} + +/** + * @tc.name: GetSyncData001 + * @tc.desc: To test the function of querying the data in the time stamp range in the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, GetSyncData001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Obtain the data within the time stamp range + * through the GetSyncData(A, C) interface of the NaturalStore, where AB + * @tc.expected: step1. The value of GetSyncData is E_INVALID_ARG. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData003(g_store, g_connection); +} + +/** + * @tc.name: GetSyncData004 + * @tc.desc: To the test database Subcon reading, a large number of data records exist in the time stamp range. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, GetSyncData004, TestSize.Level1) +{ + /** + * @tc.steps:step1. Obtain the data within the time stamp range + * through the GetSyncData(A, B) interface of the NaturalStore. + * @tc.expected: step1. Return E_GET_UNFINISHED. + */ + /** + * @tc.steps:step2. Continue to obtain data through the GetSyncDataNext() interface + * of the NaturalStore until the E_GET_FINISHED message is returned. + * @tc.expected: step2. When the GetSyncDataNext returns E_GET_FINISHED, + * the total number of obtained data is the number of inserted data and the data is consistent. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData004(g_store, g_connection); +} + +/** + * @tc.name: GetSyncData005 + * @tc.desc: In the test database, if a large number of data records exist + * in the time stamp range, a packet is read successfully. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, GetSyncData005, TestSize.Level1) +{ + /** + * @tc.steps:step1. Obtain the data within the time stamp range + * through the GetSyncData(A, B) interface of the NaturalStore. + * @tc.expected: step1. The total size of all data in OK, dataItems is 99K. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData005(g_store, g_connection); +} + +/** + * @tc.name: GetSyncData006 + * @tc.desc: To test the function of reading data when the time stamp range in the database + * is greater than the value of blockSize. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, GetSyncData006, TestSize.Level1) +{ + /** + * @tc.steps:step1. Use the GetSyncData(A, B) interface of the NaturalStore + * and set blockSize to 50 kb to obtain the data within the time stamp range. + * @tc.expected: step1. The system returns E_GET_FINISHED. The size of the obtained data is 1 kb. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetSyncData006(g_store, g_connection); +} + +/** + * @tc.name: PutSyncData001 + * @tc.desc: To test the function of synchronizing the new data of the remote device that synchronizes the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, PutSyncData001, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Set Ioption to synchronous data and insert a (key1, value1) data record by put interface. + */ + /** + * @tc.steps:step3. Insert a (key1, value2!=value1, timestamp, false) data record + * through the PutSyncData interface. The value of timestamp is less than or equal + * to the value of timestamp. For Compare the timestamp to determine whether to synchronization data. + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. The Ioption is set to synchronize data + * through the Get interface to obtain the value data of the key1. + * @tc.expected: step4. Return OK.The obtained value is value1. + */ + /** + * @tc.steps:step5. Insert a (key1, value3!=value1, timestamp, false) data record + * through the PutSyncData interface of the NaturalStore. The value of timestamp + * is greater than that of timestamp inserted in 2. + * @tc.expected: step5. Return OK. + */ + /** + * @tc.steps:step6. The Ioption is set to synchronize data through the Get interface + * to obtain the value data of the key1. + * @tc.expected: step6. Return OK. + */ + /** + * @tc.steps:step7. Insert a (key2, value4) data record through the PutSyncData interface. + * @tc.expected: step7. Return OK. + */ + /** + * @tc.steps:step8. The Ioption is set to synchronize data + * through the Get interface to obtain the value data of the key2. + * @tc.expected: step8. Returns OK, and the obtained data is value4. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::PutSyncData001(g_store, g_connection); +} + +/** + * @tc.name: PutSyncData002 + * @tc.desc: To test the function of synchronizing data from the remote device + * to the local device after the data is deleted from the remote device. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, PutSyncData002, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Set Ioption to synchronous data and insert a (key1, value1) data record by put interface. + */ + /** + * @tc.steps:step3. Insert a (key1, value2!=value1, timestamp, false) data record + * through the PutSyncData interface. The value of timestamp is less than or equal + * to the value of timestamp. For Compare the timestamp to determine whether delete data. + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. The Ioption is set to synchronize data + * through the Get interface to obtain the value data of the key1. + * @tc.expected: step4. Return OK.The obtained value is value1. + */ + /** + * @tc.steps:step5. Insert a (key1, value3!=value1, timestamp, false) data record + * through the PutSyncData interfac. The value of timestamp + * is greater than that of timestamp inserted in step2. + * @tc.expected: step5. Return OK. + */ + /** + * @tc.steps:step6. The Ioption is set to synchronize data through the Get interface + * to obtain the value data of the key1. + * @tc.expected: step6. Return E_NOT_FOUND. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::PutSyncData002(g_store, g_connection); +} + +/** + * @tc.name: PutSyncData003 + * @tc.desc: To test the function of synchronizing the mixed data of the added + * and deleted data from the remote device to the local device. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, PutSyncData003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Insert a data record (key1,value1 is not null) and (key2, value2 is not null) + * through the PutSyncData interface. + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. Set Ioption as the synchronization data to obtain the data of key1 and key2. + * @tc.expected: step2. The Get interface returns OK. The value of key1 is value1, + * and the value of key2 is value2. + */ + /** + * @tc.steps:step3. Insert a (key3, value3) and delete the data of the (key1, value1). + * @tc.expected: step3. The PutSyncData returns OK. + */ + /** + * @tc.steps:step4. Set Ioption to the synchronization data and obtain the data of key1, key2, and key3. + * @tc.expected: step4. Get key1 returns E_NOT_FOUND,Get key2. + * The value of OK,value is value2, the value of Get key3 is OK, + * and the value of value is value3. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::PutSyncData003(g_store, g_connection); +} + +/** + * @tc.name: PutMetaData001 + * @tc.desc: Test metadata insertion and modification. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, PutMetaData001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Run the PutMetaData command to insert a non-empty key1 non-empty value1 data record. + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. Run the PutMetaData command to insert a non-empty key1 non-empty value1 data record. + * @tc.expected: step2. The obtained value is the same as the value of value1. + */ + /** + * @tc.steps:step3. The key value is key1, the value is not empty, + * and the value of value2 is different from the value of value1 through the PutMetaData interface. + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. Run the GetMetaData command to obtain the value of key1 + * and check whether the value is the same as the value of value2. + * @tc.expected: step4. The obtained value is the same as the value of value2. + */ + /** + * @tc.steps:step5. Use PutMetaData to insert a record whose key is empty and value is not empty. + * @tc.expected: step5. Return E_INVALID_ARGS. + */ + /** + * @tc.steps:step6. Use PutMetaData in NaturalStore to insert data whose key2(!=key1) + * is not empty and value is empty. + * @tc.expected: step6. Return OK. + */ + /** + * @tc.steps:step7. Obtain the value of key2 and check whether the value is empty. + * @tc.expected: step7. The obtained value is empty. + */ + /** + * @tc.steps:step8. Insert the data whose key size is 1024 and value size is 4Mb + * through PutMetaData of NaturalStore. + * @tc.expected: step8. Return OK. + */ + /** + * @tc.steps:step9/10. Insert data items whose key size is greater than 1 kb + * or value size greater than 4Mb through PutMetaData of NaturalStore. + * @tc.expected: step9/10. Return E_INVALID_ARGS. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::PutMetaData001(g_store, g_connection); +} + +/** + * @tc.name: GetMetaData001 + * @tc.desc: To test the function of reading the metadata of a key in the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, GetMetaData001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Run the PutMetaData command to insert a non-empty key1 non-empty value1 data record. + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. Run the PutMetaData command to insert a non-empty key1 non-empty value1 data record. + * @tc.expected: step2. The obtained value is the same as the value of value1. + */ + /** + * @tc.steps:step3. The key value is key1, the value is not empty, + * and the value of value2 is different from the value of value1 through the PutMetaData interface. + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps:step4. Run the GetMetaData command to obtain the value of key1 + * and check whether the value is the same as the value of value2. + * @tc.expected: step4. The obtained value is the same as the value of value2. + */ + /** + * @tc.steps:step5. Use PutMetaData to insert a record whose key is empty and value is not empty. + * @tc.expected: step5. Return E_INVALID_ARGS. + */ + /** + * @tc.steps:step6. Use PutMetaData in NaturalStore to insert data whose key2(!=key1) + * is not empty and value is empty. + * @tc.expected: step6. Return OK. + */ + /** + * @tc.steps:step7. Obtain the value of key2 and check whether the value is empty. + * @tc.expected: step7. The obtained value is empty. + */ + /** + * @tc.steps:step8. Insert the data whose key size is 1024 and value size is 4Mb + * through PutMetaData of NaturalStore. + * @tc.expected: step8. Return OK. + */ + /** + * @tc.steps:step9/10. Insert data items whose key size is greater than 1 kb + * or value size greater than 4Mb through PutMetaData of NaturalStore. + * @tc.expected: step9/10. Return E_INVALID_ARGS. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetMetaData001(g_store, g_connection); +} + +/** + * @tc.name: DeleteMetaData001 + * @tc.desc: * @tc.name: To test the function of deleting the metadata with prefix key in the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, DeleteMetaData001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Put a1, b1, a2, b2. + * @tc.expected: step1. Return OK. + */ + /** + * @tc.steps:step2. Delete meta data with prefix key 'b'. + * @tc.expected: step2. Return OK. + */ + /** + * @tc.steps:step3. Get a1, b1, a2, b2. + * @tc.expected: step3. Get a1, a2 successfully, and get b1, b2 failed. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteMetaData001(g_store, g_connection); +} + + +/** + * @tc.name: GetCurrentMaxTimestamp001 + * @tc.desc: To test the function of obtaining the maximum timestamp when a record exists in the database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, GetCurrentMaxTimestamp001, TestSize.Level1) +{ + /** + * @tc.steps:step1/2. Insert a data record into the synchronization database. + */ + /** + * @tc.steps:step3. The current maximum timestamp is A. + */ + /** + * @tc.steps:step4. Insert a data record into the synchronization database. + */ + /** + * @tc.steps:step5. Obtain the maximum timestamp B and check whether B>=A exists. + * @tc.expected: step5. The obtained timestamp is B>=A. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetCurrentMaxTimestamp001(g_store, g_connection); +} + +/** + * @tc.name: GetCurrentMaxTimestamp002 + * @tc.desc: Obtain the maximum timestamp when no record exists in the test record library. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, GetCurrentMaxTimestamp002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Obtains the maximum timestamp in the current database record. + * @tc.expected: step1. Return timestamp is 0. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::GetCurrentMaxTimestamp002(g_store); +} + +/** + * @tc.name: LocalDatabaseOperate001 + * @tc.desc: Test the function of inserting data in the local database of the NaturalStore. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, LocalDatabaseOperate001, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Set Ioption to the local data and insert a record of key1 and value1. + * @tc.expected: step1/2. Return OK. + */ + /** + * @tc.steps: step3. Set Ioption to the local data and obtain the value of key1. + * Check whether the value is the same as the value of value1. + * @tc.expected: step3. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step4. Ioption Set this parameter to the local data. Insert key1. + * The value cannot be empty. value2(!=value1) + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps: step5. Set Ioption to the local data, GetMetaData to obtain the value of key1, + * and check whether the value is the same as the value of value2. + * @tc.expected: step5. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step6. The Ioption parameter is set to the local data. + * The data record whose key is empty and value is not empty is inserted. + * @tc.expected: step6. Return E_INVALID_DATA. + */ + /** + * @tc.steps: step7. Set Ioption to the local data, insert data + * whose key2(!=key1) is not empty, and value is empty. + * @tc.expected: step7. Return OK. + */ + /** + * @tc.steps: step8. Set option to local data, obtain the value of key2, + * and check whether the value is empty. + * @tc.expected: step8. Return OK, value is empty. + */ + /** + * @tc.steps: step9. Ioption Set the local data. + * Insert the data whose key size is 1024 and value size is 4Mb. + * @tc.expected: step9. Return OK. + */ + /** + * @tc.steps: step10/11. Set Ioption to the local data and insert data items + * whose value is greater than 4Mb or key is bigger than 1Kb + * @tc.expected: step10/11. Return E_INVALID_ARGS. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::LocalDatabaseOperate001(g_store, g_connection); +} + +/** + * @tc.name: LocalDatabaseOperate002 + * @tc.desc: Test the function of deleting data from the local database of the NaturalStore. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, LocalDatabaseOperate002, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Set Ioption to the local data and insert a record of key1 and value1. + * @tc.expected: step1/2. Return OK. + */ + /** + * @tc.steps: step3. Set Ioption to the local data and obtain the value of key1. + * Check whether the value is the same as the value of value1. + * @tc.expected: step3. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step4. Ioption Set this parameter to the local data. Insert key1. + * The value cannot be empty. value2(!=value1) + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps: step5. Set Ioption to the local data, GetMetaData to obtain the value of key1, + * and check whether the value is the same as the value of value2. + * @tc.expected: step5. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step6. The Ioption parameter is set to the local data. + * The data record whose key is empty and value is not empty is inserted. + * @tc.expected: step6. Return E_INVALID_DATA. + */ + /** + * @tc.steps: step7. Set Ioption to the local data, insert data + * whose key2(!=key1) is not empty, and value is empty. + * @tc.expected: step7. Return OK. + */ + /** + * @tc.steps: step8. Set option to local data, obtain the value of key2, + * and check whether the value is empty. + * @tc.expected: step8. Return OK, value is empty. + */ + /** + * @tc.steps: step9. Ioption Set the local data. + * Insert the data whose key size is 1024 and value size is 4Mb. + * @tc.expected: step9. Return OK. + */ + /** + * @tc.steps: step10/11. Set Ioption to the local data and insert data items + * whose value is greater than 4Mb or key is bigger than 1Kb + * @tc.expected: step10/11. Return E_INVALID_ARGS. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::LocalDatabaseOperate002(g_store, g_connection); +} + +/** + * @tc.name: LocalDatabaseOperate003 + * @tc.desc: To test the function of reading data from the local database of the NaturalStore. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, LocalDatabaseOperate003, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Set Ioption to the local data and insert a record of key1 and value1. + * @tc.expected: step1/2. Return OK. + */ + /** + * @tc.steps: step3. Set Ioption to the local data and obtain the value of key1. + * Check whether the value is the same as the value of value1. + * @tc.expected: step3. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step4. Ioption Set this parameter to the local data. Insert key1. + * The value cannot be empty. value2(!=value1) + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps: step5. Set Ioption to the local data, GetMetaData to obtain the value of key1, + * and check whether the value is the same as the value of value2. + * @tc.expected: step5. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step6. The Ioption parameter is set to the local data. + * The data record whose key is empty and value is not empty is inserted. + * @tc.expected: step6. Return E_INVALID_DATA. + */ + /** + * @tc.steps: step7. Set Ioption to the local data, insert data + * whose key2(!=key1) is not empty, and value is empty. + * @tc.expected: step7. Return OK. + */ + /** + * @tc.steps: step8. Set option to local data, obtain the value of key2, + * and check whether the value is empty. + * @tc.expected: step8. Return OK, value is empty. + */ + /** + * @tc.steps: step9. Ioption Set the local data. + * Insert the data whose key size is 1024 and value size is 4Mb. + * @tc.expected: step9. Return OK. + */ + /** + * @tc.steps: step10/11. Set Ioption to the local data and insert data items + * whose value is greater than 4Mb or key is bigger than 1Kb + * @tc.expected: step10/11. Return E_INVALID_ARGS. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::LocalDatabaseOperate003(g_store, g_connection); +} + +/** + * @tc.name: SyncDatabaseOperate001 + * @tc.desc: To test the function of inserting data of the local device in the synchronization database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, SyncDatabaseOperate001, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Set Ioption to the local data and insert a record of key1 and value1. + * @tc.expected: step1/2. Return OK. + */ + /** + * @tc.steps: step3. Set Ioption to the local data and obtain the value of key1. + * Check whether the value is the same as the value of value1. + * @tc.expected: step3. The obtained value and value2 are the same. + */ + /** + * @tc.steps: step4. Ioption Set this parameter to the local data. Insert key1. + * The value cannot be empty. value2(!=value1) + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps: step5. Set Ioption to the local data, GetMetaData to obtain the value of key1, + * and check whether the value is the same as the value of value2. + * @tc.expected: step5. The obtained and value2 are the same. + */ + /** + * @tc.steps: step6. The Ioption parameter is set to the local data. + * The data record whose key is empty and value is not empty is inserted. + * @tc.expected: step6. Return E_INVALID_DATA. + */ + /** + * @tc.steps: step7. Set Ioption to the local data, insert data + * whose key2(!=key1) is not empty, and value is empty. + * @tc.expected: step7. Return OK. + */ + /** + * @tc.steps: step8. Set option to local data, obtain the value of key2, + * and check whether the value is empty. + * @tc.expected: step8. Return OK, value is empty. + */ + /** + * @tc.steps: step9. Ioption Set the local data. + * Insert the data whose key size is 1024 and value size is 4Mb. + * @tc.expected: step9. Return OK. + */ + /** + * @tc.steps: step10/11. Set Ioption to the local data and insert data items + * whose value is greater than 4Mb or key is bigger than 1Kb + * @tc.expected: step10/11. Return E_INVALID_ARGS. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate001(g_store, g_connection); +} + +/** + * @tc.name: SyncDatabaseOperate002 + * @tc.desc: test the put operation after data synced from other devices. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, SyncDatabaseOperate002, TestSize.Level1) +{ + /** + * @tc.steps: step1/2. Add a remote synchronization data record. (key1, value1). + */ + /** + * @tc.steps: step3. Ioption is set to synchronous data. Obtains the value data of the key1. + * @tc.expected: step3. Return OK. The value is the same as the value of value1. + */ + /** + * @tc.steps: step4. Ioption Set the data to be synchronized and insert the data of key1,value2. + * @tc.expected: step4. Return OK. + */ + /** + * @tc.steps: step3. Ioption is set to synchronous data. Obtains the value data of the key1. + * @tc.expected: step3. Return OK. The value is the same as the value of value2. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate002(g_store, g_connection); +} + +/** + * @tc.name: SyncDatabaseOperate003 + * @tc.desc: test the delete operation in sync database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, SyncDatabaseOperate003, TestSize.Level1) +{ + /** + * @tc.steps: step2. Set Ioption to the local data and delete the data whose key is key1 (empty). + * @tc.expected: step2. Return E_INVALID_ARGS. + */ + /** + * @tc.steps: step3. Set Ioption to the local data, insert non-null key1, and non-null value1 data. + * @tc.expected: step3. Return E_OK. + */ + /** + * @tc.steps: step4. Set Ioption to the local data, obtain the value of key1, + * and check whether the value is the same as that of value1. + * @tc.expected: step4. Return E_OK. The obtained value is the same as the value of value1. + */ + /** + * @tc.steps: step5. Set Ioption to the local data and delete the data whose key is key1. + * @tc.expected: step5. Return E_OK. + */ + /** + * @tc.steps: step5. Set Ioption to the local data and obtain the value of Key1. + * @tc.expected: step5. Return E_NOT_FOUND. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate003(g_store, g_connection); +} + +/** + * @tc.name: SyncDatabaseOperate004 + * @tc.desc: test the delete for the data from other devices in sync database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, SyncDatabaseOperate004, TestSize.Level1) +{ + /** + * @tc.steps: step2. The Ioption parameter is set to synchronize data to obtain the value data of the key1. + * @tc.expected: step2. Return OK. The value is the same as the value of value1. + */ + /** + * @tc.steps: step3. The Ioption parameter is set to synchronize data, and the key1 data is deleted. + * @tc.expected: step3. Return OK. + */ + /** + * @tc.steps: step4. The Ioption parameter is set to synchronize data to obtain the value data of the key1. + * @tc.expected: step4. Return E_NOT_FOUND. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate004(g_store, g_connection); +} + +/** + * @tc.name: SyncDatabaseOperate005 + * @tc.desc: test the reading for sync database. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, SyncDatabaseOperate005, TestSize.Level1) +{ + /** + * @tc.steps: step2. Set Ioption to the local data and delete the data whose key is key1 (empty). + * @tc.expected: step2. Return E_INVALID_ARGS. + */ + /** + * @tc.steps: step3. Set Ioption to the local data, insert non-null key1, and non-null value1 data. + * @tc.expected: step3. Return E_OK. + */ + /** + * @tc.steps: step4. Set Ioption to the local data, obtain the value of key1, + * and check whether the value is the same as that of value1. + * @tc.expected: step4. Return E_OK. The obtained value is the same as the value of value1. + */ + /** + * @tc.steps: step5. Set Ioption to the local data and obtain the value data of Key1. + * Check whether the value is the same as the value of value2. + * @tc.expected: step4. Return E_OK, and the value is the same as the value of value2. + */ + /** + * @tc.steps: step5. The Ioption is set to the local. + * The data of the key1 and value2(!=value1) is inserted. + * @tc.expected: step4. Return E_OK. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate005(g_store, g_connection); +} + +/** + * @tc.name: SyncDatabaseOperate006 + * @tc.desc: test the get entries for sync database + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, SyncDatabaseOperate006, TestSize.Level1) +{ + /** + * @tc.steps: step2/3/4. Set Ioption to synchronous data. + * Insert the data of key=keyPrefix + 'a', value1. + * Insert the data of key=keyPrefix + 'c', value2. + * Insert the data of key length=keyPrefix length - 1, value3. + * @tc.expected: step2/3/4. Return E_NOT_FOUND. + */ + /** + * @tc.steps: step5. Obtain all data whose prefixKey is keyPrefix. + * @tc.expected: step5. Return OK. The number of obtained data records is 2. + */ + /** + * @tc.steps: step6. Obtain all data whose prefixKey is empty. + * @tc.expected: step6. Return OK. The number of obtained data records is 3. + */ + /** + * @tc.steps: step7. Obtain all data whose prefixKey is keyPrefix. + * @tc.expected: step7. Return E_NOT_SUPPORT. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::SyncDatabaseOperate006(g_store, g_connection); +} + +/** + * @tc.name: ClearRemoteData001 + * @tc.desc: test the clear data synced from the remote by device. + * @tc.type: FUNC + * @tc.require: AR000CIFDA AR000CQS3T + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, ClearRemoteData001, TestSize.Level1) +{ + /** + * @tc.steps: step1. New data is inserted to the B end of the device. [keyB, valueB]. + */ + /** + * @tc.steps: step2. The device pulls the data of the device B, and the device inserts the [keyA, valueA]. + */ + /** + * @tc.steps: step3. The device obtains the data of keyA and valueB. + * @tc.expected: step3. Obtain [keyA, valueA] and [keyB, valueB]. + */ + /** + * @tc.steps: step4.Invoke the interface for clearing the synchronization data of the B device. + */ + /** + * @tc.steps: step5. The device obtains the data of keyA and valueB. + * @tc.expected: step5. The value of [keyA, valueA] is obtained, + * and the value of NOT_FOUND is obtained by querying keyB. + */ + DistributedDBStorageSingleVerNaturalStoreTestCase::ClearRemoteData001(g_store, g_connection); +} + +/** + * @tc.name: DeleteUserKeyValue001 + * @tc.desc: When a user deletes a data record, the system clears the user record. + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, DeleteUserKeyValue001, TestSize.Level1) +{ + /** + * @tc.steps: step1. delete K1. + * @tc.expected: step1. delete K1 successfully. + */ + /** + * @tc.steps: step2. Real query by sqlite3. + * @tc.expected: step2. Find KEY_1, not find K2. + */ + const std::string url = g_testDir + g_databaseName; + DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue001(g_store, g_connection, url); +} + +/** + * @tc.name: DeleteUserKeyValue002 + * @tc.desc: After the synchronization library data is deleted locally, add the same key data locally. + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, DeleteUserKeyValue002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Delete key1 data via Delete interface. + * @tc.expected: step1. Delete successfully. + */ + /** + * @tc.steps: step2. New data from key1, value3 via Put interface. + * @tc.expected: step2. New data from key1, value3 via Put interface successfully. + */ + /** + * @tc.steps: step3. Query key1 data via Get interface. + * @tc.expected: step3. Query key1 data via Get interface successfully, get value3 by key1. + */ + /** + * @tc.steps: step4. Query key1 real data by sqlite3. + * @tc.expected: step4. Two records were found. + */ + const std::string url = g_testDir + g_databaseName; + DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue002(g_store, g_connection, url); +} + +/** + * @tc.name: DeleteUserKeyValue003 + * @tc.desc: After the synchronization database data is deleted locally, the same key data is added from the remote end. + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, DeleteUserKeyValue003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Delete data by key1. + * @tc.expected: step1. Delete successfully. + */ + /** + * @tc.steps: step2. Get data by key1. + * @tc.expected: step1. Key1 not exist in database. + */ + /** + * @tc.steps: step3. Get a new data from remote device B , key1, value3, + * with a smaller timestamp than the current timestamp. + */ + /** + * @tc.steps: step4. Get data by key1. + * @tc.expected: step4. Key1 not exist in database. + */ + /** + * @tc.steps: step5. Get a new data from remote device C , key1, value4, + * and the timestamp is larger than the current timestamp. + */ + /** + * @tc.steps: step6. Get data by key1. + * @tc.expected: step6. Key1 not exist in database. + */ + /** + * @tc.steps: step7. Get real data by key1. + * @tc.expected: step7. Get 1 record. + */ + const std::string url = g_testDir + g_databaseName; + DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue003(g_store, g_connection, url); +} + +/** + * @tc.name: DeleteUserKeyValue004 + * @tc.desc: Changes in key after remote delete data syncs to local + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, DeleteUserKeyValue004, TestSize.Level1) +{ + /** + * @tc.steps: step1 2 3. Synchronize data to another device B; delete key1 data from device B; + * pull the action of key1 to local. + */ + /** + * @tc.steps: step4. Close database. + */ + /** + * @tc.steps: step5 6. Get real data by key1;and get the number of records. + * @tc.expected: step5 6. Not exist key1 real data in database;Get 1 record. + */ + const std::string url = g_testDir + g_databaseName; + DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue004(g_store, g_connection, url); +} + +/** + * @tc.name: DeleteUserKeyValue005 + * @tc.desc: New unified key data locally after remote delete data syncs to local + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, DeleteUserKeyValue005, TestSize.Level1) +{ + /** + * @tc.steps: step1 2 3. Synchronize data to another device B; delete key1 data from device B; + * pull the action of key1 to local. + */ + /** + * @tc.steps: step4. Put K1 V1 to database. + * @tc.expected: step4. Put successfully. + */ + /** + * @tc.steps: step5. Close database. + */ + /** + * @tc.steps: step6 7. Get real data by key1;and get the number of records. + * @tc.expected: step6 7. Not exist key1 real data in database;Get 2 record. + */ + const std::string url = g_testDir + g_databaseName; + DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue005(g_store, g_connection, url); +} + +/** + * @tc.name: DeleteUserKeyValue006 + * @tc.desc: After the remote delete data is synced to the local, + * the same key data is added from the remote other devices + * @tc.type: FUNC + * @tc.require: AR000CKRTC AR000CQE0D + * @tc.author: sunpeng + */ +HWTEST_F(DistributedDBStorageSQLiteSingleVerNaturalStoreTest, DeleteUserKeyValue006, TestSize.Level1) +{ + /** + * @tc.steps: step1. Remote device B sync deletes data key1 and pushes to local. + */ + /** + * @tc.steps: step2. Get key1 from database. + * @tc.expected: step2. Not exist key1. + */ + /** + * @tc.steps: step3. Remote device C syncs new data (key1, value2), + * timestamp is less than delete timestamp, to local. + */ + /** + * @tc.steps: step4. Get key1 from database. + * @tc.expected: step4. Not exist key1. + */ + /** + * @tc.steps: step5. Remote device C syncs new data (key1, value2), + * timestamp is bigger than delete timestamp, to local. + */ + /** + * @tc.steps: step6. Get key1 from database. + * @tc.expected: step6. Exist key1. + */ + /** + * @tc.steps: step7. Get real data from database. + * @tc.expected: step7. Get 1 record. + */ + const std::string url = g_testDir + g_databaseName; + DistributedDBStorageSingleVerNaturalStoreTestCase::DeleteUserKeyValue006(g_store, g_connection, url); +} + diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_subscribe_query_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_subscribe_query_test.cpp new file mode 100644 index 00000000..7d7fc916 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_subscribe_query_test.cpp @@ -0,0 +1,674 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_errno.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "generic_single_ver_kv_entry.h" +#include "kvdb_manager.h" +#include "process_communicator_test_stub.h" +#include "process_system_api_adapter_impl.h" +#include "query_sync_object.h" +#include "sqlite_single_ver_natural_store.h" +#include "sqlite_single_ver_natural_store_connection.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { +DistributedDB::KvStoreConfig g_config; +std::string g_testDir; +string g_resourceDir; + +KvStoreDelegateManager g_mgr(APP_ID, USER_ID); +// define the g_kvDelegateCallback, used to get some information when open a kv store. +DBStatus g_kvDelegateStatus = INVALID_ARGS; +KvStoreNbDelegate *g_kvNbDelegatePtr = nullptr; +auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); + +const uint8_t PRESET_DATA_SIZE = 2; +const std::string SUBSCRIBE_ID = "680A20600517073AE306B11FEA8306C57DC5102CD33E322F7C513176AA707F0C"; + +const std::string REMOTE_DEVICE_ID = "remote_device_id"; +const std::string REMOTE_DEVICE_A = "remote_device_A"; +const std::string REMOTE_DEVICE_B = "remote_device_B"; +const Key PREFIX_KEY = { 'k' }; +const Key KEY1 = { 'k', '1' }; +const Key KEY2 = { 'k', '2' }; +const Key KEY3 = { 'k', '3' }; +const Value VALUE1 = { 'v', '1' }; +const Value VALUE2 = { 'v', '2' }; +const Value VALUE3 = { 'v', '3' }; + +const std::string NORMAL_FBS_FILE_NAME = "normal_fbs.bfbs"; +const string SCHEMA_STRING = + "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":\"BOOL\"," + "\"field_name3\":\"INTEGER, NOT NULL\"," + "\"field_name4\":\"LONG, DEFAULT 100\"," + "\"field_name5\":\"DOUBLE, DEFAULT 3.14\"," + "\"field_name6\":\"STRING, DEFAULT '3.1415'\"," + "\"field_name7\":\"LONG, DEFAULT 100\"," + "\"field_name8\":\"LONG, DEFAULT 100\"," + "\"field_name9\":\"LONG, DEFAULT 100\"," + "\"field_name10\":\"LONG, DEFAULT 100\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\", \"$.field_name2\"]}"; + +void PreSetData(uint8_t dataNum) +{ + EXPECT_GE(dataNum, 0); // 0 No preset data + EXPECT_LT(dataNum, 128); // 128 Max preset data size + for (uint8_t i = 0; i < dataNum; i++) { + Key keyA = {'K', i}; + Value value; + std::string validJsonData; + if (i % 2 == 0) { // 2 : for data construct + validJsonData = R"({"field_name1":false,"field_name2":true,"field_name3":100})"; + } else { + validJsonData = R"({"field_name1":false,"field_name2":false,"field_name3":100})"; + } + value.assign(validJsonData.begin(), validJsonData.end()); + EXPECT_EQ(g_kvNbDelegatePtr->Put(keyA, value), E_OK); + } +} + +void CreateAndGetStore(const std::string &storeId, const std::string &schemaString, + SQLiteSingleVerNaturalStoreConnection *&conn, SQLiteSingleVerNaturalStore *&store, uint8_t preSetDataNum = 0) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = schemaString; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + EXPECT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + PreSetData(preSetDataNum); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); + + std::string oriIdentifier = USER_ID + "-" + APP_ID + "-" + storeId; + std::string identifier = DBCommon::TransferHashString(oriIdentifier); + KvDBProperties property; + property.SetStringProp(KvDBProperties::IDENTIFIER_DATA, identifier); + std::string identifierHex = DBCommon::TransferStringToHex(identifier); + property.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + property.SetStringProp(KvDBProperties::STORE_ID, storeId); + property.SetBoolProp(KvDBProperties::MEMORY_MODE, false); + property.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::SINGLE_VER_TYPE); + property.SetStringProp(KvDBProperties::IDENTIFIER_DIR, identifierHex); + property.SetIntProp(KvDBProperties::CONFLICT_RESOLVE_POLICY, ConflictResolvePolicy::LAST_WIN); + + if (!schemaString.empty()) { + SchemaObject schemaObj; + schemaObj.ParseFromSchemaString(schemaString); + EXPECT_EQ(schemaObj.IsSchemaValid(), true); + property.SetSchema(schemaObj); + } + + int errCode = E_OK; + conn = static_cast(KvDBManager::GetDatabaseConnection(property, errCode)); + EXPECT_EQ(errCode, E_OK); + ASSERT_NE(conn, nullptr); + store = static_cast(KvDBManager::OpenDatabase(property, errCode)); + EXPECT_EQ(errCode, E_OK); + ASSERT_NE(store, nullptr); +} + +#ifndef OMIT_FLATBUFFER +std::string FbfFileToSchemaString(const std::string &fileName) +{ + std::string filePath = g_resourceDir + "fbs_files_for_ut/" + fileName; + std::ifstream is(filePath, std::ios::binary | std::ios::ate); + if (!is.is_open()) { + LOGE("[FbfFileToSchemaString] open file failed name : %s", filePath.c_str()); + return ""; + } + + auto size = is.tellg(); + LOGE("file size %u", static_cast(size)); + std::string schema(size, '\0'); + is.seekg(0); + if (is.read(&schema[0], size)) { + return schema; + } + LOGE("[FbfFileToSchemaString] read file failed path : %s", filePath.c_str()); + return ""; +} +#endif + +void CheckDataNumByKey(const std::string &storeId, const Key& key, size_t expSize) +{ + KvStoreNbDelegate::Option option = {true, false, false}; + option.schema = SCHEMA_STRING; + g_mgr.GetKvStore(storeId, option, g_kvNbDelegateCallback); + EXPECT_TRUE(g_kvNbDelegatePtr != nullptr); + EXPECT_TRUE(g_kvDelegateStatus == OK); + std::vector entries; + EXPECT_EQ(g_kvNbDelegatePtr->GetEntries(key, entries), E_OK); + EXPECT_TRUE(entries.size() == expSize); + EXPECT_EQ(g_mgr.CloseKvStore(g_kvNbDelegatePtr), OK); +} +} + +class DistributedDBStorageSubscribeQueryTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp() override; + void TearDown() override; +}; + +static std::shared_ptr g_adapter; +void DistributedDBStorageSubscribeQueryTest::SetUpTestCase(void) +{ + g_mgr.SetProcessLabel("DistributedDBStorageSubscribeQueryTest", "test"); + g_mgr.SetProcessCommunicator(std::make_shared()); // export and import get devID + + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + ASSERT_EQ(DistributedDBToolsUnitTest::GetResourceDir(g_resourceDir), E_OK); + LOGD("Test dir is %s", g_testDir.c_str()); + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir + "/TestQuerySync/" + DBConstant::SINGLE_SUB_DIR); + + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + + g_adapter = std::make_shared(); + EXPECT_TRUE(g_adapter != nullptr); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(g_adapter); +} + +void DistributedDBStorageSubscribeQueryTest::TearDownTestCase(void) +{ + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); +} + +void DistributedDBStorageSubscribeQueryTest::SetUp() +{ + Test::SetUp(); + DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBStorageSubscribeQueryTest::TearDown() +{ + Test::TearDown(); + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir); +} + +/** + * @tc.name: CheckAndInitQueryCondition001 + * @tc.desc: Check the condition is legal or not with json schema + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageSubscribeQueryTest, CheckAndInitQueryCondition001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Create a json schema db, get the natural store instance. + * @tc.expected: step1. Get results OK and non-null store. + */ + SQLiteSingleVerNaturalStoreConnection *conn = nullptr; + SQLiteSingleVerNaturalStore *store = nullptr; + CreateAndGetStore("SchemaCondition01", SCHEMA_STRING, conn, store); + + /** + * @tc.steps:step2. Create a query with prefixKey only, check it as condition. + * @tc.expected: step2. Check condition return E_OK. + */ + Query query1 = Query::Select().PrefixKey({}); + QueryObject queryObject1(query1); + int errCode = store->CheckAndInitQueryCondition(queryObject1); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps:step3. Create a query with predicate, check it as condition. + * @tc.expected: step3. Check condition return E_OK. + */ + Query query2 = Query::Select().GreaterThan("field_name3", 10); + QueryObject queryObject2(query2); + errCode = store->CheckAndInitQueryCondition(queryObject2); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps:step4. Create a query with invalid field, check it as condition. + * @tc.expected: step4. Check condition return E_INVALID_QUERY_FIELD. + */ + Query query3 = Query::Select().GreaterThan("field_name11", 10); + QueryObject queryObject3(query3); + errCode = store->CheckAndInitQueryCondition(queryObject3); + EXPECT_EQ(errCode, -E_INVALID_QUERY_FIELD); + + /** + * @tc.steps:step5. Create a query with invalid format, check it as condition. + * @tc.expected: step5. Check condition return E_INVALID_QUERY_FORMAT. + */ + Query query4 = Query::Select().GreaterThan("field_name3", 10).And().BeginGroup(). + LessThan("field_name3", 100).OrderBy("field_name3").EndGroup(); + QueryObject queryObject4(query4); + errCode = store->CheckAndInitQueryCondition(queryObject4); + EXPECT_EQ(errCode, -E_INVALID_QUERY_FORMAT); + + /** + * @tc.steps:step6. Close natural store + * @tc.expected: step6. Close ok + */ + RefObject::KillAndDecObjRef(store); + KvDBManager::ReleaseDatabaseConnection(conn); +} + +#ifndef OMIT_FLATBUFFER +/** + * @tc.name: CheckAndInitQueryCondition002 + * @tc.desc: Check the condition always illegal with flatbuffer schema + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageSubscribeQueryTest, CheckAndInitQueryCondition002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Create a flatbuffer schema db, get the natural store instance. + * @tc.expected: step1. Get results OK and non-null store. + */ + std::string fbSchema = FbfFileToSchemaString(NORMAL_FBS_FILE_NAME); + EXPECT_FALSE(fbSchema.empty()); + SQLiteSingleVerNaturalStoreConnection *conn = nullptr; + SQLiteSingleVerNaturalStore *store = nullptr; + CreateAndGetStore("SchemaCondition02", fbSchema, conn, store); + + /** + * @tc.steps:step2. Create a query, check it as condition. + * flatbuffer schema is not support with querySync and subscribe. + * @tc.expected: step2. Check condition return E_NOT_SUPPORT. + */ + Query query1 = Query::Select().PrefixKey({}); + QueryObject queryObject1(query1); + int errCode = store->CheckAndInitQueryCondition(queryObject1); + EXPECT_EQ(errCode, -E_NOT_SUPPORT); + + /** + * @tc.steps:step3. Close natural store + * @tc.expected: step3. Close ok + */ + RefObject::KillAndDecObjRef(store); + KvDBManager::ReleaseDatabaseConnection(conn); +} +#endif + +/** + * @tc.name: CheckAndInitQueryCondition003 + * @tc.desc: Check the condition always illegal with flatbuffer schema + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageSubscribeQueryTest, CheckAndInitQueryCondition003, TestSize.Level1) +{ + /** + * @tc.steps:step1. Create a kv db, get the natural store instance. + * @tc.expected: step1. Get results OK and non-null store. + */ + SQLiteSingleVerNaturalStoreConnection *conn = nullptr; + SQLiteSingleVerNaturalStore *store = nullptr; + CreateAndGetStore("SchemaCondition03", "", conn, store); + + /** + * @tc.steps:step2. Create a prefixKey query, check it as condition. + * @tc.expected: step2. Check condition return E_OK. + */ + Query query1 = Query::Select().PrefixKey({}); + QueryObject queryObject1(query1); + int errCode = store->CheckAndInitQueryCondition(queryObject1); + EXPECT_EQ(errCode, E_OK); + + /** + * @tc.steps:step2. Create a predicate query, check it as condition. + * @tc.expected: step2. Check condition return E_NOT_SUPPORT. + */ + Query query2 = Query::Select().GreaterThan("field_name3", 10); + QueryObject queryObject2(query2); + errCode = store->CheckAndInitQueryCondition(queryObject2); + EXPECT_EQ(errCode, -E_NOT_SUPPORT); + + /** + * @tc.steps:step3. Close natural store + * @tc.expected: step3. Close ok + */ + RefObject::KillAndDecObjRef(store); + KvDBManager::ReleaseDatabaseConnection(conn); +} + +/** + * @tc.name: PutSyncDataTestWithQuery + * @tc.desc: put remote devices sync data(get by query sync or subscribe) with query. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageSubscribeQueryTest, PutSyncDataTestWithQuery, TestSize.Level1) +{ + /** + * @tc.steps:step1. create and open a schema store, preset some data; + * @tc.expected: step1. open success + */ + const std::string storeId = "PutSyncData01"; + SQLiteSingleVerNaturalStoreConnection *conn = nullptr; + SQLiteSingleVerNaturalStore *store = nullptr; + CreateAndGetStore(storeId, SCHEMA_STRING, conn, store, PRESET_DATA_SIZE); + + /** + * @tc.steps:step2. Construct sync data + * @tc.expected: OK + */ + Key key; + Value value; + Timestamp now = store->GetCurrentTimestamp(); + LOGD("now time is : %ld", now); + std::vector data; + for (uint8_t i = 0; i < PRESET_DATA_SIZE; i++) { + DataItem item{key, value, now, DataItem::REMOTE_DEVICE_DATA_MISS_QUERY, REMOTE_DEVICE_ID, now}; + item.key.clear(); + DBCommon::CalcValueHash({'K', i}, item.key); + EXPECT_EQ(item.key.empty(), false); + data.push_back(item); + } + + /** + * @tc.steps:step3. put sync data with query + * @tc.expected: step3. data put success + */ + Query query = Query::Select().EqualTo("field_name2", true); + QueryObject queryObj(query); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, data, REMOTE_DEVICE_ID, queryObj), E_OK); + + RefObject::KillAndDecObjRef(store); + KvDBManager::ReleaseDatabaseConnection(conn); + + /** + * @tc.steps:step4. Check sync data + * @tc.expected: step4. check data ok + */ + CheckDataNumByKey(storeId, {'K'}, PRESET_DATA_SIZE / 2); +} + +/** + * @tc.name: PutSyncDataTestWithQuery002 + * @tc.desc: put remote devices sync data(timestamp is smaller then DB data) with query. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageSubscribeQueryTest, PutSyncDataTestWithQuery002, TestSize.Level1) +{ + /** + * @tc.steps:step1. create and open a schema store, preset some data; + * @tc.expected: step1. open success + */ + const std::string storeId = "PutSyncData02"; + + SQLiteSingleVerNaturalStoreConnection *conn = nullptr; + SQLiteSingleVerNaturalStore *store = nullptr; + CreateAndGetStore(storeId, SCHEMA_STRING, conn, store); + + Key key({'K', 'e', 'y'}); + Value value; + Timestamp now = store->GetCurrentTimestamp(); + /** + * @tc.steps:step2. put sync data + * @tc.expected: OK + */ + std::string validJsonData(R"({"field_name1":false,"field_name2":true,"field_name3":100})"); + value.assign(validJsonData.begin(), validJsonData.end()); + std::vector data; + DataItem item{key, value, now, DataItem::LOCAL_FLAG, REMOTE_DEVICE_ID, now}; + data.push_back(item); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, data, REMOTE_DEVICE_ID), E_OK); + + /** + * @tc.steps:step3. put sync miss query data with smaller timestamp + * @tc.expected: OK + */ + data.clear(); + value.clear(); + DataItem itemMiss{key, value, now - 1, DataItem::REMOTE_DEVICE_DATA_MISS_QUERY, REMOTE_DEVICE_ID, now - 1}; + itemMiss.key.clear(); + DBCommon::CalcValueHash({'K', 'e', 'y'}, itemMiss.key); + EXPECT_EQ(itemMiss.key.empty(), false); + data.push_back(itemMiss); + Query query = Query::Select().EqualTo("field_name2", true); + QueryObject queryObj(query); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, data, REMOTE_DEVICE_ID, queryObj), E_OK); + + RefObject::KillAndDecObjRef(store); + KvDBManager::ReleaseDatabaseConnection(conn); + + /** + * @tc.steps:step4. Check sync data + * @tc.expected: check data ok, data {key} is not erased. + */ + CheckDataNumByKey(storeId, {'K', 'e', 'y'}, 1); +} + +/** + * @tc.name: PutSyncDataTestWithQuery003 + * @tc.desc: put remote devices sync data(with same timestamp in DB data but different devices) with query. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageSubscribeQueryTest, PutSyncDataTestWithQuery003, TestSize.Level1) +{ + /** + * @tc.steps:step1. create and open a schema store, preset some data; + * @tc.expected: step1. open success + */ + const std::string storeId = "PutSyncData03"; + + SQLiteSingleVerNaturalStoreConnection *conn = nullptr; + SQLiteSingleVerNaturalStore *store = nullptr; + CreateAndGetStore(storeId, SCHEMA_STRING, conn, store); + + Key key({'K', 'e', 'y'}); + Value value; + Timestamp now = store->GetCurrentTimestamp(); + /** + * @tc.steps:step2. put sync data + * @tc.expected: OK + */ + std::string validJsonData(R"({"field_name1":false,"field_name2":true,"field_name3":100})"); + value.assign(validJsonData.begin(), validJsonData.end()); + std::vector data; + DataItem item{key, value, now, DataItem::LOCAL_FLAG, REMOTE_DEVICE_ID, now}; + data.push_back(item); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, data, REMOTE_DEVICE_ID), E_OK); + + /** + * @tc.steps:step3. put sync miss query data with same timestamp + * @tc.expected: OK + */ + data.clear(); + DataItem itemMiss{key, {}, now, DataItem::REMOTE_DEVICE_DATA_MISS_QUERY, REMOTE_DEVICE_ID, now}; + itemMiss.key.clear(); + DBCommon::CalcValueHash({'K', 'e', 'y'}, itemMiss.key); + EXPECT_EQ(itemMiss.key.empty(), false); + data.push_back(itemMiss); + Query query = Query::Select().EqualTo("field_name2", true); + QueryObject queryObj(query); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, data, REMOTE_DEVICE_ID, queryObj), E_OK); + + RefObject::KillAndDecObjRef(store); + KvDBManager::ReleaseDatabaseConnection(conn); + + /** + * @tc.steps:step4. Check sync data + * @tc.expected: check data ok, data {key} is not erased. + */ + CheckDataNumByKey(storeId, {'K', 'e', 'y'}, 1); +} + +/** + * @tc.name: PutSyncDataTestWithQuery004 + * @tc.desc: put remote devices sync data(with same timestamp in DB data but different devices) with query. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageSubscribeQueryTest, PutSyncDataTestWithQuery004, TestSize.Level1) +{ + /** + * @tc.steps:step1. create and open a schema store, preset some data; + * @tc.expected: step1. open success + */ + const std::string storeId = "PutSyncData04"; + SQLiteSingleVerNaturalStoreConnection *conn = nullptr; + SQLiteSingleVerNaturalStore *store = nullptr; + CreateAndGetStore(storeId, SCHEMA_STRING, conn, store); + + Key key({'K', 'e', 'y'}); + Value value; + Timestamp now = store->GetCurrentTimestamp(); + /** + * @tc.steps:step2. put sync data + * @tc.expected: OK + */ + std::string validJsonData(R"({"field_name1":false,"field_name2":true,"field_name3":100})"); + value.assign(validJsonData.begin(), validJsonData.end()); + std::vector data; + DataItem item{key, value, now, DataItem::LOCAL_FLAG, REMOTE_DEVICE_A, now}; + data.push_back(item); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, data, REMOTE_DEVICE_ID), E_OK); + + /** + * @tc.steps:step3. put sync miss query data with same timestamp + * @tc.expected: OK + */ + data.clear(); + DataItem itemMiss{key, {}, now, DataItem::REMOTE_DEVICE_DATA_MISS_QUERY, REMOTE_DEVICE_B, now}; + itemMiss.key.clear(); + DBCommon::CalcValueHash({'K', 'e', 'y'}, itemMiss.key); + EXPECT_EQ(itemMiss.key.empty(), false); + data.push_back(itemMiss); + Query query = Query::Select().EqualTo("field_name2", true); + QueryObject queryObj(query); + EXPECT_EQ(DistributedDBToolsUnitTest::PutSyncDataTest(store, data, REMOTE_DEVICE_B, queryObj), E_OK); + + RefObject::KillAndDecObjRef(store); + KvDBManager::ReleaseDatabaseConnection(conn); + + /** + * @tc.steps:step4. Check sync data + * @tc.expected: check data ok, data {key} is not erased. + */ + CheckDataNumByKey(storeId, {'K', 'e', 'y'}, 1); +} + +/** + * @tc.name: AddSubscribeTest001 + * @tc.desc: Add subscribe with query + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageSubscribeQueryTest, AddSubscribeTest001, TestSize.Level1) +{ + /** + * @tc.steps:step1. Create a json schema db, get the natural store instance. + * @tc.expected: Get results OK and non-null store. + */ + SQLiteSingleVerNaturalStoreConnection *conn = nullptr; + SQLiteSingleVerNaturalStore *store = nullptr; + CreateAndGetStore("SubscribeTest01", SCHEMA_STRING, conn, store); + + std::vector queryList; + queryList.push_back(Query::Select().PrefixKey({10, 20})); + queryList.push_back(Query::Select().EqualTo("field_name3", 30)); + queryList.push_back(Query::Select().NotEqualTo("field_name3", 30)); + queryList.push_back(Query::Select().GreaterThan("field_name3", 10)); + queryList.push_back(Query::Select().LessThan("field_name3", 30)); + queryList.push_back(Query::Select().GreaterThanOrEqualTo("field_name3", 30)); + queryList.push_back(Query::Select().LessThanOrEqualTo("field_name3", 30)); + queryList.push_back(Query::Select().Like("field_name6", "Abc%")); + queryList.push_back(Query::Select().NotLike("field_name6", "Asd%")); + std::vector set = {1, 2, 3, 4}; + queryList.push_back(Query::Select().In("field_name3", set)); + queryList.push_back(Query::Select().NotIn("field_name3", set)); + queryList.push_back(Query::Select().IsNull("field_name4")); + queryList.push_back(Query::Select().IsNotNull("field_name5")); + queryList.push_back(Query::Select().EqualTo("field_name3", 30).And().EqualTo("field_name1", true)); + queryList.push_back(Query::Select().EqualTo("field_name3", 30).Or().EqualTo("field_name1", true)); + queryList.push_back(Query::Select().EqualTo("field_name2", false).Or(). + BeginGroup().EqualTo("field_name3", 30).Or().EqualTo("field_name1", true).EndGroup()); + + /** + * @tc.steps:step2. Add subscribe with query, remove subscribe. + * @tc.expected: success. + */ + for (const auto &query : queryList) { + QueryObject queryObj(query); + EXPECT_EQ(store->AddSubscribe(SUBSCRIBE_ID, queryObj, false), E_OK); + EXPECT_EQ(store->RemoveSubscribe(SUBSCRIBE_ID), E_OK); + } + + /** + * @tc.steps:step6. Close natural store + * @tc.expected: step6. Close ok + */ + RefObject::KillAndDecObjRef(store); + KvDBManager::ReleaseDatabaseConnection(conn); +} + +/** + * @tc.name: AddSubscribeTest002 + * @tc.desc: Add subscribe with same query not failed + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: xulianhui + */ +HWTEST_F(DistributedDBStorageSubscribeQueryTest, AddSubscribeTest002, TestSize.Level1) +{ + /** + * @tc.steps:step1. Create a json schema db, get the natural store instance. + * @tc.expected: Get results OK and non-null store. + */ + SQLiteSingleVerNaturalStoreConnection *conn = nullptr; + SQLiteSingleVerNaturalStore *store = nullptr; + CreateAndGetStore("SubscribeTest02", SCHEMA_STRING, conn, store); + + Query query = Query::Select().EqualTo("field_name2", false).Or(). + BeginGroup().EqualTo("field_name3", 30).Or().EqualTo("field_name1", true).EndGroup(); + /** + * @tc.steps:step2. Add subscribe with same query + * @tc.expected: step2. add success + */ + QueryObject queryObj(query); + int errCode = store->AddSubscribe(SUBSCRIBE_ID, queryObj, false); + EXPECT_EQ(errCode, E_OK); + errCode = store->AddSubscribe(SUBSCRIBE_ID, queryObj, false); + EXPECT_EQ(errCode, E_OK); + EXPECT_EQ(store->RemoveSubscribe(SUBSCRIBE_ID), E_OK); + /** + * @tc.steps:step3. Close natural store + * @tc.expected: step3. Close ok + */ + RefObject::KillAndDecObjRef(store); + KvDBManager::ReleaseDatabaseConnection(conn); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_transaction_data_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_transaction_data_test.cpp new file mode 100644 index 00000000..ff08d251 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_transaction_data_test.cpp @@ -0,0 +1,1592 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 +#include + +#include "db_common.h" +#include "db_constant.h" +#include "db_errno.h" +#include "default_factory.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "log_print.h" +#include "multi_ver_natural_store.h" +#include "multi_ver_natural_store_commit_storage.h" +#include "multi_ver_natural_store_connection.h" +#include "process_communicator_test_stub.h" +#include "sqlite_multi_ver_data_storage.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + const int WAIT_TIME = 1000; + const uint64_t INVALID_TIMESTAMP = 0; + const uint64_t OPERATION_ADD = 1; + const uint64_t OPERATION_DELETE = 2; + const uint64_t OPERATION_CLEAR = 3; + + string g_testDir; + KvDBProperties g_prop; + SQLiteMultiVerTransaction *g_transaction = nullptr; + MultiVerNaturalStore *g_naturalStore = nullptr; + MultiVerNaturalStoreConnection *g_naturalStoreConnection = nullptr; + Version g_version = 0; + const std::string CREATE_TABLE = + "CREATE TABLE IF NOT EXISTS version_data(key BLOB, value BLOB, oper_flag INTEGER, version INTEGER, " \ + "timestamp INTEGER, ori_timestamp INTEGER, hash_key BLOB, " \ + "PRIMARY key(hash_key, version));"; +} + +class DistributedDBStorageTransactionDataTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +static void GetReadTransaction() +{ + if (g_transaction == nullptr) { + g_transaction = new (std::nothrow) SQLiteMultiVerTransaction(); + ASSERT_NE(g_transaction, nullptr); + std::string dir = g_testDir + "/31/multi_ver/multi_ver_data.db"; + LOGI("%s", dir.c_str()); + CipherPassword passwd; + int errCode = g_transaction->Initialize(dir, true, CipherType::AES_256_GCM, passwd); + ASSERT_EQ(errCode, E_OK); + } + Version versionInfo; + ASSERT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, versionInfo), E_OK); + g_version = versionInfo; + g_transaction->SetVersion(versionInfo); +} + +static void ValueEqual(const Value &read, const Value &origin) +{ + EXPECT_EQ(read.size(), origin.size()); + if (read.size() != origin.size()) { + DBCommon::PrintHexVector(origin, __LINE__, "Orig"); + } + + EXPECT_EQ(read, origin); +} + +static int RunSyncMergeForOneCommit(std::vector &entries) +{ + MultiVerCommitNode multiVerCommit; + multiVerCommit.commitId.resize(20); // 20 as commit id size + RAND_bytes(multiVerCommit.commitId.data(), 20); + multiVerCommit.deviceInfo = DBCommon::TransferHashString("deviceB") + "deviceB1"; + + // Put the multiver commit of other device. + int errCode = g_naturalStore->PutCommitData(multiVerCommit, entries, "deviceB"); + if (errCode != E_OK) { + return errCode; + } + + std::vector multiVerCommits; + multiVerCommits.push_back(multiVerCommit); + + // Merge the multiver commit of other device. + errCode = g_naturalStore->MergeSyncCommit(multiVerCommit, multiVerCommits); + + for (auto &item : entries) { + if (item != nullptr) { + delete item; + item = nullptr; + } + } + entries.clear(); + + return errCode; +} + +static uint64_t GetCommitTimestamp(const CommitID& commitId) +{ + MultiVerNaturalStoreCommitStorage *commitStorage = new (std::nothrow) MultiVerNaturalStoreCommitStorage(); + if (commitStorage == nullptr) { + return 0; + } + Timestamp timestamp = INVALID_TIMESTAMP; + CommitID newCommitId; + IKvDBCommit *commit = nullptr; + IKvDBCommitStorage::Property property; + property.isNeedCreate = false; + property.path = g_testDir; + property.identifierName = "31"; + int errCode = commitStorage->Open(property); + if (errCode != E_OK) { + goto END; + } + + if (commitId.empty()) { + newCommitId = commitStorage->GetHeader(errCode); + if (newCommitId.empty()) { + return 0; + } + } else { + newCommitId = commitId; + } + + commit = commitStorage->GetCommit(newCommitId, errCode); + if (commit == nullptr) { + LOGE("Can't get the commit:%d", errCode); + goto END; + } + + timestamp = commit->GetTimestamp(); +END: + if (commit != nullptr) { + commitStorage->ReleaseCommit(commit); + commit = nullptr; + } + + delete commitStorage; + commitStorage = nullptr; + + return timestamp; +} + +static uint64_t GetMaxTimestamp() +{ + CommitID commitId; + return GetCommitTimestamp(commitId); +} + +static void PutAndCommitEntry(const Key &key, const Value &value) +{ + EXPECT_EQ(g_naturalStoreConnection->StartTransaction(), E_OK); + IOption option; + EXPECT_EQ(g_naturalStoreConnection->Put(option, key, value), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Commit(), E_OK); +} + +static void PushOneEntry(uint64_t opr, uint64_t timestamp, const Key &key, const Value &value, + std::vector &entries) +{ + GenericMultiVerKvEntry *entry = new (std::nothrow) GenericMultiVerKvEntry; + if (entry != nullptr) { + // set key + entry->SetKey(key); + // set value + MultiVerValueObject valueObject; + valueObject.SetValue(value); + Value objectSerial; + valueObject.GetSerialData(objectSerial); + entry->SetValue(objectSerial); + // set timestamp + entry->SetTimestamp(timestamp); + + // set open_flag + entry->SetOperFlag(opr); + if (opr == OPERATION_DELETE) { + Key hashKey; + DBCommon::CalcValueHash(key, hashKey); + entry->SetKey(hashKey); + } else if (opr == OPERATION_CLEAR) { + Key clearKey = {'c', 'l', 'e', 'a', 'r'}; + entry->SetKey(clearKey); + } + + entries.push_back(entry); + } +} + +static void ValueEqualByKey(const Key &key, const Value &value) +{ + IOption option; + Value valueRead; + int errCode = g_naturalStoreConnection->Get(option, key, valueRead); + EXPECT_EQ(errCode, E_OK); + if (errCode != E_OK) { + DBCommon::PrintHexVector(key, __LINE__, "key"); + } + ValueEqual(value, valueRead); +} + +void DistributedDBStorageTransactionDataTest::SetUpTestCase(void) +{ + IKvDBFactory *factory = new (std::nothrow) DefaultFactory(); + ASSERT_TRUE(factory != nullptr); + IKvDBFactory::Register(factory); +} + +void DistributedDBStorageTransactionDataTest::TearDownTestCase(void) +{ + if (g_transaction != nullptr) { + delete g_transaction; + g_transaction = nullptr; + } + auto factory = IKvDBFactory::GetCurrent(); + if (factory != nullptr) { + delete factory; + factory = nullptr; + } + IKvDBFactory::Register(nullptr); +} + +void DistributedDBStorageTransactionDataTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + + // KvDBProperties prop; + g_prop.SetStringProp(KvDBProperties::APP_ID, "app0"); + g_prop.SetStringProp(KvDBProperties::STORE_ID, "store0"); + g_prop.SetStringProp(KvDBProperties::USER_ID, "user0"); + g_prop.SetStringProp(KvDBProperties::DATA_DIR, g_testDir); + g_prop.SetStringProp(KvDBProperties::IDENTIFIER_DIR, "31"); + g_prop.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + + g_naturalStore = new (std::nothrow) MultiVerNaturalStore(); + ASSERT_NE(g_naturalStore, nullptr); + EXPECT_EQ(g_naturalStore->Open(g_prop), E_OK); + + int errCode = 0; + IKvDBConnection *connection = g_naturalStore->GetDBConnection(errCode); + ASSERT_NE(connection, nullptr); + g_naturalStoreConnection = static_cast(connection); + + LOGI("read directory :%s", g_testDir.c_str()); +} + +void DistributedDBStorageTransactionDataTest::TearDown(void) +{ + if (g_transaction != nullptr) { + delete g_transaction; + g_transaction = nullptr; + } + + if (g_naturalStore != nullptr) { + if (g_naturalStoreConnection != nullptr) { + g_naturalStoreConnection->Close(); + g_naturalStoreConnection = nullptr; + } + g_naturalStore->DecObjRef(g_naturalStore); + g_naturalStore = nullptr; + } + DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir + "/31/" + DBConstant::MULTI_SUB_DIR); +} + +/** + * @tc.name: StorageInsert001 + * @tc.desc: Put the non-empty key, non-empty value into the database. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageInsert001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the data(non-empty key, non-empty value) into the database. + * @tc.expected: step1. Put returns E_OK. + */ + EXPECT_EQ(g_naturalStoreConnection->StartTransaction(), E_OK); + IOption option; + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_1, VALUE_1), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Commit(), E_OK); + Value valueRead; + /** + * @tc.steps: step2. Get the data. + * @tc.expected: step2. Get returns E_OK and the value is equal to the put value. + */ + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_1, valueRead), E_OK); + ValueEqual(VALUE_1, valueRead); + /** + * @tc.steps: step3. Clear the data. + */ + g_naturalStoreConnection->Clear(option); + /** + * @tc.steps: step4. Put another data(non-empty key, non-empty value) into the database. + * @tc.expected: step4. Put returns E_OK. + */ + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_2, VALUE_2), E_OK); + EXPECT_NE(g_naturalStoreConnection->Commit(), E_OK); + /** + * @tc.steps: step5. Get the data. + * @tc.expected: step5. Get returns E_OK and the value is equal to the second put value. + */ + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_2, valueRead), E_OK); + ValueEqual(VALUE_2, valueRead); +} + + /** + * @tc.name: StorageInsert002 + * @tc.desc: Put the empty key, non-empty value into the database. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageInsert002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the data(empty key, non-empty value) into the database. + * @tc.expected: step1. Put returns -E_INVALID_ARGS. + */ + EXPECT_EQ(g_naturalStoreConnection->StartTransaction(), E_OK); + IOption option; + EXPECT_EQ(g_naturalStoreConnection->Put(option, NULL_KEY_1, VALUE_1), -E_INVALID_ARGS); + EXPECT_EQ(g_naturalStoreConnection->Commit(), E_OK); +} + +/** + * @tc.name: StorageInsert003 + * @tc.desc: Put the non-empty key, empty value into the database. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageInsert003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the data(non-empty key, empty value) into the database. + * @tc.expected: step1. Put returns E_OK. + */ + IOption option; + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_1, NULL_VALUE_1), E_OK); + GetReadTransaction(); + Value valueRead; + Value valueTmp; + /** + * @tc.steps: step2. Get the data. + * @tc.expected: step2. Get returns E_OK and the value is empty. + */ + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_1, valueRead), E_OK); + ValueEqual(NULL_VALUE_1, valueRead); +} + +/** + * @tc.name: StorageUpdate001 + * @tc.desc: Update the value to non-empty + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageUpdate001, TestSize.Level1) +{ + IOption option; + /** + * @tc.steps: step1. Put one valid data into the database. + * @tc.expected: step1. Put returns E_OK. + */ + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_1, VALUE_1), E_OK); + /** + * @tc.steps: step2. Put another data whose key is same to the first put data and value(non-empty) is different. + * @tc.expected: step2. Put returns E_OK. + */ + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_1, VALUE_2), E_OK); + GetReadTransaction(); + Value valueRead; + /** + * @tc.steps: step3. Get the data. + * @tc.expected: step3. Get returns E_OK and the value is equal the second put value. + */ + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_1, valueRead), E_OK); + ValueEqual(VALUE_2, valueRead); +} + +/** + * @tc.name: StorageUpdate002 + * @tc.desc: Update the value to empty + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageUpdate002, TestSize.Level1) +{ + IOption option; + /** + * @tc.steps: step1. Put one valid data into the database. + * @tc.expected: step1. Put returns E_OK. + */ + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_1, VALUE_1), E_OK); + /** + * @tc.steps: step2. Put another data whose key is same to the first put data and value is empty. + * @tc.expected: step2. Put returns E_OK. + */ + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_1, NULL_VALUE_1), E_OK); + Value valueRead; + /** + * @tc.steps: step3. Get the data. + * @tc.expected: step3. Get returns E_OK and the value is empty. + */ + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_1, valueRead), E_OK); + ValueEqual(NULL_VALUE_1, valueRead); +} + +/** + * @tc.name: StorageDelete001 + * @tc.desc: Delete the existed data + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageDelete001, TestSize.Level1) +{ + IOption option; + /** + * @tc.steps: step1. Put one valid data. + */ + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_1, VALUE_1), E_OK); + /** + * @tc.steps: step2. Delete the data. + */ + EXPECT_EQ(g_naturalStoreConnection->Delete(option, KEY_1), E_OK); + GetReadTransaction(); + Value valueRead; + /** + * @tc.steps: step3. Get the data. + * @tc.expected: step3. Get returns -E_NOT_FOUND. + */ + EXPECT_EQ(g_transaction->Get(KEY_1, valueRead), -E_NOT_FOUND); +} + +/** + * @tc.name: StorageDelete002 + * @tc.desc: Delete the non-existed data + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageDelete002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Delete one non-existed data. + * @tc.expected: step1. Delete returns -E_NOT_FOUND. + */ + IOption option; + EXPECT_EQ(g_naturalStoreConnection->Delete(option, KEY_1), -E_NOT_FOUND); + GetReadTransaction(); + Value valueRead; + /** + * @tc.steps: step2. Get the non-existed data. + * @tc.expected: step2. Get returns -E_NOT_FOUND. + */ + EXPECT_EQ(g_transaction->Get(KEY_1, valueRead), -E_NOT_FOUND); +} + +/** + * @tc.name: StorageDelete003 + * @tc.desc: Delete the invalid key data + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageDelete003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Delete the empty-key data. + * @tc.expected: step1. Delete returns not E_OK. + */ + IOption option; + EXPECT_NE(g_naturalStoreConnection->Delete(option, NULL_KEY_1), E_OK); +} + +/** + * @tc.name: StorageClear001 + * @tc.desc: Clear the data + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageClear001, TestSize.Level1) +{ + /** + * @tc.steps: step1. put one data. + */ + IOption option; + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_1, VALUE_1), E_OK); + /** + * @tc.steps: step2. clear the data. + * @tc.expected: step2. Returns E_OK. + */ + EXPECT_EQ(g_naturalStoreConnection->Clear(option), E_OK); + GetReadTransaction(); + /** + * @tc.steps: step3. Check the data. + * @tc.expected: step3. Getting the data result -E_NOT_FOUND. + */ + Value valueRead; + EXPECT_EQ(g_transaction->Get(KEY_1, valueRead), -E_NOT_FOUND); +} + +/** + * @tc.name: StorageInsertBatch001 + * @tc.desc: Put the valid batch data + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageInsertBatch001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the batch data. + * @tc.expected: step1. Returns E_OK. + */ + std::vector entries; + entries.push_back(KV_ENTRY_1); + entries.push_back(KV_ENTRY_2); + IOption option; + EXPECT_EQ(g_naturalStoreConnection->PutBatch(option, entries), E_OK); + Value valueRead; + + /** + * @tc.steps: step2. Check the data. + * @tc.expected: step2. Get the data from the database and the value are equal to the data put before. + */ + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_1, valueRead), E_OK); + ValueEqual(VALUE_1, valueRead); + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_2, valueRead), E_OK); + ValueEqual(VALUE_2, valueRead); +} + +/** + * @tc.name: StorageInsertBatch002 + * @tc.desc: Put the partially valid batch data + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageInsertBatch002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the batch data(partially valid, partially invalid). + * @tc.expected: step1. Returns not E_OK. + */ + std::vector entries; + Entry entry; + entries.push_back(KV_ENTRY_1); + entries.push_back(entry); + IOption option; + EXPECT_NE(g_naturalStoreConnection->PutBatch(option, entries), E_OK); + GetReadTransaction(); + Value valueRead; + /** + * @tc.steps: step2. Check the data. + * @tc.expected: step2. Getting the data results not E_OK. + */ + EXPECT_NE(g_transaction->Get(KEY_1, valueRead), E_OK); +} + +/** + * @tc.name: StorageUpdateBatch001 + * @tc.desc: Update the batch data + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageUpdateBatch001, TestSize.Level1) +{ + std::vector entries1; + entries1.push_back(KV_ENTRY_1); + entries1.push_back(KV_ENTRY_2); + + Entry kvEntry1 = {KEY_1, VALUE_2}; + Entry kvEntry2 = {KEY_2, VALUE_1}; + std::vector entries2; + entries2.push_back(kvEntry1); + entries2.push_back(kvEntry2); + + IOption option; + /** + * @tc.steps: step1. Put the batch data. + */ + EXPECT_EQ(g_naturalStoreConnection->PutBatch(option, entries1), E_OK); + /** + * @tc.steps: step2. Update the batch data. + * @tc.expected: step2. Returns E_OK. + */ + EXPECT_EQ(g_naturalStoreConnection->PutBatch(option, entries2), E_OK); + Value valueRead; + /** + * @tc.steps: step3. Check the data. + * @tc.expected: step3. Get the data from the database and check whether the data have been updated. + */ + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_1, valueRead), E_OK); + ValueEqual(VALUE_2, valueRead); + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_2, valueRead), E_OK); + ValueEqual(VALUE_1, valueRead); +} + +/** + * @tc.name: StorageUpdateBatch002 + * @tc.desc: Update the batch data(partially invalid data) + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageUpdateBatch002, TestSize.Level1) +{ + std::vector entrys1; + entrys1.push_back(KV_ENTRY_1); + entrys1.push_back(KV_ENTRY_2); + + Entry kvEntry1 = {KEY_1, VALUE_2}; + Entry kvEntry; + Entry kvEntry2 = {KEY_2, VALUE_1}; + std::vector entrys2; + entrys2.push_back(kvEntry1); + entrys2.push_back(kvEntry); + entrys2.push_back(kvEntry2); + + IOption option; + /** + * @tc.steps: step1. Put the batch data. + */ + EXPECT_EQ(g_naturalStoreConnection->PutBatch(option, entrys1), E_OK); + /** + * @tc.steps: step2. Update the batch data(partially empty key). + * @tc.expected: step2. Returns not E_OK. + */ + EXPECT_NE(g_naturalStoreConnection->PutBatch(option, entrys2), E_OK); + Value valueRead; + /** + * @tc.steps: step3. Check the data. + * @tc.expected: step3. The getting result data are the first put batch . + */ + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_1, valueRead), E_OK); + ValueEqual(VALUE_1, valueRead); + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_2, valueRead), E_OK); + ValueEqual(VALUE_2, valueRead); +} + +/** + * @tc.name: StorageDeleteBatch001 + * @tc.desc: Delete the batch data + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageDeleteBatch001, TestSize.Level1) +{ + std::vector entries; + entries.push_back(KV_ENTRY_1); + entries.push_back(KV_ENTRY_2); + std::vector keys; + keys.push_back(KEY_1); + keys.push_back(KEY_2); + + IOption option; + /** + * @tc.steps: step1. Put the batch data. + */ + EXPECT_EQ(g_naturalStoreConnection->PutBatch(option, entries), E_OK); + /** + * @tc.steps: step2. Delete the batch data. + * @tc.expected: step2. Return E_OK. + */ + EXPECT_EQ(g_naturalStoreConnection->DeleteBatch(option, keys), E_OK); + GetReadTransaction(); + Value valueRead; + /** + * @tc.steps: step3. Check the data. + * @tc.expected: step3. Getting the data results -E_NOT_FOUND. + */ + EXPECT_EQ(g_transaction->Get(KEY_1, valueRead), -E_NOT_FOUND); + EXPECT_EQ(g_transaction->Get(KEY_2, valueRead), -E_NOT_FOUND); +} + +/** + * @tc.name: StorageDeleteBatch002 + * @tc.desc: Delete the batch data(partially non-existed) + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageDeleteBatch002, TestSize.Level1) +{ + std::vector entries; + entries.push_back(KV_ENTRY_1); + entries.push_back(KV_ENTRY_2); + std::vector keys; + keys.push_back(KEY_1); + keys.push_back(KEY_2); + std::vector k3 = {'3'}; + keys.push_back(k3); + + IOption option; + /** + * @tc.steps: step1. Put the batch data. + */ + EXPECT_EQ(g_naturalStoreConnection->PutBatch(option, entries), E_OK); + /** + * @tc.steps: step2. Delete the batch data(partially non-existed). + * @tc.expected: step2. Return E_OK. + */ + EXPECT_EQ(g_naturalStoreConnection->DeleteBatch(option, keys), E_OK); + GetReadTransaction(); + Value valueRead; + /** + * @tc.steps: step3. Check the data. + * @tc.expected: step3. Cannot Get the delete data in the database. + */ + EXPECT_NE(g_transaction->Get(KEY_1, valueRead), E_OK); + EXPECT_NE(g_transaction->Get(KEY_2, valueRead), E_OK); +} + +/** + * @tc.name: StorageDeleteBatch003 + * @tc.desc: Delete the batch data(partially invalid) + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageDeleteBatch003, TestSize.Level1) +{ + std::vector entries; + entries.push_back(KV_ENTRY_1); + entries.push_back(KV_ENTRY_2); + std::vector keys; + keys.push_back(KEY_1); + keys.push_back(KEY_2); + Key k3; + keys.push_back(k3); + + IOption option; + /** + * @tc.steps: step1. Put the batch data. + */ + EXPECT_EQ(g_naturalStoreConnection->PutBatch(option, entries), E_OK); + /** + * @tc.steps: step2. Delete the batch data(partially invalid). + * @tc.expected: step2. Return E_OK. + */ + EXPECT_NE(g_naturalStoreConnection->DeleteBatch(option, keys), E_OK); + GetReadTransaction(); + Value valueRead; + /** + * @tc.steps: step3. Check the data. + * @tc.expected: step3. Can get the put origined data. + */ + EXPECT_EQ(g_transaction->Get(KEY_1, valueRead), E_OK); + EXPECT_EQ(g_transaction->Get(KEY_2, valueRead), E_OK); +} + +/** + * @tc.name: StorageTransactionCombo001 + * @tc.desc: Multiple operation within the transaction + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, StorageTransactionCombo001, TestSize.Level1) +{ + Entry kvEntry3; + Entry kvEntry4; + kvEntry3.key = {'3'}; + kvEntry4.key = {'4'}; + kvEntry3.value = {'c'}; + kvEntry4.value = {'d'}; + // inserted data + std::vector entrys1; + entrys1.push_back(KV_ENTRY_1); + entrys1.push_back(KV_ENTRY_2); + entrys1.push_back(kvEntry3); + entrys1.push_back(kvEntry4); + + kvEntry3.value = {'e'}; + kvEntry4.value = {'f'}; + // updated data + std::vector entrys2; + entrys2.push_back(kvEntry3); + entrys2.push_back(kvEntry4); + + // deleted data + std::vector keys; + keys.push_back(KEY_1); + keys.push_back(KEY_2); + + IOption option; + /** + * @tc.steps: step1. Start the transaction. + */ + EXPECT_EQ(g_naturalStoreConnection->StartTransaction(), E_OK); + /** + * @tc.steps: step2. Put the batch data. + */ + EXPECT_EQ(g_naturalStoreConnection->PutBatch(option, entrys1), E_OK); + /** + * @tc.steps: step3. Delete the batch data. + */ + EXPECT_EQ(g_naturalStoreConnection->DeleteBatch(option, keys), E_OK); + /** + * @tc.steps: step4. Update the batch data. + */ + EXPECT_EQ(g_naturalStoreConnection->PutBatch(option, entrys2), E_OK); + /** + * @tc.steps: step5. Commit the transaction. + */ + EXPECT_EQ(g_naturalStoreConnection->Commit(), E_OK); + Value valueRead; + /** + * @tc.steps: step6. Check the data. + * @tc.expected: step6. Can get the updated data. + */ + EXPECT_EQ(g_naturalStoreConnection->Get(option, kvEntry3.key, valueRead), E_OK); + ValueEqual(kvEntry3.value, valueRead); + EXPECT_EQ(g_naturalStoreConnection->Get(option, kvEntry4.key, valueRead), E_OK); + ValueEqual(kvEntry4.value, valueRead); +} + +/** + * @tc.name: TransactionRollback001 + * @tc.desc: Multiple operation within the transaction + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, TransactionRollback001, TestSize.Level1) +{ + std::vector entries; + entries.push_back(KV_ENTRY_1); + entries.push_back(KV_ENTRY_2); + IOption option; + /** + * @tc.steps: step1. Start the transaction. + */ + EXPECT_EQ(g_naturalStoreConnection->StartTransaction(), E_OK); + /** + * @tc.steps: step2. Put the batch data. + */ + EXPECT_EQ(g_naturalStoreConnection->PutBatch(option, entries), E_OK); + /** + * @tc.steps: step3. Rollback the transaction. + */ + EXPECT_EQ(g_naturalStoreConnection->RollBack(), E_OK); + Value valueRead; + /** + * @tc.steps: step4. Check the data. + * @tc.expected: step4. Couldn't find the data in the database. + */ + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_1, valueRead), -E_NOT_FOUND); + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_2, valueRead), -E_NOT_FOUND); +} + +/** + * @tc.name: TransactionGetCommitData001 + * @tc.desc: Get the commit data of one transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, TransactionGetCommitData001, TestSize.Level1) +{ + std::vector entries; + entries.push_back(KV_ENTRY_1); + entries.push_back(KV_ENTRY_2); + IOption option; + /** + * @tc.steps: step1. Put the batch data within in one transaction. + */ + EXPECT_EQ(g_naturalStoreConnection->StartTransaction(), E_OK); + EXPECT_EQ(g_naturalStoreConnection->PutBatch(option, entries), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Commit(), E_OK); + + Value valueRead; + /** + * @tc.steps: step2. Check the put batch data, and could get the put data. + */ + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_1, valueRead), E_OK); + ValueEqual(valueRead, KV_ENTRY_1.value); + EXPECT_EQ(g_naturalStoreConnection->Get(option, KEY_2, valueRead), E_OK); + ValueEqual(valueRead, KV_ENTRY_2.value); + /** + * @tc.steps: step3. Get one commit data. + */ + GetReadTransaction(); + std::vector multiVerKvEntries; + EXPECT_EQ(g_transaction->GetEntriesByVersion(g_version, multiVerKvEntries), OK); + ASSERT_EQ(multiVerKvEntries.size(), 2UL); + auto entry1 = static_cast(multiVerKvEntries[0]); + ASSERT_NE(entry1, nullptr); + valueRead.clear(); + EXPECT_EQ(entry1->GetValue(valueRead), E_OK); + /** + * @tc.steps: step4. Check the commit data. + * @tc.expected: step4. Could find the batch put data in the commit data. + */ + auto entry2 = static_cast(multiVerKvEntries[1]); + ASSERT_NE(entry2, nullptr); + valueRead.clear(); + EXPECT_EQ(entry2->GetValue(valueRead), E_OK); + for (auto &item : multiVerKvEntries) { + delete item; + item = nullptr; + } +} + +/** + * @tc.name: TransactionSqliteKvEntry001 + * @tc.desc: Serialize the kv entry and deserialize the data. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, TransactionSqliteKvEntry001, TestSize.Level1) +{ + GenericMultiVerKvEntry entry; + entry.SetOperFlag(17); + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + entry.SetKey(key); + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value, 20); + /** + * @tc.steps: step1. Initialize the multi version kv entry. + */ + MultiVerValueObject valueObject; + valueObject.SetValue(value); + Value objectSerial; + + valueObject.GetSerialData(objectSerial); + entry.SetValue(objectSerial); + /** + * @tc.steps: step2. Get the serial data of the entry. + */ + std::vector serialData; + EXPECT_EQ(entry.GetSerialData(serialData), E_OK); + /** + * @tc.steps: step3. Deserial the data. + */ + GenericMultiVerKvEntry deEntry; + EXPECT_EQ(deEntry.DeSerialData(serialData), E_OK); + Key keyRead; + Value valueRead; + Value valueTmp; + uint64_t flag; + deEntry.GetKey(keyRead); + deEntry.GetValue(valueTmp); + deEntry.GetOperFlag(flag); + ValueEqual(keyRead, key); + MultiVerValueObject objectRead; + EXPECT_EQ(objectRead.DeSerialData(valueTmp), E_OK); + objectRead.GetValue(valueRead); + /** + * @tc.steps: step4. Check the deserialized data. + * @tc.expected: step4. the deserialized value is equal to the set value. + */ + ValueEqual(valueRead, value); + EXPECT_EQ(flag, 17UL); +} + +/** + * @tc.name: TransactionPutForeignData001 + * @tc.desc: Put the remote commit data into the current device database. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: huangnaigu + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, TransactionPutForeignData001, TestSize.Level1) +{ + GenericMultiVerKvEntry entry; + entry.SetOperFlag(1); + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + entry.SetKey(key); + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + MultiVerValueObject valueObject; + valueObject.SetValue(value); + Value objectSerial; + valueObject.GetSerialData(objectSerial); + entry.SetValue(objectSerial); + + std::vector entries; + entries.push_back(&entry); + /** + * @tc.steps: step1. Create the multiver commit. + */ + MultiVerCommitNode multiVerCommit; + multiVerCommit.commitId.resize(20); // commit id size + RAND_bytes(multiVerCommit.commitId.data(), 20); + multiVerCommit.deviceInfo = DBCommon::TransferHashString("deviceB") + "deviceB1"; + /** + * @tc.steps: step2. Put the multiver commit of other device. + */ + EXPECT_EQ(g_naturalStore->PutCommitData(multiVerCommit, entries, "deviceB"), E_OK); + std::vector multiVerCommits; + multiVerCommits.push_back(multiVerCommit); + /** + * @tc.steps: step3. Merge the multiver commit of other device. + */ + EXPECT_EQ(g_naturalStore->MergeSyncCommit(multiVerCommit, multiVerCommits), E_OK); + + /** + * @tc.steps: step4. Get the commit data, the foreign synced data would be got from sync. + */ + std::vector readEntries; + EXPECT_EQ(g_naturalStore->GetCommitData(multiVerCommit, readEntries), E_OK); + ASSERT_EQ(readEntries.size(), 0UL); +} + +/** + * @tc.name: DefaultConflictResolution001 + * @tc.desc: Merge data without conflicts + * @tc.type: FUNC + * @tc.require: AR000CQE13 AR000CQE14 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, DefaultConflictResolution001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the local data(KEY_1, VALUE_1) into the database. + * @tc.expected: step1. Put returns E_OK. + */ + PutAndCommitEntry(KEY_1, VALUE_1); + /** + * @tc.steps: step2. Put the external data(KEY_2, VALUE_2) into the database. + * @tc.expected: step2. Put returns E_OK + */ + std::vector entries; + PushOneEntry(OPERATION_ADD, 1, KEY_2, VALUE_2, entries); + EXPECT_EQ(RunSyncMergeForOneCommit(entries), E_OK); + /** + * @tc.steps: step3. Get value1 and value2 + * @tc.expected: step3. Value1 and value2 are correct. + */ + ValueEqualByKey(KEY_1, VALUE_1); + ValueEqualByKey(KEY_2, VALUE_2); +} + +/** + * @tc.name: DefaultConflictResolution002 + * @tc.desc: Merge data with conflicts ,no clear operation in the external data and local data + * @tc.type: FUNC + * @tc.require: AR000CQE13 AR000CQE14 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, DefaultConflictResolution002, TestSize.Level1) +{ + PutAndCommitEntry(KEY_1, VALUE_1); + /** + * @tc.steps: step1. Put the [KEY_2,V2] and [KEY_3,V3] into the database and delete [KEY_1,V1] + * @tc.expected: step1. Both Put and Delete operation returns E_OK. + */ + IOption option; + EXPECT_EQ(g_naturalStoreConnection->StartTransaction(), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_2, VALUE_2), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_3, VALUE_3), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Delete(option, KEY_1), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Commit(), E_OK); + /** + * @tc.steps: step2. Get latest timestamp + */ + Timestamp t1 = GetMaxTimestamp(); + EXPECT_TRUE(t1 > 0); + /** + * @tc.steps: step3. Put the external entry[KEY_2,VALUE_4,T2] into the database. + * @tc.expected: step3. Put returns E_OK + */ + std::vector entriesV4; + PushOneEntry(OPERATION_ADD, t1 - 1, KEY_2, VALUE_4, entriesV4); + EXPECT_EQ(RunSyncMergeForOneCommit(entriesV4), E_OK); + /** + * @tc.steps: step4. Get value of K2 + * @tc.expected: step4. value of K2 is equals V2 + */ + ValueEqualByKey(KEY_2, VALUE_2); + /** + * @tc.steps: step5. Put the external entry[KEY_2,VALUE_5,T3] into the database. + * @tc.expected: step5. Put returns E_OK + */ + std::vector entriesV5; + PushOneEntry(OPERATION_ADD, t1 + 1, KEY_2, VALUE_5, entriesV5); + EXPECT_EQ(RunSyncMergeForOneCommit(entriesV5), E_OK); + /** + * @tc.steps: step6. Get value of K2 + * @tc.expected: step6. value of K2 is equals V5 + */ + ValueEqualByKey(KEY_2, VALUE_5); + /** + * @tc.steps: step7. Put the external Delete entry[KEY_3,T2] into the database. + * @tc.expected: step7. Put returns E_OK + */ + std::vector entriesV6; + PushOneEntry(OPERATION_DELETE, t1 - 1, KEY_3, VALUE_6, entriesV6); + EXPECT_EQ(RunSyncMergeForOneCommit(entriesV6), E_OK); + /** + * @tc.steps: step8. Get value of K3 + * @tc.expected: step8. value of K3 is equals V3 + */ + ValueEqualByKey(KEY_3, VALUE_3); + /** + * @tc.steps: step9. Put the external Delete entry[KEY_3,T2] into the database. + * @tc.expected: step9. Put returns E_OK + */ + std::vector entriesV7; + PushOneEntry(OPERATION_DELETE, t1 + 1, KEY_3, VALUE_7, entriesV7); + EXPECT_EQ(RunSyncMergeForOneCommit(entriesV7), E_OK); + /** + * @tc.steps: step10. Get value of K3 + * @tc.expected: step10. Return NOT_FOUND + */ + GetReadTransaction(); + Value valueTmp; + EXPECT_EQ(g_transaction->Get(KEY_3, valueTmp), -E_NOT_FOUND); + /** + * @tc.steps: step11. Put the external entry[KEY_1,VALUE_6,T2] into the database. + * @tc.expected: step11. Put returns E_OK + */ + std::vector entriesV8; + PushOneEntry(OPERATION_ADD, t1 - 1, KEY_1, VALUE_6, entriesV8); + EXPECT_EQ(RunSyncMergeForOneCommit(entriesV8), E_OK); + /** + * @tc.steps: step12. Get value of K1 + * @tc.expected: step12. Return NOT_FOUND + */ + GetReadTransaction(); + EXPECT_EQ(g_transaction->Get(KEY_1, valueTmp), -E_NOT_FOUND); + /** + * @tc.steps: step13. Put the external entry[KEY_1,VALUE_7,T2] into the database. + * @tc.expected: step13. Put returns E_OK + */ + std::vector entriesV9; + PushOneEntry(OPERATION_ADD, t1 + 1, KEY_1, VALUE_7, entriesV9); + EXPECT_EQ(RunSyncMergeForOneCommit(entriesV9), E_OK); + /** + * @tc.steps: step14. Get value of K1 + * @tc.expected: step14. value of K1 is V7 + */ + ValueEqualByKey(KEY_1, VALUE_7); +} + +/** + * @tc.name: DefaultConflictResolution003 + * @tc.desc: Merge data with conflicts, clear operation is in the external data + * @tc.type: FUNC + * @tc.require: AR000CQE13 AR000CQE14 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, DefaultConflictResolution003, TestSize.Level2) +{ + /** + * @tc.steps: step1. Put the local data(KEY_1, VALUE_1) into the database. + * @tc.expected: step1. Put returns E_OK. + */ + PutAndCommitEntry(KEY_1, VALUE_1); + /** + * @tc.steps: step2. Get timestampV1 + */ + Timestamp timestampV1 = GetMaxTimestamp(); + Timestamp timestampClear = timestampV1 + 1; + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps: step3. Put the local data(KEY_2, VALUE_2) into the database. + * @tc.expected: step3. Put returns E_OK. + */ + PutAndCommitEntry(KEY_2, VALUE_2); + /** + * @tc.steps: step4. Get timestampV2 + */ + Timestamp timestampV2 = GetMaxTimestamp(); + /** + * @tc.steps: step5. Put the external clear entry into the database. + * @tc.expected: step5. Put returns E_OK + */ + std::vector entries; + PushOneEntry(OPERATION_CLEAR, timestampClear, KEY_3, VALUE_3, entries); + EXPECT_EQ(RunSyncMergeForOneCommit(entries), E_OK); + + EXPECT_TRUE(timestampV1 < timestampClear); + EXPECT_TRUE(timestampClear < timestampV2); + + /** + * @tc.steps: step6. Get value1 and value2 + * @tc.expected: step6. Get Value1 return NOT_FOUND , value2 is correct. + */ + Value valueTmp; + GetReadTransaction(); + EXPECT_EQ(g_transaction->Get(KEY_1, valueTmp), -E_NOT_FOUND); + + ValueEqualByKey(KEY_2, VALUE_2); +} + +/** + * @tc.name: DefaultConflictResolution004 + * @tc.desc: Merge data with conflicts, clear operation is in the local data + * @tc.type: FUNC + * @tc.require: AR000CQE13 AR000CQE14 + * @tc.author: wumin + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, DefaultConflictResolution004, TestSize.Level2) +{ + /** + * @tc.steps: step1. Put the local data(KEY_1, VALUE_1) into the database and get the latest timestamp. + * @tc.expected: step1. Put returns E_OK. + */ + PutAndCommitEntry(KEY_1, VALUE_1); + Timestamp t1 = GetMaxTimestamp(); + EXPECT_TRUE(t1 > 0); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps: step2. Put the local data(KEY_2, VALUE_2) into the database and get the latest timestamp. + * @tc.expected: step2. Put returns E_OK. + */ + PutAndCommitEntry(KEY_2, VALUE_2); + Timestamp t2 = GetMaxTimestamp(); + /** + * @tc.steps: step3. Execute Clear() operation and get the latest timestamp. + */ + IOption option; + g_naturalStoreConnection->Clear(option); + Timestamp t3 = GetMaxTimestamp(); + EXPECT_TRUE(t3 > 0); + /** + * @tc.steps: step4. Put the local data(KEY_3, VALUE_3) into the database and get the latest timestamp. + * @tc.expected: step4. Put returns E_OK. + */ + PutAndCommitEntry(KEY_3, VALUE_3); + /** + * @tc.steps: step5. Put the external data [Clear, T5],[KEY_4,VALUE_4,T6],[KEY_5,VALUE_5,T7] into the database. + * @tc.expected: step5. Put returns E_OK + */ + EXPECT_TRUE(t1 + 1 < t2); + std::vector entries; + // put clear entry + PushOneEntry(OPERATION_CLEAR, t1 + 1, KEY_7, VALUE_7, entries); + // put K4 entry + PushOneEntry(OPERATION_ADD, t3 - 1, KEY_4, VALUE_4, entries); + // put K5 entry + PushOneEntry(OPERATION_ADD, t3 + 1, KEY_5, VALUE_5, entries); + // merge data + EXPECT_EQ(RunSyncMergeForOneCommit(entries), E_OK); + /** + * @tc.steps: step6. Get value of KEY_1,KEY_2,KEY_3,KEY_4,K5 + */ + GetReadTransaction(); + Value valueTmp; + EXPECT_EQ(g_transaction->Get(KEY_1, valueTmp), -E_NOT_FOUND); + EXPECT_EQ(g_transaction->Get(KEY_2, valueTmp), -E_NOT_FOUND); + EXPECT_EQ(g_transaction->Get(KEY_4, valueTmp), -E_NOT_FOUND); + + ValueEqualByKey(KEY_3, VALUE_3); + ValueEqualByKey(KEY_5, VALUE_5); +} + +/** + * @tc.name: CommitTimestamp001 + * @tc.desc: Test the timestamp of the native commit. + * @tc.type: FUNC + * @tc.require: AR000CQE11 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, CommitTimestamp001, TestSize.Level2) +{ + /** + * @tc.steps: step1. Put in some data(non-empty key, non-empty value) within one transaction. + */ + EXPECT_EQ(g_naturalStoreConnection->StartTransaction(), E_OK); + IOption option; + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_1, VALUE_1), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_2, VALUE_2), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Commit(), E_OK); + + /** + * @tc.steps: step2. Add different operations(add,update,delete,clear) within one transaction. + */ + std::srand(std::time(0)); // set the current time to the seed. + EXPECT_EQ(g_naturalStoreConnection->StartTransaction(), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_4, VALUE_4), E_OK); // add + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 1000)); + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_1, VALUE_3), E_OK); // update + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 1000)); + EXPECT_EQ(g_naturalStoreConnection->Delete(option, KEY_2), E_OK); // delete + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 1000)); + EXPECT_EQ(g_naturalStoreConnection->Clear(option), E_OK); // clear + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 1000)); + EXPECT_EQ(g_naturalStoreConnection->Commit(), E_OK); + + /** + * @tc.steps: step3. Get all the record data in the newest commit. + */ + GetReadTransaction(); + std::vector entries; + g_transaction->GetEntriesByVersion(g_version, entries); + ASSERT_EQ(entries.size(), 4UL); // add, update, delete and clear for 4 operation. + + std::set timeSet; + for (auto item : entries) { + uint64_t timestamp = 0; + item->GetTimestamp(timestamp); + timeSet.insert(timestamp); + } + + ASSERT_EQ(timeSet.size(), 1UL); // only one timestamp in one commit. + // Tobe compare the timestamp. + CommitID commitId; + Timestamp commitTimestamp = GetCommitTimestamp(commitId); + LOGD("TimeRecord:%" PRIu64 ", TimeCommit:%" PRIu64, *(timeSet.begin()), commitTimestamp); + ASSERT_EQ(*(timeSet.begin()), commitTimestamp); + ASSERT_NE(commitTimestamp, 0UL); + + for (auto &item : entries) { + g_naturalStore->ReleaseKvEntry(item); + item = nullptr; + } +} + +static bool PutFirstSyncCommitData(MultiVerCommitNode &multiVerCommit) +{ + GenericMultiVerKvEntry entry; + entry.SetOperFlag(1); // add the new data. + entry.SetKey(KEY_2); + + MultiVerValueObject valueObject; + valueObject.SetValue(VALUE_2); + Value objectSerial; + valueObject.GetSerialData(objectSerial); + entry.SetValue(objectSerial); + entry.SetTimestamp(1000UL); // the first data timestamp. + + std::vector entries; + entries.push_back(&entry); + /** + * @tc.steps: step1. Create the multiver commit. + */ + multiVerCommit.commitId.resize(20); // commit id size + RAND_bytes(multiVerCommit.commitId.data(), 20); // commit id size + multiVerCommit.deviceInfo = DBCommon::TransferHashString("deviceB") + "deviceB1"; + multiVerCommit.timestamp = 1000UL; // the first data timestamp. + /** + * @tc.steps: step2. Put the multiver commit of other device. + */ + int errCode = g_naturalStore->PutCommitData(multiVerCommit, entries, "deviceB"); + if (errCode != E_OK) { + return false; + } + return true; +} + +static bool PutSecondSyncCommitData(const MultiVerCommitNode &multiVerCommit, MultiVerCommitNode &newCommit) +{ + GenericMultiVerKvEntry entry; + entry.SetOperFlag(1); // add flag + entry.SetKey(KEY_3); + + MultiVerValueObject valueObject; + valueObject.SetValue(VALUE_3); + Value objectSerial; + valueObject.GetSerialData(objectSerial); + entry.SetValue(objectSerial); + entry.SetTimestamp(2000UL); // bigger than the first one. + + std::vector entries; + entries.push_back(&entry); + + newCommit.commitId.resize(20); // commit id size + RAND_bytes(newCommit.commitId.data(), 20); // commit id size. + newCommit.leftParent = multiVerCommit.commitId; + newCommit.deviceInfo = DBCommon::TransferHashString("deviceB") + "deviceB1"; + newCommit.timestamp = 2000UL; // bigger than the first one. + + int errCode = g_naturalStore->PutCommitData(newCommit, entries, "deviceB"); + if (errCode != E_OK) { + return false; + } + std::vector multiVerCommits; + multiVerCommits.push_back(multiVerCommit); + multiVerCommits.push_back(newCommit); + + errCode = g_naturalStore->MergeSyncCommit(newCommit, multiVerCommits); + if (errCode != E_OK) { + return false; + } + return true; +} + +/** + * @tc.name: CommitTimestamp002 + * @tc.desc: Test the timestamp of the native commits. + * @tc.type: FUNC + * @tc.require: AR000CQE11 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, CommitTimestamp002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put in some data(non-empty key, non-empty value) within one transaction. + */ + EXPECT_EQ(g_naturalStoreConnection->StartTransaction(), E_OK); + IOption option; + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_1, VALUE_1), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_2, VALUE_2), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Commit(), E_OK); + + CommitID commitId; + Timestamp stampFirst = GetCommitTimestamp(commitId); + ASSERT_NE(stampFirst, 0UL); // non-zero + + /** + * @tc.steps: step2. Add another data within one transaction. + */ + std::srand(std::time(0)); // set the current time to the seed. + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 1000)); + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_4, VALUE_4), E_OK); + /** + * @tc.steps: step3. Check the timestamp of the two commits. + * @tc.expected: step3. the timestamp of the second commit is greater than the timestamp of the first commit. + */ + Timestamp stampSecond = GetCommitTimestamp(commitId); + ASSERT_NE(stampSecond, 0UL); // non-zero + LOGD("TimeFirst:%" PRIu64 ", TimeSecond:%" PRIu64, stampFirst, stampSecond); + ASSERT_GT(stampSecond, stampFirst); +} +static void ReleaseKvEntries(std::vector &entries) +{ + for (auto &item : entries) { + if (item == nullptr) { + continue; + } + g_naturalStore->ReleaseKvEntry(item); + item = nullptr; + } +} + +/** + * @tc.name: CommitTimestamp003 + * @tc.desc: Test the timestamp of the foreign commits. + * @tc.type: FUNC + * @tc.require: AR000CQE11 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, CommitTimestamp003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put in some data(non-empty key, non-empty value) within one transaction. + */ + EXPECT_EQ(g_naturalStoreConnection->StartTransaction(), E_OK); + IOption option; + EXPECT_EQ(g_naturalStoreConnection->Put(option, KEY_1, VALUE_1), E_OK); + EXPECT_EQ(g_naturalStoreConnection->Commit(), E_OK); + + /** + * @tc.steps: step2. Add different operations(add,update,delete,clear) within one transaction. + */ + std::srand(std::time(0)); // set the current time to the seed. + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 1000)); + MultiVerCommitNode commit; + ASSERT_EQ(PutFirstSyncCommitData(commit), true); // add + + GetReadTransaction(); + std::vector entries; + g_transaction->GetEntriesByVersion(g_version, entries); + ASSERT_EQ(entries.size(), 1UL); // sync commit have only one entry. + ASSERT_NE(entries[0], nullptr); + uint64_t timestamp = 0; + entries[0]->GetTimestamp(timestamp); + ReleaseKvEntries(entries); + + /** + * @tc.steps: step3. Check the timestamp of the commit and the data. + * @tc.steps: expected. the timestamp of the sync commit is equal to the timestamp of the data record. + */ + Timestamp commitTimestamp = GetCommitTimestamp(commit.commitId); + LOGD("TimeRecord:%" PRIu64 ", TimeCommit:%" PRIu64, timestamp, commitTimestamp); + ASSERT_EQ(timestamp, commitTimestamp); + ASSERT_NE(commitTimestamp, 0UL); +} + +/** + * @tc.name: CommitTimestamp004 + * @tc.desc: Test the timestamp of the merge commits. + * @tc.type: FUNC + * @tc.require: AR000CQE11 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, CommitTimestamp004, TestSize.Level1) +{ + /** + * @tc.steps: step1. Add the first sync commit. + */ + MultiVerCommitNode commit; + ASSERT_EQ(PutFirstSyncCommitData(commit), true); // add the first sync commit. + + std::srand(std::time(0)); // set the current time to the seed. + std::this_thread::sleep_for(std::chrono::milliseconds(std::rand() % 1000)); // sleep for random interval. + + /** + * @tc.steps: step2. Add the second sync commit and merge the commit. + */ + MultiVerCommitNode newCommit; + ASSERT_EQ(PutSecondSyncCommitData(commit, newCommit), true); + + /** + * @tc.steps: step3. Get the newest entries. + */ + GetReadTransaction(); + std::vector entries; + g_transaction->GetEntriesByVersion(g_version, entries); + ASSERT_EQ(entries.size(), 2UL); // merge node has 2 entries. + + std::set timeSet; + for (auto &item : entries) { + ASSERT_NE(item, nullptr); + uint64_t timestamp = 0; + item->GetTimestamp(timestamp); + g_naturalStore->ReleaseKvEntry(item); + item = nullptr; + timeSet.insert(timestamp); + } + /** + * @tc.steps: step4. Get the timestamp of newest entries. + * @tc.expected: step4. The merged commit have different timestamp. + */ + ASSERT_EQ(timeSet.size(), 2UL); // entries have different timestamp. + for (auto item : timeSet) { + ASSERT_NE(item, 0UL); + } +} + +/** + * @tc.name: GetBranchTag + * @tc.desc: Test the branch tag of the commits. + * @tc.type: FUNC + * @tc.require: AR000CQE11 + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionDataTest, GetBranchTag001, TestSize.Level1) +{ + KvStoreDelegateManager::SetProcessLabel("123", "456"); + KvStoreDelegateManager::SetProcessCommunicator(std::make_shared()); + ASSERT_NE(g_naturalStore, nullptr); + std::vector vectTag; + g_naturalStore->GetCurrentTag(vectTag); + DBCommon::PrintHexVector(vectTag); + for (int i = 0; i < 10; i++) { + if (g_naturalStore != nullptr) { + if (g_naturalStoreConnection != nullptr) { + g_naturalStoreConnection->Close(); + g_naturalStoreConnection = nullptr; + } + g_naturalStore->DecObjRef(g_naturalStore); + g_naturalStore = new (std::nothrow) MultiVerNaturalStore; + ASSERT_NE(g_naturalStore, nullptr); + EXPECT_EQ(g_naturalStore->Open(g_prop), E_OK); + std::vector readTag; + g_naturalStore->GetCurrentTag(readTag); + DBCommon::PrintHexVector(readTag); + EXPECT_EQ(vectTag, readTag); + } + } +} diff --git a/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_transaction_record_test.cpp b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_transaction_record_test.cpp new file mode 100644 index 00000000..4d0814b1 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/distributeddb_storage_transaction_record_test.cpp @@ -0,0 +1,1103 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_constant.h" +#include "distributeddb_tools_unit_test.h" +#include "sqlite_local_kvdb_connection.h" +#include "sqlite_multi_ver_data_storage.h" +#include "sqlite_utils.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + string g_storeDir; + SQLiteMultiVerTransaction *g_transaction = nullptr; + const std::string CREATE_TABLE = + "CREATE TABLE IF NOT EXISTS version_data(key BLOB, value BLOB, oper_flag INTEGER, version INTEGER, " \ + "timestamp INTEGER, ori_timestamp INTEGER, hash_key BLOB, " \ + "PRIMARY key(hash_key, version));"; +} + +class DistributedDBStorageTransactionRecordTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBStorageTransactionRecordTest::SetUpTestCase(void) +{ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_storeDir = g_testDir + "/test_multi_version.db"; + LOGI("read directory :%s", g_storeDir.c_str()); +} + +void DistributedDBStorageTransactionRecordTest::TearDownTestCase(void) +{ + remove(g_testDir.c_str()); +} + +void DistributedDBStorageTransactionRecordTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_transaction = new (std::nothrow) SQLiteMultiVerTransaction(); + ASSERT_NE(g_transaction, nullptr); + CipherPassword passwd; + int errCode = g_transaction->Initialize(g_storeDir, false, CipherType::AES_256_GCM, passwd); + ASSERT_EQ(errCode, E_OK); +} + +void DistributedDBStorageTransactionRecordTest::TearDown(void) +{ + if (g_transaction != nullptr) { + delete g_transaction; + g_transaction = nullptr; + } + + if (remove(g_storeDir.c_str()) != 0) { + LOGE("remove db failed, errno:%d", errno); + } +} + +/** + * @tc.name: MultiverStorage001 + * @tc.desc: test the putting non empty data with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the current version. + */ + Version originVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, originVer), E_OK); + + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + + /** + * @tc.steps: step2. Put the new data into the database. + * @tc.expected: step2. Put returns E_OK. + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + + /** + * @tc.steps: step3. Get the new data and check the value. + * @tc.expected: step3. Get returns E_OK and the read value is equal to the put value. + */ + Value valueRead; + EXPECT_EQ(g_transaction->Get(key, valueRead), E_OK); + + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value), true); + + /** + * @tc.steps: step4. Get the current max version. + * @tc.expected: step4. The current max version is greater than the max version before put. + */ + Version currentVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + ASSERT_GT(currentVer, originVer); +} + +/** + * @tc.name: MultiverStorage002 + * @tc.desc: test the putting data(empty key) with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the current version. + */ + Version originVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, originVer), E_OK); + + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + /** + * @tc.steps: step2. Put the new data whose key is empty and value is not empty into the database. + * @tc.expected: step2. Put returns not E_OK + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_NE(g_transaction->Put(key, value), E_OK); + /** + * @tc.steps: step3. Get the current max version. + * @tc.expected: step3. The current max version is equal to the max version before put + */ + Version currentVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + EXPECT_EQ(currentVer, originVer); +} + +/** + * @tc.name: MultiverStorage003 + * @tc.desc: test the putting data(empty value) with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage003, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the current version. + */ + Version originVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, originVer), E_OK); + + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + Value value; + /** + * @tc.steps: step2. Put the new data whose key is not empty and value is empty into the database. + * @tc.expected: step2. Put returns E_OK + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + + Value valueRead; + /** + * @tc.steps: step3. Get the new data and check the value. + * @tc.expected: step3. Get returns E_OK and the read value is equal to the put value. + */ + EXPECT_EQ(g_transaction->Get(key, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value), true); + /** + * @tc.steps: step4. Get the current max version. + * @tc.expected: step4. The current max version is greater than the max version before put. + */ + Version currentVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + ASSERT_GT(currentVer, originVer); +} + +/** + * @tc.name: MultiverStorage004 + * @tc.desc: Update the data value to non-empty with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage004, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the current version. + */ + Version originVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, originVer), E_OK); + + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + + /** + * @tc.steps: step2. Put the data whose key is not empty and value is empty into the database. + * @tc.expected: step2. Put returns E_OK + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + Version currentVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + EXPECT_GT(currentVer, originVer); + originVer = currentVer; + g_transaction->ResetVersion(); + + Value valueChanged; + + /** + * @tc.steps: step3. Update the data with another non-empty value. + */ + DistributedDBToolsUnitTest::GetRandomKeyValue(valueChanged); + g_transaction->SetVersion(originVer + 1); + EXPECT_EQ(g_transaction->Put(key, valueChanged), E_OK); + Value valueRead; + /** + * @tc.steps: step4. Get the data according the key and check the value. + * @tc.steps: step5. Get the current max version. + * @tc.expected: step4. Get returns E_OK and the value is equal to the new put value. + * @tc.expected: step5. The current max version is greater than the max version before update. + */ + EXPECT_EQ(g_transaction->Get(key, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, valueChanged), true); + + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + ASSERT_GT(currentVer, originVer); +} + +/** + * @tc.name: MultiverStorage005 + * @tc.desc: Update the data value to empty with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage005, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the current version. + */ + Version originVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, originVer), E_OK); + + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + /** + * @tc.steps: step2. Put the data whose key is not empty and value is not empty into the database. + * @tc.expected: step2. Put returns E_OK + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + Version currentVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + EXPECT_GT(currentVer, originVer); + originVer = currentVer; + g_transaction->ResetVersion(); + + Value valueChanged; + /** + * @tc.steps: step3. Update the data with empty value. + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_EQ(g_transaction->Put(key, valueChanged), E_OK); + Value valueRead; + /** + * @tc.steps: step4. Get the data according the key and check the value. + * @tc.steps: step5. Get the current max version. + * @tc.expected: step4. Get returns E_OK and the value is empty. + * @tc.expected: step5. The current max version is greater than the max version before update. + */ + EXPECT_EQ(g_transaction->Get(key, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, valueChanged), true); + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + ASSERT_GT(currentVer, originVer); +} + +/** + * @tc.name: MultiverStorage006 + * @tc.desc: Delete the existed data with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage006, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the current version. + */ + Version originVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, originVer), E_OK); + + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + /** + * @tc.steps: step2. Put the data whose key is not empty and value is not empty into the database. + * @tc.expected: step2. Put returns E_OK + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + Version currentVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + EXPECT_GT(currentVer, originVer); + originVer = currentVer; + + EXPECT_EQ(g_transaction->Get(key, value), E_OK); + Value valueChanged; + /** + * @tc.steps: step3. Delete the data according the key. + * @tc.expected: step3. Delete returns E_OK. + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_EQ(g_transaction->Delete(key), E_OK); + Value valueRead; + /** + * @tc.steps: step4. Get the value according the key. + * @tc.expected: step4. Get returns -E_NOT_FOUND. + */ + EXPECT_EQ(g_transaction->Get(key, valueRead), -E_NOT_FOUND); + /** + * @tc.steps: step5. Get the current max version. + * @tc.expected: step5. The current max version is greater than the max version before delete. + */ + currentVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + ASSERT_GT(currentVer, originVer); +} + +/** + * @tc.name: MultiverStorage007 + * @tc.desc: Delete the non-existed data with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage007, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the current version. + */ + Version originVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, originVer), E_OK); + + Key key = {12, 57, 89}; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + g_transaction->SetVersion(originVer + 1); + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + Version currentVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + EXPECT_GT(currentVer, originVer); + originVer = currentVer; + g_transaction->ResetVersion(); + + Key newKey = {87, 68, 78}; + /** + * @tc.steps: step2. Delete the non-existed data according the key. + * @tc.expected: step2. Delete returns not E_OK. + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_NE(g_transaction->Delete(newKey), E_OK); + /** + * @tc.steps: step3. Get the current max version. + * @tc.expected: step2. The current max version is equal to the max version before delete. + */ + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + EXPECT_EQ(currentVer, originVer); +} + +/** + * @tc.name: MultiverStorage008 + * @tc.desc: Delete an empty key with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage008, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the current version. + */ + Version originVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, originVer), E_OK); + + Key key; + /** + * @tc.steps: step2. Delete the data whose key is empty from the empty database. + * @tc.steps: step3. Get the current max version. + * @tc.expected: step2. Delete returns not E_OK + * @tc.expected: step3. The current max version is equal to the max version before delete. + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_NE(g_transaction->Delete(key), E_OK); + Version currentVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + EXPECT_EQ(currentVer, originVer); + originVer = currentVer; + g_transaction->ResetVersion(); + + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + /** + * @tc.steps: step4. Put the non-empty key and non-empty value into the database. + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + EXPECT_NE(currentVer, originVer); + originVer = currentVer; + g_transaction->ResetVersion(); + key.clear(); + /** + * @tc.steps: step5. Delete the data whose key is empty from the non-empty database. + * @tc.steps: step6. Get the current max version. + * @tc.expected: step5. Delete returns not E_OK + * @tc.expected: step6. The current max version is equal to the max version before delete. + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_NE(g_transaction->Delete(key), E_OK); + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + EXPECT_EQ(currentVer, originVer); +} + +/** + * @tc.name: MultiverStorage009 + * @tc.desc: Clear the existed data with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage009, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the current version. + */ + Version originVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, originVer), E_OK); + Key key1, key2; + Value value1, value2; + + DistributedDBToolsUnitTest::GetRandomKeyValue(key1); + DistributedDBToolsUnitTest::GetRandomKeyValue(value1); + DistributedDBToolsUnitTest::GetRandomKeyValue(key2); + DistributedDBToolsUnitTest::GetRandomKeyValue(value2); + + /** + * @tc.steps: step2. Put 2 entries into the database. + * @tc.expected: step2. Put returns E_OK + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_EQ(g_transaction->Put(key1, value1), E_OK); + EXPECT_EQ(g_transaction->Put(key2, value2), E_OK); + Version currentVer = 0; + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + EXPECT_NE(currentVer, originVer); + originVer = currentVer; + g_transaction->ResetVersion(); + /** + * @tc.steps: step3. Clear data from the database. + * @tc.expected: step3. Clear returns E_OK + */ + g_transaction->SetVersion(originVer + 1); + EXPECT_EQ(g_transaction->Clear(), E_OK); + Value value1Read, value2Read; + /** + * @tc.steps: step4. Get the current max version. + * @tc.expected: step4. The current max version is greater than the max version before clear. + */ + EXPECT_EQ(g_transaction->GetMaxVersion(MultiVerDataType::ALL_TYPE, currentVer), E_OK); + EXPECT_GT(currentVer, originVer); + /** + * @tc.steps: step5. Get the put data before clear. + * @tc.expected: step5. Cannot get the data after clear. + */ + EXPECT_NE(g_transaction->Get(key1, value1Read), E_OK); + EXPECT_NE(g_transaction->Get(key2, value2Read), E_OK); +} + +/** + * @tc.name: MultiverStorage010 + * @tc.desc: Get the existed data with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage010, TestSize.Level1) +{ + /** + * @tc.steps: step1. Generate the random data. + */ + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + Value valueRead; + EXPECT_EQ(g_transaction->Get(key, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value), true); +} + +/** + * @tc.name: MultiverStorage011 + * @tc.desc: Get the non-existed data with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage011, TestSize.Level1) +{ + /** + * @tc.steps: step1. Generate the random data. + */ + Key key; + Value value, valueRead; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + EXPECT_EQ(g_transaction->Get(key, valueRead), -E_NOT_FOUND); + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + + /** + * @tc.steps: step2. Put the data whose key is not empty and value is not empty into the database. + * @tc.expected: step2. Put returns E_OK + */ + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + + key.push_back('7'); + /** + * @tc.steps: step3. Get the non-existed key. + * @tc.expected: step3. Get returns E_OK + */ + EXPECT_EQ(g_transaction->Get(key, valueRead), -E_NOT_FOUND); +} + +/** + * @tc.name: MultiverStorage012 + * @tc.desc: Get the empty-key data with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage012, TestSize.Level1) +{ + /** + * @tc.steps: step1. Generate the random data. + */ + Key key; + Value value; + /** + * @tc.steps: step2. Get the value according the empty key from the empty database. + * @tc.expected: step2. Get returns not E_OK. + */ + EXPECT_NE(g_transaction->Get(key, value), E_OK); + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + /** + * @tc.steps: step3. Put the non-empty data into the database. + */ + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + Key keyEmpty; + /** + * @tc.steps: step4. Get the value according the empty key from the non-empty database. + * @tc.expected: step4. Get returns not E_OK. + */ + EXPECT_NE(g_transaction->Get(keyEmpty, value), E_OK); +} + +/** + * @tc.name: MultiverStorage013 + * @tc.desc: Get the deleted data with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage013, TestSize.Level1) +{ + /** + * @tc.steps: step1. Generate the random data. + */ + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + /** + * @tc.steps: step2. put the non-empty data into the database. + */ + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + EXPECT_EQ(g_transaction->Get(key, value), E_OK); + /** + * @tc.steps: step3. delete the data from the database. + */ + EXPECT_EQ(g_transaction->Delete(key), E_OK); + Value valueRead; + /** + * @tc.steps: step4. Get the value according the key. + * @tc.expected: step4. Get returns -E_NOT_FOUND. + */ + EXPECT_EQ(g_transaction->Get(key, valueRead), -E_NOT_FOUND); +} + +/** + * @tc.name: MultiverStorage014 + * @tc.desc: Get the modified data with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage014, TestSize.Level1) +{ + /** + * @tc.steps: step1. Generate the random data. + */ + Key key; + Value value; + + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + /** + * @tc.steps: step2. put the non-empty data into the database. + */ + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + Value valueChanged = value; + valueChanged.push_back('H'); + /** + * @tc.steps: step3. update the data into the database. + */ + EXPECT_EQ(g_transaction->Put(key, valueChanged), E_OK); + Value valueRead; + /** + * @tc.steps: step4. Get the value according the key and check the value. + * @tc.expected: step4. Get returns E_OK and the read value is equal to the put value. + */ + EXPECT_EQ(g_transaction->Get(key, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, valueChanged), true); +} + +/** + * @tc.name: MultiverStorage015 + * @tc.desc: Get the data after clear with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage015, TestSize.Level1) +{ + /** + * @tc.steps: step1. Generate the random data. + */ + Key key; + Value value; + + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + DistributedDBToolsUnitTest::GetRandomKeyValue(value); + + /** + * @tc.steps: step2. put the non-empty data into the database. + */ + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + /** + * @tc.steps: step3. clear the database. + */ + EXPECT_EQ(g_transaction->Clear(), E_OK); + Value valueRead; + /** + * @tc.steps: step4. get the data from the database after clear. + * @tc.expected: step4. Get returns -E_NOT_FOUND. + */ + EXPECT_EQ(g_transaction->Get(key, valueRead), -E_NOT_FOUND); +} + +/** + * @tc.name: MultiverStorage016 + * @tc.desc: Get the new inserted data after clear with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage016, TestSize.Level1) +{ + /** + * @tc.steps: step1. Generate the random data. + */ + Key key1; + Value value1; + + DistributedDBToolsUnitTest::GetRandomKeyValue(key1); + DistributedDBToolsUnitTest::GetRandomKeyValue(value1); + + /** + * @tc.steps: step2. put the non-empty data into the database. + */ + EXPECT_EQ(g_transaction->Put(key1, value1), E_OK); + /** + * @tc.steps: step3. clear the database. + */ + EXPECT_EQ(g_transaction->Clear(), E_OK); + Value valueRead; + EXPECT_EQ(g_transaction->Get(key1, valueRead), -E_NOT_FOUND); + Value value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(value2); + /** + * @tc.steps: step4. re-put the data into the database using another value. + */ + EXPECT_EQ(g_transaction->Put(key1, value2), E_OK); + /** + * @tc.steps: step5. Get the value according the key and check the value. + * @tc.expected: step5. Get returns E_OK and the read value is equal to the new put value. + */ + EXPECT_EQ(g_transaction->Get(key1, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, value2), true); +} + +/** + * @tc.name: MultiverStorage017 + * @tc.desc: Get the new inserted data after delete with the transaction. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage017, TestSize.Level1) +{ + /** + * @tc.steps: step1. Get the random data. + */ + Key key; + Value value; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + DistributedDBToolsUnitTest::GetRandomKeyValue(value, 79); + + /** + * @tc.steps: step2. Put one data into the database. + */ + EXPECT_EQ(g_transaction->Put(key, value), E_OK); + /** + * @tc.steps: step3. Delete the data from the database. + */ + EXPECT_EQ(g_transaction->Delete(key), E_OK); + Value valueRead; + /** + * @tc.steps: step4. Get the data from the database according the key. + * @tc.expected: step4. Get returns -E_NOT_FOUND. + */ + EXPECT_EQ(g_transaction->Get(key, valueRead), -E_NOT_FOUND); + Value valueChanged; + /** + * @tc.steps: step5. Put the same key, different value into the database. + */ + DistributedDBToolsUnitTest::GetRandomKeyValue(valueChanged, 178); + EXPECT_EQ(g_transaction->Put(key, valueChanged), E_OK); + /** + * @tc.steps: step6. Get the data from the database according the key. + * @tc.expected: step6. Get returns E_OK and the get value is equal to the value put int the step5. + */ + EXPECT_EQ(g_transaction->Get(key, valueRead), E_OK); + EXPECT_EQ(DistributedDBToolsUnitTest::IsValueEqual(valueRead, valueChanged), true); +} + +/** + * @tc.name: MultiverStorage018 + * @tc.desc: Get the batch inserted data through the non-empty prefix key. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage018, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the batch data into the database(3 entries have the same prefix key, + * and another has different prefix key). + */ + Entry entry1, entry2, entry3, entry4; + DistributedDBToolsUnitTest::GetRandomKeyValue(entry1.key, 97); + entry2.key = entry1.key; + entry2.key.push_back('W'); + entry3.key = entry1.key; + entry3.key.push_back('C'); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry4.key, 67); + + DistributedDBToolsUnitTest::GetRandomKeyValue(entry1.value); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry2.value); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry3.value); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry4.value); + + EXPECT_EQ(g_transaction->Put(entry1.key, entry1.value), E_OK); + EXPECT_EQ(g_transaction->Put(entry2.key, entry2.value), E_OK); + EXPECT_EQ(g_transaction->Put(entry3.key, entry3.value), E_OK); + EXPECT_EQ(g_transaction->Put(entry4.key, entry4.value), E_OK); + /** + * @tc.steps: step2. Get the batch data using the prefix key. + * @tc.expected: step2. GetEntries returns E_OK and the number of the result entries is E_OK. + */ + std::vector entriesRead; + EXPECT_EQ(g_transaction->GetEntries(entry1.key, entriesRead), E_OK); + EXPECT_EQ(entriesRead.size(), 3UL); + EXPECT_EQ(DistributedDBToolsUnitTest::IsKvEntryExist(entry1, entriesRead), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsKvEntryExist(entry2, entriesRead), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsKvEntryExist(entry3, entriesRead), true); +} + +/** + * @tc.name: MultiverStorage019 + * @tc.desc: Get the non-existed data through the non-empty prefix key. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage019, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the batch data into the database. + */ + Entry entry1, entry2; + DistributedDBToolsUnitTest::GetRandomKeyValue(entry1.key, 97); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry2.key, 204); + + DistributedDBToolsUnitTest::GetRandomKeyValue(entry1.value); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry2.value); + + EXPECT_EQ(g_transaction->Put(entry1.key, entry1.value), E_OK); + EXPECT_EQ(g_transaction->Put(entry2.key, entry2.value), E_OK); + /** + * @tc.steps: step2. Get the batch data from the database using the prefix key different from the data put before. + * @tc.expected: step2. GetEntries returns -E_NOT_FOUND. + */ + std::vector entriesRead; + Key key; + DistributedDBToolsUnitTest::GetRandomKeyValue(key, 210); + EXPECT_EQ(g_transaction->GetEntries(key, entriesRead), -E_NOT_FOUND); +} + +/** + * @tc.name: MultiverStorage020 + * @tc.desc: Get all the data through the empty prefix key. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage020, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the data. + */ + Entry entry1, entry2, entry3; + DistributedDBToolsUnitTest::GetRandomKeyValue(entry1.key, 134); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry2.key, 204); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry3.key, 43); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry1.value); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry2.value); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry3.value); + + EXPECT_EQ(g_transaction->Put(entry1.key, entry1.value), E_OK); + EXPECT_EQ(g_transaction->Put(entry2.key, entry2.value), E_OK); + EXPECT_EQ(g_transaction->Put(entry3.key, entry3.value), E_OK); + /** + * @tc.steps: step2. Get the batch data from the database using the empty prefix key. + * @tc.expected: step2. GetEntries returns E_OK and . + */ + std::vector entriesRead; + Key keyEmpty; + EXPECT_EQ(g_transaction->GetEntries(keyEmpty, entriesRead), E_OK); + EXPECT_EQ(entriesRead.size(), 3UL); + EXPECT_EQ(DistributedDBToolsUnitTest::IsKvEntryExist(entry1, entriesRead), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsKvEntryExist(entry2, entriesRead), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsKvEntryExist(entry3, entriesRead), true); +} + +/** + * @tc.name: MultiverStorage021 + * @tc.desc: Get the data through the empty prefix key for multiple put the same key data. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage021, TestSize.Level1) +{ + /** + * @tc.steps: step1. Put the same key, different value for 3 times into the database. + */ + Key key; + Value value1, value2, value3; + DistributedDBToolsUnitTest::GetRandomKeyValue(key); + + DistributedDBToolsUnitTest::GetRandomKeyValue(value1, 46); + EXPECT_EQ(g_transaction->Put(key, value1), E_OK); + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, 28); + EXPECT_EQ(g_transaction->Put(key, value2), E_OK); + DistributedDBToolsUnitTest::GetRandomKeyValue(value3, 157); + EXPECT_EQ(g_transaction->Put(key, value3), E_OK); + /** + * @tc.steps: step2. Get the batch data from the database using the empty prefix key. + * @tc.expected: step2. GetEntries returns E_OK and the entries size is 1. + */ + std::vector entriesRead; + EXPECT_EQ(g_transaction->GetEntries(key, entriesRead), E_OK); + EXPECT_EQ(entriesRead.size(), 1UL); + Entry entry = {key, value3}; + + EXPECT_EQ(DistributedDBToolsUnitTest::IsKvEntryExist(entry, entriesRead), true); +} + +/** + * @tc.name: MultiverStorage022 + * @tc.desc: Get the data through the empty prefix key for deleted data. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage022, TestSize.Level1) +{ + std::vector entries; + Entry entry; + DistributedDBToolsUnitTest::GetRandomKeyValue(entry.key); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry.value); + Entry entry1 = entry; + entry1.key.push_back('o'); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry1.value); + entries.push_back(entry); + entries.push_back(entry1); + /** + * @tc.steps: step1. Put the batch data. + */ + EXPECT_EQ(g_transaction->PutBatch(entries), E_OK); + std::vector keys = {entry.key, entry1.key}; + /** + * @tc.steps: step2. Delete the batch data. + */ + EXPECT_EQ(g_transaction->Delete(entry.key), E_OK); + EXPECT_EQ(g_transaction->Delete(entry1.key), E_OK); + + /** + * @tc.steps: step3. Get all the data. + * @tc.expected: step3. Returns -E_NOT_FOUND. + */ + Key keyEmpty; + std::vector entriesRead; + EXPECT_EQ(g_transaction->GetEntries(keyEmpty, entriesRead), -E_NOT_FOUND); +} + +/** + * @tc.name: MultiverStorage023 + * @tc.desc: Get the data through the empty prefix key for updated data. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage023, TestSize.Level1) +{ + Key key1, key2; + Value value1, value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(key1, 10); + key2 = key1; + key2.push_back('S'); + DistributedDBToolsUnitTest::GetRandomKeyValue(value1, 46); + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, 28); + /** + * @tc.steps: step1. Put the batch data. + */ + EXPECT_EQ(g_transaction->Put(key1, value1), E_OK); + EXPECT_EQ(g_transaction->Put(key2, value2), E_OK); + Value value1Changed, value2Changed; + /** + * @tc.steps: step2. Update the batch data. + */ + DistributedDBToolsUnitTest::GetRandomKeyValue(value1Changed, 86); + DistributedDBToolsUnitTest::GetRandomKeyValue(value2Changed, 149); + EXPECT_EQ(g_transaction->Put(key1, value1Changed), E_OK); + EXPECT_EQ(g_transaction->Put(key2, value2Changed), E_OK); + Key keyEmpty; + /** + * @tc.steps: step3. Get all the data. + * @tc.expected: step3. the data are equal to the updated data. + */ + std::vector entriesRead; + EXPECT_EQ(g_transaction->GetEntries(keyEmpty, entriesRead), E_OK); + ASSERT_EQ(entriesRead.size(), 2UL); + + Entry entry1 = {key1, value1Changed}; + Entry entry2 = {key2, value2Changed}; + EXPECT_EQ(DistributedDBToolsUnitTest::IsKvEntryExist(entry1, entriesRead), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsKvEntryExist(entry2, entriesRead), true); +} + +/** + * @tc.name: MultiverStorage024 + * @tc.desc: Get the data through the empty prefix key for cleared data. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage024, TestSize.Level1) +{ + Key key1, key2; + Value value1, value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(key1, 10); + DistributedDBToolsUnitTest::GetRandomKeyValue(key2, 20); + DistributedDBToolsUnitTest::GetRandomKeyValue(value1); + DistributedDBToolsUnitTest::GetRandomKeyValue(value2); + /** + * @tc.steps: step1. Put the batch data. + */ + EXPECT_EQ(g_transaction->Put(key1, value1), E_OK); + EXPECT_EQ(g_transaction->Put(key2, value2), E_OK); + Key keyEmpty; + std::vector entriesRead; + /** + * @tc.steps: step2. Get all the data. + * @tc.expected: step2. the data are equal to the data put before. + */ + EXPECT_EQ(g_transaction->GetEntries(keyEmpty, entriesRead), E_OK); + ASSERT_EQ(entriesRead.size(), 2UL); + /** + * @tc.steps: step3. Clear the data and get all the data. + * @tc.expected: step3. Get returns -E_NOT_FOUND. + */ + EXPECT_EQ(g_transaction->Clear(), E_OK); + EXPECT_EQ(g_transaction->GetEntries(keyEmpty, entriesRead), -E_NOT_FOUND); +} + +/** + * @tc.name: MultiverStorage025 + * @tc.desc: Get the data through the put, delete, re-put operation. + * @tc.type: FUNC + * @tc.require: AR000C6TRV AR000CQDTM + * @tc.author: wangbingquan + */ +HWTEST_F(DistributedDBStorageTransactionRecordTest, MultiverStorage025, TestSize.Level1) +{ + std::vector entries; + Entry entry; + DistributedDBToolsUnitTest::GetRandomKeyValue(entry.key); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry.value); + Entry entry1 = entry; + entry1.key.push_back('q'); + DistributedDBToolsUnitTest::GetRandomKeyValue(entry1.value); + entries.push_back(entry); + entries.push_back(entry1); + /** + * @tc.steps: step1. Put the batch data. + */ + EXPECT_EQ(g_transaction->PutBatch(entries), E_OK); + std::vector keys = {entry.key, entry1.key}; + /** + * @tc.steps: step2. Delete the batch data. + */ + EXPECT_EQ(g_transaction->Delete(entry.key), E_OK); + EXPECT_EQ(g_transaction->Delete(entry1.key), E_OK); + /** + * @tc.steps: step3. Get all the data. + * @tc.expected: step3. Get results -E_NOT_FOUND. + */ + Key keyEmpty; + std::vector entriesRead; + EXPECT_EQ(g_transaction->GetEntries(keyEmpty, entriesRead), -E_NOT_FOUND); + entry.value.push_back('q'); + entry1.value.push_back('s'); + Value valueRead, valueRead1; + entriesRead.clear(); + /** + * @tc.steps: step5. Re-put the different value into the database. + */ + EXPECT_EQ(g_transaction->Put(entry.key, valueRead), E_OK); + EXPECT_EQ(g_transaction->Put(entry1.key, valueRead1), E_OK); + /** + * @tc.steps: step6. Get all the data. + * @tc.expected: step6. Get results E_OK and the data are equal to the inserted data after deleted operation. + */ + EXPECT_EQ(g_transaction->GetEntries(keyEmpty, entriesRead), E_OK); + ASSERT_EQ(entriesRead.size(), 2UL); + + entry.value.clear(); + entry1.value.clear(); + EXPECT_EQ(DistributedDBToolsUnitTest::IsKvEntryExist(entry, entriesRead), true); + EXPECT_EQ(DistributedDBToolsUnitTest::IsKvEntryExist(entry1, entriesRead), true); +} + diff --git a/mock/distributeddb/test/unittest/common/storage/multi_ver_vacuum_executor_stub.cpp b/mock/distributeddb/test/unittest/common/storage/multi_ver_vacuum_executor_stub.cpp new file mode 100644 index 00000000..9a69f282 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/multi_ver_vacuum_executor_stub.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "multi_ver_vacuum_executor_stub.h" +#include +#include "db_errno.h" + +using namespace DistributedDB; + +MultiVerVacuumExecutorStub::MultiVerVacuumExecutorStub(const DbScale &inScale, int timeCostEachCall) + : dbScale_(inScale), timeCostEachCall_(timeCostEachCall), transactionOccupied_(false) +{ +} + +MultiVerVacuumExecutorStub::~MultiVerVacuumExecutorStub() +{ +} + +bool MultiVerVacuumExecutorStub::IsTransactionOccupied() +{ + return transactionOccupied_; +} + +int MultiVerVacuumExecutorStub::GetVacuumAbleCommits(std::list &leftBranchCommits, + std::list &rightBranchCommits) const +{ + std::this_thread::sleep_for(std::chrono::milliseconds(timeCostEachCall_)); + for (uint8_t i = dbScale_.left + dbScale_.right; i > dbScale_.right; i--) { + MultiVerCommitInfo commit; + commit.version = i; + commit.commitId.push_back(i); + leftBranchCommits.push_back(commit); + } + for (uint8_t i = dbScale_.right; i > 0; i--) { + MultiVerCommitInfo commit; + commit.version = i; + commit.commitId.push_back(i); + rightBranchCommits.push_back(commit); + } + return E_OK; +} + +int MultiVerVacuumExecutorStub::GetVacuumNeedRecordsByVersion(uint64_t version, + std::list &vacuumNeedRecords) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(timeCostEachCall_)); + for (uint8_t i = dbScale_.vacuumNeed; i > 0; i--) { + MultiVerRecordInfo record; + record.type = RecordType::VALID; + record.version = version; + record.hashKey.push_back(i); + vacuumNeedRecords.push_back(record); + } + return E_OK; +} + +int MultiVerVacuumExecutorStub::GetShadowRecordsOfClearTypeRecord(uint64_t version, + const std::vector &hashKey, std::list &shadowRecords) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(timeCostEachCall_)); + return E_OK; +} + +int MultiVerVacuumExecutorStub::GetShadowRecordsOfNonClearTypeRecord(uint64_t version, + const std::vector &hashKey, std::list &shadowRecords) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(timeCostEachCall_)); + for (uint8_t i = dbScale_.shadow; i > 0; i--) { + MultiVerRecordInfo record; + record.type = RecordType::VALID; + record.version = i; + record.hashKey = hashKey; + shadowRecords.push_back(record); + } + return E_OK; +} + +int MultiVerVacuumExecutorStub::StartTransactionForVacuum() +{ + std::this_thread::sleep_for(std::chrono::milliseconds(timeCostEachCall_)); + transactionOccupied_ = true; + return E_OK; +} + +int MultiVerVacuumExecutorStub::CommitTransactionForVacuum() +{ + std::this_thread::sleep_for(std::chrono::milliseconds(timeCostEachCall_)); + transactionOccupied_ = false; + return E_OK; +} + +int MultiVerVacuumExecutorStub::RollBackTransactionForVacuum() +{ + std::this_thread::sleep_for(std::chrono::milliseconds(timeCostEachCall_)); + transactionOccupied_ = false; + return E_OK; +} + +int MultiVerVacuumExecutorStub::DeleteRecordTotally(uint64_t version, const std::vector &hashKey) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(timeCostEachCall_)); + return E_OK; +} + +int MultiVerVacuumExecutorStub::MarkRecordAsVacuumDone(uint64_t version, const std::vector &hashKey) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(timeCostEachCall_)); + return E_OK; +} + +int MultiVerVacuumExecutorStub::MarkCommitAsVacuumDone(const std::vector &commitId) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(timeCostEachCall_)); + return E_OK; +} diff --git a/mock/distributeddb/test/unittest/common/storage/multi_ver_vacuum_executor_stub.h b/mock/distributeddb/test/unittest/common/storage/multi_ver_vacuum_executor_stub.h new file mode 100644 index 00000000..a96f02aa --- /dev/null +++ b/mock/distributeddb/test/unittest/common/storage/multi_ver_vacuum_executor_stub.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MULTI_VER_VACUUM_EXECUTOR_STUB_H +#define MULTI_VER_VACUUM_EXECUTOR_STUB_H + +#include +#include "multi_ver_vacuum_executor.h" + +namespace DistributedDB { +struct DbScale { + uint8_t left = 1; + uint8_t right = 1; + uint8_t vacuumNeed = 1; + uint8_t shadow = 1; +}; + +class MultiVerVacuumExecutorStub : public MultiVerVacuumExecutor { +public: + // Total Time: (3 + 2L + 2LT + LTS + 2R + RT) Multiple timeCostEachCall(In Millisecond) + MultiVerVacuumExecutorStub(const DbScale &inScale, int timeCostEachCall); + ~MultiVerVacuumExecutorStub(); + + bool IsTransactionOccupied(); + + int GetVacuumAbleCommits(std::list &leftBranchCommits, + std::list &rightBranchCommits) const; + int GetVacuumNeedRecordsByVersion(uint64_t version, std::list &vacuumNeedRecords); + int GetShadowRecordsOfClearTypeRecord(uint64_t version, const std::vector &hashKey, + std::list &shadowRecords); + int GetShadowRecordsOfNonClearTypeRecord(uint64_t version, const std::vector &hashKey, + std::list &shadowRecords); + + int StartTransactionForVacuum(); + int CommitTransactionForVacuum(); + int RollBackTransactionForVacuum(); + + int DeleteRecordTotally(uint64_t version, const std::vector &hashKey); + int MarkRecordAsVacuumDone(uint64_t version, const std::vector &hashKey); + int MarkCommitAsVacuumDone(const std::vector &commitId); +private: + DbScale dbScale_; + int timeCostEachCall_; + std::atomic transactionOccupied_; +}; +} + +#endif // MULTI_VER_VACUUM_EXECUTOR_STUB_H \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_ability_sync_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_ability_sync_test.cpp new file mode 100644 index 00000000..bd9ec951 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_ability_sync_test.cpp @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "ability_sync.h" +#include "distributeddb_tools_unit_test.h" +#include "single_ver_kv_sync_task_context.h" +#include "sync_types.h" +#include "version.h" +#include "virtual_communicator_aggregator.h" +#include "virtual_single_ver_sync_db_Interface.h" + +using namespace std; +using namespace testing::ext; +using namespace DistributedDB; + +namespace { + const std::string DEVICE_A = "deviceA"; + const std::string DEVICE_B = "deviceB"; + const std::string TEST_SCHEMA = "{\"SCHEMA_DEFINE\":{\"value\":\"LONG\"},\"SCHEMA_MODE\":\"COMPATIBLE\"," + "\"SCHEMA_VERSION\":\"1.0\"}"; + + VirtualSingleVerSyncDBInterface *g_syncInterface = nullptr; + VirtualCommunicatorAggregator *g_communicatorAggregator = nullptr; + + ICommunicator *g_communicatorA = nullptr; + ICommunicator *g_communicatorB = nullptr; + std::shared_ptr g_meta = nullptr; +} + +class DistributedDBAbilitySyncTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBAbilitySyncTest::SetUpTestCase(void) +{ + /** + * @tc.setup: NA + */ +} + +void DistributedDBAbilitySyncTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: NA + */ +} + +void DistributedDBAbilitySyncTest::SetUp(void) +{ + DistributedDBUnitTest::DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: create the instance for virtual communicator, virtual storage + */ + g_syncInterface = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(g_syncInterface != nullptr); + g_syncInterface->SetSchemaInfo(TEST_SCHEMA); + g_communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator; + ASSERT_TRUE(g_communicatorAggregator != nullptr); + int errCode = E_OK; + g_communicatorA = g_communicatorAggregator->AllocCommunicator(DEVICE_A, errCode); + ASSERT_TRUE(g_communicatorA != nullptr); + g_communicatorB = g_communicatorAggregator->AllocCommunicator(DEVICE_B, errCode); + ASSERT_TRUE(g_communicatorB != nullptr); + g_meta = std::make_shared(); + g_meta->Initialize(g_syncInterface); +} + +void DistributedDBAbilitySyncTest::TearDown(void) +{ + /** + * @tc.teardown: delete the ptr for testing + */ + if (g_communicatorA != nullptr && g_communicatorAggregator != nullptr) { + g_communicatorAggregator->ReleaseCommunicator(g_communicatorA); + g_communicatorA = nullptr; + } + if (g_communicatorB != nullptr && g_communicatorAggregator != nullptr) { + g_communicatorAggregator->ReleaseCommunicator(g_communicatorB); + g_communicatorB = nullptr; + } + if (g_communicatorAggregator != nullptr) { + RefObject::KillAndDecObjRef(g_communicatorAggregator); + g_communicatorAggregator = nullptr; + } + if (g_syncInterface != nullptr) { + delete g_syncInterface; + g_syncInterface = nullptr; + } +} + +/** + * @tc.name: RequestPacketTest001 + * @tc.desc: Verify RequestPacketSerialization and RequestPacketDeSerialization function. + * @tc.type: FUNC + * @tc.require: AR000DR9K4 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBAbilitySyncTest, RequestPacketTest001, TestSize.Level0) +{ + /** + * @tc.steps: step1. create a AbilityRequestPacket packet1 + * @tc.steps: step2. set version = ABILITY_SYNC_VERSION_V1. schema = TEST_SCHEMA. + */ + AbilitySyncRequestPacket packet1; + DbAbility ability1; +#ifndef OMIT_ZLIB + ability1.SetAbilityItem(SyncConfig::DATABASE_COMPRESSION_ZLIB, SUPPORT_MARK); +#endif + packet1.SetProtocolVersion(ABILITY_SYNC_VERSION_V1); + packet1.SetSoftwareVersion(SOFTWARE_VERSION_CURRENT); + packet1.SetSchema(TEST_SCHEMA); + packet1.SetSendCode(E_OK); + packet1.SetDbAbility(ability1); + Message msg1(ABILITY_SYNC_MESSAGE); + msg1.SetMessageType(TYPE_REQUEST); + msg1.SetCopiedObject(packet1); + + /** + * @tc.steps: step3. call Serialization to Serialization the msg + * @tc.expected: step3. Serialization return E_OK + */ + uint32_t bufflen = packet1.CalculateLen(); + ASSERT_TRUE(bufflen != 0); + std::vector buff(bufflen, 0); + ASSERT_TRUE(AbilitySync::Serialization(buff.data(), bufflen, &msg1) == E_OK); + + /** + * @tc.steps: step4. call DeSerialization to DeSerialization the buff + * @tc.expected: step4. DeSerialization return E_OK + */ + Message msg2(ABILITY_SYNC_MESSAGE); + msg2.SetMessageType(TYPE_REQUEST); + ASSERT_TRUE(AbilitySync::DeSerialization(buff.data(), bufflen, &msg2) == E_OK); + const AbilitySyncRequestPacket *packet2 = msg2.GetObject(); + ASSERT_TRUE(packet2 != nullptr); + + /** + * @tc.expected: step5. packet1 == packet2 + */ + EXPECT_TRUE(packet2->GetProtocolVersion() == ABILITY_SYNC_VERSION_V1); + EXPECT_TRUE(packet2->GetSoftwareVersion() == SOFTWARE_VERSION_CURRENT); + EXPECT_TRUE(packet2->GetSendCode() == E_OK); + EXPECT_TRUE(packet2->GetDbAbility() == ability1); + std::string schema = packet2->GetSchema(); + EXPECT_EQ(schema, TEST_SCHEMA); +} + +/** + * @tc.name: RequestPacketTest002 + * @tc.desc: Verify RequestPacketSerialization and RequestPacketDeSerialization function when version not support. + * @tc.type: FUNC + * @tc.require: AR000DR9K4 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBAbilitySyncTest, RequestPacketTest002, TestSize.Level0) +{ + /** + * @tc.steps: step1. create a AbilityRequestPacket packet1 + * @tc.steps: step2. set version = ABILITY_SYNC_VERSION_V1 + 1. schema = TEST_SCHEMA. + */ + AbilitySyncRequestPacket packet1; + packet1.SetProtocolVersion(ABILITY_SYNC_VERSION_V1 + 1); + packet1.SetSoftwareVersion(SOFTWARE_VERSION_CURRENT); + packet1.SetSchema(""); + Message msg1(ABILITY_SYNC_MESSAGE); + msg1.SetMessageType(TYPE_REQUEST); + msg1.SetCopiedObject(packet1); + + /** + * @tc.steps: step3. call Serialization to Serialization the msg + * @tc.expected: step3. Serialization return E_OK + */ + uint32_t bufflen = packet1.CalculateLen(); + ASSERT_TRUE(bufflen != 0); + std::vector buff(bufflen, 0); + ASSERT_TRUE(AbilitySync::Serialization(buff.data(), bufflen, &msg1) == E_OK); + + /** + * @tc.steps: step4. call DeSerialization to DeSerialization the buff + * @tc.expected: step4. DeSerialization return E_OK + */ + Message msg2(ABILITY_SYNC_MESSAGE); + msg2.SetMessageType(TYPE_REQUEST); + ASSERT_TRUE(AbilitySync::DeSerialization(buff.data(), bufflen, &msg2) == E_OK); + const AbilitySyncRequestPacket *packet2 = msg2.GetObject(); + ASSERT_TRUE(packet2 != nullptr); + + /** + * @tc.expected: step5. packet2->GetSendCode() == -E_VERSION_NOT_SUPPORT + */ + EXPECT_TRUE(packet2->GetSendCode() == -E_VERSION_NOT_SUPPORT); +} + +/** + * @tc.name: RequestPacketTest003 + * @tc.desc: Verify RequestPacketSerialization and RequestPacketDeSerialization function. + * @tc.type: FUNC + * @tc.require: AR000DR9K4 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBAbilitySyncTest, RequestPacketTest003, TestSize.Level0) +{ + /** + * @tc.steps: step1. create a AbilityRequestPacket packet1 + * @tc.steps: step2. set version = ABILITY_SYNC_VERSION_V1. schema = TEST_SCHEMA. + */ + AbilitySyncRequestPacket packet1; + packet1.SetProtocolVersion(ABILITY_SYNC_VERSION_V1); + packet1.SetSoftwareVersion(SOFTWARE_VERSION_CURRENT); + packet1.SetSchema(TEST_SCHEMA); + packet1.SetSendCode(E_OK); + int secLabel = 3; // label 3 + int secFlag = 1; // flag 1 + packet1.SetSecLabel(secLabel); + packet1.SetSecFlag(secFlag); + Message msg1(ABILITY_SYNC_MESSAGE); + msg1.SetMessageType(TYPE_REQUEST); + msg1.SetCopiedObject(packet1); + + /** + * @tc.steps: step3. call Serialization to Serialization the msg + * @tc.expected: step3. Serialization return E_OK + */ + uint32_t bufflen = packet1.CalculateLen(); + ASSERT_TRUE(bufflen != 0); + std::vector buff(bufflen, 0); + ASSERT_TRUE(AbilitySync::Serialization(buff.data(), bufflen, &msg1) == E_OK); + + /** + * @tc.steps: step4. call DeSerialization to DeSerialization the buff + * @tc.expected: step4. DeSerialization return E_OK + */ + Message msg2(ABILITY_SYNC_MESSAGE); + msg2.SetMessageType(TYPE_REQUEST); + ASSERT_TRUE(AbilitySync::DeSerialization(buff.data(), bufflen, &msg2) == E_OK); + const AbilitySyncRequestPacket *packet2 = msg2.GetObject(); + ASSERT_TRUE(packet2 != nullptr); + + /** + * @tc.expected: step5. packet1 == packet2 + */ + EXPECT_TRUE(packet2->GetProtocolVersion() == ABILITY_SYNC_VERSION_V1); + EXPECT_TRUE(packet2->GetSoftwareVersion() == SOFTWARE_VERSION_CURRENT); + EXPECT_TRUE(packet2->GetSendCode() == E_OK); + std::string schema = packet2->GetSchema(); + EXPECT_EQ(schema, TEST_SCHEMA); + EXPECT_TRUE(packet2->GetSecFlag() == secFlag); + EXPECT_TRUE(packet2->GetSecLabel() == secLabel); +} + +/** + * @tc.name: RequestPacketTest004 + * @tc.desc: Verify RequestPacketSerialization and RequestPacketDeSerialization function. + * @tc.type: FUNC + * @tc.require: AR000DR9K4 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBAbilitySyncTest, RequestPacketTest004, TestSize.Level0) +{ + /** + * @tc.steps: step1. create a AbilityRequestPacket packet1 + * @tc.steps: step2. set version = ABILITY_SYNC_VERSION_V1. schema = TEST_SCHEMA. + */ + AbilitySyncRequestPacket packet1; + packet1.SetProtocolVersion(ABILITY_SYNC_VERSION_V1); + packet1.SetSoftwareVersion(SOFTWARE_VERSION_RELEASE_2_0); + packet1.SetSchema(TEST_SCHEMA); + packet1.SetSendCode(E_OK); + int secLabel = 3; // label 3 + int secFlag = 1; // flag 1 + packet1.SetSecLabel(secLabel); + packet1.SetSecFlag(secFlag); + Message msg1(ABILITY_SYNC_MESSAGE); + msg1.SetMessageType(TYPE_REQUEST); + msg1.SetCopiedObject(packet1); + + /** + * @tc.steps: step3. call Serialization to Serialization the msg + * @tc.expected: step3. Serialization return E_OK + */ + uint32_t bufflen = packet1.CalculateLen(); + ASSERT_TRUE(bufflen != 0); + std::vector buff(bufflen, 0); + ASSERT_TRUE(AbilitySync::Serialization(buff.data(), bufflen, &msg1) == E_OK); + + /** + * @tc.steps: step4. call DeSerialization to DeSerialization the buff + * @tc.expected: step4. DeSerialization return E_OK + */ + Message msg2(ABILITY_SYNC_MESSAGE); + msg2.SetMessageType(TYPE_REQUEST); + ASSERT_TRUE(AbilitySync::DeSerialization(buff.data(), bufflen, &msg2) == E_OK); + const AbilitySyncRequestPacket *packet2 = msg2.GetObject(); + ASSERT_TRUE(packet2 != nullptr); + + /** + * @tc.expected: step5. packet1 == packet2 + */ + EXPECT_TRUE(packet2->GetProtocolVersion() == ABILITY_SYNC_VERSION_V1); + EXPECT_TRUE(packet2->GetSoftwareVersion() == SOFTWARE_VERSION_RELEASE_2_0); + EXPECT_TRUE(packet2->GetSendCode() == E_OK); + std::string schema = packet2->GetSchema(); + EXPECT_EQ(schema, TEST_SCHEMA); + EXPECT_TRUE(packet2->GetSecFlag() == 0); + EXPECT_TRUE(packet2->GetSecLabel() == 0); +} + +/** + * @tc.name: AckPacketTest001 + * @tc.desc: Verify AckPacketSerialization and AckPacketDeSerialization function. + * @tc.type: FUNC + * @tc.require: AR000DR9K4 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBAbilitySyncTest, AckPacketTest001, TestSize.Level0) +{ + /** + * @tc.steps: step1. create a AbilityAckPacket packet1 + * @tc.steps: step2. set version = ABILITY_SYNC_VERSION_V1. schema = TEST_SCHEMA. + */ + AbilitySyncAckPacket packet1; + DbAbility ability1; +#ifndef OMIT_ZLIB + ability1.SetAbilityItem(SyncConfig::DATABASE_COMPRESSION_ZLIB, SUPPORT_MARK); +#endif + packet1.SetProtocolVersion(ABILITY_SYNC_VERSION_V1); + packet1.SetSoftwareVersion(SOFTWARE_VERSION_CURRENT); + packet1.SetSchema(TEST_SCHEMA); + packet1.SetAckCode(E_VERSION_NOT_SUPPORT); + packet1.SetDbAbility(ability1); + Message msg1(ABILITY_SYNC_MESSAGE); + msg1.SetMessageType(TYPE_RESPONSE); + msg1.SetCopiedObject(packet1); + + /** + * @tc.steps: step3. call Serialization to Serialization the msg + * @tc.expected: step3. Serialization return E_OK + */ + uint32_t bufflen = packet1.CalculateLen(); + ASSERT_TRUE(bufflen != 0); + std::vector buff(bufflen, 0); + ASSERT_EQ(AbilitySync::Serialization(buff.data(), bufflen, &msg1), E_OK); + + /** + * @tc.steps: step4. call DeSerialization to DeSerialization the buff + * @tc.expected: step4. DeSerialization return E_OK + */ + Message msg2(ABILITY_SYNC_MESSAGE); + msg2.SetMessageType(TYPE_RESPONSE); + ASSERT_TRUE(AbilitySync::DeSerialization(buff.data(), bufflen, &msg2) == E_OK); + const AbilitySyncAckPacket *packet2 = msg2.GetObject(); + ASSERT_TRUE(packet2 != nullptr); + + /** + * @tc.expected: step5. packet1 == packet2 + */ + EXPECT_TRUE(packet2->GetProtocolVersion() == ABILITY_SYNC_VERSION_V1); + EXPECT_TRUE(packet2->GetSoftwareVersion() == SOFTWARE_VERSION_CURRENT); + EXPECT_TRUE(packet2->GetAckCode() == E_VERSION_NOT_SUPPORT); + EXPECT_TRUE(packet2->GetDbAbility() == ability1); + std::string schema = packet2->GetSchema(); + ASSERT_TRUE(schema == TEST_SCHEMA); +} + +/** + * @tc.name: SyncStartTest001 + * @tc.desc: Verify Ability sync SyncStart function. + * @tc.type: FUNC + * @tc.require: AR000DR9K4 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBAbilitySyncTest, SyncStart001, TestSize.Level0) +{ + /** + * @tc.steps: step1. create a AbilitySync + */ + AbilitySync async; + async.Initialize(g_communicatorB, g_syncInterface, g_meta, DEVICE_A); + + /** + * @tc.steps: step2. call SyncStart + * @tc.expected: step2. SyncStart return E_OK + */ + EXPECT_EQ(async.SyncStart(1, 1, 1), E_OK); + + /** + * @tc.steps: step3. disable the communicator + */ + static_cast(g_communicatorB)->Disable(); + + /** + * @tc.steps: step4. call SyncStart + * @tc.expected: step4. SyncStart return -E_PERIPHERAL_INTERFACE_FAIL + */ + EXPECT_TRUE(async.SyncStart(1, 1, 1) == -E_PERIPHERAL_INTERFACE_FAIL); +} +#ifndef OMIT_JSON +/** + * @tc.name: RequestReceiveTest001 + * @tc.desc: Verify Ability RequestReceive callback. + * @tc.type: FUNC + * @tc.require: AR000DR9K4 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBAbilitySyncTest, RequestReceiveTest001, TestSize.Level0) +{ + /** + * @tc.steps: step1. create a AbilitySync + */ + AbilitySync async; + async.Initialize(g_communicatorB, g_syncInterface, g_meta, DEVICE_A); + + /** + * @tc.steps: step2. call RequestRecv, set inMsg nullptr or set context nullptr + * @tc.expected: step2. RequestRecv return -E_INVALID_ARGS + */ + Message msg1(ABILITY_SYNC_MESSAGE); + msg1.SetMessageType(TYPE_REQUEST); + SingleVerSyncTaskContext *context = new (std::nothrow) SingleVerKvSyncTaskContext(); + ASSERT_TRUE(context != nullptr); + EXPECT_EQ(async.RequestRecv(nullptr, context), -E_INVALID_ARGS); + EXPECT_EQ(async.RequestRecv(&msg1, nullptr), -E_INVALID_ARGS); + + /** + * @tc.steps: step3. call RequestRecv, set inMsg with no packet + * @tc.expected: step3. RequestRecv return -E_INVALID_ARGS + */ + EXPECT_EQ(async.RequestRecv(&msg1, context), -E_INVALID_ARGS); + + /** + * @tc.steps: step4. create a AbilityRequestkPacket packet1 + */ + AbilitySyncRequestPacket packet1; + packet1.SetProtocolVersion(ABILITY_SYNC_VERSION_V1); + packet1.SetSoftwareVersion(SOFTWARE_VERSION_CURRENT); + packet1.SetSchema(TEST_SCHEMA); + msg1.SetCopiedObject(packet1); + + /** + * @tc.steps: step5. call RequestRecv, set inMsg with packet + * @tc.expected: step5. RequestRecv return ok, GetRemoteSoftwareVersion is SOFTWARE_VERSION_CURRENT + * IsSchemaCompatible true + * + */ + EXPECT_EQ(async.RequestRecv(&msg1, context), OK); + EXPECT_TRUE(context->GetRemoteSoftwareVersion() == SOFTWARE_VERSION_CURRENT); + EXPECT_TRUE(context->GetTaskErrCode() != -E_SCHEMA_MISMATCH); + + /** + * @tc.steps: step6. call RequestRecv, set inMsg sendCode -E_VERSION_NOT_SUPPORT + * @tc.expected: step6. RequestRecv return E_VERSION_NOT_SUPPORT + */ + packet1.SetSendCode(-E_VERSION_NOT_SUPPORT); + msg1.SetCopiedObject(packet1); + EXPECT_EQ(async.RequestRecv(&msg1, context), -E_VERSION_NOT_SUPPORT); + + /** + * @tc.steps: step7. call RequestRecv, SetSchema "" + * @tc.expected: step7. IsSchemaCompatible false + */ + packet1.SetSchema(""); + packet1.SetSendCode(E_OK); + msg1.SetCopiedObject(packet1); + EXPECT_EQ(async.RequestRecv(&msg1, context), E_OK); + EXPECT_FALSE(context->GetTaskErrCode() != -E_SCHEMA_MISMATCH); + RefObject::KillAndDecObjRef(context); +} +#endif +/** + * @tc.name: AckReceiveTest001 + * @tc.desc: Verify Ability AckReceive callback. + * @tc.type: FUNC + * @tc.require: AR000DR9K4 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBAbilitySyncTest, AckReceiveTest001, TestSize.Level0) +{ + /** + * @tc.steps: step1. create a AbilitySync + */ + AbilitySync async; + async.Initialize(g_communicatorB, g_syncInterface, g_meta, DEVICE_A); + + /** + * @tc.steps: step2. call AckRecv, set inMsg nullptr or set context nullptr + * @tc.expected: step2. AckRecv return -E_INVALID_ARGS + */ + SingleVerSyncTaskContext *context = new (std::nothrow) SingleVerKvSyncTaskContext(); + ASSERT_TRUE(context != nullptr); + Message msg1(ABILITY_SYNC_MESSAGE); + msg1.SetMessageType(TYPE_RESPONSE); + EXPECT_EQ(async.AckRecv(nullptr, context), -E_INVALID_ARGS); + EXPECT_EQ(async.AckRecv(&msg1, nullptr), -E_INVALID_ARGS); + + /** + * @tc.steps: step3. call AckRecv, set inMsg with no packet + * @tc.expected: step3. AckRecv return -E_INVALID_ARGS + */ + EXPECT_EQ(async.AckRecv(&msg1, context), -E_INVALID_ARGS); + ASSERT_TRUE(context != nullptr); + + /** + * @tc.steps: step4. create a AbilityAckPacket packet1 + */ + AbilitySyncAckPacket packet1; + packet1.SetProtocolVersion(ABILITY_SYNC_VERSION_V1); + packet1.SetSoftwareVersion(SOFTWARE_VERSION_CURRENT); + packet1.SetAckCode(E_OK); + packet1.SetSchema(TEST_SCHEMA); + msg1.SetCopiedObject(packet1); + + /** + * @tc.steps: step5. call AckRecv, set inMsg with packet + * @tc.expected: step5. AckRecv return ok GetRemoteSoftwareVersion is SOFTWARE_VERSION_CURRENT + * IsSchemaCompatible true; + */ + EXPECT_EQ(async.AckRecv(&msg1, context), OK); + EXPECT_TRUE(context->GetRemoteSoftwareVersion() == SOFTWARE_VERSION_CURRENT); + EXPECT_TRUE(context->GetTaskErrCode() != -E_SCHEMA_MISMATCH); + + /** + * @tc.steps: step6. call RequestRecv, SetSchema "" + * @tc.expected: step6. IsSchemaCompatible false + */ + packet1.SetSchema(""); + msg1.SetCopiedObject(packet1); + EXPECT_EQ(async.AckRecv(&msg1, context), E_OK); + + /** + * @tc.steps: step7. call AckRecv, set inMsg sendCode -E_VERSION_NOT_SUPPORT + * @tc.expected: step7. return -E_VERSION_NOT_SUPPORT + */ + packet1.SetSchema(TEST_SCHEMA); + packet1.SetAckCode(-E_VERSION_NOT_SUPPORT); + msg1.SetCopiedObject(packet1); + EXPECT_EQ(async.AckRecv(&msg1, context), -E_VERSION_NOT_SUPPORT); + RefObject::KillAndDecObjRef(context); +} diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_anti_dos_sync_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_anti_dos_sync_test.cpp new file mode 100644 index 00000000..13e1b283 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_anti_dos_sync_test.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_data_generate_unit_test.h" +#include "generic_single_ver_kv_entry.h" +#include "message.h" +#include "meta_data.h" +#include "ref_object.h" +#include "single_ver_data_sync.h" +#include "single_ver_sync_engine.h" +#include "version.h" +#include "virtual_communicator_aggregator.h" +#include "virtual_single_ver_sync_db_Interface.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + const string ANTI_DOS_STORE_ID = "anti_dos_sync_test"; +#ifndef RELATIONAL_STORE + const int NUM = 108; +#else + const int NUM = 120; +#endif + const int WAIT_LONG_TIME = 26000; + const int WAIT_SHORT_TIME = 18000; + const int TEST_ONE = 2; + const int TEST_TWO = 10; + const int TEST_THREE_THREAD = 20; + const int TEST_THREE_OUTDATA = 2048; + const int TEST_THREE_DATATIEM = 1024; + const int LIMIT_QUEUE_CACHE_SIZE = 1024 * 1024; + const int DEFAULT_CACHE_SIZE = 160 * 1024 * 1024; // Initial the default cache size of queue as 160MB + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate* g_kvDelegatePtr = nullptr; + VirtualCommunicatorAggregator* g_communicatorAggregator = nullptr; + std::shared_ptr g_metaData = nullptr; + SingleVerSyncEngine *g_syncEngine = nullptr; + VirtualCommunicator *g_communicator = nullptr; + VirtualSingleVerSyncDBInterface *g_syncInterface = nullptr; + + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); +} + +class DistributeddbAntiDosSyncTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributeddbAntiDosSyncTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Init datadir and VirtualCommunicatorAggregator. + */ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + + g_communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator(); + ASSERT_TRUE(g_communicatorAggregator != nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(g_communicatorAggregator); +} + +void DistributeddbAntiDosSyncTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: Release VirtualCommunicatorAggregator and clear data dir. + */ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); +} + +void DistributeddbAntiDosSyncTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: create VirtualCommunicator, VirtualSingleVerSyncDBInterface, SyncEngine, + * and set maximum cache of queue. + */ + const std::string remoteDeviceId = "real_device"; + KvStoreNbDelegate::Option option = {true}; + g_mgr.GetKvStore(ANTI_DOS_STORE_ID, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + g_syncInterface = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(g_syncInterface != nullptr); + g_metaData = std::make_shared(); + int errCodeMetaData = g_metaData->Initialize(g_syncInterface); + ASSERT_TRUE(errCodeMetaData == E_OK); + g_syncEngine = new (std::nothrow) SingleVerSyncEngine(); + ASSERT_TRUE(g_syncEngine != nullptr); + int errCodeSyncEngine = g_syncEngine->Initialize(g_syncInterface, g_metaData, nullptr, nullptr, nullptr); + ASSERT_TRUE(errCodeSyncEngine == E_OK); + g_communicator = static_cast(g_communicatorAggregator->GetCommunicator(remoteDeviceId)); + ASSERT_TRUE(g_communicator != nullptr); + g_syncEngine->SetMaxQueueCacheSize(LIMIT_QUEUE_CACHE_SIZE); +} + +void DistributeddbAntiDosSyncTest::TearDown(void) +{ + /** + * @tc.teardown: Release VirtualCommunicator, VirtualSingleVerSyncDBInterface and SyncEngine. + */ + if (g_communicator != nullptr) { + g_communicator->KillObj(); + g_communicator = nullptr; + } + if (g_syncEngine != nullptr) { + g_syncEngine->SetMaxQueueCacheSize(DEFAULT_CACHE_SIZE); + auto syncEngine = g_syncEngine; + g_syncEngine->OnKill([syncEngine]() { syncEngine->Close(); }); + RefObject::KillAndDecObjRef(g_syncEngine); + g_syncEngine = nullptr; + } + g_metaData = nullptr; + if (g_syncInterface != nullptr) { + delete g_syncInterface; + g_syncInterface = nullptr; + } + if (g_kvDelegatePtr != nullptr) { + g_mgr.CloseKvStore(g_kvDelegatePtr); + g_kvDelegatePtr = nullptr; + } + g_mgr.DeleteKvStore(ANTI_DOS_STORE_ID); +} + +/** + * @tc.name: Anti Dos attack Sync 001 + * @tc.desc: Whether function run normally when the amount of message is lower than the maximum of threads + * and the whole length of message is lower than the maximum size of queue. + * @tc.type: FUNC + * @tc.require: AR000D08KU + * @tc.author: yiguang + */ +HWTEST_F(DistributeddbAntiDosSyncTest, AntiDosAttackSync001, TestSize.Level3) +{ + /** + * @tc.steps: step1. control MessageReceiveCallback to send messages, whose number is lower than + * the maximum of threads and length is lower than the maximum size of queue. + */ + const std::string srcTarget = "001"; + std::vector outData; + + for (unsigned int index = 0; index < g_syncEngine->GetMaxExecNum() - TEST_ONE; index++) { + DataRequestPacket *packet = new (std::nothrow) DataRequestPacket; + ASSERT_TRUE(packet != nullptr); + Message *message = new (std::nothrow) Message(DATA_SYNC_MESSAGE); + ASSERT_TRUE(message != nullptr); + + GenericSingleVerKvEntry *kvEntry = new (std::nothrow) GenericSingleVerKvEntry(); + ASSERT_TRUE(kvEntry != nullptr); + outData.push_back(kvEntry); + packet->SetData(outData); + packet->SetSendCode(E_OK); + packet->SetVersion(SOFTWARE_VERSION_CURRENT); + uint32_t sessionId = index; + uint32_t sequenceId = index; + message->SetMessageType(TYPE_REQUEST); + message->SetTarget(srcTarget); + int errCode = message->SetExternalObject(packet); + ASSERT_TRUE(errCode == E_OK); + message->SetSessionId(sessionId); + message->SetSequenceId(sequenceId); + g_communicator->CallbackOnMessage(srcTarget, message); + /** + * @tc.expected: step1. no message was found to be enqueued and discarded. + */ + EXPECT_TRUE(g_syncEngine->GetQueueCacheSize() == 0); + } + EXPECT_TRUE(g_syncEngine->GetDiscardMsgNum() == 0); +} + +/** + * @tc.name: Anti Dos attack Sync 002 + * @tc.desc: Check if the enqueued and dequeue are normal when the whole length of messages is lower than + * maximum size of queue. + * @tc.type: FUNC + * @tc.require: AR000D08KU + * @tc.author: yiguang + */ +HWTEST_F(DistributeddbAntiDosSyncTest, AntiDosAttackSync002, TestSize.Level3) +{ + /** + * @tc.steps: step1. set block in function DispatchMessage as true. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_SHORT_TIME)); + g_communicatorAggregator->SetBlockValue(true); + + /** + * @tc.steps: step2. control MessageReceiveCallback to send suitable messages. + */ + const std::string srcTarget = "001"; + + for (unsigned int index = 0; index < g_syncEngine->GetMaxExecNum() + TEST_TWO; index++) { + std::vector outData; + DataRequestPacket *packet = new (std::nothrow) DataRequestPacket; + ASSERT_TRUE(packet != nullptr); + Message *message = new (std::nothrow) Message(DATA_SYNC_MESSAGE); + ASSERT_TRUE(message != nullptr); + + GenericSingleVerKvEntry *kvEntry = new (std::nothrow) GenericSingleVerKvEntry(); + ASSERT_TRUE(kvEntry != nullptr); + outData.push_back(kvEntry); + packet->SetData(outData); + packet->SetSendCode(E_OK); + packet->SetVersion(SOFTWARE_VERSION_CURRENT); + + uint32_t sessionId = index; + uint32_t sequenceId = index; + message->SetMessageType(TYPE_REQUEST); + message->SetTarget(srcTarget); + int errCode = message->SetExternalObject(packet); + ASSERT_TRUE(errCode == E_OK); + message->SetSessionId(sessionId); + message->SetSequenceId(sequenceId); + g_communicator->CallbackOnMessage(srcTarget, message); + } + + /** + * @tc.expected: step2. all messages enter the queue. + */ + EXPECT_TRUE(g_syncEngine->GetDiscardMsgNum() == 0); + EXPECT_TRUE(g_syncEngine->GetQueueCacheSize() / NUM == TEST_TWO); + + /** + * @tc.steps: step3. set block in function DispatchMessage as false after a period of time. + */ + g_communicator->Disable(); + g_communicatorAggregator->SetBlockValue(false); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_LONG_TIME)); + + /** + * @tc.expected: step3. the queue is eventually empty and no message is discarded. + */ + EXPECT_TRUE(g_syncEngine->GetDiscardMsgNum() == 0); + EXPECT_TRUE(g_syncEngine->GetQueueCacheSize() == 0); +} + +/** + * @tc.name: Anti Dos attack Sync 003 + * @tc.desc: Whether message enter and drop when all threads hang. + * @tc.type: FUNC + * @tc.require: AR000D08KU + * @tc.author: yiguang + */ +HWTEST_F(DistributeddbAntiDosSyncTest, AntiDosAttackSync003, TestSize.Level3) +{ + /** + * @tc.steps: step1. set block in function DispatchMessage as true. + */ + g_communicatorAggregator->SetBlockValue(true); + + /** + * @tc.steps: step2. control MessageReceiveCallback to send messages that are more than maximum size of queue. + */ + const std::string srcTarget = "001"; + + for (unsigned int index = 0; index < g_syncEngine->GetMaxExecNum() + TEST_THREE_THREAD; index++) { + std::vector outData; + DataRequestPacket *packet = new (std::nothrow) DataRequestPacket; + ASSERT_TRUE(packet != nullptr); + Message *message = new (std::nothrow) Message(DATA_SYNC_MESSAGE); + ASSERT_TRUE(message != nullptr); + for (int outIndex = 0; outIndex < TEST_THREE_OUTDATA; outIndex++) { + GenericSingleVerKvEntry *kvEntry = new (std::nothrow) GenericSingleVerKvEntry(); + ASSERT_TRUE(kvEntry != nullptr); + outData.push_back(kvEntry); + } + packet->SetData(outData); + packet->SetSendCode(E_OK); + packet->SetVersion(SOFTWARE_VERSION_CURRENT); + + uint32_t sessionId = index; + uint32_t sequenceId = index; + message->SetMessageType(TYPE_REQUEST); + message->SetTarget(srcTarget); + int errCode = message->SetExternalObject(packet); + ASSERT_TRUE(errCode == E_OK); + message->SetSessionId(sessionId); + message->SetSequenceId(sequenceId); + g_communicator->CallbackOnMessage(srcTarget, message); + } + + /** + * @tc.expected: step2. after part of messages are enqueued, the rest of the messages are discarded. + */ + EXPECT_TRUE(g_syncEngine->GetDiscardMsgNum() > 0); + EXPECT_TRUE(g_syncEngine->GetQueueCacheSize() > 0); + g_communicatorAggregator->SetBlockValue(false); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_communicator_proxy_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_communicator_proxy_test.cpp new file mode 100644 index 00000000..2706f615 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_communicator_proxy_test.cpp @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "communicator_proxy.h" +#include "db_constant.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "kv_store_nb_delegate.h" +#include "mock_communicator.h" +#include "platform_specific.h" +#include "virtual_communicator_aggregator.h" + +using namespace testing::ext; +using namespace testing; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + const string STORE_ID = "kv_store_sync_test"; + const std::string DEVICE_B = "deviceB"; + const std::string DEVICE_C = "deviceC"; + const std::string DEVICE_D = "deviceD"; + const std::string DEVICE_E = "deviceE"; + + + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + DistributedDBToolsUnitTest g_tool; + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate* g_kvDelegatePtr = nullptr; + + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); +} + +class DistributedDBCommunicatorProxyTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); + +protected: + MockCommunicator extComm_; + MockCommunicator mainComm_; + CommunicatorProxy *commProxy_ = nullptr; +}; + +void DistributedDBCommunicatorProxyTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Init datadir and Virtual Communicator. + */ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + + string dir = g_testDir + "/single_ver"; + DIR* dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } + + auto communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator(); + ASSERT_TRUE(communicatorAggregator != nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(communicatorAggregator); +} + +void DistributedDBCommunicatorProxyTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: Release virtual Communicator and clear data dir. + */ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); +} + +void DistributedDBCommunicatorProxyTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: Get a KvStoreNbDelegate and init the CommunicatorProxy + */ + KvStoreNbDelegate::Option option; + g_mgr.GetKvStore(STORE_ID, option, g_kvDelegateCallback); + std::string identifier = g_mgr.GetKvStoreIdentifier(USER_ID, APP_ID, STORE_ID); + ASSERT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + commProxy_ = new (std::nothrow) CommunicatorProxy(); + ASSERT_TRUE(commProxy_ != nullptr); + commProxy_->SetMainCommunicator(&mainComm_); + commProxy_->SetEqualCommunicator(&extComm_, identifier, { DEVICE_C }); +} + +void DistributedDBCommunicatorProxyTest::TearDown(void) +{ + /** + * @tc.teardown: Release the KvStoreNbDelegate and CommunicatorProxy + */ + if (g_kvDelegatePtr != nullptr) { + ASSERT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + DBStatus status = g_mgr.DeleteKvStore(STORE_ID); + LOGD("delete kv store status %d", status); + ASSERT_TRUE(status == OK); + } + if (commProxy_ != nullptr) { + RefObject::DecObjRef(commProxy_); + } + commProxy_ = nullptr; +} + +/** + * @tc.name: Interface set equal 001 + * @tc.desc: Test set equal identifier from interface. + * @tc.type: FUNC + * @tc.require: AR000F4GVG + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBCommunicatorProxyTest, InterfaceSetEqualId001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Call GetKvStoreIdentifier to make a store identifier. + */ + std::string identifier = g_mgr.GetKvStoreIdentifier("default", APP_ID, STORE_ID); + + /** + * @tc.steps: step2. Call SetEqualIdentifier to set the store identifier B, D, E. + * @tc.expected: step2. SetEqualIdentifier return OK. + */ + DBStatus status = g_kvDelegatePtr->SetEqualIdentifier(identifier, { DEVICE_B, DEVICE_D, DEVICE_E }); + EXPECT_EQ(status, DBStatus::OK); + + /** + * @tc.steps: step2. Call SetEqualIdentifier to set the store identifier B. + * @tc.expected: step2. SetEqualIdentifier return OK and D, E will offline. + */ + status = g_kvDelegatePtr->SetEqualIdentifier(identifier, { DEVICE_B }); + EXPECT_EQ(status, DBStatus::OK); +} + +/** + * @tc.name: Register callback 001 + * @tc.desc: Test register callback from CommunicatorProxy. + * @tc.type: FUNC + * @tc.require: AR000F4GVG + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBCommunicatorProxyTest, RegCallBack001, TestSize.Level1) +{ + OnMessageCallback msgCallback; + OnConnectCallback connCallback; + std::function sendableCallback; + Finalizer finalizer; + + /** + * @tc.steps: step1. Call RegOnMessageCallback from CommProxy. + * @tc.expected: step1. mainComm and extComm's RegOnMessageCallback should be called once. + */ + EXPECT_CALL(extComm_, RegOnMessageCallback(_, _)).Times(1); + EXPECT_CALL(mainComm_, RegOnMessageCallback(_, _)).Times(1); + commProxy_->RegOnMessageCallback(msgCallback, finalizer); + + /** + * @tc.steps: step2. Call RegOnConnectCallback from CommProxy. + * @tc.expected: step2. mainComm and extComm's RegOnConnectCallback should be called once. + */ + EXPECT_CALL(extComm_, RegOnConnectCallback(_, _)).Times(1); + EXPECT_CALL(mainComm_, RegOnConnectCallback(_, _)).Times(1); + commProxy_->RegOnConnectCallback(connCallback, finalizer); + + /** + * @tc.steps: step3. Call RegOnSendableCallback from CommProxy. + * @tc.expected: step3. mainComm and extComm's RegOnSendableCallback should be called once. + */ + EXPECT_CALL(extComm_, RegOnSendableCallback(_, _)).Times(1); + EXPECT_CALL(mainComm_, RegOnSendableCallback(_, _)).Times(1); + commProxy_->RegOnSendableCallback(sendableCallback, finalizer); +} + +/** + * @tc.name: Activate 001 + * @tc.desc: Test Activate called from CommunicatorProxy. + * @tc.type: FUNC + * @tc.require: AR000F4GVG + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBCommunicatorProxyTest, Activate001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Call Activate from CommProxy. + * @tc.expected: step1. mainComm and extComm's Activate should be called once. + */ + EXPECT_CALL(extComm_, Activate()).Times(1); + EXPECT_CALL(mainComm_, Activate()).Times(1); + commProxy_->Activate(); +} + +/** + * @tc.name: Get mtu 001 + * @tc.desc: Test mtu called from CommunicatorProxy. + * @tc.type: FUNC + * @tc.require: AR000F4GVG + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBCommunicatorProxyTest, GetMtu001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Call GetCommunicatorMtuSize from CommProxy with no param. + * @tc.expected: step1. GetCommunicatorMtuSize return DBConstant::MIN_MTU_SIZE. + */ + EXPECT_CALL(mainComm_, GetCommunicatorMtuSize()).WillOnce(Return(DBConstant::MIN_MTU_SIZE)); + EXPECT_EQ(commProxy_->GetCommunicatorMtuSize(), DBConstant::MIN_MTU_SIZE); + + /** + * @tc.steps: step2. Call GetCommunicatorMtuSize from CommProxy with param DEVICE_C. + * @tc.expected: step2. GetCommunicatorMtuSize return DBConstant::MAX_MTU_SIZE. + */ + EXPECT_CALL(extComm_, GetCommunicatorMtuSize(DEVICE_C)).WillOnce(Return(DBConstant::MAX_MTU_SIZE)); + EXPECT_EQ(commProxy_->GetCommunicatorMtuSize(DEVICE_C), DBConstant::MAX_MTU_SIZE); +} + +/** + * @tc.name: Get local identify 001 + * @tc.desc: Test Get local identify from CommunicatorProxy. + * @tc.type: FUNC + * @tc.require: AR000F4GVG + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBCommunicatorProxyTest, GetLocalIdentity001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Call GetLocalIdentity from CommProxy, and set mainComm return DEVICE_B. + * @tc.expected: step1. GetCommunicatorMtuSize return DEVICE_B and function call return E_OK. + */ + EXPECT_CALL(mainComm_, GetLocalIdentity(_)).WillOnce(DoAll(SetArgReferee<0>(DEVICE_B), Return(E_OK))); + std::string localId; + EXPECT_EQ(commProxy_->GetLocalIdentity(localId), E_OK); + EXPECT_EQ(localId, DEVICE_B); +} + +/** + * @tc.name: Get remote version 001 + * @tc.desc: Test Get remote version from CommunicatorProxy. + * @tc.type: FUNC + * @tc.require: AR000F4GVG + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBCommunicatorProxyTest, GetRemoteVersion001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Set mainComm called GetRemoteCommunicatorVersion will return SOFTWARE_VERSION_BASE. + */ + EXPECT_CALL(mainComm_, GetRemoteCommunicatorVersion(DEVICE_B, _)) + .WillOnce(DoAll(SetArgReferee<1>(SOFTWARE_VERSION_BASE), Return(E_OK))); + + /** + * @tc.steps: step2. Call GetRemoteCommunicatorVersion from CommProxy with param DEVICE_B. + * @tc.expected: step2. GetRemoteCommunicatorVersion return SOFTWARE_VERSION_BASE and function call return E_OK. + */ + uint16_t version = 0; + EXPECT_EQ(commProxy_->GetRemoteCommunicatorVersion(DEVICE_B, version), E_OK); + EXPECT_EQ(version, SOFTWARE_VERSION_BASE); + + /** + * @tc.steps: step3. Set extComm called GetRemoteCommunicatorVersion will return SOFTWARE_VERSION_CURRENT. + */ + EXPECT_CALL(extComm_, GetRemoteCommunicatorVersion(DEVICE_C, _)) + .WillOnce(DoAll(SetArgReferee<1>(SOFTWARE_VERSION_CURRENT), Return(E_OK))); + + /** + * @tc.steps: step4. Call GetRemoteCommunicatorVersion from CommProxy with param DEVICE_C. + * @tc.expected: step4. GetRemoteCommunicatorVersion return SOFTWARE_VERSION_CURRENT and function call return E_OK. + */ + EXPECT_EQ(commProxy_->GetRemoteCommunicatorVersion(DEVICE_C, version), E_OK); + EXPECT_EQ(version, SOFTWARE_VERSION_CURRENT); +} + +/** + * @tc.name: Send message 001 + * @tc.desc: Test Send message from CommunicatorProxy. + * @tc.type: FUNC + * @tc.require: AR000F4GVG + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBCommunicatorProxyTest, SendMessage001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Call SendMessage from CommProxy with param DEVICE_B. + * @tc.expected: step1. MainComm's SendMessage willed called and return E_OK. + */ + SendConfig conf = {true, false, 0}; + EXPECT_CALL(mainComm_, SendMessage(DEVICE_B, _, _, _)).WillOnce(Return(E_OK)); + EXPECT_EQ(commProxy_->SendMessage(DEVICE_B, nullptr, conf, nullptr), E_OK); + + /** + * @tc.steps: step1. Call SendMessage from CommProxy with param DEVICE_C. + * @tc.expected: step1. ExtComm's SendMessage willed called and return E_OK. + */ + EXPECT_CALL(extComm_, SendMessage(DEVICE_C, _, _, _)).WillOnce(Return(E_OK)); + EXPECT_EQ(commProxy_->SendMessage(DEVICE_C, nullptr, conf, nullptr), E_OK); +} + +/** + * @tc.name: Get timeout time 001 + * @tc.desc: Test get timeout called from CommunicatorProxy. + * @tc.type: FUNC + * @tc.require: AR000F4GVG + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBCommunicatorProxyTest, GetTimeout001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Call GetTimeout from CommProxy with no param. + * @tc.expected: step1. GetTimeout return DBConstant::MIN_TIMEOUT. + */ + EXPECT_CALL(mainComm_, GetTimeout()).WillOnce(Return(DBConstant::MIN_TIMEOUT)); + EXPECT_EQ(commProxy_->GetTimeout(), DBConstant::MIN_TIMEOUT); + + /** + * @tc.steps: step2. Call GetTimeout from CommProxy with param DEVICE_C. + * @tc.expected: step2. GetTimeout return DBConstant::MAX_MTU_SIZE. + */ + EXPECT_CALL(extComm_, GetTimeout(DEVICE_C)).WillOnce(Return(DBConstant::MAX_TIMEOUT)); + EXPECT_EQ(commProxy_->GetTimeout(DEVICE_C), DBConstant::MAX_TIMEOUT); +} diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_mock_sync_module_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_mock_sync_module_test.cpp new file mode 100644 index 00000000..9f971581 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_mock_sync_module_test.cpp @@ -0,0 +1,601 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "distributeddb_tools_unit_test.h" +#include "message.h" +#include "mock_auto_launch.h" +#include "mock_communicator.h" +#include "mock_meta_data.h" +#include "mock_single_ver_data_sync.h" +#include "mock_single_ver_state_machine.h" +#include "mock_sync_task_context.h" +#include "single_ver_kv_syncer.h" +#include "single_ver_relational_sync_task_context.h" +#include "virtual_communicator_aggregator.h" +#include "virtual_single_ver_sync_db_Interface.h" +#ifdef DATA_SYNC_CHECK_003 +#include "virtual_relational_ver_sync_db_interface.h" +#endif + +using namespace testing::ext; +using namespace testing; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { +void Init(MockSingleVerStateMachine &stateMachine, MockSyncTaskContext &syncTaskContext, + MockCommunicator &communicator, VirtualSingleVerSyncDBInterface &dbSyncInterface) +{ + std::shared_ptr metadata = std::make_shared(); + (void)syncTaskContext.Initialize("device", &dbSyncInterface, metadata, &communicator); + (void)stateMachine.Initialize(&syncTaskContext, &dbSyncInterface, metadata, &communicator); +} +} + +class DistributedDBMockSyncModuleTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBMockSyncModuleTest::SetUpTestCase(void) +{ +} + +void DistributedDBMockSyncModuleTest::TearDownTestCase(void) +{ +} + +void DistributedDBMockSyncModuleTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); +} + +void DistributedDBMockSyncModuleTest::TearDown(void) +{ +} + +/** + * @tc.name: StateMachineCheck001 + * @tc.desc: Test machine do timeout when has same timerId. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, StateMachineCheck001, TestSize.Level1) +{ + MockSingleVerStateMachine stateMachine; + MockSyncTaskContext syncTaskContext; + MockCommunicator communicator; + VirtualSingleVerSyncDBInterface dbSyncInterface; + Init(stateMachine, syncTaskContext, communicator, dbSyncInterface); + + TimerId expectId = 0; + TimerId actualId = expectId; + EXPECT_CALL(syncTaskContext, GetTimerId()).WillOnce(Return(expectId)); + EXPECT_CALL(stateMachine, SwitchStateAndStep(_)).WillOnce(Return()); + + stateMachine.CallStepToTimeout(actualId); +} + +/** + * @tc.name: StateMachineCheck002 + * @tc.desc: Test machine do timeout when has diff timerId. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, StateMachineCheck002, TestSize.Level1) +{ + MockSingleVerStateMachine stateMachine; + MockSyncTaskContext syncTaskContext; + MockCommunicator communicator; + VirtualSingleVerSyncDBInterface dbSyncInterface; + Init(stateMachine, syncTaskContext, communicator, dbSyncInterface); + + TimerId expectId = 0; + TimerId actualId = 1; + EXPECT_CALL(syncTaskContext, GetTimerId()).WillOnce(Return(expectId)); + EXPECT_CALL(stateMachine, SwitchStateAndStep(_)).Times(0); + + stateMachine.CallStepToTimeout(actualId); +} + +/** + * @tc.name: StateMachineCheck003 + * @tc.desc: Test machine exec next task when queue not empty. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, StateMachineCheck003, TestSize.Level1) +{ + MockSingleVerStateMachine stateMachine; + MockSyncTaskContext syncTaskContext; + MockCommunicator communicator; + VirtualSingleVerSyncDBInterface dbSyncInterface; + Init(stateMachine, syncTaskContext, communicator, dbSyncInterface); + + EXPECT_CALL(stateMachine, PrepareNextSyncTask()).WillOnce(Return(E_OK)); + + EXPECT_CALL(syncTaskContext, IsTargetQueueEmpty()).WillRepeatedly(Return(false)); + EXPECT_CALL(syncTaskContext, MoveToNextTarget()).WillRepeatedly(Return()); + EXPECT_CALL(syncTaskContext, IsCurrentSyncTaskCanBeSkipped()) + .WillOnce(Return(true)) + .WillOnce(Return(false)); + // we expect machine don't change context status when queue not empty + EXPECT_CALL(syncTaskContext, SetOperationStatus(_)).WillOnce(Return()); + EXPECT_CALL(syncTaskContext, SetTaskExecStatus(_)).Times(0); + + EXPECT_EQ(stateMachine.CallExecNextTask(), E_OK); +} + +/** + * @tc.name: StateMachineCheck004 + * @tc.desc: Test machine deal time sync ack failed. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, StateMachineCheck004, TestSize.Level1) +{ + MockSingleVerStateMachine stateMachine; + MockSyncTaskContext syncTaskContext; + MockCommunicator communicator; + VirtualSingleVerSyncDBInterface dbSyncInterface; + Init(stateMachine, syncTaskContext, communicator, dbSyncInterface); + + DistributedDB::Message *message = new(std::nothrow) DistributedDB::Message(); + ASSERT_NE(message, nullptr); + message->SetMessageType(TYPE_RESPONSE); + message->SetSessionId(1u); + EXPECT_CALL(syncTaskContext, GetRequestSessionId()).WillRepeatedly(Return(1u)); + EXPECT_EQ(stateMachine.CallTimeMarkSyncRecv(message), -E_INVALID_ARGS); + EXPECT_EQ(syncTaskContext.GetTaskErrCode(), -E_INVALID_ARGS); + delete message; +} + +/** + * @tc.name: StateMachineCheck005 + * @tc.desc: Test machine recv errCode. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, StateMachineCheck005, TestSize.Level1) +{ + MockSingleVerStateMachine stateMachine; + MockSyncTaskContext syncTaskContext; + MockCommunicator communicator; + VirtualSingleVerSyncDBInterface dbSyncInterface; + Init(stateMachine, syncTaskContext, communicator, dbSyncInterface); + EXPECT_CALL(stateMachine, SwitchStateAndStep(_)).WillRepeatedly(Return()); + EXPECT_CALL(syncTaskContext, GetRequestSessionId()).WillRepeatedly(Return(0u)); + + std::initializer_list testCode = {-E_DISTRIBUTED_SCHEMA_CHANGED, -E_DISTRIBUTED_SCHEMA_NOT_FOUND}; + for (int errCode : testCode) { + stateMachine.DataRecvErrCodeHandle(0, errCode); + EXPECT_EQ(syncTaskContext.GetTaskErrCode(), errCode); + stateMachine.CallDataAckRecvErrCodeHandle(errCode, true); + EXPECT_EQ(syncTaskContext.GetTaskErrCode(), errCode); + } +} + +/** + * @tc.name: StateMachineCheck006 + * @tc.desc: Test machine exec next task when queue not empty to empty. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, StateMachineCheck006, TestSize.Level1) +{ + MockSingleVerStateMachine stateMachine; + MockSyncTaskContext syncTaskContext; + MockCommunicator communicator; + VirtualSingleVerSyncDBInterface dbSyncInterface; + Init(stateMachine, syncTaskContext, communicator, dbSyncInterface); + + syncTaskContext.CallSetSyncMode(QUERY_PUSH); + EXPECT_CALL(syncTaskContext, IsTargetQueueEmpty()) + .WillOnce(Return(false)) + .WillOnce(Return(true)); + EXPECT_CALL(syncTaskContext, IsCurrentSyncTaskCanBeSkipped()) + .WillRepeatedly(Return(syncTaskContext.CallIsCurrentSyncTaskCanBeSkipped())); + EXPECT_CALL(syncTaskContext, MoveToNextTarget()).WillOnce(Return()); + // we expect machine don't change context status when queue not empty + EXPECT_CALL(syncTaskContext, SetOperationStatus(_)).WillOnce(Return()); + EXPECT_CALL(syncTaskContext, SetTaskExecStatus(_)).WillOnce(Return()); + EXPECT_CALL(syncTaskContext, Clear()).WillOnce(Return()); + + EXPECT_EQ(stateMachine.CallExecNextTask(), -E_NO_SYNC_TASK); +} + +/** + * @tc.name: StateMachineCheck007 + * @tc.desc: Test machine DoSaveDataNotify in another thread. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, StateMachineCheck007, TestSize.Level3) +{ + MockSingleVerStateMachine stateMachine; + uint8_t callCount = 0; + EXPECT_CALL(stateMachine, DoSaveDataNotify(_, _, _)) + .WillRepeatedly([&callCount](uint32_t sessionId, uint32_t sequenceId, uint32_t inMsgId) { + (void) sessionId; + (void) sequenceId; + (void) inMsgId; + callCount++; + std::this_thread::sleep_for(std::chrono::seconds(4)); // sleep 4s + }); + stateMachine.CallStartSaveDataNotify(0, 0, 0); + std::this_thread::sleep_for(std::chrono::seconds(5)); // sleep 5s + stateMachine.CallStopSaveDataNotify(); + // timer is called once in 2s, we sleep 5s timer call twice + EXPECT_EQ(callCount, 2); + std::this_thread::sleep_for(std::chrono::seconds(10)); // sleep 10s to wait all thread exit +} + +/** + * @tc.name: DataSyncCheck001 + * @tc.desc: Test dataSync recv error ack. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, DataSyncCheck001, TestSize.Level1) +{ + SingleVerDataSync dataSync; + DistributedDB::Message *message = new(std::nothrow) DistributedDB::Message(); + ASSERT_TRUE(message != nullptr); + message->SetErrorNo(E_FEEDBACK_COMMUNICATOR_NOT_FOUND); + EXPECT_EQ(dataSync.AckPacketIdCheck(message), true); + delete message; +} + +/** + * @tc.name: DataSyncCheck002 + * @tc.desc: Test dataSync recv notify ack. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, DataSyncCheck002, TestSize.Level1) +{ + SingleVerDataSync dataSync; + DistributedDB::Message *message = new(std::nothrow) DistributedDB::Message(); + ASSERT_TRUE(message != nullptr); + message->SetMessageType(TYPE_NOTIFY); + EXPECT_EQ(dataSync.AckPacketIdCheck(message), true); + delete message; +} +#ifdef DATA_SYNC_CHECK_003 +/** + * @tc.name: DataSyncCheck003 + * @tc.desc: Test dataSync recv notify ack. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, DataSyncCheck003, TestSize.Level1) +{ + MockSingleVerDataSync mockDataSync; + MockSyncTaskContext mockSyncTaskContext; + auto mockMetadata = std::make_shared(); + SyncTimeRange dataTimeRange = {1, 0, 1, 0}; + mockDataSync.CallUpdateSendInfo(dataTimeRange, &mockSyncTaskContext); + + VirtualRelationalVerSyncDBInterface storage; + MockCommunicator communicator; + std::shared_ptr metadata = std::static_pointer_cast(mockMetadata); + mockDataSync.Initialize(&storage, &communicator, metadata, "deviceId"); + + DistributedDB::Message *message = new(std::nothrow) DistributedDB::Message(); + ASSERT_TRUE(message != nullptr); + DataAckPacket packet; + message->SetSequenceId(1); + message->SetCopiedObject(packet); + mockSyncTaskContext.SetQuerySync(true); + + EXPECT_CALL(*mockMetadata, GetLastQueryTime(_, _, _)).WillOnce(Return(E_OK)); + EXPECT_CALL(*mockMetadata, SetLastQueryTime(_, _, _)).WillOnce([&dataTimeRange](const std::string &queryIdentify, + const std::string &deviceId, const Timestamp ×tamp) { + EXPECT_EQ(timestamp, dataTimeRange.endTime); + return E_OK; + }); + EXPECT_CALL(mockSyncTaskContext, SetOperationStatus(_)).WillOnce(Return()); + EXPECT_EQ(mockDataSync.TryContinueSync(&mockSyncTaskContext, message), -E_FINISHED); + delete message; +} +#endif +/** + * @tc.name: AutoLaunchCheck001 + * @tc.desc: Test autoLaunch close connection. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, AutoLaunchCheck001, TestSize.Level1) +{ + MockAutoLaunch mockAutoLaunch; + /** + * @tc.steps: step1. put AutoLaunchItem in cache to simulate a connection was auto launched + */ + std::string id = "TestAutoLaunch"; + std::string userId = "userId"; + AutoLaunchItem item; + mockAutoLaunch.SetAutoLaunchItem(id, userId, item); + EXPECT_CALL(mockAutoLaunch, TryCloseConnection(_)).WillOnce(Return()); + /** + * @tc.steps: step2. send close signal to simulate a connection was unused in 1 min + * @tc.expected: 10 thread try to close the connection and one thread close success + */ + const int loopCount = 10; + int finishCount = 0; + std::mutex mutex; + std::unique_lock lock(mutex); + std::condition_variable cv; + for (int i = 0; i < loopCount; i++) { + std::thread t = std::thread([&finishCount, &mockAutoLaunch, &id, &userId, &mutex, &cv] { + mockAutoLaunch.CallExtConnectionLifeCycleCallbackTask(id, userId); + finishCount++; + if (finishCount == loopCount) { + std::unique_lock lockInner(mutex); + cv.notify_one(); + } + }); + t.detach(); + } + cv.wait(lock, [&finishCount, &loopCount]() { + return finishCount == loopCount; + }); +} + +/** + * @tc.name: SyncDataSync001 + * @tc.desc: Test request start when RemoveDeviceDataIfNeed failed. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, SyncDataSync001, TestSize.Level1) +{ + MockSyncTaskContext syncTaskContext; + MockSingleVerDataSync dataSync; + + EXPECT_CALL(dataSync, RemoveDeviceDataIfNeed(_)).WillRepeatedly(Return(-E_BUSY)); + EXPECT_EQ(dataSync.CallRequestStart(&syncTaskContext, PUSH), -E_BUSY); + EXPECT_EQ(syncTaskContext.GetTaskErrCode(), -E_BUSY); +} + +/** + * @tc.name: SyncDataSync002 + * @tc.desc: Test pull request start when RemoveDeviceDataIfNeed failed. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, SyncDataSync002, TestSize.Level1) +{ + MockSyncTaskContext syncTaskContext; + MockSingleVerDataSync dataSync; + + EXPECT_CALL(dataSync, RemoveDeviceDataIfNeed(_)).WillRepeatedly(Return(-E_BUSY)); + EXPECT_EQ(dataSync.CallPullRequestStart(&syncTaskContext), -E_BUSY); + EXPECT_EQ(syncTaskContext.GetTaskErrCode(), -E_BUSY); +} + +/** + * @tc.name: SyncDataSync003 + * @tc.desc: Test call RemoveDeviceDataIfNeed in diff thread. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, SyncDataSync003, TestSize.Level1) +{ + MockSyncTaskContext syncTaskContext; + MockSingleVerDataSync dataSync; + + VirtualSingleVerSyncDBInterface storage; + MockCommunicator communicator; + std::shared_ptr mockMetadata = std::make_shared(); + std::shared_ptr metadata = std::static_pointer_cast(mockMetadata); + metadata->Initialize(&storage); + const std::string deviceId = "deviceId"; + dataSync.Initialize(&storage, &communicator, metadata, deviceId); + syncTaskContext.SetRemoteSoftwareVersion(SOFTWARE_VERSION_CURRENT); + syncTaskContext.Initialize(deviceId, &storage, metadata, &communicator); + syncTaskContext.EnableClearRemoteStaleData(true); + + /** + * @tc.steps: step1. set diff db createtime for rebuild label in meta + */ + metadata->SetDbCreateTime(deviceId, 1, true); // 1 is old db createTime + metadata->SetDbCreateTime(deviceId, 2, true); // 1 is new db createTime + + DistributedDB::Key k1 = {'k', '1'}; + DistributedDB::Value v1 = {'v', '1'}; + DistributedDB::Key k2 = {'k', '2'}; + DistributedDB::Value v2 = {'v', '2'}; + + /** + * @tc.steps: step2. call RemoveDeviceDataIfNeed in diff thread and then put data + */ + std::thread thread1([&]() { + (void)dataSync.CallRemoveDeviceDataIfNeed(&syncTaskContext); + storage.PutDeviceData(deviceId, k1, v1); + LOGD("PUT FINISH"); + }); + std::thread thread2([&]() { + (void)dataSync.CallRemoveDeviceDataIfNeed(&syncTaskContext); + storage.PutDeviceData(deviceId, k2, v2); + LOGD("PUT FINISH"); + }); + thread1.join(); + thread2.join(); + + DistributedDB::Value actualValue; + storage.GetDeviceData(deviceId, k1, actualValue); + EXPECT_EQ(v1, actualValue); + storage.GetDeviceData(deviceId, k2, actualValue); + EXPECT_EQ(v2, actualValue); +} + +/** + * @tc.name: AbilitySync001 + * @tc.desc: Test abilitySync abort when recv error. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, AbilitySync001, TestSize.Level1) +{ + MockSyncTaskContext syncTaskContext; + AbilitySync abilitySync; + + DistributedDB::Message *message = new(std::nothrow) DistributedDB::Message(); + ASSERT_TRUE(message != nullptr); + AbilitySyncAckPacket packet; + packet.SetAckCode(-E_BUSY); + message->SetCopiedObject(packet); + EXPECT_EQ(abilitySync.AckRecv(message, &syncTaskContext), -E_BUSY); + delete message; + EXPECT_EQ(syncTaskContext.GetTaskErrCode(), -E_BUSY); +} + +/** + * @tc.name: AbilitySync002 + * @tc.desc: Test abilitySync abort when save meta failed. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, AbilitySync002, TestSize.Level1) +{ + MockSyncTaskContext syncTaskContext; + AbilitySync abilitySync; + MockCommunicator comunicator; + VirtualSingleVerSyncDBInterface syncDBInterface; + std::shared_ptr metaData = std::make_shared(); + metaData->Initialize(&syncDBInterface); + abilitySync.Initialize(&comunicator, &syncDBInterface, metaData, "deviceId"); + + /** + * @tc.steps: step1. set AbilitySyncAckPacket ackCode is E_OK for pass the ack check + */ + DistributedDB::Message *message = new(std::nothrow) DistributedDB::Message(); + ASSERT_TRUE(message != nullptr); + AbilitySyncAckPacket packet; + packet.SetAckCode(E_OK); + packet.SetSoftwareVersion(SOFTWARE_VERSION_CURRENT); + message->SetCopiedObject(packet); + /** + * @tc.steps: step2. set syncDBInterface busy for save data return -E_BUSY + */ + syncDBInterface.SetBusy(true); + SyncStrategy mockStrategy = {true, false, false}; + EXPECT_CALL(syncTaskContext, GetSyncStrategy(_)).WillOnce(Return(mockStrategy)); + EXPECT_EQ(abilitySync.AckRecv(message, &syncTaskContext), -E_BUSY); + delete message; + EXPECT_EQ(syncTaskContext.GetTaskErrCode(), -E_BUSY); +} + +/** + * @tc.name: AbilitySync002 + * @tc.desc: Test abilitySync when offline. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, AbilitySync003, TestSize.Level1) +{ + /** + * @tc.steps: step1. set table TEST is permitSync + */ + SingleVerRelationalSyncTaskContext *context = new (std::nothrow) SingleVerRelationalSyncTaskContext(); + ASSERT_NE(context, nullptr); + RelationalSyncStrategy strategy; + const std::string tableName = "TEST"; + strategy[tableName] = {true, true, true}; + context->SetRelationalSyncStrategy(strategy); + QuerySyncObject query; + query.SetTableName(tableName); + /** + * @tc.steps: step2. set table is need reset ability sync but it still permit sync + */ + context->SetIsNeedResetAbilitySync(true); + EXPECT_EQ(context->GetSyncStrategy(query).permitSync, true); + /** + * @tc.steps: step3. set table is schema change now it don't permit sync + */ + context->SchemaChange(); + EXPECT_EQ(context->GetSyncStrategy(query).permitSync, false); + RefObject::KillAndDecObjRef(context); +} + +/** + * @tc.name: SyncLifeTest001 + * @tc.desc: Test syncer alive when thread still exist. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, SyncLifeTest001, TestSize.Level3) +{ + std::shared_ptr syncer = std::make_shared(); + VirtualCommunicatorAggregator *virtualCommunicatorAggregator = new VirtualCommunicatorAggregator(); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(virtualCommunicatorAggregator); + VirtualSingleVerSyncDBInterface *syncDBInterface = new VirtualSingleVerSyncDBInterface(); + syncer->Initialize(syncDBInterface, true); + syncer->EnableAutoSync(true); + for (int i = 0; i < 1000; i++) { // trigger 1000 times auto sync check + syncer->LocalDataChanged(SQLITE_GENERAL_NS_PUT_EVENT); + } + syncer = nullptr; + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); + delete syncDBInterface; +} + +/** + * @tc.name: MessageScheduleTest001 + * @tc.desc: Test MessageSchedule stop timer when no message. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBMockSyncModuleTest, MessageScheduleTest001, TestSize.Level1) +{ + MockSyncTaskContext *context = new MockSyncTaskContext(); + context->SetRemoteSoftwareVersion(SOFTWARE_VERSION_CURRENT); + bool last = false; + context->OnLastRef([&last]() { + last = true; + }); + SingleVerDataMessageSchedule schedule; + bool isNeedHandle = false; + bool isNeedContinue = false; + schedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + RefObject::KillAndDecObjRef(context); + std::this_thread::sleep_for(std::chrono::seconds(1)); + EXPECT_TRUE(last); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_multi_ver_p2p_sync_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_multi_ver_p2p_sync_test.cpp new file mode 100644 index 00000000..70d3ffbc --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_multi_ver_p2p_sync_test.cpp @@ -0,0 +1,1649 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "commit_history_sync.h" +#include "db_common.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "ikvdb_connection.h" +#include "kv_store_delegate.h" +#include "kv_virtual_device.h" +#include "kvdb_manager.h" +#include "kvdb_pragma.h" +#include "log_print.h" +#include "meta_data.h" +#include "multi_ver_data_sync.h" +#include "platform_specific.h" +#include "sync_types.h" +#include "time_sync.h" +#include "virtual_multi_ver_sync_db_interface.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +#ifndef LOW_LEVEL_MEM_DEV +namespace { + string g_testDir; + const string STORE_ID = "kv_store_sync_test"; + const string STORE_ID_A = "kv_store_sync_test_a"; + const string STORE_ID_B = "kv_store_sync_test_b"; + const int WAIT_TIME_1 = 1000; + const int WAIT_TIME_2 = 2000; + const int WAIT_LONG_TIME = 10000; + const int WAIT_LIMIT_TIME = 30000; + const std::string DEVICE_B = "deviceB"; + const std::string DEVICE_C = "deviceC"; + const int LIMIT_KEY_SIZE = 1024; + constexpr int BIG_VALUE_SIZE = 1024 + 1; // > 1K + constexpr int LIMIT_VALUE_SIZE = 4 * 1024 * 1024; // 4M + KvStoreDelegateManager g_mgr("sync_test", "sync_test"); + KvStoreConfig g_config; + KvStoreDelegate::Option g_option; + + // define the g_kvDelegateCallback, used to get some information when open a kv store. + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreDelegate *g_kvDelegatePtr = nullptr; + MultiVerNaturalStoreConnection *g_connectionA; + MultiVerNaturalStoreConnection *g_connectionB; + VirtualCommunicatorAggregator* g_communicatorAggregator = nullptr; + KvVirtualDevice *g_deviceB = nullptr; + KvVirtualDevice *g_deviceC = nullptr; + + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + + MultiVerNaturalStoreConnection *GetConnection(const std::string &dir, const std::string &storeId, int errCode) + { + KvDBProperties prop; + prop.SetStringProp(KvDBProperties::USER_ID, "sync_test"); + prop.SetStringProp(KvDBProperties::APP_ID, "sync_test"); + prop.SetStringProp(KvDBProperties::STORE_ID, storeId); + std::string identifier = DBCommon::TransferHashString("sync_test-sync_test-" + storeId); + + prop.SetStringProp(KvDBProperties::IDENTIFIER_DATA, identifier); + std::string identifierDir = DBCommon::TransferStringToHex(identifier); + prop.SetStringProp(KvDBProperties::IDENTIFIER_DIR, identifierDir); + prop.SetStringProp(KvDBProperties::DATA_DIR, dir); + prop.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::MULTI_VER_TYPE); + prop.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + errCode = E_OK; + auto conn = KvDBManager::GetDatabaseConnection(prop, errCode); + if (errCode != E_OK) { + LOGE("[DistributeddbMultiVerP2PSyncTes] db create failed path, err %d", errCode); + return nullptr; + } + return static_cast(conn); + } + + int GetDataFromConnection(IKvDBConnection *conn, const Key &key, Value &value) + { + IKvDBSnapshot *snapshot = nullptr; + int errCode = conn->GetSnapshot(snapshot); + if (errCode != E_OK) { + return errCode; + } + errCode = snapshot->Get(key, value); + conn->ReleaseSnapshot(snapshot); + return errCode; + } +} + +class DistributedDBMultiVerP2PSyncTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBMultiVerP2PSyncTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Init datadir and Virtual Communicator. + */ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + string dir = g_testDir + "/commitstore"; + g_config.dataDir = dir; + DIR* dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } + g_mgr.SetKvStoreConfig(g_config); + g_communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator(); + ASSERT_TRUE(g_communicatorAggregator != nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(g_communicatorAggregator); +} + +void DistributedDBMultiVerP2PSyncTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: Release virtual Communicator and clear data dir. + */ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } + + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); + g_communicatorAggregator = nullptr; +} + +void DistributedDBMultiVerP2PSyncTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: create virtual device B and C + */ + g_communicatorAggregator->Disable(); + g_deviceB = new (std::nothrow) KvVirtualDevice(DEVICE_B); + ASSERT_TRUE(g_deviceB != nullptr); + VirtualMultiVerSyncDBInterface *syncInterfaceB = new (std::nothrow) VirtualMultiVerSyncDBInterface; + ASSERT_TRUE(syncInterfaceB != nullptr); + ASSERT_EQ(syncInterfaceB->Initialize(DEVICE_B), E_OK); + ASSERT_EQ(g_deviceB->Initialize(g_communicatorAggregator, syncInterfaceB), E_OK); + + g_deviceC = new (std::nothrow) KvVirtualDevice(DEVICE_C); + ASSERT_TRUE(g_deviceC != nullptr); + VirtualMultiVerSyncDBInterface *syncInterfaceC = new (std::nothrow) VirtualMultiVerSyncDBInterface; + ASSERT_TRUE(syncInterfaceC != nullptr); + ASSERT_EQ(syncInterfaceC->Initialize(DEVICE_C), E_OK); + ASSERT_EQ(g_deviceC->Initialize(g_communicatorAggregator, syncInterfaceC), E_OK); + g_communicatorAggregator->Enable(); + + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + return true;}; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(permissionCheckCallback), OK); +} + +void DistributedDBMultiVerP2PSyncTest::TearDown(void) +{ + /** + * @tc.teardown: Release device A, B, C, connectionA and connectionB + */ + if (g_kvDelegatePtr != nullptr) { + ASSERT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + DBStatus status = g_mgr.DeleteKvStore(STORE_ID); + LOGD("delete kv store status %d", status); + ASSERT_TRUE(status == OK); + } + if (g_deviceB != nullptr) { + delete g_deviceB; + g_deviceB = nullptr; + } + if (g_deviceC != nullptr) { + delete g_deviceC; + g_deviceC = nullptr; + } + if (g_connectionA != nullptr) { + g_connectionA->Close(); + ASSERT_EQ(g_mgr.DeleteKvStore(STORE_ID_A), OK); + g_connectionA = nullptr; + } + if (g_connectionB != nullptr) { + g_connectionB->Close(); + ASSERT_EQ(g_mgr.DeleteKvStore(STORE_ID_B), OK); + g_connectionB = nullptr; + } + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} + +static DBStatus GetData(KvStoreDelegate *kvStore, const Key &key, Value &value) +{ + KvStoreSnapshotDelegate *snapshotTmp = nullptr; + DBStatus statusTmp; + kvStore->GetKvStoreSnapshot(nullptr, + [&statusTmp, &snapshotTmp](DBStatus status, KvStoreSnapshotDelegate *snapshot) { + statusTmp = status; + snapshotTmp = snapshot; + }); + if (statusTmp != E_OK) { + return statusTmp; + } + snapshotTmp->Get(key, [&statusTmp, &value](DBStatus status, const Value &outValue) { + statusTmp = status; + value = outValue; + }); + if (statusTmp == OK) { + LOGD("[DistributeddbMultiVerP2PSyncTes] GetData key %c, value = %c", key[0], value[0]); + } + kvStore->ReleaseKvStoreSnapshot(snapshotTmp); + return statusTmp; +} + +/** + * @tc.name: Transaction Sync 001 + * @tc.desc: Verify put transaction sync function. + * @tc.type: FUNC + * @tc.require: AR000BVRO4 AR000CQE0K + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, TransactionSync001, TestSize.Level2) +{ + /** + * @tc.steps: step1. open a KvStoreNbDelegate as deviceA + */ + g_mgr.GetKvStore(STORE_ID, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_1)); + + /** + * @tc.steps: step2. deviceB put {k1, v1}, {k2,v2} in a transaction + */ + g_deviceB->StartTransaction(); + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1), E_OK); + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_2, DistributedDBUnitTest::VALUE_2), E_OK); + g_deviceB->Commit(); + + /** + * @tc.steps: step3. deviceB online and wait for sync + */ + g_deviceB->Online(); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + + /** + * @tc.steps: step4. deviceC put {k3, v3}, {k4,v4} in a transaction + */ + g_deviceC->StartTransaction(); + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_3, DistributedDBUnitTest::VALUE_3), E_OK); + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_4, DistributedDBUnitTest::VALUE_4), E_OK); + g_deviceC->Commit(); + + /** + * @tc.steps: step5. deviceC online for sync + */ + g_deviceC->Online(); + + /** + * @tc.steps: step6. deviceC offline + */ + g_deviceC->Offline(); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + + /** + * @tc.expected: step6. deviceA have {k1, v1}, {k2, v2}, not have k3, k4 + */ + Value value; + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_1, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_1); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_2, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_2); + + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_3, value), NOT_FOUND); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_4, value), NOT_FOUND); +} + +/** + * @tc.name: Transaction Sync 002 + * @tc.desc: Verify delete transaction sync function. + * @tc.type: FUNC + * @tc.require: AR000BVRO4 AR000CQE0K + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, TransactionSync002, TestSize.Level2) +{ + /** + * @tc.steps: step1. open a KvStoreNbDelegate as deviceA + */ + g_mgr.GetKvStore(STORE_ID, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_1)); + + /** + * @tc.steps: step2. deviceB put {k1, v1}, {k2,v2} in a transaction + */ + g_deviceB->StartTransaction(); + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1), E_OK); + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_2, DistributedDBUnitTest::VALUE_2), E_OK); + g_deviceB->Commit(); + + /** + * @tc.steps: step3. deviceB online and wait for sync + */ + g_deviceB->Online(); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + + /** + * @tc.steps: step4. deviceC put {k3, v3}, and delete k3 in a transaction + */ + g_deviceC->StartTransaction(); + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_3, DistributedDBUnitTest::VALUE_3), E_OK); + ASSERT_EQ(g_deviceC->DeleteData(DistributedDBUnitTest::KEY_3), E_OK); + g_deviceC->Commit(); + + /** + * @tc.steps: step5. deviceB online for sync + */ + g_deviceC->Online(); + + /** + * @tc.steps: step6. deviceC offline + */ + g_deviceC->Offline(); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + + /** + * @tc.expected: step6. deviceA have {k1, v1}, {k2, v2}, not have k3, k4 + */ + Value value; + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_1, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_1); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_2, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_2); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_3, value), NOT_FOUND); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_3, value), NOT_FOUND); +} + +/** + * @tc.name: Transaction Sync 003 + * @tc.desc: Verify update transaction sync function. + * @tc.type: FUNC + * @tc.require: AR000BVRO4 AR000CQE0K + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, TransactionSync003, TestSize.Level2) +{ + /** + * @tc.steps: step1. open a KvStoreNbDelegate as deviceA + */ + g_mgr.GetKvStore(STORE_ID, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_1)); + + /** + * @tc.steps: step2. deviceB put {k1, v1}, {k2,v2} in a transaction + */ + g_deviceB->StartTransaction(); + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1), E_OK); + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_2, DistributedDBUnitTest::VALUE_2), E_OK); + g_deviceB->Commit(); + + /** + * @tc.steps: step3. deviceB online and wait for sync + */ + g_deviceB->Online(); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + + /** + * @tc.steps: step4. deviceC put {k3, v3}, and update {k3, v4} in a transaction + */ + g_deviceC->StartTransaction(); + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_3, DistributedDBUnitTest::VALUE_3), E_OK); + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_3, DistributedDBUnitTest::VALUE_4), E_OK); + g_deviceC->Commit(); + + /** + * @tc.steps: step5. deviceB online for sync + */ + g_deviceC->Online(); + + /** + * @tc.steps: step6. deviceC offline + */ + g_deviceC->Offline(); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + + /** + * @tc.expected: step6. deviceA have {k1, v1}, {k2, v2}, not have k3, k4 + */ + Value value; + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_1, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_1); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_2, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_2); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_3, value), NOT_FOUND); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_3, value), NOT_FOUND); +} + +/** + * @tc.name: Metadata 001 + * @tc.desc: Verify metadata add and update function + * @tc.type: FUNC + * @tc.require: AR000CQE0P AR000CQE0S + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, Metadata001, TestSize.Level1) +{ + /** + * @tc.steps: step1. Create a metadata and use VirtualMultiVerSyncDBInterface to init + * @tc.expected: step1. metadata init ok + */ + Metadata metadata; + VirtualMultiVerSyncDBInterface *syncInterface = new (std::nothrow) VirtualMultiVerSyncDBInterface; + ASSERT_TRUE(syncInterface != nullptr); + EXPECT_EQ(syncInterface->Initialize("metadata_test"), E_OK); + EXPECT_EQ(metadata.Initialize(syncInterface), E_OK); + + /** + * @tc.steps: step2. call SaveTimeOffset to write t1. + * @tc.expected: step2. SaveTimeOffset return ok + */ + const TimeOffset timeOffsetA = 1024; + EXPECT_EQ(metadata.SaveTimeOffset(DEVICE_B, timeOffsetA), E_OK); + TimeOffset timeOffsetB = 0; + + /** + * @tc.steps: step3. call GetTimeOffset to read t2. + * @tc.expected: step3. t1 == t2 + */ + metadata.GetTimeOffset(DEVICE_B, timeOffsetB); + EXPECT_EQ(timeOffsetA, timeOffsetB); + + /** + * @tc.steps: step4. call SaveTimeOffset to write t3. t3 != t1 + * @tc.expected: step4. SaveTimeOffset return ok + */ + const TimeOffset timeOffsetC = 2048; + EXPECT_EQ(metadata.SaveTimeOffset(DEVICE_B, timeOffsetC), E_OK); + + /** + * @tc.steps: step5. call GetTimeOffset to read t2. + * @tc.expected: step5. t4 == t3 + */ + TimeOffset timeOffsetD = 0; + metadata.GetTimeOffset(DEVICE_B, timeOffsetD); + EXPECT_EQ(timeOffsetC, timeOffsetD); + syncInterface->DeleteDatabase(); + delete syncInterface; + syncInterface = nullptr; +} + +/** + * @tc.name: Isolation Sync 001 + * @tc.desc: Verify add sync isolation between different kvstore. + * @tc.type: FUNC + * @tc.require: AR000BVDGP + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, IsolationSync001, TestSize.Level2) +{ + int errCode = 0; + + /** + * @tc.steps: step1. Get connectionA, connectionB from different kvstore, + * connectionB not in g_communicatorAggregator + */ + g_communicatorAggregator->Disable(); + g_connectionB = GetConnection(g_config.dataDir, STORE_ID_B, errCode); + ASSERT_TRUE(g_connectionB != nullptr); + g_communicatorAggregator->Enable(); + g_connectionA = GetConnection(g_config.dataDir, STORE_ID_A, errCode); + ASSERT_TRUE(g_connectionA != nullptr); + + /** + * @tc.steps: step2. deviceB put {k1, v1} + */ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1), E_OK); + + /** + * @tc.steps: step3. connectionA pull from deviceB + * @tc.expected: step3. Pragma OK, connectionA have {k1, v1} , connectionB don't have k1. + */ + PragmaSync pragmaData(devices, SYNC_MODE_PULL_ONLY, nullptr); + ASSERT_TRUE(g_connectionA->Pragma(PRAGMA_SYNC_DEVICES, &pragmaData) == E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + Value value; + ASSERT_EQ(GetDataFromConnection(g_connectionA, DistributedDBUnitTest::KEY_1, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_1); + EXPECT_EQ(GetDataFromConnection(g_connectionB, DistributedDBUnitTest::KEY_1, value), -E_NOT_FOUND); +} + +/** + * @tc.name: Isolation Sync 002 + * @tc.desc: Verify update sync isolation between different kvstore. + * @tc.type: FUNC + * @tc.require: AR000BVDGP + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, IsolationSync002, TestSize.Level2) +{ + int errCode = 0; + + /** + * @tc.steps: step1. Get connectionA, connectionB from different kvstore, + * connectionB not in g_communicatorAggregator + */ + g_communicatorAggregator->Disable(); + g_connectionB = GetConnection(g_config.dataDir, STORE_ID_B, errCode); + ASSERT_TRUE(g_connectionB != nullptr); + g_communicatorAggregator->Enable(); + g_connectionA = GetConnection(g_config.dataDir, STORE_ID_A, errCode); + ASSERT_TRUE(g_connectionA != nullptr); + + /** + * @tc.steps: step2. deviceB put {k1, v1} and update {k1, v2} + */ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1), E_OK); + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_2), E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + + /** + * @tc.steps: step3. connectionA pull from deviceB + * @tc.expected: step3. Pragma OK, connectionA have {k1, v2} , connectionB don't have k1. + */ + PragmaSync pragmaData(devices, SYNC_MODE_PULL_ONLY, nullptr); + ASSERT_TRUE(g_connectionA->Pragma(PRAGMA_SYNC_DEVICES, &pragmaData) == E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + + Value value; + EXPECT_EQ(GetDataFromConnection(g_connectionA, DistributedDBUnitTest::KEY_1, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_2); + EXPECT_EQ(GetDataFromConnection(g_connectionB, DistributedDBUnitTest::KEY_1, value), -E_NOT_FOUND); +} + +/** + * @tc.name: Isolation Sync 003 + * @tc.desc: Verify delete sync isolation between different kvstore. + * @tc.type: FUNC + * @tc.require: AR000BVDGP + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, IsolationSync003, TestSize.Level2) +{ + int errCode = 0; + + /** + * @tc.steps: step1. Get connectionA, connectionB from different kvstore, + * connectionB not in g_communicatorAggregator, connectionB put {k1,v1} + */ + g_communicatorAggregator->Disable(); + g_connectionB = GetConnection(g_config.dataDir, STORE_ID_B, errCode); + ASSERT_TRUE(g_connectionB != nullptr); + IOption option; + ASSERT_EQ(g_connectionB->Put(option, KEY_1, VALUE_1), E_OK); + g_communicatorAggregator->Enable(); + g_connectionA = GetConnection(g_config.dataDir, STORE_ID_A, errCode); + ASSERT_TRUE(g_connectionA != nullptr); + + /** + * @tc.steps: step2. deviceB put {k1, v1} and delete k1 + */ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1), E_OK); + ASSERT_EQ(g_deviceB->DeleteData(DistributedDBUnitTest::KEY_1), E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + + /** + * @tc.steps: step3. connectionA pull from deviceB + * @tc.expected: step3. Pragma OK, connectionA don't have k1, connectionB have {k1.v1} + */ + LOGD("[DistributeddbMultiVerP2PSyncTes] start sync"); + PragmaSync pragmaData(devices, SYNC_MODE_PULL_ONLY, nullptr); + ASSERT_TRUE(g_connectionA->Pragma(PRAGMA_SYNC_DEVICES, &pragmaData) == E_OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + + Value value; + EXPECT_EQ(GetDataFromConnection(g_connectionA, DistributedDBUnitTest::KEY_1, value), -E_NOT_FOUND); + EXPECT_EQ(GetDataFromConnection(g_connectionB, DistributedDBUnitTest::KEY_1, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_1); +} + +static void SetTimeSyncPacketField(TimeSyncPacket &inPacket, Timestamp sourceBegin, Timestamp sourceEnd, + Timestamp targetBegin, Timestamp targetEnd, SyncId theId) +{ + inPacket.SetSourceTimeBegin(sourceBegin); + inPacket.SetSourceTimeEnd(sourceEnd); + inPacket.SetTargetTimeBegin(targetBegin); + inPacket.SetTargetTimeEnd(targetEnd); +} + +static bool IsTimeSyncPacketEqual(const TimeSyncPacket &inPacketA, const TimeSyncPacket &inPacketB) +{ + bool equal = true; + equal = inPacketA.GetSourceTimeBegin() == inPacketB.GetSourceTimeBegin() ? equal : false; + equal = inPacketA.GetSourceTimeEnd() == inPacketB.GetSourceTimeEnd() ? equal : false; + equal = inPacketA.GetTargetTimeBegin() == inPacketB.GetTargetTimeBegin() ? equal : false; + equal = inPacketA.GetTargetTimeEnd() == inPacketB.GetTargetTimeEnd() ? equal : false; + return equal; +} + +/** + * @tc.name: Timesync Packet 001 + * @tc.desc: Verify TimesyncPacket Serialization and DeSerialization + * @tc.type: FUNC + * @tc.require: AR000BVRNU AR000CQE0J + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, TimesyncPacket001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create TimeSyncPacket packetA aand packetB + */ + TimeSyncPacket packetA; + TimeSyncPacket packetB; + SetTimeSyncPacketField(packetA, 1, 2, 3, 4, 5); // 1, 2, 3, 4, 5 is five field for time sync packet + SetTimeSyncPacketField(packetB, 5, 4, 3, 2, 1); // 1, 2, 3, 4, 5 is five field for time sync packet + Message oriMsgA; + Message oriMsgB; + oriMsgA.SetCopiedObject(packetA); + oriMsgA.SetMessageId(TIME_SYNC_MESSAGE); + oriMsgA.SetMessageType(TYPE_REQUEST); + oriMsgB.SetCopiedObject(packetB); + oriMsgB.SetMessageId(TIME_SYNC_MESSAGE); + oriMsgB.SetMessageType(TYPE_RESPONSE); + + /** + * @tc.steps: step2. Serialization packetA to bufferA + */ + uint32_t lenA = TimeSync::CalculateLen(&oriMsgA); + vector bufferA; + bufferA.resize(lenA); + int ret = TimeSync::Serialization(bufferA.data(), lenA, &oriMsgA); + ASSERT_EQ(ret, E_OK); + + /** + * @tc.steps: step3. Serialization packetB to bufferB + */ + uint32_t lenB = TimeSync::CalculateLen(&oriMsgB); + vector bufferB; + bufferB.resize(lenB); + ret = TimeSync::Serialization(bufferB.data(), lenB, &oriMsgB); + ASSERT_EQ(ret, E_OK); + + /** + * @tc.steps: step4. DeSerialization bufferA to outPktA + * @tc.expected: step4. packetA == outPktA + */ + Message outMsgA; + outMsgA.SetMessageId(TIME_SYNC_MESSAGE); + outMsgA.SetMessageType(TYPE_REQUEST); + ret = TimeSync::DeSerialization(bufferA.data(), lenA, &outMsgA); + ASSERT_EQ(ret, E_OK); + const TimeSyncPacket *outPktA = outMsgA.GetObject(); + ASSERT_NE(outPktA, nullptr); + EXPECT_EQ(IsTimeSyncPacketEqual(packetA, *outPktA), true); + + /** + * @tc.steps: step5. DeSerialization bufferA to outPktA + * @tc.expected: step5. packetB == outPktB outPktB != outPktA + */ + Message outMsgB; + outMsgB.SetMessageId(TIME_SYNC_MESSAGE); + outMsgB.SetMessageType(TYPE_RESPONSE); + ret = TimeSync::DeSerialization(bufferB.data(), lenB, &outMsgB); + ASSERT_EQ(ret, E_OK); + const TimeSyncPacket *outPktB = outMsgB.GetObject(); + ASSERT_NE(outPktB, nullptr); + EXPECT_EQ(IsTimeSyncPacketEqual(packetB, *outPktB), true); + EXPECT_EQ(IsTimeSyncPacketEqual(*outPktA, *outPktB), false); +} + +static MultiVerCommitNode MakeMultiVerCommitA() +{ + MultiVerCommitNode outCommit; + outCommit.commitId = vector(1, 11); // 1 is length, 11 is value + outCommit.leftParent = vector(2, 22); // 2 is length, 22 is value + outCommit.rightParent = vector(3, 33); // 3 is length, 33 is value + outCommit.timestamp = 444; // 444 is value + outCommit.version = 5555; // 5555 is value + outCommit.isLocal = 66666; // 66666 is value + outCommit.deviceInfo = "AAAAAA"; + return outCommit; +} + +static MultiVerCommitNode MakeMultiVerCommitB() +{ + MultiVerCommitNode outCommit; + outCommit.commitId = vector(9, 99); // 9 is length, 99 is value + outCommit.leftParent = vector(8, 88); // 8 is length, 88 is value + outCommit.rightParent = vector(7, 77); // 7 is length, 77 is value + outCommit.timestamp = 666; // 666 is value + outCommit.version = 5555; // 5555 is value + outCommit.isLocal = 44444; // 44444 is value + outCommit.deviceInfo = "BBBBBB"; + return outCommit; +} + +static MultiVerCommitNode MakeMultiVerCommitC() +{ + MultiVerCommitNode outCommit; + outCommit.commitId = vector(1, 99); // 1 is length, 99 is value + outCommit.leftParent = vector(2, 88); // 2 is length, 88 is value + outCommit.rightParent = vector(3, 77); // 3 is length, 77 is value + outCommit.timestamp = 466; // 466 is value + outCommit.version = 5555; // 5555 is value + outCommit.isLocal = 66444; // 66444 is value + outCommit.deviceInfo = "CCCCCC"; + return outCommit; +} + +static bool IsMultiVerCommitEqual(const MultiVerCommitNode &inCommitA, const MultiVerCommitNode &inCommitB) +{ + bool equal = true; + equal = inCommitA.commitId == inCommitB.commitId ? equal : false; + equal = inCommitA.leftParent == inCommitB.leftParent ? equal : false; + equal = inCommitA.rightParent == inCommitB.rightParent ? equal : false; + equal = inCommitA.timestamp == inCommitB.timestamp ? equal : false; + equal = inCommitA.version == inCommitB.version ? equal : false; + equal = inCommitA.isLocal == inCommitB.isLocal ? equal : false; + equal = inCommitA.deviceInfo == inCommitB.deviceInfo ? equal : false; + return equal; +} + +static void MakeCommitHistorySyncRequestPacketA(CommitHistorySyncRequestPacket &inPacket) +{ + std::map commitMap; + commitMap[string("A")] = MakeMultiVerCommitA(); + commitMap[string("C")] = MakeMultiVerCommitC(); + inPacket.SetCommitMap(commitMap); +} + +static void MakeCommitHistorySyncRequestPacketB(CommitHistorySyncRequestPacket &inPacket) +{ + std::map commitMap; + commitMap[string("B")] = MakeMultiVerCommitB(); + commitMap[string("C")] = MakeMultiVerCommitC(); + commitMap[string("BB")] = MakeMultiVerCommitB(); + inPacket.SetCommitMap(commitMap); +} + +static bool IsCommitHistorySyncRequestPacketEqual(const CommitHistorySyncRequestPacket &inPacketA, + const CommitHistorySyncRequestPacket &inPacketB) +{ + std::map commitMapA; + std::map commitMapB; + inPacketA.GetCommitMap(commitMapA); + inPacketB.GetCommitMap(commitMapB); + for (auto &entry : commitMapA) { + if (commitMapB.count(entry.first) == 0) { + return false; + } + if (!IsMultiVerCommitEqual(entry.second, commitMapB[entry.first])) { + return false; + } + } + for (auto &entry : commitMapB) { + if (commitMapA.count(entry.first) == 0) { + return false; + } + if (!IsMultiVerCommitEqual(entry.second, commitMapA[entry.first])) { + return false; + } + } + return true; +} + +/** + * @tc.name: Commit History Sync Request Packet 001 + * @tc.desc: Verify CommitHistorySyncRequestPacket Serialization and DeSerialization + * @tc.type: FUNC + * @tc.require: AR000BVRNU AR000CQE0J + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, CommitHistorySyncRequestPacket001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create CommitHistorySyncRequestPacket packetA aand packetB + */ + CommitHistorySyncRequestPacket packetA; + CommitHistorySyncRequestPacket packetB; + MakeCommitHistorySyncRequestPacketA(packetA); + MakeCommitHistorySyncRequestPacketB(packetB); + Message oriMsgA; + Message oriMsgB; + oriMsgA.SetCopiedObject(packetA); + oriMsgA.SetMessageId(COMMIT_HISTORY_SYNC_MESSAGE); + oriMsgA.SetMessageType(TYPE_REQUEST); + oriMsgB.SetCopiedObject(packetB); + oriMsgB.SetMessageId(COMMIT_HISTORY_SYNC_MESSAGE); + oriMsgB.SetMessageType(TYPE_REQUEST); + + /** + * @tc.steps: step2. Serialization packetA to bufferA + */ + uint32_t lenA = CommitHistorySync::CalculateLen(&oriMsgA); + vector bufferA; + bufferA.resize(lenA); + int ret = CommitHistorySync::Serialization(bufferA.data(), lenA, &oriMsgA); + ASSERT_EQ(ret, E_OK); + + /** + * @tc.steps: step3. Serialization packetB to bufferB + */ + uint32_t lenB = CommitHistorySync::CalculateLen(&oriMsgB); + vector bufferB; + bufferB.resize(lenB); + ret = CommitHistorySync::Serialization(bufferB.data(), lenB, &oriMsgB); + ASSERT_EQ(ret, E_OK); + + /** + * @tc.steps: step4. DeSerialization bufferA to outPktA + * @tc.expected: step4. packetA == outPktA + */ + Message outMsgA; + outMsgA.SetMessageId(COMMIT_HISTORY_SYNC_MESSAGE); + outMsgA.SetMessageType(TYPE_REQUEST); + ret = CommitHistorySync::DeSerialization(bufferA.data(), lenA, &outMsgA); + ASSERT_EQ(ret, E_OK); + const CommitHistorySyncRequestPacket *outPktA = outMsgA.GetObject(); + ASSERT_NE(outPktA, nullptr); + EXPECT_EQ(IsCommitHistorySyncRequestPacketEqual(packetA, *outPktA), true); + + /** + * @tc.steps: step5. DeSerialization bufferB to outPktB + * @tc.expected: step5. packetB == outPktB, outPktB != outPktA + */ + Message outMsgB; + outMsgB.SetMessageId(COMMIT_HISTORY_SYNC_MESSAGE); + outMsgB.SetMessageType(TYPE_REQUEST); + ret = CommitHistorySync::DeSerialization(bufferB.data(), lenB, &outMsgB); + ASSERT_EQ(ret, E_OK); + const CommitHistorySyncRequestPacket *outPktB = outMsgB.GetObject(); + ASSERT_NE(outPktB, nullptr); + EXPECT_EQ(IsCommitHistorySyncRequestPacketEqual(packetB, *outPktB), true); + EXPECT_EQ(IsCommitHistorySyncRequestPacketEqual(*outPktA, *outPktB), false); +} + +static void MakeCommitHistorySyncAckPacketA(CommitHistorySyncAckPacket &inPacket) +{ + std::vector commitVec; + commitVec.push_back(MakeMultiVerCommitA()); + commitVec.push_back(MakeMultiVerCommitC()); + inPacket.SetData(commitVec); + inPacket.SetErrorCode(10086); // 10086 is errorcode +} + +static void MakeCommitHistorySyncAckPacketB(CommitHistorySyncAckPacket &inPacket) +{ + std::vector commitVec; + commitVec.push_back(MakeMultiVerCommitB()); + commitVec.push_back(MakeMultiVerCommitC()); + commitVec.push_back(MakeMultiVerCommitB()); + inPacket.SetData(commitVec); + inPacket.SetErrorCode(10010); // 10010 is errorcode +} + +static bool IsCommitHistorySyncAckPacketEqual(const CommitHistorySyncAckPacket &inPacketA, + const CommitHistorySyncAckPacket &inPacketB) +{ + int errCodeA; + int errCodeB; + std::vector commitVecA; + std::vector commitVecB; + inPacketA.GetData(commitVecA); + inPacketB.GetData(commitVecB); + inPacketA.GetErrorCode(errCodeA); + inPacketB.GetErrorCode(errCodeB); + if (errCodeA != errCodeB) { + return false; + } + if (commitVecA.size() != commitVecB.size()) { + return false; + } + int count = 0; + for (auto &entry : commitVecA) { + if (!IsMultiVerCommitEqual(entry, commitVecB[count++])) { + return false; + } + } + return true; +} + +/** + * @tc.name: Commit History Sync Ack Packet 001 + * @tc.desc: Verify CommitHistorySyncAckPacket Serialization and DeSerialization + * @tc.type: FUNC + * @tc.require: AR000BVRNU AR000CQE0J + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, CommitHistorySyncAckPacket001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create CommitHistorySyncAckPacket packetA aand packetB + */ + CommitHistorySyncAckPacket packetA; + CommitHistorySyncAckPacket packetB; + MakeCommitHistorySyncAckPacketA(packetA); + MakeCommitHistorySyncAckPacketB(packetB); + Message oriMsgA; + Message oriMsgB; + oriMsgA.SetCopiedObject(packetA); + oriMsgA.SetMessageId(COMMIT_HISTORY_SYNC_MESSAGE); + oriMsgA.SetMessageType(TYPE_RESPONSE); + oriMsgB.SetCopiedObject(packetB); + oriMsgB.SetMessageId(COMMIT_HISTORY_SYNC_MESSAGE); + oriMsgB.SetMessageType(TYPE_RESPONSE); + + /** + * @tc.steps: step2. Serialization packetA to bufferA + */ + uint32_t lenA = CommitHistorySync::CalculateLen(&oriMsgA); + vector bufferA; + bufferA.resize(lenA); + int ret = CommitHistorySync::Serialization(bufferA.data(), lenA, &oriMsgA); + ASSERT_EQ(ret, E_OK); + + /** + * @tc.steps: step3. Serialization packetB to bufferB + */ + uint32_t lenB = CommitHistorySync::CalculateLen(&oriMsgB); + vector bufferB; + bufferB.resize(lenB); + ret = CommitHistorySync::Serialization(bufferB.data(), lenB, &oriMsgB); + ASSERT_EQ(ret, E_OK); + + /** + * @tc.steps: step4. DeSerialization bufferA to outPktA + * @tc.expected: step4. packetA == outPktA + */ + Message outMsgA; + outMsgA.SetMessageId(COMMIT_HISTORY_SYNC_MESSAGE); + outMsgA.SetMessageType(TYPE_RESPONSE); + ret = CommitHistorySync::DeSerialization(bufferA.data(), lenA, &outMsgA); + ASSERT_EQ(ret, E_OK); + const CommitHistorySyncAckPacket *outPktA = outMsgA.GetObject(); + ASSERT_NE(outPktA, nullptr); + EXPECT_EQ(IsCommitHistorySyncAckPacketEqual(packetA, *outPktA), true); + + /** + * @tc.steps: step5. DeSerialization bufferB to outPktB + * @tc.expected: step5. packetB == outPktB, outPktB!= outPktA + */ + Message outMsgB; + outMsgB.SetMessageId(COMMIT_HISTORY_SYNC_MESSAGE); + outMsgB.SetMessageType(TYPE_RESPONSE); + ret = CommitHistorySync::DeSerialization(bufferB.data(), lenB, &outMsgB); + ASSERT_EQ(ret, E_OK); + const CommitHistorySyncAckPacket *outPktB = outMsgB.GetObject(); + ASSERT_NE(outPktB, nullptr); + EXPECT_EQ(IsCommitHistorySyncAckPacketEqual(packetB, *outPktB), true); + EXPECT_EQ(IsCommitHistorySyncAckPacketEqual(*outPktA, *outPktB), false); +} + +static bool IsMultiVerRequestPacketEqual(const MultiVerRequestPacket &inPacketA, + const MultiVerRequestPacket &inPacketB) +{ + MultiVerCommitNode commitA; + MultiVerCommitNode commitB; + inPacketA.GetCommit(commitA); + inPacketB.GetCommit(commitB); + return IsMultiVerCommitEqual(commitA, commitB); +} + +/** + * @tc.name: MultiVerValueObject Request Packet 001 + * @tc.desc: Verify MultiVerRequestPacket Serialization and DeSerialization + * @tc.type: FUNC + * @tc.require: AR000BVRNU AR000CQE0J + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, MultiVerRequestPacket001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create CommitHistorySyncAckPacket packetA aand packetB + */ + MultiVerRequestPacket packetA; + MultiVerRequestPacket packetB; + MultiVerCommitNode commitA = MakeMultiVerCommitA(); + MultiVerCommitNode commitB = MakeMultiVerCommitB(); + packetA.SetCommit(commitA); + packetB.SetCommit(commitB); + Message oriMsgA; + Message oriMsgB; + oriMsgA.SetCopiedObject(packetA); + oriMsgA.SetMessageId(MULTI_VER_DATA_SYNC_MESSAGE); + oriMsgA.SetMessageType(TYPE_REQUEST); + oriMsgB.SetCopiedObject(packetB); + oriMsgB.SetMessageId(MULTI_VER_DATA_SYNC_MESSAGE); + oriMsgB.SetMessageType(TYPE_REQUEST); + + /** + * @tc.steps: step2. Serialization packetA to bufferA + */ + uint32_t lenA = MultiVerDataSync::CalculateLen(&oriMsgA); + vector bufferA; + bufferA.resize(lenA); + int ret = MultiVerDataSync::Serialization(bufferA.data(), lenA, &oriMsgA); + ASSERT_EQ(ret, E_OK); + + /** + * @tc.steps: step3. Serialization packetB to bufferB + */ + uint32_t lenB = MultiVerDataSync::CalculateLen(&oriMsgB); + vector bufferB; + bufferB.resize(lenB); + ret = MultiVerDataSync::Serialization(bufferB.data(), lenB, &oriMsgB); + ASSERT_EQ(ret, E_OK); + + /** + * @tc.steps: step4. DeSerialization bufferA to outPktA + * @tc.expected: step4. packetA == outPktA + */ + Message outMsgA; + outMsgA.SetMessageId(MULTI_VER_DATA_SYNC_MESSAGE); + outMsgA.SetMessageType(TYPE_REQUEST); + ret = MultiVerDataSync::DeSerialization(bufferA.data(), lenA, &outMsgA); + ASSERT_EQ(ret, E_OK); + const MultiVerRequestPacket *outPktA = outMsgA.GetObject(); + ASSERT_NE(outPktA, nullptr); + EXPECT_EQ(IsMultiVerRequestPacketEqual(packetA, *outPktA), true); + + /** + * @tc.steps: step5. DeSerialization bufferB to outPktB + * @tc.expected: step5. packetB == outPktB, outPktB!= outPktA + */ + Message outMsgB; + outMsgB.SetMessageId(MULTI_VER_DATA_SYNC_MESSAGE); + outMsgB.SetMessageType(TYPE_REQUEST); + ret = MultiVerDataSync::DeSerialization(bufferB.data(), lenB, &outMsgB); + ASSERT_EQ(ret, E_OK); + const MultiVerRequestPacket *outPktB = outMsgB.GetObject(); + ASSERT_NE(outPktB, nullptr); + EXPECT_EQ(IsMultiVerRequestPacketEqual(packetB, *outPktB), true); + EXPECT_EQ(IsMultiVerRequestPacketEqual(*outPktA, *outPktB), false); +} + +static void MakeMultiVerAckPacketA(MultiVerAckPacket &inPacket) +{ + std::vector> entryVec; + entryVec.push_back(vector(111, 11)); // 111 is length, 11 is value + entryVec.push_back(vector(222, 22)); // 222 is length, 22 is value + inPacket.SetData(entryVec); + inPacket.SetErrorCode(333); // 333 is errorcode +} + +static void MakeMultiVerAckPacketB(MultiVerAckPacket &inPacket) +{ + std::vector> entryVec; + entryVec.push_back(vector(999, 99)); // 999 is length, 99 is value + entryVec.push_back(vector(888, 88)); // 888 is length, 88 is value + inPacket.SetData(entryVec); + inPacket.SetErrorCode(777); // 777 is errorcode +} + +static bool IsMultiVerAckPacketEqual(const MultiVerAckPacket &inPacketA, const MultiVerAckPacket &inPacketB) +{ + int errCodeA; + int errCodeB; + std::vector> entryVecA; + std::vector> entryVecB; + inPacketA.GetData(entryVecA); + inPacketB.GetData(entryVecB); + inPacketA.GetErrorCode(errCodeA); + inPacketB.GetErrorCode(errCodeB); + if (errCodeA != errCodeB) { + return false; + } + if (entryVecA != entryVecB) { + return false; + } + return true; +} + +/** + * @tc.name: MultiVerValueObject Ack Packet 001 + * @tc.desc: Verify MultiVerAckPacket Serialization and DeSerialization + * @tc.type: FUNC + * @tc.require: AR000BVRNU AR000CQE0J + * @tc.author: xiaozhenjian + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, MultiVerAckPacket001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create MultiVerAckPacket packetA aand packetB + */ + MultiVerAckPacket packetA; + MultiVerAckPacket packetB; + MakeMultiVerAckPacketA(packetA); + MakeMultiVerAckPacketB(packetB); + Message oriMsgA; + Message oriMsgB; + oriMsgA.SetCopiedObject(packetA); + oriMsgA.SetMessageId(MULTI_VER_DATA_SYNC_MESSAGE); + oriMsgA.SetMessageType(TYPE_RESPONSE); + oriMsgB.SetCopiedObject(packetB); + oriMsgB.SetMessageId(MULTI_VER_DATA_SYNC_MESSAGE); + oriMsgB.SetMessageType(TYPE_RESPONSE); + + /** + * @tc.steps: step2. Serialization packetA to bufferA + */ + uint32_t lenA = MultiVerDataSync::CalculateLen(&oriMsgA); + vector bufferA; + bufferA.resize(lenA); + int ret = MultiVerDataSync::Serialization(bufferA.data(), lenA, &oriMsgA); + ASSERT_EQ(ret, E_OK); + + /** + * @tc.steps: step3. Serialization packetB to bufferB + */ + uint32_t lenB = MultiVerDataSync::CalculateLen(&oriMsgB); + vector bufferB; + bufferB.resize(lenB); + ret = MultiVerDataSync::Serialization(bufferB.data(), lenB, &oriMsgB); + ASSERT_EQ(ret, E_OK); + + /** + * @tc.steps: step4. DeSerialization bufferA to outPktA + * @tc.expected: step4. packetA == outPktA + */ + Message outMsgA; + outMsgA.SetMessageId(MULTI_VER_DATA_SYNC_MESSAGE); + outMsgA.SetMessageType(TYPE_RESPONSE); + ret = MultiVerDataSync::DeSerialization(bufferA.data(), lenA, &outMsgA); + ASSERT_EQ(ret, E_OK); + const MultiVerAckPacket *outPktA = outMsgA.GetObject(); + ASSERT_NE(outPktA, nullptr); + EXPECT_EQ(IsMultiVerAckPacketEqual(packetA, *outPktA), true); + + /** + * @tc.steps: step5. DeSerialization bufferB to outPktB + * @tc.expected: step5. packetB == outPktB, outPktB!= outPktA + */ + Message outMsgB; + outMsgB.SetMessageId(MULTI_VER_DATA_SYNC_MESSAGE); + outMsgB.SetMessageType(TYPE_RESPONSE); + ret = MultiVerDataSync::DeSerialization(bufferB.data(), lenB, &outMsgB); + ASSERT_EQ(ret, E_OK); + const MultiVerAckPacket *outPktB = outMsgB.GetObject(); + ASSERT_NE(outPktB, nullptr); + EXPECT_EQ(IsMultiVerAckPacketEqual(packetB, *outPktB), true); + EXPECT_EQ(IsMultiVerAckPacketEqual(*outPktA, *outPktB), false); +} + +/** + * @tc.name: Simple Data Sync 001 + * @tc.desc: Verify normal simple data sync function. + * @tc.type: FUNC + * @tc.require: AR000BVDGR + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, SimpleDataSync001, TestSize.Level2) +{ + /** + * @tc.steps: step1. open a KvStoreNbDelegate as deviceA + */ + g_mgr.GetKvStore(STORE_ID, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_1)); + + /** + * @tc.steps: step2. deviceB put {k1, v1} + */ + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1), E_OK); + + /** + * @tc.steps: step4. deviceB put {k2, v2} + */ + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_2, DistributedDBUnitTest::VALUE_2), E_OK); + + /** + * @tc.steps: step5. enable communicator and set deviceB,C online + */ + g_deviceB->Online(); + g_deviceC->Online(); + + /** + * @tc.steps: step6. wait for sync + * @tc.expected: step6. deviceA has {k1, v2} {k2, v2} + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + Value value; + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_1, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_1); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_2, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_2); +} + +/** + * @tc.name: Big Data Sync 001 + * @tc.desc: Verify normal big data sync function. + * @tc.type: FUNC + * @tc.require: AR000BVDGR + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, BigDataSync001, TestSize.Level2) +{ + /** + * @tc.steps: step1. open a KvStoreNbDelegate as deviceA + */ + g_mgr.GetKvStore(STORE_ID, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_1)); + + /** + * @tc.steps: step2. deviceB put {k1, v1}, v1 size 1k + */ + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(value1, BIG_VALUE_SIZE); // 1k +1 + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, value1), E_OK); + + /** + * @tc.steps: step4. deviceC put {k2, v2}, v2 size 1k + */ + Value value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, BIG_VALUE_SIZE); // 1k +1 + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_2, value2), E_OK); + + /** + * @tc.steps: step5. set deviceB,C online + */ + g_deviceB->Online(); + g_deviceC->Online(); + + /** + * @tc.steps: step5. wait 2s for sync + * @tc.expected: step5. deviceA has {k1, v2} {k2, v2} + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + Value value; + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_1, value), E_OK); + EXPECT_EQ(value, value1); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_2, value), E_OK); + EXPECT_EQ(value, value2); +} + +/** + * @tc.name: Limit Data Sync 001 + * @tc.desc: Verify normal limit data sync function. + * @tc.type: FUNC + * @tc.require: AR000BVDGR + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, LimitDataSync001, TestSize.Level2) +{ + /** + * @tc.steps: step1. open a KvStoreNbDelegate as deviceA + */ + g_mgr.GetKvStore(STORE_ID, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_1)); + /** + * @tc.steps: step2. deviceB put {k1, v1}, k1 size 1k, v1 size 4M + */ + Key key1; + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(key1, LIMIT_KEY_SIZE); + DistributedDBToolsUnitTest::GetRandomKeyValue(value1, LIMIT_VALUE_SIZE); + ASSERT_EQ(g_deviceB->PutData(key1, value1), E_OK); + + /** + * @tc.steps: step3. deviceC put {k2, v2}, k2 size 1k, v2 size 4M + */ + Key key2; + Value value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(key2, LIMIT_KEY_SIZE); + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, LIMIT_VALUE_SIZE); + ASSERT_EQ(g_deviceC->PutData(key2, value2), E_OK); + + /** + * @tc.steps: step4. set deviceB,C online + */ + g_deviceB->Online(); + g_deviceC->Online(); + + /** + * @tc.steps: step5. wait 30 for sync + * @tc.expected: step5. deviceA has {k1, v2} {k2, v2} + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_LIMIT_TIME)); + Value value; + EXPECT_EQ(GetData(g_kvDelegatePtr, key1, value), E_OK); + EXPECT_EQ(value, value1); + EXPECT_EQ(GetData(g_kvDelegatePtr, key2, value), E_OK); + EXPECT_EQ(value, value2); +} + +/** + * @tc.name: Multi Record 001 + * @tc.desc: Verify normal multi record sync function. + * @tc.type: FUNC + * @tc.require: AR000BVDGR + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, MultiRecord001, TestSize.Level2) +{ + /** + * @tc.steps: step1. open a KvStoreNbDelegate as deviceA + */ + g_mgr.GetKvStore(STORE_ID, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_1)); + + /** + * @tc.steps: step2. deviceB put {k1, v1} + */ + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1), E_OK); + + /** + * @tc.steps: step4. deviceB put {k1, v2} v2 > 1K + */ + Value value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, BIG_VALUE_SIZE); // 1k +1 + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, value2), E_OK); + + /** + * @tc.steps: step4. deviceB put {k2, v3} + */ + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_2, DistributedDBUnitTest::VALUE_3), E_OK); + + /** + * @tc.steps: step5. deviceB put {k3, v3} and delete k3 + */ + ASSERT_TRUE(g_deviceB->StartTransaction() == E_OK); + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_3, DistributedDBUnitTest::VALUE_3), E_OK); + ASSERT_EQ(g_deviceB->DeleteData(DistributedDBUnitTest::KEY_3), E_OK); + ASSERT_TRUE(g_deviceB->Commit() == E_OK); + + /** + * @tc.steps: step6. deviceC put {k4, v4} + */ + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_4, DistributedDBUnitTest::VALUE_4), E_OK); + + /** + * @tc.steps: step7. deviceB put {k4, v5} v2 > 1K + */ + Value value5; + DistributedDBToolsUnitTest::GetRandomKeyValue(value5, BIG_VALUE_SIZE); // 1k +1 + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_4, value5), E_OK); + + /** + * @tc.steps: step8. deviceB put {k5, v6} + */ + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_5, DistributedDBUnitTest::VALUE_6), E_OK); + + /** + * @tc.steps: step9. deviceB put {k6, v6} and delete k6 + */ + ASSERT_TRUE(g_deviceC->StartTransaction() == E_OK); + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_6, DistributedDBUnitTest::VALUE_6), E_OK); + ASSERT_EQ(g_deviceC->DeleteData(DistributedDBUnitTest::KEY_6), E_OK); + ASSERT_TRUE(g_deviceC->Commit() == E_OK); + + /** + * @tc.steps: step10. set deviceB,C online + */ + g_deviceB->Online(); + g_deviceC->Online(); + + /** + * @tc.steps: step11. wait 5s for sync + * @tc.expected: step11. deviceA has {k1, v2}, {k2, v3}, {k4, v5}, {k5, v6} + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_LONG_TIME)); + Value value; + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_1, value), E_OK); + EXPECT_EQ(value, value2); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_2, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_3); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_4, value), E_OK); + EXPECT_EQ(value, value5); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_5, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_6); +} + +/** + * @tc.name: Net Disconnect Sync 001 + * @tc.desc: Test exception sync when net disconnected. + * @tc.type: FUNC + * @tc.require: AR000BVDGR + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, NetDisconnectSync001, TestSize.Level3) +{ + /** + * @tc.steps: step1. open a KvStoreNbDelegate as deviceA + */ + g_mgr.GetKvStore(STORE_ID, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_1)); + + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + ASSERT_TRUE(g_deviceB->StartTransaction() == E_OK); + /** + * @tc.steps: step2. deviceB put {k1, v1} + */ + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1), E_OK); + + /** + * @tc.steps: step4. deviceB put {k1, v2} v2 > 1K + */ + Value value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, 1024 + 1); // 1k +1 + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, value2), E_OK); + + /** + * @tc.steps: step4. deviceB put {k2, v3} + */ + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_2, DistributedDBUnitTest::VALUE_3), E_OK); + + /** + * @tc.steps: step5. deviceB put {k3, v3} and delete k3 + */ + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_3, DistributedDBUnitTest::VALUE_3), E_OK); + ASSERT_EQ(g_deviceB->DeleteData(DistributedDBUnitTest::KEY_3), E_OK); + ASSERT_TRUE(g_deviceB->Commit() == E_OK); + + /** + * @tc.steps: step6. deviceB online and enable communicator + */ + g_deviceB->Online(); + + /** + * @tc.steps: step7. disable communicator and wait 5s + * @tc.expected: step7. deviceA has no key1, key2 + */ + g_communicatorAggregator->Disable(); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_LONG_TIME + WAIT_LONG_TIME)); + + Value value; + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_1, value), NOT_FOUND); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_2, value), NOT_FOUND); + + ASSERT_TRUE(g_deviceC->StartTransaction() == E_OK); + /** + * @tc.steps: step8. deviceC put {k4, v4} + */ + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_4, DistributedDBUnitTest::VALUE_4), E_OK); + + /** + * @tc.steps: step9. deviceB put {k4, v5} v2 > 1K + */ + Value value5; + DistributedDBToolsUnitTest::GetRandomKeyValue(value5, BIG_VALUE_SIZE); // 1k +1 + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_4, value5), E_OK); + + /** + * @tc.steps: step10. deviceB put {k5, v6} + */ + ASSERT_TRUE(g_deviceC->PutData(DistributedDBUnitTest::KEY_5, DistributedDBUnitTest::VALUE_6) == E_OK); + + /** + * @tc.steps: step11. deviceB put {k6, v6} and delete k6 + */ + ASSERT_TRUE(g_deviceC->PutData(DistributedDBUnitTest::KEY_6, DistributedDBUnitTest::VALUE_6) == E_OK); + ASSERT_TRUE(g_deviceC->DeleteData(DistributedDBUnitTest::KEY_6) == E_OK); + ASSERT_TRUE(g_deviceC->Commit() == E_OK); + + /** + * @tc.steps: step12. deviceC online and enable communicator + */ + g_communicatorAggregator->Enable(); + g_deviceC->Online(); + + /** + * @tc.steps: step13. wait 5s for sync + * @tc.expected: step13. deviceA has {k4, v5}, {k5, v6} + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_LONG_TIME)); // wait 5s + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_4, value), E_OK); + EXPECT_EQ(value, value5); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_5, value), E_OK); + EXPECT_EQ(value, DistributedDBUnitTest::VALUE_6); +} + +/** + * @tc.name: SyncQueue006 + * @tc.desc: multi version not support sync queue + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, SyncQueue006, TestSize.Level3) +{ + /** + * @tc.steps:step1. open a KvStoreNbDelegate as deviceA + */ + g_mgr.GetKvStore(STORE_ID, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + + /** + * @tc.steps:step2. Set PragmaCmd to be GET_QUEUED_SYNC_SIZE + * @tc.expected: step2. Expect return NOT_SUPPORT. + */ + int param; + PragmaData input = static_cast(¶m); + EXPECT_EQ(g_kvDelegatePtr->Pragma(GET_QUEUED_SYNC_SIZE, input), NOT_SUPPORT); + EXPECT_EQ(g_kvDelegatePtr->Pragma(SET_QUEUED_SYNC_LIMIT, input), NOT_SUPPORT); + EXPECT_EQ(g_kvDelegatePtr->Pragma(GET_QUEUED_SYNC_LIMIT, input), NOT_SUPPORT); +} + +/** + * @tc.name: PermissionCheck001 + * @tc.desc: deviceA permission check not pass + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, PermissionCheck001, TestSize.Level2) +{ + /** + * @tc.steps: step1. SetPermissionCheckCallback + * @tc.expected: step1. return OK. + */ + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + if (flag & CHECK_FLAG_RECEIVE) { + LOGD("in RunPermissionCheck callback func, check not pass, flag:%d", flag); + return false; + } else { + LOGD("in RunPermissionCheck callback func, check pass, flag:%d", flag); + return true; + } + }; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(permissionCheckCallback), OK); + + /** + * @tc.steps: step2. open a KvStoreNbDelegate as deviceA + */ + g_mgr.GetKvStore(STORE_ID, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_1)); + + /** + * @tc.steps: step3. deviceB put {k1, v1} + */ + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1), E_OK); + + /** + * @tc.steps: step4. deviceC put {k2, v2} + */ + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_2, DistributedDBUnitTest::VALUE_2), E_OK); + + /** + * @tc.steps: step5. enable communicator and set deviceB,C online + */ + g_deviceB->Online(); + g_deviceC->Online(); + + /** + * @tc.steps: step6. wait for sync + * @tc.expected: step6. deviceA do not has {k1, v2} {k2, v2} + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + Value value; + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_1, value), NOT_FOUND); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_2, value), NOT_FOUND); + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} + +/** + * @tc.name: PermissionCheck002 + * @tc.desc: deviceB deviceC permission check not pass + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBMultiVerP2PSyncTest, PermissionCheck002, TestSize.Level2) +{ + /** + * @tc.steps: step1. SetPermissionCheckCallback + * @tc.expected: step1. return OK. + */ + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + if (flag & CHECK_FLAG_SEND) { + LOGD("in RunPermissionCheck callback func, check not pass, flag:%d", flag); + return false; + } else { + LOGD("in RunPermissionCheck callback func, check pass, flag:%d", flag); + return true; + } + }; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(permissionCheckCallback), OK); + + /** + * @tc.steps: step2. open a KvStoreNbDelegate as deviceA + */ + g_mgr.GetKvStore(STORE_ID, g_option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_1)); + + /** + * @tc.steps: step3. deviceB put {k1, v1} + */ + ASSERT_EQ(g_deviceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1), E_OK); + + /** + * @tc.steps: step4. deviceC put {k2, v2} + */ + ASSERT_EQ(g_deviceC->PutData(DistributedDBUnitTest::KEY_2, DistributedDBUnitTest::VALUE_2), E_OK); + + /** + * @tc.steps: step5. enable communicator and set deviceB,C online + */ + g_deviceB->Online(); + g_deviceC->Online(); + + /** + * @tc.steps: step6. wait for sync + * @tc.expected: step6. deviceA do not has {k1, v2} {k2, v2} + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME_2)); + Value value; + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_1, value), NOT_FOUND); + EXPECT_EQ(GetData(g_kvDelegatePtr, DistributedDBUnitTest::KEY_2, value), NOT_FOUND); + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_relational_ver_p2p_sync_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_relational_ver_p2p_sync_test.cpp new file mode 100644 index 00000000..75a8193b --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_relational_ver_p2p_sync_test.cpp @@ -0,0 +1,1241 @@ +/* +* Copyright (c) 2021 Huawei Device Co., Ltd. +* 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. +*/ +#ifdef RELATIONAL_STORE +#include + +#include "db_common.h" +#include "db_constant.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "isyncer.h" +#include "kv_virtual_device.h" +#include "platform_specific.h" +#include "relational_schema_object.h" +#include "relational_store_manager.h" +#include "relational_virtual_device.h" +#include "runtime_config.h" +#include "virtual_relational_ver_sync_db_interface.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; + +namespace { + const std::string DEVICE_B = "deviceB"; + const std::string DEVICE_C = "deviceC"; + const std::string g_tableName = "TEST_TABLE"; + + const int ONE_HUNDERED = 100; + const char DEFAULT_CHAR = 'D'; + const std::string DEFAULT_TEXT = "This is a text"; + const std::vector DEFAULT_BLOB(ONE_HUNDERED, DEFAULT_CHAR); + + RelationalStoreManager g_mgr(APP_ID, USER_ID); + std::string g_testDir; + std::string g_dbDir; + std::string g_id; + std::vector g_storageType = { + StorageType::STORAGE_TYPE_INTEGER, StorageType::STORAGE_TYPE_REAL, + StorageType::STORAGE_TYPE_TEXT, StorageType::STORAGE_TYPE_BLOB + }; + DistributedDBToolsUnitTest g_tool; + RelationalStoreDelegate* g_rdbDelegatePtr = nullptr; + VirtualCommunicatorAggregator* g_communicatorAggregator = nullptr; + RelationalVirtualDevice *g_deviceB = nullptr; + RelationalVirtualDevice *g_deviceC = nullptr; + std::vector g_fieldInfoList; + RelationalStoreObserverUnitTest *g_observer = nullptr; + std::string GetDeviceTableName(const std::string &tableName) + { + return "naturalbase_rdb_aux_" + + tableName + "_" + DBCommon::TransferStringToHex(DBCommon::TransferHashString(DEVICE_B)); + } + + void OpenStore() + { + if (g_observer == nullptr) { + g_observer = new (std::nothrow) RelationalStoreObserverUnitTest(); + } + RelationalStoreDelegate::Option option = {g_observer}; + g_mgr.OpenStore(g_dbDir, STORE_ID_1, option, g_rdbDelegatePtr); + ASSERT_TRUE(g_rdbDelegatePtr != nullptr); + } + + int GetDB(sqlite3 *&db) + { + int flag = SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + const auto &dbPath = g_dbDir; + int rc = sqlite3_open_v2(dbPath.c_str(), &db, flag, nullptr); + if (rc != SQLITE_OK) { + return rc; + } + EXPECT_EQ(SQLiteUtils::RegisterCalcHash(db), E_OK); + EXPECT_EQ(SQLiteUtils::RegisterGetSysTime(db), E_OK); + EXPECT_EQ(sqlite3_exec(db, "PRAGMA journal_mode=WAL;", nullptr, nullptr, nullptr), SQLITE_OK); + return rc; + } + + std::string GetType(StorageType type) + { + static std::map typeMap = { + {StorageType::STORAGE_TYPE_INTEGER, "INT"}, + {StorageType::STORAGE_TYPE_REAL, "DOUBLE"}, + {StorageType::STORAGE_TYPE_TEXT, "TEXT"}, + {StorageType::STORAGE_TYPE_BLOB, "BLOB"} + }; + if (typeMap.find(type) == typeMap.end()) { + type = StorageType::STORAGE_TYPE_INTEGER; + } + return typeMap[type]; + } + + int DropTable(sqlite3 *db, const std::string &tableName) + { + std::string sql = "DROP TABLE " + tableName + ";"; + return sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr); + } + + int CreateTable(sqlite3 *db, std::vector &fieldInfoList, const std::string &tableName) + { + std::string sql = "CREATE TABLE " + tableName + "("; + int index = 0; + for (const auto &field : fieldInfoList) { + if (index != 0) { + sql += ","; + } + sql += field.GetFieldName() + " "; + std::string type = GetType(field.GetStorageType()); + sql += type + " "; + if (index == 0) { + sql += "PRIMARY KEY NOT NULL "; + } + index++; + } + sql += ");"; + int rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr); + return rc; + } + + int PrepareInsert(sqlite3 *db, sqlite3_stmt *&statement, + std::vector fieldInfoList, const std::string &tableName) + { + std::string sql = "INSERT OR REPLACE INTO " + tableName + "("; + int index = 0; + for (const auto &fieldInfo : fieldInfoList) { + if (index != 0) { + sql += ","; + } + sql += fieldInfo.GetFieldName(); + index++; + } + sql += ") VALUES ("; + while (index > 0) { + sql += "?"; + if (index != 1) { + sql += ", "; + } + index--; + } + sql += ");"; + return sqlite3_prepare_v2(db, sql.c_str(), -1, &statement, nullptr); + } + + int SimulateCommitData(sqlite3 *db, sqlite3_stmt *&statement) + { + sqlite3_exec(db, "BEGIN IMMEDIATE TRANSACTION", nullptr, nullptr, nullptr); + + int rc = sqlite3_step(statement); + + sqlite3_exec(db, "COMMIT TRANSACTION", nullptr, nullptr, nullptr); + return rc; + } + + void BindValue(const DataValue &item, sqlite3_stmt *stmt, int col) + { + switch (item.GetType()) { + case StorageType::STORAGE_TYPE_INTEGER: { + int64_t intData = 0; + (void)item.GetInt64(intData); + EXPECT_EQ(sqlite3_bind_int64(stmt, col, intData), SQLITE_OK); + break; + } + + case StorageType::STORAGE_TYPE_REAL: { + double doubleData = 0; + (void)item.GetDouble(doubleData); + EXPECT_EQ(sqlite3_bind_double(stmt, col, doubleData), SQLITE_OK); + break; + } + + case StorageType::STORAGE_TYPE_TEXT: { + std::string strData; + (void)item.GetText(strData); + EXPECT_EQ(SQLiteUtils::BindTextToStatement(stmt, col, strData), E_OK); + break; + } + + case StorageType::STORAGE_TYPE_BLOB: { + Blob blob; + (void)item.GetBlob(blob); + std::vector blobData(blob.GetData(), blob.GetData() + blob.GetSize()); + EXPECT_EQ(SQLiteUtils::BindBlobToStatement(stmt, col, blobData, true), E_OK); + break; + } + + case StorageType::STORAGE_TYPE_NULL: { + EXPECT_EQ(SQLiteUtils::MapSQLiteErrno(sqlite3_bind_null(stmt, col)), E_OK); + break; + } + + default: + break; + } + } + + void InsertValue(sqlite3 *db, std::map &dataMap, + std::vector &fieldInfoList, const std::string &tableName) + { + sqlite3_stmt *stmt = nullptr; + EXPECT_EQ(PrepareInsert(db, stmt, fieldInfoList, tableName), SQLITE_OK); + for (int i = 0; i < static_cast(fieldInfoList.size()); ++i) { + const auto &fieldName = fieldInfoList[i].GetFieldName(); + ASSERT_TRUE(dataMap.find(fieldName) != dataMap.end()); + const auto &item = dataMap[fieldName]; + const int index = i + 1; + BindValue(item, stmt, index); + } + EXPECT_EQ(SimulateCommitData(db, stmt), SQLITE_DONE); + sqlite3_finalize(stmt); + } + + void InsertValue(sqlite3 *db, std::map &dataMap) + { + InsertValue(db, dataMap, g_fieldInfoList, g_tableName); + } + + void SetNull(DataValue &dataValue) + { + dataValue.ResetValue(); + } + + void SetInt64(DataValue &dataValue) + { + dataValue = INT64_MAX; + } + + void SetDouble(DataValue &dataValue) + { + dataValue = 1.0; + } + + void SetText(DataValue &dataValue) + { + dataValue.SetText(DEFAULT_TEXT); + } + + void SetBlob(DataValue &dataValue) + { + Blob blob; + blob.WriteBlob(DEFAULT_BLOB.data(), DEFAULT_BLOB.size()); + dataValue.SetBlob(blob); + } + + void GenerateValue(std::map &dataMap, std::vector &fieldInfoList) + { + static std::map typeMapFunction = { + {StorageType::STORAGE_TYPE_NULL, &SetNull}, + {StorageType::STORAGE_TYPE_INTEGER, &SetInt64}, + {StorageType::STORAGE_TYPE_REAL, &SetDouble}, + {StorageType::STORAGE_TYPE_TEXT, &SetText}, + {StorageType::STORAGE_TYPE_BLOB, &SetBlob} + }; + for (auto &fieldInfo : fieldInfoList) { + DataValue dataValue; + if (typeMapFunction.find(fieldInfo.GetStorageType()) == typeMapFunction.end()) { + fieldInfo.SetStorageType(StorageType::STORAGE_TYPE_NULL); + } + typeMapFunction[fieldInfo.GetStorageType()](dataValue); + dataMap[fieldInfo.GetFieldName()] = std::move(dataValue); + } + } + + void InsertFieldInfo(std::vector &fieldInfoList) + { + fieldInfoList.clear(); + FieldInfo columnFirst; + columnFirst.SetFieldName("ID"); + columnFirst.SetStorageType(StorageType::STORAGE_TYPE_INTEGER); + columnFirst.SetColumnId(0); // the first column + FieldInfo columnSecond; + columnSecond.SetFieldName("NAME"); + columnSecond.SetStorageType(StorageType::STORAGE_TYPE_TEXT); + columnSecond.SetColumnId(1); // the 2nd column + FieldInfo columnThird; + columnThird.SetFieldName("AGE"); + columnThird.SetStorageType(StorageType::STORAGE_TYPE_INTEGER); + columnThird.SetColumnId(2); // the 3rd column(index 2 base 0) + fieldInfoList.push_back(columnFirst); + fieldInfoList.push_back(columnSecond); + fieldInfoList.push_back(columnThird); + } + + void BlockSync(const Query &query, SyncMode syncMode, DBStatus exceptStatus, + const std::vector &devices) + { + std::map> statusMap; + SyncStatusCallback callBack = [&statusMap]( + const std::map> &devicesMap) { + statusMap = devicesMap; + }; + DBStatus callStatus = g_rdbDelegatePtr->Sync(devices, syncMode, query, callBack, true); + EXPECT_EQ(callStatus, OK); + for (const auto &tablesRes : statusMap) { + for (const auto &tableStatus : tablesRes.second) { + EXPECT_EQ(tableStatus.status, exceptStatus); + } + } + } + + void BlockSync(const std::string &tableName, SyncMode syncMode, DBStatus exceptStatus, + const std::vector &devices) + { + Query query = Query::Select(tableName); + BlockSync(query, syncMode, exceptStatus, devices); + } + + void BlockSync(SyncMode syncMode, DBStatus exceptStatus, const std::vector &devices) + { + BlockSync(g_tableName, syncMode, exceptStatus, devices); + } + + int PrepareSelect(sqlite3 *db, sqlite3_stmt *&statement, const std::string &table) + { + const std::string sql = "SELECT * FROM " + table; + return sqlite3_prepare_v2(db, sql.c_str(), -1, &statement, nullptr); + } + + void GetDataValue(sqlite3_stmt *statement, int col, DataValue &dataValue) + { + int type = sqlite3_column_type(statement, col); + switch (type) { + case SQLITE_INTEGER: { + dataValue = static_cast(sqlite3_column_int64(statement, col)); + break; + } + case SQLITE_FLOAT: { + dataValue = sqlite3_column_double(statement, col); + break; + } + case SQLITE_TEXT: { + std::string str; + SQLiteUtils::GetColumnTextValue(statement, col, str); + dataValue.SetText(str); + break; + } + case SQLITE_BLOB: { + std::vector blobValue; + (void)SQLiteUtils::GetColumnBlobValue(statement, col, blobValue); + Blob blob; + blob.WriteBlob(blobValue.data(), static_cast(blobValue.size())); + dataValue.SetBlob(blob); + break; + } + case SQLITE_NULL: + break; + default: + LOGW("unknown type[%d] column[%d] ignore", type, col); + } + } + + void GetSyncDataStep(std::map &dataMap, sqlite3_stmt *statement, + std::vector fieldInfoList) + { + int columnCount = sqlite3_column_count(statement); + ASSERT_EQ(static_cast(columnCount), fieldInfoList.size()); + for (int col = 0; col < columnCount; ++col) { + DataValue dataValue; + GetDataValue(statement, col, dataValue); + dataMap[fieldInfoList.at(col).GetFieldName()] = std::move(dataValue); + } + } + + void GetSyncData(sqlite3 *db, std::map &dataMap, const std::string &tableName, + std::vector fieldInfoList) + { + sqlite3_stmt *statement = nullptr; + EXPECT_EQ(PrepareSelect(db, statement, GetDeviceTableName(tableName)), SQLITE_OK); + while (true) { + int rc = sqlite3_step(statement); + if (rc != SQLITE_ROW) { + LOGD("GetSyncData Exist by code[%d]", rc); + break; + } + GetSyncDataStep(dataMap, statement, fieldInfoList); + } + sqlite3_finalize(statement); + } + + void InsertValueToDB(std::map &dataMap) + { + sqlite3 *db = nullptr; + EXPECT_EQ(GetDB(db), SQLITE_OK); + InsertValue(db, dataMap); + sqlite3_close(db); + } + + void PrepareBasicTable(const std::string &tableName, std::vector &fieldInfoList, + std::vector &remoteDeviceVec, bool createDistributedTable = true) + { + sqlite3 *db = nullptr; + EXPECT_EQ(GetDB(db), SQLITE_OK); + if (fieldInfoList.empty()) { + InsertFieldInfo(fieldInfoList); + } + for (auto &dev : remoteDeviceVec) { + dev->SetLocalFieldInfo(fieldInfoList); + } + EXPECT_EQ(CreateTable(db, fieldInfoList, tableName), SQLITE_OK); + TableInfo tableInfo; + SQLiteUtils::AnalysisSchema(db, tableName, tableInfo); + for (auto &dev : remoteDeviceVec) { + dev->SetTableInfo(tableInfo); + } + if (createDistributedTable) { + EXPECT_EQ(g_rdbDelegatePtr->CreateDistributedTable(tableName), OK); + } + + sqlite3_close(db); + } + + void PrepareEnvironment(std::map &dataMap, + std::vector remoteDeviceVec) + { + PrepareBasicTable(g_tableName, g_fieldInfoList, remoteDeviceVec); + GenerateValue(dataMap, g_fieldInfoList); + InsertValueToDB(dataMap); + } + + void PrepareVirtualEnvironment(std::map &dataMap, const std::string &tableName, + std::vector &fieldInfoList, std::vector remoteDeviceVec, + bool createDistributedTable = true) + { + PrepareBasicTable(tableName, fieldInfoList, remoteDeviceVec, createDistributedTable); + GenerateValue(dataMap, fieldInfoList); + VirtualRowData virtualRowData; + for (const auto &item : dataMap) { + virtualRowData.objectData.PutDataValue(item.first, item.second); + } + virtualRowData.logInfo.timestamp = 1; + g_deviceB->PutData(tableName, {virtualRowData}); + } + + void PrepareVirtualEnvironment(std::map &dataMap, + std::vector remoteDeviceVec, bool createDistributedTable = true) + { + PrepareVirtualEnvironment(dataMap, g_tableName, g_fieldInfoList, remoteDeviceVec, createDistributedTable); + } + + void CheckData(std::map &targetMap, const std::string &tableName, + std::vector fieldInfoList) + { + std::map dataMap; + sqlite3 *db = nullptr; + EXPECT_EQ(GetDB(db), SQLITE_OK); + GetSyncData(db, dataMap, tableName, fieldInfoList); + sqlite3_close(db); + + for (const auto &[fieldName, dataValue] : targetMap) { + ASSERT_TRUE(dataMap.find(fieldName) != dataMap.end()); + EXPECT_TRUE(dataMap[fieldName] == dataValue); + } + } + + void CheckData(std::map &targetMap) + { + CheckData(targetMap, g_tableName, g_fieldInfoList); + } + + void CheckVirtualData(const std::string &tableName, std::map &data) + { + std::vector targetData; + g_deviceB->GetAllSyncData(tableName, targetData); + + ASSERT_EQ(targetData.size(), 1u); + for (auto &[field, value] : data) { + DataValue target; + EXPECT_EQ(targetData[0].objectData.GetDataValue(field, target), E_OK); + LOGD("field %s actual_val[%s] except_val[%s]", field.c_str(), target.ToString().c_str(), + value.ToString().c_str()); + EXPECT_TRUE(target == value); + } + } + + void CheckVirtualData(std::map &data) + { + CheckVirtualData(g_tableName, data); + } + + void GetFieldInfo(std::vector &fieldInfoList, std::vector typeList) + { + fieldInfoList.clear(); + for (size_t index = 0; index < typeList.size(); index++) { + const auto &type = typeList[index]; + FieldInfo fieldInfo; + fieldInfo.SetFieldName("field_" + std::to_string(index)); + fieldInfo.SetColumnId(index); + fieldInfo.SetStorageType(type); + fieldInfoList.push_back(fieldInfo); + } + } + + void InsertValueToDB(std::map &dataMap, + std::vector fieldInfoList, const std::string &tableName) + { + sqlite3 *db = nullptr; + EXPECT_EQ(GetDB(db), SQLITE_OK); + InsertValue(db, dataMap, fieldInfoList, tableName); + sqlite3_close(db); + } + + void PrepareEnvironment(std::map &dataMap, const std::string &tableName, + std::vector &localFieldInfoList, std::vector &remoteFieldInfoList, + std::vector remoteDeviceVec) + { + sqlite3 *db = nullptr; + EXPECT_EQ(GetDB(db), SQLITE_OK); + + EXPECT_EQ(CreateTable(db, remoteFieldInfoList, tableName), SQLITE_OK); + TableInfo tableInfo; + SQLiteUtils::AnalysisSchema(db, tableName, tableInfo); + for (auto &dev : remoteDeviceVec) { + dev->SetTableInfo(tableInfo); + } + + EXPECT_EQ(DropTable(db, tableName), SQLITE_OK); + EXPECT_EQ(CreateTable(db, localFieldInfoList, tableName), SQLITE_OK); + EXPECT_EQ(g_rdbDelegatePtr->CreateDistributedTable(tableName), OK); + + sqlite3_close(db); + + GenerateValue(dataMap, localFieldInfoList); + InsertValueToDB(dataMap, localFieldInfoList, tableName); + for (auto &dev : remoteDeviceVec) { + dev->SetLocalFieldInfo(remoteFieldInfoList); + } + } + + void PrepareEnvironment(std::map &dataMap, + std::vector &localFieldInfoList, std::vector &remoteFieldInfoList, + std::vector remoteDeviceVec) + { + PrepareEnvironment(dataMap, g_tableName, localFieldInfoList, remoteFieldInfoList, remoteDeviceVec); + } + + void CheckIdentify(RelationalStoreObserverUnitTest *observer) + { + ASSERT_NE(observer, nullptr); + StoreProperty property = observer->GetStoreProperty(); + EXPECT_EQ(property.appId, APP_ID); + EXPECT_EQ(property.storeId, STORE_ID_1); + EXPECT_EQ(property.userId, USER_ID); + } +} + +class DistributedDBRelationalVerP2PSyncTest : public testing::Test { +public: + static void SetUpTestCase(); + static void TearDownTestCase(); + void SetUp(); + void TearDown(); +}; + +void DistributedDBRelationalVerP2PSyncTest::SetUpTestCase() +{ + /** + * @tc.setup: Init datadir and Virtual Communicator. + */ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_dbDir = g_testDir + "/test.db"; + sqlite3 *db = nullptr; + ASSERT_EQ(GetDB(db), SQLITE_OK); + sqlite3_close(db); + + g_communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator(); + ASSERT_TRUE(g_communicatorAggregator != nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(g_communicatorAggregator); + + g_id = g_mgr.GetRelationalStoreIdentifier(USER_ID, APP_ID, STORE_ID_1); +} + +void DistributedDBRelationalVerP2PSyncTest::TearDownTestCase() +{ + /** + * @tc.teardown: Release virtual Communicator and clear data dir. + */ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); + LOGD("TearDownTestCase FINISH"); +} + +void DistributedDBRelationalVerP2PSyncTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + g_fieldInfoList.clear(); + /** + * @tc.setup: create virtual device B, and get a KvStoreNbDelegate as deviceA + */ + sqlite3 *db = nullptr; + ASSERT_EQ(GetDB(db), SQLITE_OK); + sqlite3_close(db); + OpenStore(); + g_deviceB = new (std::nothrow) RelationalVirtualDevice(DEVICE_B); + ASSERT_TRUE(g_deviceB != nullptr); + g_deviceC = new (std::nothrow) RelationalVirtualDevice(DEVICE_C); + ASSERT_TRUE(g_deviceC != nullptr); + auto *syncInterfaceB = new (std::nothrow) VirtualRelationalVerSyncDBInterface(); + auto *syncInterfaceC = new (std::nothrow) VirtualRelationalVerSyncDBInterface(); + ASSERT_TRUE(syncInterfaceB != nullptr); + ASSERT_EQ(g_deviceB->Initialize(g_communicatorAggregator, syncInterfaceB), E_OK); + ASSERT_EQ(g_deviceC->Initialize(g_communicatorAggregator, syncInterfaceC), E_OK); + + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + return true; + }; + EXPECT_EQ(RuntimeConfig::SetPermissionCheckCallback(permissionCheckCallback), OK); +} + +void DistributedDBRelationalVerP2PSyncTest::TearDown(void) +{ + /** + * @tc.teardown: Release device A, B, C + */ + if (g_rdbDelegatePtr != nullptr) { + LOGD("CloseStore Start"); + ASSERT_EQ(g_mgr.CloseStore(g_rdbDelegatePtr), OK); + g_rdbDelegatePtr = nullptr; + } + if (g_deviceB != nullptr) { + delete g_deviceB; + g_deviceB = nullptr; + } + if (g_deviceC != nullptr) { + delete g_deviceC; + g_deviceC = nullptr; + } + if (g_observer != nullptr) { + delete g_observer; + g_observer = nullptr; + } + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(RuntimeConfig::SetPermissionCheckCallback(nullCallback), OK); + EXPECT_EQ(DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir), OK); + LOGD("TearDown FINISH"); +} + +/** +* @tc.name: Normal Sync 001 +* @tc.desc: Test normal push sync for add data. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, NormalSync001, TestSize.Level0) +{ + std::map dataMap; + PrepareEnvironment(dataMap, {g_deviceB}); + BlockSync(SyncMode::SYNC_MODE_PUSH_ONLY, OK, {DEVICE_B}); + + CheckVirtualData(dataMap); +} + +/** +* @tc.name: Normal Sync 002 +* @tc.desc: Test normal pull sync for add data. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, NormalSync002, TestSize.Level0) +{ + std::map dataMap; + PrepareEnvironment(dataMap, {g_deviceB}); + + Query query = Query::Select(g_tableName); + g_deviceB->GenericVirtualDevice::Sync(DistributedDB::SYNC_MODE_PULL_ONLY, query, true); + + CheckVirtualData(dataMap); +} + +/** +* @tc.name: Normal Sync 003 +* @tc.desc: Test normal push sync for update data. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, NormalSync003, TestSize.Level1) +{ + std::map dataMap; + PrepareEnvironment(dataMap, {g_deviceB}); + + BlockSync(SyncMode::SYNC_MODE_PUSH_ONLY, OK, {DEVICE_B}); + + CheckVirtualData(dataMap); + + GenerateValue(dataMap, g_fieldInfoList); + dataMap["AGE"] = static_cast(1); + InsertValueToDB(dataMap); + BlockSync(SyncMode::SYNC_MODE_PUSH_ONLY, OK, {DEVICE_B}); + + CheckVirtualData(dataMap); +} + +/** +* @tc.name: Normal Sync 004 +* @tc.desc: Test normal push sync for delete data. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, NormalSync004, TestSize.Level1) +{ + std::map dataMap; + PrepareEnvironment(dataMap, {g_deviceB}); + + BlockSync(SyncMode::SYNC_MODE_PUSH_ONLY, OK, {DEVICE_B}); + + CheckVirtualData(dataMap); + + sqlite3 *db = nullptr; + EXPECT_EQ(GetDB(db), SQLITE_OK); + std::string sql = "DELETE FROM TEST_TABLE WHERE 1 = 1"; + EXPECT_EQ(SQLiteUtils::ExecuteRawSQL(db, sql), E_OK); + sqlite3_close(db); + + BlockSync(SyncMode::SYNC_MODE_PUSH_ONLY, OK, {DEVICE_B}); + + std::vector dataList; + EXPECT_EQ(g_deviceB->GetAllSyncData(g_tableName, dataList), E_OK); + EXPECT_EQ(static_cast(dataList.size()), 1); + for (const auto &item : dataList) { + EXPECT_EQ(item.logInfo.flag, DataItem::DELETE_FLAG); + } +} + +/** +* @tc.name: Normal Sync 005 +* @tc.desc: Test normal push sync for add data. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, NormalSync005, TestSize.Level0) +{ + std::map dataMap; + PrepareVirtualEnvironment(dataMap, {g_deviceB}); + + Query query = Query::Select(g_tableName); + g_deviceB->GenericVirtualDevice::Sync(SYNC_MODE_PUSH_ONLY, query, true); + + CheckData(dataMap); + + g_rdbDelegatePtr->RemoveDeviceData(DEVICE_B); + + g_deviceB->GenericVirtualDevice::Sync(SYNC_MODE_PUSH_ONLY, query, true); + + CheckData(dataMap); +} + +/** +* @tc.name: Normal Sync 007 +* @tc.desc: Test normal sync for miss query data. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, NormalSync007, TestSize.Level1) +{ + std::map dataMap; + PrepareEnvironment(dataMap, {g_deviceB}); + + Query query = Query::Select(g_tableName).EqualTo("NAME", DEFAULT_TEXT); + BlockSync(query, SyncMode::SYNC_MODE_PUSH_ONLY, OK, {DEVICE_B}); + + CheckVirtualData(dataMap); + + sqlite3 *db = nullptr; + EXPECT_EQ(GetDB(db), SQLITE_OK); + std::string sql = "UPDATE TEST_TABLE SET NAME = '' WHERE 1 = 1"; + EXPECT_EQ(SQLiteUtils::ExecuteRawSQL(db, sql), E_OK); + sqlite3_close(db); + + BlockSync(query, SyncMode::SYNC_MODE_PUSH_ONLY, OK, {DEVICE_B}); + + std::vector dataList; + EXPECT_EQ(g_deviceB->GetAllSyncData(g_tableName, dataList), E_OK); + EXPECT_EQ(static_cast(dataList.size()), 1); + for (const auto &item : dataList) { + EXPECT_EQ(item.logInfo.flag, DataItem::REMOTE_DEVICE_DATA_MISS_QUERY); + } +} + +/** +* @tc.name: Normal Sync 006 +* @tc.desc: Test normal pull sync for add data. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, NormalSync006, TestSize.Level1) +{ + std::map dataMap; + PrepareVirtualEnvironment(dataMap, {g_deviceB}); + + BlockSync(SYNC_MODE_PULL_ONLY, OK, {DEVICE_B}); + + CheckData(dataMap); +} + +/** +* @tc.name: AutoLaunchSync 001 +* @tc.desc: Test rdb autoLaunch success when callback return true. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, AutoLaunchSync001, TestSize.Level3) +{ + /** + * @tc.steps: step1. open rdb store, create distribute table and insert data + */ + std::map dataMap; + PrepareVirtualEnvironment(dataMap, {g_deviceB}); + + /** + * @tc.steps: step2. set auto launch callBack + */ + const AutoLaunchRequestCallback callback = [](const std::string &identifier, AutoLaunchParam ¶m) { + if (g_id != identifier) { + return false; + } + param.path = g_dbDir; + param.appId = APP_ID; + param.userId = USER_ID; + param.storeId = STORE_ID_1; + return true; + }; + g_mgr.SetAutoLaunchRequestCallback(callback); + /** + * @tc.steps: step3. close store ensure communicator has closed + */ + g_mgr.CloseStore(g_rdbDelegatePtr); + g_rdbDelegatePtr = nullptr; + /** + * @tc.steps: step4. RunCommunicatorLackCallback to autolaunch store + */ + LabelType labelType(g_id.begin(), g_id.end()); + g_communicatorAggregator->RunCommunicatorLackCallback(labelType); + std::this_thread::sleep_for(std::chrono::seconds(1)); + /** + * @tc.steps: step5. Call sync expect sync successful + */ + Query query = Query::Select(g_tableName); + EXPECT_EQ(g_deviceB->GenericVirtualDevice::Sync(SYNC_MODE_PUSH_ONLY, query, true), E_OK); + /** + * @tc.steps: step6. check sync data ensure sync successful + */ + CheckData(dataMap); + + OpenStore(); + std::this_thread::sleep_for(std::chrono::minutes(1)); +} + +/** +* @tc.name: AutoLaunchSync 002 +* @tc.desc: Test rdb autoLaunch failed when callback return false. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, AutoLaunchSync002, TestSize.Level3) +{ + /** + * @tc.steps: step1. open rdb store, create distribute table and insert data + */ + std::map dataMap; + PrepareVirtualEnvironment(dataMap, {g_deviceB}); + + /** + * @tc.steps: step2. set auto launch callBack + */ + const AutoLaunchRequestCallback callback = [](const std::string &identifier, AutoLaunchParam ¶m) { + return false; + }; + g_mgr.SetAutoLaunchRequestCallback(callback); + /** + * @tc.steps: step2. close store ensure communicator has closed + */ + g_mgr.CloseStore(g_rdbDelegatePtr); + g_rdbDelegatePtr = nullptr; + /** + * @tc.steps: step3. store can't autoLaunch because callback return false + */ + LabelType labelType(g_id.begin(), g_id.end()); + g_communicatorAggregator->RunCommunicatorLackCallback(labelType); + std::this_thread::sleep_for(std::chrono::seconds(1)); + /** + * @tc.steps: step4. Call sync expect sync fail + */ + Query query = Query::Select(g_tableName); + SyncOperation::UserCallback callBack = [](const std::map &statusMap) { + for (const auto &entry : statusMap) { + EXPECT_EQ(entry.second, static_cast(SyncOperation::OP_COMM_ABNORMAL)); + } + }; + EXPECT_EQ(g_deviceB->GenericVirtualDevice::Sync(SYNC_MODE_PUSH_ONLY, query, callBack, true), E_OK); + + OpenStore(); + std::this_thread::sleep_for(std::chrono::minutes(1)); +} + +/** +* @tc.name: AutoLaunchSync 003 +* @tc.desc: Test rdb autoLaunch failed when callback is nullptr. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, AutoLaunchSync003, TestSize.Level3) +{ + /** + * @tc.steps: step1. open rdb store, create distribute table and insert data + */ + std::map dataMap; + PrepareVirtualEnvironment(dataMap, {g_deviceB}); + + g_mgr.SetAutoLaunchRequestCallback(nullptr); + /** + * @tc.steps: step2. close store ensure communicator has closed + */ + g_mgr.CloseStore(g_rdbDelegatePtr); + g_rdbDelegatePtr = nullptr; + /** + * @tc.steps: step3. store can't autoLaunch because callback is nullptr + */ + LabelType labelType(g_id.begin(), g_id.end()); + g_communicatorAggregator->RunCommunicatorLackCallback(labelType); + std::this_thread::sleep_for(std::chrono::seconds(1)); + /** + * @tc.steps: step4. Call sync expect sync fail + */ + Query query = Query::Select(g_tableName); + SyncOperation::UserCallback callBack = [](const std::map &statusMap) { + for (const auto &entry : statusMap) { + EXPECT_EQ(entry.second, static_cast(SyncOperation::OP_COMM_ABNORMAL)); + } + }; + EXPECT_EQ(g_deviceB->GenericVirtualDevice::Sync(SYNC_MODE_PUSH_ONLY, query, callBack, true), E_OK); + + OpenStore(); + std::this_thread::sleep_for(std::chrono::minutes(1)); +} + +/** +* @tc.name: Ability Sync 001 +* @tc.desc: Test ability sync success when has same schema. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, AbilitySync001, TestSize.Level1) +{ + std::map dataMap; + std::vector localFieldInfo; + GetFieldInfo(localFieldInfo, g_storageType); + + PrepareEnvironment(dataMap, localFieldInfo, localFieldInfo, {g_deviceB}); + BlockSync(SyncMode::SYNC_MODE_PUSH_ONLY, OK, {DEVICE_B}); + + CheckVirtualData(dataMap); +} + +/** +* @tc.name: Ability Sync 002 +* @tc.desc: Test ability sync failed when has different schema. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, AbilitySync002, TestSize.Level1) +{ + /** + * @tc.steps: step1. set local schema is (BOOL, INTEGER, REAL, TEXT, BLOB, INTEGER) + */ + std::map dataMap; + std::vector localFieldInfo; + std::vector localStorageType = g_storageType; + localStorageType.push_back(StorageType::STORAGE_TYPE_INTEGER); + GetFieldInfo(localFieldInfo, localStorageType); + + /** + * @tc.steps: step2. set remote schema is (BOOL, INTEGER, REAL, TEXT, BLOB, TEXT) + */ + std::vector remoteFieldInfo; + std::vector remoteStorageType = g_storageType; + remoteStorageType.push_back(StorageType::STORAGE_TYPE_TEXT); + GetFieldInfo(remoteFieldInfo, remoteStorageType); + + /** + * @tc.steps: step3. call sync + * @tc.expected: sync fail when abilitySync + */ + PrepareEnvironment(dataMap, localFieldInfo, remoteFieldInfo, {g_deviceB}); + BlockSync(SyncMode::SYNC_MODE_PUSH_ONLY, SCHEMA_MISMATCH, {DEVICE_B}); +} + +/** +* @tc.name: Ability Sync 003 +* @tc.desc: Test ability sync failed when has different schema. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, AbilitySync003, TestSize.Level1) +{ + /** + * @tc.steps: step1. set local and remote schema is (BOOL, INTEGER, REAL, TEXT, BLOB) + */ + std::map dataMap; + std::vector schema; + std::vector localStorageType = g_storageType; + GetFieldInfo(schema, localStorageType); + + /** + * @tc.steps: step2. create table and insert data + */ + PrepareEnvironment(dataMap, schema, schema, {g_deviceB}); + + /** + * @tc.steps: step3. change local table to (BOOL, INTEGER, REAL, TEXT, BLOB) + * @tc.expected: sync fail + */ + g_communicatorAggregator->RegOnDispatch([](const std::string &target, Message *inMsg) { + if (target != "real_device") { + return; + } + if (inMsg->GetMessageType() != TYPE_NOTIFY || inMsg->GetMessageId() != ABILITY_SYNC_MESSAGE) { + return; + } + sqlite3 *db = nullptr; + EXPECT_EQ(GetDB(db), SQLITE_OK); + ASSERT_NE(db, nullptr); + std::string alterSql = "ALTER TABLE " + g_tableName + " ADD COLUMN NEW_COLUMN TEXT DEFAULT 'DEFAULT_TEXT'"; + EXPECT_EQ(sqlite3_exec(db, alterSql.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + EXPECT_EQ(sqlite3_close(db), SQLITE_OK); + EXPECT_EQ(g_rdbDelegatePtr->CreateDistributedTable(g_tableName), OK); + }); + + BlockSync(SyncMode::SYNC_MODE_PUSH_ONLY, OK, {DEVICE_B}); + + g_communicatorAggregator->RegOnDispatch(nullptr); +} + +/** +* @tc.name: Ability Sync 004 +* @tc.desc: Test ability sync failed when one device hasn't distributed table. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, AbilitySync004, TestSize.Level1) +{ + std::map dataMap; + PrepareVirtualEnvironment(dataMap, {g_deviceB}, false); + + Query query = Query::Select(g_tableName); + int res = DB_ERROR; + auto callBack = [&res](std::map resMap) { + if (resMap.find("real_device") != resMap.end()) { + res = resMap["real_device"]; + } + }; + EXPECT_EQ(g_deviceB->GenericVirtualDevice::Sync(DistributedDB::SYNC_MODE_PULL_ONLY, query, callBack, true), E_OK); + EXPECT_EQ(res, static_cast(SyncOperation::Status::OP_SCHEMA_INCOMPATIBLE)); +} + +/** +* @tc.name: WaterMark 001 +* @tc.desc: Test sync success after erase waterMark. +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, WaterMark001, TestSize.Level1) +{ + std::map dataMap; + PrepareEnvironment(dataMap, {g_deviceB}); + BlockSync(SyncMode::SYNC_MODE_PUSH_ONLY, OK, {DEVICE_B}); + + CheckVirtualData(dataMap); + + EXPECT_EQ(g_rdbDelegatePtr->RemoveDeviceData(g_deviceB->GetDeviceId(), g_tableName), OK); + g_deviceB->EraseSyncData(g_tableName); + + BlockSync(SyncMode::SYNC_MODE_PUSH_ONLY, OK, {DEVICE_B}); + + CheckVirtualData(dataMap); +} + +/* +* @tc.name: pressure sync 001 +* @tc.desc: Test rdb sync different table at same time +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhangqiquan +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, PressureSync001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create table A and device A push data to device B + * @tc.expected: step1. all is ok + */ + std::map tableADataMap; + std::vector tableAFieldInfo; + std::vector localStorageType = g_storageType; + localStorageType.push_back(StorageType::STORAGE_TYPE_INTEGER); + GetFieldInfo(tableAFieldInfo, localStorageType); + const std::string tableNameA = "TABLE_A"; + PrepareEnvironment(tableADataMap, tableNameA, tableAFieldInfo, tableAFieldInfo, {g_deviceB}); + + /** + * @tc.steps: step2. create table B and device B push data to device A + * @tc.expected: step2. all is ok + */ + std::map tableBDataMap; + std::vector tableBFieldInfo; + localStorageType = g_storageType; + localStorageType.push_back(StorageType::STORAGE_TYPE_REAL); + GetFieldInfo(tableBFieldInfo, localStorageType); + const std::string tableNameB = "TABLE_B"; + PrepareVirtualEnvironment(tableBDataMap, tableNameB, tableBFieldInfo, {g_deviceB}); + + std::condition_variable cv; + bool subFinish = false; + std::thread subThread = std::thread([&subFinish, &cv, &tableNameA, &tableADataMap]() { + BlockSync(tableNameA, SyncMode::SYNC_MODE_PUSH_ONLY, OK, {DEVICE_B}); + + CheckVirtualData(tableNameA, tableADataMap); + subFinish = true; + cv.notify_all(); + }); + subThread.detach(); + + Query query = Query::Select(tableNameB); + g_deviceB->GenericVirtualDevice::Sync(SYNC_MODE_PUSH_ONLY, query, true); + CheckData(tableBDataMap, tableNameB, tableBFieldInfo); + + std::mutex mutex; + std::unique_lock lock(mutex); + cv.wait(lock, [&subFinish] { return subFinish; }); +} + +/* +* @tc.name: relation observer 001 +* @tc.desc: Test relation observer while normal pull sync +* @tc.type: FUNC +* @tc.require: +* @tc.author: zhuwentao +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, Observer001, TestSize.Level0) +{ + /** + * @tc.steps: step1. device A create table and device B insert data and device C don't insert data + * @tc.expected: step1. create and insert ok + */ + g_observer->ResetToZero(); + std::map dataMap; + PrepareVirtualEnvironment(dataMap, {g_deviceB, g_deviceC}); + /** + * @tc.steps: step2. device A pull sync mode + * @tc.expected: step2. sync ok + */ + BlockSync(SyncMode::SYNC_MODE_PULL_ONLY, OK, {DEVICE_B, DEVICE_C}); + /** + * @tc.steps: step3. device A check observer + * @tc.expected: step2. data change device is deviceB + */ + EXPECT_EQ(g_observer->GetCallCount(), 1u); + EXPECT_EQ(g_observer->GetDataChangeDevice(), DEVICE_B); + CheckIdentify(g_observer); +} + +/** +* @tc.name: relation observer 002 +* @tc.desc: Test rdb observer ok in autolauchCallback scene +* @tc.type: FUNC +* @tc.require: AR000GK58N +* @tc.author: zhuwentao +*/ +HWTEST_F(DistributedDBRelationalVerP2PSyncTest, observer002, TestSize.Level3) +{ + /** + * @tc.steps: step1. open rdb store, create distribute table and insert data + */ + g_observer->ResetToZero(); + std::map dataMap; + PrepareVirtualEnvironment(dataMap, {g_deviceB}); + + /** + * @tc.steps: step2. set auto launch callBack + */ + RelationalStoreObserverUnitTest *observer = new (std::nothrow) RelationalStoreObserverUnitTest(); + const AutoLaunchRequestCallback callback = [observer](const std::string &identifier, AutoLaunchParam ¶m) { + if (g_id != identifier) { + return false; + } + param.path = g_dbDir; + param.appId = APP_ID; + param.userId = USER_ID; + param.storeId = STORE_ID_1; + param.option.storeObserver = observer; + return true; + }; + g_mgr.SetAutoLaunchRequestCallback(callback); + /** + * @tc.steps: step3. close store ensure communicator has closed + */ + g_mgr.CloseStore(g_rdbDelegatePtr); + g_rdbDelegatePtr = nullptr; + /** + * @tc.steps: step4. RunCommunicatorLackCallback to autolaunch store + */ + LabelType labelType(g_id.begin(), g_id.end()); + g_communicatorAggregator->RunCommunicatorLackCallback(labelType); + std::this_thread::sleep_for(std::chrono::seconds(1)); + /** + * @tc.steps: step5. Call sync expect sync successful and device A check observer + */ + Query query = Query::Select(g_tableName); + EXPECT_EQ(g_deviceB->GenericVirtualDevice::Sync(SYNC_MODE_PUSH_ONLY, query, true), E_OK); + EXPECT_EQ(observer->GetCallCount(), 1u); + EXPECT_EQ(observer->GetDataChangeDevice(), DEVICE_B); + CheckIdentify(observer); + std::this_thread::sleep_for(std::chrono::minutes(1)); + delete observer; +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_msg_schedule_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_msg_schedule_test.cpp new file mode 100644 index 00000000..9df55d1b --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_msg_schedule_test.cpp @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_tools_unit_test.h" +#include "single_ver_data_message_schedule.h" +#include "single_ver_data_packet.h" +#include "single_ver_kv_sync_task_context.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { +} + +class DistributedDBSingleVerMsgScheduleTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBSingleVerMsgScheduleTest::SetUpTestCase(void) +{ +} + +void DistributedDBSingleVerMsgScheduleTest::TearDownTestCase(void) +{ +} + +void DistributedDBSingleVerMsgScheduleTest::SetUp(void) +{ +} + +void DistributedDBSingleVerMsgScheduleTest::TearDown(void) +{ +} + +/** + * @tc.name: MsgSchedule001 + * @tc.desc: Test MsgSchedule function with normal sequenceId + * @tc.type: FUNC + * @tc.require: + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMsgScheduleTest, MsgSchedule001, TestSize.Level0) +{ + /** + * @tc.steps: step1. put msg sequence_3, sequence_2, sequence_1 + * @tc.expected: put msg ok + */ + SingleVerDataMessageSchedule msgSchedule; + auto *context = new SingleVerKvSyncTaskContext(); + context->SetRemoteSoftwareVersion(SOFTWARE_VERSION_CURRENT); + DataSyncMessageInfo info; + info.sessionId_ = 10; + bool isNeedHandle = true; + bool isNeedContinue = true; + for (uint32_t i = 3; i >= 1; i--) { + info.sequenceId_ = i; + info.packetId_ = i; + DistributedDB::Message *message = nullptr; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + if (i > 1) { + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg == nullptr); + } + } + /** + * @tc.steps: step2. get msg + * @tc.expected: get msg by sequence_1, sequence_2, sequence_3 + */ + for (uint32_t i = 1; i <= 3; i++) { + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg != nullptr); + EXPECT_EQ(isNeedContinue, true); + EXPECT_EQ(isNeedHandle, true); + EXPECT_EQ(msg->GetSequenceId(), i); + msgSchedule.ScheduleInfoHandle(isNeedHandle, false, msg); + delete msg; + } + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg == nullptr); + RefObject::KillAndDecObjRef(context); + context = nullptr; +} + +/** + * @tc.name: MsgSchedule002 + * @tc.desc: Test MsgSchedule function with by low version + * @tc.type: FUNC + * @tc.require: + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMsgScheduleTest, MsgSchedule002, TestSize.Level0) +{ + /** + * @tc.steps: step1. put msg session1_sequence1, session2_sequence1 + * @tc.expected: put msg ok + */ + SingleVerDataMessageSchedule msgSchedule; + auto *context = new SingleVerKvSyncTaskContext(); + context->SetRemoteSoftwareVersion(SOFTWARE_VERSION_RELEASE_2_0); + DataSyncMessageInfo info; + bool isNeedHandle = true; + bool isNeedContinue = true; + for (uint32_t i = 1; i <= 2; i++) { + info.sessionId_ = i; + info.sequenceId_ = 1; + DistributedDB::Message *message = nullptr; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + } + /** + * @tc.steps: step2. get msg + * @tc.expected: get msg by session2_sequence1 + */ + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg != nullptr); + EXPECT_EQ(isNeedContinue, true); + EXPECT_EQ(isNeedHandle, true); + EXPECT_EQ(msg->GetSequenceId(), 1u); + msgSchedule.ScheduleInfoHandle(false, false, msg); + delete msg; + msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg == nullptr); + RefObject::KillAndDecObjRef(context); + context = nullptr; +} + +/** + * @tc.name: MsgSchedule003 + * @tc.desc: Test MsgSchedule function with cross sessionId + * @tc.type: FUNC + * @tc.require: + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMsgScheduleTest, MsgSchedule003, TestSize.Level0) +{ + /** + * @tc.steps: step1. put msg session1_seq1, session2_seq1, session1_seq2, session2_seq2, and handle session1_seq1 + * @tc.expected: handle ok + */ + SingleVerDataMessageSchedule msgSchedule; + auto *context = new SingleVerKvSyncTaskContext(); + context->SetRemoteSoftwareVersion(SOFTWARE_VERSION_CURRENT); + DataSyncMessageInfo info; + bool isNeedHandle = true; + bool isNeedContinue = true; + info.sessionId_ = 1; + info.sequenceId_ = 1; + info.packetId_ = 1; + DistributedDB::Message *message = nullptr; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg != nullptr); + EXPECT_EQ(isNeedContinue, true); + EXPECT_EQ(isNeedHandle, true); + msgSchedule.ScheduleInfoHandle(isNeedHandle, false, msg); + delete msg; + info.sessionId_ = 2; + info.sequenceId_ = 1; + info.packetId_ = 1; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + info.sessionId_ = 1; + info.sequenceId_ = 2; + info.packetId_ = 2; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + info.sessionId_ = 2; + info.sequenceId_ = 2; + info.packetId_ = 2; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + + /** + * @tc.steps: step2. get msg + * @tc.expected: get msg by session2_seq1, session2_seq2 + */ + for (uint32_t i = 1; i <= 2; i++) { + msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg != nullptr); + EXPECT_EQ(isNeedContinue, true); + EXPECT_EQ(isNeedHandle, true); + EXPECT_EQ(msg->GetSequenceId(), i); + EXPECT_EQ(msg->GetSessionId(), 2u); + msgSchedule.ScheduleInfoHandle(isNeedHandle, false, msg); + delete msg; + } + msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg == nullptr); + RefObject::KillAndDecObjRef(context); + context = nullptr; +} + +/** + * @tc.name: MsgSchedule004 + * @tc.desc: Test MsgSchedule function with same sessionId with different packetId + * @tc.type: FUNC + * @tc.require: + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMsgScheduleTest, MsgSchedule004, TestSize.Level0) +{ + /** + * @tc.steps: step1. put msg seq2_packet2, seq3_packet3, seq1_packet4, seq2_packet5, seq3_packet6 + * @tc.expected: put msg ok + */ + SingleVerDataMessageSchedule msgSchedule; + auto *context = new SingleVerKvSyncTaskContext(); + context->SetRemoteSoftwareVersion(SOFTWARE_VERSION_CURRENT); + DataSyncMessageInfo info; + info.sessionId_ = 10; + bool isNeedHandle = true; + bool isNeedContinue = true; + for (uint32_t i = 2; i <= 3; i++) { + info.sequenceId_ = i; + info.packetId_ = i; + DistributedDB::Message *message = nullptr; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg == nullptr); + } + for (uint32_t i = 1; i <= 3; i++) { + info.sequenceId_ = i; + info.packetId_ = i + 3; + DistributedDB::Message *message = nullptr; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + } + /** + * @tc.steps: step2. get msg + * @tc.expected: drop seq2_packet2, seq3_packet3 and get seq1_packet4, seq2_packet5, seq3_packet6 + */ + isNeedHandle = true; + isNeedContinue = true; + for (uint32_t i = 1; i <= 3; i++) { + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg != nullptr); + EXPECT_EQ(isNeedContinue, true); + EXPECT_EQ(isNeedHandle, true); + EXPECT_EQ(msg->GetSequenceId(), i); + const DataRequestPacket *packet = msg->GetObject(); + EXPECT_EQ(packet->GetPacketId(), i + 3); + msgSchedule.ScheduleInfoHandle(isNeedHandle, false, msg); + delete msg; + } + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg == nullptr); + RefObject::KillAndDecObjRef(context); + context = nullptr; +} + +/** + * @tc.name: MsgSchedule005 + * @tc.desc: Test MsgSchedule function with same sessionId with different packetId + * @tc.type: FUNC + * @tc.require: + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMsgScheduleTest, MsgSchedule005, TestSize.Level0) +{ + /** + * @tc.steps: step1. put msg seq1_packet4, seq2_packet5, seq2_packet2, seq3_packet3 + * @tc.expected: put msg ok + */ + SingleVerDataMessageSchedule msgSchedule; + auto *context = new SingleVerKvSyncTaskContext(); + context->SetRemoteSoftwareVersion(SOFTWARE_VERSION_CURRENT); + DataSyncMessageInfo info; + info.sessionId_ = 10; + bool isNeedHandle = true; + bool isNeedContinue = true; + for (uint32_t i = 1; i <= 2; i++) { + info.sequenceId_ = i; + info.packetId_ = i + 3; + DistributedDB::Message *message = nullptr; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + } + for (uint32_t i = 2; i <= 3; i++) { + info.sequenceId_ = i; + info.packetId_ = i; + DistributedDB::Message *message = nullptr; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + } + /** + * @tc.steps: step2. get msg + * @tc.expected: drop seq2_packet2, seq3_packet3 and get seq1_packet4, seq2_packet5 + */ + isNeedHandle = true; + isNeedContinue = true; + for (uint32_t i = 1; i <= 2; i++) { + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg != nullptr); + EXPECT_EQ(isNeedContinue, true); + EXPECT_EQ(isNeedHandle, true); + EXPECT_EQ(msg->GetSequenceId(), i); + const DataRequestPacket *packet = msg->GetObject(); + EXPECT_EQ(packet->GetPacketId(), i + 3); + msgSchedule.ScheduleInfoHandle(isNeedHandle, false, msg); + delete msg; + } + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg == nullptr); + RefObject::KillAndDecObjRef(context); + context = nullptr; +} + +/** + * @tc.name: MsgSchedule006 + * @tc.desc: Test MsgSchedule function with same sessionId and same sequenceId and packetId + * @tc.type: FUNC + * @tc.require: + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMsgScheduleTest, MsgSchedule006, TestSize.Level0) +{ + /** + * @tc.steps: step1. put msg seq1_packet1, seq2_packet2 + * @tc.expected: put msg ok + */ + SingleVerDataMessageSchedule msgSchedule; + auto *context = new SingleVerKvSyncTaskContext(); + context->SetRemoteSoftwareVersion(SOFTWARE_VERSION_CURRENT); + DataSyncMessageInfo info; + info.sessionId_ = 10; + bool isNeedHandle = true; + bool isNeedContinue = true; + for (uint32_t i = 1; i <= 2; i++) { + info.sequenceId_ = i; + info.packetId_ = i; + DistributedDB::Message *message = nullptr; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + } + for (uint32_t i = 1; i <= 2; i++) { + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg != nullptr); + EXPECT_EQ(isNeedContinue, true); + EXPECT_EQ(isNeedHandle, true); + EXPECT_EQ(msg->GetSequenceId(), i); + msgSchedule.ScheduleInfoHandle(isNeedHandle, false, msg); + delete msg; + } + /** + * @tc.steps: step2. put msg seq1_packet1, seq2_packet2 again and seq3_packet3 + * @tc.expected: get msg ok and get seq1_packet1, seq2_packet2 and seq3_packet3 + */ + for (uint32_t i = 1; i <= 3; i++) { + info.sequenceId_ = i; + info.packetId_ = i; + DistributedDB::Message *message = nullptr; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + } + isNeedHandle = true; + isNeedContinue = true; + for (uint32_t i = 1; i <= 3; i++) { + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg != nullptr); + EXPECT_EQ(isNeedContinue, true); + EXPECT_EQ(isNeedHandle, (i == 3) ? true : false); + EXPECT_EQ(msg->GetSequenceId(), i); + const DataRequestPacket *packet = msg->GetObject(); + EXPECT_EQ(packet->GetPacketId(), i); + msgSchedule.ScheduleInfoHandle(isNeedHandle, false, msg); + delete msg; + } + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg == nullptr); + RefObject::KillAndDecObjRef(context); + context = nullptr; +} + +/** + * @tc.name: MsgSchedule007 + * @tc.desc: Test MsgSchedule function with same sessionId and duplicate sequenceId and low packetId + * @tc.type: FUNC + * @tc.require: + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMsgScheduleTest, MsgSchedule007, TestSize.Level0) +{ + /** + * @tc.steps: step1. put msg seq1_packet4, seq2_packet5 + * @tc.expected: put msg ok and get msg seq1_packet4, seq2_packet5 + */ + SingleVerDataMessageSchedule msgSchedule; + auto *context = new SingleVerKvSyncTaskContext(); + context->SetRemoteSoftwareVersion(SOFTWARE_VERSION_CURRENT); + DataSyncMessageInfo info; + info.sessionId_ = 10; + bool isNeedHandle = true; + bool isNeedContinue = true; + for (uint32_t i = 1; i <= 2; i++) { + info.sequenceId_ = i; + info.packetId_ = i + 3; + DistributedDB::Message *message = nullptr; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + } + for (uint32_t i = 1; i <= 2; i++) { + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg != nullptr); + EXPECT_EQ(isNeedContinue, true); + EXPECT_EQ(isNeedHandle, true); + EXPECT_EQ(msg->GetSequenceId(), i); + const DataRequestPacket *packet = msg->GetObject(); + EXPECT_EQ(packet->GetPacketId(), i + 3); + msgSchedule.ScheduleInfoHandle(isNeedHandle, false, msg); + delete msg; + } + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + ASSERT_TRUE(msg == nullptr); + /** + * @tc.steps: step2. put msg seq1_packet1, seq2_packet2 + * @tc.expected: get nullptr + */ + for (uint32_t i = 1; i <= 2; i++) { + info.sequenceId_ = i; + info.packetId_ = i; + DistributedDB::Message *message = nullptr; + DistributedDBToolsUnitTest::BuildMessage(info, message); + msgSchedule.PutMsg(message); + } + isNeedHandle = true; + isNeedContinue = true; + for (uint32_t i = 1; i <= 3; i++) { + Message *msg = msgSchedule.MoveNextMsg(context, isNeedHandle, isNeedContinue); + EXPECT_EQ(isNeedContinue, true); + ASSERT_TRUE(msg == nullptr); + } + RefObject::KillAndDecObjRef(context); + context = nullptr; +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_multi_user_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_multi_user_test.cpp new file mode 100644 index 00000000..aa3614c4 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_multi_user_test.cpp @@ -0,0 +1,775 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "db_constant.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "kv_store_nb_delegate.h" +#include "kv_virtual_device.h" +#include "platform_specific.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + const string STORE_ID = "kv_stroe_sync_test"; + const string USER_ID_1 = "userId1"; + const string USER_ID_2 = "userId2"; + const std::string DEVICE_B = "deviceB"; + const std::string DEVICE_C = "deviceC"; + const int WAIT_TIME = 1000; // 1000ms + const int WAIT_3_SECONDS = 3000; + + KvStoreDelegateManager g_mgr1(APP_ID, USER_ID_1); + KvStoreDelegateManager g_mgr2(APP_ID, USER_ID_2); + KvStoreConfig g_config; + DistributedDBToolsUnitTest g_tool; + KvStoreNbDelegate* g_kvDelegatePtr1 = nullptr; + KvStoreNbDelegate* g_kvDelegatePtr2 = nullptr; + VirtualCommunicatorAggregator* g_communicatorAggregator = nullptr; + KvVirtualDevice *g_deviceB = nullptr; + KvVirtualDevice *g_deviceC = nullptr; + DBStatus g_kvDelegateStatus1 = INVALID_ARGS; + DBStatus g_kvDelegateStatus2 = INVALID_ARGS; + std::string g_identifier; + + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback1 = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus1), std::ref(g_kvDelegatePtr1)); + auto g_kvDelegateCallback2 = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus2), std::ref(g_kvDelegatePtr2)); + auto g_syncActivationCheckCallback1 = [] (const std::string &userId, const std::string &appId, + const std::string &storeId)-> bool { + if (userId == USER_ID_2) { + return true; + } else { + return false; + } + return true; + }; + auto g_syncActivationCheckCallback2 = [] (const std::string &userId, const std::string &appId, + const std::string &storeId)-> bool { + if (userId == USER_ID_1) { + return true; + } else { + return false; + } + return true; + }; +} + +class DistributedDBSingleVerMultiUserTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBSingleVerMultiUserTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Init datadir and Virtual Communicator. + */ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr1.SetKvStoreConfig(g_config); + g_mgr2.SetKvStoreConfig(g_config); + + string dir = g_testDir + "/single_ver"; + DIR* dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } + + g_communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator(); + ASSERT_TRUE(g_communicatorAggregator != nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(g_communicatorAggregator); +} + +void DistributedDBSingleVerMultiUserTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: Release virtual Communicator and clear data dir. + */ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); +} + +void DistributedDBSingleVerMultiUserTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: create virtual device B + */ + g_deviceB = new (std::nothrow) KvVirtualDevice(DEVICE_B); + ASSERT_TRUE(g_deviceB != nullptr); + VirtualSingleVerSyncDBInterface *syncInterfaceB = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(syncInterfaceB != nullptr); + ASSERT_EQ(g_deviceB->Initialize(g_communicatorAggregator, syncInterfaceB), E_OK); + g_deviceC = new (std::nothrow) KvVirtualDevice(DEVICE_C); + ASSERT_TRUE(g_deviceC != nullptr); + VirtualSingleVerSyncDBInterface *syncInterfaceC = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(syncInterfaceC != nullptr); + ASSERT_EQ(g_deviceC->Initialize(g_communicatorAggregator, syncInterfaceC), E_OK); +} + +void DistributedDBSingleVerMultiUserTest::TearDown(void) +{ + /** + * @tc.teardown: Release device A, B, C + */ + if (g_deviceB != nullptr) { + delete g_deviceB; + g_deviceB = nullptr; + } + if (g_deviceC != nullptr) { + delete g_deviceC; + g_deviceC = nullptr; + } + SyncActivationCheckCallback callback = nullptr; + g_mgr1.SetSyncActivationCheckCallback(callback); +} + +namespace { +void OpenStore1(bool syncDualTupleMode = true) +{ + KvStoreNbDelegate::Option option; + option.syncDualTupleMode = syncDualTupleMode; + g_mgr1.GetKvStore(STORE_ID, option, g_kvDelegateCallback1); + ASSERT_TRUE(g_kvDelegateStatus1 == OK); + ASSERT_TRUE(g_kvDelegatePtr1 != nullptr); +} + +void OpenStore2(bool syncDualTupleMode = true) +{ + KvStoreNbDelegate::Option option; + option.syncDualTupleMode = syncDualTupleMode; + g_mgr2.GetKvStore(STORE_ID, option, g_kvDelegateCallback2); + ASSERT_TRUE(g_kvDelegateStatus2 == OK); + ASSERT_TRUE(g_kvDelegatePtr2 != nullptr); +} + +void CloseStore() +{ + if (g_kvDelegatePtr1 != nullptr) { + ASSERT_EQ(g_mgr1.CloseKvStore(g_kvDelegatePtr1), OK); + g_kvDelegatePtr1 = nullptr; + DBStatus status = g_mgr1.DeleteKvStore(STORE_ID); + LOGD("delete kv store status %d", status); + ASSERT_TRUE(status == OK); + } + if (g_kvDelegatePtr2 != nullptr) { + ASSERT_EQ(g_mgr2.CloseKvStore(g_kvDelegatePtr2), OK); + g_kvDelegatePtr2 = nullptr; + DBStatus status = g_mgr2.DeleteKvStore(STORE_ID); + LOGD("delete kv store status %d", status); + ASSERT_TRUE(status == OK); + } +} + +void CheckSyncTest(DBStatus status1, DBStatus status2, std::vector &devices) +{ + std::map result; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr1, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == status1); + if (status == OK) { + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + } + result.clear(); + status = g_tool.SyncTest(g_kvDelegatePtr2, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == status2); + if (status == OK) { + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + } +} + +bool AutoLaunchCallBack(const std::string &identifier, AutoLaunchParam ¶m, KvStoreObserverUnitTest *observer, + bool ret) +{ + LOGD("int AutoLaunchCallBack"); + EXPECT_TRUE(identifier == g_identifier); + param.appId = APP_ID; + param.storeId = STORE_ID; + CipherPassword passwd; + param.option = {true, false, CipherType::DEFAULT, passwd, "", false, g_testDir, observer, + 0, nullptr}; + param.notifier = nullptr; + param.option.syncDualTupleMode = true; + return ret; +} + +void TestSyncWithUserChange(bool wait) +{ + /** + * @tc.steps: step1. set SyncActivationCheckCallback and only userId1 can active + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback2); + /** + * @tc.steps: step2. openstore1 in dual tuple sync mode and openstore2 in normal sync mode + * @tc.expected: step2. only user2 sync mode is active + */ + OpenStore1(true); + OpenStore2(true); + /** + * @tc.steps: step3. set SyncActivationCheckCallback and only userId2 can active + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback1); + + /** + * @tc.steps: step4. call NotifyUserChanged and block sync db concurrently + * @tc.expected: step4. return OK + */ + CipherPassword passwd; + bool startSync = false; + std::condition_variable cv; + thread subThread([&]() { + std::mutex notifyLock; + std::unique_lock lck(notifyLock); + cv.wait(lck, [&startSync]() { return startSync; }); + EXPECT_TRUE(KvStoreDelegateManager::NotifyUserChanged() == OK); + }); + subThread.detach(); + g_communicatorAggregator->RegOnDispatch([&](const std::string&, Message *inMsg) { + if (!startSync) { + startSync = true; + cv.notify_all(); + } + }); + + /** + * @tc.steps: step5. deviceA call sync and wait + * @tc.expected: step5. sync should return OK. + */ + std::map result; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr1, devices, SYNC_MODE_PUSH_ONLY, result, wait); + EXPECT_EQ(status, OK); + g_communicatorAggregator->RegOnDispatch(nullptr); + /** + * @tc.expected: step6. onComplete should be called, and status is USER_CHANGED + */ + EXPECT_EQ(result.size(), devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_EQ(pair.second, USER_CHANGED); + } + CloseStore(); +} +} + +/** + * @tc.name: multi user 001 + * @tc.desc: Test multi user change + * @tc.type: FUNC + * @tc.require: AR000CQS3S SR000CQE0B + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMultiUserTest, MultiUser001, TestSize.Level0) +{ + /** + * @tc.steps: step1. set SyncActivationCheckCallback and only userId2 can active + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback1); + /** + * @tc.steps: step2. openstore1 and openstore2 + * @tc.expected: step2. only user2 sync mode is active + */ + OpenStore1(); + OpenStore2(); + /** + * @tc.steps: step3. g_kvDelegatePtr1 and g_kvDelegatePtr2 put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + Value value2 = {'2'}; + EXPECT_TRUE(g_kvDelegatePtr1->Put(key, value2) == OK); + EXPECT_TRUE(g_kvDelegatePtr2->Put(key, value) == OK); + /** + * @tc.steps: step4. g_kvDelegatePtr1 and g_kvDelegatePtr2 call sync + * @tc.expected: step4. g_kvDelegatePtr2 call success + */ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + CheckSyncTest(NOT_ACTIVE, OK, devices); + /** + * @tc.steps: step5. g_kvDelegatePtr1 support some pragma cmd call + * @tc.expected: step5. Pragma call success + */ + int pragmaData = 1; + PragmaData input = static_cast(&pragmaData); + EXPECT_TRUE(g_kvDelegatePtr1->Pragma(AUTO_SYNC, input) == OK); + pragmaData = 100; + input = static_cast(&pragmaData); + EXPECT_TRUE(g_kvDelegatePtr1->Pragma(SET_QUEUED_SYNC_LIMIT, input) == OK); + EXPECT_TRUE(g_kvDelegatePtr1->Pragma(GET_QUEUED_SYNC_LIMIT, input) == OK); + EXPECT_TRUE(input == static_cast(&pragmaData)); + pragmaData = 1; + input = static_cast(&pragmaData); + EXPECT_TRUE(g_kvDelegatePtr1->Pragma(SET_WIPE_POLICY, input) == OK); + EXPECT_TRUE(g_kvDelegatePtr1->Pragma(SET_SYNC_RETRY, input) == OK); + /** + * @tc.expected: step6. onComplete should be called, DeviceB have {k1,v1} + */ + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value == value); + /** + * @tc.expected: step7. user change + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback2); + KvStoreDelegateManager::NotifyUserChanged(); + /** + * @tc.steps: step8. g_kvDelegatePtr1 and g_kvDelegatePtr2 call sync + * @tc.expected: step8. g_kvDelegatePtr1 call success + */ + devices.clear(); + devices.push_back(g_deviceC->GetDeviceId()); + CheckSyncTest(OK, NOT_ACTIVE, devices); + /** + * @tc.expected: step9. onComplete should be called, DeviceC have {k1,v1} + */ + g_deviceC->GetData(key, item); + EXPECT_TRUE(item.value == value2); + CloseStore(); +} + +/** + * @tc.name: multi user 002 + * @tc.desc: Test multi user not change + * @tc.type: FUNC + * @tc.require: AR000CQS3S SR000CQE0B + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMultiUserTest, MultiUser002, TestSize.Level0) +{ + /** + * @tc.steps: step1. set SyncActivationCheckCallback and only userId2 can active + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback1); + /** + * @tc.steps: step2. openstore1 and openstore2 + * @tc.expected: step2. only user2 sync mode is active + */ + OpenStore1(); + OpenStore2(); + /** + * @tc.steps: step3. g_kvDelegatePtr1 and g_kvDelegatePtr2 put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + EXPECT_TRUE(g_kvDelegatePtr1->Put(key, value) == OK); + EXPECT_TRUE(g_kvDelegatePtr2->Put(key, value) == OK); + /** + * @tc.steps: step4. GetKvStoreIdentifier success when userId is invalid + */ + std::string userId; + EXPECT_TRUE(g_mgr1.GetKvStoreIdentifier(userId, APP_ID, USER_ID_2, true) != ""); + userId.resize(130); + EXPECT_TRUE(g_mgr1.GetKvStoreIdentifier(userId, APP_ID, USER_ID_2, true) != ""); + /** + * @tc.steps: step5. g_kvDelegatePtr1 and g_kvDelegatePtr2 call sync + * @tc.expected: step5. g_kvDelegatePtr2 call success + */ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + CheckSyncTest(NOT_ACTIVE, OK, devices); + /** + * @tc.expected: step6. onComplete should be called, DeviceB have {k1,v1} + */ + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value == value); + /** + * @tc.expected: step7. user change + */ + KvStoreDelegateManager::NotifyUserChanged(); + /** + * @tc.steps: step8. g_kvDelegatePtr1 and g_kvDelegatePtr2 put {k2, v2} + */ + key = {'2'}; + value = {'2'}; + EXPECT_TRUE(g_kvDelegatePtr1->Put(key, value) == OK); + EXPECT_TRUE(g_kvDelegatePtr2->Put(key, value) == OK); + /** + * @tc.steps: step9. g_kvDelegatePtr1 and g_kvDelegatePtr2 call sync + * @tc.expected: step9. g_kvDelegatePtr2 call success + */ + devices.clear(); + devices.push_back(g_deviceB->GetDeviceId()); + CheckSyncTest(NOT_ACTIVE, OK, devices); + /** + * @tc.expected: step10. onComplete should be called, DeviceB have {k2,v2} + */ + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value == value); + CloseStore(); +} + +/** + * @tc.name: multi user 003 + * @tc.desc: enhancement callback return true in multiuser mode + * @tc.type: FUNC + * @tc.require: AR000EPARJ + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMultiUserTest, MultiUser003, TestSize.Level3) +{ + /** + * @tc.steps: step1. set SyncActivationCheckCallback and only userId2 can active + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback1); + + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + EXPECT_TRUE(observer != nullptr); + /** + * @tc.steps: step2. SetAutoLaunchRequestCallback + * @tc.expected: step2. success. + */ + g_mgr1.SetAutoLaunchRequestCallback( + std::bind(AutoLaunchCallBack, std::placeholders::_1, std::placeholders::_2, observer, true)); + + /** + * @tc.steps: step2. RunCommunicatorLackCallback + * @tc.expected: step2. success. + */ + g_identifier = g_mgr1.GetKvStoreIdentifier(USER_ID_2, APP_ID, STORE_ID, true); + EXPECT_TRUE(g_identifier == g_mgr1.GetKvStoreIdentifier(USER_ID_1, APP_ID, STORE_ID, true)); + std::vector label(g_identifier.begin(), g_identifier.end()); + g_communicatorAggregator->SetCurrentUserId(USER_ID_2); + g_communicatorAggregator->RunCommunicatorLackCallback(label); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps: step3. device B put {k1, v1} + * @tc.expected: step3. success. + */ + Key key = {'1'}; + Value value = {'1'}; + Timestamp currentTime; + (void)OS::GetCurrentSysTimeInMicrosecond(currentTime); + EXPECT_TRUE(g_deviceB->PutData(key, value, currentTime, 0) == OK); + /** + * @tc.steps: step4. device B push sync to A + * @tc.expected: step4. success. + */ + EXPECT_TRUE(g_deviceB->Sync(SYNC_MODE_PUSH_ONLY, true) == OK); + EXPECT_TRUE(observer->GetCallCount() == 1); // only A + /** + * @tc.steps: step5. deviceA have {k1,v1} + * @tc.expected: step5. success. + */ + OpenStore2(); + Value actualValue; + g_kvDelegatePtr2->Get(key, actualValue); + EXPECT_EQ(actualValue, value); + std::this_thread::sleep_for(std::chrono::seconds(70)); + g_mgr1.SetAutoLaunchRequestCallback(nullptr); + CloseStore(); + delete observer; +} + +/** + * @tc.name: MultiUser004 + * @tc.desc: CommunicatorLackCallback in multi user mode + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMultiUserTest, MultiUser004, TestSize.Level0) +{ + /** + * @tc.steps: step1. set SyncActivationCheckCallback and only userId2 can active + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback1); + + /** + * @tc.steps: step2. right param A B enable + * @tc.expected: step2. success. + */ + AutoLaunchNotifier notifier = nullptr; + KvStoreObserverUnitTest *observer = new (std::nothrow) KvStoreObserverUnitTest; + EXPECT_TRUE(observer != nullptr); + AutoLaunchOption option; + CipherPassword passwd; + option = {true, false, CipherType::DEFAULT, passwd, "", false, g_testDir, observer, + 0, nullptr}; + option.notifier = nullptr; + option.observer = observer; + option.syncDualTupleMode = true; + EXPECT_TRUE(g_mgr1.EnableKvStoreAutoLaunch(USER_ID_2, APP_ID, STORE_ID, option, notifier) == OK); + EXPECT_TRUE(g_mgr1.EnableKvStoreAutoLaunch(USER_ID_1, APP_ID, STORE_ID, option, notifier) == OK); + + /** + * @tc.steps: step3. RunCommunicatorLackCallback + * @tc.expected: step3. userId2 open db successfully. + */ + g_identifier = g_mgr1.GetKvStoreIdentifier(USER_ID_2, APP_ID, STORE_ID, true); + std::vector label(g_identifier.begin(), g_identifier.end()); + g_communicatorAggregator->SetCurrentUserId(USER_ID_2); + g_communicatorAggregator->RunCommunicatorLackCallback(label); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps: step5. device B put {k1, v1} + * @tc.expected: step5. success. + */ + Key key = {'1'}; + Value value = {'1'}; + Timestamp currentTime; + (void)OS::GetCurrentSysTimeInMicrosecond(currentTime); + EXPECT_TRUE(g_deviceB->PutData(key, value, currentTime, 0) == OK); + /** + * @tc.steps: step6. device B push sync to A + * @tc.expected: step6. success. + */ + EXPECT_TRUE(g_deviceB->Sync(SYNC_MODE_PUSH_ONLY, true) == OK); + EXPECT_TRUE(observer->GetCallCount() == 1); // only A + /** + * @tc.steps: step7. deviceA have {k1,v1} + * @tc.expected: step7. success. + */ + OpenStore2(); + Value actualValue; + g_kvDelegatePtr2->Get(key, actualValue); + EXPECT_EQ(actualValue, value); + /** + * @tc.steps: step8. param A B disable + * @tc.expected: step8. notifier WRITE_CLOSED + */ + EXPECT_TRUE(g_mgr1.DisableKvStoreAutoLaunch(USER_ID_2, APP_ID, STORE_ID) == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + EXPECT_TRUE(g_mgr1.DisableKvStoreAutoLaunch(USER_ID_1, APP_ID, STORE_ID) == OK); + CloseStore(); + delete observer; +} + +/** + * @tc.name: MultiUser005 + * @tc.desc: test NotifyUserChanged func when all db in normal sync mode + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMultiUserTest, MultiUser005, TestSize.Level0) +{ + /** + * @tc.steps: step1. openstore1 and openstore2 in normal sync mode + * @tc.expected: step1. only user2 sync mode is active + */ + OpenStore1(false); + OpenStore2(false); + /** + * @tc.steps: step2. call NotifyUserChanged + * @tc.expected: step2. return OK + */ + EXPECT_TRUE(KvStoreDelegateManager::NotifyUserChanged() == OK); + CloseStore(); + /** + * @tc.steps: step3. openstore1 open normal sync mode and and openstore2 in dual tuple + * @tc.expected: step3. only user2 sync mode is active + */ + OpenStore1(false); + OpenStore2(); + /** + * @tc.steps: step4. call NotifyUserChanged + * @tc.expected: step4. return OK + */ + EXPECT_TRUE(KvStoreDelegateManager::NotifyUserChanged() == OK); + CloseStore(); +} + +/** + * @tc.name: MultiUser006 + * @tc.desc: test NotifyUserChanged and close db concurrently + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMultiUserTest, MultiUser006, TestSize.Level0) +{ + /** + * @tc.steps: step1. set SyncActivationCheckCallback and only userId1 can active + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback2); + /** + * @tc.steps: step2. openstore1 in dual tuple sync mode and openstore2 in normal sync mode + * @tc.expected: step2. only user2 sync mode is active + */ + OpenStore1(true); + OpenStore2(false); + /** + * @tc.steps: step3. set SyncActivationCheckCallback and only userId2 can active + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback1); + /** + * @tc.steps: step4. call NotifyUserChanged and close db concurrently + * @tc.expected: step4. return OK + */ + thread subThread([&]() { + EXPECT_TRUE(KvStoreDelegateManager::NotifyUserChanged() == OK); + }); + subThread.detach(); + EXPECT_EQ(g_mgr1.CloseKvStore(g_kvDelegatePtr1), OK); + g_kvDelegatePtr1 = nullptr; + CloseStore(); +} + +/** + * @tc.name: MultiUser007 + * @tc.desc: test NotifyUserChanged and rekey db concurrently + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMultiUserTest, MultiUser007, TestSize.Level0) +{ + /** + * @tc.steps: step1. set SyncActivationCheckCallback and only userId1 can active + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback2); + /** + * @tc.steps: step2. openstore1 in dual tuple sync mode and openstore2 in normal sync mode + * @tc.expected: step2. only user2 sync mode is active + */ + OpenStore1(true); + OpenStore2(false); + /** + * @tc.steps: step3. set SyncActivationCheckCallback and only userId2 can active + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback1); + /** + * @tc.steps: step2. call NotifyUserChanged and close db concurrently + * @tc.expected: step2. return OK + */ + CipherPassword passwd; + thread subThread([&]() { + EXPECT_TRUE(KvStoreDelegateManager::NotifyUserChanged() == OK); + }); + subThread.detach(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + EXPECT_TRUE(g_kvDelegatePtr1->Rekey(passwd) == OK); + CloseStore(); +} + +/** + * @tc.name: MultiUser008 + * @tc.desc: test NotifyUserChanged and block sync concurrently + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMultiUserTest, MultiUser008, TestSize.Level0) +{ + TestSyncWithUserChange(true); +} + +/** + * @tc.name: MultiUser009 + * @tc.desc: test NotifyUserChanged and non-block sync concurrently + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMultiUserTest, MultiUser009, TestSize.Level0) +{ + TestSyncWithUserChange(false); +} + +/** + * @tc.name: MultiUser010 + * @tc.desc: test NotifyUserChanged and non-block sync with multi devices concurrently + * @tc.type: FUNC + * @tc.require: AR000E8S2T + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerMultiUserTest, MultiUser010, TestSize.Level3) +{ + /** + * @tc.steps: step1. set SyncActivationCheckCallback and only userId1 can active + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback2); + /** + * @tc.steps: step2. openstore1 and openstore2 in dual tuple sync mode + * @tc.expected: step2. only userId1 sync mode is active + */ + OpenStore1(true); + OpenStore2(true); + /** + * @tc.steps: step3. set SyncActivationCheckCallback and only userId2 can active + */ + g_mgr1.SetSyncActivationCheckCallback(g_syncActivationCheckCallback1); + /** + * @tc.steps: step4. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + EXPECT_TRUE(g_kvDelegatePtr1->Put(key, value) == OK); + + /** + * @tc.steps: step5. deviceB set sava data dely 5s + */ + g_deviceC->SetSaveDataDelayTime(WAIT_3_SECONDS); + /** + * @tc.steps: step6. call NotifyUserChanged and block sync db concurrently + * @tc.expected: step6. return OK + */ + CipherPassword passwd; + thread subThread([&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + EXPECT_TRUE(KvStoreDelegateManager::NotifyUserChanged() == OK); + }); + subThread.detach(); + /** + * @tc.steps: step7. deviceA call sync and wait + * @tc.expected: step7. sync should return OK. + */ + std::map result; + std::vector devices = {g_deviceB->GetDeviceId(), g_deviceC->GetDeviceId()}; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr1, devices, SYNC_MODE_PUSH_ONLY, result, false); + EXPECT_TRUE(status == OK); + + /** + * @tc.expected: step8. onComplete should be called, and status is USER_CHANGED + */ + EXPECT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + if (pair.first == g_deviceB->GetDeviceId()) { + EXPECT_TRUE(pair.second == OK); + } else { + EXPECT_TRUE(pair.second == USER_CHANGED); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_3_SECONDS)); + CloseStore(); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_query_sync_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_query_sync_test.cpp new file mode 100644 index 00000000..9939f468 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_query_sync_test.cpp @@ -0,0 +1,1641 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_common.h" +#include "db_constant.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "generic_single_ver_kv_entry.h" +#include "kv_store_nb_delegate.h" +#include "kv_virtual_device.h" +#include "platform_specific.h" +#include "query.h" +#include "query_sync_object.h" +#include "single_ver_data_sync.h" +#include "single_ver_serialize_manager.h" +#include "sync_types.h" +#include "virtual_communicator.h" +#include "virtual_communicator_aggregator.h" +#include "virtual_single_ver_sync_db_Interface.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + const string STORE_ID = "kv_store_sync_test"; + const string SCHEMA_STORE_ID = "kv_store_sync_schema_test"; + const std::string DEVICE_B = "deviceB"; + + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreDelegateManager g_schemaMgr(SCHEMA_APP_ID, USER_ID); + KvStoreConfig g_config; + DistributedDBToolsUnitTest g_tool; + DBStatus g_kvDelegateStatus = INVALID_ARGS; + DBStatus g_schemaKvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate* g_kvDelegatePtr = nullptr; + KvStoreNbDelegate* g_schemaKvDelegatePtr = nullptr; + VirtualCommunicatorAggregator* g_communicatorAggregator = nullptr; + KvVirtualDevice *g_deviceB = nullptr; + + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); + auto g_schemaKvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_schemaKvDelegateStatus), std::ref(g_schemaKvDelegatePtr)); + const string SCHEMA_STRING = + "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":\"BOOL\"," + "\"field_name3\":\"INTEGER, NOT NULL\"," + "\"field_name4\":\"LONG, DEFAULT 100\"," + "\"field_name5\":\"DOUBLE, NOT NULL, DEFAULT 3.14\"," + "\"field_name6\":\"STRING, NOT NULL, DEFAULT '3.1415'\"," + "\"field_name7\":\"LONG, DEFAULT 100\"," + "\"field_name8\":\"LONG, DEFAULT 100\"," + "\"field_name9\":\"LONG, DEFAULT 100\"," + "\"field_name10\":\"LONG, DEFAULT 100\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\", \"$.field_name2\"]}"; + + const std::string SCHEMA_VALUE1 = + "{\"field_name1\":true," + "\"field_name2\":false," + "\"field_name3\":10," + "\"field_name4\":20," + "\"field_name5\":3.14," + "\"field_name6\":\"3.1415\"," + "\"field_name7\":100," + "\"field_name8\":100," + "\"field_name9\":100," + "\"field_name10\":100}"; + + const std::string SCHEMA_VALUE2 = + "{\"field_name1\":false," + "\"field_name2\":true," + "\"field_name3\":100," + "\"field_name4\":200," + "\"field_name5\":3.14," + "\"field_name6\":\"3.1415\"," + "\"field_name7\":100," + "\"field_name8\":100," + "\"field_name9\":100," + "\"field_name10\":100}"; +} + +class DistributedDBSingleVerP2PQuerySyncTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBSingleVerP2PQuerySyncTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Init datadir and Virtual Communicator. + */ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + string dir = g_testDir + "/single_ver"; + DIR* dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } + + g_communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator(); + ASSERT_TRUE(g_communicatorAggregator != nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(g_communicatorAggregator); +} + +void DistributedDBSingleVerP2PQuerySyncTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: Release virtual Communicator and clear data dir. + */ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); +} + +void DistributedDBSingleVerP2PQuerySyncTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: create virtual device B and get a KvStoreNbDelegate as deviceA + */ + g_deviceB = new (std::nothrow) KvVirtualDevice(DEVICE_B); + ASSERT_TRUE(g_deviceB != nullptr); + VirtualSingleVerSyncDBInterface *syncInterfaceB = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(syncInterfaceB != nullptr); + ASSERT_EQ(g_deviceB->Initialize(g_communicatorAggregator, syncInterfaceB), E_OK); +} + +void DistributedDBSingleVerP2PQuerySyncTest::TearDown(void) +{ + /** + * @tc.teardown: Release device A, B + */ + if (g_kvDelegatePtr != nullptr) { + ASSERT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + DBStatus status = g_mgr.DeleteKvStore(STORE_ID); + LOGD("delete kv store status %d", status); + ASSERT_TRUE(status == OK); + } + if (g_schemaKvDelegatePtr != nullptr) { + ASSERT_EQ(g_schemaMgr.CloseKvStore(g_schemaKvDelegatePtr), OK); + g_schemaKvDelegatePtr = nullptr; + DBStatus status = g_schemaMgr.DeleteKvStore(SCHEMA_STORE_ID); + LOGD("delete kv store status %d", status); + ASSERT_TRUE(status == OK); + } + if (g_deviceB != nullptr) { + delete g_deviceB; + g_deviceB = nullptr; + } + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} + +void InitNormalDb() +{ + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + KvStoreNbDelegate::Option option; + g_mgr.GetKvStore(STORE_ID, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); +} + +void InitSchemaDb() +{ + g_config.dataDir = g_testDir; + g_schemaMgr.SetKvStoreConfig(g_config); + KvStoreNbDelegate::Option option; + option.schema = SCHEMA_STRING; + g_schemaMgr.GetKvStore(SCHEMA_STORE_ID, option, g_schemaKvDelegateCallback); + ASSERT_TRUE(g_schemaKvDelegateStatus == OK); + ASSERT_TRUE(g_schemaKvDelegatePtr != nullptr); +} + +/** + * @tc.name: Normal Sync 001 + * @tc.desc: Test normal push sync for keyprefix data. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, NormalSync001, TestSize.Level1) +{ + InitNormalDb(); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k0, v0} - {k9, v9} + */ + Key key = {'1'}; + Value value = {'1'}; + const int dataSize = 10; + for (int i = 0; i < dataSize; i++) { + key.push_back(i); + value.push_back(i); + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + key.pop_back(); + value.pop_back(); + } + Key key2 = {'2'}; + Value value2 = {'2'}; + status = g_kvDelegatePtr->Put(key2, value2); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step2. deviceA call query sync and wait + * @tc.expected: step2. sync should return OK. + */ + Query query = Query::Select().PrefixKey(key); + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result, query); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, DeviceB have {k1,v1} - {k9, v9} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + for (int i = 0; i < dataSize; i++) { + key.push_back(i); + value.push_back(i); + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value == value); + key.pop_back(); + value.pop_back(); + } + EXPECT_TRUE(g_deviceB->GetData(key2, item) != E_OK); +} + +/** + * @tc.name: Normal Sync 002 + * @tc.desc: Test normal push sync for limit and offset. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, NormalSync002, TestSize.Level1) +{ + InitNormalDb(); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k0, v0} - {k9, v9} + */ + Key key = {'1'}; + Value value = {'1'}; + const int dataSize = 10; + for (int i = 0; i < dataSize; i++) { + key.push_back(i); + value.push_back(i); + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + key.pop_back(); + value.pop_back(); + } + + /** + * @tc.steps: step2. deviceA call sync and wait + * @tc.expected: step2. sync should return OK. + */ + const int limit = 5; + const int offset = 4; + Query query = Query::Select().PrefixKey(key).Limit(limit, offset); + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result, query); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, DeviceB have {k4,v4} {k8, v8} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + + VirtualDataItem item; + for (int i = limit - 1; i < limit + offset; i++) { + key.push_back(i); + value.push_back(i); + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value == value); + key.pop_back(); + value.pop_back(); + } +} + +/** + * @tc.name: Normal Sync 001 + * @tc.desc: Test normal push_and_pull sync for keyprefix data. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, NormalSync003, TestSize.Level1) +{ + InitNormalDb(); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k, v}, {b, v} + */ + Key key = {'1'}; + Value value = {'1'}; + const int dataSize = 10; + status = g_kvDelegatePtr->Put(key, value); + Key key2 = {'2'}; + Value value2 = {'2'}; + status = g_kvDelegatePtr->Put(key2, value2); + + /** + * @tc.steps: step2. deviceB put {b0, v0} - {b9, v9}, {c, v} + */ + for (int i = 0; i < dataSize; i++) { + key2.push_back(i); + value2.push_back(i); + g_deviceB->PutData(key2, value2, 10 + i, 0); + key2.pop_back(); + value2.pop_back(); + } + Key key3 = {'3'}; + Value value3 = {'3'}; + g_deviceB->PutData(key3, value3, 20, 0); + + /** + * @tc.steps: step2. deviceA call query sync and wait + * @tc.expected: step2. sync should return OK. + */ + Query query = Query::Select().PrefixKey(key2); + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, result, query); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, DeviceA have {b0, v0} - {b9, v9}, DeviceB have {b, v} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + Value tmpValue; + for (int i = 0; i < dataSize; i++) { + key2.push_back(i); + value2.push_back(i); + g_kvDelegatePtr->Get(key2, tmpValue); + EXPECT_TRUE(tmpValue == value2); + key2.pop_back(); + value2.pop_back(); + } + EXPECT_TRUE(g_deviceB->GetData(key, item) != E_OK); + EXPECT_TRUE(g_deviceB->GetData(key2, item) == E_OK); + g_kvDelegatePtr->Get(key3, tmpValue); + EXPECT_TRUE(tmpValue != value3); +} + +/** + * @tc.name: Normal Sync 001 + * @tc.desc: Test normal pull sync for keyprefix data. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, NormalSync004, TestSize.Level1) +{ + InitNormalDb(); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + /** + * @tc.steps: step1. deviceB put {k1, v1} - {k9, k9}, {b0, v0} - {b9, v9} + */ + Key key = {'1'}; + Value value = {'1'}; + const int dataSize = 10; + Key key2 = {'2'}; + Value value2 = {'2'}; + vector> key1Vec; + vector> key2Vec; + for (int i = 0; i < dataSize; i++) { + Key tmpKey(key); + Value tmpValue(value); + tmpKey.push_back(i); + tmpValue.push_back(i); + key1Vec.push_back(pair {tmpKey, tmpValue}); + } + for (int i = 0; i < dataSize; i++) { + Key tmpKey(key2); + Value tmpValue(value2); + tmpKey.push_back(i); + tmpValue.push_back(i); + key2Vec.push_back(pair {tmpKey, tmpValue}); + } + for (int i = 0; i < dataSize; i++) { + g_deviceB->PutData(key2Vec[i].first, key2Vec[i].second, 20 + i, 0); + g_deviceB->PutData(key1Vec[i].first, key1Vec[i].second, 10 + i, 0); + } + + /** + * @tc.steps: step2. deviceA call query sync and wait + * @tc.expected: step2. sync should return OK. + */ + Query query = Query::Select().PrefixKey(key2); + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result, query); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, DeviceA have {b0, v0} - {b9, v9} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + Value tmpValue; + for (int i = 0; i < dataSize; i++) { + g_kvDelegatePtr->Get(key2Vec[i].first, tmpValue); + EXPECT_TRUE(tmpValue == key2Vec[i].second); + g_kvDelegatePtr->Get(key1Vec[i].first, tmpValue); + EXPECT_TRUE(tmpValue != key1Vec[i].second); + } +} + +/** + * @tc.name: NormalSync005 + * @tc.desc: Test normal push sync for inkeys query. + * @tc.type: FUNC + * @tc.require: AR000GOHO7 + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, NormalSync005, TestSize.Level1) +{ + InitNormalDb(); + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put K1-K5 + */ + ASSERT_EQ(g_kvDelegatePtr->PutBatch( + {{KEY_1, VALUE_1}, {KEY_2, VALUE_2}, {KEY_3, VALUE_3}, {KEY_4, VALUE_4}, {KEY_5, VALUE_5}}), OK); + + /** + * @tc.steps: step2. deviceA sync K2,K4 and wait + * @tc.expected: step2. sync should return OK. + */ + Query query = Query::Select().InKeys({KEY_2, KEY_4}); + std::map result; + ASSERT_EQ(g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result, query), OK); + + /** + * @tc.expected: step3. onComplete should be called. + */ + ASSERT_EQ(result.size(), devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_EQ(pair.second, OK); + } + + /** + * @tc.steps: step4. deviceB have K2K4 and have no K1K3K5. + * @tc.expected: step4. sync should return OK. + */ + VirtualDataItem item; + EXPECT_EQ(g_deviceB->GetData(KEY_2, item), E_OK); + EXPECT_EQ(item.value, VALUE_2); + EXPECT_EQ(g_deviceB->GetData(KEY_4, item), E_OK); + EXPECT_EQ(item.value, VALUE_4); + EXPECT_EQ(g_deviceB->GetData(KEY_1, item), -E_NOT_FOUND); + EXPECT_EQ(g_deviceB->GetData(KEY_3, item), -E_NOT_FOUND); + EXPECT_EQ(g_deviceB->GetData(KEY_5, item), -E_NOT_FOUND); + + /** + * @tc.steps: step5. deviceA sync with invalid inkeys query + * @tc.expected: step5. sync failed and the rc is right. + */ + query = Query::Select().InKeys({}); + result.clear(); + ASSERT_EQ(g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result, query), INVALID_ARGS); + + std::set keys; + for (uint8_t i = 0; i < DBConstant::MAX_BATCH_SIZE + 1; i++) { + Key key = { i }; + keys.emplace(key); + } + query = Query::Select().InKeys(keys); + result.clear(); + ASSERT_EQ(g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result, query), OVER_MAX_LIMITS); + + query = Query::Select().InKeys({{}}); + result.clear(); + ASSERT_EQ(g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result, query), INVALID_ARGS); +} + +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, QueryRequestPacketTest001, TestSize.Level1) +{ + /** + * @tc.steps: step1. prepare a QuerySyncRequestPacket. + */ + auto packet = new (std::nothrow) DataRequestPacket; + ASSERT_TRUE(packet != nullptr); + auto kvEntry = new (std::nothrow) GenericSingleVerKvEntry; + ASSERT_TRUE(kvEntry != nullptr); + kvEntry->SetTimestamp(1); + SyncEntry syncData {.entries = {kvEntry}}; +#ifndef OMIT_ZLIB + ASSERT_TRUE(GenericSingleVerKvEntry::Compress(syncData.entries, syncData.compressedEntries, + {CompressAlgorithm::ZLIB, SOFTWARE_VERSION_CURRENT}) == E_OK); + packet->SetCompressAlgo(CompressAlgorithm::ZLIB); + packet->SetFlag(4); // set IS_COMPRESS_DATA flag true +#endif + packet->SetBasicInfo(-E_NOT_SUPPORT, SOFTWARE_VERSION_CURRENT, SyncModeType::QUERY_PUSH_PULL); + packet->SetData(syncData.entries); + packet->SetCompressData(syncData.compressedEntries); + packet->SetEndWaterMark(INT8_MAX); + packet->SetWaterMark(INT16_MAX, INT32_MAX, INT64_MAX); + QuerySyncObject syncQuery(Query::Select().PrefixKey({'2'})); + packet->SetQuery(syncQuery); + packet->SetQueryId(syncQuery.GetIdentify()); + packet->SetReserved(std::vector {INT8_MAX}); + + /** + * @tc.steps: step2. put the QuerySyncRequestPacket into a message. + */ + Message msg; + msg.SetExternalObject(packet); + msg.SetMessageId(QUERY_SYNC_MESSAGE); + msg.SetMessageType(TYPE_REQUEST); + + /** + * @tc.steps: step3. Serialization the message to a buffer. + */ + int len = SingleVerSerializeManager::CalculateLen(&msg); + vector buffer(len); + ASSERT_EQ(SingleVerSerializeManager::Serialization(buffer.data(), buffer.size(), &msg), E_OK); + + /** + * @tc.steps: step4. DeSerialization the buffer to a message. + */ + Message outMsg(QUERY_SYNC_MESSAGE); + outMsg.SetMessageType(TYPE_REQUEST); + ASSERT_EQ(SingleVerSerializeManager::DeSerialization(buffer.data(), buffer.size(), &outMsg), E_OK); + + /** + * @tc.steps: step5. checkout the outMsg. + * @tc.expected: step5. outMsg equal the the in msg + */ + auto outPacket = outMsg.GetObject(); + EXPECT_EQ(outPacket->GetVersion(), SOFTWARE_VERSION_CURRENT); + EXPECT_EQ(outPacket->GetMode(), SyncModeType::QUERY_PUSH_PULL); + EXPECT_EQ(outPacket->GetEndWaterMark(), static_cast(INT8_MAX)); + EXPECT_EQ(outPacket->GetLocalWaterMark(), static_cast(INT16_MAX)); + EXPECT_EQ(outPacket->GetPeerWaterMark(), static_cast(INT32_MAX)); + EXPECT_EQ(outPacket->GetDeletedWaterMark(), static_cast(INT64_MAX)); +#ifndef OMIT_ZLIB + EXPECT_EQ(outPacket->GetFlag(), static_cast(4)); // check IS_COMPRESS_DATA flag true +#endif + EXPECT_EQ(outPacket->GetQueryId(), syncQuery.GetIdentify()); + EXPECT_EQ(outPacket->GetReserved(), std::vector {INT8_MAX}); + EXPECT_EQ(outPacket->GetSendCode(), -E_NOT_SUPPORT); + EXPECT_EQ(outPacket->GetData()[0]->GetTimestamp(), 1u); +} + +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, QueryAckPacketTest001, TestSize.Level1) +{ + /** + * @tc.steps: step1. prepare a QuerySyncAckPacket. + */ + DataAckPacket packet; + packet.SetVersion(SOFTWARE_VERSION_CURRENT); + packet.SetData(INT64_MAX); + packet.SetRecvCode(-E_NOT_SUPPORT); + std::vector reserved = {INT8_MAX}; + packet.SetReserved(reserved); + + /** + * @tc.steps: step2. put the QuerySyncAckPacket into a message. + */ + Message msg; + msg.SetCopiedObject(packet); + msg.SetMessageId(QUERY_SYNC_MESSAGE); + msg.SetMessageType(TYPE_RESPONSE); + + /** + * @tc.steps: step3. Serialization the message to a buffer. + */ + int len = SingleVerSerializeManager::CalculateLen(&msg); + LOGE("test leng = %d", len); + uint8_t *buffer = new (nothrow) uint8_t[len]; + ASSERT_TRUE(buffer != nullptr); + int errCode = SingleVerSerializeManager::Serialization(buffer, len, &msg); + ASSERT_EQ(errCode, E_OK); + + /** + * @tc.steps: step4. DeSerialization the buffer to a message. + */ + Message outMsg; + outMsg.SetMessageId(QUERY_SYNC_MESSAGE); + outMsg.SetMessageType(TYPE_RESPONSE); + errCode = SingleVerSerializeManager::DeSerialization(buffer, len, &outMsg); + ASSERT_EQ(errCode, E_OK); + + /** + * @tc.steps: step5. checkout the outMsg. + * @tc.expected: step5. outMsg equal the the in msg + */ + auto outPacket = outMsg.GetObject(); + EXPECT_EQ(outPacket->GetVersion(), SOFTWARE_VERSION_CURRENT); + EXPECT_EQ(outPacket->GetData(), static_cast(INT64_MAX)); + std::vector reserved2 = {INT8_MAX}; + EXPECT_EQ(outPacket->GetReserved(), reserved2); + EXPECT_EQ(outPacket->GetRecvCode(), -E_NOT_SUPPORT); + delete[] buffer; +} + +/** + * @tc.name: GetQueryWaterMark 001 + * @tc.desc: Test metaData save and get queryWaterMark. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, GetQueryWaterMark001, TestSize.Level1) +{ + VirtualSingleVerSyncDBInterface storage; + Metadata meta; + + /** + * @tc.steps: step1. initialize meta with storage + * @tc.expected: step1. E_OK + */ + int errCode = meta.Initialize(&storage); + ASSERT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. save receive and send watermark + * @tc.expected: step2. E_OK + */ + WaterMark w1 = 1; + EXPECT_EQ(meta.SetRecvQueryWaterMark("Q1", "D1", w1), E_OK); + EXPECT_EQ(meta.SetSendQueryWaterMark("Q1", "D1", w1), E_OK); + + /** + * @tc.steps: step3. get receive and send watermark + * @tc.expected: step3. E_OK and get the latest value + */ + WaterMark w = 0; + EXPECT_EQ(meta.GetRecvQueryWaterMark("Q1", "D1", w), E_OK); + EXPECT_EQ(w1, w); + EXPECT_EQ(meta.GetSendQueryWaterMark("Q1", "D1", w), E_OK); + EXPECT_EQ(w1, w); + + /** + * @tc.steps: step4. set peer and local watermark + * @tc.expected: step4. E_OK + */ + WaterMark w2 = 2; + EXPECT_EQ(meta.SaveLocalWaterMark("D1", w2), E_OK); + EXPECT_EQ(meta.SavePeerWaterMark("D1", w2, true), E_OK); + + /** + * @tc.steps: step5. get receive and send watermark + * @tc.expected: step5. E_OK and get the w1 + */ + EXPECT_EQ(meta.GetRecvQueryWaterMark("Q1", "D1", w), E_OK); + EXPECT_EQ(w2, w); + EXPECT_EQ(meta.GetSendQueryWaterMark("Q1", "D1", w), E_OK); + EXPECT_EQ(w2, w); + + /** + * @tc.steps: step6. set peer and local watermark + * @tc.expected: step6. E_OK + */ + WaterMark w3 = 3; + EXPECT_EQ(meta.SaveLocalWaterMark("D2", w3), E_OK); + EXPECT_EQ(meta.SavePeerWaterMark("D2", w3, true), E_OK); + + /** + * @tc.steps: step7. get receive and send watermark + * @tc.expected: step7. E_OK and get the w3 + */ + EXPECT_EQ(meta.GetRecvQueryWaterMark("Q2", "D2", w), E_OK); + EXPECT_EQ(w3, w); + EXPECT_EQ(meta.GetSendQueryWaterMark("Q2", "D2", w), E_OK); + EXPECT_EQ(w3, w); + + /** + * @tc.steps: step8. get not exit receive and send watermark + * @tc.expected: step8. E_OK and get the 0 + */ + EXPECT_EQ(meta.GetRecvQueryWaterMark("Q3", "D3", w), E_OK); + EXPECT_EQ(w, 0u); + EXPECT_EQ(meta.GetSendQueryWaterMark("Q3", "D3", w), E_OK); + EXPECT_EQ(w, 0u); +} + +/** + * @tc.name: GetQueryWaterMark 002 + * @tc.desc: Test metaData save and get queryWaterMark after push or pull mode. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, GetQueryWaterMark002, TestSize.Level1) +{ + VirtualSingleVerSyncDBInterface storage; + Metadata meta; + + /** + * @tc.steps: step1. initialize meta with storage + * @tc.expected: step1. E_OK + */ + int errCode = meta.Initialize(&storage); + ASSERT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. set peer and local watermark + * @tc.expected: step2. E_OK + */ + WaterMark w1 = 2; + EXPECT_EQ(meta.SaveLocalWaterMark("D1", w1), E_OK); + EXPECT_EQ(meta.SavePeerWaterMark("D1", w1, true), E_OK); + + /** + * @tc.steps: step2. save receive and send watermark + * @tc.expected: step2. E_OK + */ + WaterMark w2 = 1; + EXPECT_EQ(meta.SetRecvQueryWaterMark("Q1", "D1", w2), E_OK); + EXPECT_EQ(meta.SetSendQueryWaterMark("Q1", "D1", w2), E_OK); + + /** + * @tc.steps: step3. get receive and send watermark + * @tc.expected: step3. E_OK and get the bigger value + */ + WaterMark w = 0; + EXPECT_EQ(meta.GetRecvQueryWaterMark("Q1", "D1", w), E_OK); + EXPECT_EQ(w1, w); + EXPECT_EQ(meta.GetSendQueryWaterMark("Q1", "D1", w), E_OK); + EXPECT_EQ(w1, w); +} + +/** + * @tc.name: ClearQueryWaterMark 001 + * @tc.desc: Test metaData clear watermark function. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, ClearQueryWaterMark001, TestSize.Level1) +{ + VirtualSingleVerSyncDBInterface storage; + Metadata meta; + + /** + * @tc.steps: step1. initialize meta with storage + * @tc.expected: step1. E_OK + */ + int errCode = meta.Initialize(&storage); + ASSERT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. save receive watermark + * @tc.expected: step2. E_OK + */ + WaterMark w1 = 1; + EXPECT_EQ(meta.SetRecvQueryWaterMark("Q1", "D1", w1), E_OK); + + /** + * @tc.steps: step3. erase peer watermark + * @tc.expected: step3. E_OK + */ + EXPECT_EQ(meta.EraseDeviceWaterMark("D1", true), E_OK); + + /** + * @tc.steps: step4. get receive watermark + * @tc.expected: step4. E_OK receive watermark is zero + */ + WaterMark w2 = -1; + EXPECT_EQ(meta.GetRecvQueryWaterMark("Q1", "D1", w2), E_OK); + EXPECT_EQ(w2, 0u); + + /** + * @tc.steps: step5. set peer watermark + * @tc.expected: step5. E_OK + */ + WaterMark w3 = 2; + EXPECT_EQ(meta.SavePeerWaterMark("D1", w3, true), E_OK); + + /** + * @tc.steps: step6. get receive watermark + * @tc.expected: step6. E_OK receive watermark is peer watermark + */ + WaterMark w4 = -1; + EXPECT_EQ(meta.GetRecvQueryWaterMark("Q1", "D1", w4), E_OK); + EXPECT_EQ(w4, w3); +} + +/** + * @tc.name: ClearQueryWaterMark 002 + * @tc.desc: Test metaData clear watermark function. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, ClearQueryWaterMark002, TestSize.Level1) +{ + VirtualSingleVerSyncDBInterface storage; + Metadata meta; + + /** + * @tc.steps: step1. initialize meta with storage + * @tc.expected: step1. E_OK + */ + int errCode = meta.Initialize(&storage); + ASSERT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. save receive watermark + * @tc.expected: step2. E_OK + */ + WaterMark w1 = 1; + EXPECT_EQ(meta.SetRecvQueryWaterMark("Q1", "D1", w1), E_OK); + EXPECT_EQ(meta.SetRecvQueryWaterMark("Q2", "D1", w1), E_OK); + EXPECT_EQ(meta.SetRecvQueryWaterMark("Q1", "D2", w1), E_OK); + + /** + * @tc.steps: step3. erase peer watermark, make sure data remove in db + * @tc.expected: step3. E_OK + */ + Metadata anotherMeta; + ASSERT_EQ(anotherMeta.Initialize(&storage), E_OK); + EXPECT_EQ(anotherMeta.EraseDeviceWaterMark("D1", true), E_OK); + + /** + * @tc.steps: step4. get receive watermark + * @tc.expected: step4. E_OK receive watermark is zero + */ + WaterMark w2 = -1; + EXPECT_EQ(anotherMeta.GetRecvQueryWaterMark("Q1", "D1", w2), E_OK); + EXPECT_EQ(w2, 0u); + w2 = -1; + EXPECT_EQ(anotherMeta.GetRecvQueryWaterMark("Q2", "D1", w2), E_OK); + EXPECT_EQ(w2, 0u); + w2 = -1; + EXPECT_EQ(anotherMeta.GetRecvQueryWaterMark("Q1", "D2", w2), E_OK); + EXPECT_EQ(w2, w1); +} + +/** + * @tc.name: GetDeleteKeyWaterMark 001 + * @tc.desc: Test metaData save and get deleteWaterMark. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, GetDeleteKeyWaterMark001, TestSize.Level1) +{ + VirtualSingleVerSyncDBInterface storage; + Metadata meta; + + /** + * @tc.steps: step1. initialize meta with storage + * @tc.expected: step1. E_OK + */ + int errCode = meta.Initialize(&storage); + ASSERT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. save receive and send watermark + * @tc.expected: step2. E_OK + */ + WaterMark w1 = 1; + EXPECT_EQ(meta.SetRecvDeleteSyncWaterMark("D1", w1), E_OK); + EXPECT_EQ(meta.SetSendDeleteSyncWaterMark("D1", w1), E_OK); + + /** + * @tc.steps: step3. get receive and send watermark + * @tc.expected: step3. E_OK and get the latest value + */ + WaterMark w = 0; + EXPECT_EQ(meta.GetRecvDeleteSyncWaterMark("D1", w), E_OK); + EXPECT_EQ(w1, w); + EXPECT_EQ(meta.GetSendDeleteSyncWaterMark("D1", w), E_OK); + EXPECT_EQ(w1, w); + + /** + * @tc.steps: step4. set peer and local watermark + * @tc.expected: step4. E_OK + */ + WaterMark w2 = 2; + EXPECT_EQ(meta.SaveLocalWaterMark("D1", w2), E_OK); + EXPECT_EQ(meta.SavePeerWaterMark("D1", w2, true), E_OK); + + /** + * @tc.steps: step5. get receive and send watermark + * @tc.expected: step5. E_OK and get the w1 + */ + EXPECT_EQ(meta.GetRecvDeleteSyncWaterMark("D1", w), E_OK); + EXPECT_EQ(w2, w); + EXPECT_EQ(meta.GetSendDeleteSyncWaterMark("D1", w), E_OK); + EXPECT_EQ(w2, w); + + /** + * @tc.steps: step6. set peer and local watermark + * @tc.expected: step6. E_OK + */ + WaterMark w3 = 3; + EXPECT_EQ(meta.SaveLocalWaterMark("D2", w3), E_OK); + EXPECT_EQ(meta.SavePeerWaterMark("D2", w3, true), E_OK); + + /** + * @tc.steps: step7. get receive and send watermark + * @tc.expected: step7. E_OK and get the w3 + */ + EXPECT_EQ(meta.GetRecvDeleteSyncWaterMark("D2", w), E_OK); + EXPECT_EQ(w3, w); + EXPECT_EQ(meta.GetSendDeleteSyncWaterMark("D2", w), E_OK); + EXPECT_EQ(w3, w); + + /** + * @tc.steps: step8. get not exit receive and send watermark + * @tc.expected: step8. E_OK and get the 0 + */ + EXPECT_EQ(meta.GetRecvDeleteSyncWaterMark("D3", w), E_OK); + EXPECT_EQ(w, 0u); + EXPECT_EQ(meta.GetSendDeleteSyncWaterMark("D3", w), E_OK); + EXPECT_EQ(w, 0u); +} + +/** + * @tc.name: GetDeleteKeyWaterMark 002 + * @tc.desc: Test metaData save and get deleteWaterMark after push or pull mode. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, GetDeleteKeyWaterMark002, TestSize.Level1) +{ + VirtualSingleVerSyncDBInterface storage; + Metadata meta; + + /** + * @tc.steps: step1. initialize meta with storage + * @tc.expected: step1. E_OK + */ + int errCode = meta.Initialize(&storage); + ASSERT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. set peer and local watermark + * @tc.expected: step2. E_OK + */ + WaterMark w1 = 3; + EXPECT_EQ(meta.SaveLocalWaterMark("D1", w1), E_OK); + EXPECT_EQ(meta.SavePeerWaterMark("D1", w1, true), E_OK); + + /** + * @tc.steps: step2. save receive and send watermark + * @tc.expected: step2. E_OK + */ + WaterMark w2 = 1; + EXPECT_EQ(meta.SetRecvDeleteSyncWaterMark("D1", w2), E_OK); + EXPECT_EQ(meta.SetSendDeleteSyncWaterMark("D1", w2), E_OK); + + /** + * @tc.steps: step3. get receive and send watermark + * @tc.expected: step3. E_OK and get the bigger value + */ + WaterMark w = 0; + EXPECT_EQ(meta.GetRecvDeleteSyncWaterMark("D1", w), E_OK); + EXPECT_EQ(w1, w); + EXPECT_EQ(meta.GetSendDeleteSyncWaterMark("D1", w), E_OK); + EXPECT_EQ(w1, w); +} + +/** + * @tc.name: ClearDeleteKeyWaterMark 001 + * @tc.desc: Test metaData clear watermark function. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, ClearDeleteKeyWaterMark001, TestSize.Level1) +{ + VirtualSingleVerSyncDBInterface storage; + Metadata meta; + + /** + * @tc.steps: step1. initialize meta with storage + * @tc.expected: step1. E_OK + */ + int errCode = meta.Initialize(&storage); + ASSERT_EQ(errCode, E_OK); + + /** + * @tc.steps: step2. save receive watermark + * @tc.expected: step2. E_OK + */ + WaterMark w1 = 1; + EXPECT_EQ(meta.SetRecvDeleteSyncWaterMark("D1", w1), E_OK); + + /** + * @tc.steps: step3. erase peer watermark + * @tc.expected: step3. E_OK + */ + EXPECT_EQ(meta.EraseDeviceWaterMark("D1", true), E_OK); + + /** + * @tc.steps: step4. get receive watermark + * @tc.expected: step4. E_OK receive watermark is zero + */ + WaterMark w2 = -1; + EXPECT_EQ(meta.GetRecvDeleteSyncWaterMark("D1", w2), E_OK); + EXPECT_EQ(w2, 0u); + + /** + * @tc.steps: step5. set peer watermark + * @tc.expected: step5. E_OK + */ + WaterMark w3 = 2; + EXPECT_EQ(meta.SavePeerWaterMark("D1", w3, true), E_OK); + + /** + * @tc.steps: step6. get receive watermark + * @tc.expected: step6. E_OK receive watermark is peer watermark + */ + WaterMark w4 = -1; + EXPECT_EQ(meta.GetRecvDeleteSyncWaterMark("D1", w4), E_OK); + EXPECT_EQ(w4, w3); +} + +/** + * @tc.name: VerifyCacheAndDb 001 + * @tc.desc: Test metaData watermark cache and db are consistent and correct. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, VerifyMetaDataQuerySync001, TestSize.Level1) +{ + Metadata meta; + VirtualSingleVerSyncDBInterface storage; + + /** + * @tc.steps: step1. initialize meta with storage + * @tc.expected: step1. E_OK + */ + int errCode = meta.Initialize(&storage); + ASSERT_EQ(errCode, E_OK); + + const std::string deviceId = "D1"; + const std::string queryId = "Q1"; + + /** + * @tc.steps: step2. save deleteSync watermark + * @tc.expected: step2. E_OK + */ + WaterMark deleteWaterMark = 1; + EXPECT_EQ(meta.SetRecvDeleteSyncWaterMark(deviceId, deleteWaterMark), E_OK); + EXPECT_EQ(meta.SetSendDeleteSyncWaterMark(deviceId, deleteWaterMark), E_OK); + + /** + * @tc.steps: step3. save querySync watermark + * @tc.expected: step2. E_OK + */ + WaterMark queryWaterMark = 2; + EXPECT_EQ(meta.SetRecvQueryWaterMark(queryId, deviceId, queryWaterMark), E_OK); + EXPECT_EQ(meta.SetSendQueryWaterMark(queryId, deviceId, queryWaterMark), E_OK); + + /** + * @tc.steps: step4. initialize meta with storage + * @tc.expected: step4. E_OK + */ + Metadata anotherMeta; + ASSERT_EQ(anotherMeta.Initialize(&storage), E_OK); + + /** + * @tc.steps: step5. verify delete sync data + * @tc.expected: step5. E_OK and waterMark equal to deleteWaterMark + */ + WaterMark waterMark; + EXPECT_EQ(anotherMeta.GetRecvDeleteSyncWaterMark(deviceId, waterMark), E_OK); + EXPECT_EQ(waterMark, deleteWaterMark); + EXPECT_EQ(anotherMeta.GetSendDeleteSyncWaterMark(deviceId, waterMark), E_OK); + EXPECT_EQ(waterMark, deleteWaterMark); + + /** + * @tc.steps: step6. verify query sync data + * @tc.expected: step6. E_OK and waterMark equal to queryWaterMark + */ + EXPECT_EQ(anotherMeta.GetRecvQueryWaterMark(queryId, deviceId, waterMark), E_OK); + EXPECT_EQ(waterMark, queryWaterMark); + EXPECT_EQ(anotherMeta.GetSendQueryWaterMark(queryId, deviceId, waterMark), E_OK); + EXPECT_EQ(waterMark, queryWaterMark); +} + +/** + * @tc.name: VerifyLruMap 001 + * @tc.desc: Test metaData watermark cache lru ability. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, VerifyLruMap001, TestSize.Level1) +{ + LruMap lruMap; + const int maxCacheItems = 200; + + /** + * @tc.steps: step1. fill items to LruMap + * @tc.expected: step1. E_OK + */ + const int startCount = 0; + for (int i = startCount; i < maxCacheItems; i++) { + std::string key = std::to_string(i); + QueryWaterMark value; + value.recvWaterMark = i + 1; + EXPECT_EQ(lruMap.Put(key, value), E_OK); + } + + /** + * @tc.steps: step2. get the first item + * @tc.expected: step2. E_OK first item will move to last + */ + std::string firstItemKey = std::to_string(startCount); + QueryWaterMark firstItemValue; + EXPECT_EQ(lruMap.Get(firstItemKey, firstItemValue), E_OK); + EXPECT_EQ(firstItemValue.recvWaterMark, 1u); + + /** + * @tc.steps: step3. insert new items to LruMap + * @tc.expected: step3. the second items was removed + */ + std::string key = std::to_string(maxCacheItems); + QueryWaterMark value; + value.recvWaterMark = maxCacheItems; + EXPECT_EQ(lruMap.Put(key, value), E_OK); + + /** + * @tc.steps: step4. get the second item + * @tc.expected: step4. E_NOT_FOUND it was removed by algorithm + */ + std::string secondItemKey = std::to_string(startCount + 1); + QueryWaterMark secondItemValue; + EXPECT_EQ(lruMap.Get(secondItemKey, secondItemValue), -E_NOT_FOUND); + EXPECT_EQ(secondItemValue.recvWaterMark, 0u); +} + +/** + * @tc.name: VerifyMetaDataInit 001 + * @tc.desc: Test metaData init correctly + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, VerifyMetaDataInit001, TestSize.Level1) +{ + Metadata meta; + VirtualSingleVerSyncDBInterface storage; + + /** + * @tc.steps: step1. initialize meta with storage + * @tc.expected: step1. E_OK + */ + ASSERT_EQ(meta.Initialize(&storage), E_OK); + + DeviceID deviceA = "DeviceA"; + DeviceID deviceB = "DeviceA"; + WaterMark setWaterMark = 1; + + /** + * @tc.steps: step2. meta save and get waterMark + * @tc.expected: step2. expect get the same waterMark + */ + EXPECT_EQ(meta.SaveLocalWaterMark(deviceA, setWaterMark), E_OK); + EXPECT_EQ(meta.SaveLocalWaterMark(deviceB, setWaterMark), E_OK); + WaterMark getWaterMark = 0; + meta.GetLocalWaterMark(deviceA, getWaterMark); + EXPECT_EQ(getWaterMark, setWaterMark); + meta.GetLocalWaterMark(deviceB, getWaterMark); + EXPECT_EQ(getWaterMark, setWaterMark); + + + /** + * @tc.steps: step3. init again + * @tc.expected: step3. E_OK + */ + Metadata anotherMeta; + ASSERT_EQ(anotherMeta.Initialize(&storage), E_OK); + + /** + * @tc.steps: step4. get waterMark again + * @tc.expected: step4. expect get the same waterMark + */ + anotherMeta.GetLocalWaterMark(deviceA, getWaterMark); + EXPECT_EQ(getWaterMark, setWaterMark); + anotherMeta.GetLocalWaterMark(deviceB, getWaterMark); + EXPECT_EQ(getWaterMark, setWaterMark); +} + +namespace { +void InitVerifyStorageEnvironment(Metadata &meta, VirtualSingleVerSyncDBInterface &storage, + const std::string &deviceId, const int &startCount, const uint32_t &maxStoreItems) +{ + /** + * @tc.steps: step1. initialize meta with storage + * @tc.expected: step1. E_OK + */ + ASSERT_EQ(meta.Initialize(&storage), E_OK); + + /** + * @tc.steps: step2. fill items to metadata + * @tc.expected: step2. E_OK + */ + for (uint32_t i = startCount; i < maxStoreItems; i++) { + std::string queryId = std::to_string(i); + WaterMark recvWaterMark = i + 1; + EXPECT_EQ(meta.SetRecvQueryWaterMark(queryId, deviceId, recvWaterMark), E_OK); + } +} +} + +/** + * @tc.name: VerifyManagerQuerySyncStorage 001 + * @tc.desc: Test metaData remove least used querySync storage items. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, VerifyManagerQuerySyncStorage001, TestSize.Level3) +{ + Metadata meta; + VirtualSingleVerSyncDBInterface storage; + const uint32_t maxStoreItems = 100000; + const int startCount = 0; + const std::string deviceId = "Device"; + + InitVerifyStorageEnvironment(meta, storage, deviceId, startCount, maxStoreItems); + + /** + * @tc.steps: step3. insert new items to metadata + * @tc.expected: step3. E_OK + */ + std::string newQueryId = std::to_string(maxStoreItems); + WaterMark newWaterMark = maxStoreItems + 1; + EXPECT_EQ(meta.SetRecvQueryWaterMark(newQueryId, deviceId, newWaterMark), E_OK); + + /** + * @tc.steps: step4. touch the first item + * @tc.expected: step4. E_OK update first item used time + */ + std::string firstItemKey = std::to_string(startCount); + WaterMark firstWaterMark = 11u; + EXPECT_EQ(meta.SetRecvQueryWaterMark(firstItemKey, deviceId, firstWaterMark), E_OK); + + /** + * @tc.steps: step5. initialize new meta with storage + * @tc.expected: step5. the second item will be removed + */ + Metadata newMeta; + ASSERT_EQ(newMeta.Initialize(&storage), E_OK); + + /** + * @tc.steps: step6. touch the first item + * @tc.expected: step6. E_OK it still exist + */ + WaterMark exceptWaterMark; + EXPECT_EQ(newMeta.GetRecvQueryWaterMark(firstItemKey, deviceId, exceptWaterMark), E_OK); + EXPECT_EQ(exceptWaterMark, firstWaterMark); + + /** + * @tc.steps: step7. get the second item + * @tc.expected: step7. NOT_FOUND secondWaterMark is zero + */ + WaterMark secondWaterMark; + std::string secondQueryId = std::to_string(startCount + 1); + EXPECT_EQ(newMeta.GetRecvQueryWaterMark(secondQueryId, deviceId, secondWaterMark), E_OK); + EXPECT_EQ(secondWaterMark, 0u); +} + +/** + * @tc.name: VerifyMetaDbCreateTime 001 + * @tc.desc: Test metaData get and set cbCreateTime. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, VerifyMetaDbCreateTime001, TestSize.Level1) +{ + Metadata meta; + VirtualSingleVerSyncDBInterface storage; + /** + * @tc.steps: step1. initialize meta with storage + * @tc.expected: step1. E_OK + */ + int errCode = meta.Initialize(&storage); + ASSERT_EQ(errCode, E_OK); + /** + * @tc.steps: step2. set local and peer watermark and dbCreateTime + * @tc.expected: step4. E_OK + */ + WaterMark value = 2; + EXPECT_EQ(meta.SaveLocalWaterMark("D1", value), E_OK); + EXPECT_EQ(meta.SavePeerWaterMark("D1", value, true), E_OK); + EXPECT_EQ(meta.SetDbCreateTime("D1", 10u, true), E_OK); + /** + * @tc.steps: step3. check peer and local watermark and dbCreateTime + * @tc.expected: step4. E_OK + */ + WaterMark curValue = 0; + meta.GetLocalWaterMark("D1", curValue); + EXPECT_EQ(value, curValue); + meta.GetPeerWaterMark("D1", curValue); + EXPECT_EQ(value, curValue); + uint64_t curDbCreatTime = 0; + meta.GetDbCreateTime("D1", curDbCreatTime); + EXPECT_EQ(curDbCreatTime, 10u); + /** + * @tc.steps: step3. change dbCreateTime and check + * @tc.expected: step4. E_OK + */ + EXPECT_EQ(meta.SetDbCreateTime("D1", 20u, true), E_OK); + uint64_t clearDeviceDataMark = INT_MAX; + meta.GetRemoveDataMark("D1", clearDeviceDataMark); + EXPECT_EQ(clearDeviceDataMark, 1u); + EXPECT_EQ(meta.ResetMetaDataAfterRemoveData("D1"), E_OK); + meta.GetRemoveDataMark("D1", clearDeviceDataMark); + EXPECT_EQ(clearDeviceDataMark, 0u); + meta.GetDbCreateTime("D1", curDbCreatTime); + EXPECT_EQ(curDbCreatTime, 20u); +} + +/** + * @tc.name: VerifyManagerQuerySyncStorage 002 + * @tc.desc: Test metaData remove least used querySync storage items when exit wrong data. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, VerifyManagerQuerySyncStorage002, TestSize.Level3) +{ + Metadata meta; + VirtualSingleVerSyncDBInterface storage; + const uint32_t maxStoreItems = 100000; + const int startCount = 0; + const std::string deviceId = "Device"; + + InitVerifyStorageEnvironment(meta, storage, deviceId, startCount, maxStoreItems); + + /** + * @tc.steps: step3. insert a wrong Value + * @tc.expected: step3. E_OK + */ + std::string newQueryId = std::to_string(maxStoreItems); + Key dbKey; + DBCommon::StringToVector(QuerySyncWaterMarkHelper::GetQuerySyncPrefixKey() + + DBCommon::TransferHashString(deviceId) + newQueryId, dbKey); + Value wrongValue; + EXPECT_EQ(storage.PutMetaData(dbKey, wrongValue), E_OK); + + /** + * @tc.steps: step4. initialize new meta with storage + * @tc.expected: step4. E_OK + */ + Metadata newMeta; + ASSERT_EQ(newMeta.Initialize(&storage), E_OK); + + /** + * @tc.steps: step5. touch the first item + * @tc.expected: step5. E_OK still exit + */ + std::string firstItemKey = std::to_string(startCount); + WaterMark exceptWaterMark; + EXPECT_EQ(newMeta.GetRecvQueryWaterMark(firstItemKey, deviceId, exceptWaterMark), E_OK); + EXPECT_EQ(exceptWaterMark, 1u); +} + +/** + * @tc.name: AllPredicateQuerySync001 + * @tc.desc: Test normal push sync for AllPredicate data. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, AllPredicateQuerySync001, TestSize.Level1) +{ + /** + * @tc.steps: step1. InitSchemaDb + */ + InitSchemaDb(); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step2. deviceA put {key11, SCHEMA_VALUE1} - {key19, SCHEMA_VALUE1} + {key21, SCHEMA_VALUE2} - {key29, SCHEMA_VALUE2} + */ + Value value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end()); + Value value2(SCHEMA_VALUE2.begin(), SCHEMA_VALUE2.end()); + Key key = {'1'}; + Key key2 = {'2'}; + const int dataSize = 4000; + for (int i = 0; i < dataSize; i++) { + key.push_back(i); + key2.push_back(i); + status = g_schemaKvDelegatePtr->Put(key, value); + status = g_schemaKvDelegatePtr->Put(key2, value2); + ASSERT_TRUE(status == OK); + key.pop_back(); + key2.pop_back(); + } + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call query sync and wait + * @tc.expected: step3. sync should return OK. + */ + Query query = Query::Select().EqualTo("$.field_name1", 1); + std::map result; + status = g_tool.SyncTest(g_schemaKvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result, query); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step4. onComplete should be called, DeviceB have {key11, SCHEMA_VALUE1} - {key19, SCHEMA_VALUE1} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + VirtualDataItem item2; + for (int i = 0; i < dataSize; i++) { + key.push_back(i); + key2.push_back(i); + g_deviceB->GetData(key, item); + EXPECT_TRUE(g_deviceB->GetData(key2, item2) != E_OK); + EXPECT_TRUE(item.value == value); + key.pop_back(); + key2.pop_back(); + } +} + +/** + * @tc.name: AllPredicateQuerySync002 + * @tc.desc: Test wrong query param push sync for AllPredicate data. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, AllPredicateQuerySync002, TestSize.Level1) +{ + /** + * @tc.steps: step1. InitSchemaDb + */ + InitSchemaDb(); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step2. deviceA call query sync and wait + * @tc.expected: step2. sync should return INVALID_QUERY_FIELD + */ + Query query = Query::Select().GreaterThan("field_name11", 10); + std::map result; + status = g_tool.SyncTest(g_schemaKvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result, query); + ASSERT_TRUE(status == INVALID_QUERY_FIELD); + status = g_tool.SyncTest(g_schemaKvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, result, query); + ASSERT_TRUE(status == INVALID_QUERY_FIELD); + status = g_tool.SyncTest(g_schemaKvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result, query); + ASSERT_TRUE(status == INVALID_QUERY_FIELD); +} + +/** + * @tc.name: AllPredicateQuerySync003 + * @tc.desc: Test normal push sync for AllPredicate data with limit + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, AllPredicateQuerySync003, TestSize.Level1) +{ + /** + * @tc.steps: step1. InitSchemaDb + */ + InitSchemaDb(); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step2. deviceA put {key1, SCHEMA_VALUE1} - {key9, SCHEMA_VALUE1} + */ + Value value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end()); + Value value2(SCHEMA_VALUE2.begin(), SCHEMA_VALUE2.end()); + Key key = {'1'}; + Key key2 = {'2'}; + const int dataSize = 10; + for (int i = 0; i < dataSize; i++) { + key.push_back(i); + key2.push_back(i); + status = g_schemaKvDelegatePtr->Put(key, value); + status = g_schemaKvDelegatePtr->Put(key2, value2); + ASSERT_TRUE(status == OK); + key.pop_back(); + key2.pop_back(); + } + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call query sync with limit and wait + * @tc.expected: step3. sync should return OK. + */ + Query query = Query::Select().EqualTo("$.field_name1", 1).Limit(20, 0); + std::map result; + status = g_tool.SyncTest(g_schemaKvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result, query); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step4. onComplete should be called, DeviceB have {key1, SCHEMA_VALUE1} - {key9, SCHEMA_VALUE1} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + VirtualDataItem item2; + for (int i = 0; i < dataSize; i++) { + key.push_back(i); + key2.push_back(i); + g_deviceB->GetData(key, item); + EXPECT_TRUE(g_deviceB->GetData(key2, item2) != E_OK); + EXPECT_TRUE(item.value == value); + key.pop_back(); + key2.pop_back(); + } +} + +/** + * @tc.name: AllPredicateQuerySync004 + * @tc.desc: Test normal pull sync for AllPredicate data. + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PQuerySyncTest, AllPredicateQuerySync004, TestSize.Level1) +{ + /** + * @tc.steps: step1. InitSchemaDb + */ + InitSchemaDb(); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step2. deviceB put {key11, SCHEMA_VALUE1} - {key19, SCHEMA_VALUE1} + */ + Value value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end()); + Key key = {'1'}; + const int dataSize = 10; + for (int i = 0; i < dataSize; i++) { + key.push_back(i); + g_deviceB->PutData(key, value, 10 + i, 0); + ASSERT_TRUE(status == OK); + key.pop_back(); + } + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call query sync and wait + * @tc.expected: step3. sync should return OK. + */ + Query query = Query::Select().EqualTo("$.field_name1", 1); + std::map result; + status = g_tool.SyncTest(g_schemaKvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result, query); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step4. onComplete should be called, DeviceA have {key11, SCHEMA_VALUE1} - {key19, SCHEMA_VALUE1} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + Value item; + Value item2; + for (int i = 0; i < dataSize; i++) { + key.push_back(i); + g_schemaKvDelegatePtr->Get(key, item); + EXPECT_TRUE(item == value); + key.pop_back(); + } +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_subsribe_sync_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_subsribe_sync_test.cpp new file mode 100644 index 00000000..0d7037fb --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_subsribe_sync_test.cpp @@ -0,0 +1,881 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_constant.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "kv_store_nb_delegate.h" +#include "kv_virtual_device.h" +#include "platform_specific.h" +#include "query.h" +#include "query_sync_object.h" +#include "single_ver_data_sync.h" +#include "single_ver_serialize_manager.h" +#include "subscribe_manager.h" +#include "sync_types.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + const string SCHEMA_STORE_ID = "kv_store_sync_schema_test"; + const std::string DEVICE_A = "deviceA"; + const std::string DEVICE_B = "deviceB"; + + KvStoreDelegateManager g_schemaMgr(SCHEMA_APP_ID, USER_ID); + KvStoreConfig g_config; + DistributedDBToolsUnitTest g_tool; + DBStatus g_schemaKvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate* g_schemaKvDelegatePtr = nullptr; + VirtualCommunicatorAggregator* g_communicatorAggregator = nullptr; + KvVirtualDevice* g_deviceB = nullptr; + + // the type of g_kvDelegateCallback is function + auto g_schemaKvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_schemaKvDelegateStatus), std::ref(g_schemaKvDelegatePtr)); + const string SCHEMA_STRING = + "{\"SCHEMA_VERSION\":\"1.0\"," + "\"SCHEMA_MODE\":\"STRICT\"," + "\"SCHEMA_DEFINE\":{" + "\"field_name1\":\"BOOL\"," + "\"field_name2\":\"BOOL\"," + "\"field_name3\":\"INTEGER, NOT NULL\"," + "\"field_name4\":\"LONG, DEFAULT 100\"," + "\"field_name5\":\"DOUBLE, NOT NULL, DEFAULT 3.14\"," + "\"field_name6\":\"STRING, NOT NULL, DEFAULT '3.1415'\"," + "\"field_name7\":\"LONG, DEFAULT 100\"," + "\"field_name8\":\"LONG, DEFAULT 100\"," + "\"field_name9\":\"LONG, DEFAULT 100\"," + "\"field_name10\":\"LONG, DEFAULT 100\"" + "}," + "\"SCHEMA_INDEXES\":[\"$.field_name1\", \"$.field_name2\"]}"; + + const std::string SCHEMA_VALUE1 = + "{\"field_name1\":true," + "\"field_name2\":false," + "\"field_name3\":10," + "\"field_name4\":20," + "\"field_name5\":3.14," + "\"field_name6\":\"3.1415\"," + "\"field_name7\":100," + "\"field_name8\":100," + "\"field_name9\":100," + "\"field_name10\":100}"; + + const std::string SCHEMA_VALUE2 = + "{\"field_name1\":false," + "\"field_name2\":true," + "\"field_name3\":100," + "\"field_name4\":200," + "\"field_name5\":3.14," + "\"field_name6\":\"3.1415\"," + "\"field_name7\":100," + "\"field_name8\":100," + "\"field_name9\":100," + "\"field_name10\":100}"; +} + +class DistributedDBSingleVerP2PSubscribeSyncTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBSingleVerP2PSubscribeSyncTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Init datadir and Virtual Communicator. + */ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_schemaMgr.SetKvStoreConfig(g_config); + + string dir = g_testDir + "/single_ver"; + DIR* dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } + + g_communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator(); + ASSERT_TRUE(g_communicatorAggregator != nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(g_communicatorAggregator); +} + +void DistributedDBSingleVerP2PSubscribeSyncTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: Release virtual Communicator and clear data dir. + */ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); +} + +void DistributedDBSingleVerP2PSubscribeSyncTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: create virtual device B and get a KvStoreNbDelegate as deviceA + */ + g_deviceB = new (std::nothrow) KvVirtualDevice(DEVICE_B); + ASSERT_TRUE(g_deviceB != nullptr); + VirtualSingleVerSyncDBInterface *syncInterfaceB = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(syncInterfaceB != nullptr); + ASSERT_EQ(g_deviceB->Initialize(g_communicatorAggregator, syncInterfaceB), E_OK); +} + +void DistributedDBSingleVerP2PSubscribeSyncTest::TearDown(void) +{ + /** + * @tc.teardown: Release device A, B + */ + if (g_schemaKvDelegatePtr != nullptr) { + ASSERT_EQ(g_schemaMgr.CloseKvStore(g_schemaKvDelegatePtr), OK); + g_schemaKvDelegatePtr = nullptr; + DBStatus status = g_schemaMgr.DeleteKvStore(SCHEMA_STORE_ID); + LOGD("delete kv store status %d", status); + ASSERT_TRUE(status == OK); + } + if (g_deviceB != nullptr) { + delete g_deviceB; + g_deviceB = nullptr; + } + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_schemaMgr.SetPermissionCheckCallback(nullCallback), OK); +} + +void InitSubSchemaDb() +{ + g_config.dataDir = g_testDir; + g_schemaMgr.SetKvStoreConfig(g_config); + KvStoreNbDelegate::Option option; + option.schema = SCHEMA_STRING; + g_schemaMgr.GetKvStore(SCHEMA_STORE_ID, option, g_schemaKvDelegateCallback); + ASSERT_TRUE(g_schemaKvDelegateStatus == OK); + ASSERT_TRUE(g_schemaKvDelegatePtr != nullptr); +} + +void CheckUnFinishedMap(uint32_t sizeA, uint32_t sizeB, std::vector &deviceAQueies, + std::vector &deviceBQueies, SubscribeManager &subManager) +{ + std::map> allSyncQueries; + subManager.GetAllUnFinishSubQueries(allSyncQueries); + ASSERT_TRUE(allSyncQueries[DEVICE_A].size() == sizeA); + ASSERT_TRUE(allSyncQueries[DEVICE_B].size() == sizeB); + for (auto &item : allSyncQueries[DEVICE_A]) { + std::string queryId = item.GetIdentify(); + ASSERT_TRUE(std::find(deviceAQueies.begin(), deviceAQueies.end(), queryId) != deviceAQueies.end()); + } + for (auto &item : allSyncQueries[DEVICE_B]) { + std::string queryId = item.GetIdentify(); + ASSERT_TRUE(std::find(deviceBQueies.begin(), deviceBQueies.end(), queryId) != deviceBQueies.end()); + } +} + +void InitLocalSubscribeMap(QuerySyncObject &queryCommonObj, std::map &queryMap, + std::vector &deviceAQueies, std::vector &deviceBQueies, SubscribeManager &subManager) +{ + ASSERT_TRUE(subManager.ReserveLocalSubscribeQuery(DEVICE_A, queryCommonObj) == E_OK); + ASSERT_TRUE(subManager.ActiveLocalSubscribeQuery(DEVICE_A, queryCommonObj) == E_OK); + ASSERT_TRUE(subManager.ReserveLocalSubscribeQuery(DEVICE_B, queryCommonObj) == E_OK); + ASSERT_TRUE(subManager.ActiveLocalSubscribeQuery(DEVICE_B, queryCommonObj) == E_OK); + queryMap[queryCommonObj.GetIdentify()] = queryCommonObj; + deviceAQueies.push_back(queryCommonObj.GetIdentify()); + deviceBQueies.push_back(queryCommonObj.GetIdentify()); + for (int i = 0; i < 3; i++) { // 3 subscribe + QuerySyncObject querySyncObj(Query::Select().PrefixKey({'a', static_cast('a' + i)})); + deviceAQueies.push_back(querySyncObj.GetIdentify()); + queryMap[querySyncObj.GetIdentify()] = querySyncObj; + ASSERT_TRUE(subManager.ReserveLocalSubscribeQuery(DEVICE_A, querySyncObj) == E_OK); + ASSERT_TRUE(subManager.ActiveLocalSubscribeQuery(DEVICE_A, querySyncObj) == E_OK); + } + for (int i = 0; i < 1; i++) { + QuerySyncObject querySyncObj(Query::Select().PrefixKey({'a', static_cast('b' + i)})); + deviceBQueies.push_back(querySyncObj.GetIdentify()); + queryMap[querySyncObj.GetIdentify()] = querySyncObj; + ASSERT_TRUE(subManager.ReserveLocalSubscribeQuery(DEVICE_B, querySyncObj) == E_OK); + ASSERT_TRUE(subManager.ActiveLocalSubscribeQuery(DEVICE_B, querySyncObj) == E_OK); + } +} +/** + * @tc.name: SubscribeRequestTest001 + * @tc.desc: test Serialize/DoSerialize SubscribeRequest + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PSubscribeSyncTest, SubscribeRequestTest001, TestSize.Level1) +{ + /** + * @tc.steps: step1. prepare a SubscribeRequest. + */ + auto packet = new (std::nothrow) SubscribeRequest; + ASSERT_TRUE(packet != nullptr); + packet->SetPacketHead(100, SOFTWARE_VERSION_CURRENT, SUBSCRIBE_QUERY_CMD, 1); + Query query = Query::Select().EqualTo("$.field_name1", 1); + QuerySyncObject syncQuery(query); + packet->SetQuery(syncQuery); + + /** + * @tc.steps: step2. put the SubscribeRequest Packet into a message. + */ + Message msg; + msg.SetExternalObject(packet); + msg.SetMessageId(CONTROL_SYNC_MESSAGE); + msg.SetMessageType(TYPE_REQUEST); + + /** + * @tc.steps: step3. Serialization the message to a buffer. + */ + int len = SingleVerSerializeManager::CalculateLen(&msg); + LOGE("test leng = %d", len); + uint8_t *buffer = new (nothrow) uint8_t[len]; + ASSERT_TRUE(buffer != nullptr); + ASSERT_EQ(SingleVerSerializeManager::Serialization(buffer, len, &msg), E_OK); + + /** + * @tc.steps: step4. DeSerialization the buffer to a message. + */ + Message outMsg; + outMsg.SetMessageId(CONTROL_SYNC_MESSAGE); + outMsg.SetMessageType(TYPE_REQUEST); + ASSERT_EQ(SingleVerSerializeManager::DeSerialization(buffer, len, &outMsg), E_OK); + + /** + * @tc.steps: step5. checkout the outMsg. + * @tc.expected: step5. outMsg equal the the in msg + */ + auto outPacket = outMsg.GetObject(); + EXPECT_EQ(outPacket->GetVersion(), SOFTWARE_VERSION_CURRENT); + EXPECT_EQ(outPacket->GetSendCode(), 100); + EXPECT_EQ(outPacket->GetcontrolCmdType(), SUBSCRIBE_QUERY_CMD); + EXPECT_EQ(outPacket->GetFlag(), 1u); + EXPECT_EQ(outPacket->GetQuery().GetIdentify(), syncQuery.GetIdentify()); + delete[] buffer; +} + +/** + * @tc.name: ControlAckTest001 + * @tc.desc: test Serialize/DoSerialize ControlAckPacket + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PSubscribeSyncTest, ControlAckTest001, TestSize.Level1) +{ + /** + * @tc.steps: step1. prepare a ControlAckPacket. + */ + ControlAckPacket packet; + packet.SetPacketHead(-E_NOT_SUPPORT, SOFTWARE_VERSION_CURRENT, SUBSCRIBE_QUERY_CMD, 1); + + /** + * @tc.steps: step2. put the QuerySyncAckPacket into a message. + */ + Message msg; + msg.SetCopiedObject(packet); + msg.SetMessageId(CONTROL_SYNC_MESSAGE); + msg.SetMessageType(TYPE_RESPONSE); + + /** + * @tc.steps: step3. Serialization the message to a buffer. + */ + int len = SingleVerSerializeManager::CalculateLen(&msg); + LOGE("test leng = %d", len); + uint8_t *buffer = new (nothrow) uint8_t[len]; + ASSERT_TRUE(buffer != nullptr); + int errCode = SingleVerSerializeManager::Serialization(buffer, len, &msg); + ASSERT_EQ(errCode, E_OK); + + /** + * @tc.steps: step4. DeSerialization the buffer to a message. + */ + Message outMsg; + outMsg.SetMessageId(CONTROL_SYNC_MESSAGE); + outMsg.SetMessageType(TYPE_RESPONSE); + errCode = SingleVerSerializeManager::DeSerialization(buffer, len, &outMsg); + ASSERT_EQ(errCode, E_OK); + + /** + * @tc.steps: step5. checkout the outMsg. + * @tc.expected: step5. outMsg equal the the in msg + */ + auto outPacket = outMsg.GetObject(); + EXPECT_EQ(outPacket->GetVersion(), SOFTWARE_VERSION_CURRENT); + EXPECT_EQ(outPacket->GetRecvCode(), -E_NOT_SUPPORT); + EXPECT_EQ(outPacket->GetcontrolCmdType(), SUBSCRIBE_QUERY_CMD); + EXPECT_EQ(outPacket->GetFlag(), 1u); + delete[] buffer; +} + +/** + * @tc.name: subscribeManager001 + * @tc.desc: test subscribe class subscribe local function with one device + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PSubscribeSyncTest, subscribeManager001, TestSize.Level1) +{ + SubscribeManager subManager; + std::string device = "device_A"; + /** + * @tc.steps: step1. test one device limit four subscribe queries in local map + */ + LOGI("============step 1============"); + for (int i = 0; i < 4; i++) { + QuerySyncObject querySyncObj(Query::Select().PrefixKey({'a', static_cast('a' + i)})); + ASSERT_TRUE(subManager.ReserveLocalSubscribeQuery(device, querySyncObj) == E_OK); + ASSERT_TRUE(subManager.ActiveLocalSubscribeQuery(device, querySyncObj) == E_OK); + } + std::vector subscribeQueries; + subManager.GetLocalSubscribeQueries(device, subscribeQueries); + ASSERT_TRUE(subscribeQueries.size() == 4); + subscribeQueries.clear(); + QuerySyncObject querySyncObj1(Query::Select().PrefixKey({'a', static_cast('a' + 4)})); + int errCode = subManager.ReserveLocalSubscribeQuery(device, querySyncObj1); + ASSERT_TRUE(errCode != E_OK); + /** + * @tc.steps: step2. allow to subscribe existed query + */ + LOGI("============step 2============"); + QuerySyncObject querySyncObj2(Query::Select().PrefixKey({'a', static_cast('a' + 3)})); + ASSERT_TRUE(subManager.ReserveLocalSubscribeQuery(device, querySyncObj2) == E_OK); + ASSERT_TRUE(subManager.ActiveLocalSubscribeQuery(device, querySyncObj2) == E_OK); + subManager.GetLocalSubscribeQueries(device, subscribeQueries); + ASSERT_TRUE(subscribeQueries.size() == 4); + subscribeQueries.clear(); + /** + * @tc.steps: step3. unsubscribe no existed queries + */ + LOGI("============step 3============"); + subManager.RemoveLocalSubscribeQuery(device, querySyncObj1); + subManager.GetLocalSubscribeQueries(device, subscribeQueries); + ASSERT_TRUE(subscribeQueries.size() == 4); + subscribeQueries.clear(); + /** + * @tc.steps: step4. unsubscribe queries + */ + LOGI("============step 4============"); + for (int i = 0; i < 4; i++) { + QuerySyncObject querySyncObj(Query::Select().PrefixKey({'a', static_cast('a' + i)})); + subManager.RemoveLocalSubscribeQuery(device, querySyncObj); + } + subManager.GetLocalSubscribeQueries(device, subscribeQueries); + ASSERT_TRUE(subscribeQueries.size() == 0); + + /** + * @tc.steps: step5. reserve twice while subscribe queries + */ + LOGI("============step 5============"); + ASSERT_TRUE(subManager.ReserveLocalSubscribeQuery(device, querySyncObj2) == E_OK); + ASSERT_TRUE(subManager.ReserveLocalSubscribeQuery(device, querySyncObj2) == E_OK); + ASSERT_TRUE(subManager.ActiveLocalSubscribeQuery(device, querySyncObj2) == E_OK); + subManager.GetLocalSubscribeQueries(device, subscribeQueries); + ASSERT_TRUE(subscribeQueries.size() == 1); + subscribeQueries.clear(); + subManager.RemoveLocalSubscribeQuery(device, querySyncObj2); + subManager.GetLocalSubscribeQueries(device, subscribeQueries); + ASSERT_TRUE(subscribeQueries.size() == 0); +} + +/** + * @tc.name: subscribeManager002 + * @tc.desc: test subscribe class subscribe remote function with one device + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PSubscribeSyncTest, subscribeManager002, TestSize.Level1) +{ + SubscribeManager subManager; + std::string device = "device_A"; + /** + * @tc.steps: step1. test one device limit four subscribe queries in remote map + */ + LOGI("============step 1============"); + for (int i = 0; i < 4; i++) { + QuerySyncObject querySyncObj(Query::Select().PrefixKey({'a', static_cast('a' + i)})); + ASSERT_TRUE(subManager.ReserveRemoteSubscribeQuery(device, querySyncObj) == E_OK); + ASSERT_TRUE(subManager.ActiveRemoteSubscribeQuery(device, querySyncObj) == E_OK); + } + QuerySyncObject querySyncObj1(Query::Select().PrefixKey({'a', static_cast('a' + 4)})); + ASSERT_TRUE(subManager.ReserveRemoteSubscribeQuery(device, querySyncObj1) != E_OK); + std::vector subscribeQueryId; + subManager.GetRemoteSubscribeQueryIds(device, subscribeQueryId); +ASSERT_TRUE(subscribeQueryId.size() == 4); + subscribeQueryId.clear(); + /** + * @tc.steps: step2. allow to subscribe existed query + */ + LOGI("============step 2============"); + QuerySyncObject querySyncObj2(Query::Select().PrefixKey({'a', static_cast('a' + 3)})); + ASSERT_TRUE(subManager.ReserveRemoteSubscribeQuery(device, querySyncObj2) == E_OK); + ASSERT_TRUE(subManager.ActiveRemoteSubscribeQuery(device, querySyncObj2) == E_OK); + subManager.GetRemoteSubscribeQueryIds(device, subscribeQueryId); + ASSERT_TRUE(subscribeQueryId.size() == 4); + subscribeQueryId.clear(); + /** + * @tc.steps: step3. unsubscribe no existed queries + */ + LOGI("============step 3============"); + subManager.RemoveRemoteSubscribeQuery(device, querySyncObj1); + subManager.GetRemoteSubscribeQueryIds(device, subscribeQueryId); + ASSERT_TRUE(subscribeQueryId.size() == 4); + subscribeQueryId.clear(); + /** + * @tc.steps: step4. unsubscribe queries + */ + LOGI("============step 4============"); + for (int i = 0; i < 4; i++) { + QuerySyncObject querySyncObj(Query::Select().PrefixKey({'a', static_cast('a' + i)})); + subManager.RemoveRemoteSubscribeQuery(device, querySyncObj); + } + subManager.GetRemoteSubscribeQueryIds(device, subscribeQueryId); + ASSERT_TRUE(subscribeQueryId.size() == 0); +} + +/** + * @tc.name: subscribeManager003 + * @tc.desc: test subscribe class subscribe remote function with multi device + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PSubscribeSyncTest, subscribeManager003, TestSize.Level1) +{ + SubscribeManager subManager; + std::string device = "device_"; + std::vector subscribeQueries; + /** + * @tc.steps: step1. test mutil device limit 32 devices in remote map and check each device has one subscribe + */ + LOGI("============step 1============"); + QuerySyncObject querySyncObj(Query::Select().PrefixKey({'a', static_cast('a' + 1)})); + for (int i = 0; i < 32; i++) { + ASSERT_TRUE(subManager.ReserveLocalSubscribeQuery(device + std::to_string(i), querySyncObj) == E_OK); + ASSERT_TRUE(subManager.ActiveLocalSubscribeQuery(device + std::to_string(i), querySyncObj) == E_OK); + } + ASSERT_TRUE(subManager.ReserveLocalSubscribeQuery(device + std::to_string(33), querySyncObj) != E_OK); + for (int i = 0; i < 32; i++) { + subManager.GetLocalSubscribeQueries(device + std::to_string(i), subscribeQueries); + ASSERT_TRUE(subscribeQueries.size() == 1); + subscribeQueries.clear(); + } + /** + * @tc.steps: step2. clear remote subscribe query map and check each device has no subscribe + */ + LOGI("============step 2============"); + for (int i = 0; i < 32; i++) { + subManager.ClearLocalSubscribeQuery(device + std::to_string(i)); + subManager.GetLocalSubscribeQueries(device + std::to_string(i), subscribeQueries); + ASSERT_TRUE(subscribeQueries.size() == 0); + subscribeQueries.clear(); + } + /** + * @tc.steps: step3. test mutil device limit 8 queries in db and check each device has one subscribe + */ + LOGI("============step 3============"); + for (int i = 0; i < 8; i++) { + QuerySyncObject querySyncObj(Query::Select().PrefixKey({'a', static_cast('a' + i)})); + ASSERT_TRUE(subManager.ReserveLocalSubscribeQuery(device + std::to_string(i), querySyncObj) == E_OK); + ASSERT_TRUE(subManager.ActiveLocalSubscribeQuery(device + std::to_string(i), querySyncObj) == E_OK); + } + QuerySyncObject querySyncObj1(Query::Select().PrefixKey({'a', static_cast('a' + 8)})); + ASSERT_TRUE(subManager.ReserveLocalSubscribeQuery(device + std::to_string(8), querySyncObj1) != E_OK); +} + +/** + * @tc.name: subscribeManager004 + * @tc.desc: test subscribe class subscribe remote function with multi device + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PSubscribeSyncTest, subscribeManager004, TestSize.Level1) +{ + SubscribeManager subManager; + std::string device = "device_"; + std::vector subscribeQueryId; + /** + * @tc.steps: step1. test mutil device limit 32 devices in remote map and check each device has one subscribe + */ + LOGI("============step 1============"); + QuerySyncObject querySyncObj(Query::Select().PrefixKey({'a', static_cast('a' + 1)})); + for (int i = 0; i < 32; i++) { + ASSERT_TRUE(subManager.ReserveRemoteSubscribeQuery(device + std::to_string(i), querySyncObj) == E_OK); + ASSERT_TRUE(subManager.ActiveRemoteSubscribeQuery(device + std::to_string(i), querySyncObj) == E_OK); + } + ASSERT_TRUE(subManager.ReserveRemoteSubscribeQuery(device + std::to_string(33), querySyncObj) != E_OK); + for (int i = 0; i < 32; i++) { + subManager.GetRemoteSubscribeQueryIds(device + std::to_string(i), subscribeQueryId); + ASSERT_TRUE(subscribeQueryId.size() == 1); + subscribeQueryId.clear(); + } + /** + * @tc.steps: step2. clear remote subscribe query map and check each device has no subscribe + */ + LOGI("============step 2============"); + for (int i = 0; i < 32; i++) { + subManager.ClearRemoteSubscribeQuery(device + std::to_string(i)); + subManager.GetRemoteSubscribeQueryIds(device + std::to_string(i), subscribeQueryId); + ASSERT_TRUE(subscribeQueryId.size() == 0); + subscribeQueryId.clear(); + } + subManager.ClearRemoteSubscribeQuery(device); + /** + * @tc.steps: step3. test mutil device limit 8 queries in db and check each device has one subscribe + */ + LOGI("============step 3============"); + for (int i = 0; i < 8; i++) { + QuerySyncObject querySyncObj(Query::Select().PrefixKey({'a', static_cast('a' + i)})); + ASSERT_TRUE(subManager.ReserveRemoteSubscribeQuery(device + std::to_string(i), querySyncObj) == E_OK); + ASSERT_TRUE(subManager.ActiveRemoteSubscribeQuery(device + std::to_string(i), querySyncObj) == E_OK); + } + QuerySyncObject querySyncObj1(Query::Select().PrefixKey({'a', static_cast('a' + 8)})); + ASSERT_TRUE(subManager.ReserveRemoteSubscribeQuery(device + std::to_string(8), querySyncObj1) != E_OK); +} + +/** + * @tc.name: subscribeManager005 + * @tc.desc: test subscribe class subscribe remote function with put into unfinished map + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PSubscribeSyncTest, subscribeManager005, TestSize.Level1) +{ + SubscribeManager subManager; + std::vector subscribeQueries; + std::map queryMap; + std::vector deviceAQueies; + std::vector deviceBQueies; + QuerySyncObject queryCommonObj(Query::Select().PrefixKey({'a'})); + /** + * @tc.steps: step1. test one devices has 4 subscribes and another has 2 in local map, put into unfinished map + */ + LOGI("============step 1============"); + InitLocalSubscribeMap(queryCommonObj, queryMap, deviceAQueies, deviceBQueies, subManager); + /** + * @tc.steps: step2. check all device unFinished subscribe queries and put into unfinished map + */ + LOGI("============step 2============"); + subManager.GetLocalSubscribeQueries(DEVICE_A, subscribeQueries); + ASSERT_TRUE(subscribeQueries.size() == 4); + subManager.PutLocalUnFiniedSubQueries(DEVICE_A, subscribeQueries); + subscribeQueries.clear(); + subManager.GetLocalSubscribeQueries(DEVICE_B, subscribeQueries); + ASSERT_TRUE(subscribeQueries.size() == 2); + subManager.PutLocalUnFiniedSubQueries(DEVICE_B, subscribeQueries); + subscribeQueries.clear(); + /** + * @tc.steps: step3. get all device unFinished subscribe queries and check + */ + LOGI("============step 3============"); + CheckUnFinishedMap(4, 2, deviceAQueies, deviceBQueies, subManager); + /** + * @tc.steps: step4. active some subscribe queries + */ + LOGI("============step 4============"); + subManager.ActiveLocalSubscribeQuery(DEVICE_A, queryCommonObj); + subManager.ActiveLocalSubscribeQuery(DEVICE_A, queryMap[deviceAQueies[3]]); + subManager.ActiveLocalSubscribeQuery(DEVICE_B, queryMap[deviceBQueies[1]]); + deviceAQueies.erase(deviceAQueies.begin() + 3); + deviceAQueies.erase(deviceAQueies.begin()); + queryMap.erase(queryMap[deviceBQueies[1]].GetIdentify()); + deviceBQueies.erase(deviceBQueies.begin() + 1); + /** + * @tc.steps: step5. get all device unFinished subscribe queries and check + */ + LOGI("============step 5============"); + CheckUnFinishedMap(2, 1, deviceAQueies, deviceBQueies, subManager); + /** + * @tc.steps: step6. remove left subscribe queries + */ + LOGI("============step 6============"); + for (int i = 0; i < 2; i++) { + QuerySyncObject querySyncObj(Query::Select().PrefixKey({'a', static_cast('a' + i)})); + subManager.RemoveLocalSubscribeQuery(DEVICE_A, querySyncObj); + } + subManager.RemoveLocalSubscribeQuery(DEVICE_A, queryCommonObj); + subManager.RemoveLocalSubscribeQuery(DEVICE_B, queryCommonObj); + /** + * @tc.steps: step7. get all device unFinished subscribe queries and check + */ + LOGI("============step 7============"); + CheckUnFinishedMap(0, 0, deviceAQueies, deviceBQueies, subManager); +} + +/** + * @tc.name: subscribeSync001 + * @tc.desc: test subscribe normal sync + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PSubscribeSyncTest, subscribeSync001, TestSize.Level1) +{ + /** + * @tc.steps: step1. InitSchemaDb + */ + LOGI("============step 1============"); + InitSubSchemaDb(); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + Query query = Query::Select().EqualTo("$.field_name1", 1); + QuerySyncObject querySyncObj(query); + + /** + * @tc.steps: step2. deviceB subscribe query to deviceA + */ + LOGI("============step 2============"); + g_deviceB->Subscribe(querySyncObj, true, 1); + + /** + * @tc.steps: step3. deviceA put {key1, SCHEMA_VALUE1} and wait 1s + */ + LOGI("============step 3============"); + Value value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end()); + Key key = {'1'}; + status = g_schemaKvDelegatePtr->Put(key, value); + EXPECT_EQ(status, OK); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + /** + * @tc.steps: step4. deviceB has {key11, SCHEMA_VALUE1} + */ + LOGI("============step 4============"); + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value == value); + + /** + * @tc.steps: step5. deviceB unsubscribe query to deviceA + */ + g_deviceB->UnSubscribe(querySyncObj, true, 2); + + /** + * @tc.steps: step5. deviceA put {key2, SCHEMA_VALUE1} and wait 1s + */ + LOGI("============step 5============"); + Value value2(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end()); + Key key2 = {'2'}; + status = g_schemaKvDelegatePtr->Put(key2, value2); + EXPECT_EQ(status, OK); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + /** + * @tc.steps: step6. deviceB don't has {key2, SCHEMA_VALUE1} + */ + LOGI("============step 6============"); + VirtualDataItem item2; + EXPECT_TRUE(g_deviceB->GetData(key2, item2) != E_OK); +} + +/** + * @tc.name: subscribeSync002 + * @tc.desc: test subscribe sync over 32 devices,limit,orderBy + * @tc.type: FUNC + * @tc.require: AR000FN6G9 + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PSubscribeSyncTest, subscribeSync002, TestSize.Level1) +{ + /** + * @tc.steps: step1. InitSchemaDb + */ + LOGI("============step 1============"); + InitSubSchemaDb(); + std::vector devices; + std::string device = "device_"; + Query query = Query::Select().EqualTo("$.field_name1", 1); + + /** + * @tc.steps: step2. deviceA subscribe query to 33 devices, and return overlimit + */ + LOGI("============step 2============"); + for (int i = 0; i < 33; i++) { + devices.push_back(device + std::to_string(i)); + } + EXPECT_TRUE(g_schemaKvDelegatePtr->SubscribeRemoteQuery(devices, nullptr, query, true) == OVER_MAX_LIMITS); + + /** + * @tc.steps: step3. deviceA subscribe query with limit + */ + LOGI("============step 3============"); + devices.clear(); + devices.push_back("device_B"); + Query query2 = Query::Select().EqualTo("$.field_name1", 1).Limit(20, 0); + EXPECT_TRUE(g_schemaKvDelegatePtr->SubscribeRemoteQuery(devices, nullptr, query2, true) == NOT_SUPPORT); + + /** + * @tc.steps: step4. deviceA subscribe query with orderBy + */ + LOGI("============step 4============"); + Query query3 = Query::Select().EqualTo("$.field_name1", 1).OrderBy("$.field_name7"); + EXPECT_TRUE(g_schemaKvDelegatePtr->SubscribeRemoteQuery(devices, nullptr, query3, true) == NOT_SUPPORT); +} + +/** + * @tc.name: subscribeSync003 + * @tc.desc: test subscribe sync with inkeys query + * @tc.type: FUNC + * @tc.require: AR000GOHO7 + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBSingleVerP2PSubscribeSyncTest, subscribeSync003, TestSize.Level1) +{ + /** + * @tc.steps: step1. InitSchemaDb + */ + LOGI("============step 1============"); + InitSubSchemaDb(); + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step2. deviceB subscribe inkeys(k2k4) query to deviceA + */ + LOGI("============step 2============"); + Query query = Query::Select().InKeys({KEY_2, KEY_4}); + g_deviceB->Subscribe(QuerySyncObject(query), true, 1); + + /** + * @tc.steps: step3. deviceA put k1-k5 and wait + */ + LOGI("============step 3============"); + EXPECT_EQ(OK, g_schemaKvDelegatePtr->PutBatch({ + {KEY_1, Value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end())}, + {KEY_2, Value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end())}, + {KEY_3, Value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end())}, + {KEY_4, Value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end())}, + {KEY_5, Value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end())}, + })); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + /** + * @tc.steps: step4. deviceB has k2k4, has no k1k3k5 + */ + LOGI("============step 4============"); + VirtualDataItem item; + EXPECT_EQ(g_deviceB->GetData(KEY_2, item), E_OK); + EXPECT_EQ(item.value, Value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end())); + EXPECT_EQ(g_deviceB->GetData(KEY_4, item), E_OK); + EXPECT_EQ(item.value, Value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end())); + EXPECT_EQ(g_deviceB->GetData(KEY_1, item), -E_NOT_FOUND); + EXPECT_EQ(g_deviceB->GetData(KEY_3, item), -E_NOT_FOUND); + EXPECT_EQ(g_deviceB->GetData(KEY_5, item), -E_NOT_FOUND); +} + +/** + * @tc.name: subscribeSync004 + * @tc.desc: test subscribe sync with inkeys query + * @tc.type: FUNC + * @tc.require: AR000GOHO7 + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBSingleVerP2PSubscribeSyncTest, subscribeSync004, TestSize.Level1) +{ + /** + * @tc.steps: step1. InitSchemaDb + */ + LOGI("============step 1============"); + InitSubSchemaDb(); + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step2. deviceB subscribe inkeys(k3k5) and equal to query to deviceA + */ + LOGI("============step 2============"); + Query query = Query::Select().InKeys({KEY_3, KEY_5}).EqualTo("$.field_name3", 100); // 100 for test. + g_deviceB->Subscribe(QuerySyncObject(query), true, 2); + + /** + * @tc.steps: step3. deviceA put k1v2,k3v2,k5v1 and wait + */ + LOGI("============step 3============"); + EXPECT_EQ(OK, g_schemaKvDelegatePtr->PutBatch({ + {KEY_1, Value(SCHEMA_VALUE2.begin(), SCHEMA_VALUE2.end())}, + {KEY_3, Value(SCHEMA_VALUE2.begin(), SCHEMA_VALUE2.end())}, + {KEY_5, Value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end())}, + })); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + /** + * @tc.steps: step4. deviceB has k3, has no k1k5 + */ + LOGI("============step 4============"); + VirtualDataItem item; + EXPECT_EQ(g_deviceB->GetData(KEY_3, item), E_OK); + EXPECT_EQ(item.value, Value(SCHEMA_VALUE2.begin(), SCHEMA_VALUE2.end())); + EXPECT_EQ(g_deviceB->GetData(KEY_1, item), -E_NOT_FOUND); + EXPECT_EQ(g_deviceB->GetData(KEY_5, item), -E_NOT_FOUND); +} + +/** + * @tc.name: subscribeSync005 + * @tc.desc: test subscribe sync with inkeys query + * @tc.type: FUNC + * @tc.require: AR000GOHO7 + * @tc.author: lidongwei + */ +HWTEST_F(DistributedDBSingleVerP2PSubscribeSyncTest, subscribeSync005, TestSize.Level1) +{ + /** + * @tc.steps: step1. InitSchemaDb + */ + LOGI("============step 1============"); + InitSubSchemaDb(); + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step2. deviceB subscribe inkeys(k1, key6) and prefix key "k" query to deviceA + */ + LOGI("============step 2============"); + Key key6 { 'k', '6' }; + Query query = Query::Select().InKeys({KEY_1, key6}).PrefixKey({ 'k' }); + g_deviceB->Subscribe(QuerySyncObject(query), true, 3); + + /** + * @tc.steps: step3. deviceA put k1,key6 and wait + */ + LOGI("============step 3============"); + EXPECT_EQ(OK, g_schemaKvDelegatePtr->PutBatch({ + {key6, Value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end())}, + {KEY_1, Value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end())}, + })); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + /** + * @tc.steps: step4. deviceB has key6, has no k1 + */ + LOGI("============step 4============"); + VirtualDataItem item; + EXPECT_EQ(g_deviceB->GetData(key6, item), E_OK); + EXPECT_EQ(item.value, Value(SCHEMA_VALUE1.begin(), SCHEMA_VALUE1.end())); + EXPECT_EQ(g_deviceB->GetData(KEY_1, item), -E_NOT_FOUND); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_sync_check_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_sync_check_test.cpp new file mode 100644 index 00000000..7fc131be --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_sync_check_test.cpp @@ -0,0 +1,1273 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "kv_virtual_device.h" +#include "platform_specific.h" +#include "process_system_api_adapter_impl.h" +#include "single_ver_data_packet.h" +#include "virtual_communicator_aggregator.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + const string STORE_ID = "kv_stroe_sync_check_test"; + const std::string DEVICE_B = "deviceB"; + const std::string DEVICE_C = "deviceC"; + const int LOCAL_WATER_MARK_NOT_INIT = 0xaa; + const int EIGHT_HUNDRED = 800; + const int NORMAL_SYNC_SEND_REQUEST_CNT = 3; + const int TWO_CNT = 2; + const int SLEEP_MILLISECONDS = 500; + const int TEN_SECONDS = 10; + const int THREE_HUNDRED = 300; + + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + DistributedDBToolsUnitTest g_tool; + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate* g_kvDelegatePtr = nullptr; + VirtualCommunicatorAggregator* g_communicatorAggregator = nullptr; + KvVirtualDevice* g_deviceB = nullptr; + KvVirtualDevice* g_deviceC = nullptr; + VirtualSingleVerSyncDBInterface *g_syncInterfaceB = nullptr; + VirtualSingleVerSyncDBInterface *g_syncInterfaceC = nullptr; + + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); +#ifndef LOW_LEVEL_MEM_DEV + const int KEY_LEN = 20; // 20 Bytes + const int VALUE_LEN = 4 * 1024 * 1024; // 4MB + const int ENTRY_NUM = 2; // 16 entries +#endif +} + +class DistributedDBSingleVerP2PSyncCheckTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBSingleVerP2PSyncCheckTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Init datadir and Virtual Communicator. + */ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + + string dir = g_testDir + "/single_ver"; + DIR* dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } + + g_communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator(); + ASSERT_TRUE(g_communicatorAggregator != nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(g_communicatorAggregator); + + std::shared_ptr g_adapter = std::make_shared(); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(g_adapter); +} + +void DistributedDBSingleVerP2PSyncCheckTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: Release virtual Communicator and clear data dir. + */ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); + RuntimeContext::GetInstance()->SetProcessSystemApiAdapter(nullptr); +} + +void DistributedDBSingleVerP2PSyncCheckTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: create virtual device B and C, and get a KvStoreNbDelegate as deviceA + */ + KvStoreNbDelegate::Option option; + option.secOption.securityLabel = SecurityLabel::S3; + option.secOption.securityFlag = SecurityFlag::SECE; + g_mgr.GetKvStore(STORE_ID, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + g_deviceB = new (std::nothrow) KvVirtualDevice(DEVICE_B); + ASSERT_TRUE(g_deviceB != nullptr); + g_syncInterfaceB = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(g_syncInterfaceB != nullptr); + ASSERT_EQ(g_deviceB->Initialize(g_communicatorAggregator, g_syncInterfaceB), E_OK); + + g_deviceC = new (std::nothrow) KvVirtualDevice(DEVICE_C); + ASSERT_TRUE(g_deviceC != nullptr); + g_syncInterfaceC = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(g_syncInterfaceC != nullptr); + ASSERT_EQ(g_deviceC->Initialize(g_communicatorAggregator, g_syncInterfaceC), E_OK); +} + +void DistributedDBSingleVerP2PSyncCheckTest::TearDown(void) +{ + /** + * @tc.teardown: Release device A, B, C + */ + if (g_kvDelegatePtr != nullptr) { + ASSERT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + DBStatus status = g_mgr.DeleteKvStore(STORE_ID); + LOGD("delete kv store status %d", status); + ASSERT_TRUE(status == OK); + } + if (g_deviceB != nullptr) { + delete g_deviceB; + g_deviceB = nullptr; + } + if (g_deviceC != nullptr) { + delete g_deviceC; + g_deviceC = nullptr; + } + if (g_communicatorAggregator != nullptr) { + g_communicatorAggregator->RegOnDispatch(nullptr); + } +} + +/** + * @tc.name: sec option check Sync 001 + * @tc.desc: if sec option not equal, forbid sync + * @tc.type: FUNC + * @tc.require: AR000EV1G6 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, SecOptionCheck001, TestSize.Level1) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + + ASSERT_TRUE(g_syncInterfaceB != nullptr); + ASSERT_TRUE(g_syncInterfaceC != nullptr); + SecurityOption secOption{SecurityLabel::S4, SecurityFlag::ECE}; + g_syncInterfaceB->SetSecurityOption(secOption); + g_syncInterfaceC->SetSecurityOption(secOption); + + /** + * @tc.steps: step2. deviceA call sync and wait + * @tc.expected: step2. sync should return SECURITY_OPTION_CHECK_ERROR. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == SECURITY_OPTION_CHECK_ERROR); + } + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value.empty()); + g_deviceC->GetData(key, item); + EXPECT_TRUE(item.value.empty()); +} + +/** + * @tc.name: sec option check Sync 002 + * @tc.desc: if sec option not equal, forbid sync + * @tc.type: FUNC + * @tc.require: AR000EV1G6 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, SecOptionCheck002, TestSize.Level1) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + + ASSERT_TRUE(g_syncInterfaceC != nullptr); + SecurityOption secOption{SecurityLabel::S4, SecurityFlag::ECE}; + g_syncInterfaceC->SetSecurityOption(secOption); + secOption.securityLabel = SecurityLabel::S3; + secOption.securityFlag = SecurityFlag::SECE; + g_syncInterfaceB->SetSecurityOption(secOption); + + /** + * @tc.steps: step2. deviceA call sync and wait + * @tc.expected: step2. sync should return SECURITY_OPTION_CHECK_ERROR. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + if (pair.first == DEVICE_B) { + EXPECT_TRUE(pair.second == OK); + } else { + EXPECT_TRUE(pair.second == SECURITY_OPTION_CHECK_ERROR); + } + } + VirtualDataItem item; + g_deviceC->GetData(key, item); + EXPECT_TRUE(item.value.empty()); + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value == value); +} + +#ifndef LOW_LEVEL_MEM_DEV +/** + * @tc.name: BigDataSync001 + * @tc.desc: big data sync push mode. + * @tc.type: FUNC + * @tc.require: AR000F3OOU + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, BigDataSync001, TestSize.Level1) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put 16 bigData + */ + std::vector entries; + std::vector keys; + DistributedDBUnitTest::GenerateRecords(ENTRY_NUM, entries, keys, KEY_LEN, VALUE_LEN); + for (const auto &entry : entries) { + status = g_kvDelegatePtr->Put(entry.key, entry.value); + ASSERT_TRUE(status == OK); + } + + /** + * @tc.steps: step2. deviceA call sync and wait + * @tc.expected: step2. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step2. onComplete should be called, DeviceB,C have {k1,v1} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + for (const auto &entry : entries) { + item.value.clear(); + g_deviceB->GetData(entry.key, item); + EXPECT_TRUE(item.value == entry.value); + item.value.clear(); + g_deviceC->GetData(entry.key, item); + EXPECT_TRUE(item.value == entry.value); + } +} + +/** + * @tc.name: BigDataSync002 + * @tc.desc: big data sync pull mode. + * @tc.type: FUNC + * @tc.require: AR000F3OOU + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, BigDataSync002, TestSize.Level1) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA deviceB put bigData + */ + std::vector entries; + std::vector keys; + DistributedDBUnitTest::GenerateRecords(ENTRY_NUM, entries, keys, KEY_LEN, VALUE_LEN); + + for (uint32_t i = 0; i < entries.size(); i++) { + if (i % 2 == 0) { + g_deviceB->PutData(entries[i].key, entries[i].value, 0, 0); + } else { + g_deviceC->PutData(entries[i].key, entries[i].value, 0, 0); + } + } + + /** + * @tc.steps: step3. deviceA call pull sync + * @tc.expected: step3. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, DeviceA have all bigData + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + for (const auto &entry : entries) { + Value value; + EXPECT_EQ(g_kvDelegatePtr->Get(entry.key, value), OK); + EXPECT_EQ(value, entry.value); + } +} + +/** + * @tc.name: BigDataSync003 + * @tc.desc: big data sync pushAndPull mode. + * @tc.type: FUNC + * @tc.require: AR000F3OOV + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, BigDataSync003, TestSize.Level1) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA deviceB put bigData + */ + std::vector entries; + std::vector keys; + DistributedDBUnitTest::GenerateRecords(ENTRY_NUM, entries, keys, KEY_LEN, VALUE_LEN); + + for (uint32_t i = 0; i < entries.size(); i++) { + if (i % 3 == 0) { // 0 3 6 9 12 15 for deivec B + g_deviceB->PutData(entries[i].key, entries[i].value, 0, 0); + } else if (i % 3 == 1) { // 1 4 7 10 13 16 for device C + g_deviceC->PutData(entries[i].key, entries[i].value, 0, 0); + } else { // 2 5 8 11 14 for device A + status = g_kvDelegatePtr->Put(entries[i].key, entries[i].value); + ASSERT_TRUE(status == OK); + } + } + + /** + * @tc.steps: step3. deviceA call pushAndpull sync + * @tc.expected: step3. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, DeviceA have all bigData + * deviceB and deviceC has deviceA data + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + + VirtualDataItem item; + for (uint32_t i = 0; i < entries.size(); i++) { + Value value; + EXPECT_EQ(g_kvDelegatePtr->Get(entries[i].key, value), OK); + EXPECT_EQ(value, entries[i].value); + + if (i % 3 == 2) { // 2 5 8 11 14 for device A + item.value.clear(); + g_deviceB->GetData(entries[i].key, item); + EXPECT_TRUE(item.value == entries[i].value); + item.value.clear(); + g_deviceC->GetData(entries[i].key, item); + EXPECT_TRUE(item.value == entries[i].value); + } + } +} +#endif + +/** + * @tc.name: PushFinishedNotify 001 + * @tc.desc: Test remote device push finished notify function. + * @tc.type: FUNC + * @tc.require: AR000CQS3S + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, PushFinishedNotify001, TestSize.Level1) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA call SetRemotePushFinishedNotify + * @tc.expected: step1. set should return OK. + */ + int pushfinishedFlag = 0; + DBStatus status = g_kvDelegatePtr->SetRemotePushFinishedNotify( + [&pushfinishedFlag](const RemotePushNotifyInfo &info) { + EXPECT_TRUE(info.deviceId == DEVICE_B); + pushfinishedFlag = 1; + }); + ASSERT_EQ(status, OK); + + /** + * @tc.steps: step2. deviceB put k2, v2, and deviceA pull from deviceB + * @tc.expected: step2. deviceA can not receive push finished notify + */ + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_2, VALUE_2), OK); + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, result); + EXPECT_TRUE(status == OK); + EXPECT_EQ(pushfinishedFlag, 0); + pushfinishedFlag = 0; + + /** + * @tc.steps: step3. deviceB put k3, v3, and deviceA push and pull to deviceB + * @tc.expected: step3. deviceA can not receive push finished notify + */ + EXPECT_EQ(g_kvDelegatePtr->Put(KEY_3, VALUE_3), OK); + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, result); + EXPECT_TRUE(status == OK); + EXPECT_EQ(pushfinishedFlag, 0); + pushfinishedFlag = 0; + + /** + * @tc.steps: step4. deviceA call SetRemotePushFinishedNotify to reset notify + * @tc.expected: step4. set should return OK. + */ + status = g_kvDelegatePtr->SetRemotePushFinishedNotify([&pushfinishedFlag](const RemotePushNotifyInfo &info) { + EXPECT_TRUE(info.deviceId == DEVICE_B); + pushfinishedFlag = 2; + }); + ASSERT_EQ(status, OK); + + /** + * @tc.steps: step5. deviceA call SetRemotePushFinishedNotify set null to unregist + * @tc.expected: step5. set should return OK. + */ + status = g_kvDelegatePtr->SetRemotePushFinishedNotify(nullptr); + ASSERT_EQ(status, OK); +} + +namespace { +void RegOnDispatchWithDelayAck(bool &errCodeAck, bool &afterErrAck) +{ + // just delay the busy ack + g_communicatorAggregator->RegOnDispatch([&errCodeAck, &afterErrAck](const std::string &dev, Message *inMsg) { + if (dev != g_deviceB->GetDeviceId()) { + return; + } + auto *packet = inMsg->GetObject(); + if (packet->GetRecvCode() == -E_BUSY) { + errCodeAck = true; + while (!afterErrAck) { + } + LOGW("NOW SEND BUSY ACK"); + } else if (errCodeAck) { + afterErrAck = true; + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + }); +} + +void RegOnDispatchWithOffline(bool &offlineFlag, bool &invalid, condition_variable &conditionOffline) +{ + g_communicatorAggregator->RegOnDispatch([&offlineFlag, &invalid, &conditionOffline]( + const std::string &dev, Message *inMsg) { + auto *packet = inMsg->GetObject(); + if (dev != DEVICE_B) { + if (packet->GetRecvCode() == LOCAL_WATER_MARK_NOT_INIT) { + offlineFlag = true; + conditionOffline.notify_all(); + LOGW("[Dispatch] NOTIFY OFFLINE"); + std::this_thread::sleep_for(std::chrono::microseconds(EIGHT_HUNDRED)); + } + } else if (!invalid && inMsg->GetMessageType() == TYPE_REQUEST) { + LOGW("[Dispatch] NOW INVALID THIS MSG"); + inMsg->SetMessageType(TYPE_INVALID); + inMsg->SetMessageId(INVALID_MESSAGE_ID); + invalid = true; + } + }); +} + +void RegOnDispatchWithInvalidMsg(bool &invalid) +{ + g_communicatorAggregator->RegOnDispatch([&invalid]( + const std::string &dev, Message *inMsg) { + if (dev == DEVICE_B && !invalid && inMsg->GetMessageType() == TYPE_REQUEST) { + LOGW("[Dispatch] NOW INVALID THIS MSG"); + inMsg->SetMessageType(TYPE_INVALID); + inMsg->SetMessageId(INVALID_MESSAGE_ID); + invalid = true; + } + }); +} + +void PrepareEnv(vector &devices, Key &key, Query &query) +{ + /** + * @tc.steps: step1. ensure the watermark is no zero and finish timeSync and abilitySync + * @tc.expected: step1. should return OK. + */ + Value value = {'1'}; + std::map result; + ASSERT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, DistributedDB::SYNC_MODE_PUSH_ONLY, result, query); + EXPECT_TRUE(status == OK); + ASSERT_TRUE(result[g_deviceB->GetDeviceId()] == OK); +} + +void Sync(vector &devices, const DBStatus &targetStatus) +{ + std::map result; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, DistributedDB::SYNC_MODE_PUSH_ONLY, result); + EXPECT_TRUE(status == OK); + for (const auto &deviceId : devices) { + ASSERT_TRUE(result[deviceId] == targetStatus); + } +} + +void SyncWithQuery(vector &devices, const Query &query, const DBStatus &targetStatus) +{ + std::map result; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, DistributedDB::SYNC_MODE_PUSH_ONLY, result, query); + EXPECT_TRUE(status == OK); + for (const auto &deviceId : devices) { + ASSERT_TRUE(result[deviceId] == targetStatus); + } +} + +void SyncWithDeviceOffline(vector &devices, Key &key, Query &query) +{ + Value value = {'2'}; + ASSERT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + + /** + * @tc.steps: step2. invalid the sync msg + * @tc.expected: step2. should return TIME_OUT. + */ + SyncWithQuery(devices, query, TIME_OUT); + + /** + * @tc.steps: step3. device offline when sync + * @tc.expected: step3. should return COMM_FAILURE. + */ + SyncWithQuery(devices, query, COMM_FAILURE); +} +} + +/** + * @tc.name: AckSessionCheck 001 + * @tc.desc: Test ack session check function. + * @tc.type: FUNC + * @tc.require: AR000F3OOV + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, AckSessionCheck001, TestSize.Level3) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step1. deviceB sync to deviceA just for timeSync and abilitySync + * @tc.expected: step1. should return OK. + */ + std::map result; + ASSERT_TRUE(g_deviceB->Sync(SYNC_MODE_PUSH_ONLY, true) == OK); + + /** + * @tc.steps: step2. deviceA StartTransaction for prevent other sync action deviceB sync will fail + * @tc.expected: step2. should return OK. + */ + ASSERT_TRUE(g_kvDelegatePtr->StartTransaction() == OK); + + bool errCodeAck = false; + bool afterErrAck = false; + RegOnDispatchWithDelayAck(errCodeAck, afterErrAck); + + Key key = {'1'}; + Value value = {'1'}; + Timestamp currentTime; + (void)OS::GetCurrentSysTimeInMicrosecond(currentTime); + EXPECT_TRUE(g_deviceB->PutData(key, value, currentTime, 0) == OK); + EXPECT_TRUE(g_deviceB->Sync(SYNC_MODE_PUSH_ONLY, true) == OK); + + Value outValue; + EXPECT_TRUE(g_kvDelegatePtr->Get(key, outValue) == NOT_FOUND); + + /** + * @tc.steps: step3. release the writeHandle and try again, sync success + * @tc.expected: step3. should return OK. + */ + EXPECT_TRUE(g_kvDelegatePtr->Commit() == OK); + EXPECT_TRUE(g_deviceB->Sync(SYNC_MODE_PUSH_ONLY, true) == OK); + + EXPECT_TRUE(g_kvDelegatePtr->Get(key, outValue) == E_OK); + EXPECT_EQ(outValue, value); +} + +/** + * @tc.name: AckSafeCheck001 + * @tc.desc: Test ack session check filter all bad ack in device offline scene. + * @tc.type: FUNC + * @tc.require: AR000F3OOV + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, AckSafeCheck001, TestSize.Level3) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + g_deviceB->Online(); + + Key key = {'1'}; + Query query = Query::Select().PrefixKey(key); + PrepareEnv(devices, key, query); + + std::condition_variable conditionOnline; + std::condition_variable conditionOffline; + bool onlineFlag = false; + bool invalid = false; + bool offlineFlag = false; + thread subThread([&onlineFlag, &conditionOnline, &offlineFlag, &conditionOffline]() { + LOGW("[Dispatch] NOW DEVICES IS OFFLINE"); + std::mutex offlineMtx; + std::unique_lock lck(offlineMtx); + conditionOffline.wait(lck, [&offlineFlag]{ return offlineFlag; }); + g_deviceB->Offline(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + g_deviceB->Online(); + onlineFlag = true; + conditionOnline.notify_all(); + LOGW("[Dispatch] NOW DEVICES IS ONLINE"); + }); + subThread.detach(); + + RegOnDispatchWithOffline(offlineFlag, invalid, conditionOffline); + + SyncWithDeviceOffline(devices, key, query); + + std::mutex onlineMtx; + std::unique_lock lck(onlineMtx); + conditionOnline.wait(lck, [&onlineFlag]{ return onlineFlag; }); + + /** + * @tc.steps: step4. sync again if has problem it will sync never end + * @tc.expected: step4. should return OK. + */ + SyncWithQuery(devices, query, OK); +} + + +/** + * @tc.name: WaterMarkCheck001 + * @tc.desc: Test waterMark work correct in lost package scene. + * @tc.type: FUNC + * @tc.require: AR000F3OOV + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, WaterMarkCheck001, TestSize.Level1) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + g_deviceB->Online(); + + Key key = {'1'}; + Query query = Query::Select().PrefixKey(key); + PrepareEnv(devices, key, query); + + /** + * @tc.steps: step2. query sync and set queryWaterMark + * @tc.expected: step2. should return OK. + */ + Value value = {'2'}; + ASSERT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + SyncWithQuery(devices, query, OK); + + /** + * @tc.steps: step3. sync and invalid msg for set local device waterMark + * @tc.expected: step3. should return TIME_OUT. + */ + bool invalidMsg = false; + RegOnDispatchWithInvalidMsg(invalidMsg); + value = {'3'}; + ASSERT_TRUE(g_kvDelegatePtr->Put(key, value) == OK); + Sync(devices, TIME_OUT); + + /** + * @tc.steps: step4. sync again see it work correct + * @tc.expected: step4. should return OK. + */ + SyncWithQuery(devices, query, OK); +} + +void RegOnDispatchToGetSyncCount(int &sendRequestCount, int sleepMs = 0) +{ + g_communicatorAggregator->RegOnDispatch([sleepMs, &sendRequestCount]( + const std::string &dev, Message *inMsg) { + if (dev == DEVICE_B && inMsg->GetMessageType() == TYPE_REQUEST) { + std::this_thread::sleep_for(std::chrono::milliseconds(sleepMs)); + sendRequestCount++; + LOGD("sendRequestCount++..."); + } + }); +} + +void TestDifferentSyncMode(SyncMode mode) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + DBStatus status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + + int sendRequestCount = 0; + RegOnDispatchToGetSyncCount(sendRequestCount); + + /** + * @tc.steps: step2. deviceA call sync and wait + * @tc.expected: step2. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, mode, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step2. onComplete should be called, DeviceB have {k1,v1}, send request message 3 times + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value == value); + + EXPECT_EQ(sendRequestCount, NORMAL_SYNC_SEND_REQUEST_CNT); + + /** + * @tc.steps: step3. reset sendRequestCount to 0, deviceA call sync and wait again without any change in db + * @tc.expected: step3. sync should return OK, and sendRequestCount should be 1, because this merge can not + * be skipped + */ + sendRequestCount = 0; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + EXPECT_EQ(sendRequestCount, 1); +} + +/** + * @tc.name: PushSyncMergeCheck001 + * @tc.desc: Test push sync task merge, task can not be merged when the two sync task is not in the queue + * at the same time. + * @tc.type: FUNC + * @tc.require: AR000F3OOV + * @tc.author: zhangshijie + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, SyncMergeCheck001, TestSize.Level1) +{ + TestDifferentSyncMode(SYNC_MODE_PUSH_ONLY); +} + +/** + * @tc.name: PushSyncMergeCheck002 + * @tc.desc: Test push_pull sync task merge, task can not be merged when the two sync task is not in the queue + * at the same time. + * @tc.type: FUNC + * @tc.require: AR000F3OOV + * @tc.author: zhangshijie + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, SyncMergeCheck002, TestSize.Level1) +{ + TestDifferentSyncMode(SYNC_MODE_PUSH_PULL); +} + +void PrepareForSyncMergeTest(std::vector &devices, int &sendRequestCount) +{ + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + DBStatus status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + + RegOnDispatchToGetSyncCount(sendRequestCount, SLEEP_MILLISECONDS); + + /** + * @tc.steps: step2. deviceA call sync and don't wait + * @tc.expected: step2. sync should return OK. + */ + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, + [&sendRequestCount, devices, key, value](const std::map& statusMap) { + ASSERT_TRUE(statusMap.size() == devices.size()); + for (const auto &pair : statusMap) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_EQ(item.value, value); + EXPECT_EQ(sendRequestCount, NORMAL_SYNC_SEND_REQUEST_CNT); + + // reset sendRequestCount to 0 + sendRequestCount = 0; + }); + ASSERT_TRUE(status == OK); +} + +/** + * @tc.name: PushSyncMergeCheck003 + * @tc.desc: Test push sync task merge, task can not be merged when there is change in db since last push sync + * @tc.type: FUNC + * @tc.require: AR000F3OOV + * @tc.author: zhangshijie + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, SyncMergeCheck003, TestSize.Level3) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + int sendRequestCount = 0; + PrepareForSyncMergeTest(devices, sendRequestCount); + + /** + * @tc.steps: step3. deviceA call sync and don't wait + * @tc.expected: step3. sync should return OK. + */ + Key key = {'1'}; + Value value = {'2'}; + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, + [&sendRequestCount, devices, key, value, this](const std::map& statusMap) { + /** + * @tc.expected: when the second sync task return, sendRequestCount should be 1, because this merge can not be + * skipped, but it is no need to do time sync and ability sync, only need to do data sync + */ + ASSERT_TRUE(statusMap.size() == devices.size()); + for (const auto &pair : statusMap) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_EQ(item.value, value); + }); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step4. deviceA put {k1, v2} + */ + while (sendRequestCount < TWO_CNT) { + std::this_thread::sleep_for(std::chrono::milliseconds(THREE_HUNDRED)); + } + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + // wait for the second sync task finish + std::this_thread::sleep_for(std::chrono::seconds(TEN_SECONDS)); + EXPECT_EQ(sendRequestCount, 1); +} + +/** + * @tc.name: PushSyncMergeCheck004 + * @tc.desc: Test push sync task merge, task can be merged when there is no change in db since last push sync + * @tc.type: FUNC + * @tc.require: AR000F3OOV + * @tc.author: zhangshijie + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, SyncMergeCheck004, TestSize.Level3) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + int sendRequestCount = 0; + PrepareForSyncMergeTest(devices, sendRequestCount); + + /** + * @tc.steps: step3. deviceA call sync and don't wait + * @tc.expected: step3. sync should return OK. + */ + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, + [devices, this](const std::map& statusMap) { + /** + * @tc.expected: when the second sync task return, sendRequestCount should be 0, because this merge can be + * skipped + */ + ASSERT_TRUE(statusMap.size() == devices.size()); + for (const auto &pair : statusMap) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + }); + ASSERT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::seconds(TEN_SECONDS)); + EXPECT_EQ(sendRequestCount, 0); +} + +void RegOnDispatchWithInvalidMsgAndCnt(int &sendRequestCount, int sleepMs, bool &invalid) +{ + g_communicatorAggregator->RegOnDispatch([&sendRequestCount, sleepMs, &invalid]( + const std::string &dev, Message *inMsg) { + if (dev == DEVICE_B && !invalid && inMsg->GetMessageType() == TYPE_REQUEST) { + inMsg->SetMessageType(TYPE_INVALID); + inMsg->SetMessageId(INVALID_MESSAGE_ID); + sendRequestCount++; + invalid = true; + LOGW("[Dispatch]invalid THIS MSG, sendRequestCount = %d", sendRequestCount); + std::this_thread::sleep_for(std::chrono::milliseconds(sleepMs)); + } + }); +} + +/** + * @tc.name: PushSyncMergeCheck005 + * @tc.desc: Test push sync task merge, task cannot be merged when the last push sync is failed + * @tc.type: FUNC + * @tc.require: AR000F3OOV + * @tc.author: zhangshijie + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, SyncMergeCheck005, TestSize.Level3) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + + int sendRequestCount = 0; + bool invalid = false; + RegOnDispatchWithInvalidMsgAndCnt(sendRequestCount, SLEEP_MILLISECONDS, invalid); + + /** + * @tc.steps: step2. deviceA call sync and don't wait + * @tc.expected: step2. sync should return TIME_OUT. + */ + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, + [&sendRequestCount, devices, this](const std::map& statusMap) { + ASSERT_TRUE(statusMap.size() == devices.size()); + for (const auto &deviceId : devices) { + ASSERT_EQ(statusMap.at(deviceId), TIME_OUT); + } + }); + EXPECT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call sync and don't wait + * @tc.expected: step3. sync should return OK. + */ + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, + [key, value, &sendRequestCount, devices, this](const std::map& statusMap) { + /** + * @tc.expected: when the second sync task return, sendRequestCount should be 3, because this merge can not be + * skipped, deviceB should have {k1, v1}. + */ + ASSERT_TRUE(statusMap.size() == devices.size()); + for (const auto &pair : statusMap) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_EQ(pair.second, OK); + } + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_EQ(item.value, value); + }); + ASSERT_TRUE(status == OK); + while (sendRequestCount < 1) { + std::this_thread::sleep_for(std::chrono::milliseconds(THREE_HUNDRED)); + } + sendRequestCount = 0; + RegOnDispatchToGetSyncCount(sendRequestCount, SLEEP_MILLISECONDS); + + // wait for the second sync task finish + std::this_thread::sleep_for(std::chrono::seconds(TEN_SECONDS)); + EXPECT_EQ(sendRequestCount, NORMAL_SYNC_SEND_REQUEST_CNT); +} + +void PrePareForQuerySyncMergeTest(bool isQuerySync, std::vector &devices, + Key &key, Value &value, int &sendRequestCount) +{ + DBStatus status = OK; + /** + * @tc.steps: step1. deviceA put {k1, v1}...{k10, v10} + */ + Query query = Query::Select().PrefixKey(key); + const int dataSize = 10; + for (int i = 0; i < dataSize; i++) { + key.push_back(i); + value.push_back(i); + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + key.pop_back(); + value.pop_back(); + } + + RegOnDispatchToGetSyncCount(sendRequestCount, SLEEP_MILLISECONDS); + /** + * @tc.steps: step2. deviceA call query sync and don't wait + * @tc.expected: step2. sync should return OK. + */ + auto completeCallBack = [&sendRequestCount, &key, &value, dataSize, devices] + (const std::map& statusMap) { + ASSERT_TRUE(statusMap.size() == devices.size()); + for (const auto &pair : statusMap) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_EQ(pair.second, OK); + } + // when first sync finish, DeviceB have {k1,v1}, {k3,v3}, {k5,v5} .. send request message 3 times + VirtualDataItem item; + for (int i = 0; i < dataSize; i++) { + key.push_back(i); + value.push_back(i); + g_deviceB->GetData(key, item); + EXPECT_EQ(item.value, value); + key.pop_back(); + value.pop_back(); + } + EXPECT_EQ(sendRequestCount, NORMAL_SYNC_SEND_REQUEST_CNT); + // reset sendRequestCount to 0 + sendRequestCount = 0; + }; + if (isQuerySync) { + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, completeCallBack, query, false); + } else { + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, completeCallBack); + } + ASSERT_TRUE(status == OK); +} + +/** + * @tc.name: QuerySyncMergeCheck001 + * @tc.desc: Test query push sync task merge, task can be merged when there is no change in db since last query sync + * @tc.type: FUNC + * @tc.require: AR000F3OOV + * @tc.author: zhangshijie + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, QuerySyncMergeCheck001, TestSize.Level3) +{ + std::vector devices; + int sendRequestCount = 0; + devices.push_back(g_deviceB->GetDeviceId()); + + Key key{'1'}; + Value value{'1'}; + Query query = Query::Select().PrefixKey(key); + PrePareForQuerySyncMergeTest(true, devices, key, value, sendRequestCount); + + /** + * @tc.steps: step3. deviceA call query sync and don't wait + * @tc.expected: step3. sync should return OK. + */ + DBStatus status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, + [devices, this](const std::map& statusMap) { + /** + * @tc.expected: when the second sync task return, sendRequestCount should be 0, because this merge can be + * skipped because there is no change in db since last query sync + */ + ASSERT_TRUE(statusMap.size() == devices.size()); + for (const auto &pair : statusMap) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + }, query, false); + ASSERT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::seconds(TEN_SECONDS)); + EXPECT_EQ(sendRequestCount, 0); +} + +/** + * @tc.name: QuerySyncMergeCheck002 + * @tc.desc: Test query push sync task merge, task can not be merged when there is change in db since last sync + * @tc.type: FUNC + * @tc.require: AR000F3OOV + * @tc.author: zhangshijie + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, QuerySyncMergeCheck002, TestSize.Level3) +{ + std::vector devices; + int sendRequestCount = 0; + devices.push_back(g_deviceB->GetDeviceId()); + + Key key{'1'}; + Value value{'1'}; + Query query = Query::Select().PrefixKey(key); + PrePareForQuerySyncMergeTest(true, devices, key, value, sendRequestCount); + + /** + * @tc.steps: step3. deviceA call query sync and don't wait + * @tc.expected: step3. sync should return OK. + */ + Value value3{'3'}; + DBStatus status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, + [&sendRequestCount, devices, key, value3, this](const std::map& statusMap) { + /** + * @tc.expected: when the second sync task return, sendRequestCount should be 1, because this merge can not be + * skipped when there is change in db since last query sync, deviceB have {k1, v1'} + */ + ASSERT_TRUE(statusMap.size() == devices.size()); + for (const auto &pair : statusMap) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value == value3); + EXPECT_EQ(sendRequestCount, 1); + }, query, false); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step4. deviceA put {k1, v1'} + * @tc.steps: step4. reset sendRequestCount to 0, deviceA call sync and wait + * @tc.expected: step4. sync should return OK, and sendRequestCount should be 1, because this merge can not + * be skipped + */ + while (sendRequestCount < TWO_CNT) { + std::this_thread::sleep_for(std::chrono::milliseconds(THREE_HUNDRED)); + } + g_kvDelegatePtr->Put(key, value3); + std::this_thread::sleep_for(std::chrono::seconds(TEN_SECONDS)); +} + +/** + * @tc.name: QuerySyncMergeCheck003 + * @tc.desc: Test query push sync task merge, task can not be merged when then query id is different + * @tc.type: FUNC + * @tc.require: AR000F3OOV + * @tc.author: zhangshijie + */ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, QuerySyncMergeCheck003, TestSize.Level3) +{ + std::vector devices; + int sendRequestCount = 0; + devices.push_back(g_deviceB->GetDeviceId()); + + Key key{'1'}; + Value value{'1'}; + PrePareForQuerySyncMergeTest(true, devices, key, value, sendRequestCount); + + /** + * @tc.steps: step3. deviceA call another query sync + * @tc.expected: step3. sync should return OK. + */ + Key key2 = {'2'}; + Value value2 = {'2'}; + DBStatus status = g_kvDelegatePtr->Put(key2, value2); + ASSERT_TRUE(status == OK); + Query query2 = Query::Select().PrefixKey(key2); + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, + [&sendRequestCount, key2, value2, devices, this](const std::map& statusMap) { + /** + * @tc.expected: when the second sync task return, sendRequestCount should be 1, because this merge can not be + * skipped, deviceB have {k2,v2} + */ + ASSERT_TRUE(statusMap.size() == devices.size()); + for (const auto &pair : statusMap) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + g_deviceB->GetData(key2, item); + EXPECT_TRUE(item.value == value2); + EXPECT_EQ(sendRequestCount, 1); + }, query2, false); + ASSERT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::seconds(TEN_SECONDS)); +} + +/** +* @tc.name: QuerySyncMergeCheck004 +* @tc.desc: Test query push sync task merge, task can be merged when there is no change in db since last push sync +* @tc.type: FUNC +* @tc.require: AR000F3OOV +* @tc.author: zhangshijie +*/ +HWTEST_F(DistributedDBSingleVerP2PSyncCheckTest, QuerySyncMergeCheck004, TestSize.Level3) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + Key key{'1'}; + Value value{'1'}; + int sendRequestCount = 0; + PrePareForQuerySyncMergeTest(false, devices, key, value, sendRequestCount); + + /** + * @tc.steps: step3. deviceA call query sync without any change in db + * @tc.expected: step3. sync should return OK, and sendRequestCount should be 0, because this merge can be skipped + */ + Query query = Query::Select().PrefixKey(key); + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, + [devices, this](const std::map& statusMap) { + /** + * @tc.expected step3: when the second sync task return, sendRequestCount should be 0, because this merge + * can be skipped because there is no change in db since last push sync + */ + ASSERT_TRUE(statusMap.size() == devices.size()); + for (const auto &pair : statusMap) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + }, query, false); + ASSERT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::seconds(TEN_SECONDS)); + EXPECT_EQ(sendRequestCount, 0); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_sync_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_sync_test.cpp new file mode 100644 index 00000000..bcc74326 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_single_ver_p2p_sync_test.cpp @@ -0,0 +1,2290 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "db_constant.h" +#include "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "kv_store_nb_delegate.h" +#include "kv_virtual_device.h" +#include "platform_specific.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace DistributedDBUnitTest; +using namespace std; + +namespace { + string g_testDir; + const string STORE_ID = "kv_stroe_sync_test"; + const int64_t TIME_OFFSET = 5000000; + const int WAIT_TIME = 1000; + const int WAIT_5_SECONDS = 5000; + const int WAIT_30_SECONDS = 30000; + const int WAIT_36_SECONDS = 36000; + const std::string DEVICE_B = "deviceB"; + const std::string DEVICE_C = "deviceC"; + + KvStoreDelegateManager g_mgr(APP_ID, USER_ID); + KvStoreConfig g_config; + DistributedDBToolsUnitTest g_tool; + DBStatus g_kvDelegateStatus = INVALID_ARGS; + KvStoreNbDelegate* g_kvDelegatePtr = nullptr; + VirtualCommunicatorAggregator* g_communicatorAggregator = nullptr; + KvVirtualDevice *g_deviceB = nullptr; + KvVirtualDevice *g_deviceC = nullptr; + + // the type of g_kvDelegateCallback is function + auto g_kvDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, + placeholders::_1, placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvDelegatePtr)); +} + +class DistributedDBSingleVerP2PSyncTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBSingleVerP2PSyncTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Init datadir and Virtual Communicator. + */ + DistributedDBToolsUnitTest::TestDirInit(g_testDir); + g_config.dataDir = g_testDir; + g_mgr.SetKvStoreConfig(g_config); + + string dir = g_testDir + "/single_ver"; + DIR* dirTmp = opendir(dir.c_str()); + if (dirTmp == nullptr) { + OS::MakeDBDirectory(dir); + } else { + closedir(dirTmp); + } + + g_communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator(); + ASSERT_TRUE(g_communicatorAggregator != nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(g_communicatorAggregator); +} + +void DistributedDBSingleVerP2PSyncTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: Release virtual Communicator and clear data dir. + */ + if (DistributedDBToolsUnitTest::RemoveTestDbFiles(g_testDir) != 0) { + LOGE("rm test db files error!"); + } + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); +} + +void DistributedDBSingleVerP2PSyncTest::SetUp(void) +{ + DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: create virtual device B and C, and get a KvStoreNbDelegate as deviceA + */ + KvStoreNbDelegate::Option option; + g_mgr.GetKvStore(STORE_ID, option, g_kvDelegateCallback); + ASSERT_TRUE(g_kvDelegateStatus == OK); + ASSERT_TRUE(g_kvDelegatePtr != nullptr); + g_deviceB = new (std::nothrow) KvVirtualDevice(DEVICE_B); + ASSERT_TRUE(g_deviceB != nullptr); + VirtualSingleVerSyncDBInterface *syncInterfaceB = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(syncInterfaceB != nullptr); + ASSERT_EQ(g_deviceB->Initialize(g_communicatorAggregator, syncInterfaceB), E_OK); + + g_deviceC = new (std::nothrow) KvVirtualDevice(DEVICE_C); + ASSERT_TRUE(g_deviceC != nullptr); + VirtualSingleVerSyncDBInterface *syncInterfaceC = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(syncInterfaceC != nullptr); + ASSERT_EQ(g_deviceC->Initialize(g_communicatorAggregator, syncInterfaceC), E_OK); + + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + return true;}; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(permissionCheckCallback), OK); +} + +void DistributedDBSingleVerP2PSyncTest::TearDown(void) +{ + /** + * @tc.teardown: Release device A, B, C + */ + if (g_kvDelegatePtr != nullptr) { + ASSERT_EQ(g_mgr.CloseKvStore(g_kvDelegatePtr), OK); + g_kvDelegatePtr = nullptr; + DBStatus status = g_mgr.DeleteKvStore(STORE_ID); + LOGD("delete kv store status %d", status); + ASSERT_TRUE(status == OK); + } + if (g_deviceB != nullptr) { + delete g_deviceB; + g_deviceB = nullptr; + } + if (g_deviceC != nullptr) { + delete g_deviceC; + g_deviceC = nullptr; + } + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} + +/** + * @tc.name: Normal Sync 001 + * @tc.desc: Test normal push sync for add data. + * @tc.type: FUNC + * @tc.require: AR000CQS3S SR000CQE0B + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, NormalSync001, TestSize.Level1) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step2. deviceA call sync and wait + * @tc.expected: step2. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step2. onComplete should be called, DeviceB,C have {k1,v1} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value == value); + g_deviceC->GetData(key, item); + EXPECT_TRUE(item.value == value); +} + +/** + * @tc.name: Normal Sync 002 + * @tc.desc: Test normal push sync for update data. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, NormalSync002, TestSize.Level1) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step2. deviceA put {k1, v2} + */ + Value value2; + value2.push_back('2'); + status = g_kvDelegatePtr->Put(key, value2); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call sync and wait + * @tc.expected: step3. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, DeviceB,C have {k1,v2} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + g_deviceC->GetData(key, item); + EXPECT_TRUE(item.value == value2); + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value == value2); +} + +/** + * @tc.name: Normal Sync 003 + * @tc.desc: Test normal push sync for delete data. + * @tc.type: FUNC + * @tc.require: AR000CQS3S + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, NormalSync003, TestSize.Level1) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step2. deviceA delete k1 + */ + status = g_kvDelegatePtr->Delete(key); + ASSERT_TRUE(status == OK); + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call sync and wait + * @tc.expected: step3. sync should return OK. + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + + /** + * @tc.expected: step3. onComplete should be called, DeviceB,C have {k1, delete} + */ + VirtualDataItem item; + Key hashKey; + DistributedDBToolsUnitTest::CalcHash(key, hashKey); + EXPECT_EQ(g_deviceB->GetData(hashKey, item), E_OK); + EXPECT_TRUE(item.flag != 0); + g_deviceC->GetData(hashKey, item); + EXPECT_TRUE(item.flag != 0); +} + +/** + * @tc.name: Normal Sync 004 + * @tc.desc: Test normal pull sync for add data. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, NormalSync004, TestSize.Level1) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceB put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + g_deviceB->PutData(key, value, 0, 0); + + /** + * @tc.steps: step2. deviceB put {k2, v2} + */ + Key key2 = {'2'}; + Value value2 = {'2'}; + g_deviceC->PutData(key2, value2, 0, 0); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call pull sync + * @tc.expected: step3. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, DeviceA have {k1, VALUE_1}, {K2. VALUE_2} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + Value value3; + EXPECT_EQ(g_kvDelegatePtr->Get(key, value3), OK); + EXPECT_EQ(value3, value); + EXPECT_EQ(g_kvDelegatePtr->Get(key2, value3), OK); + EXPECT_EQ(value3, value2); +} + +/** + * @tc.name: Normal Sync 005 + * @tc.desc: Test normal pull sync for update data. + * @tc.type: FUNC + * @tc.require: AR000CCPOM SR000CQE10 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, NormalSync005, TestSize.Level2) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1}, {k2, v2} t1 + */ + Key key1 = {'1'}; + Value value1 = {'1'}; + status = g_kvDelegatePtr->Put(key1, value1); + ASSERT_TRUE(status == OK); + Key key2 = {'2'}; + Value value2 = {'2'}; + status = g_kvDelegatePtr->Put(key2, value2); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step2. deviceB put {k1, v3} t2, t2 > t1 + */ + Value value3; + value3.push_back('3'); + g_deviceB->PutData(key1, value3, + TimeHelper::GetSysCurrentTime() + g_deviceB->GetLocalTimeOffset() + TIME_OFFSET, 0); + + /** + * @tc.steps: step3. deviceC put {k2, v4} t2, t4 < t1 + */ + Value value4; + value4.push_back('4'); + g_deviceC->PutData(key2, value4, + TimeHelper::GetSysCurrentTime() + g_deviceC->GetLocalTimeOffset() - TIME_OFFSET, 0); + + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps: step4. deviceA call pull sync + * @tc.expected: step4. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step4. onComplete should be called, DeviceA have {k1, v3}, {k2. v2} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + + Value value5; + g_kvDelegatePtr->Get(key1, value5); + EXPECT_TRUE(value5 == value3); + g_kvDelegatePtr->Get(key2, value5); + EXPECT_TRUE(value5 == value2); +} + +/** + * @tc.name: Normal Sync 006 + * @tc.desc: Test normal pull sync for delete data. + * @tc.type: FUNC + * @tc.require: AR000CQS3S + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, NormalSync006, TestSize.Level2) +{ + /** + * @tc.steps: step1. deviceA put {k1, v1}, {k2, v2} t1 + */ + Key key1 = {'1'}; + Value value1 = {'1'}; + DBStatus status = g_kvDelegatePtr->Put(key1, value1); + ASSERT_TRUE(status == OK); + Key key2 = {'2'}; + Value value2 = {'2'}; + status = g_kvDelegatePtr->Put(key2, value2); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step2. deviceA put {k1, delete} t2, t2 PutData(hashKey1, value1, + TimeHelper::GetSysCurrentTime() + g_deviceB->GetLocalTimeOffset() + TIME_OFFSET, 1); + + /** + * @tc.steps: step3. deviceA put {k1, delete} t3, t3 < t1 + */ + Key hashKey2; + DistributedDBToolsUnitTest::CalcHash(key2, hashKey2); + g_deviceC->PutData(hashKey2, value1, + TimeHelper::GetSysCurrentTime() + g_deviceC->GetLocalTimeOffset() - TIME_OFFSET, 0); + + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps: step4. deviceA call pull sync + * @tc.expected: step4. sync should return OK. + */ + std::map result; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step4. onComplete should be called, DeviceA have {k2. v2} don't have k1 + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + Value value5; + g_kvDelegatePtr->Get(key1, value5); + EXPECT_TRUE(value5.empty()); + g_kvDelegatePtr->Get(key2, value5); + EXPECT_TRUE(value5 == value2); +} + +/** + * @tc.name: Normal Sync 007 + * @tc.desc: Test normal push_pull sync for add data. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, NormalSync007, TestSize.Level1) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + Key key1 = {'1'}; + Value value1 = {'1'}; + status = g_kvDelegatePtr->Put(key1, value1); + EXPECT_TRUE(status == OK); + + /** + * @tc.steps: step1. deviceB put {k2, v2} + */ + Key key2 = {'2'}; + Value value2 = {'2'}; + g_deviceB->PutData(key2, value2, 0, 0); + + /** + * @tc.steps: step1. deviceB put {k3, v3} + */ + Key key3 = {'3'}; + Value value3 = {'3'}; + g_deviceC->PutData(key3, value3, 0, 0); + + /** + * @tc.steps: step4. deviceA call push_pull sync + * @tc.expected: step4. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, result); + ASSERT_TRUE(status == OK); + + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + + /** + * @tc.expected: step4. onComplete should be called, DeviceA have {k1. v1}, {k2, v2}, {k3, v3} + * deviceB received {k1. v1}, don't received k3, deviceC received {k1. v1}, don't received k2 + */ + Value value4; + g_kvDelegatePtr->Get(key2, value4); + EXPECT_TRUE(value4 == value2); + g_kvDelegatePtr->Get(key3, value4); + EXPECT_TRUE(value4 == value3); + + VirtualDataItem item1; + g_deviceB->GetData(key1, item1); + EXPECT_TRUE(item1.value == value1); + item1.value.clear(); + g_deviceB->GetData(key3, item1); + EXPECT_TRUE(item1.value.empty()); + + VirtualDataItem item2; + g_deviceC->GetData(key1, item2); + EXPECT_TRUE(item2.value == value1); + item2.value.clear(); + g_deviceC->GetData(key2, item2); + EXPECT_TRUE(item2.value.empty()); +} + +/** + * @tc.name: Normal Sync 008 + * @tc.desc: Test normal push_pull sync for update data. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, NormalSync008, TestSize.Level2) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1}, {k2, v2} t1 + */ + Key key1 = {'1'}; + Value value1 = {'1'}; + status = g_kvDelegatePtr->Put(key1, value1); + ASSERT_TRUE(status == OK); + + Key key2 = {'2'}; + Value value2 = {'2'}; + status = g_kvDelegatePtr->Put(key2, value2); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step2. deviceB put {k1, v3} t2, t2 > t1 + */ + Value value3 = {'3'}; + g_deviceB->PutData(key1, value3, + TimeHelper::GetSysCurrentTime() + g_deviceB->GetLocalTimeOffset() + TIME_OFFSET, 0); + + /** + * @tc.steps: step3. deviceB put {k1, v4} t3, t4 PutData(key2, value4, + TimeHelper::GetSysCurrentTime() + g_deviceC->GetLocalTimeOffset() - TIME_OFFSET, 0); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + + /** + * @tc.steps: step4. deviceA call push_pull sync + * @tc.expected: step4. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, result); + ASSERT_TRUE(status == OK); + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + + /** + * @tc.expected: step4. onComplete should be called, DeviceA have {k1. v3}, {k2, v2} + * deviceB have {k1. v3}, deviceC have {k2. v2} + */ + Value value5; + g_kvDelegatePtr->Get(key1, value5); + EXPECT_EQ(value5, value3); + g_kvDelegatePtr->Get(key2, value5); + EXPECT_EQ(value5, value2); + + VirtualDataItem item1; + g_deviceB->GetData(key1, item1); + EXPECT_TRUE(item1.value == value3); + item1.value.clear(); + g_deviceB->GetData(key2, item1); + EXPECT_TRUE(item1.value == value2); + + VirtualDataItem item2; + g_deviceC->GetData(key2, item2); + EXPECT_TRUE(item2.value == value2); +} + +/** + * @tc.name: Normal Sync 009 + * @tc.desc: Test normal push_pull sync for delete data. + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, NormalSync009, TestSize.Level2) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1}, {k2, v2} t1 + */ + Key key1 = {'1'}; + Value value1 = {'1'}; + status = g_kvDelegatePtr->Put(key1, value1); + ASSERT_TRUE(status == OK); + + Key key2 = {'2'}; + Value value2 = {'2'}; + status = g_kvDelegatePtr->Put(key2, value2); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step2. deviceB put {k1, delete} t2, t2 > t1 + */ + Key hashKey1; + DistributedDBToolsUnitTest::CalcHash(key1, hashKey1); + g_deviceB->PutData(hashKey1, value1, + TimeHelper::GetSysCurrentTime() + g_deviceB->GetLocalTimeOffset() + TIME_OFFSET, 1); + + /** + * @tc.steps: step3. deviceB put {k1, delete} t3, t2 < t1 + */ + Key hashKey2; + DistributedDBToolsUnitTest::CalcHash(key2, hashKey2); + g_deviceC->PutData(hashKey2, value2, + TimeHelper::GetSysCurrentTime() + g_deviceC->GetLocalTimeOffset() - TIME_OFFSET, 1); + + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps: step4. deviceA call push_pull sync + * @tc.expected: step4. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step4. onComplete should be called, DeviceA have {k1. delete}, {k2, v2} + * deviceB have {k2. v2}, deviceC have {k2. v2} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + + Value value3; + g_kvDelegatePtr->Get(key1, value3); + EXPECT_TRUE(value3.empty()); + value3.clear(); + g_kvDelegatePtr->Get(key2, value3); + EXPECT_TRUE(value3 == value2); + + VirtualDataItem item1; + g_deviceB->GetData(key2, item1); + EXPECT_TRUE(item1.value == value2); + + VirtualDataItem item2; + g_deviceC->GetData(key2, item2); + EXPECT_TRUE(item2.value == value2); +} + +/** + * @tc.name: Limit Data Sync 001 + * @tc.desc: Test sync limit key and value data + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, LimitDataSync001, TestSize.Level1) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + Key key1; + Value value1; + DistributedDBToolsUnitTest::GetRandomKeyValue(key1, DBConstant::MAX_KEY_SIZE + 1); + DistributedDBToolsUnitTest::GetRandomKeyValue(value1, DBConstant::MAX_VALUE_SIZE + 1); + + Key key2; + Value value2; + DistributedDBToolsUnitTest::GetRandomKeyValue(key2, DBConstant::MAX_KEY_SIZE); + DistributedDBToolsUnitTest::GetRandomKeyValue(value2, DBConstant::MAX_VALUE_SIZE); + + /** + * @tc.steps: step1. deviceB put {k1, v1}, K1 > 1k, v1 > 4M + */ + g_deviceB->PutData(key1, value1, 0, 0); + + /** + * @tc.steps: step2. deviceB put {k2, v2}, K2 = 1k, v2 = 4M + */ + g_deviceC->PutData(key2, value2, 0, 0); + + /** + * @tc.steps: step3. deviceA call pull sync from device B + * @tc.expected: step3. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called. + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + if (pair.first == g_deviceB->GetDeviceId()) { + EXPECT_TRUE(pair.second != OK); + } else { + EXPECT_TRUE(pair.second == OK); + } + } + + /** + * @tc.steps: step4. deviceA call pull sync from deviceC + * @tc.expected: step4. sync should return OK. + */ + devices.clear(); + result.clear(); + devices.push_back(g_deviceC->GetDeviceId()); + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step4. onComplete should be called, DeviceA have {k2. v2}, don't have {k1, v1} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + + // Get value from A + Value valueRead; + EXPECT_TRUE(g_kvDelegatePtr->Get(key1, valueRead) != OK); + valueRead.clear(); + EXPECT_EQ(g_kvDelegatePtr->Get(key2, valueRead), OK); + EXPECT_TRUE(valueRead == value2); +} + +/** + * @tc.name: Device Offline Sync 001 + * @tc.desc: Test push sync when device offline + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, DeviceOfflineSync001, TestSize.Level1) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1}, {k2, v2}, {k3 delete}, {k4,v2} + */ + Key key1 = {'1'}; + Value value1 = {'1'}; + ASSERT_TRUE(g_kvDelegatePtr->Put(key1, value1) == OK); + + Key key2 = {'2'}; + Value value2 = {'2'}; + ASSERT_TRUE(g_kvDelegatePtr->Put(key2, value2) == OK); + + Key key3 = {'3'}; + Value value3 = {'3'}; + ASSERT_TRUE(g_kvDelegatePtr->Put(key3, value3) == OK); + ASSERT_TRUE(g_kvDelegatePtr->Delete(key3) == OK); + + Key key4 = {'4'}; + Value value4 = {'4'}; + ASSERT_TRUE(g_kvDelegatePtr->Put(key4, value4) == OK); + + /** + * @tc.steps: step2. deviceB offline + */ + g_deviceB->Offline(); + + /** + * @tc.steps: step3. deviceA call pull sync + * @tc.expected: step3. sync should return OK. + */ + std::map result; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, DeviceB status is timeout + * deviceC has {k1, v1}, {k2, v2}, {k3 delete}, {k4,v4} + */ + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + if (pair.first == DEVICE_B) { + EXPECT_TRUE(pair.second == COMM_FAILURE); + } else { + EXPECT_TRUE(pair.second == OK); + } + } + VirtualDataItem item; + g_deviceC->GetData(key1, item); + EXPECT_TRUE(item.value == value1); + item.value.clear(); + g_deviceC->GetData(key2, item); + EXPECT_TRUE(item.value == value2); + item.value.clear(); + Key hashKey; + DistributedDBToolsUnitTest::CalcHash(key3, hashKey); + g_deviceC->GetData(hashKey, item); + EXPECT_TRUE((item.flag & VirtualDataItem::DELETE_FLAG) == 1); + item.value.clear(); + g_deviceC->GetData(key4, item); + EXPECT_TRUE(item.value == value4); +} + +/** + * @tc.name: Device Offline Sync 002 + * @tc.desc: Test pull sync when device offline + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, DeviceOfflineSync002, TestSize.Level1) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceB put {k1, v1} + */ + Key key1 = {'1'}; + Value value1 = {'1'}; + g_deviceB->PutData(key1, value1, 0, 0); + + /** + * @tc.steps: step2. deviceB offline + */ + g_deviceB->Offline(); + + /** + * @tc.steps: step3. deviceC put {k2, v2}, {k3, delete}, {k4, v4} + */ + Key key2 = {'2'}; + Value value2 = {'2'}; + g_deviceC->PutData(key2, value2, 0, 0); + + Key key3 = {'3'}; + Value value3 = {'3'}; + g_deviceC->PutData(key3, value3, 0, 1); + + Key key4 = {'4'}; + Value value4 = {'4'}; + g_deviceC->PutData(key4, value4, 0, 0); + + /** + * @tc.steps: step2. deviceA call pull sync + * @tc.expected: step2. sync should return OK. + */ + std::map result; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, DeviceB status is timeout + * deviceA has {k2, v2}, {k3 delete}, {k4,v4} + */ + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + if (pair.first == DEVICE_B) { + EXPECT_TRUE(pair.second == COMM_FAILURE); + } else { + EXPECT_TRUE(pair.second == OK); + } + } + + Value value5; + EXPECT_TRUE(g_kvDelegatePtr->Get(key1, value5) != OK); + g_kvDelegatePtr->Get(key2, value5); + EXPECT_EQ(value5, value2); + EXPECT_TRUE(g_kvDelegatePtr->Get(key3, value5) != OK); + g_kvDelegatePtr->Get(key4, value5); + EXPECT_EQ(value5, value4); +} + +/** + * @tc.name: Auto Sync 001 + * @tc.desc: Verify auto sync enable function. + * @tc.type: FUNC + * @tc.require: AR000CKRTD AR000CQE0E + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, AutoSync001, TestSize.Level1) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. enable auto sync + * @tc.expected: step1, Pragma return OK. + */ + bool autoSync = true; + PragmaData data = static_cast(&autoSync); + DBStatus status = g_kvDelegatePtr->Pragma(AUTO_SYNC, data); + ASSERT_EQ(status, OK); + + /** + * @tc.steps: step2. deviceA put {k1, v1}, {k2, v2} + */ + ASSERT_TRUE(g_kvDelegatePtr->Put(KEY_1, VALUE_1) == OK); + ASSERT_TRUE(g_kvDelegatePtr->Put(KEY_2, VALUE_2) == OK); + + /** + * @tc.steps: step3. sleep for data sync + * @tc.expected: step3. deviceB,C has {k1, v1}, {k2, v2} + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + VirtualDataItem item; + g_deviceB->GetData(KEY_1, item); + EXPECT_EQ(item.value, VALUE_1); + g_deviceB->GetData(KEY_2, item); + EXPECT_EQ(item.value, VALUE_2); + g_deviceC->GetData(KEY_1, item); + EXPECT_EQ(item.value, VALUE_1); + g_deviceC->GetData(KEY_2, item); + EXPECT_EQ(item.value, VALUE_2); +} + +/** + * @tc.name: Auto Sync 002 + * @tc.desc: Verify auto sync disable function. + * @tc.type: FUNC + * @tc.require: AR000CKRTD AR000CQE0E + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, AutoSync002, TestSize.Level1) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. disable auto sync + * @tc.expected: step1, Pragma return OK. + */ + bool autoSync = false; + PragmaData data = static_cast(&autoSync); + DBStatus status = g_kvDelegatePtr->Pragma(AUTO_SYNC, data); + ASSERT_EQ(status, OK); + + /** + * @tc.steps: step2. deviceB put {k1, v1}, deviceC put {k2, v2} + */ + g_deviceB->PutData(KEY_1, VALUE_1, 0, 0); + g_deviceC->PutData(KEY_2, VALUE_2, 0, 0); + + /** + * @tc.steps: step3. sleep for data sync + * @tc.expected: step3. deviceA don't have k1, k2. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + Value value3; + EXPECT_TRUE(g_kvDelegatePtr->Get(KEY_1, value3) == NOT_FOUND); + EXPECT_TRUE(g_kvDelegatePtr->Get(KEY_2, value3) == NOT_FOUND); +} + +/** + * @tc.name: Block Sync 001 + * @tc.desc: Verify block push sync function. + * @tc.type: FUNC + * @tc.require: AR000CKRTD AR000CQE0E + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, BlockSync001, TestSize.Level1) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + g_kvDelegatePtr->Put(KEY_1, VALUE_1); + + /** + * @tc.steps: step2. deviceA call block push sync to deviceB & deviceC. + * @tc.expected: step2. Sync return OK, devices status OK, deviceB & deivceC has {k1, v1}. + */ + std::map result; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result, true); + ASSERT_EQ(status, OK); + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item1; + EXPECT_EQ(g_deviceB->GetData(KEY_1, item1), OK); + EXPECT_EQ(item1.value, VALUE_1); + VirtualDataItem item2; + EXPECT_EQ(g_deviceC->GetData(KEY_1, item2), OK); + EXPECT_EQ(item2.value, VALUE_1); +} + +/** + * @tc.name: Block Sync 002 + * @tc.desc: Verify block pull sync function. + * @tc.type: FUNC + * @tc.require: AR000CKRTD AR000CQE0E + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, BlockSync002, TestSize.Level1) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceB put {k1, v1}, deviceC put {k2, v2} + */ + g_deviceB->PutData(KEY_1, VALUE_1, 0, 0); + g_deviceC->PutData(KEY_2, VALUE_2, 0, 0); + + /** + * @tc.steps: step2. deviceA call block pull and pull sync to deviceB & deviceC. + * @tc.expected: step2. Sync return OK, devices status OK, deviceA has {k1, v1}, {k2, v2} + */ + std::map result; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result, true); + ASSERT_EQ(status, OK); + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + Value value3; + EXPECT_TRUE(g_kvDelegatePtr->Get(KEY_1, value3) == OK); + EXPECT_TRUE(value3 == VALUE_1); + EXPECT_TRUE(g_kvDelegatePtr->Get(KEY_2, value3) == OK); + EXPECT_TRUE(value3 == VALUE_2); +} + +/** + * @tc.name: Block Sync 003 + * @tc.desc: Verify block push and pull sync function. + * @tc.type: FUNC + * @tc.require: AR000CKRTD AR000CQE0E + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, BlockSync003, TestSize.Level1) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + g_kvDelegatePtr->Put(KEY_1, VALUE_1); + + /** + * @tc.steps: step2. deviceB put {k1, v1}, deviceB put {k2, v2} + */ + g_deviceB->PutData(KEY_2, VALUE_2, 0, 0); + g_deviceC->PutData(KEY_3, VALUE_3, 0, 0); + + /** + * @tc.steps: step3. deviceA call block pull and pull sync to deviceB & deviceC. + * @tc.expected: step3. Sync return OK, devices status OK, deviceA has {k1, v1}, {k2, v2} {k3, v3} + * deviceB has {k1, v1}, {k2. v2} , deviceC has {k1, v1}, {k3, v3} + */ + std::map result; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, result, true); + ASSERT_EQ(status, OK); + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == OK); + } + + VirtualDataItem item1; + g_deviceB->GetData(KEY_1, item1); + EXPECT_TRUE(item1.value == VALUE_1); + g_deviceB->GetData(KEY_2, item1); + EXPECT_TRUE(item1.value == VALUE_2); + + VirtualDataItem item2; + g_deviceC->GetData(KEY_1, item2); + EXPECT_TRUE(item2.value == VALUE_1); + g_deviceC->GetData(KEY_3, item2); + EXPECT_TRUE(item2.value == VALUE_3); + + Value value3; + EXPECT_TRUE(g_kvDelegatePtr->Get(KEY_1, value3) == OK); + EXPECT_TRUE(value3 == VALUE_1); + EXPECT_TRUE(g_kvDelegatePtr->Get(KEY_2, value3) == OK); + EXPECT_TRUE(value3 == VALUE_2); + EXPECT_TRUE(g_kvDelegatePtr->Get(KEY_3, value3) == OK); + EXPECT_TRUE(value3 == VALUE_3); +} + +/** + * @tc.name: Block Sync 004 + * @tc.desc: Verify block sync function invalid args. + * @tc.type: FUNC + * @tc.require: AR000CKRTD AR000CQE0E + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, BlockSync004, TestSize.Level2) +{ + std::vector devices; + + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + g_kvDelegatePtr->Put(KEY_1, VALUE_1); + + /** + * @tc.steps: step2. deviceA call block push sync to deviceB & deviceC. + * @tc.expected: step2. Sync return INVALID_ARGS + */ + std::map result; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result, true); + EXPECT_EQ(status, INVALID_ARGS); + + /** + * @tc.steps: step3. deviceB, deviceC offlinem and push deviceA sync to deviceB and deviceC. + * @tc.expected: step3. Sync return OK, but the deviceB and deviceC are TIME_OUT + */ + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + g_deviceB->Offline(); + g_deviceC->Offline(); + + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result, true); + EXPECT_EQ(status, OK); + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == COMM_FAILURE); + } +} + +/** + * @tc.name: Block Sync 005 + * @tc.desc: Verify block sync function busy. + * @tc.type: FUNC + * @tc.require: AR000CKRTD AR000CQE0E + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, BlockSync005, TestSize.Level2) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + g_kvDelegatePtr->Put(KEY_1, VALUE_1); + + /** + * @tc.steps: step2. New a thread to deviceA call block push sync to deviceB & deviceC, + * but deviceB & C is blocked + * @tc.expected: step2. Sync will be blocked util timeout, and then return OK + */ + g_deviceB->Offline(); + g_deviceC->Offline(); + thread thread([devices](){ + std::map resultInner; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, resultInner, true); + EXPECT_EQ(status, OK); + }); + thread.detach(); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps: step3. sleep 1s and call sync. + * @tc.expected: step3. Sync will return BUSY. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + std::map result; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, result, true); + EXPECT_EQ(status, OK); +} + +/** + * @tc.name: SyncQueue001 + * @tc.desc: Invalid args check of Pragma GET_QUEUED_SYNC_SIZE SET_QUEUED_SYNC_LIMIT and + * GET_QUEUED_SYNC_LIMIT, expect return INVALID_ARGS. + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, SyncQueue001, TestSize.Level3) +{ + /** + * @tc.steps:step1. Set PragmaCmd to be GET_QUEUED_SYNC_SIZE, and set param to be null + * @tc.expected: step1. Expect return INVALID_ARGS. + */ + int *param = nullptr; + PragmaData input = static_cast(param); + EXPECT_EQ(g_kvDelegatePtr->Pragma(GET_QUEUED_SYNC_SIZE, input), INVALID_ARGS); + + /** + * @tc.steps:step2. Set PragmaCmd to be SET_QUEUED_SYNC_LIMIT, and set param to be null + * @tc.expected: step2. Expect return INVALID_ARGS. + */ + input = static_cast(param); + EXPECT_EQ(g_kvDelegatePtr->Pragma(SET_QUEUED_SYNC_LIMIT, input), INVALID_ARGS); + + /** + * @tc.steps:step3. Set PragmaCmd to be GET_QUEUED_SYNC_LIMIT, and set param to be null + * @tc.expected: step3. Expect return INVALID_ARGS. + */ + input = static_cast(param); + EXPECT_EQ(g_kvDelegatePtr->Pragma(GET_QUEUED_SYNC_LIMIT, input), INVALID_ARGS); + + /** + * @tc.steps:step4. Set PragmaCmd to be SET_QUEUED_SYNC_LIMIT, and set param to be QUEUED_SYNC_LIMIT_MIN - 1 + * @tc.expected: step4. Expect return INVALID_ARGS. + */ + int limit = DBConstant::QUEUED_SYNC_LIMIT_MIN - 1; + input = static_cast(&limit); + EXPECT_EQ(g_kvDelegatePtr->Pragma(SET_QUEUED_SYNC_LIMIT, input), INVALID_ARGS); + + /** + * @tc.steps:step5. Set PragmaCmd to be SET_QUEUED_SYNC_LIMIT, and set param to be QUEUED_SYNC_LIMIT_MAX + 1 + * @tc.expected: step5. Expect return INVALID_ARGS. + */ + limit = DBConstant::QUEUED_SYNC_LIMIT_MAX + 1; + input = static_cast(&limit); + EXPECT_EQ(g_kvDelegatePtr->Pragma(SET_QUEUED_SYNC_LIMIT, input), INVALID_ARGS); +} + +/** + * @tc.name: SyncQueue002 + * @tc.desc: Pragma GET_QUEUED_SYNC_LIMIT and SET_QUEUED_SYNC_LIMIT + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, SyncQueue002, TestSize.Level3) +{ + /** + * @tc.steps:step1. Set PragmaCmd to be GET_QUEUED_SYNC_LIMIT, + * @tc.expected: step1. Expect return OK, limit eq QUEUED_SYNC_LIMIT_DEFAULT. + */ + int limit = 0; + PragmaData input = static_cast(&limit); + EXPECT_EQ(g_kvDelegatePtr->Pragma(GET_QUEUED_SYNC_LIMIT, input), OK); + EXPECT_EQ(limit, DBConstant::QUEUED_SYNC_LIMIT_DEFAULT); + + /** + * @tc.steps:step2. Set PragmaCmd to be SET_QUEUED_SYNC_LIMIT, and set param to be 50 + * @tc.expected: step2. Expect return OK. + */ + limit = 50; + input = static_cast(&limit); + EXPECT_EQ(g_kvDelegatePtr->Pragma(SET_QUEUED_SYNC_LIMIT, input), OK); + + /** + * @tc.steps:step3. Set PragmaCmd to be GET_QUEUED_SYNC_LIMIT, + * @tc.expected: step3. Expect return OK, limit eq 50 + */ + limit = 0; + input = static_cast(&limit); + EXPECT_EQ(g_kvDelegatePtr->Pragma(GET_QUEUED_SYNC_LIMIT, input), OK); + EXPECT_EQ(limit, 50); +} + +/** + * @tc.name: SyncQueue003 + * @tc.desc: sync queue test + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, SyncQueue003, TestSize.Level3) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps:step1. Set PragmaCmd to be GET_QUEUED_SYNC_SIZE, + * @tc.expected: step1. Expect return OK, size eq 0. + */ + int size; + PragmaData input = static_cast(&size); + EXPECT_EQ(g_kvDelegatePtr->Pragma(GET_QUEUED_SYNC_SIZE, input), OK); + EXPECT_EQ(size, 0); + + /** + * @tc.steps:step2. deviceA put {k1, v1} + */ + status = g_kvDelegatePtr->Put(KEY_1, VALUE_1); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps:step3. deviceA sync SYNC_MODE_PUSH_ONLY + */ + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, nullptr, false); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps:step4. deviceA put {k2, v2} + */ + status = g_kvDelegatePtr->Put(KEY_2, VALUE_2); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps:step5. deviceA sync SYNC_MODE_PUSH_ONLY + */ + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, nullptr, false); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps:step6. deviceB put {k3, v3} + */ + g_deviceB->PutData(KEY_3, VALUE_3, 0, 0); + + /** + * @tc.steps:step7. deviceA put {k4, v4} + */ + status = g_kvDelegatePtr->Put(KEY_4, VALUE_4); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps:step8. deviceA sync SYNC_MODE_PUSH_PULL + */ + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_PULL, nullptr, false); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps:step9. Set PragmaCmd to be GET_QUEUED_SYNC_SIZE, + * @tc.expected: step1. Expect return OK, 0 <= size <= 4 + */ + EXPECT_EQ(g_kvDelegatePtr->Pragma(GET_QUEUED_SYNC_SIZE, input), OK); + ASSERT_TRUE((size >= 0) && (size <= 4)); + + /** + * @tc.steps:step10. deviceB put {k5, v5} + */ + g_deviceB->PutData(KEY_5, VALUE_5, 0, 0); + + /** + * @tc.steps:step11. deviceA call sync and wait + * @tc.expected: step11. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step11. onComplete should be called, DeviceA,B,C have {k1,v1}~ {KEY_5,VALUE_5} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + EXPECT_TRUE(pair.second == OK); + } + VirtualDataItem item; + g_deviceB->GetData(KEY_1, item); + EXPECT_TRUE(item.value == VALUE_1); + g_deviceB->GetData(KEY_2, item); + EXPECT_TRUE(item.value == VALUE_2); + g_deviceB->GetData(KEY_3, item); + EXPECT_TRUE(item.value == VALUE_3); + g_deviceB->GetData(KEY_4, item); + EXPECT_TRUE(item.value == VALUE_4); + g_deviceB->GetData(KEY_5, item); + EXPECT_TRUE(item.value == VALUE_5); + Value value; + EXPECT_EQ(g_kvDelegatePtr->Get(KEY_3, value), OK); + EXPECT_EQ(VALUE_3, value); + EXPECT_EQ(g_kvDelegatePtr->Get(KEY_5, value), OK); + EXPECT_EQ(VALUE_5, value); +} + +/** + * @tc.name: SyncQueue004 + * @tc.desc: sync queue full test + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, SyncQueue004, TestSize.Level3) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps:step1. deviceB C block + */ + g_communicatorAggregator->SetBlockValue(true); + + /** + * @tc.steps:step2. deviceA put {k1, v1} + */ + status = g_kvDelegatePtr->Put(KEY_1, VALUE_1); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps:step3. deviceA sync QUEUED_SYNC_LIMIT_DEFAULT times + * @tc.expected: step3. Expect return OK + */ + for (int i = 0; i < DBConstant::QUEUED_SYNC_LIMIT_DEFAULT; i++) { + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, nullptr, false); + ASSERT_TRUE(status == OK); + } + + /** + * @tc.steps:step4. deviceA sync + * @tc.expected: step4. Expect return BUSY + */ + status = g_kvDelegatePtr->Sync(devices, SYNC_MODE_PUSH_ONLY, nullptr, false); + ASSERT_TRUE(status == BUSY); + g_communicatorAggregator->SetBlockValue(false); +} + +/** + * @tc.name: SyncQueue005 + * @tc.desc: block sync queue test + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, SyncQueue005, TestSize.Level3) +{ + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + /** + * @tc.steps:step1. New a thread to deviceA call block push sync to deviceB & deviceC, + * but deviceB & C is offline + * @tc.expected: step1. Sync will be blocked util timeout, and then return OK + */ + g_deviceB->Offline(); + g_deviceC->Offline(); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + + /** + * @tc.steps:step2. deviceA put {k1, v1} + */ + g_kvDelegatePtr->Put(KEY_1, VALUE_1); + + std::mutex lockMutex; + std::condition_variable conditionVar; + + std::thread threadFirst([devices](){ + std::map resultInner; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, resultInner, true); + EXPECT_EQ(status, OK); + }); + threadFirst.detach(); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + /** + * @tc.steps:step3. New a thread to deviceA call block push sync to deviceB & deviceC, + * but deviceB & C is offline + * @tc.expected: step2. Sync will be blocked util timeout, and then return OK + */ + std::thread threadSecond([devices, &lockMutex, &conditionVar](){ + std::map resultInner; + DBStatus status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, resultInner, true); + EXPECT_EQ(status, OK); + std::unique_lock lockInner(lockMutex); + conditionVar.notify_one(); + }); + threadSecond.detach(); + + /** + * @tc.steps:step4. Set PragmaCmd to be GET_QUEUED_SYNC_SIZE, + * @tc.expected: step1. Expect return OK, size eq 0. + */ + int size; + PragmaData input = static_cast(&size); + EXPECT_EQ(g_kvDelegatePtr->Pragma(GET_QUEUED_SYNC_SIZE, input), OK); + EXPECT_EQ(size, 0); + + /** + * @tc.steps:step5. wait exit + */ + std::unique_lock lock(lockMutex); + auto now = std::chrono::system_clock::now(); + conditionVar.wait_until(lock, now + 2 * INT8_MAX * 1000ms); +} + +/** + * @tc.name: PermissionCheck001 + * @tc.desc: deviceA PermissionCheck not pass test, SYNC_MODE_PUSH_ONLY + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, PermissionCheck001, TestSize.Level3) +{ + /** + * @tc.steps: step1. SetPermissionCheckCallback + * @tc.expected: step1. return OK. + */ + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + if (flag & CHECK_FLAG_SEND) { + LOGD("in RunPermissionCheck callback func, check not pass, flag:%d", flag); + return false; + } else { + LOGD("in RunPermissionCheck callback func, check pass, flag:%d", flag); + return true; + } + }; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(permissionCheckCallback), OK); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step2. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call sync and wait + * @tc.expected: step3. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, + * status == PERMISSION_CHECK_FORBID_SYNC, deviceB and deviceC do not have {k1, v1} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == PERMISSION_CHECK_FORBID_SYNC); + } + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value.empty()); + g_deviceC->GetData(key, item); + EXPECT_TRUE(item.value.empty()); + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} + +/** + * @tc.name: PermissionCheck002 + * @tc.desc: deviceA PermissionCheck not pass test, SYNC_MODE_PULL_ONLY + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, PermissionCheck002, TestSize.Level3) +{ + /** + * @tc.steps: step1. SetPermissionCheckCallback + * @tc.expected: step1. return OK. + */ + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + if (flag & CHECK_FLAG_RECEIVE) { + LOGD("in RunPermissionCheck callback func, check not pass, flag:%d", flag); + return false; + } else { + LOGD("in RunPermissionCheck callback func, check pass, flag:%d", flag); + return true; + } + }; + + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(permissionCheckCallback), OK); + + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step2. deviceB put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + g_deviceB->PutData(key, value, 0, 0); + + /** + * @tc.steps: step2. deviceB put {k2, v2} + */ + Key key2 = {'2'}; + Value value2 = {'2'}; + g_deviceC->PutData(key2, value2, 0, 0); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call pull sync + * @tc.expected: step3. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, + * status == PERMISSION_CHECK_FORBID_SYNC, DeviceA do not have {k1, VALUE_1}, {K2. VALUE_2} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == PERMISSION_CHECK_FORBID_SYNC); + } + Value value3; + EXPECT_EQ(g_kvDelegatePtr->Get(key, value3), NOT_FOUND); + EXPECT_EQ(g_kvDelegatePtr->Get(key2, value3), NOT_FOUND); + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} + +/** + * @tc.name: PermissionCheck003 + * @tc.desc: deviceA PermissionCheck not pass test, SYNC_MODE_PUSH_PULL + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, PermissionCheck003, TestSize.Level3) +{ + /** + * @tc.steps: step1. SetPermissionCheckCallback + * @tc.expected: step1. return OK. + */ + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + if (flag & (CHECK_FLAG_SEND | CHECK_FLAG_RECEIVE)) { + LOGD("in RunPermissionCheck callback func, check not pass, flag:%d", flag); + return false; + } else { + LOGD("in RunPermissionCheck callback func, check pass, flag:%d", flag); + return true; + } + }; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(permissionCheckCallback), OK); + + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step2. deviceA put {k1, v1} + */ + Key key1 = {'1'}; + Value value1 = {'1'}; + DBStatus status = g_kvDelegatePtr->Put(key1, value1); + EXPECT_TRUE(status == OK); + + /** + * @tc.steps: step2. deviceB put {k2, v2} + */ + Key key2 = {'2'}; + Value value2 = {'2'}; + g_deviceB->PutData(key2, value2, 0, 0); + + /** + * @tc.steps: step2. deviceB put {k3, v3} + */ + Key key3 = {'3'}; + Value value3 = {'3'}; + g_deviceC->PutData(key3, value3, 0, 0); + + /** + * @tc.steps: step3. deviceA call push_pull sync + * @tc.expected: step3. sync should return OK. + * onComplete should be called, status == PERMISSION_CHECK_FORBID_SYNC + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_PULL, result); + ASSERT_TRUE(status == OK); + + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == PERMISSION_CHECK_FORBID_SYNC); + } + + /** + * @tc.expected: step3. DeviceA only have {k1. v1} + * DeviceB only have {k2. v2}, DeviceC only have {k3. v3} + */ + Value value4; + EXPECT_TRUE(g_kvDelegatePtr->Get(key2, value4) == NOT_FOUND); + EXPECT_TRUE(g_kvDelegatePtr->Get(key3, value4) == NOT_FOUND); + + VirtualDataItem item1; + g_deviceB->GetData(key1, item1); + EXPECT_TRUE(item1.value.empty()); + g_deviceB->GetData(key3, item1); + EXPECT_TRUE(item1.value.empty()); + + VirtualDataItem item2; + g_deviceC->GetData(key1, item2); + EXPECT_TRUE(item1.value.empty()); + g_deviceC->GetData(key2, item2); + EXPECT_TRUE(item2.value.empty()); + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} + +/** + * @tc.name: PermissionCheck004 + * @tc.desc: deviceB and deviceC PermissionCheck not pass test, SYNC_MODE_PUSH_ONLY + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, PermissionCheck004, TestSize.Level3) +{ + /** + * @tc.steps: step1. SetPermissionCheckCallback + * @tc.expected: step1. return OK. + */ + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + if (flag & CHECK_FLAG_RECEIVE) { + LOGD("in RunPermissionCheck callback func, check not pass, flag:%d", flag); + return false; + } else { + LOGD("in RunPermissionCheck callback func, check pass, flag:%d", flag); + return true; + } + }; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(permissionCheckCallback), OK); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step2. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call sync and wait + * @tc.expected: step3. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, + * status == PERMISSION_CHECK_FORBID_SYNC, deviceB and deviceC do not have {k1, v1} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == PERMISSION_CHECK_FORBID_SYNC); + } + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value.empty()); + g_deviceC->GetData(key, item); + EXPECT_TRUE(item.value.empty()); + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} + +/** + * @tc.name: PermissionCheck005 + * @tc.desc: deviceB and deviceC PermissionCheck not pass test, SYNC_MODE_PULL_ONLY + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, PermissionCheck005, TestSize.Level3) +{ + /** + * @tc.steps: step1. SetPermissionCheckCallback + * @tc.expected: step1. return OK. + */ + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + if (flag & CHECK_FLAG_SEND) { + LOGD("in RunPermissionCheck callback func, check not pass, flag:%d", flag); + return false; + } else { + LOGD("in RunPermissionCheck callback func, check pass, flag:%d", flag); + return true; + } + }; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(permissionCheckCallback), OK); + + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step2. deviceB put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + g_deviceB->PutData(key, value, 0, 0); + + /** + * @tc.steps: step2. deviceB put {k2, v2} + */ + Key key2 = {'2'}; + Value value2 = {'2'}; + g_deviceC->PutData(key2, value2, 0, 0); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call pull sync + * @tc.expected: step3. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, + * status == PERMISSION_CHECK_FORBID_SYNC, DeviceA do not have {k1, VALUE_1}, {K2. VALUE_2} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + EXPECT_TRUE(pair.second == PERMISSION_CHECK_FORBID_SYNC); + } + Value value3; + EXPECT_EQ(g_kvDelegatePtr->Get(key, value3), NOT_FOUND); + EXPECT_EQ(g_kvDelegatePtr->Get(key2, value3), NOT_FOUND); + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} + +/** + * @tc.name: PermissionCheck006 + * @tc.desc: deviceA PermissionCheck deviceB not pass, deviceC pass + * @tc.type: FUNC + * @tc.require: AR000EJJOJ + * @tc.author: wangchuanqing + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, PermissionCheck006, TestSize.Level3) +{ + /** + * @tc.steps: step1. SetPermissionCheckCallback + * @tc.expected: step1. return OK. + */ + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + if (deviceId == g_deviceB->GetDeviceId()) { + LOGD("in RunPermissionCheck callback func, check not pass, flag:%d", flag); + return false; + } else { + LOGD("in RunPermissionCheck callback func, check pass, flag:%d", flag); + return true; + } + }; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(permissionCheckCallback), OK); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step2. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call sync and wait + * @tc.expected: step3. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + + /** + * @tc.expected: step3. onComplete should be called, + * status == PERMISSION_CHECK_FORBID_SYNC, deviceB and deviceC do not have {k1, v1} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + if (g_deviceB->GetDeviceId() == pair.first) { + EXPECT_TRUE(pair.second == PERMISSION_CHECK_FORBID_SYNC); + } else { + EXPECT_TRUE(pair.second == OK); + } + } + VirtualDataItem item; + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value.empty()); + g_deviceC->GetData(key, item); + EXPECT_TRUE(item.value == value); + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} + +/** + * @tc.name: PermissionCheck007 + * @tc.desc: deviceA PermissionCheck, deviceB not pass, deviceC pass in SYNC_MODE_AUTO_PUSH + * @tc.type: FUNC + * @tc.require: AR000G3RLS + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, PermissionCheck007, TestSize.Level3) +{ + /** + * @tc.steps: step1. SetPermissionCheckCallback + * @tc.expected: step1. return OK. + */ + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + if (deviceId == g_deviceC->GetDeviceId() && + (flag & (CHECK_FLAG_RECEIVE | CHECK_FLAG_AUTOSYNC))) { + LOGD("in RunPermissionCheck callback func, check not pass, flag:%d", flag); + return false; + } else { + LOGD("in RunPermissionCheck callback func, check pass, flag:%d", flag); + return true; + } + }; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(permissionCheckCallback), OK); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + /** + * @tc.steps: step2. deviceA set auto sync + */ + bool autoSync = true; + PragmaData data = static_cast(&autoSync); + status = g_kvDelegatePtr->Pragma(AUTO_SYNC, data); + ASSERT_EQ(status, OK); + + /** + * @tc.steps: step3. deviceA put {k1, v1}, and sleep 1s + */ + Key key = {'1'}; + Value value = {'1'}; + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + + /** + * @tc.steps: step3. check value in device B and not in device C. + */ + VirtualDataItem item; + g_deviceC->GetData(key, item); + EXPECT_TRUE(item.value.empty()); + g_deviceB->GetData(key, item); + EXPECT_TRUE(item.value == value); + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} + +/** ++ * @tc.name: PermissionCheck008 ++ * @tc.desc: deviceA PermissionCheck, deviceB not pass, deviceC pass in SYNC_MODE_AUTO_PULL ++ * @tc.type: FUNC ++ * @tc.require: AR000G3RLS ++ * @tc.author: zhangqiquan ++ */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, PermissionCheck008, TestSize.Level3) +{ + /** + * @tc.steps: step1. SetPermissionCheckCallback + * @tc.expected: step1. return OK. + */ + auto permissionCheckCallback = [] (const std::string &userId, const std::string &appId, const std::string &storeId, + const std::string &deviceId, uint8_t flag) -> bool { + if (deviceId == g_deviceC->GetDeviceId() && + (flag & CHECK_FLAG_SPONSOR)) { + LOGD("in RunPermissionCheck callback func, check not pass, flag:%d", flag); + return false; + } else { + LOGD("in RunPermissionCheck callback func, check pass, flag:%d", flag); + return true; + } + }; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(permissionCheckCallback), OK); + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + devices.push_back(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step2. deviceB put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + g_deviceB->PutData(key, value, 0, 0); + + /** + * @tc.steps: step2. device put {k2, v2} + */ + Key key2 = {'2'}; + Value value2 = {'2'}; + g_deviceC->PutData(key2, value2, 0, 0); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step3. deviceA call push sync + * @tc.expected: step3. sync should return OK. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PULL_ONLY, result); + ASSERT_TRUE(status == OK); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + + /** + * @tc.expected: step4. onComplete should be called, + * status == PERMISSION_CHECK_FORBID_SYNC, deviceB and deviceC do not have {k1, v1} + */ + ASSERT_TRUE(result.size() == devices.size()); + for (const auto &pair : result) { + LOGD("dev %s, status %d", pair.first.c_str(), pair.second); + if (g_deviceC->GetDeviceId() == pair.first) { + EXPECT_TRUE(pair.second == PERMISSION_CHECK_FORBID_SYNC); + } else { + EXPECT_TRUE(pair.second == OK); + } + } + /** + * @tc.steps: step5. check value in device A + */ + Value value4; + EXPECT_TRUE(g_kvDelegatePtr->Get(key, value4) == OK); + EXPECT_TRUE(g_kvDelegatePtr->Get(key2, value4) == NOT_FOUND); + PermissionCheckCallbackV2 nullCallback; + EXPECT_EQ(g_mgr.SetPermissionCheckCallback(nullCallback), OK); +} + +/** + * @tc.name: SaveDataNotify001 + * @tc.desc: Test SaveDataNotify function, delay < 30s should sync ok, > 36 should timeout + * @tc.type: FUNC + * @tc.require: AR000D4876 + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, SaveDataNotify001, TestSize.Level3) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step1. deviceA put {k1, v1} + */ + Key key = {'1'}; + Value value = {'1'}; + status = g_kvDelegatePtr->Put(key, value); + ASSERT_TRUE(status == OK); + + /** + * @tc.steps: step2. deviceB set sava data dely 5s + */ + g_deviceB->SetSaveDataDelayTime(WAIT_5_SECONDS); + + /** + * @tc.steps: step3. deviceA call sync and wait + * @tc.expected: step3. sync should return OK. onComplete should be called, deviceB sync success. + */ + std::map result; + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + ASSERT_TRUE(result.size() == devices.size()); + ASSERT_TRUE(result[DEVICE_B] == OK); + + /** + * @tc.steps: step4. deviceB set sava data dely 30s and put {k1, v1} + */ + g_deviceB->SetSaveDataDelayTime(WAIT_30_SECONDS); + status = g_kvDelegatePtr->Put(key, value); + + /** + * @tc.steps: step3. deviceA call sync and wait + * @tc.expected: step3. sync should return OK. onComplete should be called, deviceB sync success. + */ + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + ASSERT_TRUE(result.size() == devices.size()); + ASSERT_TRUE(result[DEVICE_B] == OK); + + /** + * @tc.steps: step4. deviceB set sava data dely 36s and put {k1, v1} + */ + g_deviceB->SetSaveDataDelayTime(WAIT_36_SECONDS); + status = g_kvDelegatePtr->Put(key, value); + + /** + * @tc.steps: step5. deviceA call sync and wait + * @tc.expected: step5. sync should return OK. onComplete should be called, deviceB sync TIME_OUT. + */ + status = g_tool.SyncTest(g_kvDelegatePtr, devices, SYNC_MODE_PUSH_ONLY, result); + ASSERT_TRUE(status == OK); + ASSERT_TRUE(result.size() == devices.size()); + ASSERT_TRUE(result[DEVICE_B] == TIME_OUT); +} + +/** + * @tc.name: SametimeSync001 + * @tc.desc: Test 2 device sync with each other + * @tc.type: FUNC + * @tc.require: AR000CCPOM + * @tc.author: zhangqiquan + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, SametimeSync001, TestSize.Level3) +{ + DBStatus status = OK; + std::vector devices; + devices.push_back(g_deviceB->GetDeviceId()); + + int responseCount = 0; + int requestCount = 0; + Key key = {'1'}; + Value value = {'1'}; + /** + * @tc.steps: step1. make sure deviceB send pull firstly and response_pull secondly + * @tc.expected: step1. deviceA put data when finish push task. put data should return OK. + */ + g_communicatorAggregator->RegOnDispatch([&responseCount, &requestCount, &key, &value]( + const std::string &target, DistributedDB::Message *msg) { + if (target == "real_device" && msg->GetMessageId() == DATA_SYNC_MESSAGE) { + if (msg->GetMessageType() == TYPE_RESPONSE) { + responseCount++; + if (responseCount == 1) { // 1 is the ack which B response A's push task + EXPECT_EQ(g_kvDelegatePtr->Put(key, value), DBStatus::OK); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } else if (responseCount == 2) { // 2 is the ack which B response A's response_pull task + msg->SetErrorNo(E_FEEDBACK_COMMUNICATOR_NOT_FOUND); + } + } if (msg->GetMessageType() == TYPE_REQUEST) { + requestCount++; + if (requestCount == 1) { // 1 is A push task + std::this_thread::sleep_for(std::chrono::seconds(2)); // sleep 2 sec + } + } + } + }); + /** + * @tc.steps: step2. deviceA,deviceB sync to each other at same time + * @tc.expected: step2. sync should return OK. + */ + std::map result; + std::thread subThread([]{ + g_deviceB->Sync(DistributedDB::SYNC_MODE_PULL_ONLY, true); + }); + status = g_tool.SyncTest(g_kvDelegatePtr, devices, DistributedDB::SYNC_MODE_PUSH_PULL, result); + subThread.join(); + g_communicatorAggregator->RegOnDispatch(nullptr); + + EXPECT_TRUE(status == OK); + ASSERT_TRUE(result.size() == devices.size()); + EXPECT_TRUE(result[DEVICE_B] == OK); + Value actualValue; + g_kvDelegatePtr->Get(key, actualValue); + EXPECT_EQ(actualValue, value); +} + +/** + * @tc.name: DatabaseOnlineCallback001 + * @tc.desc: check database status notify online callback + * @tc.type: FUNC + * @tc.require: AR000CQS3S SR000CQE0B + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, DatabaseOnlineCallback001, TestSize.Level1) +{ + /** + * @tc.steps: step1. SetStoreStatusNotifier + * @tc.expected: step1. SetStoreStatusNotifier ok + */ + std::string targetDev = "DEVICE_X"; + bool isCheckOk = false; + auto databaseStatusNotifyCallback = [targetDev, &isCheckOk] (std::string userId, + std::string appId, std::string storeId, const std::string deviceId, bool onlineStatus) -> void { + if (userId == USER_ID && appId == APP_ID && storeId == STORE_ID && deviceId == targetDev && + onlineStatus == true) { + isCheckOk = true; + }}; + g_mgr.SetStoreStatusNotifier(databaseStatusNotifyCallback); + /** + * @tc.steps: step2. trigger device online + * @tc.expected: step2. check callback ok + */ + g_communicatorAggregator->OnlineDevice(targetDev); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME / 20)); + EXPECT_EQ(isCheckOk, true); + StoreStatusNotifier nullCallback; + g_mgr.SetStoreStatusNotifier(nullCallback); +} + +/** + * @tc.name: DatabaseOfflineCallback001 + * @tc.desc: check database status notify online callback + * @tc.type: FUNC + * @tc.require: AR000CQS3S SR000CQE0B + * @tc.author: zhuwentao + */ +HWTEST_F(DistributedDBSingleVerP2PSyncTest, DatabaseOfflineCallback001, TestSize.Level1) +{ + /** + * @tc.steps: step1. SetStoreStatusNotifier + * @tc.expected: step1. SetStoreStatusNotifier ok + */ + std::string targetDev = "DEVICE_X"; + bool isCheckOk = false; + auto databaseStatusNotifyCallback = [targetDev, &isCheckOk] (std::string userId, + std::string appId, std::string storeId, const std::string deviceId, bool onlineStatus) -> void { + if (userId == USER_ID && appId == APP_ID && storeId == STORE_ID && deviceId == targetDev && + onlineStatus == false) { + isCheckOk = true; + }}; + g_mgr.SetStoreStatusNotifier(databaseStatusNotifyCallback); + /** + * @tc.steps: step2. trigger device offline + * @tc.expected: step2. check callback ok + */ + g_communicatorAggregator->OfflineDevice(targetDev); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME / 20)); + EXPECT_EQ(isCheckOk, true); + StoreStatusNotifier nullCallback; + g_mgr.SetStoreStatusNotifier(nullCallback); +} diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_syncer_device_manager_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_syncer_device_manager_test.cpp new file mode 100644 index 00000000..c4593b97 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_syncer_device_manager_test.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "device_manager.h" +#include "distributeddb_tools_unit_test.h" +#include "kv_virtual_device.h" +#include "log_print.h" +#include "parcel.h" +#include "sync_types.h" +#include "virtual_communicator.h" +#include "virtual_communicator_aggregator.h" +#include "virtual_single_ver_sync_db_Interface.h" + +using namespace testing::ext; +using namespace DistributedDB; +using namespace std; + +namespace { + const std::string DEVICE_B = "deviceB"; + const std::string DEVICE_C = "deviceC"; + VirtualCommunicatorAggregator* g_communicatorAggregator = nullptr; + VirtualCommunicator* g_virtualCommunicator = nullptr; + KvVirtualDevice *g_deviceB = nullptr; + KvVirtualDevice *g_deviceC = nullptr; + DeviceManager *g_deviceManager = nullptr; + const int WAIT_TIME = 1000; +} + +class DistributedDBSyncerDeviceManagerTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBSyncerDeviceManagerTest::SetUpTestCase(void) +{ + /** + * @tc.setup: Virtual Communicator. + */ + g_communicatorAggregator = new (std::nothrow) VirtualCommunicatorAggregator(); + ASSERT_TRUE(g_communicatorAggregator != nullptr); + RuntimeContext::GetInstance()->SetCommunicatorAggregator(g_communicatorAggregator); + int errCode; + g_virtualCommunicator = static_cast(g_communicatorAggregator->AllocCommunicator(0, errCode)); + ASSERT_TRUE(g_virtualCommunicator != nullptr); +} + +void DistributedDBSyncerDeviceManagerTest::TearDownTestCase(void) +{ + /** + * @tc.setup: Release Virtual CommunicatorAggregator. + */ + RuntimeContext::GetInstance()->SetCommunicatorAggregator(nullptr); + g_communicatorAggregator = nullptr; +} + +void DistributedDBSyncerDeviceManagerTest::SetUp(void) +{ + DistributedDBUnitTest::DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: Init a DeviceManager and DeviceB, C + */ + g_deviceManager = new (std::nothrow) DeviceManager; + ASSERT_TRUE(g_deviceManager != nullptr); + g_deviceManager->Initialize(g_virtualCommunicator, nullptr, nullptr); + g_virtualCommunicator->RegOnConnectCallback( + std::bind(&DeviceManager::OnDeviceConnectCallback, g_deviceManager, + std::placeholders::_1, std::placeholders::_2), nullptr); + + g_deviceB = new (std::nothrow) KvVirtualDevice(DEVICE_B); + ASSERT_TRUE(g_deviceB != nullptr); + VirtualSingleVerSyncDBInterface *syncInterfaceB = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(syncInterfaceB != nullptr); + ASSERT_EQ(g_deviceB->Initialize(g_communicatorAggregator, syncInterfaceB), E_OK); + + g_deviceC = new (std::nothrow) KvVirtualDevice(DEVICE_C); + ASSERT_TRUE(g_deviceC != nullptr); + VirtualSingleVerSyncDBInterface *syncInterfaceC = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(syncInterfaceC != nullptr); + ASSERT_EQ(g_deviceC->Initialize(g_communicatorAggregator, syncInterfaceC), E_OK); +} + +void DistributedDBSyncerDeviceManagerTest::TearDown(void) +{ + /** + * @tc.setup: Release a DeviceManager and DeviceB, C + */ + if (g_deviceManager != nullptr) { + g_virtualCommunicator->RegOnConnectCallback(nullptr, nullptr); + delete g_deviceManager; + g_deviceManager = nullptr; + } + if (g_deviceB != nullptr) { + delete g_deviceB; + g_deviceB = nullptr; + } + if (g_deviceC != nullptr) { + delete g_deviceC; + g_deviceC = nullptr; + } +} + +/** + * @tc.name: Online Callback 001 + * @tc.desc: Test DeviceManager device online callback function. + * @tc.type: FUNC + * @tc.require: AR000CKRTD AR000CQE0E + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSyncerDeviceManagerTest, OnlineCallback001, TestSize.Level0) +{ + bool onlineCalled = false; + + /** + * @tc.steps: step1. set device online callback + */ + g_deviceManager->RegDeviceOnLineCallBack([&onlineCalled](const std::string &targetDev) { + LOGD("DeviceManageTest online called, dev %s", targetDev.c_str()); + if (targetDev == g_deviceB->GetDeviceId()) { + LOGD("DEVICE TEST CALL ONLINE CALLBACK"); + onlineCalled = true; + } + }); + + /** + * @tc.steps: step2. deviceB online + * @tc.expected: step2, the online callback should be called. + */ + g_communicatorAggregator->OnlineDevice(g_deviceB->GetDeviceId()); + EXPECT_TRUE(onlineCalled); +} + +/** + * @tc.name: Offline Callback 001 + * @tc.desc: Test DeviceManager device offline callback function. + * @tc.type: FUNC + * @tc.require: AR000CKRTD AR000CQE0E + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSyncerDeviceManagerTest, OfflineCallback001, TestSize.Level0) +{ + bool offlineCalled = false; + g_communicatorAggregator->OnlineDevice(g_deviceB->GetDeviceId()); + + /** + * @tc.steps: step1. set device offline callback + */ + g_deviceManager->RegDeviceOffLineCallBack([&offlineCalled](const std::string &targetDev) { + LOGD("DeviceManageTest offline called, dev %s", targetDev.c_str()); + if (targetDev == g_deviceB->GetDeviceId()) { + offlineCalled = true; + } + }); + + /** + * @tc.steps: step2. deviceB offline + * @tc.expected: step2, the offline callback should be called. + */ + g_communicatorAggregator->OfflineDevice(g_deviceB->GetDeviceId()); + EXPECT_TRUE(offlineCalled); +} + +/** + * @tc.name: Get Devices 001 + * @tc.desc: Test DeviceManager GetDevices function. + * @tc.type: FUNC + * @tc.require: AR000CKRTD AR000CQE0E + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSyncerDeviceManagerTest, GetDevices001, TestSize.Level0) +{ + std::vector deviceList; + + /** + * @tc.steps: step1. call GetDevices + * @tc.expected: step1, GetDevices return deviceB,C + */ + g_deviceManager->GetOnlineDevices(deviceList); + int size = deviceList.size(); + ASSERT_EQ(size, 2); + EXPECT_TRUE(deviceList[0] == g_deviceB->GetDeviceId()); + EXPECT_TRUE(deviceList[1] == g_deviceC->GetDeviceId()); + g_communicatorAggregator->OfflineDevice(g_deviceC->GetDeviceId()); + + /** + * @tc.steps: step2. deiceC offline and call GetDevices + * @tc.expected: step2, GetDevices return deviceB + */ + g_deviceManager->GetOnlineDevices(deviceList); + ASSERT_TRUE(deviceList.size() == 1); + EXPECT_TRUE(deviceList[0] == g_deviceB->GetDeviceId()); +} + +/** + * @tc.name: Send BroadCast 001 + * @tc.desc: Test DeviceManager SendBroadCast function. + * @tc.type: FUNC + * @tc.require: AR000CKRTD AR000CQE0E + * @tc.author: xushaohua + */ +HWTEST_F(DistributedDBSyncerDeviceManagerTest, SendBroadCast001, TestSize.Level1) +{ + bool deviceBReviced = false; + bool deviceCReviced = false; + + /** + * @tc.steps: step1. deviceB, C set OnRemoteDataChanged callback + */ + g_deviceB->OnRemoteDataChanged([&deviceBReviced](const std::string &deviceId){ + deviceBReviced = true; + }); + g_deviceC->OnRemoteDataChanged([&deviceCReviced](const std::string &deviceId){ + deviceCReviced = true; + }); + + /** + * @tc.steps: step2. call SendBroadCast. + * @tc.expected: step2, deviceB,C OnRemoteDataChanged should be called + */ + int errCode = g_deviceManager->SendBroadCast(LOCAL_DATA_CHANGED); + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_TIME)); + ASSERT_TRUE(errCode == E_OK); + EXPECT_TRUE(deviceBReviced); + EXPECT_TRUE(deviceCReviced); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/distributeddb_time_sync_test.cpp b/mock/distributeddb/test/unittest/common/syncer/distributeddb_time_sync_test.cpp new file mode 100644 index 00000000..293d95c0 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/distributeddb_time_sync_test.cpp @@ -0,0 +1,484 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "distributeddb_data_generate_unit_test.h" +#include "distributeddb_tools_unit_test.h" +#include "isyncer.h" +#include "single_ver_sync_state_machine.h" +#include "single_ver_kv_sync_task_context.h" +#include "sync_types.h" +#include "virtual_single_ver_sync_db_Interface.h" +#include "virtual_time_sync_communicator.h" + +using namespace std; +using namespace testing::ext; +using namespace DistributedDB; + +namespace { + const std::string DEVICE_A = "deviceA"; + const std::string DEVICE_B = "deviceB"; + VirtualTimeSyncCommunicator *g_virtualCommunicator = nullptr; + TimeSync *g_timeSyncA = nullptr; + TimeSync *g_timeSyncB = nullptr; + VirtualSingleVerSyncDBInterface *g_syncInterfaceA = nullptr; + VirtualSingleVerSyncDBInterface *g_syncInterfaceB = nullptr; + std::shared_ptr g_metadataA = nullptr; + std::shared_ptr g_metadataB = nullptr; + SingleVerSyncTaskContext *g_syncTaskContext = nullptr; + const int NETWORK_DELAY = 100 * 1000; // 100ms +} + +class DistributedDBTimeSyncTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void DistributedDBTimeSyncTest::SetUpTestCase(void) +{ + /** + * @tc.setup: NA + */ +} + +void DistributedDBTimeSyncTest::TearDownTestCase(void) +{ + /** + * @tc.teardown: NA + */ +} + +void DistributedDBTimeSyncTest::SetUp(void) +{ + DistributedDBUnitTest::DistributedDBToolsUnitTest::PrintTestCaseInfo(); + /** + * @tc.setup: create the instance for virtual communicator, virtual storage component and time syncer + */ + g_virtualCommunicator = new (std::nothrow) VirtualTimeSyncCommunicator(); + ASSERT_TRUE(g_virtualCommunicator != nullptr); + + g_syncInterfaceA = new (std::nothrow) VirtualSingleVerSyncDBInterface(); + ASSERT_TRUE(g_syncInterfaceA != nullptr); + + g_metadataA = std::make_shared(); + + g_syncInterfaceB = new (std::nothrow) VirtualSingleVerSyncDBInterface; + ASSERT_TRUE(g_syncInterfaceB != nullptr); + + g_metadataB = std::make_shared(); + + g_timeSyncA = new (std::nothrow) TimeSync(); + ASSERT_TRUE(g_timeSyncA != nullptr); + + g_timeSyncB = new (std::nothrow) TimeSync(); + ASSERT_TRUE(g_timeSyncB != nullptr); + + g_syncTaskContext = new (std::nothrow) SingleVerKvSyncTaskContext(); + ASSERT_TRUE(g_syncTaskContext != nullptr); +} + +void DistributedDBTimeSyncTest::TearDown(void) +{ + /** + * @tc.teardown: delete the ptr for testing + */ + if (g_syncTaskContext != nullptr) { + RefObject::DecObjRef(g_syncTaskContext); + g_syncTaskContext = nullptr; + } + if (g_syncInterfaceA != nullptr) { + delete g_syncInterfaceA; + g_syncInterfaceA = nullptr; + } + if (g_syncInterfaceB != nullptr) { + delete g_syncInterfaceB; + g_syncInterfaceB = nullptr; + } + + g_metadataA = nullptr; + g_metadataB = nullptr; + if (g_timeSyncA != nullptr) { + delete g_timeSyncA; + g_timeSyncA = nullptr; + } + if (g_timeSyncB != nullptr) { + delete g_timeSyncB; + g_timeSyncB = nullptr; + } + if (g_virtualCommunicator != nullptr) { + delete g_virtualCommunicator; + g_virtualCommunicator = nullptr; + } +} + +/** + * @tc.name: NormalSync001 + * @tc.desc: Verify time sync function is normal between two time sync instance with different timestamp. + * @tc.type: FUNC + * @tc.require: AR000C05EP AR000CQE0G + * @tc.author: wumin + */ +HWTEST_F(DistributedDBTimeSyncTest, NormalSync001, TestSize.Level0) +{ + /** + * @tc.steps: step1. Initialize the time sync A and B + * @tc.steps: step2. Write the timestamp into virtual storage component + * @tc.expected: step1. Initialize time sync A and B successfully + * @tc.expected: step2. Write the timestamp into virtual storage component successfully. + */ + g_metadataA->Initialize(g_syncInterfaceA); + TimeOffset offsetA = 100 * 1000 * 1000; // 100 seconds + // set timestamp for A virtual storage component + g_syncInterfaceA->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1, + TimeHelper::GetSysCurrentTime() + TimeHelper::BASE_OFFSET + offsetA, 0); + int errCode; + // initialize timeSyncA + errCode = g_timeSyncA->Initialize(g_virtualCommunicator, g_metadataA, g_syncInterfaceA, DEVICE_B); + EXPECT_TRUE(errCode == E_OK); + + g_metadataB->Initialize(g_syncInterfaceB); + TimeOffset offsetB = 200 * 1000 * 1000; // 200 seconds + // set timestamp for B virtual storage component + g_syncInterfaceB->PutData(DistributedDBUnitTest::KEY_1, DistributedDBUnitTest::VALUE_1, + TimeHelper::GetSysCurrentTime() + TimeHelper::BASE_OFFSET + offsetB, 0); + // initialize timeSyncB + errCode = g_timeSyncB->Initialize(g_virtualCommunicator, g_metadataB, g_syncInterfaceB, DEVICE_A); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step3. Register the OnMessageCallback to virtual communicator + */ + g_syncTaskContext->Initialize(DEVICE_B, g_syncInterfaceA, g_metadataA, g_virtualCommunicator); + g_virtualCommunicator->SetTimeSync(g_timeSyncA, g_timeSyncB, DEVICE_A, g_syncTaskContext); + + /** + * @tc.steps: step4. Fetch timeOffset value + * @tc.expected: step4. (offsetB - offsetA ) - timeOffset < 100ms. + */ + TimeOffset timeOffset = 0; + g_timeSyncA->GetTimeOffset(timeOffset, TIME_SYNC_WAIT_TIME); + offsetB = g_metadataB->GetLocalTimeOffset(); + offsetA = g_metadataA->GetLocalTimeOffset(); + EXPECT_TRUE(abs(offsetB - offsetA - timeOffset) < NETWORK_DELAY); +} + +/** + * @tc.name: NormalSync002 + * @tc.desc: Verify time sync function is normal between two time sync instance with the same timestamp. + * @tc.type: FUNC + * @tc.require: AR000C05EP AR000CQE0G + * @tc.author: wumin + */ +HWTEST_F(DistributedDBTimeSyncTest, NormalSync002, TestSize.Level0) +{ + /** + * @tc.steps: step1. Initialize the time sync A and B + * @tc.expected: step1. Initialize time sync A and B successfully + */ + g_metadataA->Initialize(g_syncInterfaceA); + int errCode; + // initialize timeSyncA + errCode = g_timeSyncA->Initialize(g_virtualCommunicator, g_metadataA, g_syncInterfaceA, DEVICE_B); + EXPECT_TRUE(errCode == E_OK); + + g_metadataB->Initialize(g_syncInterfaceB); + // initialize timeSyncB + errCode = g_timeSyncB->Initialize(g_virtualCommunicator, g_metadataB, g_syncInterfaceB, DEVICE_A); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step2. Register the OnMessageCallback to virtual communicator + */ + g_syncTaskContext->Initialize(DEVICE_B, g_syncInterfaceA, g_metadataA, g_virtualCommunicator); + g_virtualCommunicator->SetTimeSync(g_timeSyncA, g_timeSyncB, DEVICE_A, g_syncTaskContext); + + /** + * @tc.steps: step3. Fetch timeOffset value + * @tc.expected: step3. (offsetB - offsetA ) - timeOffset < 100ms. + */ + TimeOffset timeOffset; + g_timeSyncA->GetTimeOffset(timeOffset, TIME_SYNC_WAIT_TIME); + TimeOffset offsetB = g_metadataB->GetLocalTimeOffset(); + TimeOffset offsetA = g_metadataA->GetLocalTimeOffset(); + EXPECT_TRUE(abs(offsetB - offsetA - timeOffset) < NETWORK_DELAY); +} + +/** + * @tc.name: NormalSync003 + * @tc.desc: Verify time sync function is normal between two time sync instance with different localTimeOffset. + * @tc.type: FUNC + * @tc.require: AR000C05EP AR000CQE0G + * @tc.author: wumin + */ +HWTEST_F(DistributedDBTimeSyncTest, NormalSync003, TestSize.Level0) +{ + /** + * @tc.steps: step1. Initialize the time sync A and B + * @tc.steps: step2. Write the timeOffset into time sync A and B + * @tc.expected: step1. Initialize time sync A and B successfully + * @tc.expected: step2. Write the timeOffset into time sync A and B successfully. + */ + g_metadataA->Initialize(g_syncInterfaceA); + + // set timeOffset for timeSyncA + TimeOffset offsetA = 1; + g_metadataA->SaveLocalTimeOffset(offsetA); + + int errCode; + // initialize timeSyncA + errCode = g_timeSyncA->Initialize(g_virtualCommunicator, g_metadataA, g_syncInterfaceA, DEVICE_B); + EXPECT_TRUE(errCode == E_OK); + + // set timeOffset for timeSyncA + g_metadataB->Initialize(g_syncInterfaceB); + TimeOffset offsetB = 100 * 1000 * 1000; + g_metadataB->SaveLocalTimeOffset(offsetB); + + // initialize timeSyncB + errCode = g_timeSyncB->Initialize(g_virtualCommunicator, g_metadataB, g_syncInterfaceB, DEVICE_A); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step3. Register the OnMessageCallback to virtual communicator + */ + g_syncTaskContext->Initialize(DEVICE_B, g_syncInterfaceA, g_metadataA, g_virtualCommunicator); + g_virtualCommunicator->SetTimeSync(g_timeSyncA, g_timeSyncB, DEVICE_A, g_syncTaskContext); + + /** + * @tc.steps: step4. Fetch timeOffset value + * @tc.expected: step4. (offsetB - offsetA ) - timeOffset < 100ms. + */ + TimeOffset timeOffset = 0; + g_timeSyncA->GetTimeOffset(timeOffset, TIME_SYNC_WAIT_TIME); + + TimeOffset absTimeOffset = abs(timeOffset); + EXPECT_TRUE(abs(offsetB - offsetA - absTimeOffset) < NETWORK_DELAY); +} + +/** + * @tc.name: NetDisconnetSyncTest001 + * @tc.desc: Verify time sync function return failed when the virtual communicator disabled. + * @tc.type: FUNC + * @tc.require: AR000C05EP AR000CQE0G + * @tc.author: wumin + */ +HWTEST_F(DistributedDBTimeSyncTest, NetDisconnetSyncTest001, TestSize.Level0) +{ + /** + * @tc.steps: step1. Initialize the time sync A and B + * @tc.expected: step1. Initialize time sync A and B successfully + */ + g_metadataA->Initialize(g_syncInterfaceA); + int errCode; + // initialize timeSyncA + errCode = g_timeSyncA->Initialize(g_virtualCommunicator, g_metadataA, g_syncInterfaceA, DEVICE_B); + EXPECT_TRUE(errCode == E_OK); + + g_metadataB->Initialize(g_syncInterfaceB); + // initialize timeSyncB + errCode = g_timeSyncB->Initialize(g_virtualCommunicator, g_metadataB, g_syncInterfaceB, DEVICE_A); + EXPECT_TRUE(errCode == E_OK); + + g_syncTaskContext->Initialize(DEVICE_B, g_syncInterfaceA, g_metadataA, g_virtualCommunicator); + g_virtualCommunicator->SetTimeSync(g_timeSyncA, g_timeSyncB, DEVICE_A, g_syncTaskContext); + + /** + * @tc.steps: step2. Disable the virtual communicator + */ + g_virtualCommunicator->Disable(); + + /** + * @tc.steps: step3. Start time sync function + * @tc.expected: step3. time sync return -E_PERIPHERAL_INTERFACE_FAIL + */ + errCode = g_timeSyncA->SyncStart(); + EXPECT_TRUE(errCode == -E_PERIPHERAL_INTERFACE_FAIL); +} + +/** + * @tc.name: InvalidMessgeTest001 + * @tc.desc: Verify RequestReceive() return failed with invalid input. + * @tc.type: FUNC + * @tc.require: AR000C05EP AR000CQE0G + * @tc.author: wumin + */ +HWTEST_F(DistributedDBTimeSyncTest, InvalidMessgeTest001, TestSize.Level0) +{ + /** + * @tc.steps: step1. Initialize the time sync A and B + * @tc.expected: step1. Initialize time sync A and B successfully + */ + g_metadataA->Initialize(g_syncInterfaceA); + int errCode; + // initialize timeSyncA + errCode = g_timeSyncA->Initialize(g_virtualCommunicator, g_metadataA, g_syncInterfaceA, DEVICE_B); + EXPECT_TRUE(errCode == E_OK); + + g_metadataB->Initialize(g_syncInterfaceB); + // initialize timeSyncB + errCode = g_timeSyncB->Initialize(g_virtualCommunicator, g_metadataB, g_syncInterfaceB, DEVICE_A); + EXPECT_TRUE(errCode == E_OK); + + g_virtualCommunicator->SetTimeSync(g_timeSyncA, g_timeSyncB, DEVICE_A, g_syncTaskContext); + + Message *msg = new (std::nothrow) Message(); + ASSERT_TRUE(msg != nullptr); + + /** + * @tc.steps: step2. SendMessage with id = TIME_SYNC_MESSAGE, type = TYPE_REQUEST and no data set + * @tc.expected: step2. RequestRecv() return -E_INVALID_ARGS + */ + msg->SetMessageId(TIME_SYNC_MESSAGE); + msg->SetMessageType(TYPE_REQUEST); + SendConfig conf = {false, false, 0}; + errCode = g_virtualCommunicator->SendMessage(DEVICE_B, msg, conf); + EXPECT_TRUE(errCode == -E_INVALID_ARGS); + + TimeSyncPacket data; + data.SetSourceTimeBegin(0); + data.SetSourceTimeEnd(0); + data.SetTargetTimeBegin(0); + data.SetTargetTimeEnd(0); + + /** + * @tc.steps: step3. SendMessage with id = DATA_SYNC_MESSAGE, type = TYPE_REQUEST + * @tc.expected: step3. RequestRecv() return -E_INVALID_ARGS + */ + msg = new (std::nothrow) Message(); + ASSERT_TRUE(msg != nullptr); + msg->SetMessageId(DATA_SYNC_MESSAGE); + msg->SetMessageType(TYPE_REQUEST); + msg->SetCopiedObject<>(data); + errCode = g_virtualCommunicator->SendMessage(DEVICE_B, msg, conf); + EXPECT_TRUE(errCode == -E_INVALID_ARGS); + + /** + * @tc.steps: step4. SendMessage with id = TIME_SYNC_MESSAGE, type = TYPE_RESPONSE + * @tc.expected: step4. RequestRecv() return -E_INVALID_ARGS + */ + msg = new (std::nothrow) Message(); + ASSERT_TRUE(msg != nullptr); + msg->SetMessageId(TIME_SYNC_MESSAGE); + msg->SetMessageType(TYPE_RESPONSE); + msg->SetCopiedObject<>(data); + errCode = g_virtualCommunicator->SendMessage(DEVICE_B, msg, conf); + EXPECT_TRUE(errCode == -E_INVALID_ARGS); +} + +/** + * @tc.name: InvalidMessgeTest002 + * @tc.desc: Verify AckRec() return failed with invalid input. + * @tc.type: FUNC + * @tc.require: AR000C05EP AR000CQE0G + * @tc.author: wumin + */ +HWTEST_F(DistributedDBTimeSyncTest, InvalidMessgeTest002, TestSize.Level0) +{ + /** + * @tc.steps: step1. Initialize the time sync A and B + * @tc.expected: step1. Initialize time sync A and B successfully + */ + g_metadataA->Initialize(g_syncInterfaceA); + int errCode; + // initialize timeSyncA + errCode = g_timeSyncA->Initialize(g_virtualCommunicator, g_metadataA, g_syncInterfaceA, DEVICE_B); + EXPECT_TRUE(errCode == E_OK); + + g_metadataB->Initialize(g_syncInterfaceB); + // initialize timeSyncB + errCode = g_timeSyncB->Initialize(g_virtualCommunicator, g_metadataB, g_syncInterfaceB, DEVICE_A); + EXPECT_TRUE(errCode == E_OK); + + g_syncTaskContext->Initialize(DEVICE_B, g_syncInterfaceA, g_metadataA, g_virtualCommunicator); + g_virtualCommunicator->SetTimeSync(g_timeSyncA, g_timeSyncB, DEVICE_A, g_syncTaskContext); + + Message *msg = new (std::nothrow) Message(); + ASSERT_TRUE(msg != nullptr); + + /** + * @tc.steps: step2. SendMessage with id = TIME_SYNC_MESSAGE, type = TYPE_RESPONSE and no data set + * @tc.expected: step2. AckRecv() return -E_INVALID_ARGS + */ + msg->SetMessageId(TIME_SYNC_MESSAGE); + msg->SetMessageType(TYPE_RESPONSE); + SendConfig conf = {false, false, 0}; + errCode = g_virtualCommunicator->SendMessage(DEVICE_A, msg, conf); + EXPECT_TRUE(errCode == -E_INVALID_ARGS); + + TimeSyncPacket data; + data.SetSourceTimeBegin(0); + data.SetSourceTimeEnd(0); + data.SetTargetTimeBegin(0); + data.SetTargetTimeEnd(0); + + /** + * @tc.steps: step3. SendMessage with id = DATA_SYNC_MESSAGE, type = TYPE_RESPONSE and no data set + * @tc.expected: step3. AckRecv() return -E_INVALID_ARGS + */ + msg = new (std::nothrow) Message(); + ASSERT_TRUE(msg != nullptr); + msg->SetMessageId(DATA_SYNC_MESSAGE); + msg->SetMessageType(TYPE_RESPONSE); + msg->SetCopiedObject<>(data); + errCode = g_virtualCommunicator->SendMessage(DEVICE_A, msg, conf); + EXPECT_TRUE(errCode == -E_INVALID_ARGS); + + /** + * @tc.steps: step4. SendMessage with id = TIME_SYNC_MESSAGE, type = TYPE_REQUEST and no data set + * @tc.expected: step4. AckRecv() return -E_INVALID_ARGS + */ + msg = new (std::nothrow) Message(); + ASSERT_TRUE(msg != nullptr); + msg->SetMessageId(TIME_SYNC_MESSAGE); + msg->SetMessageType(TYPE_REQUEST); + msg->SetCopiedObject<>(data); + errCode = g_virtualCommunicator->SendMessage(DEVICE_A, msg, conf); + EXPECT_TRUE(errCode == -E_INVALID_ARGS); +} + +/** + * @tc.name: SyncTimeout001 + * @tc.desc: Verify the timeout scenario for time sync. + * @tc.type: FUNC + * @tc.require: AR000C05EP AR000CQE0G + * @tc.author: wumin + */ +HWTEST_F(DistributedDBTimeSyncTest, SyncTimeout001, TestSize.Level2) +{ + // initialize timeSyncA + g_metadataA->Initialize(g_syncInterfaceA); + int errCode; + errCode = g_timeSyncA->Initialize(g_virtualCommunicator, g_metadataA, g_syncInterfaceA, DEVICE_B); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step1. Initialize the syncTaskContext + * @tc.expected: step1. Initialize syncTaskContext successfully + */ + errCode = g_syncTaskContext->Initialize(DEVICE_B, g_syncInterfaceA, g_metadataA, g_virtualCommunicator); + EXPECT_TRUE(errCode == E_OK); + + /** + * @tc.steps: step2. Start the time syc task invoking StartSync() method + * @tc.expected: step2. Start the time sync task return E_TIMEOUT + */ + TimeOffset offset; + errCode = g_timeSyncA->GetTimeOffset(offset, TIME_SYNC_WAIT_TIME); + EXPECT_TRUE(errCode == -E_TIMEOUT); +} \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/generic_virtual_device.cpp b/mock/distributeddb/test/unittest/common/syncer/generic_virtual_device.cpp new file mode 100644 index 00000000..338152b1 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/generic_virtual_device.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "generic_virtual_device.h" + +#include "multi_ver_sync_task_context.h" +#include "single_ver_kv_sync_task_context.h" +#include "single_ver_relational_sync_task_context.h" + +namespace DistributedDB { +GenericVirtualDevice::GenericVirtualDevice(std::string deviceId) + : communicateHandle_(nullptr), + communicatorAggregator_(nullptr), + storage_(nullptr), + metadata_(nullptr), + deviceId_(std::move(deviceId)), + remoteDeviceId_("real_device"), + context_(nullptr), + onRemoteDataChanged_(nullptr), + subManager_(nullptr) +{ +} + +GenericVirtualDevice::~GenericVirtualDevice() +{ + std::mutex cvMutex; + std::condition_variable cv; + bool finished = false; + Offline(); + + if (communicateHandle_ != nullptr) { + communicateHandle_->RegOnMessageCallback(nullptr, nullptr); + communicatorAggregator_->ReleaseCommunicator(communicateHandle_); + communicateHandle_ = nullptr; + } + communicatorAggregator_ = nullptr; + + if (context_ != nullptr) { + ISyncInterface *storage = storage_; + context_->OnLastRef([storage, &cv, &cvMutex, &finished]() { + delete storage; + { + std::lock_guard lock(cvMutex); + finished = true; + } + cv.notify_one(); + }); + RefObject::KillAndDecObjRef(context_); + std::unique_lock lock(cvMutex); + cv.wait(lock, [&finished] { return finished; }); + } else { + delete storage_; + } + context_ = nullptr; + metadata_ = nullptr; + storage_ = nullptr; +} + +int GenericVirtualDevice::Initialize(VirtualCommunicatorAggregator *comAggregator, ISyncInterface *syncInterface) +{ + if ((comAggregator == nullptr) || (syncInterface == nullptr)) { + return -E_INVALID_ARGS; + } + + communicatorAggregator_ = comAggregator; + int errCode = E_OK; + communicateHandle_ = communicatorAggregator_->AllocCommunicator(deviceId_, errCode); + if (communicateHandle_ == nullptr) { + return errCode; + } + + storage_ = syncInterface; + metadata_ = std::make_shared(); + if (metadata_->Initialize(storage_) != E_OK) { + LOGE("metadata_ init failed"); + return -E_NOT_SUPPORT; + } + if (storage_->GetInterfaceType() == IKvDBSyncInterface::SYNC_SVD) { + context_ = new (std::nothrow) SingleVerKvSyncTaskContext; + subManager_ = std::make_shared(); + static_cast(context_)->SetSubscribeManager(subManager_); + } else if (storage_->GetInterfaceType() == IKvDBSyncInterface::SYNC_RELATION) { + context_ = new (std::nothrow) SingleVerRelationalSyncTaskContext; + } else { + context_ = new (std::nothrow) MultiVerSyncTaskContext; + } + if (context_ == nullptr) { + return -E_OUT_OF_MEMORY; + } + communicateHandle_->RegOnMessageCallback( + std::bind(&GenericVirtualDevice::MessageCallback, this, std::placeholders::_1, std::placeholders::_2), []() {}); + context_->Initialize(remoteDeviceId_, storage_, metadata_, communicateHandle_); + context_->SetRetryStatus(SyncTaskContext::NO_NEED_RETRY); + context_->RegOnSyncTask(std::bind(&GenericVirtualDevice::StartResponseTask, this)); + return E_OK; +} + +void GenericVirtualDevice::SetDeviceId(const std::string &deviceId) +{ + deviceId_ = deviceId; +} + +std::string GenericVirtualDevice::GetDeviceId() const +{ + return deviceId_; +} + +int GenericVirtualDevice::MessageCallback(const std::string &deviceId, Message *inMsg) +{ + if (inMsg->GetMessageId() == LOCAL_DATA_CHANGED) { + if (onRemoteDataChanged_) { + onRemoteDataChanged_(deviceId); + delete inMsg; + inMsg = nullptr; + return E_OK; + } + delete inMsg; + inMsg = nullptr; + return -E_INVALID_ARGS; + } + + LOGD("[GenericVirtualDevice] onMessage, src %s", deviceId.c_str()); + RefObject::IncObjRef(context_); + RefObject::IncObjRef(communicateHandle_); + SyncTaskContext *context = context_; + ICommunicator *communicateHandle = communicateHandle_; + std::thread thread([context, communicateHandle, inMsg]() { + int errCode = context->ReceiveMessageCallback(inMsg); + if (errCode != -E_NOT_NEED_DELETE_MSG) { + delete inMsg; + } + RefObject::DecObjRef(context); + RefObject::DecObjRef(communicateHandle); + }); + thread.detach(); + return E_OK; +} + +void GenericVirtualDevice::OnRemoteDataChanged(const std::function &callback) +{ + onRemoteDataChanged_ = callback; +} + +void GenericVirtualDevice::Online() +{ + static_cast(communicateHandle_)->Enable(); + communicatorAggregator_->OnlineDevice(deviceId_); +} + +void GenericVirtualDevice::Offline() +{ + static_cast(communicateHandle_)->Disable(); + communicatorAggregator_->OfflineDevice(deviceId_); +} + +int GenericVirtualDevice::StartResponseTask() +{ + LOGD("[KvVirtualDevice] StartResponseTask"); + RefObject::AutoLock lockGuard(context_); + int status = context_->GetTaskExecStatus(); + if ((status == SyncTaskContext::RUNNING) || context_->IsKilled()) { + LOGD("[KvVirtualDevice] StartResponseTask status:%d", status); + return -E_NOT_SUPPORT; + } + if (context_->IsTargetQueueEmpty()) { + LOGD("[KvVirtualDevice] StartResponseTask IsTargetQueueEmpty is empty"); + return E_OK; + } + context_->SetTaskExecStatus(ISyncTaskContext::RUNNING); + context_->MoveToNextTarget(); + LOGI("[KvVirtualDevice] machine StartSync"); + context_->UnlockObj(); + int errCode = context_->StartStateMachine(); + context_->LockObj(); + if (errCode != E_OK) { + LOGE("[KvVirtualDevice] machine StartSync failed"); + context_->SetOperationStatus(SyncOperation::OP_FAILED); + } + return errCode; +} + +TimeOffset GenericVirtualDevice::GetLocalTimeOffset() const +{ + return metadata_->GetLocalTimeOffset(); +} + +int GenericVirtualDevice::Sync(SyncMode mode, bool wait) +{ + auto operation = new (std::nothrow) SyncOperation(1, {remoteDeviceId_}, mode, nullptr, wait); + if (operation == nullptr) { + return -E_OUT_OF_MEMORY; + } + operation->Initialize(); + operation->SetOnSyncFinished([operation](int id) { + operation->NotifyIfNeed(); + }); + context_->AddSyncOperation(operation); + operation->WaitIfNeed(); + RefObject::KillAndDecObjRef(operation); + return E_OK; +} + +int GenericVirtualDevice::Sync(SyncMode mode, const Query &query, bool wait) +{ + return Sync(mode, query, nullptr, wait); +} + +int GenericVirtualDevice::Sync(SyncMode mode, const Query &query, + const SyncOperation::UserCallback &callBack, bool wait) +{ + auto operation = new (std::nothrow) SyncOperation(1, {remoteDeviceId_}, mode, callBack, wait); + if (operation == nullptr) { + return -E_OUT_OF_MEMORY; + } + operation->Initialize(); + operation->SetOnSyncFinished([operation](int id) { + operation->NotifyIfNeed(); + }); + QuerySyncObject querySyncObject(query); + int errCode = querySyncObject.Init(); + if (errCode != E_OK) { + return errCode; + } + operation->SetQuery(querySyncObject); + context_->AddSyncOperation(operation); + operation->WaitIfNeed(); + RefObject::KillAndDecObjRef(operation); + return errCode; +} +} // DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/generic_virtual_device.h b/mock/distributeddb/test/unittest/common/syncer/generic_virtual_device.h new file mode 100644 index 00000000..25525870 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/generic_virtual_device.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 GENERIC_VIRTUAL_DEVICE_H +#define GENERIC_VIRTUAL_DEVICE_H + +#include + +#include "ikvdb_sync_interface.h" +#include "meta_data.h" +#include "subscribe_manager.h" +#include "sync_task_context.h" +#include "store_types.h" +#include "virtual_communicator_aggregator.h" + +namespace DistributedDB { +class GenericVirtualDevice { +public: + explicit GenericVirtualDevice(std::string deviceId); + virtual ~GenericVirtualDevice(); + + int Initialize(VirtualCommunicatorAggregator *comAggregator, ISyncInterface *syncInterface); + void SetDeviceId(const std::string &deviceId); + std::string GetDeviceId() const; + int MessageCallback(const std::string &deviceId, Message *inMsg); + void OnRemoteDataChanged(const std::function &callback); + void Online(); + void Offline(); + int StartResponseTask(); + TimeOffset GetLocalTimeOffset() const; + virtual int Sync(SyncMode mode, bool wait); + virtual int Sync(SyncMode mode, const Query &query, bool wait); + virtual int Sync(SyncMode mode, const Query &query, const SyncOperation::UserCallback &callBack, bool wait); + +protected: + ICommunicator *communicateHandle_; + VirtualCommunicatorAggregator *communicatorAggregator_; + ISyncInterface *storage_; + std::shared_ptr metadata_; + std::string deviceId_; + std::string remoteDeviceId_; + SyncTaskContext *context_; + std::function onRemoteDataChanged_; + + std::shared_ptr subManager_; +}; +} +#endif // GENERIC_VIRTUAL_DEVICE_H diff --git a/mock/distributeddb/test/unittest/common/syncer/kv_virtual_device.cpp b/mock/distributeddb/test/unittest/common/syncer/kv_virtual_device.cpp new file mode 100644 index 00000000..1cfde89e --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/kv_virtual_device.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "kv_virtual_device.h" + +#include "log_print.h" +#include "virtual_multi_ver_sync_db_interface.h" + +namespace DistributedDB { +KvVirtualDevice::KvVirtualDevice(const std::string &deviceId) : GenericVirtualDevice(deviceId) +{ +} + +KvVirtualDevice::~KvVirtualDevice() +{ +} + +int KvVirtualDevice::GetData(const Key &key, VirtualDataItem &item) +{ + VirtualSingleVerSyncDBInterface *syncAble = static_cast(storage_); + return syncAble->GetSyncData(key, item); +} + +int KvVirtualDevice::GetData(const Key &key, Value &value) +{ + VirtualMultiVerSyncDBInterface *syncInterface = static_cast(storage_); + return syncInterface->GetData(key, value); +} + +int KvVirtualDevice::PutData(const Key &key, const Value &value, const Timestamp &time, int flag) +{ + VirtualSingleVerSyncDBInterface *syncAble = static_cast(storage_); + LOGI("dev %s put data time %" PRIu64, deviceId_.c_str(), time); + return syncAble->PutData(key, value, time, flag); +} + +int KvVirtualDevice::PutData(const Key &key, const Value &value) +{ + VirtualMultiVerSyncDBInterface *syncInterface = static_cast(storage_); + return syncInterface->PutData(key, value); +} + +int KvVirtualDevice::DeleteData(const Key &key) +{ + VirtualMultiVerSyncDBInterface *syncInterface = static_cast(storage_); + return syncInterface->DeleteData(key); +} + +int KvVirtualDevice::StartTransaction() +{ + VirtualMultiVerSyncDBInterface *syncInterface = static_cast(storage_); + return syncInterface->StartTransaction(); +} + +int KvVirtualDevice::Commit() +{ + VirtualMultiVerSyncDBInterface *syncInterface = static_cast(storage_); + return syncInterface->Commit(); +} + + +void KvVirtualDevice::SetSaveDataDelayTime(uint64_t milliDelayTime) +{ + VirtualSingleVerSyncDBInterface *syncInterface = static_cast(storage_); + syncInterface->SetSaveDataDelayTime(milliDelayTime); +} + +int KvVirtualDevice::Subscribe(QuerySyncObject query, bool wait, int id) +{ + auto operation = new (std::nothrow) SyncOperation(id, {remoteDeviceId_}, SUBSCRIBE_QUERY, nullptr, wait); + if (operation == nullptr) { + return -E_OUT_OF_MEMORY; + } + operation->Initialize(); + operation->SetOnSyncFinished([operation](int id) { + operation->NotifyIfNeed(); + }); + operation->SetQuery(query); + context_->AddSyncOperation(operation); + operation->WaitIfNeed(); + RefObject::KillAndDecObjRef(operation); + return E_OK; +} + +int KvVirtualDevice::UnSubscribe(QuerySyncObject query, bool wait, int id) +{ + auto operation = new (std::nothrow) SyncOperation(id, {remoteDeviceId_}, UNSUBSCRIBE_QUERY, nullptr, wait); + if (operation == nullptr) { + return -E_OUT_OF_MEMORY; + } + operation->Initialize(); + operation->SetOnSyncFinished([operation](int id) { + operation->NotifyIfNeed(); + }); + operation->SetQuery(query); + context_->AddSyncOperation(operation); + operation->WaitIfNeed(); + RefObject::KillAndDecObjRef(operation); + return E_OK; +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/kv_virtual_device.h b/mock/distributeddb/test/unittest/common/syncer/kv_virtual_device.h new file mode 100644 index 00000000..220aabc4 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/kv_virtual_device.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KV_VIRTUAL_DEVICE_H +#define KV_VIRTUAL_DEVICE_H + +#include "generic_virtual_device.h" + +#include "virtual_single_ver_sync_db_Interface.h" +namespace DistributedDB { +class KvVirtualDevice final : public GenericVirtualDevice { +public: + explicit KvVirtualDevice(const std::string &deviceId); + ~KvVirtualDevice() override; + + int GetData(const Key &key, VirtualDataItem &item); + int GetData(const Key &key, Value &value); + int PutData(const Key &key, const Value &value, const Timestamp &time, int flag); + int PutData(const Key &key, const Value &value); + int DeleteData(const Key &key); + int StartTransaction(); + int Commit(); + void SetSaveDataDelayTime(uint64_t milliDelayTime); + + int Subscribe(QuerySyncObject query, bool wait, int id); + int UnSubscribe(QuerySyncObject query, bool wait, int id); +}; +} // namespace DistributedDB + +#endif // KV_VIRTUAL_DEVICE_H diff --git a/mock/distributeddb/test/unittest/common/syncer/mock_auto_launch.h b/mock/distributeddb/test/unittest/common/syncer/mock_auto_launch.h new file mode 100644 index 00000000..af89a85a --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/mock_auto_launch.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 MOCK_AUTO_LAUNCH_H +#define MOCK_AUTO_LAUNCH_H + +#include +#include "auto_launch.h" + +namespace DistributedDB { +class MockAutoLaunch : public AutoLaunch { +public: + void SetAutoLaunchItem(const std::string &identify, const std::string &userId, AutoLaunchItem &item) + { + std::lock_guard autoLock(extLock_); + extItemMap_[identify][userId] = item; + } + + void CallExtConnectionLifeCycleCallbackTask(const std::string &identifier, const std::string &userId) + { + AutoLaunch::ExtConnectionLifeCycleCallbackTask(identifier, userId); + } + + MOCK_METHOD1(TryCloseConnection, void(AutoLaunchItem &)); +}; +} // namespace DistributedDB +#endif // #define MOCK_AUTO_LAUNCH_H \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/mock_communicator.h b/mock/distributeddb/test/unittest/common/syncer/mock_communicator.h new file mode 100644 index 00000000..aab37dff --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/mock_communicator.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 MOCK_COMMUNICATOR_H +#define MOCK_COMMUNICATOR_H + +#include +#include "icommunicator.h" + +namespace DistributedDB { +class MockCommunicator : public ICommunicator { +public: + MOCK_CONST_METHOD1(GetLocalIdentity, int(std::string &)); + MOCK_CONST_METHOD2(GetRemoteCommunicatorVersion, int(const std::string &, uint16_t &)); + MOCK_METHOD3(SendMessage, int(const std::string &, const Message *, const SendConfig &)); + MOCK_METHOD4(SendMessage, int(const std::string &, const Message *, const SendConfig &, const OnSendEnd &)); + MOCK_CONST_METHOD0(GetCommunicatorMtuSize, uint32_t(void)); + MOCK_CONST_METHOD1(GetCommunicatorMtuSize, uint32_t(const std::string &)); + MOCK_CONST_METHOD0(GetTimeout, uint32_t(void)); + MOCK_CONST_METHOD1(GetTimeout, uint32_t(const std::string &)); + MOCK_METHOD2(RegOnConnectCallback, int(const OnConnectCallback &, const Finalizer &)); + MOCK_METHOD2(RegOnSendableCallback, int(const std::function &, const Finalizer &)); + MOCK_METHOD2(RegOnMessageCallback, int(const OnMessageCallback &, const Finalizer &)); + MOCK_METHOD0(Activate, void(void)); + MOCK_CONST_METHOD1(IsDeviceOnline, bool(const std::string &)); +}; +} // namespace DistributedDB +#endif // #define MOCK_COMMUNICATOR_H + diff --git a/mock/distributeddb/test/unittest/common/syncer/mock_meta_data.h b/mock/distributeddb/test/unittest/common/syncer/mock_meta_data.h new file mode 100644 index 00000000..c1e83d6e --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/mock_meta_data.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 MOCK_META_DATA_H +#define MOCK_META_DATA_H + +#include +#include "meta_data.h" + +namespace DistributedDB { +class MockMetadata : public Metadata { +public: + MOCK_METHOD3(SetLastQueryTime, int(const std::string &, const std::string &, const Timestamp &)); + + MOCK_METHOD3(GetLastQueryTime, int(const std::string &, const std::string &, Timestamp &)); +}; +} // namespace DistributedDB +#endif // #define MOCK_META_DATA_H \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/mock_single_ver_data_sync.h b/mock/distributeddb/test/unittest/common/syncer/mock_single_ver_data_sync.h new file mode 100644 index 00000000..550ece3d --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/mock_single_ver_data_sync.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 MOCK_SINGLE_VER_DATA_SYNC_H +#define MOCK_SINGLE_VER_DATA_SYNC_H + +#include +#include "single_ver_data_sync.h" + +namespace DistributedDB { +class MockSingleVerDataSync : public SingleVerDataSync { +public: + int CallRequestStart(SingleVerSyncTaskContext *context, int mode) + { + return SingleVerDataSync::RequestStart(context, mode); + } + + int CallPullRequestStart(SingleVerSyncTaskContext *context) + { + return SingleVerDataSync::PullRequestStart(context); + } + + void CallUpdateSendInfo(SyncTimeRange dataTimeRange, SingleVerSyncTaskContext *context) + { + SingleVerDataSync::UpdateSendInfo(dataTimeRange, context); + } + + int CallRemoveDeviceDataIfNeed(SingleVerSyncTaskContext *context) + { + return SingleVerDataSync::RemoveDeviceDataIfNeed(context); + } + + MOCK_METHOD1(RemoveDeviceDataIfNeed, int(SingleVerSyncTaskContext *)); +}; +} // namespace DistributedDB +#endif // #define MOCK_SINGLE_VER_DATA_SYNC_H \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/mock_single_ver_state_machine.h b/mock/distributeddb/test/unittest/common/syncer/mock_single_ver_state_machine.h new file mode 100644 index 00000000..878dd420 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/mock_single_ver_state_machine.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 MOCK_SINGLE_VER_STATE_MACHINE_H +#define MOCK_SINGLE_VER_STATE_MACHINE_H + +#include +#include "single_ver_sync_state_machine.h" + +namespace DistributedDB { +class MockSingleVerStateMachine : public SingleVerSyncStateMachine { +public: + void CallStepToTimeout(TimerId timerId) + { + SingleVerSyncStateMachine::StepToTimeout(timerId); + } + + int CallExecNextTask() + { + return SyncStateMachine::ExecNextTask(); + } + + int CallTimeMarkSyncRecv(const Message *inMsg) + { + return SingleVerSyncStateMachine::TimeMarkSyncRecv(inMsg); + } + + void CallDataAckRecvErrCodeHandle(int errCode, bool handleError) + { + SingleVerSyncStateMachine::DataAckRecvErrCodeHandle(errCode, handleError); + } + + bool CallStartSaveDataNotify(uint32_t sessionId, uint32_t sequenceId, uint32_t inMsgId) + { + return SingleVerSyncStateMachine::StartSaveDataNotify(sessionId, sequenceId, inMsgId); + } + + void CallStopSaveDataNotify() + { + SingleVerSyncStateMachine::StopSaveDataNotify(); + } + + MOCK_METHOD1(SwitchStateAndStep, void(uint8_t)); + + MOCK_METHOD0(PrepareNextSyncTask, int(void)); + + MOCK_METHOD3(DoSaveDataNotify, void(uint32_t, uint32_t, uint32_t)); +}; +} // namespace DistributedDB +#endif // #define MOCK_SINGLE_VER_STATE_MACHINE_H \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/mock_sync_task_context.h b/mock/distributeddb/test/unittest/common/syncer/mock_sync_task_context.h new file mode 100644 index 00000000..95a999c6 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/mock_sync_task_context.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 MOCK_SYNC_TASK_CONTEXT_H +#define MOCK_SYNC_TASK_CONTEXT_H + +#include +#include "single_ver_kv_sync_task_context.h" + +namespace DistributedDB { +class MockSyncTaskContext : public SingleVerKvSyncTaskContext { +public: + bool CallIsCurrentSyncTaskCanBeSkipped() + { + return SingleVerKvSyncTaskContext::IsCurrentSyncTaskCanBeSkipped(); + } + + void CallSetSyncMode(int mode) + { + SingleVerKvSyncTaskContext::SetMode(mode); + } + + MOCK_CONST_METHOD0(GetTimerId, TimerId(void)); + + MOCK_CONST_METHOD0(IsTargetQueueEmpty, bool(void)); + + MOCK_METHOD0(MoveToNextTarget, void(void)); + + MOCK_CONST_METHOD0(IsCurrentSyncTaskCanBeSkipped, bool(void)); + + MOCK_METHOD1(SetOperationStatus, void(int)); + + MOCK_METHOD1(SetTaskExecStatus, void(int)); + + MOCK_METHOD0(Clear, void(void)); + + MOCK_CONST_METHOD0(GetRequestSessionId, uint32_t(void)); + + MOCK_CONST_METHOD1(GetSyncStrategy, SyncStrategy(QuerySyncObject &)); +}; +} // namespace DistributedDB +#endif // #define MOCK_SINGLE_VER_STATE_MACHINE_H \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/relational_virtual_device.cpp b/mock/distributeddb/test/unittest/common/syncer/relational_virtual_device.cpp new file mode 100644 index 00000000..816795b6 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/relational_virtual_device.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "relational_virtual_device.h" +#include "virtual_relational_ver_sync_db_interface.h" +namespace DistributedDB { +RelationalVirtualDevice::RelationalVirtualDevice(const std::string &deviceId) : GenericVirtualDevice(deviceId) +{ +} + +RelationalVirtualDevice::~RelationalVirtualDevice() +{ +} + +int RelationalVirtualDevice::PutData(const std::string &tableName, const std::vector &dataList) +{ + return static_cast(storage_)->PutLocalData(dataList, tableName); +} + +int RelationalVirtualDevice::GetAllSyncData(const std::string &tableName, std::vector &data) +{ + return static_cast(storage_)->GetAllSyncData(tableName, data); +} + +int RelationalVirtualDevice::GetSyncData(const std::string &tableName, + const std::string &hashKey, VirtualRowData &data) +{ + return static_cast(storage_)->GetVirtualSyncData(tableName, hashKey, data); +} + +void RelationalVirtualDevice::SetLocalFieldInfo(const std::vector &localFieldInfo) +{ + static_cast(storage_)->SetLocalFieldInfo(localFieldInfo); +} + +int RelationalVirtualDevice::Sync(SyncMode mode, bool wait) +{ + return -E_NOT_SUPPORT; +} + +void RelationalVirtualDevice::EraseSyncData(const std::string &tableName) +{ + static_cast(storage_)->EraseSyncData(tableName); +} + +void RelationalVirtualDevice::SetTableInfo(const TableInfo &tableInfo) +{ + static_cast(storage_)->SetTableInfo(tableInfo); +} +} // DistributedDB +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/relational_virtual_device.h b/mock/distributeddb/test/unittest/common/syncer/relational_virtual_device.h new file mode 100644 index 00000000..9aed0bbf --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/relational_virtual_device.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 RELATIONAL_VIRTUAL_DEVICE_H +#define RELATIONAL_VIRTUAL_DEVICE_H +#ifdef RELATIONAL_STORE + +#include "data_transformer.h" +#include "generic_virtual_device.h" +#include "relational_schema_object.h" +#include "virtual_relational_ver_sync_db_interface.h" + +namespace DistributedDB { +class RelationalVirtualDevice final : public GenericVirtualDevice { +public: + explicit RelationalVirtualDevice(const std::string &deviceId); + ~RelationalVirtualDevice() override; + + int PutData(const std::string &tableName, const std::vector &dataList); + int GetAllSyncData(const std::string &tableName, std::vector &data); + int GetSyncData(const std::string &tableName, const std::string &hashKey, VirtualRowData &data); + void SetLocalFieldInfo(const std::vector &localFieldInfo); + void SetTableInfo(const TableInfo &tableInfo); + int Sync(SyncMode mode, bool wait) override; + void EraseSyncData(const std::string &tableName); +}; +} +#endif +#endif // RELATIONAL_VIRTUAL_DEVICE_H diff --git a/mock/distributeddb/test/unittest/common/syncer/virtual_communicator.cpp b/mock/distributeddb/test/unittest/common/syncer/virtual_communicator.cpp new file mode 100644 index 00000000..caed2ea2 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/virtual_communicator.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "virtual_communicator.h" + +#include "log_print.h" +#include "sync_engine.h" +#include "virtual_communicator_aggregator.h" + +namespace DistributedDB { +int VirtualCommunicator::RegOnMessageCallback(const OnMessageCallback &onMessage, const Finalizer &inOper) +{ + std::lock_guard lock(onMessageLock_); + onMessage_ = onMessage; + return E_OK; +} + +int VirtualCommunicator::RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) +{ + std::lock_guard lock(onConnectLock_); + onConnect_ = onConnect; + return E_OK; +} + +int VirtualCommunicator::RegOnSendableCallback(const std::function &onSendable, const Finalizer &inOper) +{ + return E_OK; +} + +void VirtualCommunicator::Activate() +{ +} + +int VirtualCommunicator::SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config) +{ + return SendMessage(dstTarget, inMsg, config, nullptr); +} + +int VirtualCommunicator::SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config, + const OnSendEnd &onEnd) +{ + AutoLock lock(this); + if (IsKilled()) { + return -E_OBJ_IS_KILLED; + } + if (!isEnable_) { + LOGD("[VirtualCommunicator] the VirtualCommunicator disabled!"); + return -E_PERIPHERAL_INTERFACE_FAIL; + } + if (dstTarget == deviceId_) { + delete inMsg; + inMsg = nullptr; + return E_OK; + } + communicatorAggregator_->DispatchMessage(deviceId_, dstTarget, inMsg, onEnd); + return E_OK; +} + +int VirtualCommunicator::GetRemoteCommunicatorVersion(const std::string &deviceId, uint16_t &version) const +{ + version = UINT16_MAX; + return E_OK; +} + +void VirtualCommunicator::CallbackOnMessage(const std::string &srcTarget, Message *inMsg) const +{ + std::lock_guard lock(onMessageLock_); + if (isEnable_ && onMessage_ && (srcTarget != deviceId_)) { + onMessage_(srcTarget, inMsg); + } else { + delete inMsg; + inMsg = nullptr; + } +} + +void VirtualCommunicator::CallbackOnConnect(const std::string &target, bool isConnect) const +{ + { + std::lock_guard lock(devicesMapLock_); + if (target != deviceId_) { + onlineDevicesMap_[target] = isConnect; + } + } + std::lock_guard lock(onConnectLock_); + if (isEnable_ && onConnect_) { + onConnect_(target, isConnect); + } +} + +uint32_t VirtualCommunicator::GetCommunicatorMtuSize() const +{ + return 5 * 1024 * 1024; // 5 * 1024 * 1024B +} + +uint32_t VirtualCommunicator::GetCommunicatorMtuSize(const std::string &target) const +{ + return GetCommunicatorMtuSize(); +} + +uint32_t VirtualCommunicator::GetTimeout() const +{ + return 5 * 1000; // 5 * 1000ms +} + +uint32_t VirtualCommunicator::GetTimeout(const std::string &target) const +{ + return GetTimeout(); +} + +int VirtualCommunicator::GetLocalIdentity(std::string &outTarget) const +{ + outTarget = deviceId_; + return E_OK; +} + +int VirtualCommunicator::GeneralVirtualSyncId() +{ + std::lock_guard lock(syncIdLock_); + currentSyncId_++; + return currentSyncId_; +} + +void VirtualCommunicator::Disable() +{ + isEnable_ = false; +} + +void VirtualCommunicator::Enable() +{ + isEnable_ = true; +} + +void VirtualCommunicator::SetDeviceId(const std::string &deviceId) +{ + deviceId_ = deviceId; +} + +std::string VirtualCommunicator::GetDeviceId() const +{ + return deviceId_; +} + +bool VirtualCommunicator::IsEnabled() const +{ + return isEnable_; +} + +bool VirtualCommunicator::IsDeviceOnline(const std::string &device) const +{ + bool res = true; + { + std::lock_guard lock(devicesMapLock_); + if (onlineDevicesMap_.find(device) != onlineDevicesMap_.end()) { + res = onlineDevicesMap_[device]; + } + } + return res; +} + +VirtualCommunicator::~VirtualCommunicator() +{ +} + +VirtualCommunicator::VirtualCommunicator(const std::string &deviceId, + VirtualCommunicatorAggregator *communicatorAggregator) + : deviceId_(deviceId), communicatorAggregator_(communicatorAggregator) +{ +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/virtual_communicator.h b/mock/distributeddb/test/unittest/common/syncer/virtual_communicator.h new file mode 100644 index 00000000..bd68e82f --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/virtual_communicator.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 VIRTUAL_COMMUNICATOR_H +#define VIRTUAL_COMMUNICATOR_H + +#include +#include +#include +#include +#include +#include +#include + +#include "icommunicator.h" +#include "ref_object.h" +#include "serial_buffer.h" + +namespace DistributedDB { +class VirtualCommunicatorAggregator; + +class VirtualCommunicator : public ICommunicator { +public: + VirtualCommunicator(const std::string &deviceId, VirtualCommunicatorAggregator *communicatorAggregator); + ~VirtualCommunicator() override; + + DISABLE_COPY_ASSIGN_MOVE(VirtualCommunicator); + + int RegOnMessageCallback(const OnMessageCallback &onMessage, const Finalizer &inOper) override; + int RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) override; + int RegOnSendableCallback(const std::function &onSendable, const Finalizer &inOper) override; + + void Activate() override; + + uint32_t GetCommunicatorMtuSize() const override; + uint32_t GetCommunicatorMtuSize(const std::string &target) const override; + + uint32_t GetTimeout() const override; + uint32_t GetTimeout(const std::string &target) const override; + int GetLocalIdentity(std::string &outTarget) const override; + + int SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config) override; + int SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config, + const OnSendEnd &onEnd) override; + + int GetRemoteCommunicatorVersion(const std::string &deviceId, uint16_t &version) const override; + + void CallbackOnMessage(const std::string &srcTarget, Message *inMsg) const; + + void CallbackOnConnect(const std::string &target, bool isConnect) const; + + int GeneralVirtualSyncId(); + + void Disable(); + + void Enable(); + + void SetDeviceId(const std::string &deviceId); + + std::string GetDeviceId() const; + + bool IsEnabled() const; + + bool IsDeviceOnline(const std::string &device) const override; + +private: + int TimeSync(); + int DataSync(); + int WaterMarkSync(); + + mutable std::mutex onMessageLock_; + OnMessageCallback onMessage_; + + mutable std::mutex onConnectLock_; + OnConnectCallback onConnect_; + mutable std::mutex devicesMapLock_; + mutable std::map onlineDevicesMap_; + + std::string remoteDeviceId_ = "real_device"; + std::mutex syncIdLock_; + int currentSyncId_ = 1000; + bool isEnable_ = true; + std::string deviceId_; + + std::mutex onAggregatorLock_; + VirtualCommunicatorAggregator *communicatorAggregator_; +}; +} // namespace DistributedDB + +#endif // VIRTUAL_COMMUNICATOR_H diff --git a/mock/distributeddb/test/unittest/common/syncer/virtual_communicator_aggregator.cpp b/mock/distributeddb/test/unittest/common/syncer/virtual_communicator_aggregator.cpp new file mode 100644 index 00000000..561a3f46 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/virtual_communicator_aggregator.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "virtual_communicator_aggregator.h" + +#include +#include +#include + +#include "db_errno.h" +#include "log_print.h" +#include "runtime_context.h" + +namespace DistributedDB { +int VirtualCommunicatorAggregator::Initialize(IAdapter *inAdapter) +{ + return E_OK; +} + +void VirtualCommunicatorAggregator::Finalize() +{ +} + +// If not success, return nullptr and set outErrorNo +ICommunicator *VirtualCommunicatorAggregator::AllocCommunicator(uint64_t commLabel, int &outErrorNo) +{ + if (isEnable_) { + return AllocCommunicator(remoteDeviceId_, outErrorNo); + } + return nullptr; +} + +ICommunicator *VirtualCommunicatorAggregator::AllocCommunicator(const LabelType &commLabel, int &outErrorNo) +{ + if (isEnable_) { + return AllocCommunicator(remoteDeviceId_, outErrorNo); + } + return nullptr; +} + +void VirtualCommunicatorAggregator::ReleaseCommunicator(ICommunicator *inCommunicator) +{ + // Called in main thread only + VirtualCommunicator *communicator = static_cast(inCommunicator); + OfflineDevice(communicator->GetDeviceId()); + { + std::lock_guard lock(communicatorsLock_); + communicators_.erase(communicator->GetDeviceId()); + } + RefObject::KillAndDecObjRef(communicator); + communicator = nullptr; +} + +int VirtualCommunicatorAggregator::RegCommunicatorLackCallback(const CommunicatorLackCallback &onCommLack, + const Finalizer &inOper) +{ + onCommLack_ = onCommLack; + return E_OK; +} + +int VirtualCommunicatorAggregator::RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) +{ + onConnect_ = onConnect; + RunOnConnectCallback("deviceId", true); + return E_OK; +} + +void VirtualCommunicatorAggregator::RunCommunicatorLackCallback(const LabelType &commLabel) +{ + if (onCommLack_) { + std::string userId; + onCommLack_(commLabel, userId_); + } +} + +void VirtualCommunicatorAggregator::RunOnConnectCallback(const std::string &target, bool isConnect) +{ + if (onConnect_) { + onConnect_(target, isConnect); + } +} + +int VirtualCommunicatorAggregator::GetLocalIdentity(std::string &outTarget) const +{ + return E_OK; +} + +void VirtualCommunicatorAggregator::OnlineDevice(const std::string &deviceId) const +{ + if (!isEnable_) { + return; + } + + // Called in main thread only + for (const auto &iter : communicators_) { + VirtualCommunicator *communicatorTmp = static_cast(iter.second); + if (iter.first != deviceId) { + communicatorTmp->CallbackOnConnect(deviceId, true); + } + } +} + +void VirtualCommunicatorAggregator::OfflineDevice(const std::string &deviceId) const +{ + if (!isEnable_) { + return; + } + + // Called in main thread only + for (const auto &iter : communicators_) { + VirtualCommunicator *communicatorTmp = static_cast(iter.second); + if (iter.first != deviceId) { + communicatorTmp->CallbackOnConnect(deviceId, false); + } + } +} + +ICommunicator *VirtualCommunicatorAggregator::AllocCommunicator(const std::string &deviceId, int &outErrorNo) +{ + // Called in main thread only + VirtualCommunicator *communicator = new (std::nothrow) VirtualCommunicator(deviceId, this); + if (communicator == nullptr) { + outErrorNo = -E_OUT_OF_MEMORY; + } + { + std::lock_guard lock(communicatorsLock_); + communicators_.insert(std::pair(deviceId, communicator)); + } + OnlineDevice(deviceId); + return communicator; +} + +ICommunicator *VirtualCommunicatorAggregator::GetCommunicator(const std::string &deviceId) const +{ + std::lock_guard lock(communicatorsLock_); + auto iter = communicators_.find(deviceId); + if (iter != communicators_.end()) { + VirtualCommunicator *communicator = static_cast(iter->second); + return communicator; + } + return nullptr; +} + +void VirtualCommunicatorAggregator::DispatchMessage(const std::string &srcTarget, const std::string &dstTarget, + const Message *inMsg, const OnSendEnd &onEnd) +{ + if (VirtualCommunicatorAggregator::GetBlockValue()) { + std::unique_lock lock(blockLock_); + conditionVar_.wait(lock); + } + + if (!isEnable_) { + LOGD("[VirtualCommunicatorAggregator] DispatchMessage, VirtualCommunicatorAggregator is disabled"); + delete inMsg; + inMsg = nullptr; + return CallSendEnd(-E_PERIPHERAL_INTERFACE_FAIL, onEnd); + } + std::lock_guard lock(communicatorsLock_); + auto iter = communicators_.find(dstTarget); + if (iter != communicators_.end()) { + LOGE("[VirtualCommunicatorAggregator] DispatchMessage, find dstTarget %s", dstTarget.c_str()); + VirtualCommunicator *communicator = static_cast(iter->second); + if (!communicator->IsEnabled()) { + LOGE("[VirtualCommunicatorAggregator] DispatchMessage, find dstTarget %s disabled", dstTarget.c_str()); + delete inMsg; + inMsg = nullptr; + return CallSendEnd(-E_PERIPHERAL_INTERFACE_FAIL, onEnd); + } + Message *msg = const_cast(inMsg); + msg->SetTarget(srcTarget); + RefObject::IncObjRef(communicator); + auto onDispatch = onDispatch_; + std::thread thread([communicator, srcTarget, dstTarget, msg, onDispatch]() { + if (onDispatch) { + onDispatch(dstTarget, msg); + } + communicator->CallbackOnMessage(srcTarget, msg); + RefObject::DecObjRef(communicator); + }); + thread.detach(); + CallSendEnd(E_OK, onEnd); + } else { + LOGE("[VirtualCommunicatorAggregator] DispatchMessage, can't find dstTarget %s", dstTarget.c_str()); + delete inMsg; + inMsg = nullptr; + CallSendEnd(-E_NOT_FOUND, onEnd); + } +} + +void VirtualCommunicatorAggregator::SetBlockValue(bool value) +{ + std::unique_lock lock(blockLock_); + isBlock_ = value; + if (!value) { + conditionVar_.notify_all(); + } +} + +bool VirtualCommunicatorAggregator::GetBlockValue() const +{ + return isBlock_; +} + +void VirtualCommunicatorAggregator::Disable() +{ + isEnable_ = false; +} + +void VirtualCommunicatorAggregator::Enable() +{ + LOGD("[VirtualCommunicatorAggregator] enable"); + isEnable_ = true; +} + +void VirtualCommunicatorAggregator::CallSendEnd(int errCode, const OnSendEnd &onEnd) +{ + if (onEnd) { + (void)RuntimeContext::GetInstance()->ScheduleTask([errCode, onEnd]() { + onEnd(errCode); + }); + } +} + +void VirtualCommunicatorAggregator::RegOnDispatch( + const std::function &onDispatch) +{ + onDispatch_ = onDispatch; +} + +void VirtualCommunicatorAggregator::SetCurrentUserId(const std::string &userId) +{ + userId_ = userId; +} +} // namespace DistributedDB + diff --git a/mock/distributeddb/test/unittest/common/syncer/virtual_communicator_aggregator.h b/mock/distributeddb/test/unittest/common/syncer/virtual_communicator_aggregator.h new file mode 100644 index 00000000..22db8b58 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/virtual_communicator_aggregator.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 VIRTUAL_ICOMMUNICATORAGGREGATOR_H +#define VIRTUAL_ICOMMUNICATORAGGREGATOR_H + +#include + +#include "icommunicator_aggregator.h" +#include "virtual_communicator.h" + +namespace DistributedDB { +class ICommunicator; // Forward Declaration + +class VirtualCommunicatorAggregator : public ICommunicatorAggregator { +public: + // Return 0 as success. Return negative as error + int Initialize(IAdapter *inAdapter) override; + + void Finalize() override; + + // If not success, return nullptr and set outErrorNo + ICommunicator *AllocCommunicator(uint64_t commLabel, int &outErrorNo) override; + ICommunicator *AllocCommunicator(const LabelType &commLabel, int &outErrorNo) override; + + void ReleaseCommunicator(ICommunicator *inCommunicator) override; + + int RegCommunicatorLackCallback(const CommunicatorLackCallback &onCommLack, const Finalizer &inOper) override; + int RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) override; + void RunCommunicatorLackCallback(const LabelType &commLabel); + void RunOnConnectCallback(const std::string &target, bool isConnect); + + int GetLocalIdentity(std::string &outTarget) const override; + + // online a virtual device to the VirtualCommunicator, should call in main thread + void OnlineDevice(const std::string &deviceId) const; + + // offline a virtual device to the VirtualCommunicator, should call in main thread + void OfflineDevice(const std::string &deviceId) const; + + void DispatchMessage(const std::string &srcTarget, const std::string &dstTarget, const Message *inMsg, + const OnSendEnd &onEnd); + + // If not success, return nullptr and set outErrorNo + ICommunicator *AllocCommunicator(const std::string &deviceId, int &outErrorNo); + + ICommunicator *GetCommunicator(const std::string &deviceId) const; + + void Disable(); + + void Enable(); + + void SetBlockValue(bool value); + + bool GetBlockValue() const; + + void RegOnDispatch(const std::function &onDispatch); + + void SetCurrentUserId(const std::string &userId); + + ~VirtualCommunicatorAggregator() {}; + VirtualCommunicatorAggregator() {}; + +private: + void CallSendEnd(int errCode, const OnSendEnd &onEnd); + + mutable std::mutex communicatorsLock_; + std::map communicators_; + std::string remoteDeviceId_ = "real_device"; + std::mutex blockLock_; + std::condition_variable conditionVar_; + bool isEnable_ = true; + bool isBlock_ = false; + CommunicatorLackCallback onCommLack_; + OnConnectCallback onConnect_; + std::function onDispatch_; + std::string userId_; +}; +} // namespace DistributedDB + +#endif // VIRTUAL_ICOMMUNICATORAGGREGATOR_H \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/virtual_multi_ver_sync_db_interface.cpp b/mock/distributeddb/test/unittest/common/syncer/virtual_multi_ver_sync_db_interface.cpp new file mode 100644 index 00000000..fffec2ab --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/virtual_multi_ver_sync_db_interface.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "virtual_multi_ver_sync_db_interface.h" + +#include "db_common.h" +#include "kvdb_manager.h" +#include "multi_ver_natural_store_snapshot.h" + +namespace DistributedDB { +VirtualMultiVerSyncDBInterface::VirtualMultiVerSyncDBInterface() : kvStore_(nullptr), connection_(nullptr) +{ +} + +VirtualMultiVerSyncDBInterface::~VirtualMultiVerSyncDBInterface() +{ + DeleteDatabase(); +} + +int VirtualMultiVerSyncDBInterface::GetInterfaceType() const +{ + return IKvDBSyncInterface::SYNC_MVD; +} + +void VirtualMultiVerSyncDBInterface::IncRefCount() +{ + kvStore_->IncRefCount(); +} + +void VirtualMultiVerSyncDBInterface::DecRefCount() +{ + kvStore_->DecRefCount(); +} + +std::vector VirtualMultiVerSyncDBInterface::GetIdentifier() const +{ + return kvStore_->GetIdentifier(); +} + +void VirtualMultiVerSyncDBInterface::GetMaxTimestamp(Timestamp &stamp) const +{ + return kvStore_->GetMaxTimestamp(stamp); +} + +int VirtualMultiVerSyncDBInterface::GetMetaData(const Key &key, Value &value) const +{ + return kvStore_->GetMetaData(key, value); +} + +int VirtualMultiVerSyncDBInterface::PutMetaData(const Key &key, const Value &value) +{ + return kvStore_->PutMetaData(key, value); +} + +int VirtualMultiVerSyncDBInterface::DeleteMetaData(const std::vector &keys) +{ + return kvStore_->DeleteMetaData(keys); +} + +int VirtualMultiVerSyncDBInterface::GetAllMetaKeys(std::vector &keys) const +{ + return kvStore_->GetAllMetaKeys(keys); +} + +bool VirtualMultiVerSyncDBInterface::IsCommitExisted(const MultiVerCommitNode &commit) const +{ + return kvStore_->IsCommitExisted(commit); +} + +int VirtualMultiVerSyncDBInterface::GetDeviceLatestCommit(std::map &commits) const +{ + return kvStore_->GetDeviceLatestCommit(commits); +} + +int VirtualMultiVerSyncDBInterface::GetCommitTree(const std::map &inCommit, + std::vector &outCommit) const +{ + return kvStore_->GetCommitTree(inCommit, outCommit); +} + +int VirtualMultiVerSyncDBInterface::GetCommitData(const MultiVerCommitNode &commit, + std::vector &entries) const +{ + return kvStore_->GetCommitData(commit, entries); +} + +MultiVerKvEntry *VirtualMultiVerSyncDBInterface::CreateKvEntry(const std::vector &entries) +{ + return kvStore_->CreateKvEntry(entries); +} + +void VirtualMultiVerSyncDBInterface::ReleaseKvEntry(const MultiVerKvEntry *entry) +{ + return kvStore_->ReleaseKvEntry(entry); +} + +bool VirtualMultiVerSyncDBInterface::IsValueSliceExisted(const ValueSliceHash &value) const +{ + return kvStore_->IsValueSliceExisted(value); +} + +int VirtualMultiVerSyncDBInterface::GetValueSlice(const ValueSliceHash &hashValue, ValueSlice &sliceValue) const +{ + return kvStore_->GetValueSlice(hashValue, sliceValue); +} + +int VirtualMultiVerSyncDBInterface::PutValueSlice(const ValueSliceHash &hashValue, const ValueSlice &sliceValue) const +{ + return kvStore_->PutValueSlice(hashValue, sliceValue); +} + +int VirtualMultiVerSyncDBInterface::PutCommitData(const MultiVerCommitNode &commit, + const std::vector &entries, const std::string &deviceName) +{ + return kvStore_->PutCommitData(commit, entries, deviceName); +} + +int VirtualMultiVerSyncDBInterface::MergeSyncCommit(const MultiVerCommitNode &commit, + const std::vector &commits) +{ + return kvStore_->MergeSyncCommit(commit, commits); +} + +int VirtualMultiVerSyncDBInterface::TransferSyncCommitDevInfo(MultiVerCommitNode &commit, const std::string &devId, + bool isSyncedIn) const +{ + return kvStore_->TransferSyncCommitDevInfo(commit, devId, isSyncedIn); +} + +int VirtualMultiVerSyncDBInterface::Initialize(const std::string &deviceId) +{ + std::string dir; + testTool_.TestDirInit(dir); + KvDBProperties prop; + prop.SetStringProp(KvDBProperties::USER_ID, "sync_test"); + prop.SetStringProp(KvDBProperties::APP_ID, "sync_test"); + prop.SetStringProp(KvDBProperties::STORE_ID, deviceId); + std::string identifier = DBCommon::TransferHashString("sync_test-sync_test-" + deviceId); + + prop.SetStringProp(KvDBProperties::IDENTIFIER_DATA, identifier); + std::string identifierDir = DBCommon::TransferStringToHex(identifier); + prop.SetStringProp(KvDBProperties::IDENTIFIER_DIR, identifierDir); + prop.SetStringProp(KvDBProperties::DATA_DIR, dir + "/commitstore"); + prop.SetIntProp(KvDBProperties::DATABASE_TYPE, KvDBProperties::MULTI_VER_TYPE); + prop.SetBoolProp(KvDBProperties::CREATE_IF_NECESSARY, true); + + int errCode = E_OK; + IKvDB *kvDB = KvDBManager::OpenDatabase(prop, errCode); + if (errCode != E_OK) { + LOGE("[VirtualMultiVerSyncDBInterface] db create failed path, err %d", errCode); + return errCode; + } + kvStore_ = static_cast(kvDB); + IKvDBConnection *conn = kvDB->GetDBConnection(errCode); + if (errCode != E_OK) { + LOGE("[VirtualMultiVerSyncDBInterface] connection get failed path, err %d", errCode); + RefObject::DecObjRef(kvStore_); + kvStore_ = nullptr; + return errCode; + } + RefObject::DecObjRef(kvStore_); + connection_ = static_cast(conn); + return E_OK; +} + +int VirtualMultiVerSyncDBInterface::GetData(const Key &key, Key &value) +{ + IKvDBSnapshot *snapshot = nullptr; + int errCode = connection_->GetSnapshot(snapshot); + if (errCode != E_OK) { + LOGE("[VirtualMultiVerSyncDBInterface] GetSnapshot failed err %d", errCode); + return errCode; + } + errCode = snapshot->Get(key, value); + connection_->ReleaseSnapshot(snapshot); + return errCode; +} + +int VirtualMultiVerSyncDBInterface::PutData(const Key &key, const Key &value) +{ + IOption option; + return connection_->Put(option, key, value); +} + +int VirtualMultiVerSyncDBInterface::DeleteData(const Key &key) +{ + IOption option; + return connection_->Delete(option, key); +} + +int VirtualMultiVerSyncDBInterface::StartTransaction() +{ + return connection_->StartTransaction(); +} + +int VirtualMultiVerSyncDBInterface::Commit() +{ + return connection_->Commit(); +} + +int VirtualMultiVerSyncDBInterface::DeleteDatabase() +{ + if (connection_ != nullptr) { + KvDBProperties prop = kvStore_->GetMyProperties(); + int errCode = connection_->Close(); + if (errCode != E_OK) { + return errCode; + } + connection_ = nullptr; + kvStore_ = nullptr; + return KvDBManager::RemoveDatabase(prop); + } + return -E_NOT_FOUND; +} + +const KvDBProperties &VirtualMultiVerSyncDBInterface::GetDbProperties() const +{ + return properties_; +} + +int VirtualMultiVerSyncDBInterface::DeleteMetaDataByPrefixKey(const Key &keyPrefix) const +{ + return -E_NOT_SUPPORT; +} +} // namespace DistributedDB + diff --git a/mock/distributeddb/test/unittest/common/syncer/virtual_multi_ver_sync_db_interface.h b/mock/distributeddb/test/unittest/common/syncer/virtual_multi_ver_sync_db_interface.h new file mode 100644 index 00000000..e9d985be --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/virtual_multi_ver_sync_db_interface.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 VIRTUAL_MULTI_VER_SYNC_INTERFACE_H +#define VIRTUAL_MULTI_VER_SYNC_INTERFACE_H + +#include "distributeddb_tools_unit_test.h" +#include "multi_ver_natural_store.h" +#include "multi_ver_natural_store_connection.h" + +namespace DistributedDB { +class VirtualMultiVerSyncDBInterface final : public MultiVerKvDBSyncInterface { +public: + VirtualMultiVerSyncDBInterface(); + ~VirtualMultiVerSyncDBInterface() override; + + int GetInterfaceType() const override; + + void IncRefCount() override; + + void DecRefCount() override; + + std::vector GetIdentifier() const override; + + void GetMaxTimestamp(Timestamp &stamp) const override; + + int GetMetaData(const Key &key, Value &value) const override; + + int PutMetaData(const Key &key, const Value &value) override; + + // Delete multiple meta data records in a transaction. + int DeleteMetaData(const std::vector &keys) override; + + // Delete multiple meta data records with key prefix in a transaction. + int DeleteMetaDataByPrefixKey(const Key &keyPrefix) const override; + + int GetAllMetaKeys(std::vector &keys) const override; + + bool IsCommitExisted(const MultiVerCommitNode &) const override; + + int GetDeviceLatestCommit(std::map &) const override; + + int GetCommitTree(const std::map &, + std::vector &) const override; + + int GetCommitData(const MultiVerCommitNode &commit, std::vector &entries) const override; + + MultiVerKvEntry *CreateKvEntry(const std::vector &) override; + + void ReleaseKvEntry(const MultiVerKvEntry *entry) override; + + bool IsValueSliceExisted(const ValueSliceHash &value) const override; + + int GetValueSlice(const ValueSliceHash &hashValue, ValueSlice &sliceValue) const override; + + int PutValueSlice(const ValueSliceHash &hashValue, const ValueSlice &sliceValue) const override; + + int PutCommitData(const MultiVerCommitNode &commit, const std::vector &entries, + const std::string &deviceName) override; + + int MergeSyncCommit(const MultiVerCommitNode &commit, const std::vector &commits) override; + + void NotifyStartSyncOperation() override {}; + + void NotifyFinishSyncOperation() override {}; + + int TransferSyncCommitDevInfo(MultiVerCommitNode &commit, const std::string &devId, bool isSyncedIn) const override; + + int Initialize(const std::string &deviceId); + + int GetData(const Key &key, Key &value); + + int PutData(const Key &key, const Key &value); + + int DeleteData(const Key &key); + + int StartTransaction(); + + int Commit(); + + int DeleteDatabase(); + + const KvDBProperties &GetDbProperties() const override; + +private: + DistributedDBUnitTest::DistributedDBToolsUnitTest testTool_; + MultiVerNaturalStore *kvStore_; + MultiVerNaturalStoreConnection *connection_; + KvDBProperties properties_; +}; +} // namespace DistributedDB + +#endif // VIRTUAL_MULTI_VER_SYNC_INTERFACE \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/virtual_relational_ver_sync_db_interface.cpp b/mock/distributeddb/test/unittest/common/syncer/virtual_relational_ver_sync_db_interface.cpp new file mode 100644 index 00000000..c91bf9dc --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/virtual_relational_ver_sync_db_interface.cpp @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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. + */ +#ifdef RELATIONAL_STORE +#include "db_common.h" +#include "virtual_relational_ver_sync_db_interface.h" +#include "generic_single_ver_kv_entry.h" +#include "virtual_single_ver_sync_db_Interface.h" + +namespace DistributedDB { +namespace { + int GetEntriesFromItems(std::vector &entries, const std::vector &dataItems) + { + int errCode = E_OK; + for (auto &item : dataItems) { + auto entry = new (std::nothrow) GenericSingleVerKvEntry(); + if (entry == nullptr) { + LOGE("Create entry failed."); + errCode = -E_OUT_OF_MEMORY; + break; + } + DataItem storageItem; + storageItem.key = item.key; + storageItem.value = item.value; + storageItem.flag = item.flag; + storageItem.timestamp = item.timestamp; + storageItem.writeTimestamp = item.writeTimestamp; + storageItem.hashKey = item.hashKey; + entry->SetEntryData(std::move(storageItem)); + entries.push_back(entry); + } + if (errCode != E_OK) { + LOGD("[GetEntriesFromItems] failed:%d", errCode); + for (auto &kvEntry : entries) { + delete kvEntry; + kvEntry = nullptr; + } + entries.clear(); + } + LOGD("[GetEntriesFromItems] size:%zu", dataItems.size()); + return errCode; + } + + std::string GetStr(const std::vector &vec) + { + std::string str; + DBCommon::VectorToString(vec, str); + return str; + } +} + +int VirtualRelationalVerSyncDBInterface::PutSyncDataWithQuery(const QueryObject &object, + const std::vector &entries, const std::string &deviceName) +{ + LOGD("[PutSyncData] size %zu", entries.size()); + std::vector dataItems; + for (auto itemEntry : entries) { + auto *entry = static_cast(itemEntry); + if (entry != nullptr) { + DataItem item; + item.origDev = entry->GetOrigDevice(); + item.flag = entry->GetFlag(); + item.timestamp = entry->GetTimestamp(); + item.writeTimestamp = entry->GetWriteTimestamp(); + entry->GetKey(item.key); + entry->GetValue(item.value); + entry->GetHashKey(item.hashKey); + dataItems.push_back(item); + } + } + OptTableDataWithLog optTableDataWithLog; + optTableDataWithLog.tableName = object.GetTableName(); + int errCode = DataTransformer::TransformDataItem(dataItems, localFieldInfo_, + localFieldInfo_, optTableDataWithLog); + if (errCode != E_OK) { + return errCode; + } + for (const auto &optRowDataWithLog : optTableDataWithLog.dataList) { + VirtualRowData virtualRowData; + virtualRowData.logInfo = optRowDataWithLog.logInfo; + size_t index = 0; + for (const auto &optItem : optRowDataWithLog.optionalData) { + if (index >= localFieldInfo_.size()) { + break; + } + DataValue dataValue = std::move(optItem); + LOGD("type:%d", static_cast(optItem.GetType())); + virtualRowData.objectData.PutDataValue(localFieldInfo_[index].GetFieldName(), dataValue); + index++; + } + syncData_[object.GetTableName()][GetStr(virtualRowData.logInfo.hashKey)] = virtualRowData; + } + LOGD("tableName %s", optTableDataWithLog.tableName.c_str()); + return errCode; +} + +int VirtualRelationalVerSyncDBInterface::PutLocalData(const std::vector &dataList, + const std::string &tableName) +{ + for (const auto &item : dataList) { + localData_[tableName][GetStr(item.logInfo.hashKey)] = item; + } + return E_OK; +} + +int VirtualRelationalVerSyncDBInterface::GetSyncData(QueryObject &query, + const SyncTimeRange &timeRange, const DataSizeSpecInfo &dataSizeInfo, + ContinueToken &continueStmtToken, std::vector &entries) const +{ + if (localData_.find(query.GetTableName()) == localData_.end()) { + LOGD("[GetSyncData] No Data Return"); + return E_OK; + } + std::vector dataItemList; + TableDataWithLog tableDataWithLog = {query.GetTableName(), {}}; + for (const auto &[hashKey, virtualData] : localData_[query.GetTableName()]) { + if (virtualData.logInfo.timestamp < timeRange.beginTime || + virtualData.logInfo.timestamp >= timeRange.endTime) { + LOGD("ignore hashkey %s", hashKey.c_str()); + continue; + } + RowDataWithLog rowData; + for (const auto &field : localFieldInfo_) { + DataValue dataValue; + (void)virtualData.objectData.GetDataValue(field.GetFieldName(), dataValue); + rowData.rowData.push_back(std::move(dataValue)); + } + rowData.logInfo = virtualData.logInfo; + tableDataWithLog.dataList.push_back(rowData); + } + + int errCode = DataTransformer::TransformTableData(tableDataWithLog, localFieldInfo_, dataItemList); + if (errCode != E_OK) { + return errCode; + } + continueStmtToken = nullptr; + return GetEntriesFromItems(entries, dataItemList); +} + +RelationalSchemaObject VirtualRelationalVerSyncDBInterface::GetSchemaInfo() const +{ + return schemaObj_; +} + +int VirtualRelationalVerSyncDBInterface::GetDatabaseCreateTimestamp(Timestamp &outTime) const +{ + return E_OK; +} + +int VirtualRelationalVerSyncDBInterface::GetBatchMetaData(const std::vector &keys, + std::vector &entries) const +{ + int errCode = E_OK; + for (const auto &key : keys) { + Entry entry; + entry.key = key; + errCode = GetMetaData(key, entry.value); + if (errCode != E_OK) { + return errCode; + } + entries.push_back(entry); + } + return errCode; +} + +int VirtualRelationalVerSyncDBInterface::PutBatchMetaData(std::vector &entries) +{ + int errCode = E_OK; + for (const auto &entry : entries) { + errCode = PutMetaData(entry.key, entry.value); + if (errCode != E_OK) { + return errCode; + } + } + return errCode; +} + +std::vector VirtualRelationalVerSyncDBInterface::GetTablesQuery() +{ + return {}; +} + +int VirtualRelationalVerSyncDBInterface::LocalDataChanged(int notifyEvent, std::vector &queryObj) +{ + return E_OK; +} + +int VirtualRelationalVerSyncDBInterface::GetInterfaceType() const +{ + return SYNC_RELATION; +} + +void VirtualRelationalVerSyncDBInterface::IncRefCount() +{ +} + +void VirtualRelationalVerSyncDBInterface::DecRefCount() +{ +} + +std::vector VirtualRelationalVerSyncDBInterface::GetIdentifier() const +{ + return {}; +} + +void VirtualRelationalVerSyncDBInterface::GetMaxTimestamp(Timestamp &stamp) const +{ + for (const auto &item : syncData_) { + for (const auto &entry : item.second) { + if (stamp < entry.second.logInfo.timestamp) { + stamp = entry.second.logInfo.timestamp; + } + } + } + LOGD("VirtualSingleVerSyncDBInterface::GetMaxTimestamp time = %" PRIu64, stamp); +} + +int VirtualRelationalVerSyncDBInterface::GetMetaData(const Key &key, Value &value) const +{ + auto iter = metadata_.find(key); + if (iter != metadata_.end()) { + value = iter->second; + return E_OK; + } + return -E_NOT_FOUND; +} + +int VirtualRelationalVerSyncDBInterface::PutMetaData(const Key &key, const Value &value) +{ + metadata_[key] = value; + return E_OK; +} + +int VirtualRelationalVerSyncDBInterface::DeleteMetaData(const std::vector &keys) +{ + for (const auto &key : keys) { + (void)metadata_.erase(key); + } + return E_OK; +} + +int VirtualRelationalVerSyncDBInterface::DeleteMetaDataByPrefixKey(const Key &keyPrefix) const +{ + size_t prefixKeySize = keyPrefix.size(); + for (auto iter = metadata_.begin();iter != metadata_.end();) { + if (prefixKeySize <= iter->first.size() && + keyPrefix == Key(iter->first.begin(), std::next(iter->first.begin(), prefixKeySize))) { + iter = metadata_.erase(iter); + } else { + ++iter; + } + } + return E_OK; +} + +int VirtualRelationalVerSyncDBInterface::GetAllMetaKeys(std::vector &keys) const +{ + for (auto &iter : metadata_) { + keys.push_back(iter.first); + } + LOGD("GetAllMetaKeys size %zu", keys.size()); + return E_OK; +} + +const KvDBProperties &VirtualRelationalVerSyncDBInterface::GetDbProperties() const +{ + return properties_; +} + +void VirtualRelationalVerSyncDBInterface::SetLocalFieldInfo(const std::vector &localFieldInfo) +{ + localFieldInfo_.clear(); + localFieldInfo_ = localFieldInfo; +} + +int VirtualRelationalVerSyncDBInterface::GetAllSyncData(const std::string &tableName, + std::vector &data) +{ + if (syncData_.find(tableName) == syncData_.end()) { + return -E_NOT_FOUND; + } + for (const auto &entry : syncData_[tableName]) { + data.push_back(entry.second); + } + return E_OK; +} + +int VirtualRelationalVerSyncDBInterface::GetVirtualSyncData(const std::string &tableName, + const std::string &hashKey, VirtualRowData &data) +{ + if (syncData_.find(tableName) == syncData_.end()) { + return -E_NOT_FOUND; + } + if (syncData_.find(hashKey) == syncData_.end()) { + return -E_NOT_FOUND; + } + data = syncData_[tableName][hashKey]; + return E_OK; +} + +void VirtualRelationalVerSyncDBInterface::EraseSyncData(const std::string &tableName) +{ + if (syncData_.find(tableName) == syncData_.end()) { + return; + } + syncData_.erase(tableName); +} + +int VirtualRelationalVerSyncDBInterface::CreateDistributedDeviceTable(const std::string &device, + const RelationalSyncStrategy &syncStrategy) +{ + return E_OK; +} + +int VirtualRelationalVerSyncDBInterface::RegisterSchemaChangedCallback(const std::function &onSchemaChanged) +{ + return E_OK; +} + +void VirtualRelationalVerSyncDBInterface::SetTableInfo(const TableInfo &tableInfo) +{ + schemaObj_.AddRelationalTable(tableInfo); +} + +int VirtualRelationalVerSyncDBInterface::GetMaxTimestamp(const std::string &tableName, Timestamp ×tamp) const +{ + (void)tableName; + timestamp = 0; + return E_OK; +} + +void ObjectData::PutDataValue(const std::string &fieldName, const DataValue &value) +{ + fieldData[fieldName] = value; +} + +int ObjectData::GetDataValue(const std::string &fieldName, DataValue &value) const +{ + if (fieldData.find(fieldName) == fieldData.end()) { + return -E_NOT_FOUND; + } + value = fieldData[fieldName]; + return E_OK; +} +} +#endif \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/virtual_relational_ver_sync_db_interface.h b/mock/distributeddb/test/unittest/common/syncer/virtual_relational_ver_sync_db_interface.h new file mode 100644 index 00000000..c2f30c82 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/virtual_relational_ver_sync_db_interface.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 VIRTUAL_RELATIONAL_VER_SYNC_DB_INTERFACE_H +#define VIRTUAL_RELATIONAL_VER_SYNC_DB_INTERFACE_H +#ifdef RELATIONAL_STORE + +#include "data_transformer.h" +#include "relational_db_sync_interface.h" +#include "sqlite_single_ver_continue_token.h" +#include "relational_schema_object.h" + +namespace DistributedDB { +struct ObjectData { +public: + void PutDataValue(const std::string &fieldName, const DataValue &value); + int GetDataValue(const std::string &fieldName, DataValue &value) const; +private: + mutable std::map fieldData; +}; + +struct VirtualRowData { + LogInfo logInfo; + ObjectData objectData; +}; + +class VirtualRelationalVerSyncDBInterface : public RelationalDBSyncInterface { +public: + VirtualRelationalVerSyncDBInterface() = default; + ~VirtualRelationalVerSyncDBInterface() override = default; + + int PutSyncDataWithQuery(const QueryObject &query, const std::vector &entries, + const std::string &deviceName) override; + + int PutLocalData(const std::vector &dataList, const std::string &tableName); + + RelationalSchemaObject GetSchemaInfo() const override; + + int GetDatabaseCreateTimestamp(Timestamp &outTime) const override; + + int GetBatchMetaData(const std::vector &keys, std::vector &entries) const override; + + int PutBatchMetaData(std::vector &entries) override; + + std::vector GetTablesQuery() override; + + int LocalDataChanged(int notifyEvent, std::vector &queryObj) override; + + int GetSyncData(QueryObject &query, const SyncTimeRange &timeRange, + const DataSizeSpecInfo &dataSizeInfo, ContinueToken &continueStmtToken, + std::vector &entries) const override; + + int GetInterfaceType() const override; + + void IncRefCount() override; + + void DecRefCount() override; + + std::vector GetIdentifier() const override; + + void GetMaxTimestamp(Timestamp &stamp) const override; + + // Get the max timestamp of one table. + int GetMaxTimestamp(const std::string &tableName, Timestamp ×tamp) const override; + + int GetMetaData(const Key &key, Value &value) const override; + + int PutMetaData(const Key &key, const Value &value) override; + + int DeleteMetaData(const std::vector &keys) override; + + int DeleteMetaDataByPrefixKey(const Key &keyPrefix) const override; + + int GetAllMetaKeys(std::vector &keys) const override; + + const KvDBProperties &GetDbProperties() const override; + + void SetLocalFieldInfo(const std::vector &localFieldInfo); + + int GetAllSyncData(const std::string &tableName, std::vector &data); + + int GetVirtualSyncData(const std::string &tableName, const std::string &hashKey, VirtualRowData &data); + + int InterceptData(std::vector &entries, + const std::string &sourceID, const std::string &targetID) const override + { + return E_OK; + } + + int CheckAndInitQueryCondition(QueryObject &query) const override + { + return E_OK; + } + + int CreateDistributedDeviceTable(const std::string &device, const RelationalSyncStrategy &syncStrategy) override; + + int RegisterSchemaChangedCallback(const std::function &onSchemaChanged) override; + + void EraseSyncData(const std::string &tableName); + + void SetTableInfo(const TableInfo &tableInfo); + +private: + mutable std::map, std::vector> metadata_; + std::map> syncData_; + mutable std::map> localData_; + std::string schema_; + RelationalSchemaObject schemaObj_; + std::vector localFieldInfo_; + KvDBProperties properties_; + SecurityOption secOption_; +}; +} +#endif +#endif // VIRTUAL_RELATIONAL_VER_SYNC_DB_INTERFACE_H \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/virtual_single_ver_sync_db_Interface.cpp b/mock/distributeddb/test/unittest/common/syncer/virtual_single_ver_sync_db_Interface.cpp new file mode 100644 index 00000000..f4641e5d --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/virtual_single_ver_sync_db_Interface.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "virtual_single_ver_sync_db_Interface.h" + +#include +#include + +#include "db_common.h" +#include "db_errno.h" +#include "generic_single_ver_kv_entry.h" +#include "intercepted_data_impl.h" +#include "log_print.h" +#include "query_object.h" +#include "securec.h" + +namespace DistributedDB { +namespace { + int GetEntriesFromItems(std::vector &entries, const std::vector &dataItems) + { + int errCode = E_OK; + for (auto &item : dataItems) { + auto entry = new (std::nothrow) GenericSingleVerKvEntry(); + if (entry == nullptr) { + LOGE("Create entry failed."); + errCode = -E_OUT_OF_MEMORY; + break; + } + DataItem storageItem; + storageItem.key = item.key; + storageItem.value = item.value; + storageItem.flag = item.flag; + storageItem.timestamp = item.timestamp; + storageItem.writeTimestamp = item.writeTimestamp; + entry->SetEntryData(std::move(storageItem)); + entries.push_back(entry); + } + if (errCode != E_OK) { + for (auto &kvEntry : entries) { + delete kvEntry; + kvEntry = nullptr; + } + entries.clear(); + } + return errCode; + } +} +int VirtualSingleVerSyncDBInterface::GetInterfaceType() const +{ + return SYNC_SVD; +} + +void VirtualSingleVerSyncDBInterface::IncRefCount() +{ +} + +void VirtualSingleVerSyncDBInterface::DecRefCount() +{ +} + +std::vector VirtualSingleVerSyncDBInterface::GetIdentifier() const +{ + std::vector identifier; + return identifier; +} + +int VirtualSingleVerSyncDBInterface::GetMetaData(const Key &key, Value &value) const +{ + auto iter = metadata_.find(key); + if (iter != metadata_.end()) { + value = iter->second; + return E_OK; + } + return -E_NOT_FOUND; +} + +int VirtualSingleVerSyncDBInterface::PutMetaData(const Key &key, const Value &value) +{ + if (busy_) { + return -E_BUSY; + } + metadata_[key] = value; + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::DeleteMetaData(const std::vector &keys) +{ + for (const auto &key : keys) { + (void)metadata_.erase(key); + } + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::GetAllMetaKeys(std::vector &keys) const +{ + for (auto iter = metadata_.begin(); iter != metadata_.end(); ++iter) { + keys.push_back(iter->first); + } + LOGD("GetAllMetaKeys size %zu", keys.size()); + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::GetSyncData(Timestamp begin, Timestamp end, std::vector &dataItems, + ContinueToken &continueStmtToken, const DataSizeSpecInfo &dataSizeInfo) const +{ + return -E_NOT_SUPPORT; +} + +int VirtualSingleVerSyncDBInterface::GetSyncDataNext(std::vector &dataItems, ContinueToken &continueStmtToken, + const DataSizeSpecInfo &dataSizeInfo) const +{ + return -E_NOT_SUPPORT; +} + +void VirtualSingleVerSyncDBInterface::ReleaseContinueToken(ContinueToken& continueStmtToken) const +{ + return; +} + +SchemaObject VirtualSingleVerSyncDBInterface::GetSchemaInfo() const +{ + return schemaObj_; +} + +bool VirtualSingleVerSyncDBInterface::CheckCompatible(const std::string& schema, uint8_t type) const +{ + if (schema_.empty() && schema.empty() && ReadSchemaType(type) != SchemaType::UNRECOGNIZED) { + return true; + } + return (schemaObj_.CompareAgainstSchemaString(schema) == -E_SCHEMA_EQUAL_EXACTLY); +} + +int VirtualSingleVerSyncDBInterface::PutData(const Key &key, const Value &value, const Timestamp &time, int flag) +{ + VirtualDataItem item; + item.key = key; + item.value = value; + item.timestamp = time; + item.writeTimestamp = time; + item.flag = flag; + item.isLocal = true; + dbData_.push_back(item); + return E_OK; +} + +void VirtualSingleVerSyncDBInterface::GetMaxTimestamp(Timestamp& stamp) const +{ + for (auto iter = dbData_.begin(); iter != dbData_.end(); ++iter) { + if (stamp < iter->writeTimestamp) { + stamp = iter->writeTimestamp; + } + } + LOGD("VirtualSingleVerSyncDBInterface::GetMaxTimestamp time = %" PRIu64, stamp); +} + +int VirtualSingleVerSyncDBInterface::RemoveDeviceData(const std::string &deviceName, bool isNeedNotify) +{ + std::lock_guard autoLock(deviceDataLock_); + deviceData_.erase(deviceName); + LOGD("RemoveDeviceData FINISH"); + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::GetSyncData(const Key &key, VirtualDataItem &dataItem) +{ + auto iter = std::find_if(dbData_.begin(), dbData_.end(), + [key](const VirtualDataItem& item) { return item.key == key; }); + if (iter != dbData_.end()) { + dataItem.key = iter->key; + dataItem.value = iter->value; + dataItem.timestamp = iter->timestamp; + dataItem.writeTimestamp = iter->writeTimestamp; + dataItem.flag = iter->flag; + dataItem.isLocal = iter->isLocal; + return E_OK; + } + return -E_NOT_FOUND; +} + +int VirtualSingleVerSyncDBInterface::GetSyncData(Timestamp begin, Timestamp end, + std::vector &entries, ContinueToken &continueStmtToken, + const DataSizeSpecInfo &dataSizeInfo) const +{ + std::vector dataItems; + int errCode = GetSyncData(begin, end, dataSizeInfo.blockSize, dataItems, continueStmtToken); + if (errCode != E_OK) { + LOGE("[VirtualSingleVerSyncDBInterface][GetSyncData] GetSyncData failed err %d", errCode); + return errCode; + } + return GetEntriesFromItems(entries, dataItems); +} + +int VirtualSingleVerSyncDBInterface::GetSyncDataNext(std::vector &entries, + ContinueToken &continueStmtToken, const DataSizeSpecInfo &dataSizeInfo) const +{ + if (continueStmtToken == nullptr) { + return -E_NOT_SUPPORT; + } + return 0; +} + +int VirtualSingleVerSyncDBInterface::GetSyncData(Timestamp begin, Timestamp end, uint32_t blockSize, + std::vector &dataItems, ContinueToken &continueStmtToken) const +{ + for (const auto &data : dbData_) { + if (data.isLocal) { + if (data.writeTimestamp >= begin && data.writeTimestamp < end) { + dataItems.push_back(data); + } + } + } + continueStmtToken = nullptr; + LOGD("dataItems size %zu", dataItems.size()); + return E_OK; +} + +void VirtualSingleVerSyncDBInterface::SetSaveDataDelayTime(uint64_t milliDelayTime) +{ + saveDataDelayTime_ = milliDelayTime; +} + +int VirtualSingleVerSyncDBInterface::GetSyncDataNext(std::vector& dataItems, + uint32_t blockSize, ContinueToken& continueStmtToken) const +{ + if (continueStmtToken == nullptr) { + return -E_NOT_SUPPORT; + } + return 0; +} + +int VirtualSingleVerSyncDBInterface::PutSyncData(std::vector& dataItems, + const std::string &deviceName) +{ + for (auto iter = dataItems.begin(); iter != dataItems.end(); ++iter) { + LOGD("PutSyncData"); + auto dbDataIter = std::find_if(dbData_.begin(), dbData_.end(), + [iter](VirtualDataItem item) { return item.key == iter->key; }); + if ((dbDataIter != dbData_.end()) && (dbDataIter->writeTimestamp < iter->writeTimestamp)) { + // if has conflict, compare writeTimestamp + LOGI("conflict data time local %" PRIu64 ", remote %" PRIu64, dbDataIter->writeTimestamp, + iter->writeTimestamp); + dbDataIter->key = iter->key; + dbDataIter->value = iter->value; + dbDataIter->timestamp = iter->timestamp; + dbDataIter->writeTimestamp = iter->writeTimestamp; + dbDataIter->flag = iter->flag; + dbDataIter->isLocal = false; + } else { + LOGI("PutSyncData, use remote data %" PRIu64, iter->timestamp); + VirtualDataItem dataItem; + dataItem.key = iter->key; + dataItem.value = iter->value; + dataItem.timestamp = iter->timestamp; + dataItem.writeTimestamp = iter->writeTimestamp; + dataItem.flag = iter->flag; + dataItem.isLocal = false; + dbData_.push_back(dataItem); + } + } + return E_OK; +} + +void VirtualSingleVerSyncDBInterface::SetSchemaInfo(const std::string& schema) +{ + schema_ = schema; + SchemaObject emptyObj; + schemaObj_ = emptyObj; + schemaObj_.ParseFromSchemaString(schema); +} + +const KvDBProperties &VirtualSingleVerSyncDBInterface::GetDbProperties() const +{ + return properties_; +} + +int VirtualSingleVerSyncDBInterface::GetSecurityOption(SecurityOption &option) const +{ + option = secOption_; + return E_OK; +} + +bool VirtualSingleVerSyncDBInterface::IsReadable() const +{ + return true; +} + +void VirtualSingleVerSyncDBInterface::SetSecurityOption(SecurityOption &option) +{ + secOption_ = option; +} + +void VirtualSingleVerSyncDBInterface::NotifyRemotePushFinished(const std::string &targetId) const +{ +} + +int VirtualSingleVerSyncDBInterface::GetDatabaseCreateTimestamp(Timestamp &outTime) const +{ + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::GetSyncData(QueryObject &query, const SyncTimeRange &timeRange, + const DataSizeSpecInfo &dataSizeInfo, ContinueToken &continueStmtToken, + std::vector &entries) const +{ + const auto &startKey = query.GetPrefixKey(); + Key endKey = startKey; + endKey.resize(DBConstant::MAX_KEY_SIZE, UCHAR_MAX); + + std::vector dataItems; + for (const auto &data : dbData_) { + // Only get local data. + if (!data.isLocal) { + continue; + } + + if ((data.flag & VirtualDataItem::DELETE_FLAG) != 0) { + if (data.timestamp >= timeRange.deleteBeginTime && data.timestamp < timeRange.deleteEndTime) { + dataItems.push_back(data); + } + } else { + if (data.timestamp >= timeRange.beginTime && data.timestamp < timeRange.endTime && + data.key >= startKey && data.key <= endKey) { + dataItems.push_back(data); + } + } + } + + LOGD("dataItems size %zu", dataItems.size()); + return GetEntriesFromItems(entries, dataItems); +} + +int VirtualSingleVerSyncDBInterface::DeleteMetaDataByPrefixKey(const Key &keyPrefix) const +{ + size_t prefixKeySize = keyPrefix.size(); + for (auto iter = metadata_.begin(); iter != metadata_.end();) { + if (prefixKeySize <= iter->first.size() && + keyPrefix == Key(iter->first.begin(), std::next(iter->first.begin(), prefixKeySize))) { + iter = metadata_.erase(iter); + } else { + ++iter; + } + } + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::GetCompressionOption(bool &needCompressOnSync, uint8_t &compressionRate) const +{ + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::GetCompressionAlgo(std::set &algorithmSet) const +{ + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::PutSyncData(const DataItem &item) +{ + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::CheckAndInitQueryCondition(QueryObject &query) const +{ + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::InterceptData(std::vector &entries, + const std::string &sourceID, const std::string &targetID) const +{ + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::PutSyncDataWithQuery(const QueryObject &query, + const std::vector &entries, const std::string &deviceName) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(saveDataDelayTime_)); + std::vector dataItems; + for (auto kvEntry : entries) { + auto genericKvEntry = static_cast(kvEntry); + VirtualDataItem item; + genericKvEntry->GetKey(item.key); + genericKvEntry->GetValue(item.value); + item.timestamp = genericKvEntry->GetTimestamp(); + item.writeTimestamp = genericKvEntry->GetWriteTimestamp(); + item.flag = genericKvEntry->GetFlag(); + item.isLocal = false; + dataItems.push_back(item); + } + return PutSyncData(dataItems, deviceName); +} + +int VirtualSingleVerSyncDBInterface::AddSubscribe(const std::string &subscribeId, const QueryObject &query, + bool needCacheSubscribe) +{ + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::RemoveSubscribe(const std::string &subscribeId) +{ + return E_OK; +} + +int VirtualSingleVerSyncDBInterface::RemoveSubscribe(const std::vector &subscribeIds) +{ + return E_OK; +} + +void VirtualSingleVerSyncDBInterface::SetBusy(bool busy) +{ + busy_ = busy; +} + +void VirtualSingleVerSyncDBInterface::PutDeviceData(const std::string &deviceName, const Key &key, const Value &value) +{ + std::lock_guard autoLock(deviceDataLock_); + deviceData_[deviceName][key] = value; +} + +void VirtualSingleVerSyncDBInterface::GetDeviceData(const std::string &deviceName, const Key &key, Value &value) +{ + std::lock_guard autoLock(deviceDataLock_); + value = deviceData_[deviceName][key]; +} +} // namespace DistributedDB diff --git a/mock/distributeddb/test/unittest/common/syncer/virtual_single_ver_sync_db_Interface.h b/mock/distributeddb/test/unittest/common/syncer/virtual_single_ver_sync_db_Interface.h new file mode 100644 index 00000000..43a7a9c9 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/virtual_single_ver_sync_db_Interface.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 KVDB_SYNCABLE_TEST_H +#define KVDB_SYNCABLE_TEST_H + +#include +#include +#include + +#include "single_ver_kvdb_sync_interface.h" +#include "query_object.h" +#include "store_types.h" + +namespace DistributedDB { +struct VirtualDataItem { + Key key; + Value value; + Timestamp timestamp = 0; + Timestamp writeTimestamp = 0; + uint64_t flag = 0; + bool isLocal = true; + static const uint64_t DELETE_FLAG = 0x01; + static const uint64_t LOCAL_FLAG = 0x02; +}; +class VirtualSingleVerSyncDBInterface : public SingleVerKvDBSyncInterface { +public: + int GetInterfaceType() const override; + + void IncRefCount() override; + + void DecRefCount() override; + + std::vector GetIdentifier() const override; + + int GetMetaData(const Key& key, Value& value) const override; + + int PutMetaData(const Key& key, const Value& value) override; + + int DeleteMetaData(const std::vector &keys) override; + // Delete multiple meta data records with key prefix in a transaction. + int DeleteMetaDataByPrefixKey(const Key &keyPrefix) const override; + + int GetAllMetaKeys(std::vector& keys) const override; + + int GetSyncData(Timestamp begin, Timestamp end, std::vector &dataItems, + ContinueToken &continueStmtToken, const DataSizeSpecInfo &dataSizeInfo) const override; + + int GetSyncDataNext(std::vector &dataItems, ContinueToken &continueStmtToken, + const DataSizeSpecInfo &dataSizeInfo) const override; + + void ReleaseContinueToken(ContinueToken& continueStmtToken) const override; + + void GetMaxTimestamp(Timestamp& stamp) const override; + + int RemoveDeviceData(const std::string &deviceName, bool isNeedNotify) override; + + int GetSyncData(const Key& key, VirtualDataItem& dataItem); + + int PutSyncData(const DataItem& item); + + int PutData(const Key &key, const Value &value, const Timestamp &time, int flag); + + int GetSyncData(Timestamp begin, Timestamp end, std::vector &entries, + ContinueToken &continueStmtToken, const DataSizeSpecInfo &dataSizeInfo) const override; + + int GetSyncData(QueryObject &query, const SyncTimeRange &timeRange, const DataSizeSpecInfo &dataSizeInfo, + ContinueToken &continueStmtToken, std::vector &entries) const override; + + int GetSyncDataNext(std::vector &entries, ContinueToken &continueStmtToken, + const DataSizeSpecInfo &dataSizeInfo) const override; + + int PutSyncDataWithQuery(const QueryObject &query, const std::vector &entries, + const std::string &deviceName) override; + + SchemaObject GetSchemaInfo() const override; + + bool CheckCompatible(const std::string& schema, uint8_t type) const override; + + void SetSchemaInfo(const std::string& schema); + + const KvDBProperties &GetDbProperties() const override; + + void SetSaveDataDelayTime(uint64_t milliDelayTime); + + int GetSecurityOption(SecurityOption &option) const override; + + bool IsReadable() const override; + + void SetSecurityOption(SecurityOption &option); + + void NotifyRemotePushFinished(const std::string &targetId) const override; + + int GetDatabaseCreateTimestamp(Timestamp &outTime) const override; + + int GetCompressionOption(bool &needCompressOnSync, uint8_t &compressionRate) const override; + int GetCompressionAlgo(std::set &algorithmSet) const override; + + // return E_OK if subscribe is legal, ERROR on exception. + int CheckAndInitQueryCondition(QueryObject &query) const override; + + int InterceptData(std::vector &entries, const std::string &sourceID, + const std::string &targetID) const override; + + int AddSubscribe(const std::string &subscribeId, const QueryObject &query, bool needCacheSubscribe) override; + + int RemoveSubscribe(const std::string &subscribeId) override; + + int RemoveSubscribe(const std::vector &subscribeIds) override; + + void SetBusy(bool busy); + + void PutDeviceData(const std::string &deviceName, const Key &key, const Value &value); + + void GetDeviceData(const std::string &deviceName, const Key &key, Value &value); +private: + int GetSyncData(Timestamp begin, Timestamp end, uint32_t blockSize, std::vector& dataItems, + ContinueToken& continueStmtToken) const; + + int GetSyncDataNext(std::vector& dataItems, + uint32_t blockSize, ContinueToken& continueStmtToken) const; + + int PutSyncData(std::vector& dataItems, const std::string &deviceName); + + mutable std::map, std::vector> metadata_; + std::vector dbData_; + std::string schema_; + SchemaObject schemaObj_; + KvDBProperties properties_; + uint64_t saveDataDelayTime_ = 0; + SecurityOption secOption_; + bool busy_ = false; + + std::mutex deviceDataLock_; + std::map> deviceData_; +}; +} // namespace DistributedDB + +#endif // KVDB_SYNCABLE_TEST_H \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/virtual_time_sync_communicator.cpp b/mock/distributeddb/test/unittest/common/syncer/virtual_time_sync_communicator.cpp new file mode 100644 index 00000000..5c42b426 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/virtual_time_sync_communicator.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "virtual_time_sync_communicator.h" + +#include "log_print.h" + +namespace DistributedDB { +VirtualTimeSyncCommunicator::VirtualTimeSyncCommunicator() + : srcTimeSync_(nullptr), + dstTimeSync_(nullptr), + timeOffset_(0), + deviceID_(""), + syncTaskcontext_(nullptr), + isEnable_(true) +{ +} + +VirtualTimeSyncCommunicator::~VirtualTimeSyncCommunicator() {} + +int VirtualTimeSyncCommunicator::RegOnMessageCallback(const OnMessageCallback &onMessage, const Finalizer &inOper) +{ + return 0; +} + +int VirtualTimeSyncCommunicator::RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) +{ + return 0; +} + +int VirtualTimeSyncCommunicator::RegOnSendableCallback(const std::function &onSendable, + const Finalizer &inOper) +{ + return 0; +} + +void VirtualTimeSyncCommunicator::Activate() +{ +} + +// return maximum allowed data size +uint32_t VirtualTimeSyncCommunicator::GetCommunicatorMtuSize() const +{ + return 0; +} + +uint32_t VirtualTimeSyncCommunicator::GetCommunicatorMtuSize(const std::string &target) const +{ + return GetCommunicatorMtuSize(); +} + +uint32_t VirtualTimeSyncCommunicator::GetTimeout() const +{ + return 0; +} + +uint32_t VirtualTimeSyncCommunicator::GetTimeout(const std::string &target) const +{ + return 0; +} + +bool VirtualTimeSyncCommunicator::IsDeviceOnline(const std::string &device) const +{ + return true; +} + +// Get local target name for identify self +int VirtualTimeSyncCommunicator::GetLocalIdentity(std::string &outTarget) const +{ + return 0; +} + +int VirtualTimeSyncCommunicator::SendMessage(const std::string &dstTarget, const Message *inMsg, + const SendConfig &config) +{ + return SendMessage(dstTarget, inMsg, config, nullptr); +} + +int VirtualTimeSyncCommunicator::SendMessage(const std::string &dstTarget, const Message *inMsg, + const SendConfig &config, const OnSendEnd &onEnd) +{ + if (!isEnable_) { + LOGD("[VirtualTimeSyncCommunicator]the VirtualTimeSyncCommunicator disabled!"); + return -E_PERIPHERAL_INTERFACE_FAIL; + } + LOGD("VirtualTimeSyncCommunicator::sendMessage dev = %s, syncid = %d", dstTarget.c_str(), inMsg->GetSequenceId()); + int errCode; + if (dstTarget == deviceID_) { + if (srcTimeSync_ == nullptr) { + LOGD("srcTimeSync_ = nullprt"); + return -E_INVALID_ARGS; + } + if (syncTaskcontext_ == nullptr) { + LOGD("syncTaskcontext_ = nullprt"); + return -E_INVALID_ARGS; + } + errCode = srcTimeSync_->AckRecv(inMsg); + } else { + if (dstTimeSync_ == nullptr) { + LOGD("dstTimeSync_ is nullprt"); + return -E_INVALID_ARGS; + } + Message *msgTmp = const_cast(inMsg); + errCode = dstTimeSync_->RequestRecv(msgTmp); + } + if (inMsg != nullptr) { + delete inMsg; + inMsg = nullptr; + } + return errCode; +} + +int VirtualTimeSyncCommunicator::GetRemoteCommunicatorVersion(const std::string &deviceId, uint16_t &version) const +{ + version = 0; + return E_OK; +} + +void VirtualTimeSyncCommunicator::SetTimeSync(TimeSync *srcTimeSync, TimeSync *dstTimeSync, + const std::string &deviceID, SyncTaskContext *syncTaskcontext) +{ + srcTimeSync_ = srcTimeSync; + dstTimeSync_ = dstTimeSync; + deviceID_ = deviceID; + syncTaskcontext_ = syncTaskcontext; +} + +void VirtualTimeSyncCommunicator::GetTimeOffset(TimeOffset &timeOffset) const +{ + timeOffset = timeOffset_; +} + +void VirtualTimeSyncCommunicator::Disable() +{ + isEnable_ = false; +} +} // namespace DistributedDB \ No newline at end of file diff --git a/mock/distributeddb/test/unittest/common/syncer/virtual_time_sync_communicator.h b/mock/distributeddb/test/unittest/common/syncer/virtual_time_sync_communicator.h new file mode 100644 index 00000000..e3165a46 --- /dev/null +++ b/mock/distributeddb/test/unittest/common/syncer/virtual_time_sync_communicator.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 VIRTUAL_TIME_SYNC_COMMUNICATOR_H +#define VIRTUAL_TIME_SYNC_COMMUNICATOR_H + +#include +#include +#include +#include +#include +#include + +#include "db_types.h" +#include "communicator_aggregator.h" +#include "icommunicator.h" +#include "ref_object.h" +#include "serial_buffer.h" +#include "time_sync.h" + +namespace DistributedDB { +class VirtualTimeSyncCommunicator : public ICommunicator { +public: + VirtualTimeSyncCommunicator(); + ~VirtualTimeSyncCommunicator(); + + DISABLE_COPY_ASSIGN_MOVE(VirtualTimeSyncCommunicator); + + int RegOnMessageCallback(const OnMessageCallback &onMessage, const Finalizer &inOper) override; + int RegOnConnectCallback(const OnConnectCallback &onConnect, const Finalizer &inOper) override; + int RegOnSendableCallback(const std::function &onSendable, const Finalizer &inOper) override; + + void Activate() override; + + // return maximum allowed data size + uint32_t GetCommunicatorMtuSize() const override; + uint32_t GetCommunicatorMtuSize(const std::string &target) const override; + + // return timeout + uint32_t GetTimeout() const override; + uint32_t GetTimeout(const std::string &target) const override; + + bool IsDeviceOnline(const std::string &device) const override; + + // Get local target name for identify self + int GetLocalIdentity(std::string &outTarget) const override; + + int SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config) override; + int SendMessage(const std::string &dstTarget, const Message *inMsg, const SendConfig &config, + const OnSendEnd &onEnd) override; + + int GetRemoteCommunicatorVersion(const std::string &deviceId, uint16_t &version) const override; + + void SetTimeSync(TimeSync *srcTimeSync, TimeSync *dstTimeSync, + const std::string &deviceID, SyncTaskContext *syncTaskcontext); + + void GetTimeOffset(TimeOffset &timeOffset) const; + + void Disable(); + +private: + TimeSync *srcTimeSync_; + TimeSync *dstTimeSync_; + TimeOffset timeOffset_; + std::string deviceID_; + SyncTaskContext *syncTaskcontext_; + bool isEnable_ = true; +}; +} // namespace DistributedDB + +#endif // VIRTUAL_TIME_SYNC_COMMUNICATOR_H diff --git a/mock/include/CMakeLists.txt b/mock/include/CMakeLists.txt index 8768389e..8cc2ad18 100644 --- a/mock/include/CMakeLists.txt +++ b/mock/include/CMakeLists.txt @@ -94,6 +94,7 @@ include_directories(${MOCK_DIR}/innerkits/storage_service/storage_manager_sa_pro include_directories(${MOCK_DIR}/innerkits/distributeddatamgr/distributeddata_inner/include) include_directories(${MOCK_DIR}/innerkits/distributeddatamgr/rdb/include) include_directories(${MOCK_DIR}/innerkits/distributeddatamgr/dfx) +include_directories(${MOCK_DIR}/innerkits/distributeddatamgr/objectstore) include_directories(${MOCK_DIR}/innerkits/appverify/libhapverify/include/provision) include_directories(${MOCK_DIR}/innerkits/appverify/libhapverify/include/interfaces) include_directories(${MOCK_DIR}/innerkits/appverify/libhapverify/include/common) diff --git a/mock/innerkits/distributeddatamgr/objectstore/iobject_callback.h b/mock/innerkits/distributeddatamgr/objectstore/iobject_callback.h new file mode 100644 index 00000000..bd49a8e4 --- /dev/null +++ b/mock/innerkits/distributeddatamgr/objectstore/iobject_callback.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 I_OBJECT_CALLBACK_H +#define I_OBJECT_CALLBACK_H + +#include +#include "iremote_broker.h" +#include "iremote_proxy.h" +#include "iremote_stub.h" +#include "types.h" + +namespace OHOS { +namespace DistributedObject { +using namespace DistributedKv; +class IObjectSaveCallback : public IRemoteBroker { +public: + DECLARE_INTERFACE_DESCRIPTOR(u"OHOS.DistributedObject.IObjectSaveCallback"); + virtual void Completed(const std::map &results) = 0; +}; + +class ObjectSaveCallbackStub : public IRemoteStub { +public: + int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) override; +}; + +class ObjectSaveCallbackProxy : public IRemoteProxy { +public: + explicit ObjectSaveCallbackProxy(const sptr &impl); + ~ObjectSaveCallbackProxy() = default; + void Completed(const std::map &results) override; + +private: + static inline BrokerDelegator delegator_; +}; + +class IObjectRevokeSaveCallback : public IRemoteBroker { +public: + DECLARE_INTERFACE_DESCRIPTOR(u"OHOS.DistributedObject.IObjectRevokeSaveCallback"); + virtual void Completed(int32_t status) = 0; +}; + +class ObjectRevokeSaveCallbackStub : public IRemoteStub { +public: + int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) override; +}; + +class ObjectRevokeSaveCallbackProxy : public IRemoteProxy { +public: + explicit ObjectRevokeSaveCallbackProxy(const sptr &impl); + ~ObjectRevokeSaveCallbackProxy() = default; + void Completed(int32_t status) override; + +private: + static inline BrokerDelegator delegator_; +}; + +class IObjectRetrieveCallback : public IRemoteBroker { +public: + DECLARE_INTERFACE_DESCRIPTOR(u"OHOS.DistributedObject.IObjectRetrieveCallback"); + virtual void Completed(const std::map> &results) = 0; +}; + +class ObjectRetrieveCallbackStub : public IRemoteStub { +public: + int OnRemoteRequest(uint32_t code, MessageParcel &data, MessageParcel &reply, MessageOption &option) override; +}; + +class ObjectRetrieveCallbackProxy : public IRemoteProxy { +public: + explicit ObjectRetrieveCallbackProxy(const sptr &impl); + ~ObjectRetrieveCallbackProxy() = default; + void Completed(const std::map> &results) override; + +private: + static inline BrokerDelegator delegator_; +}; +} // namespace DistributedKv +} // namespace OHOS + +#endif // I_KVSTORE_SYNC_CALLBACK_H diff --git a/mock/innerkits/distributeddatamgr/objectstore/iobject_service.h b/mock/innerkits/distributeddatamgr/objectstore/iobject_service.h new file mode 100644 index 00000000..b31ecc8e --- /dev/null +++ b/mock/innerkits/distributeddatamgr/objectstore/iobject_service.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDATAFWK_IOBJECT_SERVICE_H +#define DISTRIBUTEDDATAFWK_IOBJECT_SERVICE_H + +#include + +#include +#include "object_service.h" + +namespace OHOS::DistributedObject { +class IObjectService : public ObjectService, public IRemoteBroker { +public: + enum { + OBJECTSTORE_SAVE, + OBJECTSTORE_REVOKE_SAVE, + OBJECTSTORE_RETRIEVE, + OBJECTSTORE_SERVICE_CMD_MAX + }; + DECLARE_INTERFACE_DESCRIPTOR(u"OHOS.DistributedObject.IObjectService"); +}; +} // namespace OHOS::DistributedRdb +#endif diff --git a/mock/innerkits/distributeddatamgr/objectstore/object_service.h b/mock/innerkits/distributeddatamgr/objectstore/object_service.h new file mode 100644 index 00000000..6dedb80f --- /dev/null +++ b/mock/innerkits/distributeddatamgr/objectstore/object_service.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTED_OBJECT_SERVICE_H +#define DISTRIBUTED_OBJECT_SERVICE_H + +#include +#include +#include + +#include "iobject_callback.h" +namespace OHOS::DistributedObject { +class ObjectService { +public: + virtual int32_t ObjectStoreSave(const std::string &bundleName, const std::string &sessionId, + const std::vector &deviceList, const std::map> &data, + sptr callback) = 0; + virtual int32_t ObjectStoreRetrieve( + const std::string &bundleName, const std::string &sessionId, sptr callback) = 0; + virtual int32_t ObjectStoreRevokeSave( + const std::string &bundleName, const std::string &sessionId, sptr callback) = 0; +}; +} // namespace OHOS::DistributedObject +#endif diff --git a/mock/innerkits/distributeddatamgr/objectstore/object_service_proxy.h b/mock/innerkits/distributeddatamgr/objectstore/object_service_proxy.h new file mode 100644 index 00000000..788884c6 --- /dev/null +++ b/mock/innerkits/distributeddatamgr/objectstore/object_service_proxy.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTED_OBJECT_SERVICE_PROXY_H +#define DISTRIBUTED_OBJECT_SERVICE_PROXY_H + +#include +#include +#include +#include "iobject_service.h" + +namespace OHOS::DistributedObject { +class ObjectServiceProxy : public IRemoteProxy { +public: + explicit ObjectServiceProxy(const sptr &impl); + ~ObjectServiceProxy() = default; + int32_t ObjectStoreSave(const std::string &bundleName, const std::string &sessionId, + const std::vector &deviceList, const std::map> &data, + sptr callback) override; + int32_t ObjectStoreRetrieve( + const std::string &bundleName, const std::string &sessionId, sptr callback) override; + int32_t ObjectStoreRevokeSave(const std::string &bundleName, const std::string &sessionId, + sptr callback) override; + +private: + static inline BrokerDelegator delegator_; +}; +} // namespace OHOS::DistributedRdb +#endif -- Gitee From 8eabeb687540a26dba01a236fa3806ca5c19c3ca Mon Sep 17 00:00:00 2001 From: hanlu Date: Wed, 10 Aug 2022 15:24:45 +0800 Subject: [PATCH 3/3] fix Signed-off-by: hanlu --- data_object/CMakeLists.txt | 26 + data_object/LICENSE | 177 +++++ data_object/README_zh.md | 80 ++ data_object/bundle.json | 87 ++ .../include/adaptor/client_adaptor.h | 33 + .../include/adaptor/distributed_object_impl.h | 47 ++ .../adaptor/distributed_objectstore_impl.h | 74 ++ .../adaptor/flat_object_storage_engine.h | 56 ++ .../include/adaptor/flat_object_store.h | 75 ++ .../include/adaptor/object_callback.h | 53 ++ .../include/adaptor/object_service.h | 36 + .../include/adaptor/object_storage_engine.h | 67 ++ .../innerkitsimpl/include/adaptor/watcher.h | 38 + .../innerkitsimpl/include/common/bytes.h | 28 + .../include/common/condition_lock.h | 61 ++ .../innerkitsimpl/include/common/logger.h | 53 ++ .../innerkitsimpl/include/common/macro.h | 26 + .../include/common/object_utils.h | 35 + .../include/common/string_utils.h | 64 ++ .../communicator/app_data_change_listener.h | 33 + .../include/communicator/app_device_handler.h | 47 ++ .../app_device_status_change_listener.h | 40 + .../include/communicator/app_pipe_handler.h | 55 ++ .../include/communicator/app_pipe_mgr.h | 59 ++ .../include/communicator/app_types.h | 70 ++ .../communicator/ark_communication_provider.h | 41 + .../communicator/communication_provider.h | 76 ++ .../communication_provider_impl.h | 74 ++ .../communicator/process_communicator_impl.h | 68 ++ .../include/communicator/softbus_adapter.h | 107 +++ .../include/communicator/visibility.h | 26 + .../src/adaptor/client_adaptor.cpp | 65 ++ .../src/adaptor/distributed_object_impl.cpp | 220 ++++++ .../adaptor/distributed_object_store_impl.cpp | 284 +++++++ .../adaptor/flat_object_storage_engine.cpp | 433 ++++++++++ .../src/adaptor/flat_object_store.cpp | 280 +++++++ .../src/adaptor/object_callback.cpp | 47 ++ .../src/communicator/app_device_handler.cpp | 73 ++ .../src/communicator/app_pipe_handler.cpp | 70 ++ .../src/communicator/app_pipe_mgr.cpp | 152 ++++ .../ark_communication_provider.cpp | 42 + .../communicator/communication_provider.cpp | 27 + .../communication_provider_impl.cpp | 92 +++ .../process_communicator_impl.cpp | 190 +++++ .../communicator/softbus_adapter_standard.cpp | 725 +++++++++++++++++ .../test/fuzztest/objectstore_fuzzer/BUILD.gn | 48 ++ .../fuzztest/objectstore_fuzzer/corpus/init | 16 + .../objectstore_fuzzer/objectstore_fuzzer.cpp | 196 +++++ .../objectstore_fuzzer/objectstore_fuzzer.h | 22 + .../fuzztest/objectstore_fuzzer/project.xml | 25 + .../innerkitsimpl/test/unittest/BUILD.gn | 42 + .../test/unittest/object_store_test.cpp | 357 +++++++++ .../jskitsimpl/include/adaptor/js_common.h | 58 ++ .../include/adaptor/js_distributedobject.h | 46 ++ .../adaptor/js_distributedobjectstore.h | 47 ++ .../include/adaptor/js_object_wrapper.h | 47 ++ .../jskitsimpl/include/adaptor/js_watcher.h | 135 ++++ .../include/adaptor/notifier_impl.h | 38 + .../jskitsimpl/include/common/js_util.h | 64 ++ .../jskitsimpl/include/common/napi_queue.h | 96 +++ .../jskitsimpl/include/common/uv_queue.h | 43 + .../src/adaptor/js_distributedobject.cpp | 366 +++++++++ .../src/adaptor/js_distributedobjectstore.cpp | 444 +++++++++++ .../jskitsimpl/src/adaptor/js_module_init.cpp | 86 ++ .../src/adaptor/js_object_wrapper.cpp | 95 +++ .../jskitsimpl/src/adaptor/js_watcher.cpp | 362 +++++++++ .../jskitsimpl/src/adaptor/notifier_impl.cpp | 74 ++ .../jskitsimpl/src/common/js_util.cpp | 158 ++++ .../jskitsimpl/src/common/napi_queue.cpp | 147 ++++ .../jskitsimpl/src/common/uv_queue.cpp | 71 ++ .../jskitsimpl/test/unittest/BUILD.gn | 23 + .../jskitsimpl/test/unittest/src/BUILD.gn | 34 + .../unittest/src/ObjectStoreJsunit.test.js | 743 ++++++++++++++++++ .../jskitsimpl/test/unittest/src/config.json | 77 ++ .../test/unittest/src/openharmony_sx.p7b | Bin 0 -> 3509 bytes data_object/interfaces/innerkits/BUILD.gn | 78 ++ .../interfaces/innerkits/distributed_object.h | 52 ++ .../innerkits/distributed_objectstore.h | 45 ++ .../interfaces/innerkits/objectstore_errors.h | 46 ++ data_object/interfaces/jskits/BUILD.gn | 132 ++++ .../jskits/distributed_data_object.js | 225 ++++++ data_object/pictures/icon-note.gif | Bin 0 -> 394 bytes .../samples/distributedNotepad/ReadMe.md | 245 ++++++ .../samples/distributedNotepad/build.gradle | 47 ++ .../distributedNotepad/entry/build.gradle | 35 + .../entry/src/main/config.json | 70 ++ .../entry/src/main/js/MainAbility/app.js | 23 + .../src/main/js/MainAbility/common/green.png | Bin 0 -> 2226 bytes .../src/main/js/MainAbility/common/red.png | Bin 0 -> 1754 bytes .../src/main/js/MainAbility/i18n/en-US.json | 8 + .../src/main/js/MainAbility/i18n/zh-CN.json | 8 + .../src/main/js/MainAbility/pages/add/add.css | 39 + .../src/main/js/MainAbility/pages/add/add.hml | 27 + .../src/main/js/MainAbility/pages/add/add.js | 42 + .../js/MainAbility/pages/detail/detail.css | 39 + .../js/MainAbility/pages/detail/detail.hml | 28 + .../js/MainAbility/pages/detail/detail.js | 52 ++ .../main/js/MainAbility/pages/index/index.css | 91 +++ .../main/js/MainAbility/pages/index/index.hml | 39 + .../main/js/MainAbility/pages/index/index.js | 81 ++ .../src/main/js/model/DistributedDataModel.js | 118 +++ .../main/resources/base/element/string.json | 12 + .../src/main/resources/base/media/icon.png | Bin 0 -> 6790 bytes ...6\346\200\201\346\217\220\347\244\272.png" | Bin 0 -> 43962 bytes ...6\346\200\201\346\217\220\347\244\272.png" | Bin 0 -> 43141 bytes ...4\347\224\250\345\261\225\347\244\272.gif" | Bin 0 -> 2135829 bytes ...7\350\241\250\345\261\225\347\244\272.png" | Bin 0 -> 42815 bytes ...6\345\244\207\347\273\204\347\275\221.gif" | Bin 0 -> 972890 bytes ...6\351\241\265\345\261\225\347\244\272.jpg" | Bin 0 -> 39743 bytes .../distributedNotepad/settings.gradle | 1 + 110 files changed, 10055 insertions(+) create mode 100644 data_object/CMakeLists.txt create mode 100644 data_object/LICENSE create mode 100644 data_object/README_zh.md create mode 100644 data_object/bundle.json create mode 100644 data_object/frameworks/innerkitsimpl/include/adaptor/client_adaptor.h create mode 100644 data_object/frameworks/innerkitsimpl/include/adaptor/distributed_object_impl.h create mode 100644 data_object/frameworks/innerkitsimpl/include/adaptor/distributed_objectstore_impl.h create mode 100644 data_object/frameworks/innerkitsimpl/include/adaptor/flat_object_storage_engine.h create mode 100644 data_object/frameworks/innerkitsimpl/include/adaptor/flat_object_store.h create mode 100644 data_object/frameworks/innerkitsimpl/include/adaptor/object_callback.h create mode 100644 data_object/frameworks/innerkitsimpl/include/adaptor/object_service.h create mode 100644 data_object/frameworks/innerkitsimpl/include/adaptor/object_storage_engine.h create mode 100644 data_object/frameworks/innerkitsimpl/include/adaptor/watcher.h create mode 100644 data_object/frameworks/innerkitsimpl/include/common/bytes.h create mode 100644 data_object/frameworks/innerkitsimpl/include/common/condition_lock.h create mode 100644 data_object/frameworks/innerkitsimpl/include/common/logger.h create mode 100644 data_object/frameworks/innerkitsimpl/include/common/macro.h create mode 100644 data_object/frameworks/innerkitsimpl/include/common/object_utils.h create mode 100644 data_object/frameworks/innerkitsimpl/include/common/string_utils.h create mode 100644 data_object/frameworks/innerkitsimpl/include/communicator/app_data_change_listener.h create mode 100644 data_object/frameworks/innerkitsimpl/include/communicator/app_device_handler.h create mode 100644 data_object/frameworks/innerkitsimpl/include/communicator/app_device_status_change_listener.h create mode 100644 data_object/frameworks/innerkitsimpl/include/communicator/app_pipe_handler.h create mode 100644 data_object/frameworks/innerkitsimpl/include/communicator/app_pipe_mgr.h create mode 100644 data_object/frameworks/innerkitsimpl/include/communicator/app_types.h create mode 100644 data_object/frameworks/innerkitsimpl/include/communicator/ark_communication_provider.h create mode 100644 data_object/frameworks/innerkitsimpl/include/communicator/communication_provider.h create mode 100644 data_object/frameworks/innerkitsimpl/include/communicator/communication_provider_impl.h create mode 100644 data_object/frameworks/innerkitsimpl/include/communicator/process_communicator_impl.h create mode 100644 data_object/frameworks/innerkitsimpl/include/communicator/softbus_adapter.h create mode 100644 data_object/frameworks/innerkitsimpl/include/communicator/visibility.h create mode 100644 data_object/frameworks/innerkitsimpl/src/adaptor/client_adaptor.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/adaptor/distributed_object_impl.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/adaptor/distributed_object_store_impl.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/adaptor/flat_object_storage_engine.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/adaptor/flat_object_store.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/adaptor/object_callback.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/communicator/app_device_handler.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/communicator/app_pipe_handler.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/communicator/app_pipe_mgr.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/communicator/ark_communication_provider.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/communicator/communication_provider.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/communicator/communication_provider_impl.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/communicator/process_communicator_impl.cpp create mode 100644 data_object/frameworks/innerkitsimpl/src/communicator/softbus_adapter_standard.cpp create mode 100644 data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/BUILD.gn create mode 100644 data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/corpus/init create mode 100644 data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/objectstore_fuzzer.cpp create mode 100644 data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/objectstore_fuzzer.h create mode 100644 data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/project.xml create mode 100644 data_object/frameworks/innerkitsimpl/test/unittest/BUILD.gn create mode 100644 data_object/frameworks/innerkitsimpl/test/unittest/object_store_test.cpp create mode 100644 data_object/frameworks/jskitsimpl/include/adaptor/js_common.h create mode 100644 data_object/frameworks/jskitsimpl/include/adaptor/js_distributedobject.h create mode 100644 data_object/frameworks/jskitsimpl/include/adaptor/js_distributedobjectstore.h create mode 100644 data_object/frameworks/jskitsimpl/include/adaptor/js_object_wrapper.h create mode 100644 data_object/frameworks/jskitsimpl/include/adaptor/js_watcher.h create mode 100644 data_object/frameworks/jskitsimpl/include/adaptor/notifier_impl.h create mode 100644 data_object/frameworks/jskitsimpl/include/common/js_util.h create mode 100644 data_object/frameworks/jskitsimpl/include/common/napi_queue.h create mode 100644 data_object/frameworks/jskitsimpl/include/common/uv_queue.h create mode 100644 data_object/frameworks/jskitsimpl/src/adaptor/js_distributedobject.cpp create mode 100644 data_object/frameworks/jskitsimpl/src/adaptor/js_distributedobjectstore.cpp create mode 100644 data_object/frameworks/jskitsimpl/src/adaptor/js_module_init.cpp create mode 100644 data_object/frameworks/jskitsimpl/src/adaptor/js_object_wrapper.cpp create mode 100644 data_object/frameworks/jskitsimpl/src/adaptor/js_watcher.cpp create mode 100644 data_object/frameworks/jskitsimpl/src/adaptor/notifier_impl.cpp create mode 100644 data_object/frameworks/jskitsimpl/src/common/js_util.cpp create mode 100644 data_object/frameworks/jskitsimpl/src/common/napi_queue.cpp create mode 100644 data_object/frameworks/jskitsimpl/src/common/uv_queue.cpp create mode 100644 data_object/frameworks/jskitsimpl/test/unittest/BUILD.gn create mode 100644 data_object/frameworks/jskitsimpl/test/unittest/src/BUILD.gn create mode 100644 data_object/frameworks/jskitsimpl/test/unittest/src/ObjectStoreJsunit.test.js create mode 100644 data_object/frameworks/jskitsimpl/test/unittest/src/config.json create mode 100644 data_object/frameworks/jskitsimpl/test/unittest/src/openharmony_sx.p7b create mode 100644 data_object/interfaces/innerkits/BUILD.gn create mode 100644 data_object/interfaces/innerkits/distributed_object.h create mode 100644 data_object/interfaces/innerkits/distributed_objectstore.h create mode 100644 data_object/interfaces/innerkits/objectstore_errors.h create mode 100644 data_object/interfaces/jskits/BUILD.gn create mode 100644 data_object/interfaces/jskits/distributed_data_object.js create mode 100644 data_object/pictures/icon-note.gif create mode 100644 data_object/samples/distributedNotepad/ReadMe.md create mode 100644 data_object/samples/distributedNotepad/build.gradle create mode 100644 data_object/samples/distributedNotepad/entry/build.gradle create mode 100644 data_object/samples/distributedNotepad/entry/src/main/config.json create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/app.js create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/common/green.png create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/common/red.png create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/i18n/en-US.json create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/i18n/zh-CN.json create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/add/add.css create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/add/add.hml create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/add/add.js create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/detail/detail.css create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/detail/detail.hml create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/detail/detail.js create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/index/index.css create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/index/index.hml create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/index/index.js create mode 100644 data_object/samples/distributedNotepad/entry/src/main/js/model/DistributedDataModel.js create mode 100644 data_object/samples/distributedNotepad/entry/src/main/resources/base/element/string.json create mode 100644 data_object/samples/distributedNotepad/entry/src/main/resources/base/media/icon.png create mode 100644 "data_object/samples/distributedNotepad/pictures/\344\270\212\347\272\277\347\212\266\346\200\201\346\217\220\347\244\272.png" create mode 100644 "data_object/samples/distributedNotepad/pictures/\344\270\213\347\272\277\347\212\266\346\200\201\346\217\220\347\244\272.png" create mode 100644 "data_object/samples/distributedNotepad/pictures/\345\272\224\347\224\250\345\261\225\347\244\272.gif" create mode 100644 "data_object/samples/distributedNotepad/pictures/\346\225\260\346\215\256\345\206\205\345\256\271\345\210\227\350\241\250\345\261\225\347\244\272.png" create mode 100644 "data_object/samples/distributedNotepad/pictures/\350\256\276\345\244\207\347\273\204\347\275\221.gif" create mode 100644 "data_object/samples/distributedNotepad/pictures/\351\246\226\351\241\265\345\261\225\347\244\272.jpg" create mode 100644 data_object/samples/distributedNotepad/settings.gradle diff --git a/data_object/CMakeLists.txt b/data_object/CMakeLists.txt new file mode 100644 index 00000000..b557cf18 --- /dev/null +++ b/data_object/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.10.2) +project(data_object) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_FLAGS "-std=c++1y -fno-rtti -fvisibility=default -D_GNU_SOURCE") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -fPIC -fpic -ffunction-sections -D_GLIBC_MOCK") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-as-needed -ldl") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=0") + +set(MOCK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../mock) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/frameworks/innerkitsimpl/src/adaptor data_object_src) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/frameworks/innerkitsimpl/src/communicator data_object_src) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/frameworks/jskitsimpl/src/adaptor data_object_src) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/frameworks/jskitsimpl/src/common data_object_src) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/frameworks/innerkitsimpl/include/adaptor) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/frameworks/innerkitsimpl/include/common) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/frameworks/innerkitsimpl/include/communicator) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/frameworks/jskitsimpl/include/adaptor ) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/frameworks/jskitsimpl/include/common ) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/interfaces/jskits/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/interfaces/innerkits/include) +include(${MOCK_DIR}/include/CMakeLists.txt OPTIONAL) + +set(links secure mock libs) +add_library(data_object SHARED ${data_object_src}) +target_link_libraries(data_object ${links}) \ No newline at end of file diff --git a/data_object/LICENSE b/data_object/LICENSE new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/data_object/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/data_object/README_zh.md b/data_object/README_zh.md new file mode 100644 index 00000000..b5e548f8 --- /dev/null +++ b/data_object/README_zh.md @@ -0,0 +1,80 @@ +# 分布式数据对象 +## 简介 +分布式数据对象管理框架是一款面向对象的内存数据管理框架,向应用开发者提供内存对象的创建、查询、删除、修改、订阅等基本数据对象的管理能力,同时具备分布式能力,满足超级终端场景下,相同应用多设备间的数据对象协同需求。 + +分布式数据对象提供JS接口,让开发者能以使用本地对象的方式使用分布式对象。分布式数据对象支持的数据类型包括数字型、字符型、布尔型等基本类型,同时也支持数组、基本类型嵌套等复杂类型。 + +## 约束 + +• 不同设备间只有相同bundleName的应用才能直接同步 + +• 不建议创建过多分布式对象,每个分布式对象将占用100-150KB内存 + +• 每个对象大小不超过500KB + +• 支持JS接口间的互通,与其他语言不互通 + +• 如对复杂类型的数据进行修改,仅支持修改根属性,暂不支持下级属性修改 + +## 目录 + +``` +//foundation/distributeddatamgr/data_object/ +├── frameworks # 框架层代码 +│ ├── innerkitsimpl # 内部接口实现 +│ │ ├── include +│ │ ├── src +│ │ └── test # C++测试用例 +│ └── jskitsimpl # JS API实现 +│ ├── include +│ ├── src +│ └── test # js测试用例 +├── interfaces # 接口代码 +│ ├── innerkits # 内部接口声明 +│ └── jskits # js接口声明 +├── picture # 资源图库 +└── samples # 开发实例 +``` + +## 接口说明 + +### 引用分布式对象头文件 + +```js +import distributedObject from '@ohos.data.distributedDataObject' +``` + +### 接口 + +| 接口名称 | 描述 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| function createDistributedObject(source: object): DistributedObject; | 创建分布式对象
source中指定分布式对象中的属性
返回值是创建出的分布式对象,接口见DistrubutedObject | +| function genSessionId(): string; | 随机创建sessionId
返回值是随机创建的sessionId | + +### DistrubutedObject + +| 接口名称 | 描述 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| setSessionId(sessionId?: string): boolean; | 设置同步的sessionId,可信组网中有多个设备时,多个设备间的对象如果设置为同一个sessionId,就能自动同步
sessionId是不同设备间组网的标识,将sessionId设置为" "或不设置均可退出组网
返回值是操作结果,true表示设置sessionId成功 | +| on(type: 'change', callback: Callback<{ sessionId: string, fields: Array }>): void; | 监听对象数据的变更
type固定为'change'
callback是变更时触发的回调,回调参数sessionId标识变更对象的sessionId,fields标识对象变更的属性名 | +| off(type: 'change', callback?: Callback<{ sessionId: string, fields: Array } | 删除数据对象变更的监听
type固定为'change'
callback为可选参数,不设置表示删除该对象所有变更监听 | +| on(type: 'status', callback: Callback<{ sessionId: string, networkId: string, status: 'online' \| 'offline' }>): void | 监听数据对象上下线的变更
type固定为'status'
callback是变更时触发的回调,回调参数sessionId标识变更对象的sessionId,networkId标识对象设备的networkId,status标识对象为'online'(上线)或'offline'(下线)的状态 | +| off(type: 'status', callback?: Callback<{ sessionId: string, deviceId: string, status: 'online' \| 'offline' }>): void | 删除数据对象上下线变更的监听
type固定为'change'
callback为可选参数,不设置表示删除该数据对象所有上下线监听 | +| save(deviceId: string, callback: AsyncCallback<SaveSuccessResponse>): void | 持久化保存数据对象
deviceId为设备Id,用户自定义。例如"local"。
| +| revokeSave(callback: AsyncCallback): void | 撤回已保存的数据对象。
type固定为'change'
callback为撤回已保存的数据对象回调 | + + + +## 开发实例 + +针对分布式数据对象,有以下开发实例可供参考: + +[备忘录应用](https://gitee.com/openharmony/distributeddatamgr_objectstore/tree/master/samples/distributedNotepad) + +分布式数据对象在备忘录应用中,通过分布式数据对象框架,当用户在某一端设备上新增备忘录事件,修改编辑事件标题和内容以及清空事件列表时,产生的数据变更结果均可以同步刷新显现在可信组网内其他设备上。 + +## 相关仓 +- [分布式数据对象开发指导](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/database/database-distributedobject-guidelines.md) +- [分布式数据对象API文档](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-data-distributedobject.md) +- [distributeddatamgr\_datamgr](https://gitee.com/openharmony/distributeddatamgr_datamgr) +- [third\_party\_sqlite](https://gitee.com/openharmony/third_party_sqlite) diff --git a/data_object/bundle.json b/data_object/bundle.json new file mode 100644 index 00000000..f0598a92 --- /dev/null +++ b/data_object/bundle.json @@ -0,0 +1,87 @@ +{ + "name": "@ohos/distributeddatamgr_data_object", + "version": "", + "description": "The distributed data object management framework is an object-oriented in-memory data management framework", + "homePage": "https://gitee.com/openharmony", + "license": "Apache V2", + "repository": "https://gitee.com/openharmony/distributeddatamgr_data_object ", + "domain": "os", + "language": "", + "publishAs": "code-segment", + "private": false, + "scripts": {}, + "tags": [ + "foundation" + ], + "envs": [], + "dirs": [], + "author": { + "name": "", + "email": "", + "url": "" + }, + "contributors": [ + { + "name": "", + "email": "", + "url": "" + } + ], + "segment": { + "destPath": "foundation/distributeddatamgr/data_object" + }, + "component": { + "name": "data_object", + "subsystem": "distributeddatamgr", + "syscap": [ + "SystemCapability.DistributedDataManager.DataObject.DistributedObject" + ], + "features": [], + "adapted_system_type": [ + "standard" + ], + "rom": "", + "ram": "", + "deps": { + "components": [ + "ability_base", + "ability_runtime", + "hitrace_native", + "dsoftbus", + "distributeddatamgr", + "napi", + "common", + "samgr", + "ipc", + "hiviewdfx_hilog_native", + "libuv", + "utils_base", + "access_token" + ], + "third_party": [] + }, + "build": { + "sub_component": [ + "//foundation/distributeddatamgr/data_object/interfaces/jskits:build_module" + ], + "inner_kits": [ + { + "name": "//foundation/distributeddatamgr/data_object/interfaces/innerkits:distributeddataobject_impl", + "header": { + "header_files": [ + "distributed_object.h", + "distributed_objectstore.h", + "objectstore_errors.h" + ], + "header_base": "//foundation/distributeddatamgr/data_object/interfaces/innerkits" + } + } + ], + "test": [ + "//foundation/distributeddatamgr/data_object/frameworks/innerkitsimpl/test/unittest:unittest", + "//foundation/distributeddatamgr/data_object/frameworks/jskitsimpl/test/unittest:unittest", + "//foundation/distributeddatamgr/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer:fuzztest" + ] + } + } +} \ No newline at end of file diff --git a/data_object/frameworks/innerkitsimpl/include/adaptor/client_adaptor.h b/data_object/frameworks/innerkitsimpl/include/adaptor/client_adaptor.h new file mode 100644 index 00000000..86a331d0 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/adaptor/client_adaptor.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 OBJECT_CLIENT_ADAPTOR_H +#define OBJECT_CLIENT_ADAPTOR_H + +#include "object_service_proxy.h" +#include "ikvstore_data_service.h" +namespace OHOS::ObjectStore { +class ClientAdaptor { +public: + static sptr GetObjectService(); +private: + static constexpr int32_t DISTRIBUTED_KV_DATA_SERVICE_ABILITY_ID = 1301; + static constexpr int32_t GET_SA_RETRY_TIMES = 3; + static constexpr int32_t RETRY_INTERVAL = 1; + static sptr GetDistributedDataManager(); +}; +} // namespace OHOS::ObjectStore + +#endif // OBJECT_CLIENT_ADAPTOR_H diff --git a/data_object/frameworks/innerkitsimpl/include/adaptor/distributed_object_impl.h b/data_object/frameworks/innerkitsimpl/include/adaptor/distributed_object_impl.h new file mode 100644 index 00000000..ad213765 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/adaptor/distributed_object_impl.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTED_OBJECT_IMPL_H +#define DISTRIBUTED_OBJECT_IMPL_H +#include + +#include "distributed_object.h" +#include "flat_object_store.h" + +namespace OHOS::ObjectStore { +class DistributedObjectImpl : public DistributedObject { +public: + DistributedObjectImpl(const std::string &sessionId, FlatObjectStore *flatObjectStore); + ~DistributedObjectImpl(); + uint32_t PutDouble(const std::string &key, double value) override; + uint32_t PutBoolean(const std::string &key, bool value) override; + uint32_t PutString(const std::string &key, const std::string &value) override; + uint32_t GetDouble(const std::string &key, double &value) override; + uint32_t GetBoolean(const std::string &key, bool &value) override; + uint32_t GetString(const std::string &key, std::string &value) override; + uint32_t PutComplex(const std::string &key, const std::vector &value) override; + uint32_t GetComplex(const std::string &key, std::vector &value) override; + std::string &GetSessionId() override; + uint32_t Save(const std::string &deviceId) override; + uint32_t RevokeSave() override; + uint32_t GetType(const std::string &key, Type &type) override; + +private: + std::string sessionId_; + FlatObjectStore *flatObjectStore_ = nullptr; +}; +} // namespace OHOS::ObjectStore + +#endif // DISTRIBUTED_OBJECT_IMPL_H diff --git a/data_object/frameworks/innerkitsimpl/include/adaptor/distributed_objectstore_impl.h b/data_object/frameworks/innerkitsimpl/include/adaptor/distributed_objectstore_impl.h new file mode 100644 index 00000000..d9e99452 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/adaptor/distributed_objectstore_impl.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTED_OBJECTSTORE_IMPL_H +#define DISTRIBUTED_OBJECTSTORE_IMPL_H + +#include + +#include + +#include "distributed_objectstore.h" + +namespace OHOS::ObjectStore { +class WatcherProxy; +enum SyncStatus { + SYNC_START, + SYNCING, + SYNC_SUCCESS, + SYNC_FAIL, +}; +class DistributedObjectStoreImpl : public DistributedObjectStore { +public: + DistributedObjectStoreImpl(FlatObjectStore *flatObjectStore); + ~DistributedObjectStoreImpl() override; + uint32_t Get(const std::string &sessionId, DistributedObject **object) override; + DistributedObject *CreateObject(const std::string &sessionId) override; + uint32_t DeleteObject(const std::string &sessionId) override; + uint32_t Watch(DistributedObject *object, std::shared_ptr watcher) override; + uint32_t UnWatch(DistributedObject *object) override; + uint32_t SetStatusNotifier(std::shared_ptr notifier) override; + void TriggerSync() override; + void TriggerRestore(std::function notifier) override; + +private: + DistributedObject *CacheObject(const std::string &sessionId, FlatObjectStore *flatObjectStore); + void RemoveCacheObject(const std::string &sessionId); + FlatObjectStore *flatObjectStore_ = nullptr; + std::map> watchers_; + std::shared_mutex dataMutex_{}; + std::vector objects_{}; +}; +class StatusNotifierProxy : public StatusWatcher { +public: + virtual ~StatusNotifierProxy(); + StatusNotifierProxy(const std::shared_ptr ¬ifier); + void OnChanged( + const std::string &sessionId, const std::string &networkId, const std::string &onlineStatus) override; + +private: + std::shared_ptr notifier; +}; +class WatcherProxy : public FlatObjectWatcher { +public: + WatcherProxy(const std::shared_ptr objectWatcher, const std::string &sessionId); + void OnChanged(const std::string &sessionid, const std::vector &changedData) override; + +private: + std::shared_ptr objectWatcher_; +}; +} // namespace OHOS::ObjectStore + +#endif // DISTRIBUTED_OBJECTSTORE_H diff --git a/data_object/frameworks/innerkitsimpl/include/adaptor/flat_object_storage_engine.h b/data_object/frameworks/innerkitsimpl/include/adaptor/flat_object_storage_engine.h new file mode 100644 index 00000000..17e033c9 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/adaptor/flat_object_storage_engine.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 FLAT_OBJECT_STORAGE_ENGINE_H +#define FLAT_OBJECT_STORAGE_ENGINE_H + +#include +#include +#include +#include + +#include "kv_store_delegate_manager.h" +#include "object_storage_engine.h" + +namespace OHOS::ObjectStore { +class FlatObjectStorageEngine : public ObjectStorageEngine { +public: + FlatObjectStorageEngine() = default; + ~FlatObjectStorageEngine() override; + uint32_t Open(const std::string &bundleName) override; + uint32_t Close() override; + uint32_t DeleteTable(const std::string &key) override; + uint32_t CreateTable(const std::string &key) override; + uint32_t GetTable(const std::string &key, std::map &result) override; + uint32_t UpdateItem(const std::string &key, const std::string &itemKey, Value &value) override; + uint32_t UpdateItems(const std::string &key, const std::map> &data) override; + uint32_t GetItem(const std::string &key, const std::string &itemKey, Value &value) override; + uint32_t GetItems(const std::string &key, std::map> &data) override; + uint32_t RegisterObserver(const std::string &key, std::shared_ptr watcher) override; + uint32_t UnRegisterObserver(const std::string &key) override; + uint32_t SetStatusNotifier(std::shared_ptr watcher) override; + uint32_t SyncAllData(const std::string &sessionId, const std::vector &deviceIds, + const std::function &)> &onComplete); + bool isOpened_ = false; + +private: + std::mutex operationMutex_{}; + std::shared_ptr storeManager_; + std::map delegates_; + std::map> observerMap_; + std::shared_ptr statusWatcher_ = nullptr; +}; +} // namespace OHOS::ObjectStore +#endif diff --git a/data_object/frameworks/innerkitsimpl/include/adaptor/flat_object_store.h b/data_object/frameworks/innerkitsimpl/include/adaptor/flat_object_store.h new file mode 100644 index 00000000..794f2617 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/adaptor/flat_object_store.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 FLAT_OBJECT_STORE_H +#define FLAT_OBJECT_STORE_H + +#include +#include + +#include "bytes.h" +#include "flat_object_storage_engine.h" +#include "condition_lock.h" + +namespace OHOS::ObjectStore { +class FlatObjectWatcher : public TableWatcher { +public: + FlatObjectWatcher(const std::string &sessionId) : TableWatcher(sessionId) + { + } + void OnChanged(const std::string &sessionid, const std::vector &changedData) override; +}; + +class CacheManager { +public: + CacheManager(); + uint32_t Save(const std::string &bundleName, const std::string &sessionId, const std::string &deviceId, + const std::map> &objectData); + uint32_t RevokeSave(const std::string &bundleName, const std::string &sessionId); + int32_t ResumeObject(const std::string &bundleName, const std::string &sessionId, + std::function> &data)> &callback); +private: + int32_t SaveObject(const std::string &bundleName, const std::string &sessionId, + const std::string &deviceId, const std::map> &objectData, + const std::function &)> &callback); + int32_t RevokeSaveObject( + const std::string &bundleName, const std::string &sessionId, std::function &callback); + std::mutex mutex_; +}; + +class FlatObjectStore { +public: + explicit FlatObjectStore(const std::string &bundleName); + ~FlatObjectStore(); + uint32_t CreateObject(const std::string &sessionId); + uint32_t Delete(const std::string &objectId); + uint32_t Watch(const std::string &objectId, std::shared_ptr watcher); + uint32_t UnWatch(const std::string &objectId); + uint32_t Put(const std::string &sessionId, const std::string &key, std::vector value); + uint32_t Get(std::string &sessionId, const std::string &key, Bytes &value); + uint32_t SetStatusNotifier(std::shared_ptr sharedPtr); + uint32_t SyncAllData(const std::string &sessionId, + const std::function &)> &onComplete); + uint32_t Save(const std::string &sessionId, const std::string &deviceId); + uint32_t RevokeSave(const std::string &sessionId); + +private: + std::shared_ptr storageEngine_; + CacheManager *cacheManager_; + std::string bundleName_; +}; +} // namespace OHOS::ObjectStore + +#endif // FLAT_OBJECT_STORE_H diff --git a/data_object/frameworks/innerkitsimpl/include/adaptor/object_callback.h b/data_object/frameworks/innerkitsimpl/include/adaptor/object_callback.h new file mode 100644 index 00000000..25918118 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/adaptor/object_callback.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 OBJECT_CALLBACK_H +#define OBJECT_CALLBACK_H + +#include "iobject_callback.h" + +namespace OHOS { +namespace ObjectStore { +using namespace DistributedObject; +class ObjectSaveCallback : public ObjectSaveCallbackStub { +public: + ObjectSaveCallback(const std::function &)> &callback); + void Completed(const std::map &results) override; + +private: + const std::function &)> callback_; +}; + +class ObjectRevokeSaveCallback : public ObjectRevokeSaveCallbackStub { +public: + ObjectRevokeSaveCallback(const std::function &callback); + void Completed(int32_t) override; + +private: + const std::function callback_; +}; + +class ObjectRetrieveCallback : public ObjectRetrieveCallbackStub { +public: + ObjectRetrieveCallback(const std::function> &)> &callback); + void Completed(const std::map> &results) override; + +private: + const std::function> &)> callback_; +}; +} // namespace DistributedKv +} // namespace OHOS + +#endif // OBJECT_CALLBACK_H diff --git a/data_object/frameworks/innerkitsimpl/include/adaptor/object_service.h b/data_object/frameworks/innerkitsimpl/include/adaptor/object_service.h new file mode 100644 index 00000000..bc2b54a6 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/adaptor/object_service.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTED_OBJECT_SERVICE_H +#define DISTRIBUTED_OBJECT_SERVICE_H + +#include +#include +#include + +#include "iobject_callback.h" +namespace OHOS::ObjectStore { +class ObjectService { +public: + virtual int32_t ObjectStoreSave(const std::string &bundleName, const std::string &sessionId, + const std::string &deviceId, const std::map> &data, + sptr callback) = 0; + virtual int32_t ObjectStoreRetrieve( + const std::string &bundleName, const std::string &sessionId, sptr callback) = 0; + virtual int32_t ObjectStoreRevokeSave( + const std::string &bundleName, const std::string &sessionId, sptr callback) = 0; +}; +} // namespace OHOS::ObjectStore +#endif diff --git a/data_object/frameworks/innerkitsimpl/include/adaptor/object_storage_engine.h b/data_object/frameworks/innerkitsimpl/include/adaptor/object_storage_engine.h new file mode 100644 index 00000000..74932e59 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/adaptor/object_storage_engine.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 OBJECT_STORAGE_ENGINE_H +#define OBJECT_STORAGE_ENGINE_H + +#include +#include +#include + +#include "kv_store_observer.h" +#include "watcher.h" + +namespace OHOS::ObjectStore { +using Key = std::vector; +using Value = std::vector; +using Field = std::vector; + +class TableWatcher : public Watcher { +public: + TableWatcher(const std::string &sessionId) : Watcher(sessionId) + { + } + void OnChanged(const std::string &sessionid, const std::vector &changedData) override; +}; + +class StatusWatcher { +public: + virtual void OnChanged( + const std::string &sessionId, const std::string &networkId, const std::string &onlineStatus) = 0; +}; + +class ObjectStorageEngine { +public: + ObjectStorageEngine(const ObjectStorageEngine &) = delete; + ObjectStorageEngine &operator=(const ObjectStorageEngine &) = delete; + ObjectStorageEngine(ObjectStorageEngine &&) = delete; + ObjectStorageEngine &operator=(ObjectStorageEngine &&) = delete; + ObjectStorageEngine() = default; + virtual ~ObjectStorageEngine() = default; + virtual uint32_t Open(const std::string &bundleName) = 0; + virtual uint32_t Close() = 0; + virtual uint32_t DeleteTable(const std::string &key) = 0; + virtual uint32_t CreateTable(const std::string &key) = 0; + virtual uint32_t GetTable(const std::string &key, std::map &result) = 0; + virtual uint32_t UpdateItem(const std::string &key, const std::string &itemKey, Value &value) = 0; + virtual uint32_t UpdateItems(const std::string &key, const std::map> &data) = 0; + virtual uint32_t GetItem(const std::string &key, const std::string &itemKey, Value &value) = 0; + virtual uint32_t GetItems(const std::string &key, std::map> &data) = 0; + virtual uint32_t RegisterObserver(const std::string &key, std::shared_ptr watcher) = 0; + virtual uint32_t UnRegisterObserver(const std::string &key) = 0; + virtual uint32_t SetStatusNotifier(std::shared_ptr watcher) = 0; +}; +} // namespace OHOS::ObjectStore +#endif \ No newline at end of file diff --git a/data_object/frameworks/innerkitsimpl/include/adaptor/watcher.h b/data_object/frameworks/innerkitsimpl/include/adaptor/watcher.h new file mode 100644 index 00000000..cd3ad646 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/adaptor/watcher.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 WATCHER_H +#define WATCHER_H + +#include + +#include "kv_store_delegate_manager.h" +#include "logger.h" + +namespace OHOS::ObjectStore { +class Watcher : public DistributedDB::KvStoreObserver { +public: + Watcher(const std::string &sessionId); + virtual ~Watcher() = default; + virtual void OnChanged(const std::string &sessionid, const std::vector &changedData) = 0; + + void OnChange(const DistributedDB::KvStoreChangedData &data) override; + +private: + std::string sessionId_; +}; +} // namespace OHOS::ObjectStore + +#endif // WATCHER_H \ No newline at end of file diff --git a/data_object/frameworks/innerkitsimpl/include/common/bytes.h b/data_object/frameworks/innerkitsimpl/include/common/bytes.h new file mode 100644 index 00000000..11f94903 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/common/bytes.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 BYTES_H +#define BYTES_H + +#include +#include + +namespace OHOS::ObjectStore { +using Bytes = std::vector; +static const char *FIELDS_PREFIX = "p_"; +static const int32_t FIELDS_PREFIX_LEN = 2; +} // namespace OHOS::ObjectStore + +#endif // BYTES_H diff --git a/data_object/frameworks/innerkitsimpl/include/common/condition_lock.h b/data_object/frameworks/innerkitsimpl/include/common/condition_lock.h new file mode 100644 index 00000000..16d720c4 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/common/condition_lock.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 CONDITION_LOCK_H +#define CONDITION_LOCK_H + +#include +#include + +namespace OHOS::ObjectStore { +template +class ConditionLock { +public: + explicit ConditionLock() {} + ~ConditionLock() {} +public: + void Notify(const T &data) + { + std::lock_guard lock(mutex_); + data_ = data; + isSet_ = true; + cv_.notify_one(); + } + + T Wait() + { + std::unique_lock lock(mutex_); + cv_.wait(lock, [this]() { return isSet_; }); + T data = data_; + cv_.notify_one(); + return data; + } + + void Clear() + { + std::lock_guard lock(mutex_); + isSet_ = false; + cv_.notify_one(); + } + +private: + bool isSet_ = false; + T data_; + std::mutex mutex_; + std::condition_variable cv_; +}; +} // namespace OHOS::ObjectStore + +#endif // CONDITION_LOCK_H diff --git a/data_object/frameworks/innerkitsimpl/include/common/logger.h b/data_object/frameworks/innerkitsimpl/include/common/logger.h new file mode 100644 index 00000000..20b4ade4 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/common/logger.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 OBJECT_STORE_LOGGER_H +#define OBJECT_STORE_LOGGER_H +#include +#ifdef HILOG_ENABLE +#include "hilog/log.h" +namespace OHOS::ObjectStore { +static const OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, 0xD001652, "ObjectStore-x" }; + +#define LOG_DEBUG(fmt, ...) \ + ((void)OHOS::HiviewDFX::HiLog::Debug( \ + LABEL, "%{public}d: %{public}s " fmt " ", __LINE__, __FUNCTION__, ##__VA_ARGS__)) +#define LOG_INFO(fmt, ...) \ + ((void)OHOS::HiviewDFX::HiLog::Info( \ + LABEL, "%{public}d: %{public}s " fmt " ", __LINE__, __FUNCTION__, ##__VA_ARGS__)) +#define LOG_WARN(fmt, ...) \ + ((void)OHOS::HiviewDFX::HiLog::Warn( \ + LABEL, "%{public}d: %{public}s " fmt " ", __LINE__, __FUNCTION__, ##__VA_ARGS__)) +#define LOG_ERROR(fmt, ...) \ + ((void)OHOS::HiviewDFX::HiLog::Error( \ + LABEL, "%{public}d: %{public}s " fmt " ", __LINE__, __FUNCTION__, ##__VA_ARGS__)) +#define LOG_FATAL(fmt, ...) \ + ((void)OHOS::HiviewDFX::HiLog::Fatal( \ + LABEL, "%{public}d: %{public}s " fmt " ", __LINE__, __FUNCTION__, ##__VA_ARGS__)) +} // namespace OHOS::ObjectStore +#else +#include +#include + +#define LOG_DEBUG(fmt, ...) \ + printf("[D][ObjectStore]%s:%d %s: " fmt "\n", __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__) +#define LOG_ERROR(fmt, ...) \ + printf("[E][ObjectStore]%s:%d %s: " fmt "\n", __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__) +#define LOG_INFO(fmt, ...) \ + printf("[I][ObjectStore]%s:%d %s: " fmt "\n", __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__) +#define LOG_WARN(fmt, ...) \ + printf("[W][ObjectStore]%s:%d %s: " fmt "\n", __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__) +#endif // #ifdef HILOG_ENABLE +#endif // OBJECT_STORE_LOGGER_H diff --git a/data_object/frameworks/innerkitsimpl/include/common/macro.h b/data_object/frameworks/innerkitsimpl/include/common/macro.h new file mode 100644 index 00000000..0939b0d0 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/common/macro.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 MACRO_H +#define MACRO_H + +namespace OHOS::ObjectStore { +#define DISABLE_COPY_AND_MOVE(ClassName) \ + ClassName(const ClassName &) = delete; \ + ClassName(ClassName &&) = delete; \ + ClassName &operator=(const ClassName &) = delete; \ + ClassName &operator=(ClassName &&) = delete +} // namespace OHOS::ObjectStore +#endif // MACRO_H \ No newline at end of file diff --git a/data_object/frameworks/innerkitsimpl/include/common/object_utils.h b/data_object/frameworks/innerkitsimpl/include/common/object_utils.h new file mode 100644 index 00000000..f1137298 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/common/object_utils.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 OBJECT_UTILS_H +#define OBJECT_UTILS_H + +#include + +namespace OHOS::ObjectStore { +class ObjectUtils final { +public: + ObjectUtils() = delete; + ~ObjectUtils() = delete; + + static std::string GenObjectIdPrefix( + const std::string &host, const std::string &user, const std::string &bundle, const std::string &store); + + static std::string GetObjectHost(const std::string &objectId); + + static std::string GetObjectStoreName(const std::string &objectId); +}; +} // namespace OHOS::ObjectStore + +#endif \ No newline at end of file diff --git a/data_object/frameworks/innerkitsimpl/include/common/string_utils.h b/data_object/frameworks/innerkitsimpl/include/common/string_utils.h new file mode 100644 index 00000000..de81a4fe --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/common/string_utils.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 STRING_UTILS_H +#define STRING_UTILS_H + +#include +#include +#include +#include + +#include "bytes.h" +#include "distributed_object.h" +#include "logger.h" +#include "objectstore_errors.h" + +namespace OHOS::ObjectStore { +class StringUtils final { +public: + StringUtils() = delete; + ~StringUtils() = delete; + static std::vector StrToBytes(const std::string &src) + { + std::vector dst; + dst.resize(src.size()); + dst.assign(src.begin(), src.end()); + return dst; + } + + static std::string BytesToStr(const std::vector src) + { + std::string result; + Bytes rstStr(src.begin(), src.end()); + result.assign(reinterpret_cast(rstStr.data()), rstStr.size()); + return result; + } + static uint32_t BytesToStrWithType(Bytes input, std::string &str) + { + uint32_t len = input.end() - input.begin(); + if (len <= sizeof(Type)) { + LOG_ERROR("StringUtils:BytesToStrWithType get input len err."); + return ERR_DATA_LEN; + } + std::vector::const_iterator first = input.begin() + sizeof(Type); + std::vector::const_iterator end = input.end(); + Bytes rstStr(first, end); + str.assign(reinterpret_cast(rstStr.data()), rstStr.size()); + return SUCCESS; + } +}; +} // namespace OHOS::ObjectStore +#endif // STRING_UTILS_H diff --git a/data_object/frameworks/innerkitsimpl/include/communicator/app_data_change_listener.h b/data_object/frameworks/innerkitsimpl/include/communicator/app_data_change_listener.h new file mode 100644 index 00000000..8a49723a --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/communicator/app_data_change_listener.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 APP_DISTRIBUTEDDATA_INCLUDE_DATA_CHANGE_LISTENER_H +#define APP_DISTRIBUTEDDATA_INCLUDE_DATA_CHANGE_LISTENER_H + +#include "app_types.h" +#include "visibility.h" +namespace OHOS { +namespace ObjectStore { +class AppDataChangeListener { +public: + KVSTORE_API AppDataChangeListener() = default; + KVSTORE_API virtual ~AppDataChangeListener(){}; + + KVSTORE_API virtual void OnMessage( + const DeviceInfo &info, const uint8_t *ptr, const int size, const PipeInfo &pipeInfo) const = 0; +}; +} // namespace ObjectStore +} // namespace OHOS +#endif // APP_DISTRIBUTEDDATA_INCLUDE_DATA_CHANGE_LISTENER_H diff --git a/data_object/frameworks/innerkitsimpl/include/communicator/app_device_handler.h b/data_object/frameworks/innerkitsimpl/include/communicator/app_device_handler.h new file mode 100644 index 00000000..99c72dae --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/communicator/app_device_handler.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDATAFWK_SRC_DEVICE_HANDLER_H +#define DISTRIBUTEDDATAFWK_SRC_DEVICE_HANDLER_H +#include "softbus_adapter.h" + +namespace OHOS { +namespace ObjectStore { +class AppDeviceHandler { +public: + ~AppDeviceHandler(); + explicit AppDeviceHandler(); + void Init(); + + // add DeviceChangeListener to watch device change; + Status StartWatchDeviceChange(const AppDeviceStatusChangeListener *observer, const PipeInfo &pipeInfo); + // stop DeviceChangeListener to watch device change; + Status StopWatchDeviceChange(const AppDeviceStatusChangeListener *observer, const PipeInfo &pipeInfo); + + DeviceInfo GetLocalDevice(); + std::vector GetDeviceList() const; + + std::string GetUdidByNodeId(const std::string &nodeId) const; + // get local device node information; + DeviceInfo GetLocalBasicInfo() const; + // get all remote connected device's node information; + std::vector GetRemoteNodesBasicInfo() const; + +private: + std::shared_ptr softbusAdapter_{}; +}; +} // namespace ObjectStore +} // namespace OHOS +#endif // DISTRIBUTEDDATAFWK_SRC_DEVICE_HANDLER_H diff --git a/data_object/frameworks/innerkitsimpl/include/communicator/app_device_status_change_listener.h b/data_object/frameworks/innerkitsimpl/include/communicator/app_device_status_change_listener.h new file mode 100644 index 00000000..257a5e4f --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/communicator/app_device_status_change_listener.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 APP_DEVICE_STATUS_CHANGE_LISTENER_H +#define APP_DEVICE_STATUS_CHANGE_LISTENER_H + +#include "app_types.h" + +namespace OHOS { +namespace ObjectStore { +enum class ChangeLevelType { + HIGH, + LOW, + MIN, +}; +class AppDeviceStatusChangeListener { +public: + KVSTORE_API virtual ~AppDeviceStatusChangeListener(){}; + KVSTORE_API virtual void OnDeviceChanged(const DeviceInfo &info, const DeviceChangeType &type) const = 0; + KVSTORE_API virtual ChangeLevelType GetChangeLevelType() const + { + return ChangeLevelType::LOW; + } +}; +} // namespace ObjectStore +} // namespace OHOS + +#endif // APP_DEVICE_STATUS_CHANGE_LISTENER_H diff --git a/data_object/frameworks/innerkitsimpl/include/communicator/app_pipe_handler.h b/data_object/frameworks/innerkitsimpl/include/communicator/app_pipe_handler.h new file mode 100644 index 00000000..f7e43909 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/communicator/app_pipe_handler.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDATAFWK_SRC_PIPE_HANDLER_H +#define DISTRIBUTEDDATAFWK_SRC_PIPE_HANDLER_H + +#include +#include +#include +#include + +#include "app_data_change_listener.h" +#include "app_types.h" +#include "logger.h" +#include "softbus_adapter.h" + +namespace OHOS { +namespace ObjectStore { +class AppPipeHandler { +public: + ~AppPipeHandler(); + explicit AppPipeHandler(const PipeInfo &pipeInfo); + + // add DataChangeListener to watch data change; + Status StartWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo); + // stop DataChangeListener to watch data change; + Status StopWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo); + // Send data to other device, function will be called back after sent to notify send result. + Status SendData( + const PipeInfo &pipeInfo, const DeviceId &deviceId, const uint8_t *ptr, int size, const MessageInfo &info); + bool IsSameStartedOnPeer(const struct PipeInfo &pipeInfo, const struct DeviceId &peer); + + int CreateSessionServer(const std::string &sessionName) const; + + int RemoveSessionServer(const std::string &sessionName) const; + +private: + PipeInfo pipeInfo_; + std::shared_ptr softbusAdapter_{}; +}; +} // namespace ObjectStore +} // namespace OHOS +#endif /* DISTRIBUTEDDATAFWK_SRC_PIPE_HANDLER_H */ diff --git a/data_object/frameworks/innerkitsimpl/include/communicator/app_pipe_mgr.h b/data_object/frameworks/innerkitsimpl/include/communicator/app_pipe_mgr.h new file mode 100644 index 00000000..493e7075 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/communicator/app_pipe_mgr.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDATAMGR_APP_PIPE_MGR_H +#define DISTRIBUTEDDATAMGR_APP_PIPE_MGR_H + +#include +#include + +#include "app_data_change_listener.h" +#include "app_pipe_handler.h" +#include "app_types.h" +#include "logger.h" + +namespace OHOS { +namespace ObjectStore { +class AppPipeMgr { +public: + explicit AppPipeMgr() + { + } + ~AppPipeMgr() + { + } + // add DataChangeListener to watch data change; + Status StartWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo); + + // stop DataChangeListener to watch data change; + Status StopWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo); + + // Send data to other device, function will be called back after sent to notify send result. + Status SendData( + const PipeInfo &pipeInfo, const DeviceId &deviceId, const uint8_t *ptr, int size, const MessageInfo &info); + // start server + Status Start(const PipeInfo &pipeInfo); + // stop server + Status Stop(const PipeInfo &pipeInfo); + + bool IsSameStartedOnPeer(const struct PipeInfo &pipeInfo, const struct DeviceId &peer); + +private: + std::mutex dataBusMapMutex_{}; + std::map> dataBusMap_{}; +}; +} // namespace ObjectStore +} // namespace OHOS +#endif // DISTRIBUTEDDATAMGR_APP_PIPE_MGR_H diff --git a/data_object/frameworks/innerkitsimpl/include/communicator/app_types.h b/data_object/frameworks/innerkitsimpl/include/communicator/app_types.h new file mode 100644 index 00000000..f1c1267a --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/communicator/app_types.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 APP_DISTRIBUTED_KVSTORE_APP_TYPES_H +#define APP_DISTRIBUTED_KVSTORE_APP_TYPES_H + +#include + +#include +#include +#include + +#include "visibility.h" + +namespace OHOS { +namespace ObjectStore { +struct PipeInfo { + std::string pipeId; +}; + +struct DeviceInfo { + std::string deviceId; + std::string deviceName; + std::string deviceType; +}; + +enum class MessageType { + DEFAULT = 0, +}; + +struct MessageInfo { + MessageType msgType; +}; + +enum class DeviceChangeType { + DEVICE_OFFLINE = 0, + DEVICE_ONLINE = 1, +}; + +struct DeviceId { + std::string deviceId; +}; + +// app_distributed_data_manager using sub error code 0 +constexpr ErrCode APP_DISTRIBUTEDDATAMGR_ERR_OFFSET = ErrCodeOffset(SUBSYS_DISTRIBUTEDDATAMNG, 0); + +enum class Status { + SUCCESS = ERR_OK, + ERROR = APP_DISTRIBUTEDDATAMGR_ERR_OFFSET, + INVALID_ARGUMENT = APP_DISTRIBUTEDDATAMGR_ERR_OFFSET + 1, + ILLEGAL_STATE = APP_DISTRIBUTEDDATAMGR_ERR_OFFSET + 2, + KEY_NOT_FOUND = APP_DISTRIBUTEDDATAMGR_ERR_OFFSET + 7, + REPEATED_REGISTER = APP_DISTRIBUTEDDATAMGR_ERR_OFFSET + 14, + CREATE_SESSION_ERROR = APP_DISTRIBUTEDDATAMGR_ERR_OFFSET + 15, +}; +} // namespace ObjectStore +} // namespace OHOS +#endif // APP_DISTRIBUTED_KVSTORE_TYPES_H diff --git a/data_object/frameworks/innerkitsimpl/include/communicator/ark_communication_provider.h b/data_object/frameworks/innerkitsimpl/include/communicator/ark_communication_provider.h new file mode 100644 index 00000000..a6189dde --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/communicator/ark_communication_provider.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDATAFWK_ARK_COMMUNICATION_PROVIDER_H +#define DISTRIBUTEDDATAFWK_ARK_COMMUNICATION_PROVIDER_H + +#include "app_device_handler.h" +#include "app_pipe_mgr.h" +#include "communication_provider_impl.h" +#include "nocopyable.h" + +namespace OHOS { +namespace ObjectStore { +class ArkCommunicationProvider : public CommunicationProviderImpl { +public: + static CommunicationProvider &Init(); + + ~ArkCommunicationProvider() override{}; + +private: + DISALLOW_COPY_AND_MOVE(ArkCommunicationProvider); + ArkCommunicationProvider(); + AppPipeMgr appPipeMgrImpl_{}; + AppDeviceHandler appDeviceHandlerImpl_{}; + bool isInited = false; +}; +} // namespace ObjectStore +} // namespace OHOS +#endif // DISTRIBUTEDDATAFWK_ARK_COMMUNICATION_PROVIDER_H diff --git a/data_object/frameworks/innerkitsimpl/include/communicator/communication_provider.h b/data_object/frameworks/innerkitsimpl/include/communicator/communication_provider.h new file mode 100644 index 00000000..70fb9b96 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/communicator/communication_provider.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDATA_COMMUNICATION_PROVIDER_H +#define DISTRIBUTEDDATA_COMMUNICATION_PROVIDER_H + +#include +#include + +#include "app_data_change_listener.h" +#include "app_device_status_change_listener.h" +#include "app_types.h" +#include "visibility.h" +namespace OHOS { +namespace ObjectStore { +class CommunicationProvider { +public: + // constructor + KVSTORE_API CommunicationProvider(){}; + + // destructor + KVSTORE_API virtual ~CommunicationProvider(){}; + + // add DeviceChangeListener to watch device change + KVSTORE_API + virtual Status StartWatchDeviceChange(const AppDeviceStatusChangeListener *observer, const PipeInfo &pipeInfo) = 0; + + // stop DeviceChangeListener to watch device change + KVSTORE_API + virtual Status StopWatchDeviceChange(const AppDeviceStatusChangeListener *observer, const PipeInfo &pipeInfo) = 0; + + // add DataChangeListener to watch data change + KVSTORE_API + virtual Status StartWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo) = 0; + + // stop DataChangeListener to watch data change + KVSTORE_API virtual Status StopWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo) = 0; + + // Send data to other device, function will be called back after sent to notify send result + KVSTORE_API + virtual Status SendData(const PipeInfo &pipeInfo, const DeviceId &deviceId, const uint8_t *ptr, int size, + const MessageInfo &info = { MessageType::DEFAULT }) = 0; + + // Get online deviceList + KVSTORE_API virtual std::vector GetDeviceList() const = 0; + + // Get local device information + KVSTORE_API virtual DeviceInfo GetLocalDevice() const = 0; + + // start one server to listen data from other devices; + KVSTORE_API virtual Status Start(const PipeInfo &pipeInfo) = 0; + + // stop server + KVSTORE_API virtual Status Stop(const PipeInfo &pipeInfo) = 0; + + // user should use this method to get instance of CommunicationProvider; + KVSTORE_API static CommunicationProvider &GetInstance(); + + // check peer device pipeInfo Process + KVSTORE_API virtual bool IsSameStartedOnPeer(const PipeInfo &pipeInfo, const DeviceId &peer) const = 0; +}; +} // namespace ObjectStore +} // namespace OHOS +#endif // DISTRIBUTEDDATA_COMMUNICATION_PROVIDER_H diff --git a/data_object/frameworks/innerkitsimpl/include/communicator/communication_provider_impl.h b/data_object/frameworks/innerkitsimpl/include/communicator/communication_provider_impl.h new file mode 100644 index 00000000..ec22e088 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/communicator/communication_provider_impl.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDATA_SRC_COMMUNICATION_PROVIDER_IMPL_H +#define DISTRIBUTEDDATA_SRC_COMMUNICATION_PROVIDER_IMPL_H + +#include + +#include "app_device_handler.h" +#include "app_pipe_mgr.h" +#include "communication_provider.h" + +namespace OHOS { +namespace ObjectStore { +class CommunicationProviderImpl : public CommunicationProvider { +public: + CommunicationProviderImpl(AppPipeMgr &appPipeMgr, AppDeviceHandler &deviceHandler); + + virtual ~CommunicationProviderImpl(); + + // add DeviceChangeListener to watch device change; + Status StartWatchDeviceChange(const AppDeviceStatusChangeListener *observer, const PipeInfo &pipeInfo) override; + + // stop watching device change; + Status StopWatchDeviceChange(const AppDeviceStatusChangeListener *observer, const PipeInfo &pipeInfo) override; + + // add DataChangeListener to watch data change; + Status StartWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo) override; + + // stop watching data change; + Status StopWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo) override; + + // Send data to other device, function will be called back after sent to notify send result. + Status SendData(const PipeInfo &pipeInfo, const DeviceId &deviceId, const uint8_t *ptr, int size, + const MessageInfo &info) override; + + // Get online deviceList + std::vector GetDeviceList() const override; + + // Get local device information + DeviceInfo GetLocalDevice() const override; + + // start 1 server to listen data from other devices; + Status Start(const PipeInfo &pipeInfo) override; + + // stop server + Status Stop(const PipeInfo &pipeInfo) override; + + bool IsSameStartedOnPeer(const PipeInfo &pipeInfo, const DeviceId &peer) const override; + +protected: + virtual Status Initialize(); + + static std::mutex mutex_; + +private: + AppPipeMgr &appPipeMgr_; + AppDeviceHandler &appDeviceHandler_; +}; +} // namespace ObjectStore +} // namespace OHOS +#endif /* DISTRIBUTEDDATA_SRC_COMMUNICATION_PROVIDER_IMPL_H */ diff --git a/data_object/frameworks/innerkitsimpl/include/communicator/process_communicator_impl.h b/data_object/frameworks/innerkitsimpl/include/communicator/process_communicator_impl.h new file mode 100644 index 00000000..65a92802 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/communicator/process_communicator_impl.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 PROCESS_COMMUNICATOR_IMPL_H +#define PROCESS_COMMUNICATOR_IMPL_H + +#include + +#include "communication_provider.h" +#include "iprocess_communicator.h" + +namespace OHOS { +namespace ObjectStore { +class ProcessCommunicatorImpl + : public DistributedDB::IProcessCommunicator + , private AppDataChangeListener + , private AppDeviceStatusChangeListener { +public: + using DBStatus = DistributedDB::DBStatus; + using OnDeviceChange = DistributedDB::OnDeviceChange; + using OnDataReceive = DistributedDB::OnDataReceive; + using DeviceInfos = DistributedDB::DeviceInfos; + KVSTORE_API ProcessCommunicatorImpl(); + KVSTORE_API ~ProcessCommunicatorImpl() override; + + KVSTORE_API DBStatus Start(const std::string &processLabel) override; + KVSTORE_API DBStatus Stop() override; + + KVSTORE_API DBStatus RegOnDeviceChange(const OnDeviceChange &callback) override; + KVSTORE_API DBStatus RegOnDataReceive(const OnDataReceive &callback) override; + + KVSTORE_API DBStatus SendData(const DeviceInfos &dstDevInfo, const uint8_t *data, uint32_t length) override; + KVSTORE_API uint32_t GetMtuSize() override; + KVSTORE_API uint32_t GetMtuSize(const DeviceInfos &devInfo) override; + KVSTORE_API DeviceInfos GetLocalDeviceInfos() override; + KVSTORE_API std::vector GetRemoteOnlineDeviceInfosList() override; + KVSTORE_API bool IsSameProcessLabelStartedOnPeerDevice(const DeviceInfos &peerDevInfo) override; + +private: + void OnMessage(const DeviceInfo &info, const uint8_t *ptr, const int size, const PipeInfo &pipeInfo) const override; + void OnDeviceChanged(const DeviceInfo &info, const DeviceChangeType &type) const override; + + std::string thisProcessLabel_; + OnDeviceChange onDeviceChangeHandler_; + OnDataReceive onDataReceiveHandler_; + mutable std::mutex onDeviceChangeMutex_; + mutable std::mutex onDataReceiveMutex_; + + static constexpr uint32_t MTU_SIZE = 4096 * 1024; // the max transmission unit size(4M - 80B) + static constexpr uint32_t MTU_SIZE_WATCH = 81920; // the max transmission unit size(80K) + static constexpr const char *SMART_WATCH_TYPE = "SMART_WATCH"; + static constexpr const char *CHILDREN_WATCH_TYPE = "CHILDREN_WATCH"; +}; +} // namespace ObjectStore +} // namespace OHOS +#endif // PROCESS_COMMUNICATOR_IMPL_H diff --git a/data_object/frameworks/innerkitsimpl/include/communicator/softbus_adapter.h b/data_object/frameworks/innerkitsimpl/include/communicator/softbus_adapter.h new file mode 100644 index 00000000..e91d39bc --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/communicator/softbus_adapter.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTEDDATAFWK_SRC_SOFTBUS_ADAPTER_H +#define DISTRIBUTEDDATAFWK_SRC_SOFTBUS_ADAPTER_H +#include +#include +#include +#include + +#include "app_data_change_listener.h" +#include "app_device_status_change_listener.h" +#include "app_types.h" +#include "session.h" +#include "softbus_bus_center.h" +#include "condition_lock.h" + +namespace OHOS { +namespace ObjectStore { +class SoftBusAdapter { +public: + SoftBusAdapter(); + ~SoftBusAdapter(); + static std::shared_ptr GetInstance(); + + void Init(); + // add DeviceChangeListener to watch device change; + Status StartWatchDeviceChange(const AppDeviceStatusChangeListener *observer, const PipeInfo &pipeInfo); + // stop DeviceChangeListener to watch device change; + Status StopWatchDeviceChange(const AppDeviceStatusChangeListener *observer, const PipeInfo &pipeInfo); + void NotifyAll(const DeviceInfo &deviceInfo, const DeviceChangeType &type); + DeviceInfo GetLocalDevice(); + std::vector GetDeviceList() const; + std::string GetUdidByNodeId(const std::string &nodeId) const; + // get local device node information; + DeviceInfo GetLocalBasicInfo() const; + // get all remote connected device's node information; + std::vector GetRemoteNodesBasicInfo() const; + static std::string ToBeAnonymous(const std::string &name); + + // add DataChangeListener to watch data change; + Status StartWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo); + + // stop DataChangeListener to watch data change; + Status StopWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo); + + // Send data to other device, function will be called back after sent to notify send result. + Status SendData( + const PipeInfo &pipeInfo, const DeviceId &deviceId, const uint8_t *ptr, int size, const MessageInfo &info); + + bool IsSameStartedOnPeer(const struct PipeInfo &pipeInfo, const struct DeviceId &peer); + + void SetMessageTransFlag(const PipeInfo &pipeInfo, bool flag); + + int CreateSessionServerAdapter(const std::string &sessionName); + + int RemoveSessionServerAdapter(const std::string &sessionName) const; + + void UpdateRelationship(const std::string &networkid, const DeviceChangeType &type); + + void InsertSession(const std::string &sessionName); + + void DeleteSession(const std::string &sessionName); + + void NotifyDataListeners(const uint8_t *ptr, const int size, const std::string &deviceId, const PipeInfo &pipeInfo); + + std::string ToNodeID(const std::string &nodeId) const; + + int32_t GetSessionStatus(int32_t sessionId); + + void OnSessionOpen(int32_t sessionId, int32_t status); + + void OnSessionClose(int32_t sessionId); + +private: + std::shared_ptr> GetSemaphore (int32_t sessinId); + mutable std::mutex networkMutex_{}; + mutable std::map networkId2Udid_{}; + DeviceInfo localInfo_{}; + static std::shared_ptr instance_; + std::mutex deviceChangeMutex_; + std::set listeners_{}; + std::mutex dataChangeMutex_{}; + std::map dataChangeListeners_{}; + std::mutex busSessionMutex_{}; + std::map busSessionMap_{}; + bool flag_ = true; // only for br flag + INodeStateCb nodeStateCb_{}; + ISessionListener sessionListener_{}; + std::mutex statusMutex_ {}; + std::map>> sessionsStatus_; +}; +} // namespace ObjectStore +} // namespace OHOS +#endif /* DISTRIBUTEDDATAFWK_SRC_SOFTBUS_ADAPTER_H */ \ No newline at end of file diff --git a/data_object/frameworks/innerkitsimpl/include/communicator/visibility.h b/data_object/frameworks/innerkitsimpl/include/communicator/visibility.h new file mode 100644 index 00000000..0b836f21 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/include/communicator/visibility.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 KVSTORE_API +#ifdef _WIN32 +#ifdef DB_DLL_EXPORT +#define KVSTORE_API __declspec(dllexport) +#else +#define KVSTORE_API +#endif +#else +#define KVSTORE_API __attribute__((visibility("default"))) +#endif +#endif diff --git a/data_object/frameworks/innerkitsimpl/src/adaptor/client_adaptor.cpp b/data_object/frameworks/innerkitsimpl/src/adaptor/client_adaptor.cpp new file mode 100644 index 00000000..f984b701 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/adaptor/client_adaptor.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "client_adaptor.h" + +#include + +#include "logger.h" +#include "iservice_registry.h" + +namespace OHOS::ObjectStore { +sptr ClientAdaptor::GetObjectService() +{ + static sptr distributedDataMgr; + if (distributedDataMgr == nullptr) { + distributedDataMgr = GetDistributedDataManager(); + } + if (distributedDataMgr == nullptr) { + LOG_ERROR("get distributed data manager failed"); + return nullptr; + } + + auto remote = distributedDataMgr->GetObjectService(); + if (remote == nullptr) { + LOG_ERROR("get object service failed"); + return nullptr; + } + return iface_cast(remote); +} + +sptr ClientAdaptor::GetDistributedDataManager() +{ + int retry = 0; + while (++retry <= GET_SA_RETRY_TIMES) { + auto manager = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager(); + if (manager == nullptr) { + LOG_ERROR("get system ability manager failed"); + return nullptr; + } + LOG_INFO("get distributed data manager %{public}d", retry); + auto remoteObject = manager->CheckSystemAbility(DISTRIBUTED_KV_DATA_SERVICE_ABILITY_ID); + if (remoteObject == nullptr) { + std::this_thread::sleep_for(std::chrono::seconds(RETRY_INTERVAL)); + continue; + } + LOG_INFO("get distributed data manager success"); + return iface_cast(remoteObject); + } + + LOG_ERROR("get distributed data manager failed"); + return nullptr; +} +} \ No newline at end of file diff --git a/data_object/frameworks/innerkitsimpl/src/adaptor/distributed_object_impl.cpp b/data_object/frameworks/innerkitsimpl/src/adaptor/distributed_object_impl.cpp new file mode 100644 index 00000000..1cd4d378 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/adaptor/distributed_object_impl.cpp @@ -0,0 +1,220 @@ +/* +* Copyright (c) 2022 Huawei Device Co., Ltd. +* 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 "distributed_object_impl.h" + +#include "dds_trace.h" +#include "objectstore_errors.h" +#include "string_utils.h" + +namespace OHOS::ObjectStore { +DistributedObjectImpl::~DistributedObjectImpl() +{ +} + +void PutNum(void *val, uint32_t offset, uint32_t valLen, Bytes &data) +{ + uint32_t len = valLen + offset; + if (len > sizeof(data.front()) * data.size()) { + data.resize(len); + } + + for (uint32_t i = 0; i < valLen; i++) { + // 8 bit = 1 byte + data[offset + i] = *(static_cast(val)) >> ((valLen - i - 1) * 8); + } +} + +uint32_t GetNum(Bytes &data, uint32_t offset, void *val, uint32_t valLen) +{ + uint8_t *value = (uint8_t *)val; + uint32_t len = offset + valLen; + uint32_t dataLen = data.size(); + if (dataLen < len) { + LOG_ERROR("DistributedObjectImpl:GetNum data.size() %{public}d, offset %{public}d, valLen %{public}d", dataLen, + offset, valLen); + return ERR_DATA_LEN; + } + for (uint32_t i = 0; i < valLen; i++) { + value[i] = data[len - 1 - i]; + } + return SUCCESS; +} + +uint32_t DistributedObjectImpl::PutDouble(const std::string &key, double value) +{ + DistributedDataDfx::DdsTrace trace(std::string("DistributedObjectImpl::") + std::string(__FUNCTION__), + DistributedDataDfx::TraceSwitch::BYTRACE_ON | DistributedDataDfx::TraceSwitch::TRACE_CHAIN_ON); + Bytes data; + Type type = Type::TYPE_DOUBLE; + PutNum(&type, 0, sizeof(type), data); + PutNum(&value, sizeof(type), sizeof(value), data); + uint32_t status = flatObjectStore_->Put(sessionId_, FIELDS_PREFIX + key, data); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl::PutDouble setField err %{public}d", status); + } + return status; +} + +uint32_t DistributedObjectImpl::PutBoolean(const std::string &key, bool value) +{ + DistributedDataDfx::DdsTrace trace(std::string("DistributedObjectImpl::") + std::string(__FUNCTION__), + DistributedDataDfx::TraceSwitch::BYTRACE_ON | DistributedDataDfx::TraceSwitch::TRACE_CHAIN_ON); + Bytes data; + Type type = Type::TYPE_BOOLEAN; + PutNum(&type, 0, sizeof(type), data); + PutNum(&value, sizeof(type), sizeof(value), data); + uint32_t status = flatObjectStore_->Put(sessionId_, FIELDS_PREFIX + key, data); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl::PutBoolean setField err %{public}d", status); + } + return status; +} + +uint32_t DistributedObjectImpl::PutString(const std::string &key, const std::string &value) +{ + DistributedDataDfx::DdsTrace trace(std::string("DistributedObjectImpl::") + std::string(__FUNCTION__), + DistributedDataDfx::TraceSwitch::BYTRACE_ON | DistributedDataDfx::TraceSwitch::TRACE_CHAIN_ON); + Bytes data; + Type type = Type::TYPE_STRING; + PutNum(&type, 0, sizeof(type), data); + Bytes dst = StringUtils::StrToBytes(value); + data.insert(data.end(), dst.begin(), dst.end()); + uint32_t status = flatObjectStore_->Put(sessionId_, FIELDS_PREFIX + key, data); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl::PutString setField err %{public}d", status); + } + return status; +} + +uint32_t DistributedObjectImpl::GetDouble(const std::string &key, double &value) +{ + Bytes data; + Bytes keyBytes = StringUtils::StrToBytes(key); + uint32_t status = flatObjectStore_->Get(sessionId_, FIELDS_PREFIX + key, data); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl:GetDouble field not exist. %{public}d %{public}s", status, key.c_str()); + return status; + } + status = GetNum(data, sizeof(Type), &value, sizeof(value)); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl::GetDouble getNum err. %{public}d", status); + } + return status; +} + +uint32_t DistributedObjectImpl::GetBoolean(const std::string &key, bool &value) +{ + Bytes data; + Bytes keyBytes = StringUtils::StrToBytes(key); + uint32_t status = flatObjectStore_->Get(sessionId_, FIELDS_PREFIX + key, data); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl:GetBoolean field not exist. %{public}d %{public}s", status, key.c_str()); + return status; + } + status = GetNum(data, sizeof(Type), &value, sizeof(value)); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl::GetBoolean getNum err. %{public}d", status); + return status; + } + return SUCCESS; +} + +uint32_t DistributedObjectImpl::GetString(const std::string &key, std::string &value) +{ + Bytes data; + uint32_t status = flatObjectStore_->Get(sessionId_, FIELDS_PREFIX + key, data); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl:GetString field not exist. %{public}d %{public}s", status, key.c_str()); + return status; + } + status = StringUtils::BytesToStrWithType(data, value); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl::GetString dataToVal err. %{public}d", status); + } + return status; +} + +uint32_t DistributedObjectImpl::GetType(const std::string &key, Type &type) +{ + Bytes data; + uint32_t status = flatObjectStore_->Get(sessionId_, FIELDS_PREFIX + key, data); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl:GetString field not exist. %{public}d %{public}s", status, key.c_str()); + return status; + } + status = GetNum(data, 0, &type, sizeof(type)); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl::GetBoolean getNum err. %{public}d", status); + return status; + } + return SUCCESS; +} + +std::string &DistributedObjectImpl::GetSessionId() +{ + return sessionId_; +} + +DistributedObjectImpl::DistributedObjectImpl(const std::string &sessionId, FlatObjectStore *flatObjectStore) + : sessionId_(sessionId), flatObjectStore_(flatObjectStore) +{ +} + +uint32_t DistributedObjectImpl::PutComplex(const std::string &key, const std::vector &value) +{ + DistributedDataDfx::DdsTrace trace(std::string("DistributedObjectImpl::") + std::string(__FUNCTION__), + DistributedDataDfx::TraceSwitch::BYTRACE_ON | DistributedDataDfx::TraceSwitch::TRACE_CHAIN_ON); + Bytes data; + Type type = Type::TYPE_COMPLEX; + PutNum(&type, 0, sizeof(type), data); + data.insert(data.end(), value.begin(), value.end()); + uint32_t status = flatObjectStore_->Put(sessionId_, FIELDS_PREFIX + key, data); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl::PutBoolean setField err %{public}d", status); + } + return status; +} + +uint32_t DistributedObjectImpl::GetComplex(const std::string &key, std::vector &value) +{ + uint32_t status = flatObjectStore_->Get(sessionId_, FIELDS_PREFIX + key, value); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl:GetString field not exist. %{public}d %{public}s", status, key.c_str()); + return status; + } + return status; +} + +uint32_t DistributedObjectImpl::Save(const std::string &deviceId) +{ + uint32_t status = flatObjectStore_->Save(sessionId_, deviceId); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl:Save failed. status = %{public}d", status); + return status; + } + return status; +} + +uint32_t DistributedObjectImpl::RevokeSave() +{ + uint32_t status = flatObjectStore_->RevokeSave(sessionId_); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectImpl:RevokeSave failed. status = %{public}d", status); + return status; + } + return status; +} +} // namespace OHOS::ObjectStore \ No newline at end of file diff --git a/data_object/frameworks/innerkitsimpl/src/adaptor/distributed_object_store_impl.cpp b/data_object/frameworks/innerkitsimpl/src/adaptor/distributed_object_store_impl.cpp new file mode 100644 index 00000000..ea12d8e4 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/adaptor/distributed_object_store_impl.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "dds_trace.h" +#include "distributed_object_impl.h" +#include "distributed_objectstore_impl.h" +#include "objectstore_errors.h" +#include "softbus_adapter.h" +#include "string_utils.h" + +namespace OHOS::ObjectStore { +DistributedObjectStoreImpl::DistributedObjectStoreImpl(FlatObjectStore *flatObjectStore) + : flatObjectStore_(flatObjectStore) +{ +} + +DistributedObjectStoreImpl::~DistributedObjectStoreImpl() +{ + delete flatObjectStore_; +} + +DistributedObject *DistributedObjectStoreImpl::CacheObject( + const std::string &sessionId, FlatObjectStore *flatObjectStore) +{ + DistributedObjectImpl *object = new (std::nothrow) DistributedObjectImpl(sessionId, flatObjectStore); + if (object == nullptr) { + return nullptr; + } + std::unique_lock cacheLock(dataMutex_); + objects_.push_back(object); + return object; +} + +void DistributedObjectStoreImpl::RemoveCacheObject(const std::string &sessionId) +{ + std::unique_lock cacheLock(dataMutex_); + auto iter = objects_.begin(); + while (iter != objects_.end()) { + if ((*iter)->GetSessionId() == sessionId) { + delete *iter; + iter = objects_.erase(iter); + } else { + iter++; + } + } + return; +} + +DistributedObject *DistributedObjectStoreImpl::CreateObject(const std::string &sessionId) +{ + DistributedDataDfx::DdsTrace trace(std::string("DistributedObjectImpl::") + std::string(__FUNCTION__), + DistributedDataDfx::TraceSwitch::TRACE_CHAIN_ON); + if (flatObjectStore_ == nullptr) { + LOG_ERROR("DistributedObjectStoreImpl::CreateObject store not opened!"); + return nullptr; + } + uint32_t status = flatObjectStore_->CreateObject(sessionId); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectStoreImpl::CreateObject CreateTable err %{public}d", status); + return nullptr; + } + return CacheObject(sessionId, flatObjectStore_); +} + +uint32_t DistributedObjectStoreImpl::DeleteObject(const std::string &sessionId) +{ + DistributedDataDfx::DdsTrace trace(std::string("DistributedObjectImpl::") + std::string(__FUNCTION__), + DistributedDataDfx::TraceSwitch::TRACE_CHAIN_ON); + if (flatObjectStore_ == nullptr) { + LOG_ERROR("DistributedObjectStoreImpl::Sync object err "); + return ERR_NULL_OBJECTSTORE; + } + uint32_t status = flatObjectStore_->Delete(sessionId); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectStoreImpl::DeleteObject store delete err %{public}d", status); + return status; + } + RemoveCacheObject(sessionId); + return SUCCESS; +} + +uint32_t DistributedObjectStoreImpl::Get(const std::string &sessionId, DistributedObject **object) +{ + auto iter = objects_.begin(); + while (iter != objects_.end()) { + if ((*iter)->GetSessionId() == sessionId) { + *object = *iter; + return SUCCESS; + } + iter++; + } + LOG_ERROR("DistributedObjectStoreImpl::Get object err, no object"); + return ERR_GET_OBJECT; +} + +uint32_t DistributedObjectStoreImpl::Watch(DistributedObject *object, std::shared_ptr watcher) +{ + if (object == nullptr) { + LOG_ERROR("DistributedObjectStoreImpl::Sync object err "); + return ERR_NULL_OBJECT; + } + if (flatObjectStore_ == nullptr) { + LOG_ERROR("DistributedObjectStoreImpl::Sync object err "); + return ERR_NULL_OBJECTSTORE; + } + if (watchers_.count(object) != 0) { + LOG_ERROR("DistributedObjectStoreImpl::Watch already gets object"); + return ERR_EXIST; + } + std::shared_ptr watcherProxy = std::make_shared(watcher, object->GetSessionId()); + uint32_t status = flatObjectStore_->Watch(object->GetSessionId(), watcherProxy); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectStoreImpl::Watch failed %{public}d", status); + return status; + } + watchers_.insert_or_assign(object, watcherProxy); + LOG_INFO("DistributedObjectStoreImpl:Watch object success."); + return SUCCESS; +} + +uint32_t DistributedObjectStoreImpl::UnWatch(DistributedObject *object) +{ + if (object == nullptr) { + LOG_ERROR("DistributedObjectStoreImpl::Sync object err "); + return ERR_NULL_OBJECT; + } + if (flatObjectStore_ == nullptr) { + LOG_ERROR("DistributedObjectStoreImpl::Sync object err "); + return ERR_NULL_OBJECTSTORE; + } + uint32_t status = flatObjectStore_->UnWatch(object->GetSessionId()); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectStoreImpl::Watch failed %{public}d", status); + return status; + } + watchers_.erase(object); + LOG_INFO("DistributedObjectStoreImpl:UnWatch object success."); + return SUCCESS; +} + +void DistributedObjectStoreImpl::TriggerSync() +{ +} + +void DistributedObjectStoreImpl::TriggerRestore(std::function notifier) +{ + std::thread th = std::thread([=]() { + bool isFinished; + int16_t i; + constexpr static int16_t MAX_RETRY_SIZE = 5000; + std::map syncStatus; + for (auto &item : objects_) { + syncStatus[item->GetSessionId()] = SYNC_START; + } + for (i = 0; i < MAX_RETRY_SIZE; i++) { + { + std::unique_lock cacheLock(dataMutex_); + for (auto &item : objects_) { + if (syncStatus[item->GetSessionId()] != SYNC_SUCCESS + && syncStatus[item->GetSessionId()] != SYNCING) { + auto onComplete = [this, item, &syncStatus]( + const std::map &devices) { + LOG_INFO("%{public}s pull data", item->GetSessionId().c_str()); + std::unique_lock cacheLock(dataMutex_); + SyncStatus result = SYNC_SUCCESS; + for (auto device : devices) { + if (device.second != DistributedDB::OK) { + result = SYNC_FAIL; + LOG_ERROR("%{public}s pull data fail %{public}d in device %{public}s", + item->GetSessionId().c_str(), device.second, + SoftBusAdapter::GetInstance()->ToNodeID(device.first).c_str()); + } + } + LOG_INFO("%{public}s pull data success", item->GetSessionId().c_str()); + syncStatus[item->GetSessionId()] = result; + }; + LOG_INFO("start sync %{public}s", item->GetSessionId().c_str()); + uint32_t result = flatObjectStore_->SyncAllData(item->GetSessionId(), onComplete); + if (result == SUCCESS) { + syncStatus[item->GetSessionId()] = SYNCING; + } else if (result == ERR_SINGLE_DEVICE) { + // single device, do not retry + syncStatus[item->GetSessionId()] = SYNC_SUCCESS; + } + } + } + } + + isFinished = true; + for (auto &item : syncStatus) { + if (item.second != SYNC_SUCCESS) { + LOG_INFO("%{public}s not ready", item.first.c_str()); + isFinished = false; + break; + } + } + if (!isFinished) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + LOG_WARN("restore result"); + notifier(); + LOG_WARN("notify end"); + }); + th.detach(); + return; +} +uint32_t DistributedObjectStoreImpl::SetStatusNotifier(std::shared_ptr notifier) +{ + if (flatObjectStore_ == nullptr) { + LOG_ERROR("DistributedObjectStoreImpl::Sync object err "); + return ERR_NULL_OBJECTSTORE; + } + std::shared_ptr watcherProxy = std::make_shared(notifier); + uint32_t status = flatObjectStore_->SetStatusNotifier(watcherProxy); + if (status != SUCCESS) { + LOG_ERROR("DistributedObjectStoreImpl::Watch failed %{public}d", status); + } + return status; +} + +WatcherProxy::WatcherProxy(const std::shared_ptr objectWatcher, const std::string &sessionId) + : FlatObjectWatcher(sessionId), objectWatcher_(objectWatcher) +{ +} + +void WatcherProxy::OnChanged(const std::string &sessionid, const std::vector &changedData) +{ + objectWatcher_->OnChanged(sessionid, changedData); +} + +DistributedObjectStore *DistributedObjectStore::GetInstance(const std::string &bundleName) +{ + static std::mutex instLock_; + static DistributedObjectStore *instPtr = nullptr; + if (instPtr == nullptr) { + std::lock_guard lock(instLock_); + if (instPtr == nullptr && !bundleName.empty()) { + LOG_INFO("new objectstore %{public}s", bundleName.c_str()); + FlatObjectStore *flatObjectStore = new (std::nothrow) FlatObjectStore(bundleName); + if (flatObjectStore == nullptr) { + LOG_ERROR("no memory for FlatObjectStore malloc!"); + return nullptr; + } + // Use instMemory to make sure this singleton not free before other object. + // This operation needn't to malloc memory, we needn't to check nullptr. + instPtr = new DistributedObjectStoreImpl(flatObjectStore); + } + } + return instPtr; +} + +void StatusNotifierProxy::OnChanged( + const std::string &sessionId, const std::string &networkId, const std::string &onlineStatus) +{ + if (notifier != nullptr) { + notifier->OnChanged(sessionId, networkId, onlineStatus); + } +} + +StatusNotifierProxy::StatusNotifierProxy(const std::shared_ptr ¬ifier) : notifier(notifier) +{ +} + +StatusNotifierProxy::~StatusNotifierProxy() +{ + LOG_ERROR("destroy"); + notifier = nullptr; +} +} // namespace OHOS::ObjectStore diff --git a/data_object/frameworks/innerkitsimpl/src/adaptor/flat_object_storage_engine.cpp b/data_object/frameworks/innerkitsimpl/src/adaptor/flat_object_storage_engine.cpp new file mode 100644 index 00000000..bb5ceed3 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/adaptor/flat_object_storage_engine.cpp @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "flat_object_storage_engine.h" + +#include "logger.h" +#include "objectstore_errors.h" +#include "process_communicator_impl.h" +#include "securec.h" +#include "softbus_adapter.h" +#include "string_utils.h" +#include "types_export.h" + +namespace OHOS::ObjectStore { +FlatObjectStorageEngine::~FlatObjectStorageEngine() +{ + if (!isOpened_) { + return; + } + storeManager_ = nullptr; + LOG_INFO("FlatObjectStorageEngine::~FlatObjectStorageEngine Crash! end"); +} + +uint32_t FlatObjectStorageEngine::Open(const std::string &bundleName) +{ + if (isOpened_) { + LOG_INFO("FlatObjectDatabase: No need to reopen it"); + return SUCCESS; + } + auto status = DistributedDB::KvStoreDelegateManager::SetProcessLabel("objectstoreDB", bundleName); + if (status != DistributedDB::DBStatus::OK) { + LOG_ERROR("delegate SetProcessLabel failed: %{public}d.", static_cast(status)); + return ERR_DB_SET_PROCESS; + } + + auto communicator = std::make_shared(); + auto commStatus = DistributedDB::KvStoreDelegateManager::SetProcessCommunicator(communicator); + if (commStatus != DistributedDB::DBStatus::OK) { + LOG_ERROR("set distributed db communicator failed."); + return ERR_DB_SET_PROCESS; + } + storeManager_ = std::make_shared(bundleName, "default"); + if (storeManager_ == nullptr) { + LOG_ERROR("FlatObjectStorageEngine::make shared fail"); + return ERR_NOMEM; + } + + DistributedDB::KvStoreConfig config; + config.dataDir = "/data/log"; + storeManager_->SetKvStoreConfig(config); + isOpened_ = true; + LOG_INFO("FlatObjectDatabase::Open Succeed"); + return SUCCESS; +} + +uint32_t FlatObjectStorageEngine::Close() +{ + if (!isOpened_) { + LOG_INFO("FlatObjectStorageEngine::Close has been closed!"); + return SUCCESS; + } + std::lock_guard lock(operationMutex_); + storeManager_ = nullptr; + isOpened_ = false; + return SUCCESS; +} + +uint32_t FlatObjectStorageEngine::CreateTable(const std::string &key) +{ + if (!isOpened_) { + return ERR_DB_NOT_INIT; + } + { + std::lock_guard lock(operationMutex_); + if (delegates_.count(key) != 0) { + LOG_ERROR("FlatObjectStorageEngine::CreateTable %{public}s already created", key.c_str()); + return ERR_EXIST; + } + } + DistributedDB::KvStoreNbDelegate *kvStore = nullptr; + DistributedDB::DBStatus status; + DistributedDB::KvStoreNbDelegate::Option option = { true, true, + false }; // createIfNecessary, isMemoryDb, isEncryptedDb + LOG_INFO("start create table"); + storeManager_->GetKvStore(key, option, + [&status, &kvStore](DistributedDB::DBStatus dbStatus, DistributedDB::KvStoreNbDelegate *kvStoreNbDelegate) { + status = dbStatus; + kvStore = kvStoreNbDelegate; + LOG_INFO("create table result %{public}d", status); + }); + bool autoSync = true; + DistributedDB::PragmaData data = static_cast(&autoSync); + LOG_INFO("start Pragma"); + status = kvStore->Pragma(DistributedDB::AUTO_SYNC, data); + if (status != DistributedDB::DBStatus::OK) { + LOG_ERROR("FlatObjectStorageEngine::CreateTable %{public}s getkvstore fail[%{public}d]", key.c_str(), status); + return ERR_DB_GETKV_FAIL; + } + LOG_INFO("create table %{public}s success", key.c_str()); + { + std::lock_guard lock(operationMutex_); + delegates_.insert_or_assign(key, kvStore); + } + + auto onComplete = [key, this](const std::map &devices) { + LOG_INFO("complete"); + for (auto item : devices) { + LOG_INFO("%{public}s pull data result %{public}d in device %{public}s", key.c_str(), item.second, + SoftBusAdapter::GetInstance()->ToNodeID(item.first).c_str()); + } + if (statusWatcher_ != nullptr) { + for (auto item : devices) { + statusWatcher_->OnChanged(key, SoftBusAdapter::GetInstance()->ToNodeID(item.first), + item.second == DistributedDB::OK ? "online" : "offline"); + } + } + }; + std::vector devices = SoftBusAdapter::GetInstance()->GetDeviceList(); + std::vector deviceIds; + for (auto item : devices) { + deviceIds.push_back(item.deviceId); + } + SyncAllData(key, deviceIds, onComplete); + return SUCCESS; +} + +uint32_t FlatObjectStorageEngine::GetTable(const std::string &key, std::map &result) +{ + if (!isOpened_) { + LOG_ERROR("not opened %{public}s", key.c_str()); + return ERR_DB_NOT_INIT; + } + std::lock_guard lock(operationMutex_); + if (delegates_.count(key) == 0) { + LOG_INFO("FlatObjectStorageEngine::GetTable %{public}s not exist", key.c_str()); + return ERR_DB_NOT_EXIST; + } + result.clear(); + DistributedDB::KvStoreResultSet *resultSet = nullptr; + Key emptyKey; + LOG_INFO("start GetEntries"); + DistributedDB::DBStatus status = delegates_.at(key)->GetEntries(emptyKey, resultSet); + if (status != DistributedDB::DBStatus::OK) { + LOG_INFO("FlatObjectStorageEngine::GetTable %{public}s GetEntries fail", key.c_str()); + return ERR_DB_GET_FAIL; + } + LOG_INFO("end GetEntries"); + while (resultSet->IsAfterLast()) { + DistributedDB::Entry entry; + status = resultSet->GetEntry(entry); + if (status != DistributedDB::DBStatus::OK) { + LOG_INFO("FlatObjectStorageEngine::GetTable GetEntry fail"); + return ERR_DB_ENTRY_FAIL; + } + result.insert_or_assign(StringUtils::BytesToStr(entry.key), entry.value); + resultSet->MoveToNext(); + } + return SUCCESS; +} + +uint32_t FlatObjectStorageEngine::UpdateItem(const std::string &key, const std::string &itemKey, Value &value) +{ + if (!isOpened_) { + return ERR_DB_NOT_INIT; + } + std::lock_guard lock(operationMutex_); + if (delegates_.count(key) == 0) { + LOG_INFO("FlatObjectStorageEngine::GetTable %{public}s not exist", key.c_str()); + return ERR_DB_NOT_EXIST; + } + auto delegate = delegates_.at(key); + LOG_INFO("start Put"); + auto status = delegate->Put(StringUtils::StrToBytes(itemKey), value); + if (status != DistributedDB::DBStatus::OK) { + LOG_ERROR("%{public}s Put fail[%{public}d]", key.c_str(), status); + return ERR_CLOSE_STORAGE; + } + LOG_INFO("put success"); + return SUCCESS; +} + +uint32_t FlatObjectStorageEngine::UpdateItems( + const std::string &key, const std::map> &data) +{ + if (!isOpened_ || data.size() == 0) { + return ERR_DB_NOT_INIT; + } + std::lock_guard lock(operationMutex_); + if (delegates_.count(key) == 0) { + LOG_INFO("FlatObjectStorageEngine::UpdateItems %{public}s not exist", key.c_str()); + return ERR_DB_NOT_EXIST; + } + + std::vector entries; + for (auto &item : data) { + DistributedDB::Entry entry = { .key = StringUtils::StrToBytes(item.first), .value = item.second }; + entries.emplace_back(entry); + } + auto delegate = delegates_.at(key); + LOG_INFO("start PutBatch"); + auto status = delegate->PutBatch(entries); + if (status != DistributedDB::DBStatus::OK) { + LOG_ERROR("%{public}s PutBatch fail[%{public}d]", key.c_str(), status); + return ERR_CLOSE_STORAGE; + } + LOG_INFO("put success"); + return SUCCESS; +} + +uint32_t FlatObjectStorageEngine::DeleteTable(const std::string &key) +{ + if (!isOpened_) { + return ERR_DB_NOT_INIT; + } + std::lock_guard lock(operationMutex_); + if (delegates_.count(key) == 0) { + LOG_INFO("FlatObjectStorageEngine::GetTable %{public}s not exist", key.c_str()); + return ERR_DB_NOT_EXIST; + } + LOG_INFO("start DeleteTable %{public}s", key.c_str()); + auto status = storeManager_->CloseKvStore(delegates_.at(key)); + if (status != DistributedDB::DBStatus::OK) { + LOG_ERROR( + "FlatObjectStorageEngine::CloseKvStore %{public}s CloseKvStore fail[%{public}d]", key.c_str(), status); + return ERR_CLOSE_STORAGE; + } + LOG_INFO("DeleteTable success"); + delegates_.erase(key); + return SUCCESS; +} + +uint32_t FlatObjectStorageEngine::GetItem(const std::string &key, const std::string &itemKey, Value &value) +{ + if (!isOpened_) { + return ERR_DB_NOT_INIT; + } + std::lock_guard lock(operationMutex_); + if (delegates_.count(key) == 0) { + LOG_ERROR("FlatObjectStorageEngine::GetItem %{public}s not exist", key.c_str()); + return ERR_DB_NOT_EXIST; + } + LOG_INFO("start Get %{public}s", key.c_str()); + DistributedDB::DBStatus status = delegates_.at(key)->Get(StringUtils::StrToBytes(itemKey), value); + if (status != DistributedDB::DBStatus::OK) { + LOG_ERROR("FlatObjectStorageEngine::GetItem %{public}s item fail %{public}d", itemKey.c_str(), status); + return status; + } + LOG_INFO("end Get %{public}s", key.c_str()); + return SUCCESS; +} + +uint32_t FlatObjectStorageEngine::RegisterObserver(const std::string &key, std::shared_ptr watcher) +{ + if (!isOpened_) { + LOG_ERROR("FlatObjectStorageEngine::RegisterObserver kvStore has not init"); + return ERR_DB_NOT_INIT; + } + std::lock_guard lock(operationMutex_); + if (delegates_.count(key) == 0) { + LOG_INFO("FlatObjectStorageEngine::RegisterObserver %{public}s not exist", key.c_str()); + return ERR_DB_NOT_EXIST; + } + if (observerMap_.count(key) != 0) { + LOG_INFO("FlatObjectStorageEngine::RegisterObserver observer already exist."); + return SUCCESS; + } + auto delegate = delegates_.at(key); + std::vector tmpKey; + LOG_INFO("start RegisterObserver %{public}s", key.c_str()); + DistributedDB::DBStatus status = + delegate->RegisterObserver(tmpKey, DistributedDB::ObserverMode::OBSERVER_CHANGES_FOREIGN, watcher.get()); + if (status != DistributedDB::DBStatus::OK) { + LOG_ERROR("FlatObjectStorageEngine::RegisterObserver watch err %{public}d", status); + return ERR_REGISTER; + } + LOG_INFO("end RegisterObserver %{public}s", key.c_str()); + observerMap_.insert_or_assign(key, watcher); + return SUCCESS; +} + +uint32_t FlatObjectStorageEngine::UnRegisterObserver(const std::string &key) +{ + if (!isOpened_) { + LOG_ERROR("FlatObjectStorageEngine::RegisterObserver kvStore has not init"); + return ERR_DB_NOT_INIT; + } + std::lock_guard lock(operationMutex_); + if (delegates_.count(key) == 0) { + LOG_INFO("FlatObjectStorageEngine::RegisterObserver %{public}s not exist", key.c_str()); + return ERR_DB_NOT_EXIST; + } + auto iter = observerMap_.find(key); + if (iter == observerMap_.end()) { + LOG_ERROR("FlatObjectStorageEngine::UnRegisterObserver observer not exist."); + return ERR_NO_OBSERVER; + } + auto delegate = delegates_.at(key); + std::shared_ptr watcher = iter->second; + LOG_INFO("start UnRegisterObserver %{public}s", key.c_str()); + DistributedDB::DBStatus status = delegate->UnRegisterObserver(watcher.get()); + if (status != DistributedDB::DBStatus::OK) { + LOG_ERROR("FlatObjectStorageEngine::UnRegisterObserver unRegister err %{public}d", status); + return ERR_UNRIGSTER; + } + LOG_INFO("end UnRegisterObserver %{public}s", key.c_str()); + observerMap_.erase(key); + return SUCCESS; +} + +uint32_t FlatObjectStorageEngine::SetStatusNotifier(std::shared_ptr watcher) +{ + if (!isOpened_) { + LOG_ERROR("FlatObjectStorageEngine::SetStatusNotifier kvStore has not init"); + return ERR_DB_NOT_INIT; + } + auto databaseStatusNotifyCallback = [this](std::string userId, std::string appId, std::string storeId, + const std::string deviceId, bool onlineStatus) -> void { + LOG_INFO("complete"); + if (statusWatcher_ == nullptr) { + LOG_INFO("FlatObjectStorageEngine::statusWatcher_ null"); + return; + } + if (onlineStatus) { + auto onComplete = [this, storeId](const std::map &devices) { + for (auto item : devices) { + LOG_INFO("%{public}s pull data result %{public}d in device %{public}s", storeId.c_str(), + item.second, SoftBusAdapter::GetInstance()->ToNodeID(item.first).c_str()); + } + if (statusWatcher_ != nullptr) { + for (auto item : devices) { + statusWatcher_->OnChanged(storeId, SoftBusAdapter::GetInstance()->ToNodeID(item.first), + item.second == DistributedDB::OK ? "online" : "offline"); + } + } + }; + SyncAllData(storeId, std::vector({ deviceId }), onComplete); + } else { + statusWatcher_->OnChanged(storeId, SoftBusAdapter::GetInstance()->ToNodeID(deviceId), "offline"); + } + }; + storeManager_->SetStoreStatusNotifier(databaseStatusNotifyCallback); + LOG_INFO("FlatObjectStorageEngine::SetStatusNotifier success"); + statusWatcher_ = watcher; + return SUCCESS; +} + +uint32_t FlatObjectStorageEngine::SyncAllData(const std::string &sessionId, const std::vector &deviceIds, + const std::function &)> &onComplete) +{ + LOG_INFO("start"); + std::lock_guard lock(operationMutex_); + if (delegates_.count(sessionId) == 0) { + LOG_ERROR("FlatObjectStorageEngine::SyncAllData %{public}s already deleted", sessionId.c_str()); + return ERR_DB_NOT_EXIST; + } + DistributedDB::KvStoreNbDelegate *kvstore = delegates_.at(sessionId); + if (deviceIds.empty()) { + LOG_INFO("single device,no need sync"); + return ERR_SINGLE_DEVICE; + } + LOG_INFO("start sync %{public}s", sessionId.c_str()); + DistributedDB::DBStatus status = kvstore->Sync(deviceIds, DistributedDB::SyncMode::SYNC_MODE_PULL_ONLY, onComplete); + if (status != DistributedDB::DBStatus::OK) { + LOG_ERROR("FlatObjectStorageEngine::UnRegisterObserver unRegister err %{public}d", status); + return ERR_UNRIGSTER; + } + LOG_INFO("end sync %{public}s", sessionId.c_str()); + return SUCCESS; +} + +uint32_t FlatObjectStorageEngine::GetItems(const std::string &key, std::map> &data) +{ + if (!isOpened_) { + LOG_ERROR("FlatObjectStorageEngine::GetItems %{public}s not init", key.c_str()); + return ERR_DB_NOT_INIT; + } + std::lock_guard lock(operationMutex_); + if (delegates_.count(key) == 0) { + LOG_ERROR("FlatObjectStorageEngine::GetItems %{public}s not exist", key.c_str()); + return ERR_DB_NOT_EXIST; + } + LOG_INFO("start Get %{public}s", key.c_str()); + std::vector entries; + DistributedDB::DBStatus status = delegates_.at(key)->GetEntries(StringUtils::StrToBytes(""), entries); + if (status != DistributedDB::DBStatus::OK) { + LOG_ERROR("FlatObjectStorageEngine::GetItems item fail status = %{public}d", status); + return status; + } + for (auto &item : entries) { + data[StringUtils::BytesToStr(item.key)] = item.value; + } + LOG_INFO("end Get %{public}s", key.c_str()); + return SUCCESS; +} + +void Watcher::OnChange(const DistributedDB::KvStoreChangedData &data) +{ + std::vector changedData; + std::string tmp; + for (DistributedDB::Entry item : data.GetEntriesInserted()) { + tmp = StringUtils::BytesToStr(item.key); + LOG_INFO("inserted %{public}s", tmp.c_str()); + // property key start with p_, 2 is p_ size + if (tmp.compare(0, FIELDS_PREFIX_LEN, FIELDS_PREFIX) == 0) { + changedData.push_back(tmp.substr(FIELDS_PREFIX_LEN)); + } + } + for (DistributedDB::Entry item : data.GetEntriesUpdated()) { + tmp = StringUtils::BytesToStr(item.key); + LOG_INFO("updated %{public}s", tmp.c_str()); + // property key start with p_, 2 is p_ size + if (tmp.compare(0, FIELDS_PREFIX_LEN, FIELDS_PREFIX) == 0) { + changedData.push_back(tmp.substr(FIELDS_PREFIX_LEN)); + } + } + this->OnChanged(sessionId_, changedData); +} + +Watcher::Watcher(const std::string &sessionId) : sessionId_(sessionId) +{ +} +} // namespace OHOS::ObjectStore diff --git a/data_object/frameworks/innerkitsimpl/src/adaptor/flat_object_store.cpp b/data_object/frameworks/innerkitsimpl/src/adaptor/flat_object_store.cpp new file mode 100644 index 00000000..388f6568 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/adaptor/flat_object_store.cpp @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "flat_object_store.h" + +#include "client_adaptor.h" +#include "distributed_objectstore_impl.h" +#include "logger.h" +#include "object_callback.h" +#include "object_service_proxy.h" +#include "objectstore_errors.h" +#include "softbus_adapter.h" + +namespace OHOS::ObjectStore { +FlatObjectStore::FlatObjectStore(const std::string &bundleName) +{ + bundleName_ = bundleName; + storageEngine_ = std::make_shared(); + uint32_t status = storageEngine_->Open(bundleName); + if (status != SUCCESS) { + LOG_ERROR("FlatObjectStore: Failed to open, error: open storage engine failure %{public}d", status); + } + cacheManager_ = new CacheManager(); +} + +FlatObjectStore::~FlatObjectStore() +{ + if (storageEngine_ != nullptr) { + storageEngine_->Close(); + storageEngine_ = nullptr; + } + delete cacheManager_; + cacheManager_ = nullptr; +} + +uint32_t FlatObjectStore::CreateObject(const std::string &sessionId) +{ + if (!storageEngine_->isOpened_) { + LOG_ERROR("FlatObjectStore::DB has not inited"); + return ERR_DB_NOT_INIT; + } + uint32_t status = storageEngine_->CreateTable(sessionId); + if (status != SUCCESS) { + LOG_ERROR("FlatObjectStore::CreateObject createTable err %{public}d", status); + return status; + } + std::function> &data)> callback = + [sessionId, this]( + const std::map> &data) { + if (data.size() > 0) { + LOG_INFO("objectstore, retrieve success"); + auto result = storageEngine_->UpdateItems(sessionId, data); + if (result != SUCCESS) { + LOG_ERROR("UpdateItems failed, status = %{public}d", result); + } + } else { + LOG_INFO("objectstore, retrieve empty"); + } + }; + cacheManager_->ResumeObject(bundleName_, sessionId, callback); + return SUCCESS; +} + +uint32_t FlatObjectStore::Delete(const std::string &sessionId) +{ + if (!storageEngine_->isOpened_) { + LOG_ERROR("FlatObjectStore::DB has not inited"); + return ERR_DB_NOT_INIT; + } + uint32_t status = storageEngine_->DeleteTable(sessionId); + if (status != SUCCESS) { + LOG_ERROR("FlatObjectStore: Failed to delete object %{public}d", status); + return status; + } + return SUCCESS; +} + +uint32_t FlatObjectStore::Watch(const std::string &sessionId, std::shared_ptr watcher) +{ + if (!storageEngine_->isOpened_) { + LOG_ERROR("FlatObjectStore::DB has not inited"); + return ERR_DB_NOT_INIT; + } + uint32_t status = storageEngine_->RegisterObserver(sessionId, watcher); + if (status != SUCCESS) { + LOG_ERROR("FlatObjectStore::Watch failed %{public}d", status); + } + return status; +} + +uint32_t FlatObjectStore::UnWatch(const std::string &sessionId) +{ + if (!storageEngine_->isOpened_) { + LOG_ERROR("FlatObjectStore::DB has not inited"); + return ERR_DB_NOT_INIT; + } + uint32_t status = storageEngine_->UnRegisterObserver(sessionId); + if (status != SUCCESS) { + LOG_ERROR("FlatObjectStore::Watch failed %{public}d", status); + } + return status; +} + +uint32_t FlatObjectStore::Put(const std::string &sessionId, const std::string &key, std::vector value) +{ + if (!storageEngine_->isOpened_) { + LOG_ERROR("FlatObjectStore::DB has not inited"); + return ERR_DB_NOT_INIT; + } + return storageEngine_->UpdateItem(sessionId, key, value); +} + +uint32_t FlatObjectStore::Get(std::string &sessionId, const std::string &key, Bytes &value) +{ + if (!storageEngine_->isOpened_) { + LOG_ERROR("FlatObjectStore::DB has not inited"); + return ERR_DB_NOT_INIT; + } + return storageEngine_->GetItem(sessionId, key, value); +} + +uint32_t FlatObjectStore::SetStatusNotifier(std::shared_ptr notifier) +{ + if (!storageEngine_->isOpened_) { + LOG_ERROR("FlatObjectStore::DB has not inited"); + return ERR_DB_NOT_INIT; + } + return storageEngine_->SetStatusNotifier(notifier); +} + +uint32_t FlatObjectStore::SyncAllData(const std::string &sessionId, + const std::function &)> &onComplete) +{ + if (!storageEngine_->isOpened_) { + LOG_ERROR("FlatObjectStore::DB has not inited"); + return ERR_DB_NOT_INIT; + } + std::vector devices = SoftBusAdapter::GetInstance()->GetDeviceList(); + std::vector deviceIds; + for (auto item : devices) { + deviceIds.push_back(item.deviceId); + } + return storageEngine_->SyncAllData(sessionId, deviceIds, onComplete); +} + +uint32_t FlatObjectStore::Save(const std::string &sessionId, const std::string &deviceId) +{ + if (cacheManager_ == nullptr) { + LOG_ERROR("FlatObjectStore::cacheManager_ is null"); + return ERR_NULL_PTR; + } + std::map> objectData; + uint32_t status = storageEngine_->GetItems(sessionId, objectData); + if (status != SUCCESS) { + LOG_ERROR("FlatObjectStore::GetItems fail"); + return status; + } + return cacheManager_->Save(bundleName_, sessionId, deviceId, objectData); +} + +uint32_t FlatObjectStore::RevokeSave(const std::string &sessionId) +{ + if (cacheManager_ == nullptr) { + LOG_ERROR("FlatObjectStore::cacheManager_ is null"); + return ERR_NULL_PTR; + } + return cacheManager_->RevokeSave(bundleName_, sessionId); +} + +CacheManager::CacheManager() +{ +} + +uint32_t CacheManager::Save(const std::string &bundleName, const std::string &sessionId, const std::string &deviceId, + const std::map> &objectData) +{ + std::unique_lock lck(mutex_); + ConditionLock conditionLock; + int32_t status = SaveObject(bundleName, sessionId, deviceId, objectData, + [this, &deviceId, &conditionLock](const std::map &results) { + LOG_INFO("CacheManager::task callback"); + if (results.count(deviceId) != 0) { + conditionLock.Notify(results.at(deviceId)); + } else { + conditionLock.Notify(ERR_DB_GET_FAIL); + } + }); + if (status != SUCCESS) { + LOG_ERROR("SaveObject failed"); + return status; + } + LOG_INFO("CacheManager::start wait"); + status = conditionLock.Wait(); + LOG_INFO("CacheManager::end wait, %{public}d", status); + return status == SUCCESS ? status : ERR_DB_GET_FAIL; +} + +uint32_t CacheManager::RevokeSave(const std::string &bundleName, const std::string &sessionId) +{ + std::unique_lock lck(mutex_); + ConditionLock conditionLock; + std::function callback = [this, &conditionLock](int32_t result) { + LOG_INFO("CacheManager::task callback"); + conditionLock.Notify(result); + }; + int32_t status = RevokeSaveObject(bundleName, sessionId, callback); + if (status != SUCCESS) { + LOG_ERROR("RevokeSaveObject failed"); + return status; + } + LOG_INFO("CacheManager::start wait"); + status = conditionLock.Wait(); + LOG_INFO("CacheManager::end wait, %{public}d", status); + return status == SUCCESS ? status : ERR_DB_GET_FAIL; +} + +int32_t CacheManager::SaveObject(const std::string &bundleName, const std::string &sessionId, + const std::string &deviceId, const std::map> &objectData, + const std::function &)> &callback) +{ + sptr proxy = ClientAdaptor::GetObjectService(); + if (proxy == nullptr) { + LOG_ERROR("proxy is nullptr."); + return ERR_NULL_PTR; + } + sptr objectSaveCallback = new ObjectSaveCallback(callback); + int32_t status = proxy->ObjectStoreSave(bundleName, sessionId, deviceId, objectData, objectSaveCallback); + if (status != SUCCESS) { + LOG_ERROR("object save failed code=%d.", static_cast(status)); + } + LOG_INFO("object save successful"); + return status; +} + +int32_t CacheManager::RevokeSaveObject( + const std::string &bundleName, const std::string &sessionId, std::function &callback) +{ + sptr proxy = ClientAdaptor::GetObjectService(); + if (proxy == nullptr) { + LOG_ERROR("proxy is nullptr."); + return ERR_NULL_PTR; + } + sptr objectRevokeSaveCallback = new ObjectRevokeSaveCallback(callback); + int32_t status = proxy->ObjectStoreRevokeSave(bundleName, sessionId, objectRevokeSaveCallback); + if (status != SUCCESS) { + LOG_ERROR("object revoke save failed code=%d.", static_cast(status)); + } + LOG_INFO("object revoke save successful"); + return status; +} + +int32_t CacheManager::ResumeObject(const std::string &bundleName, const std::string &sessionId, + std::function> &data)> &callback) +{ + sptr proxy = ClientAdaptor::GetObjectService(); + if (proxy == nullptr) { + LOG_ERROR("proxy is nullptr."); + return ERR_NULL_PTR; + } + sptr objectRevokeSaveCallback = new ObjectRetrieveCallback(callback); + int32_t status = proxy->ObjectStoreRetrieve(bundleName, sessionId, objectRevokeSaveCallback); + if (status != SUCCESS) { + LOG_ERROR("object resume failed code=%d.", static_cast(status)); + } + LOG_INFO("object resume successful"); + return status; +} +} // namespace OHOS::ObjectStore diff --git a/data_object/frameworks/innerkitsimpl/src/adaptor/object_callback.cpp b/data_object/frameworks/innerkitsimpl/src/adaptor/object_callback.cpp new file mode 100644 index 00000000..408121f7 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/adaptor/object_callback.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "object_callback.h" + +namespace OHOS::ObjectStore { +void ObjectSaveCallback::Completed(const std::map &results) +{ + callback_(results); +} + +ObjectSaveCallback::ObjectSaveCallback(const std::function &)> &callback) + : callback_(callback) +{ +} + +void ObjectRevokeSaveCallback::Completed(int32_t status) +{ + callback_(status); +} + +ObjectRevokeSaveCallback::ObjectRevokeSaveCallback(const std::function &callback) : callback_(callback) +{ +} + +void ObjectRetrieveCallback::Completed(const std::map> &results) +{ + callback_(results); +} + +ObjectRetrieveCallback::ObjectRetrieveCallback( + const std::function> &)> &callback) + : callback_(callback) +{ +} +} // namespace OHOS::DistributedKv diff --git a/data_object/frameworks/innerkitsimpl/src/communicator/app_device_handler.cpp b/data_object/frameworks/innerkitsimpl/src/communicator/app_device_handler.cpp new file mode 100644 index 00000000..2838d3e7 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/communicator/app_device_handler.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "app_device_handler.h" + +#include + +namespace OHOS { +namespace ObjectStore { +AppDeviceHandler::AppDeviceHandler() +{ + softbusAdapter_ = SoftBusAdapter::GetInstance(); +} + +AppDeviceHandler::~AppDeviceHandler() +{ + LOG_INFO("destruct"); +} +void AppDeviceHandler::Init() +{ + softbusAdapter_->Init(); +} + +Status AppDeviceHandler::StartWatchDeviceChange( + const AppDeviceStatusChangeListener *observer, __attribute__((unused)) const PipeInfo &pipeInfo) +{ + return softbusAdapter_->StartWatchDeviceChange(observer, pipeInfo); +} + +Status AppDeviceHandler::StopWatchDeviceChange( + const AppDeviceStatusChangeListener *observer, __attribute__((unused)) const PipeInfo &pipeInfo) +{ + return softbusAdapter_->StopWatchDeviceChange(observer, pipeInfo); +} + +std::vector AppDeviceHandler::GetDeviceList() const +{ + return softbusAdapter_->GetDeviceList(); +} + +DeviceInfo AppDeviceHandler::GetLocalDevice() +{ + return softbusAdapter_->GetLocalDevice(); +} + +DeviceInfo AppDeviceHandler::GetLocalBasicInfo() const +{ + return softbusAdapter_->GetLocalBasicInfo(); +} + +std::vector AppDeviceHandler::GetRemoteNodesBasicInfo() const +{ + return softbusAdapter_->GetRemoteNodesBasicInfo(); +} + +std::string AppDeviceHandler::GetUdidByNodeId(const std::string &nodeId) const +{ + return softbusAdapter_->GetUdidByNodeId(nodeId); +} +} // namespace ObjectStore +} // namespace OHOS diff --git a/data_object/frameworks/innerkitsimpl/src/communicator/app_pipe_handler.cpp b/data_object/frameworks/innerkitsimpl/src/communicator/app_pipe_handler.cpp new file mode 100644 index 00000000..d42b1d07 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/communicator/app_pipe_handler.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "app_pipe_handler.h" + +#include + +#include "logger.h" + +namespace OHOS { +namespace ObjectStore { +using namespace std; + +AppPipeHandler::~AppPipeHandler() +{ + LOG_INFO("destructor pipeId: %{public}s", pipeInfo_.pipeId.c_str()); +} + +AppPipeHandler::AppPipeHandler(const PipeInfo &pipeInfo) : pipeInfo_(pipeInfo) +{ + LOG_INFO("constructor pipeId: %{public}s", pipeInfo_.pipeId.c_str()); + softbusAdapter_ = SoftBusAdapter::GetInstance(); +} + +Status AppPipeHandler::SendData( + const PipeInfo &pipeInfo, const DeviceId &deviceId, const uint8_t *ptr, int size, const MessageInfo &info) +{ + return softbusAdapter_->SendData(pipeInfo, deviceId, ptr, size, info); +} + +Status AppPipeHandler::StartWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo) +{ + return softbusAdapter_->StartWatchDataChange(observer, pipeInfo); +} + +Status AppPipeHandler::StopWatchDataChange( + __attribute__((unused)) const AppDataChangeListener *observer, const PipeInfo &pipeInfo) +{ + return softbusAdapter_->StopWatchDataChange(observer, pipeInfo); +} + +bool AppPipeHandler::IsSameStartedOnPeer( + const struct PipeInfo &pipeInfo, __attribute__((unused)) const struct DeviceId &peer) +{ + return softbusAdapter_->IsSameStartedOnPeer(pipeInfo, peer); +} + +int AppPipeHandler::CreateSessionServer(const std::string &sessionName) const +{ + return softbusAdapter_->CreateSessionServerAdapter(sessionName); +} + +int AppPipeHandler::RemoveSessionServer(const std::string &sessionName) const +{ + return softbusAdapter_->RemoveSessionServerAdapter(sessionName); +} +} // namespace ObjectStore +} // namespace OHOS diff --git a/data_object/frameworks/innerkitsimpl/src/communicator/app_pipe_mgr.cpp b/data_object/frameworks/innerkitsimpl/src/communicator/app_pipe_mgr.cpp new file mode 100644 index 00000000..700d7ba2 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/communicator/app_pipe_mgr.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "app_pipe_mgr.h" + +namespace OHOS { +namespace ObjectStore { +static const int MAX_TRANSFER_SIZE = 1024 * 1024 * 5; +Status AppPipeMgr::StartWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo) +{ + LOG_INFO("begin"); + if (observer == nullptr || pipeInfo.pipeId.empty()) { + LOG_ERROR("argument invalid"); + return Status::INVALID_ARGUMENT; + } + std::lock_guard lock(dataBusMapMutex_); + auto it = dataBusMap_.find(pipeInfo.pipeId); + if (it == dataBusMap_.end()) { + LOG_ERROR("pipeid not found"); + return Status::ERROR; + } + LOG_INFO("end"); + return it->second->StartWatchDataChange(observer, pipeInfo); +} + +// stop DataChangeListener to watch data change; +Status AppPipeMgr::StopWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo) +{ + LOG_INFO("begin"); + if (observer == nullptr || pipeInfo.pipeId.empty()) { + LOG_ERROR("argument invalid"); + return Status::INVALID_ARGUMENT; + } + std::lock_guard lock(dataBusMapMutex_); + auto it = dataBusMap_.find(pipeInfo.pipeId); + if (it == dataBusMap_.end()) { + LOG_ERROR("pipeid not found"); + return Status::ERROR; + } + LOG_INFO("end"); + return it->second->StopWatchDataChange(observer, pipeInfo); +} + +// Send data to other device, function will be called back after sent to notify send result. +Status AppPipeMgr::SendData( + const PipeInfo &pipeInfo, const DeviceId &deviceId, const uint8_t *ptr, int size, const MessageInfo &info) +{ + if (size > MAX_TRANSFER_SIZE || size <= 0 || ptr == nullptr || pipeInfo.pipeId.empty() + || deviceId.deviceId.empty()) { + LOG_WARN("Input is invalid, maxSize:%{public}d, current size:%{public}d", MAX_TRANSFER_SIZE, size); + return Status::ERROR; + } + LOG_DEBUG("pipeInfo:%{public}s ,size:%{public}d", pipeInfo.pipeId.c_str(), size); + std::shared_ptr appPipeHandler; + { + std::lock_guard lock(dataBusMapMutex_); + auto it = dataBusMap_.find(pipeInfo.pipeId); + if (it == dataBusMap_.end()) { + LOG_WARN("pipeInfo:%{public}s not found", pipeInfo.pipeId.c_str()); + return Status::KEY_NOT_FOUND; + } + appPipeHandler = it->second; + } + return appPipeHandler->SendData(pipeInfo, deviceId, ptr, size, info); +} + +// start server +Status AppPipeMgr::Start(const PipeInfo &pipeInfo) +{ + if (pipeInfo.pipeId.empty()) { + LOG_WARN("Start Failed, pipeInfo is empty."); + return Status::INVALID_ARGUMENT; + } + std::lock_guard lock(dataBusMapMutex_); + auto it = dataBusMap_.find(pipeInfo.pipeId); + if (it != dataBusMap_.end()) { + LOG_WARN("repeated start, pipeInfo:%{public}s.", pipeInfo.pipeId.c_str()); + return Status::REPEATED_REGISTER; + } + LOG_DEBUG("Start pipeInfo:%{public}s ", pipeInfo.pipeId.c_str()); + auto handler = std::make_shared(pipeInfo); + if (handler == nullptr) { + LOG_WARN("pipeInfo:%{public}s. new failed", pipeInfo.pipeId.c_str()); + return Status::ILLEGAL_STATE; + } + int ret = handler->CreateSessionServer(pipeInfo.pipeId); + if (ret != 0) { + LOG_WARN("Start pipeInfo:%{public}s, failed ret:%{public}d.", pipeInfo.pipeId.c_str(), ret); + return Status::ILLEGAL_STATE; + } + + dataBusMap_.insert(std::pair>(pipeInfo.pipeId, handler)); + return Status::SUCCESS; +} + +// stop server +Status AppPipeMgr::Stop(const PipeInfo &pipeInfo) +{ + std::shared_ptr appPipeHandler; + { + std::lock_guard lock(dataBusMapMutex_); + auto it = dataBusMap_.find(pipeInfo.pipeId); + if (it == dataBusMap_.end()) { + LOG_WARN("pipeInfo:%{public}s not found", pipeInfo.pipeId.c_str()); + return Status::KEY_NOT_FOUND; + } + appPipeHandler = it->second; + int ret = appPipeHandler->RemoveSessionServer(pipeInfo.pipeId); + if (ret != 0) { + LOG_WARN("Stop pipeInfo:%{public}s ret:%{public}d.", pipeInfo.pipeId.c_str(), ret); + return Status::ERROR; + } + dataBusMap_.erase(pipeInfo.pipeId); + return Status::SUCCESS; + } + return Status::KEY_NOT_FOUND; +} + +bool AppPipeMgr::IsSameStartedOnPeer(const struct PipeInfo &pipeInfo, const struct DeviceId &peer) +{ + LOG_INFO("start"); + if (pipeInfo.pipeId.empty() || peer.deviceId.empty()) { + LOG_ERROR("pipeId or deviceId is empty. Return false."); + return false; + } + LOG_INFO("pipeInfo == [%{public}s]", pipeInfo.pipeId.c_str()); + std::shared_ptr appPipeHandler; + { + std::lock_guard lock(dataBusMapMutex_); + auto it = dataBusMap_.find(pipeInfo.pipeId); + if (it == dataBusMap_.end()) { + LOG_ERROR("pipeInfo:%{public}s not found. Return false.", pipeInfo.pipeId.c_str()); + return false; + } + appPipeHandler = it->second; + } + return appPipeHandler->IsSameStartedOnPeer(pipeInfo, peer); +} +} // namespace ObjectStore +} // namespace OHOS diff --git a/data_object/frameworks/innerkitsimpl/src/communicator/ark_communication_provider.cpp b/data_object/frameworks/innerkitsimpl/src/communicator/ark_communication_provider.cpp new file mode 100644 index 00000000..cdb8dba6 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/communicator/ark_communication_provider.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "ark_communication_provider.h" + +#include + +namespace OHOS { +namespace ObjectStore { +CommunicationProvider &ArkCommunicationProvider::Init() +{ + static ArkCommunicationProvider instance; + if (instance.isInited) { + return instance; + } + LOG_INFO("begin"); + std::lock_guard lock(mutex_); + if (!instance.isInited) { + instance.Initialize(); + } + instance.isInited = true; + LOG_INFO("normal end"); + return instance; +} +ArkCommunicationProvider::ArkCommunicationProvider() + : CommunicationProviderImpl(appPipeMgrImpl_, appDeviceHandlerImpl_) +{ +} +} // namespace ObjectStore +} // namespace OHOS diff --git a/data_object/frameworks/innerkitsimpl/src/communicator/communication_provider.cpp b/data_object/frameworks/innerkitsimpl/src/communicator/communication_provider.cpp new file mode 100644 index 00000000..ffa2a1c4 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/communicator/communication_provider.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "communication_provider.h" + +#include "ark_communication_provider.h" + +namespace OHOS { +namespace ObjectStore { +CommunicationProvider &CommunicationProvider::GetInstance() +{ + return ArkCommunicationProvider::Init(); +} +} // namespace ObjectStore +} // namespace OHOS diff --git a/data_object/frameworks/innerkitsimpl/src/communicator/communication_provider_impl.cpp b/data_object/frameworks/innerkitsimpl/src/communicator/communication_provider_impl.cpp new file mode 100644 index 00000000..95f94f36 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/communicator/communication_provider_impl.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "communication_provider_impl.h" + +#include + +namespace OHOS { +namespace ObjectStore { +std::mutex CommunicationProviderImpl::mutex_; +CommunicationProviderImpl::CommunicationProviderImpl(AppPipeMgr &appPipeMgr, AppDeviceHandler &deviceHandler) + : appPipeMgr_(appPipeMgr), appDeviceHandler_(deviceHandler) +{ +} + +CommunicationProviderImpl::~CommunicationProviderImpl() +{ + LOG_DEBUG("destructor."); +} + +Status CommunicationProviderImpl::Initialize() +{ + appDeviceHandler_.Init(); + return Status::SUCCESS; +} + +Status CommunicationProviderImpl::StartWatchDeviceChange( + const AppDeviceStatusChangeListener *observer, const PipeInfo &pipeInfo) +{ + return appDeviceHandler_.StartWatchDeviceChange(observer, pipeInfo); +} + +Status CommunicationProviderImpl::StopWatchDeviceChange( + const AppDeviceStatusChangeListener *observer, const PipeInfo &pipeInfo) +{ + return appDeviceHandler_.StopWatchDeviceChange(observer, pipeInfo); +} + +DeviceInfo CommunicationProviderImpl::GetLocalDevice() const +{ + return appDeviceHandler_.GetLocalDevice(); +} + +std::vector CommunicationProviderImpl::GetDeviceList() const +{ + return appDeviceHandler_.GetDeviceList(); +} + +Status CommunicationProviderImpl::StartWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo) +{ + return appPipeMgr_.StartWatchDataChange(observer, pipeInfo); +} + +Status CommunicationProviderImpl::StopWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo) +{ + return appPipeMgr_.StopWatchDataChange(observer, pipeInfo); +} + +Status CommunicationProviderImpl::SendData( + const PipeInfo &pipeInfo, const DeviceId &deviceId, const uint8_t *ptr, int size, const MessageInfo &info) +{ + return appPipeMgr_.SendData(pipeInfo, deviceId, ptr, size, info); +} + +Status CommunicationProviderImpl::Start(const PipeInfo &pipeInfo) +{ + return appPipeMgr_.Start(pipeInfo); +} + +Status CommunicationProviderImpl::Stop(const PipeInfo &pipeInfo) +{ + return appPipeMgr_.Stop(pipeInfo); +} + +bool CommunicationProviderImpl::IsSameStartedOnPeer(const PipeInfo &pipeInfo, const DeviceId &peer) const +{ + return appPipeMgr_.IsSameStartedOnPeer(pipeInfo, peer); +} +} // namespace ObjectStore +} // namespace OHOS diff --git a/data_object/frameworks/innerkitsimpl/src/communicator/process_communicator_impl.cpp b/data_object/frameworks/innerkitsimpl/src/communicator/process_communicator_impl.cpp new file mode 100644 index 00000000..05265887 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/communicator/process_communicator_impl.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "process_communicator_impl.h" + +#include + +namespace OHOS { +namespace ObjectStore { +using namespace DistributedDB; +ProcessCommunicatorImpl::ProcessCommunicatorImpl() +{ +} + +ProcessCommunicatorImpl::~ProcessCommunicatorImpl() +{ + LOG_ERROR("destructor."); +} + +DBStatus ProcessCommunicatorImpl::Start(const std::string &processLabel) +{ + LOG_INFO("init commProvider"); + thisProcessLabel_ = processLabel; + PipeInfo pi = { thisProcessLabel_ }; + Status errCode = CommunicationProvider::GetInstance().Start(pi); + if (errCode != Status::SUCCESS) { + LOG_ERROR("commProvider_ Start Fail."); + return DBStatus::DB_ERROR; + } + return DBStatus::OK; +} + +DBStatus ProcessCommunicatorImpl::Stop() +{ + PipeInfo pi = { thisProcessLabel_ }; + Status errCode = CommunicationProvider::GetInstance().Stop(pi); + if (errCode != Status::SUCCESS) { + LOG_ERROR("commProvider_ Stop Fail."); + return DBStatus::DB_ERROR; + } + return DBStatus::OK; +} + +DBStatus ProcessCommunicatorImpl::RegOnDeviceChange(const OnDeviceChange &callback) +{ + { + std::lock_guard onDeviceChangeLockGard(onDeviceChangeMutex_); + onDeviceChangeHandler_ = callback; + } + + PipeInfo pi = { thisProcessLabel_ }; + if (callback) { + Status errCode = CommunicationProvider::GetInstance().StartWatchDeviceChange(this, pi); + if (errCode != Status::SUCCESS) { + LOG_ERROR("commProvider_ StartWatchDeviceChange Fail."); + return DBStatus::DB_ERROR; + } + } else { + Status errCode = CommunicationProvider::GetInstance().StopWatchDeviceChange(this, pi); + if (errCode != Status::SUCCESS) { + LOG_ERROR("commProvider_ StopWatchDeviceChange Fail."); + return DBStatus::DB_ERROR; + } + } + + return DBStatus::OK; +} + +DBStatus ProcessCommunicatorImpl::RegOnDataReceive(const OnDataReceive &callback) +{ + { + std::lock_guard onDataReceiveLockGard(onDataReceiveMutex_); + onDataReceiveHandler_ = callback; + } + + PipeInfo pi = { thisProcessLabel_ }; + if (callback) { + Status errCode = CommunicationProvider::GetInstance().StartWatchDataChange(this, pi); + if (errCode != Status::SUCCESS) { + LOG_ERROR("commProvider_ StartWatchDataChange Fail."); + return DBStatus::DB_ERROR; + } + } else { + Status errCode = CommunicationProvider::GetInstance().StopWatchDataChange(this, pi); + if (errCode != Status::SUCCESS) { + LOG_ERROR("commProvider_ StopWatchDataChange Fail."); + return DBStatus::DB_ERROR; + } + } + return DBStatus::OK; +} + +DBStatus ProcessCommunicatorImpl::SendData(const DeviceInfos &dstDevInfo, const uint8_t *data, uint32_t length) +{ + PipeInfo pi = { thisProcessLabel_ }; + DeviceId destination; + destination.deviceId = dstDevInfo.identifier; + Status errCode = CommunicationProvider::GetInstance().SendData(pi, destination, data, static_cast(length)); + if (errCode != Status::SUCCESS) { + LOG_ERROR("commProvider_ SendData Fail."); + return DBStatus::DB_ERROR; + } + + return DBStatus::OK; +} + +uint32_t ProcessCommunicatorImpl::GetMtuSize() +{ + return MTU_SIZE; +} + +uint32_t ProcessCommunicatorImpl::GetMtuSize(const DeviceInfos &devInfo) +{ + LOG_INFO("GetMtuSize start"); + std::vector devInfos = CommunicationProvider::GetInstance().GetDeviceList(); + for (auto const &entry : devInfos) { + LOG_INFO("GetMtuSize deviceType: %{public}s", entry.deviceType.c_str()); + bool isWatch = (entry.deviceType == SMART_WATCH_TYPE || entry.deviceType == CHILDREN_WATCH_TYPE); + if (entry.deviceId == devInfo.identifier && isWatch) { + return MTU_SIZE_WATCH; + } + } + return MTU_SIZE; +} + +DeviceInfos ProcessCommunicatorImpl::GetLocalDeviceInfos() +{ + DeviceInfos localDevInfos; + DeviceInfo devInfo = CommunicationProvider::GetInstance().GetLocalDevice(); + localDevInfos.identifier = devInfo.deviceId; + return localDevInfos; +} + +std::vector ProcessCommunicatorImpl::GetRemoteOnlineDeviceInfosList() +{ + std::vector remoteDevInfos; + std::vector devInfoVec = CommunicationProvider::GetInstance().GetDeviceList(); + for (auto const &entry : devInfoVec) { + DeviceInfos remoteDev; + remoteDev.identifier = entry.deviceId; + remoteDevInfos.push_back(remoteDev); + } + return remoteDevInfos; +} + +bool ProcessCommunicatorImpl::IsSameProcessLabelStartedOnPeerDevice(const DeviceInfos &peerDevInfo) +{ + PipeInfo pi = { thisProcessLabel_ }; + DeviceId di = { peerDevInfo.identifier }; + return CommunicationProvider::GetInstance().IsSameStartedOnPeer(pi, di); +} + +void ProcessCommunicatorImpl::OnMessage( + const DeviceInfo &info, const uint8_t *ptr, const int size, __attribute__((unused)) const PipeInfo &pipeInfo) const +{ + std::lock_guard onDataReceiveLockGuard(onDataReceiveMutex_); + if (onDataReceiveHandler_ == nullptr) { + LOG_ERROR("onDataReceiveHandler_ invalid."); + return; + } + DeviceInfos devInfo; + devInfo.identifier = info.deviceId; + onDataReceiveHandler_(devInfo, ptr, static_cast(size)); +} + +void ProcessCommunicatorImpl::OnDeviceChanged(const DeviceInfo &info, const DeviceChangeType &type) const +{ + std::lock_guard onDeviceChangeLockGuard(onDeviceChangeMutex_); + if (onDeviceChangeHandler_ == nullptr) { + LOG_ERROR("onDeviceChangeHandler_ invalid."); + return; + } + DeviceInfos devInfo; + devInfo.identifier = info.deviceId; + onDeviceChangeHandler_(devInfo, (type == DeviceChangeType::DEVICE_ONLINE)); +} +} // namespace ObjectStore +} // namespace OHOS diff --git a/data_object/frameworks/innerkitsimpl/src/communicator/softbus_adapter_standard.cpp b/data_object/frameworks/innerkitsimpl/src/communicator/softbus_adapter_standard.cpp new file mode 100644 index 00000000..00b05690 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/src/communicator/softbus_adapter_standard.cpp @@ -0,0 +1,725 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "kv_store_delegate_manager.h" +#include "process_communicator_impl.h" +#include "securec.h" +#include "session.h" +#include "softbus_adapter.h" +#include "softbus_bus_center.h" + +namespace OHOS { +namespace ObjectStore { +constexpr int32_t HEAD_SIZE = 3; +constexpr int32_t END_SIZE = 3; +constexpr int32_t MIN_SIZE = HEAD_SIZE + END_SIZE + 3; +constexpr const char *REPLACE_CHAIN = "***"; +constexpr const char *DEFAULT_ANONYMOUS = "******"; +constexpr int32_t SOFTBUS_OK = 0; +constexpr int32_t SOFTBUS_ERR = 1; +constexpr int32_t INVALID_SESSION_ID = -1; +constexpr int32_t SESSION_NAME_SIZE_MAX = 65; +constexpr int32_t DEVICE_ID_SIZE_MAX = 65; +constexpr int32_t ID_BUF_LEN = 65; +using namespace std; + +class AppDeviceListenerWrap { +public: + explicit AppDeviceListenerWrap() + { + } + ~AppDeviceListenerWrap() + { + } + static void SetDeviceHandler(SoftBusAdapter *handler); + static void OnDeviceOnline(NodeBasicInfo *info); + static void OnDeviceOffline(NodeBasicInfo *info); + static void OnDeviceInfoChanged(NodeBasicInfoType type, NodeBasicInfo *info); + +private: + static SoftBusAdapter *softBusAdapter_; + static void NotifyAll(NodeBasicInfo *info, DeviceChangeType type); +}; +SoftBusAdapter *AppDeviceListenerWrap::softBusAdapter_; + +class AppDataListenerWrap { +public: + static void SetDataHandler(SoftBusAdapter *handler); + static int OnSessionOpened(int sessionId, int result); + static void OnSessionClosed(int sessionId); + static void OnMessageReceived(int sessionId, const void *data, unsigned int dataLen); + static void OnBytesReceived(int sessionId, const void *data, unsigned int dataLen); + +public: + // notifiy all listeners when received message + static void NotifyDataListeners( + const uint8_t *ptr, const int size, const std::string &deviceId, const PipeInfo &pipeInfo); + static SoftBusAdapter *softBusAdapter_; +}; +SoftBusAdapter *AppDataListenerWrap::softBusAdapter_; +std::shared_ptr SoftBusAdapter::instance_; + +void AppDeviceListenerWrap::OnDeviceInfoChanged(NodeBasicInfoType type, NodeBasicInfo *info) +{ + std::string udid = softBusAdapter_->GetUdidByNodeId(std::string(info->networkId)); + LOG_INFO("[InfoChange] type:%{public}d, id:%{public}s, name:%{public}s", type, + SoftBusAdapter::ToBeAnonymous(udid).c_str(), info->deviceName); +} + +void AppDeviceListenerWrap::OnDeviceOffline(NodeBasicInfo *info) +{ + std::string udid = softBusAdapter_->GetUdidByNodeId(std::string(info->networkId)); + LOG_INFO("[Offline] id:%{public}s, name:%{public}s, typeId:%{public}d", + SoftBusAdapter::ToBeAnonymous(udid).c_str(), info->deviceName, info->deviceTypeId); + NotifyAll(info, DeviceChangeType::DEVICE_OFFLINE); +} + +void AppDeviceListenerWrap::OnDeviceOnline(NodeBasicInfo *info) +{ + std::string udid = softBusAdapter_->GetUdidByNodeId(std::string(info->networkId)); + LOG_INFO("[Online] id:%{public}s, name:%{public}s, typeId:%{public}d", SoftBusAdapter::ToBeAnonymous(udid).c_str(), + info->deviceName, info->deviceTypeId); + NotifyAll(info, DeviceChangeType::DEVICE_ONLINE); +} + +void AppDeviceListenerWrap::SetDeviceHandler(SoftBusAdapter *handler) +{ + LOG_INFO("SetDeviceHandler."); + softBusAdapter_ = handler; +} + +void AppDeviceListenerWrap::NotifyAll(NodeBasicInfo *info, DeviceChangeType type) +{ + DeviceInfo di = { std::string(info->networkId), std::string(info->deviceName), std::to_string(info->deviceTypeId) }; + softBusAdapter_->NotifyAll(di, type); +} + +SoftBusAdapter::SoftBusAdapter() +{ + LOG_INFO("begin"); + AppDeviceListenerWrap::SetDeviceHandler(this); + AppDataListenerWrap::SetDataHandler(this); + + nodeStateCb_.events = EVENT_NODE_STATE_MASK; + nodeStateCb_.onNodeOnline = AppDeviceListenerWrap::OnDeviceOnline; + nodeStateCb_.onNodeOffline = AppDeviceListenerWrap::OnDeviceOffline; + nodeStateCb_.onNodeBasicInfoChanged = AppDeviceListenerWrap::OnDeviceInfoChanged; + + sessionListener_.OnSessionOpened = AppDataListenerWrap::OnSessionOpened; + sessionListener_.OnSessionClosed = AppDataListenerWrap::OnSessionClosed; + sessionListener_.OnBytesReceived = AppDataListenerWrap::OnBytesReceived; + sessionListener_.OnMessageReceived = AppDataListenerWrap::OnMessageReceived; +} + +SoftBusAdapter::~SoftBusAdapter() +{ + LOG_INFO("begin"); + int32_t errNo = UnregNodeDeviceStateCb(&nodeStateCb_); + if (errNo != SOFTBUS_OK) { + LOG_ERROR("UnregNodeDeviceStateCb fail %{public}d", errNo); + } +} + +void SoftBusAdapter::Init() +{ + LOG_INFO("begin"); + std::thread th = std::thread([&]() { + int i = 0; + constexpr int RETRY_TIMES = 300; + while (i++ < RETRY_TIMES) { + int32_t errNo = RegNodeDeviceStateCb("ohos.objectstore", &nodeStateCb_); + if (errNo != SOFTBUS_OK) { + LOG_ERROR("RegNodeDeviceStateCb fail %{public}d, time:%{public}d", errNo, i); + std::this_thread::sleep_for(std::chrono::seconds(1)); + continue; + } + LOG_INFO("RegNodeDeviceStateCb success"); + return; + } + LOG_ERROR("Init failed %{public}d times and exit now.", RETRY_TIMES); + }); + th.detach(); +} + +Status SoftBusAdapter::StartWatchDeviceChange( + const AppDeviceStatusChangeListener *observer, __attribute__((unused)) const PipeInfo &pipeInfo) +{ + LOG_INFO("begin"); + if (observer == nullptr) { + LOG_WARN("observer is null."); + return Status::ERROR; + } + std::lock_guard lock(deviceChangeMutex_); + auto result = listeners_.insert(observer); + if (!result.second) { + LOG_WARN("Add listener error."); + return Status::ERROR; + } + LOG_INFO("end"); + return Status::SUCCESS; +} + +Status SoftBusAdapter::StopWatchDeviceChange( + const AppDeviceStatusChangeListener *observer, __attribute__((unused)) const PipeInfo &pipeInfo) +{ + LOG_INFO("begin"); + if (observer == nullptr) { + LOG_WARN("observer is null."); + return Status::ERROR; + } + std::lock_guard lock(deviceChangeMutex_); + auto result = listeners_.erase(observer); + if (result <= 0) { + return Status::ERROR; + } + LOG_INFO("end"); + return Status::SUCCESS; +} + +void SoftBusAdapter::NotifyAll(const DeviceInfo &deviceInfo, const DeviceChangeType &type) +{ + std::thread th = std::thread([this, deviceInfo, type]() { + std::vector listeners; + { + std::lock_guard lock(deviceChangeMutex_); + for (const auto &listener : listeners_) { + listeners.push_back(listener); + } + } + LOG_DEBUG("high"); + std::string udid = GetUdidByNodeId(deviceInfo.deviceId); + LOG_DEBUG("[Notify] to DB from: %{public}s, type:%{public}d", ToBeAnonymous(udid).c_str(), type); + UpdateRelationship(deviceInfo.deviceId, type); + for (const auto &device : listeners) { + if (device == nullptr) { + continue; + } + if (device->GetChangeLevelType() == ChangeLevelType::HIGH) { + DeviceInfo di = { udid, deviceInfo.deviceName, deviceInfo.deviceType }; + device->OnDeviceChanged(di, type); + break; + } + } + LOG_DEBUG("low"); + for (const auto &device : listeners) { + if (device == nullptr) { + continue; + } + if (device->GetChangeLevelType() == ChangeLevelType::LOW) { + DeviceInfo di = { udid, deviceInfo.deviceName, deviceInfo.deviceType }; + device->OnDeviceChanged(di, DeviceChangeType::DEVICE_OFFLINE); + device->OnDeviceChanged(di, type); + } + } + LOG_DEBUG("min"); + for (const auto &device : listeners) { + if (device == nullptr) { + continue; + } + if (device->GetChangeLevelType() == ChangeLevelType::MIN) { + DeviceInfo di = { udid, deviceInfo.deviceName, deviceInfo.deviceType }; + device->OnDeviceChanged(di, type); + } + } + }); + th.detach(); +} + +std::vector SoftBusAdapter::GetDeviceList() const +{ + std::vector dis; + NodeBasicInfo *info = nullptr; + int32_t infoNum = 0; + dis.clear(); + + int32_t ret = GetAllNodeDeviceInfo("ohos.objectstore", &info, &infoNum); + if (ret != SOFTBUS_OK) { + LOG_ERROR("GetAllNodeDeviceInfo error"); + return dis; + } + LOG_INFO("GetAllNodeDeviceInfo success infoNum=%{public}d", infoNum); + + for (int i = 0; i < infoNum; i++) { + std::string udid = GetUdidByNodeId(std::string(info[i].networkId)); + DeviceInfo deviceInfo = { udid, std::string(info[i].deviceName), std::to_string(info[i].deviceTypeId) }; + dis.push_back(deviceInfo); + } + if (info != nullptr) { + FreeNodeInfo(info); + } + return dis; +} + +DeviceInfo SoftBusAdapter::GetLocalDevice() +{ + if (!localInfo_.deviceId.empty()) { + return localInfo_; + } + + NodeBasicInfo info; + int32_t ret = GetLocalNodeDeviceInfo("ohos.objectstore", &info); + if (ret != SOFTBUS_OK) { + LOG_ERROR("GetLocalNodeDeviceInfo error"); + return DeviceInfo(); + } + std::string uuid = GetUdidByNodeId(std::string(info.networkId)); + LOG_DEBUG("[LocalDevice] id:%{private}s, name:%{private}s, type:%{private}d", ToBeAnonymous(uuid).c_str(), + info.deviceName, info.deviceTypeId); + localInfo_ = { uuid, std::string(info.deviceName), std::to_string(info.deviceTypeId) }; + return localInfo_; +} + +std::string SoftBusAdapter::GetUdidByNodeId(const std::string &nodeId) const +{ + char udid[ID_BUF_LEN] = { 0 }; + int32_t ret = GetNodeKeyInfo("ohos.objectstore", nodeId.c_str(), NodeDeviceInfoKey::NODE_KEY_UDID, + reinterpret_cast(udid), ID_BUF_LEN); + if (ret != SOFTBUS_OK) { + LOG_WARN("GetNodeKeyInfo error, nodeId:%{public}s", ToBeAnonymous(nodeId).c_str()); + return ""; + } + return std::string(udid); +} + +DeviceInfo SoftBusAdapter::GetLocalBasicInfo() const +{ + LOG_DEBUG("begin"); + NodeBasicInfo info; + int32_t ret = GetLocalNodeDeviceInfo("ohos.objectstore", &info); + if (ret != SOFTBUS_OK) { + LOG_ERROR("GetLocalNodeDeviceInfo error"); + return DeviceInfo(); + } + LOG_DEBUG("[LocalBasicInfo] networkId:%{private}s, name:%{private}s, " + "type:%{private}d", + ToBeAnonymous(std::string(info.networkId)).c_str(), info.deviceName, info.deviceTypeId); + DeviceInfo localInfo = { std::string(info.networkId), std::string(info.deviceName), + std::to_string(info.deviceTypeId) }; + return localInfo; +} + +std::vector SoftBusAdapter::GetRemoteNodesBasicInfo() const +{ + LOG_DEBUG("begin"); + std::vector dis; + NodeBasicInfo *info = nullptr; + int32_t infoNum = 0; + dis.clear(); + + int32_t ret = GetAllNodeDeviceInfo("ohos.objectstore", &info, &infoNum); + if (ret != SOFTBUS_OK) { + LOG_ERROR("GetAllNodeDeviceInfo error"); + return dis; + } + LOG_DEBUG("GetAllNodeDeviceInfo success infoNum=%{public}d", infoNum); + + for (int i = 0; i < infoNum; i++) { + dis.push_back( + { std::string(info[i].networkId), std::string(info[i].deviceName), std::to_string(info[i].deviceTypeId) }); + } + if (info != nullptr) { + FreeNodeInfo(info); + } + return dis; +} + +void SoftBusAdapter::UpdateRelationship(const std::string &networkid, const DeviceChangeType &type) +{ + auto udid = GetUdidByNodeId(networkid); + lock_guard lock(networkMutex_); + switch (type) { + case DeviceChangeType::DEVICE_OFFLINE: { + auto size = this->networkId2Udid_.erase(networkid); + if (size == 0) { + LOG_WARN("not found id:%{public}s.", networkid.c_str()); + } + break; + } + case DeviceChangeType::DEVICE_ONLINE: { + std::pair value = { networkid, udid }; + auto res = this->networkId2Udid_.insert(std::move(value)); + if (!res.second) { + LOG_WARN("insert failed."); + } + break; + } + default: { + LOG_WARN("unknown type."); + break; + } + } +} +std::string SoftBusAdapter::ToNodeID(const std::string &nodeId) const +{ + { + lock_guard lock(networkMutex_); + for (auto const &e : networkId2Udid_) { + if (nodeId == e.second) { // id is udid + return e.first; + } + } + } + + LOG_WARN("get the network id from devices."); + std::vector devices; + NodeBasicInfo *info = nullptr; + int32_t infoNum = 0; + std::string networkId; + int32_t ret = GetAllNodeDeviceInfo("ohos.objectstore", &info, &infoNum); + if (ret == SOFTBUS_OK) { + lock_guard lock(networkMutex_); + for (int i = 0; i < infoNum; i++) { + if (networkId2Udid_.find(info[i].networkId) != networkId2Udid_.end()) { + continue; + } + auto udid = GetUdidByNodeId(std::string(info[i].networkId)); + networkId2Udid_.insert({ info[i].networkId, udid }); + if (udid == nodeId) { + networkId = info[i].networkId; + } + } + } + if (info != nullptr) { + FreeNodeInfo(info); + } + return networkId; +} + +std::string SoftBusAdapter::ToBeAnonymous(const std::string &name) +{ + if (name.length() <= HEAD_SIZE) { + return DEFAULT_ANONYMOUS; + } + + if (name.length() < MIN_SIZE) { + return (name.substr(0, HEAD_SIZE) + REPLACE_CHAIN); + } + + return (name.substr(0, HEAD_SIZE) + REPLACE_CHAIN + name.substr(name.length() - END_SIZE, END_SIZE)); +} + +std::shared_ptr SoftBusAdapter::GetInstance() +{ + static std::once_flag onceFlag; + std::call_once(onceFlag, [&] { instance_ = std::make_shared(); }); + return instance_; +} + +Status SoftBusAdapter::StartWatchDataChange(const AppDataChangeListener *observer, const PipeInfo &pipeInfo) +{ + LOG_DEBUG("begin"); + if (observer == nullptr) { + return Status::INVALID_ARGUMENT; + } + lock_guard lock(dataChangeMutex_); + auto it = dataChangeListeners_.find(pipeInfo.pipeId); + if (it != dataChangeListeners_.end()) { + LOG_WARN("Add listener error or repeated adding."); + return Status::ERROR; + } + LOG_DEBUG("current appid %{public}s", pipeInfo.pipeId.c_str()); + dataChangeListeners_.insert({ pipeInfo.pipeId, observer }); + return Status::SUCCESS; +} + +Status SoftBusAdapter::StopWatchDataChange( + __attribute__((unused)) const AppDataChangeListener *observer, const PipeInfo &pipeInfo) +{ + LOG_DEBUG("begin"); + lock_guard lock(dataChangeMutex_); + if (dataChangeListeners_.erase(pipeInfo.pipeId)) { + return Status::SUCCESS; + } + LOG_WARN("stop data observer error, pipeInfo:%{public}s", pipeInfo.pipeId.c_str()); + return Status::ERROR; +} + +Status SoftBusAdapter::SendData( + const PipeInfo &pipeInfo, const DeviceId &deviceId, const uint8_t *ptr, int size, const MessageInfo &info) +{ + SessionAttribute attr; + attr.dataType = TYPE_BYTES; + LOG_INFO("[SendData] to %{public}s ,session:%{public}s, size:%{public}d", + ToBeAnonymous(deviceId.deviceId).c_str(), pipeInfo.pipeId.c_str(), size); + int sessionId = OpenSession( + pipeInfo.pipeId.c_str(), pipeInfo.pipeId.c_str(), ToNodeID(deviceId.deviceId).c_str(), "GROUP_ID", &attr); + if (sessionId < 0) { + LOG_WARN("OpenSession %{public}s, type:%{public}d failed, sessionId:%{public}d", pipeInfo.pipeId.c_str(), + info.msgType, sessionId); + return Status::CREATE_SESSION_ERROR; + } + int state = GetSessionStatus(sessionId); + LOG_DEBUG("Waited for notification, state:%{public}d", state); + if (state != SOFTBUS_OK) { + LOG_ERROR("OpenSession callback result error"); + return Status::CREATE_SESSION_ERROR; + } + LOG_DEBUG("[SendBytes] start,session id is %{public}d, size is %{public}d, " + "session type is %{public}d.", + sessionId, size, attr.dataType); + int32_t ret = SendBytes(sessionId, (void *)ptr, size); + if (ret != SOFTBUS_OK) { + LOG_ERROR("[SendBytes] to %{public}d failed, ret:%{public}d.", sessionId, ret); + return Status::ERROR; + } + return Status::SUCCESS; +} + +int32_t SoftBusAdapter::GetSessionStatus(int32_t sessionId) +{ + auto semaphore = GetSemaphore(sessionId); + return semaphore->Wait(); +} + +void SoftBusAdapter::OnSessionOpen(int32_t sessionId, int32_t status) +{ + auto semaphore = GetSemaphore(sessionId); + semaphore->Notify(status); +} + +void SoftBusAdapter::OnSessionClose(int32_t sessionId) +{ + lock_guard lock(statusMutex_); + auto it = sessionsStatus_.find(sessionId); + if (it != sessionsStatus_.end()) { + it->second->Clear(); + sessionsStatus_.erase(it); + } +} + +std::shared_ptr> SoftBusAdapter::GetSemaphore(int32_t sessionId) +{ + lock_guard lock(statusMutex_); + if (sessionsStatus_.find(sessionId) == sessionsStatus_.end()) { + sessionsStatus_.emplace(sessionId, std::make_shared>()); + } + return sessionsStatus_[sessionId]; +} + +bool SoftBusAdapter::IsSameStartedOnPeer( + const struct PipeInfo &pipeInfo, __attribute__((unused)) const struct DeviceId &peer) +{ + LOG_INFO( + "pipeInfo:%{public}s peer.deviceId:%{public}s", pipeInfo.pipeId.c_str(), ToBeAnonymous(peer.deviceId).c_str()); + { + lock_guard lock(busSessionMutex_); + if (busSessionMap_.find(pipeInfo.pipeId + peer.deviceId) != busSessionMap_.end()) { + LOG_INFO("Found session in map. Return true."); + return true; + } + } + SessionAttribute attr; + attr.dataType = TYPE_BYTES; + int sessionId = OpenSession( + pipeInfo.pipeId.c_str(), pipeInfo.pipeId.c_str(), ToNodeID(peer.deviceId).c_str(), "GROUP_ID", &attr); + LOG_INFO("[IsSameStartedOnPeer] sessionId=%{public}d", sessionId); + if (sessionId == INVALID_SESSION_ID) { + LOG_ERROR("OpenSession return null, pipeInfo:%{public}s. Return false.", pipeInfo.pipeId.c_str()); + return false; + } + LOG_INFO("session started, pipeInfo:%{public}s. sessionId:%{public}d Return " + "true. ", + pipeInfo.pipeId.c_str(), sessionId); + return true; +} + +void SoftBusAdapter::SetMessageTransFlag(const PipeInfo &pipeInfo, bool flag) +{ + LOG_INFO("pipeInfo: %{public}s flag: %{public}d", pipeInfo.pipeId.c_str(), static_cast(flag)); + flag_ = flag; +} + +int SoftBusAdapter::CreateSessionServerAdapter(const std::string &sessionName) +{ + LOG_DEBUG("begin"); + return CreateSessionServer("ohos.objectstore", sessionName.c_str(), &sessionListener_); +} + +int SoftBusAdapter::RemoveSessionServerAdapter(const std::string &sessionName) const +{ + LOG_DEBUG("begin"); + return RemoveSessionServer("ohos.objectstore", sessionName.c_str()); +} + +void SoftBusAdapter::InsertSession(const std::string &sessionName) +{ + lock_guard lock(busSessionMutex_); + busSessionMap_.insert({sessionName, true}); +} + +void SoftBusAdapter::DeleteSession(const std::string &sessionName) +{ + lock_guard lock(busSessionMutex_); + busSessionMap_.erase(sessionName); +} + +void SoftBusAdapter::NotifyDataListeners( + const uint8_t *ptr, const int size, const std::string &deviceId, const PipeInfo &pipeInfo) +{ + LOG_DEBUG("begin"); + lock_guard lock(dataChangeMutex_); + auto it = dataChangeListeners_.find(pipeInfo.pipeId); + if (it != dataChangeListeners_.end()) { + LOG_DEBUG("ready to notify, pipeName:%{public}s, deviceId:%{public}s.", pipeInfo.pipeId.c_str(), + ToBeAnonymous(deviceId).c_str()); + DeviceInfo deviceInfo = { deviceId, "", "" }; + it->second->OnMessage(deviceInfo, ptr, size, pipeInfo); + return; + } + LOG_WARN("no listener %{public}s.", pipeInfo.pipeId.c_str()); +} + +void AppDataListenerWrap::SetDataHandler(SoftBusAdapter *handler) +{ + LOG_INFO("begin"); + softBusAdapter_ = handler; +} + +int AppDataListenerWrap::OnSessionOpened(int sessionId, int result) +{ + LOG_INFO("[SessionOpen] sessionId:%{public}d, result:%{public}d", sessionId, result); + char mySessionName[SESSION_NAME_SIZE_MAX] = ""; + char peerSessionName[SESSION_NAME_SIZE_MAX] = ""; + char peerDevId[DEVICE_ID_SIZE_MAX] = ""; + softBusAdapter_->OnSessionOpen(sessionId, result); + if (result != SOFTBUS_OK) { + LOG_WARN("session %{public}d open failed, result:%{public}d.", sessionId, result); + return result; + } + int ret = GetMySessionName(sessionId, mySessionName, sizeof(mySessionName)); + if (ret != SOFTBUS_OK) { + LOG_WARN("get my session name failed, session id is %{public}d.", sessionId); + return SOFTBUS_ERR; + } + ret = GetPeerSessionName(sessionId, peerSessionName, sizeof(peerSessionName)); + if (ret != SOFTBUS_OK) { + LOG_WARN("get my peer session name failed, session id is %{public}d.", sessionId); + return SOFTBUS_ERR; + } + ret = GetPeerDeviceId(sessionId, peerDevId, sizeof(peerDevId)); + if (ret != SOFTBUS_OK) { + LOG_WARN("get my peer device id failed, session id is %{public}d.", sessionId); + return SOFTBUS_ERR; + } + std::string peerUdid = softBusAdapter_->GetUdidByNodeId(std::string(peerDevId)); + LOG_DEBUG("[SessionOpen] mySessionName:%{public}s, " + "peerSessionName:%{public}s, peerDevId:%{public}s", + mySessionName, peerSessionName, SoftBusAdapter::ToBeAnonymous(peerUdid).c_str()); + + if (strlen(peerSessionName) < 1) { + softBusAdapter_->InsertSession(std::string(mySessionName) + peerUdid); + } else { + softBusAdapter_->InsertSession(std::string(peerSessionName) + peerUdid); + } + return 0; +} + +void AppDataListenerWrap::OnSessionClosed(int sessionId) +{ + LOG_INFO("[SessionClosed] sessionId:%{public}d", sessionId); + char mySessionName[SESSION_NAME_SIZE_MAX] = ""; + char peerSessionName[SESSION_NAME_SIZE_MAX] = ""; + char peerDevId[DEVICE_ID_SIZE_MAX] = ""; + + softBusAdapter_->OnSessionClose(sessionId); + int ret = GetMySessionName(sessionId, mySessionName, sizeof(mySessionName)); + if (ret != SOFTBUS_OK) { + LOG_WARN("get my session name failed, session id is %{public}d.", sessionId); + return; + } + ret = GetPeerSessionName(sessionId, peerSessionName, sizeof(peerSessionName)); + if (ret != SOFTBUS_OK) { + LOG_WARN("get my peer session name failed, session id is %{public}d.", sessionId); + return; + } + ret = GetPeerDeviceId(sessionId, peerDevId, sizeof(peerDevId)); + if (ret != SOFTBUS_OK) { + LOG_WARN("get my peer device id failed, session id is %{public}d.", sessionId); + return; + } + std::string peerUdid = softBusAdapter_->GetUdidByNodeId(std::string(peerDevId)); + LOG_DEBUG("[SessionClosed] mySessionName:%{public}s, " + "peerSessionName:%{public}s, peerDevId:%{public}s", + mySessionName, peerSessionName, SoftBusAdapter::ToBeAnonymous(peerUdid).c_str()); + + if (strlen(peerSessionName) < 1) { + softBusAdapter_->DeleteSession(std::string(mySessionName) + peerUdid); + } else { + softBusAdapter_->DeleteSession(std::string(peerSessionName) + peerUdid); + } +} + +void AppDataListenerWrap::OnMessageReceived(int sessionId, const void *data, unsigned int dataLen) +{ + LOG_INFO("begin"); + if (sessionId == INVALID_SESSION_ID) { + return; + } + char peerSessionName[SESSION_NAME_SIZE_MAX] = ""; + char peerDevId[DEVICE_ID_SIZE_MAX] = ""; + int ret = GetPeerSessionName(sessionId, peerSessionName, sizeof(peerSessionName)); + if (ret != SOFTBUS_OK) { + LOG_WARN("get my peer session name failed, session id is %{public}d.", sessionId); + return; + } + ret = GetPeerDeviceId(sessionId, peerDevId, sizeof(peerDevId)); + if (ret != SOFTBUS_OK) { + LOG_WARN("get my peer device id failed, session id is %{public}d.", sessionId); + return; + } + std::string peerUdid = softBusAdapter_->GetUdidByNodeId(std::string(peerDevId)); + LOG_DEBUG("[MessageReceived] session id:%{public}d, " + "peerSessionName:%{public}s, peerDevId:%{public}s", + sessionId, peerSessionName, SoftBusAdapter::ToBeAnonymous(peerUdid).c_str()); + NotifyDataListeners(reinterpret_cast(data), dataLen, peerUdid, { std::string(peerSessionName) }); +} + +void AppDataListenerWrap::OnBytesReceived(int sessionId, const void *data, unsigned int dataLen) +{ + LOG_INFO("begin"); + if (sessionId == INVALID_SESSION_ID) { + return; + } + char peerSessionName[SESSION_NAME_SIZE_MAX] = ""; + char peerDevId[DEVICE_ID_SIZE_MAX] = ""; + int ret = GetPeerSessionName(sessionId, peerSessionName, sizeof(peerSessionName)); + if (ret != SOFTBUS_OK) { + LOG_WARN("get my peer session name failed, session id is %{public}d.", sessionId); + return; + } + ret = GetPeerDeviceId(sessionId, peerDevId, sizeof(peerDevId)); + if (ret != SOFTBUS_OK) { + LOG_WARN("get my peer device id failed, session id is %{public}d.", sessionId); + return; + } + std::string peerUdid = softBusAdapter_->GetUdidByNodeId(std::string(peerDevId)); + LOG_DEBUG("[BytesReceived] session id:%{public}d, peerSessionName:%{public}s, " + "peerDevId:%{public}s", + sessionId, peerSessionName, SoftBusAdapter::ToBeAnonymous(peerUdid).c_str()); + NotifyDataListeners(reinterpret_cast(data), dataLen, peerUdid, { std::string(peerSessionName) }); +} + +void AppDataListenerWrap::NotifyDataListeners( + const uint8_t *ptr, const int size, const std::string &deviceId, const PipeInfo &pipeInfo) +{ + return softBusAdapter_->NotifyDataListeners(ptr, size, deviceId, pipeInfo); +} +} // namespace ObjectStore +} // namespace OHOS diff --git a/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/BUILD.gn b/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/BUILD.gn new file mode 100644 index 00000000..69bcbece --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/BUILD.gn @@ -0,0 +1,48 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +#####################hydra-fuzz################### +import("//build/config/features.gni") +import("//build/test.gni") +config("module_private_config") { + visibility = [ ":*" ] + + include_dirs = [] +} + +##############################fuzztest########################################## +ohos_fuzztest("ObjectStoreFuzzTest") { + module_out_path = "data_object/impl" + + fuzz_config_file = "//foundation/distributeddatamgr/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer" + + sources = [ "objectstore_fuzzer.cpp" ] + + configs = [ ":module_private_config" ] + + external_deps = [ + "data_object:distributeddataobject_impl", + "hilog_native:libhilog", + ] +} + +############################################################################### +group("fuzztest") { + testonly = true + deps = [] + deps += [ + # deps file + ":ObjectStoreFuzzTest", + ] +} +############################################################################### diff --git a/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/corpus/init b/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/corpus/init new file mode 100644 index 00000000..8eb5a7d6 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/corpus/init @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ + +FUZZ \ No newline at end of file diff --git a/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/objectstore_fuzzer.cpp b/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/objectstore_fuzzer.cpp new file mode 100644 index 00000000..c41d7fe9 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/objectstore_fuzzer.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "distributed_object.h" +#include "distributed_objectstore.h" +#include "objectstore_errors.h" + +using namespace OHOS::ObjectStore; + +namespace OHOS { +bool PutDoubleFuzz(const uint8_t *data, size_t size) +{ + bool result = false; + std::string bundleName = "com.example.myapplication"; + std::string sessionId = "123456"; + double sval = static_cast(size); + std::string skey(data, data + size); + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + DistributedObject *object = objectStore->CreateObject(sessionId); + uint32_t ret = object->PutDouble(skey, sval); + if (!ret) { + result = true; + } + objectStore->DeleteObject(sessionId); + return result; +} + +bool PutBooleanFuzz(const uint8_t *data, size_t size) +{ + bool result = false; + std::string bundleName = "com.example.myapplication"; + std::string sessionId = "123456"; + std::string skey(data, data + size); + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + DistributedObject *object = objectStore->CreateObject(sessionId); + uint32_t ret = object->PutBoolean(skey, true); + if (!ret) { + result = true; + } + ret = object->PutBoolean(skey, false); + if (ret != SUCCESS) { + result = false; + } + objectStore->DeleteObject(sessionId); + return result; +} + +bool PutStringFuzz(const uint8_t *data, size_t size) +{ + bool result = false; + std::string bundleName = "com.example.myapplication"; + std::string sessionId = "123456"; + std::string skey(data, data + size); + std::string sval(data, data + size); + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + DistributedObject *object = objectStore->CreateObject(sessionId); + uint32_t ret = object->PutString(skey, sval); + if (!ret) { + result = true; + } + objectStore->DeleteObject(sessionId); + return result; +} + +bool PutComplexFuzz(const uint8_t *data, size_t size) +{ + bool result = false; + std::string bundleName = "com.example.myapplication"; + std::string sessionId = "123456"; + size_t sum = 10; + std::string skey(data, data + size); + std::vector value; + for (int i = 0; i < sum; i++) { + value.push_back(*data + i); + } + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + DistributedObject *object = objectStore->CreateObject(sessionId); + uint32_t ret = object->PutComplex(skey, value); + if (!ret) { + result = true; + } + objectStore->DeleteObject(sessionId); + return result; +} + +bool GetDoubleFuzz(const uint8_t *data, size_t size) +{ + bool result = false; + std::string bundleName = "com.example.myapplication"; + std::string sessionId = "123456"; + double sval = static_cast(size); + double val; + std::string skey(data, data + size); + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + DistributedObject *object = objectStore->CreateObject(sessionId); + if (SUCCESS == object->PutDouble(skey, sval)) { + uint32_t ret = object->GetDouble(skey, val); + if (!ret) { + result = true; + } + } + objectStore->DeleteObject(sessionId); + return result; +} + +bool GetBooleanFuzz(const uint8_t *data, size_t size) +{ + bool val, result = false; + std::string bundleName = "com.example.myapplication"; + std::string sessionId = "123456"; + std::string skey(data, data + size); + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + DistributedObject *object = objectStore->CreateObject(sessionId); + if (SUCCESS == object->PutBoolean(skey, true)) { + uint32_t ret = object->GetBoolean(skey, val); + if (!ret) { + result = true; + } + } + objectStore->DeleteObject(sessionId); + return result; +} + +bool GetStringFuzz(const uint8_t *data, size_t size) +{ + bool result = false; + std::string bundleName = "com.example.myapplication"; + std::string sessionId = "123456"; + std::string skey(data, data + size); + std::string sval(data, data + size); + std::string val; + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + DistributedObject *object = objectStore->CreateObject(sessionId); + if (SUCCESS == object->PutString(skey, sval)) { + uint32_t ret = object->GetString(skey, val); + if (!ret) { + result = true; + } + } + objectStore->DeleteObject(sessionId); + return result; +} + +bool GetComplexFuzz(const uint8_t *data, size_t size) +{ + bool result = false; + std::string bundleName = "com.example.myapplication"; + std::string sessionId = "123456"; + size_t sum = 10; + std::string skey(data, data + size); + std::vector svalue; + std::vector val; + for (int i = 0; i < sum; i++) { + svalue.push_back(*data + i); + } + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + DistributedObject *object = objectStore->CreateObject(sessionId); + if (SUCCESS == object->PutComplex(skey, svalue)) { + uint32_t ret = object->GetComplex(skey, val); + if (!ret) { + result = true; + } + } + objectStore->DeleteObject(sessionId); + return result; +} +} + +/* Fuzzer entry point */ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + OHOS::PutDoubleFuzz(data, size); + OHOS::PutBooleanFuzz(data, size); + OHOS::PutStringFuzz(data, size); + OHOS::PutComplexFuzz(data, size); + OHOS::GetDoubleFuzz(data, size); + OHOS::GetBooleanFuzz(data, size); + OHOS::GetStringFuzz(data, size); + OHOS::GetComplexFuzz(data, size); + /* Run your code on data */ + return 0; +} \ No newline at end of file diff --git a/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/objectstore_fuzzer.h b/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/objectstore_fuzzer.h new file mode 100644 index 00000000..3431adfe --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/objectstore_fuzzer.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 OBJECTSTORE_FUZZER_H +#define OBJECTSTORE_FUZZER_H + +#define OBJECTSTORE_FUZZER_H "objectstore_fuzzer" + +#endif // OBJECTSTORE_FUZZER_H + diff --git a/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/project.xml b/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/project.xml new file mode 100644 index 00000000..6e8ad2cf --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/test/fuzztest/objectstore_fuzzer/project.xml @@ -0,0 +1,25 @@ + + + + + + 1000 + + 300 + + 4096 + + diff --git a/data_object/frameworks/innerkitsimpl/test/unittest/BUILD.gn b/data_object/frameworks/innerkitsimpl/test/unittest/BUILD.gn new file mode 100644 index 00000000..521846dc --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/test/unittest/BUILD.gn @@ -0,0 +1,42 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. + +import("//build/test.gni") + +module_output_path = "data_object/impl" + +config("module_private_config") { + visibility = [ ":*" ] + + include_dirs = [] +} + +ohos_unittest("NativeObjectStoreTest") { + module_out_path = module_output_path + + sources = [ "object_store_test.cpp" ] + + configs = [ ":module_private_config" ] + + external_deps = [ + "data_object:distributeddataobject_impl", + "hilog_native:libhilog", + ] + + deps = [ "//third_party/googletest:gtest_main" ] +} + +group("unittest") { + testonly = true + deps = [ ":NativeObjectStoreTest" ] +} diff --git a/data_object/frameworks/innerkitsimpl/test/unittest/object_store_test.cpp b/data_object/frameworks/innerkitsimpl/test/unittest/object_store_test.cpp new file mode 100644 index 00000000..39cbc362 --- /dev/null +++ b/data_object/frameworks/innerkitsimpl/test/unittest/object_store_test.cpp @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "distributed_object.h" +#include "distributed_objectstore.h" +#include "objectstore_errors.h" + +using namespace testing::ext; +using namespace OHOS::ObjectStore; + +constexpr static double SALARY = 100.5; + +static void TestSetSessionId(std::string bundleName, std::string sessionId) +{ + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + EXPECT_NE(nullptr, objectStore); + DistributedObject *object = objectStore->CreateObject(sessionId); + EXPECT_NE(nullptr, object); + + uint32_t ret = objectStore->DeleteObject(sessionId); + EXPECT_EQ(SUCCESS, ret); +} + +static void TestSaveAndRevokeSave(std::string bundleName, std::string sessionId) +{ + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + EXPECT_NE(nullptr, objectStore); + DistributedObject *object = objectStore->CreateObject(sessionId); + EXPECT_NE(nullptr, object); + + uint32_t ret = object->PutString("name", "zhangsan"); + EXPECT_EQ(SUCCESS, ret); + ret = object->PutDouble("salary", SALARY); + EXPECT_EQ(SUCCESS, ret); + ret = object->PutBoolean("isTrue", true); + EXPECT_EQ(SUCCESS, ret); + + ret = object->Save("local"); + EXPECT_EQ(SUCCESS, ret); + ret = object->RevokeSave(); + EXPECT_EQ(SUCCESS, ret); + + ret = objectStore->DeleteObject(sessionId); + EXPECT_EQ(SUCCESS, ret); +} + +class NativeObjectStoreTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void NativeObjectStoreTest::SetUpTestCase(void) +{ + // input testsuit setup step,setup invoked before all testcases +} + +void NativeObjectStoreTest::TearDownTestCase(void) +{ + // input testsuit teardown step,teardown invoked after all testcases +} + +void NativeObjectStoreTest::SetUp(void) +{ + // input testcase setup step,setup invoked before each testcases +} + +void NativeObjectStoreTest::TearDown(void) +{ + // input testcase teardown step,teardown invoked after each testcases +} + +/** + * @tc.name: DistributedObjectStore_Create_Destroy_001 + * @tc.desc: test Create DistributedObject and Destroy DistrbutedObject + * @tc.type: FUNC + */ +HWTEST_F(NativeObjectStoreTest, DistributedObjectStore_Create_Destroy_001, TestSize.Level1) +{ + std::string bundleName = "default"; + std::string sessionId = "123456"; + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + EXPECT_NE(nullptr, objectStore); + + DistributedObject *object = objectStore->CreateObject(sessionId); + EXPECT_NE(nullptr, object); + + uint32_t ret = objectStore->DeleteObject(sessionId); + EXPECT_EQ(SUCCESS, ret); +} + +/** + * @tc.name: DistributedObjectStore_Get_001 + * @tc.desc: test DistributedObjectStore Get. + * @tc.type: FUNC + */ +HWTEST_F(NativeObjectStoreTest, DistributedObjectStore_Get_001, TestSize.Level1) +{ + std::string bundleName = "default"; + std::string sessionId = "123456"; + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + EXPECT_NE(nullptr, objectStore); + + DistributedObject *object = objectStore->CreateObject(sessionId); + EXPECT_NE(nullptr, object); + + DistributedObject *object2 = nullptr; + uint32_t ret = objectStore->Get(sessionId, &object2); + EXPECT_EQ(SUCCESS, ret); + EXPECT_EQ(object, object2); + + ret = objectStore->DeleteObject(sessionId); + EXPECT_EQ(SUCCESS, ret); +} + +/** + * @tc.name: DistributedObjectStore_Watch_UnWatch_001 + * @tc.desc: test DistributedObjectStore Watch and UnWatch. + * @tc.type: FUNC + */ +HWTEST_F(NativeObjectStoreTest, DistributedObjectStore_Watch_UnWatch_001, TestSize.Level1) +{ + std::string bundleName = "default"; + std::string sessionId = "123456"; + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + EXPECT_NE(nullptr, objectStore); + + DistributedObject *object = objectStore->CreateObject(sessionId); + EXPECT_NE(nullptr, object); + + auto watcherPtr = std::shared_ptr(); + uint32_t ret = objectStore->Watch(object, watcherPtr); + EXPECT_EQ(SUCCESS, ret); + + ret = objectStore->UnWatch(object); + EXPECT_EQ(SUCCESS, ret); + + ret = objectStore->DeleteObject(sessionId); + EXPECT_EQ(SUCCESS, ret); +} + +/** + * @tc.name: DistributedObjectStore_SetStatusNotifier_001 + * @tc.desc: test DistributedObjectStore SetStatusNotifier. + * @tc.type: FUNC + */ +HWTEST_F(NativeObjectStoreTest, DistributedObjectStore_SetStatusNotifier_001, TestSize.Level1) +{ + std::string bundleName = "default"; + std::string sessionId = "123456"; + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + EXPECT_NE(nullptr, objectStore); + DistributedObject *object = objectStore->CreateObject(sessionId); + EXPECT_NE(nullptr, object); + + auto notifierPtr = std::shared_ptr(); + uint32_t ret = objectStore->SetStatusNotifier(notifierPtr); + EXPECT_EQ(ret, 0); + + ret = objectStore->DeleteObject(sessionId); + EXPECT_EQ(ret, 0); +} + +/** + * @tc.name: DistributedObject_Double_001 + * @tc.desc: test DistributedObjectStore PutDouble. + * @tc.type: FUNC + */ +HWTEST_F(NativeObjectStoreTest, DistributedObject_Double_001, TestSize.Level1) +{ + std::string bundleName = "default"; + std::string sessionId = "123456"; + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + EXPECT_NE(nullptr, objectStore); + DistributedObject *object = objectStore->CreateObject(sessionId); + EXPECT_NE(nullptr, object); + + uint32_t ret = object->PutDouble("salary", SALARY); + EXPECT_EQ(ret, 0); + + double value = 0.0; + object->GetDouble("salary", value); + EXPECT_EQ(ret, 0); + EXPECT_EQ(value, SALARY); + + ret = objectStore->DeleteObject(sessionId); + EXPECT_EQ(ret, 0); +} + +/** + * @tc.name: DistributedObject_Boolean_001 + * @tc.desc: test DistributedObjectStore PutBoolean. + * @tc.type: FUNC + */ +HWTEST_F(NativeObjectStoreTest, DistributedObject_Boolean_001, TestSize.Level1) +{ + std::string bundleName = "default"; + std::string sessionId = "123456"; + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + EXPECT_NE(nullptr, objectStore); + DistributedObject *object = objectStore->CreateObject(sessionId); + EXPECT_NE(nullptr, object); + + uint32_t ret = object->PutBoolean("isTrue", true); + EXPECT_EQ(SUCCESS, ret); + + + bool value = false; + ret = object->GetBoolean("isTrue", value); + EXPECT_EQ(SUCCESS, ret); + EXPECT_EQ(true, value); + + ret = objectStore->DeleteObject(sessionId); + EXPECT_EQ(SUCCESS, ret); +} + +/** + * @tc.name: DistributedObject_String_001 + * @tc.desc: test DistributedObjectStore String. + * @tc.type: FUNC + */ +HWTEST_F(NativeObjectStoreTest, DistributedObject_String_001, TestSize.Level1) +{ + std::string bundleName = "default"; + std::string sessionId = "123456"; + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + EXPECT_NE(nullptr, objectStore); + DistributedObject *object = objectStore->CreateObject(sessionId); + EXPECT_NE(nullptr, object); + + uint32_t ret = object->PutString("name", "zhangsan"); + EXPECT_EQ(SUCCESS, ret); + + std::string value = ""; + ret = object->GetString("name", value); + EXPECT_EQ(SUCCESS, ret); + EXPECT_EQ(value, "zhangsan"); + + ret = objectStore->DeleteObject(sessionId); + EXPECT_EQ(SUCCESS, ret); +} + +/** + * @tc.name: DistributedObject_GetSessionId_001 + * @tc.desc: test DistributedObjectStore GetSessionId. + * @tc.type: FUNC + */ +HWTEST_F(NativeObjectStoreTest, DistributedObject_GetSessionId_001, TestSize.Level1) +{ + std::string bundleName = "default"; + std::string sessionId = "123456"; + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + EXPECT_NE(nullptr, objectStore); + DistributedObject *object = objectStore->CreateObject(sessionId); + EXPECT_NE(nullptr, object); + std::string getSessionId = object->GetSessionId(); + EXPECT_EQ(sessionId, getSessionId); + uint32_t ret = objectStore->DeleteObject(sessionId); + EXPECT_EQ(SUCCESS, ret); +} + +/** + * @tc.name: DistributedObject_TestSetSessionId_001 + * @tc.desc: test DistributedObjectStore TestSetSessionId. + * @tc.type: FUNC + */ +HWTEST_F(NativeObjectStoreTest, DistributedObject_TestSetSessionId_001, TestSize.Level1) +{ + std::thread t1(TestSetSessionId, "default1", "session1"); + std::thread t2(TestSetSessionId, "default2", "session2"); + std::thread t3(TestSetSessionId, "default3", "session3"); + t1.join(); + t2.join(); + t3.join(); +} + +/** + * @tc.name: DistributedObject_GetType_001 + * @tc.desc: test DistributedObject GetType. + * @tc.type: FUNC + */ +HWTEST_F(NativeObjectStoreTest, DistributedObject_GetType_001, TestSize.Level1) +{ + std::string bundleName = "default"; + std::string sessionId = "123456"; + DistributedObjectStore *objectStore = DistributedObjectStore::GetInstance(bundleName); + EXPECT_NE(nullptr, objectStore); + DistributedObject *object = objectStore->CreateObject(sessionId); + EXPECT_NE(nullptr, object); + + uint32_t ret = object->PutString("name", "zhangsan"); + EXPECT_EQ(SUCCESS, ret); + Type type; + ret = object->GetType("name", type); + EXPECT_EQ(SUCCESS, ret); + EXPECT_EQ(TYPE_STRING, type); + + ret = object->PutDouble("salary", SALARY); + EXPECT_EQ(SUCCESS, ret); + ret = object->GetType("salary", type); + EXPECT_EQ(SUCCESS, ret); + EXPECT_EQ(TYPE_DOUBLE, type); + + ret = object->PutBoolean("isTrue", true); + EXPECT_EQ(SUCCESS, ret); + ret = object->GetType("isTrue", type); + EXPECT_EQ(SUCCESS, ret); + EXPECT_EQ(TYPE_BOOLEAN, type); + + ret = objectStore->DeleteObject(sessionId); + EXPECT_EQ(SUCCESS, ret); +} + +/** + * @tc.name: DistributedObject_Save_RevokeSave_001 + * @tc.desc: test DistributedObjectStore Save. + * @tc.type: FUNC + */ +HWTEST_F(NativeObjectStoreTest, DistributedObject_Save_RevokeSave_001, TestSize.Level1) +{ + std::string bundleName = "default"; + std::string sessionId = "123456"; + TestSaveAndRevokeSave(bundleName, sessionId); +} + +/** + * @tc.name: DistributedObject_Save_RevokeSave_002 + * @tc.desc: test DistributedObjectStore Save. + * @tc.type: FUNC + */ +HWTEST_F(NativeObjectStoreTest, DistributedObject_Save_RevokeSave_002, TestSize.Level1) +{ + std::thread t1(TestSaveAndRevokeSave, "default1", "session1"); + std::thread t2(TestSaveAndRevokeSave, "default2", "session2"); + std::thread t3(TestSaveAndRevokeSave, "default3", "session3"); + t1.join(); + t2.join(); + t3.join(); +} \ No newline at end of file diff --git a/data_object/frameworks/jskitsimpl/include/adaptor/js_common.h b/data_object/frameworks/jskitsimpl/include/adaptor/js_common.h new file mode 100644 index 00000000..65d9da5c --- /dev/null +++ b/data_object/frameworks/jskitsimpl/include/adaptor/js_common.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 JS_COMMON_H +#define JS_COMMON_H +#include "hilog/log.h" +namespace OHOS::ObjectStore { +#define CHECK_EQUAL_WITH_RETURN_NULL(status, value) \ + { \ + if (status != value) { \ + LOG_ERROR("error! %{public}d %{public}d", status, value); \ + return nullptr; \ + } \ + } +#define CHECK_EQUAL_WITH_RETURN_VOID(status, value) \ + { \ + if (status != value) { \ + LOG_ERROR("error! %{public}d %{public}d", status, value); \ + return; \ + } \ + } +#define ASSERT_MATCH_ELSE_RETURN_VOID(condition) \ + { \ + if (!(condition)) { \ + LOG_ERROR("error! %{public}s", #condition); \ + return; \ + } \ + } +#define ASSERT_MATCH_ELSE_RETURN_NULL(condition) \ + { \ + if (!(condition)) { \ + LOG_ERROR("error! %{public}s", #condition); \ + return nullptr; \ + } \ + } +#define ASSERT_MATCH_ELSE_GOTO_ERROR(condition) \ + { \ + if (!(condition)) { \ + LOG_ERROR("error! %{public}s", #condition); \ + goto ERROR; \ + } \ + } +} // namespace OHOS::ObjectStore +static const char *CHANGE = "change"; +static const char *STATUS = "status"; +#endif // JS_COMMON_H diff --git a/data_object/frameworks/jskitsimpl/include/adaptor/js_distributedobject.h b/data_object/frameworks/jskitsimpl/include/adaptor/js_distributedobject.h new file mode 100644 index 00000000..9cca0f54 --- /dev/null +++ b/data_object/frameworks/jskitsimpl/include/adaptor/js_distributedobject.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 JS_DISTRIBUTEDOBJECT_H +#define JS_DISTRIBUTEDOBJECT_H + +#include + +#include "distributed_objectstore.h" +#include "js_object_wrapper.h" +namespace OHOS::ObjectStore { +struct ConstructContext { + DistributedObjectStore *objectStore; + DistributedObject *object; +}; + +class JSDistributedObject { +public: + static napi_value JSConstructor(napi_env env, napi_callback_info info); + static napi_value JSGet(napi_env env, napi_callback_info info); + static napi_value JSPut(napi_env env, napi_callback_info info); + static napi_value JSSave(napi_env env, napi_callback_info info); + static napi_value JSRevokeSave(napi_env env, napi_callback_info info); + static napi_value GetCons(napi_env env); + +private: + static void DoPut(napi_env env, JSObjectWrapper *wrapper, char *key, napi_valuetype type, napi_value value); + static void DoGet(napi_env env, JSObjectWrapper *wrapper, char *key, napi_value &value); + static napi_value GetSaveResultCons(napi_env env, std::string &sessionId, double version, std::string deviceId); + static napi_value GetRevokeSaveResultCons(napi_env env, std::string &sessionId); +}; +} // namespace OHOS::ObjectStore + +#endif diff --git a/data_object/frameworks/jskitsimpl/include/adaptor/js_distributedobjectstore.h b/data_object/frameworks/jskitsimpl/include/adaptor/js_distributedobjectstore.h new file mode 100644 index 00000000..5ab653b7 --- /dev/null +++ b/data_object/frameworks/jskitsimpl/include/adaptor/js_distributedobjectstore.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 JS_DISTRIBUTEDDATAOBJECTSTORE_H +#define JS_DISTRIBUTEDDATAOBJECTSTORE_H + +#include + +#include "distributed_objectstore.h" +#include "js_native_api.h" +#include "js_object_wrapper.h" +#include "node_api.h" +namespace OHOS::ObjectStore { +class JSDistributedObjectStore { +public: + static napi_value JSCreateObjectSync(napi_env env, napi_callback_info info); + static napi_value JSDestroyObjectSync(napi_env env, napi_callback_info info); + static napi_value JSOn(napi_env env, napi_callback_info info); + static napi_value JSOff(napi_env env, napi_callback_info info); + static napi_value JSRecordCallback(napi_env env, napi_callback_info info); + static napi_value JSDeleteCallback(napi_env env, napi_callback_info info); +private: + static napi_value NewDistributedObject( + napi_env env, DistributedObjectStore *objectStore, DistributedObject *object, const std::string &objectId); + static void AddCallback(napi_env env, std::map> &callbacks, + const std::string &objectId, napi_value callback); + static void DelCallback(napi_env env, std::map> &callbacks, + const std::string &sessionId, napi_value callback = nullptr); + static bool CheckSyncPermission(); + static void RestoreWatchers(napi_env env, JSObjectWrapper *wrapper, const std::string &objectId); + static std::string GetBundleName(napi_env env); + static bool IsSandBox(); +}; +} // namespace OHOS::ObjectStore +#endif // JS_DISTRIBUTEDDATAOBJECTSTORE_H diff --git a/data_object/frameworks/jskitsimpl/include/adaptor/js_object_wrapper.h b/data_object/frameworks/jskitsimpl/include/adaptor/js_object_wrapper.h new file mode 100644 index 00000000..626be1db --- /dev/null +++ b/data_object/frameworks/jskitsimpl/include/adaptor/js_object_wrapper.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 JS_OBJECT_WRAPPER_H +#define JS_OBJECT_WRAPPER_H + +#include + +#include "distributed_object.h" +#include "distributed_objectstore.h" +#include "js_watcher.h" + +namespace OHOS::ObjectStore { +class JSObjectWrapper { +public: + JSObjectWrapper(DistributedObjectStore *objectStore, DistributedObject *object); + virtual ~JSObjectWrapper(); + DistributedObject *GetObject(); + bool AddWatch(napi_env env, const char *type, napi_value handler); + void DeleteWatch(napi_env env, const char *type, napi_value handler = nullptr); + bool isUndefined(char *value); + void AddUndefined(char *value); + void DeleteUndefined(char *value); + void DestroyObject(); + +private: + DistributedObjectStore *objectStore_; + DistributedObject *object_; + std::unique_ptr watcher_ = nullptr; + std::shared_mutex watchMutex_{}; + std::vector undefinedProperties; +}; +} // namespace OHOS::ObjectStore + +#endif diff --git a/data_object/frameworks/jskitsimpl/include/adaptor/js_watcher.h b/data_object/frameworks/jskitsimpl/include/adaptor/js_watcher.h new file mode 100644 index 00000000..877c3180 --- /dev/null +++ b/data_object/frameworks/jskitsimpl/include/adaptor/js_watcher.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 JSWATCHER_H +#define JSWATCHER_H + +#include "distributed_objectstore.h" +#include "flat_object_store.h" +#include "napi/native_api.h" +#include "napi/native_node_api.h" +#include "uv_queue.h" + +namespace OHOS::ObjectStore { +class JSWatcher; +struct EventHandler { + napi_ref callbackRef = nullptr; + EventHandler *next = nullptr; +}; + +class EventListener { +public: + EventListener() : handlers_(nullptr) + { + } + + virtual ~EventListener() + { + } + + virtual bool Add(napi_env env, napi_value handler); + + virtual bool Del(napi_env env, napi_value handler); + + virtual void Clear(napi_env env); + + EventHandler *Find(napi_env env, napi_value handler); + EventHandler *handlers_; +}; + +class ChangeEventListener : public EventListener { +public: + ChangeEventListener(JSWatcher *watcher, DistributedObjectStore *objectStore, DistributedObject *object); + + bool Add(napi_env env, napi_value handler) override; + + bool Del(napi_env env, napi_value handler) override; + + void Clear(napi_env env) override; + +private: + bool isWatched_ = false; + DistributedObjectStore *objectStore_; + DistributedObject *object_; + JSWatcher *watcher_; +}; + +class StatusEventListener : public EventListener { +public: + StatusEventListener(JSWatcher *watcher, const std::string &sessionId); + bool Add(napi_env env, napi_value handler) override; + + bool Del(napi_env env, napi_value handler) override; + + void Clear(napi_env env) override; + +private: + JSWatcher *watcher_; + std::string sessionId_; +}; + +class JSWatcher : public UvQueue { +public: + JSWatcher(const napi_env env, DistributedObjectStore *objectStore, DistributedObject *object); + + ~JSWatcher(); + + bool On(const char *type, napi_value handler); + + void Off(const char *type, napi_value handler = nullptr); + + void Emit(const char *type, const std::string &sessionId, const std::vector &changeData); + + void Emit(const char *type, const std::string &sessionId, const std::string &networkId, const std::string &status); + +private: + struct ChangeArgs { + ChangeArgs(const napi_ref callback, const std::string &sessionId, const std::vector &changeData); + napi_ref callback_; + const std::string sessionId_; + const std::vector changeData_; + }; + struct StatusArgs { + StatusArgs(const napi_ref callback, const std::string &sessionId, const std::string &networkId, + const std::string &status); + napi_ref callback_; + const std::string sessionId_; + const std::string networkId_; + const std::string status_; + }; + EventListener *Find(const char *type); + static void ProcessChange(napi_env env, std::list &args); + static void ProcessStatus(napi_env env, std::list &args); + napi_env env_; + ChangeEventListener *changeEventListener_; + StatusEventListener *statusEventListener_; +}; + +class WatcherImpl : public ObjectWatcher { +public: + WatcherImpl(JSWatcher *watcher) : watcher_(watcher) + { + } + + virtual ~WatcherImpl(); + + void OnChanged(const std::string &sessionid, const std::vector &changedData) override; + +private: + JSWatcher *watcher_ = nullptr; +}; +} // namespace OHOS::ObjectStore + +#endif // JSWATCHER_H diff --git a/data_object/frameworks/jskitsimpl/include/adaptor/notifier_impl.h b/data_object/frameworks/jskitsimpl/include/adaptor/notifier_impl.h new file mode 100644 index 00000000..0f6d394f --- /dev/null +++ b/data_object/frameworks/jskitsimpl/include/adaptor/notifier_impl.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 JS_NOTIFIER_IMPL_H +#define JS_NOTIFIER_IMPL_H + +#include + +#include "distributed_objectstore.h" +#include "js_watcher.h" +namespace OHOS::ObjectStore { +class NotifierImpl : public StatusNotifier { +public: + static std::shared_ptr GetInstance(); + virtual ~NotifierImpl(); + void AddWatcher(std::string &sessionId, JSWatcher *watcher); + void DelWatcher(std::string &sessionId); + void OnChanged( + const std::string &sessionId, const std::string &networkId, const std::string &onlineStatus) override; + +private: + std::mutex mutex_; + std::map watchers_; +}; +} // namespace OHOS::ObjectStore +#endif // JS_NOTIFIER_IMPL_H diff --git a/data_object/frameworks/jskitsimpl/include/common/js_util.h b/data_object/frameworks/jskitsimpl/include/common/js_util.h new file mode 100644 index 00000000..4e1c675c --- /dev/null +++ b/data_object/frameworks/jskitsimpl/include/common/js_util.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 OHOS_JS_UTIL_H +#define OHOS_JS_UTIL_H +#include +#include +#include + +#include "napi/native_api.h" +#include "napi/native_node_api.h" + +namespace OHOS::ObjectStore { +class JSUtil final { +public: + /* napi_value <-> bool */ + static napi_status GetValue(napi_env env, napi_value in, bool &out); + static napi_status SetValue(napi_env env, const bool &in, napi_value &out); + + /* napi_value <-> double */ + static napi_status GetValue(napi_env env, napi_value in, double &out); + static napi_status SetValue(napi_env env, const double &in, napi_value &out); + + /* napi_value <-> std::string */ + static napi_status GetValue(napi_env env, napi_value in, std::string &out); + static napi_status SetValue(napi_env env, const std::string &in, napi_value &out); + + /* napi_value <-> std::vector */ + static napi_status GetValue(napi_env env, napi_value in, std::vector &out); + static napi_status SetValue(napi_env env, const std::vector &in, napi_value &out); + + /* napi_value <-> std::vector */ + static napi_status GetValue(napi_env env, napi_value in, std::vector &out); + static napi_status SetValue(napi_env env, const std::vector &in, napi_value &out); +}; + +#define LOG_ERROR_RETURN(condition, message, retVal) \ + do { \ + if (!(condition)) { \ + LOG_ERROR("test (" #condition ") failed: " message); \ + return retVal; \ + } \ + } while (0) + +#define LOG_ERROR_RETURN_VOID(condition, message) \ + do { \ + if (!(condition)) { \ + LOG_ERROR("test (" #condition ") failed: " message); \ + return; \ + } \ + } while (0) +} // namespace OHOS::ObjectStore +#endif // OHOS_JS_UTIL_H diff --git a/data_object/frameworks/jskitsimpl/include/common/napi_queue.h b/data_object/frameworks/jskitsimpl/include/common/napi_queue.h new file mode 100644 index 00000000..58865d7f --- /dev/null +++ b/data_object/frameworks/jskitsimpl/include/common/napi_queue.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 NAPI_QUEUE_H +#define NAPI_QUEUE_H +#include +#include +#include + +#include "napi/native_api.h" +#include "napi/native_common.h" +#include "napi/native_node_api.h" + +namespace OHOS::ObjectStore { +using NapiCbInfoParser = std::function; +using NapiAsyncExecute = std::function; +using NapiAsyncComplete = std::function; +static constexpr size_t ARGC_MAX = 6; +struct ContextBase { + virtual ~ContextBase(); + void GetCbInfo( + napi_env env, napi_callback_info info, NapiCbInfoParser parse = NapiCbInfoParser(), bool sync = false); + + inline void GetCbInfoSync(napi_env env, napi_callback_info info, NapiCbInfoParser parse = NapiCbInfoParser()) + { + /* sync = true, means no callback, not AsyncWork. */ + GetCbInfo(env, info, parse, true); + } + + napi_env env = nullptr; + napi_value output = nullptr; + napi_status status = napi_invalid_arg; + std::string error; + + napi_value self = nullptr; + void *native = nullptr; + +private: + napi_deferred deferred = nullptr; + napi_async_work work = nullptr; + napi_ref callbackRef = nullptr; + napi_ref selfRef = nullptr; + + NapiAsyncExecute execute = nullptr; + NapiAsyncComplete complete = nullptr; + std::shared_ptr hold; /* cross thread data */ + + friend class NapiQueue; +}; + +/* check condition related to argc/argv, return and logging. */ +#define CHECK_ARGS_RETURN_VOID(ctxt, condition, message) \ + do { \ + if (!(condition)) { \ + (ctxt)->status = napi_invalid_arg; \ + (ctxt)->error = std::string(message); \ + LOG_ERROR("test (" #condition ") failed: " message); \ + return; \ + } \ + } while (0) + +#define CHECK_STATUS_RETURN_VOID(ctxt, message) \ + do { \ + if ((ctxt)->status != napi_ok) { \ + (ctxt)->error = std::string(message); \ + LOG_ERROR("test (ctxt->status == napi_ok) failed: " message); \ + return; \ + } \ + } while (0) +class NapiQueue { +public: + static napi_value AsyncWork(napi_env env, std::shared_ptr ctxt, const std::string &name, + NapiAsyncExecute execute = NapiAsyncExecute(), NapiAsyncComplete complete = NapiAsyncComplete()); + +private: + enum { + /* AsyncCallback / Promise output result index */ + RESULT_ERROR = 0, + RESULT_DATA = 1, + RESULT_ALL = 2 + }; + static void GenerateOutput(ContextBase *ctxt); +}; +} // namespace OHOS::ObjectStore +#endif // OHOS_NAPI_QUEUE_H diff --git a/data_object/frameworks/jskitsimpl/include/common/uv_queue.h b/data_object/frameworks/jskitsimpl/include/common/uv_queue.h new file mode 100644 index 00000000..699186aa --- /dev/null +++ b/data_object/frameworks/jskitsimpl/include/common/uv_queue.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 UV_QUEUE_H +#define UV_QUEUE_H +#include +#include +#include +#include + +#include "napi/native_api.h" +#include "napi/native_node_api.h" +#include "uv.h" + +namespace OHOS::ObjectStore { +typedef void (*Process)(napi_env env, std::list &); +class UvQueue { +public: + UvQueue(napi_env env); + virtual ~UvQueue(); + + void CallFunction(Process process, void *argv); + +private: + napi_env env_; + std::shared_mutex mutex_{}; + // key is callback,value is list of args + std::map> args_; + uv_loop_s *loop_ = nullptr; +}; +} // namespace OHOS::ObjectStore +#endif diff --git a/data_object/frameworks/jskitsimpl/src/adaptor/js_distributedobject.cpp b/data_object/frameworks/jskitsimpl/src/adaptor/js_distributedobject.cpp new file mode 100644 index 00000000..5a404ed1 --- /dev/null +++ b/data_object/frameworks/jskitsimpl/src/adaptor/js_distributedobject.cpp @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "js_distributedobject.h" + +#include + +#include "js_common.h" +#include "js_object_wrapper.h" +#include "js_util.h" +#include "logger.h" +#include "napi_queue.h" +#include "objectstore_errors.h" + +namespace OHOS::ObjectStore { +constexpr size_t KEY_SIZE = 64; +napi_value JSDistributedObject::JSConstructor(napi_env env, napi_callback_info info) +{ + LOG_INFO("start"); + napi_value thisVar = nullptr; + void *data = nullptr; + napi_status status = napi_get_cb_info(env, info, nullptr, 0, &thisVar, &data); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + return thisVar; +} + +// get(key: string): ValueType; +napi_value JSDistributedObject::JSGet(napi_env env, napi_callback_info info) +{ + size_t requireArgc = 1; + size_t argc = 1; + napi_value argv[1] = { 0 }; + napi_value thisVar = nullptr; + void *data = nullptr; + char key[KEY_SIZE] = { 0 }; + size_t keyLen = 0; + napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(argc >= requireArgc); + status = napi_get_value_string_utf8(env, argv[0], key, KEY_SIZE, &keyLen); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + JSObjectWrapper *wrapper = nullptr; + status = napi_unwrap(env, thisVar, (void **)&wrapper); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(wrapper != nullptr); + ASSERT_MATCH_ELSE_RETURN_NULL(wrapper->GetObject() != nullptr); + napi_value result = nullptr; + if (wrapper->isUndefined(key)) { + napi_get_undefined(env, &result); + return result; + } + DoGet(env, wrapper, key, result); + return result; +} + +// put(key: string, value: ValueType): void; +napi_value JSDistributedObject::JSPut(napi_env env, napi_callback_info info) +{ + size_t requireArgc = 2; + size_t argc = 2; + napi_value argv[2] = { 0 }; + napi_value thisVar = nullptr; + char key[KEY_SIZE] = { 0 }; + size_t keyLen = 0; + napi_valuetype valueType; + napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(argc >= requireArgc); + status = napi_typeof(env, argv[0], &valueType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + CHECK_EQUAL_WITH_RETURN_NULL(valueType, napi_string); + status = napi_get_value_string_utf8(env, argv[0], key, KEY_SIZE, &keyLen); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + status = napi_typeof(env, argv[1], &valueType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + JSObjectWrapper *wrapper = nullptr; + status = napi_unwrap(env, thisVar, (void **)&wrapper); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(wrapper != nullptr); + ASSERT_MATCH_ELSE_RETURN_NULL(wrapper->GetObject() != nullptr); + if (valueType == napi_undefined) { + wrapper->AddUndefined(key); + return nullptr; + } + wrapper->DeleteUndefined(key); + DoPut(env, wrapper, key, valueType, argv[1]); + LOG_INFO("put %{public}s success", key); + return nullptr; +} + +napi_value JSDistributedObject::GetCons(napi_env env) +{ + static thread_local napi_ref g_instance = nullptr; + napi_value distributedObjectClass = nullptr; + if (g_instance != nullptr) { + napi_status status = napi_get_reference_value(env, g_instance, &distributedObjectClass); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + return distributedObjectClass; + } + const char *distributedObjectName = "DistributedObject"; + napi_property_descriptor distributedObjectDesc[] = { + DECLARE_NAPI_FUNCTION("put", JSDistributedObject::JSPut), + DECLARE_NAPI_FUNCTION("get", JSDistributedObject::JSGet), + DECLARE_NAPI_FUNCTION("save", JSDistributedObject::JSSave), + DECLARE_NAPI_FUNCTION("revokeSave", JSDistributedObject::JSRevokeSave), + }; + + napi_status status = napi_define_class(env, distributedObjectName, strlen(distributedObjectName), + JSDistributedObject::JSConstructor, nullptr, sizeof(distributedObjectDesc) / sizeof(distributedObjectDesc[0]), + distributedObjectDesc, &distributedObjectClass); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + if (g_instance == nullptr) { + status = napi_create_reference(env, distributedObjectClass, 1, &g_instance); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + } + return distributedObjectClass; +} + +void JSDistributedObject::DoPut( + napi_env env, JSObjectWrapper *wrapper, char *key, napi_valuetype type, napi_value value) +{ + std::string keyString = key; + switch (type) { + case napi_boolean: { + bool putValue = false; + napi_status status = JSUtil::GetValue(env, value, putValue); + CHECK_EQUAL_WITH_RETURN_VOID(status, napi_ok); + wrapper->GetObject()->PutBoolean(keyString, putValue); + break; + } + case napi_number: { + double putValue = 0; + napi_status status = JSUtil::GetValue(env, value, putValue); + CHECK_EQUAL_WITH_RETURN_VOID(status, napi_ok); + wrapper->GetObject()->PutDouble(keyString, putValue); + break; + } + case napi_string: { + std::string putValue; + napi_status status = JSUtil::GetValue(env, value, putValue); + CHECK_EQUAL_WITH_RETURN_VOID(status, napi_ok); + wrapper->GetObject()->PutString(keyString, putValue); + break; + } + case napi_object: { + std::vector putValue; + napi_status status = JSUtil::GetValue(env, value, putValue); + CHECK_EQUAL_WITH_RETURN_VOID(status, napi_ok); + wrapper->GetObject()->PutComplex(keyString, putValue); + break; + } + default: { + LOG_ERROR("error type! %{public}d", type); + break; + } + } +} + +void JSDistributedObject::DoGet(napi_env env, JSObjectWrapper *wrapper, char *key, napi_value &value) +{ + std::string keyString = key; + Type type = TYPE_STRING; + wrapper->GetObject()->GetType(keyString, type); + LOG_DEBUG("get type %{public}s %{public}d", key, type); + switch (type) { + case TYPE_STRING: { + std::string result; + uint32_t ret = wrapper->GetObject()->GetString(keyString, result); + ASSERT_MATCH_ELSE_RETURN_VOID(ret == SUCCESS) + napi_status status = JSUtil::SetValue(env, result, value); + ASSERT_MATCH_ELSE_RETURN_VOID(status == napi_ok) + break; + } + case TYPE_DOUBLE: { + double result; + uint32_t ret = wrapper->GetObject()->GetDouble(keyString, result); + LOG_DEBUG("%{public}f", result); + ASSERT_MATCH_ELSE_RETURN_VOID(ret == SUCCESS) + napi_status status = JSUtil::SetValue(env, result, value); + ASSERT_MATCH_ELSE_RETURN_VOID(status == napi_ok) + break; + } + case TYPE_BOOLEAN: { + bool result; + uint32_t ret = wrapper->GetObject()->GetBoolean(keyString, result); + LOG_DEBUG("%{public}d", result); + ASSERT_MATCH_ELSE_RETURN_VOID(ret == SUCCESS) + napi_status status = JSUtil::SetValue(env, result, value); + ASSERT_MATCH_ELSE_RETURN_VOID(status == napi_ok) + break; + } + case TYPE_COMPLEX: { + std::vector result; + uint32_t ret = wrapper->GetObject()->GetComplex(keyString, result); + ASSERT_MATCH_ELSE_RETURN_VOID(ret == SUCCESS) + napi_status status = JSUtil::SetValue(env, result, value); + ASSERT_MATCH_ELSE_RETURN_VOID(status == napi_ok) + break; + } + default: { + LOG_ERROR("error type! %{public}d", type); + break; + } + } +} + +// save(deviceId: string, version: number, callback?:AsyncCallback): void; +// save(deviceId: string, version: number): Promise; +napi_value JSDistributedObject::JSSave(napi_env env, napi_callback_info info) +{ + LOG_DEBUG("JSSave()"); + struct SaveContext : public ContextBase { + double version; + std::string deviceId; + DistributedObject *object; + }; + auto ctxt = std::make_shared(); + std::function getCbOpe = [env, ctxt](size_t argc, napi_value *argv) { + // required 1 arguments :: + CHECK_ARGS_RETURN_VOID(ctxt, argc >= 2, "invalid arguments!"); + ctxt->status = JSUtil::GetValue(env, argv[0], ctxt->deviceId); + CHECK_STATUS_RETURN_VOID(ctxt, "invalid arg[0], i.e. invalid deviceId!"); + ctxt->status = JSUtil::GetValue(env, argv[1], ctxt->version); + CHECK_STATUS_RETURN_VOID(ctxt, "invalid arg[1], i.e. invalid version!"); + JSObjectWrapper *wrapper = nullptr; + napi_status status = napi_unwrap(env, ctxt->self, (void **)&wrapper); + CHECK_EQUAL_WITH_RETURN_VOID(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_VOID(wrapper != nullptr); + ASSERT_MATCH_ELSE_RETURN_VOID(wrapper->GetObject() != nullptr); + ctxt->object = wrapper->GetObject(); + }; + ctxt->GetCbInfo(env, info, getCbOpe); + auto output = [env, ctxt](napi_value &result) { + if (ctxt->status == napi_ok) { + ctxt->status = napi_new_instance(env, + JSDistributedObject::GetSaveResultCons(env, ctxt->object->GetSessionId(), + ctxt->version, ctxt->deviceId), 0, nullptr, &result); + CHECK_STATUS_RETURN_VOID(ctxt, "output failed!"); + } + }; + return NapiQueue::AsyncWork( + env, ctxt, std::string(__FUNCTION__), + [ctxt]() { + LOG_INFO("start"); + if (ctxt->object == nullptr) { + LOG_ERROR("object is null"); + ctxt->status = napi_invalid_arg; + ctxt->error = std::string("object is null"); + return; + } + uint32_t status = ctxt->object->Save(ctxt->deviceId); + if (status != SUCCESS) { + LOG_ERROR("Save failed, status = %{public}d", status); + ctxt->status = napi_invalid_arg; + ctxt->error = std::string("operation failed"); + return; + } + ctxt->status = napi_ok; + LOG_INFO("end"); + }, + output); +} + +// revokeSave(callback?:AsyncCallback): void; +// revokeSave(): Promise; +napi_value JSDistributedObject::JSRevokeSave(napi_env env, napi_callback_info info) +{ + LOG_DEBUG("JSRevokeSave()"); + struct RevokeSaveContext : public ContextBase { + DistributedObject *object; + }; + auto ctxt = std::make_shared(); + std::function getCbOpe = [env, ctxt](size_t argc, napi_value *argv) { + JSObjectWrapper *wrapper = nullptr; + napi_status status = napi_unwrap(env, ctxt->self, (void **)&wrapper); + CHECK_EQUAL_WITH_RETURN_VOID(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_VOID(wrapper != nullptr); + ASSERT_MATCH_ELSE_RETURN_VOID(wrapper->GetObject() != nullptr); + ctxt->object = wrapper->GetObject(); + }; + ctxt->GetCbInfo(env, info, getCbOpe); + auto output = [env, ctxt](napi_value &result) { + if (ctxt->status == napi_ok) { + ctxt->status = napi_new_instance(env, + JSDistributedObject::GetRevokeSaveResultCons(env, ctxt->object->GetSessionId()), 0, nullptr, &result); + CHECK_STATUS_RETURN_VOID(ctxt, "output failed!"); + } + }; + return NapiQueue::AsyncWork( + env, ctxt, std::string(__FUNCTION__), + [ctxt]() { + LOG_INFO("start"); + if (ctxt->object == nullptr) { + LOG_ERROR("object is null"); + ctxt->status = napi_invalid_arg; + ctxt->error = std::string("object is null"); + return; + } + uint32_t status = ctxt->object->RevokeSave(); + if (status != SUCCESS) { + LOG_ERROR("Save failed, status = %{public}d", status); + ctxt->status = napi_invalid_arg; + ctxt->error = std::string("operation failed"); + return; + } + ctxt->status = napi_ok; + LOG_INFO("end"); + }, + output); +} + +napi_value JSDistributedObject::GetSaveResultCons( + napi_env env, std::string &sessionId, double version, std::string deviceId) +{ + const char *objectName = "SaveResult"; + napi_value napiSessionId, napiVersion, napiDeviceId; + napi_value result; + + napi_status status = JSUtil::SetValue(env, sessionId, napiSessionId); + ASSERT_MATCH_ELSE_RETURN_NULL(status == napi_ok); + status = JSUtil::SetValue(env, version, napiVersion); + ASSERT_MATCH_ELSE_RETURN_NULL(status == napi_ok); + status = JSUtil::SetValue(env, deviceId, napiDeviceId); + ASSERT_MATCH_ELSE_RETURN_NULL(status == napi_ok); + napi_property_descriptor desc[] = { + DECLARE_NAPI_PROPERTY("sessionId", napiSessionId), + DECLARE_NAPI_PROPERTY("version", napiVersion), + DECLARE_NAPI_PROPERTY("deviceId", napiDeviceId) + }; + + status = napi_define_class(env, objectName, strlen(objectName), JSDistributedObject::JSConstructor, nullptr, + sizeof(desc) / sizeof(desc[0]), desc, &result); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + return result; +} + +napi_value JSDistributedObject::GetRevokeSaveResultCons(napi_env env, std::string &sessionId) +{ + const char *objectName = "RevokeSaveResult"; + napi_value napiSessionId; + napi_value result; + + napi_status status = JSUtil::SetValue(env, sessionId, napiSessionId); + ASSERT_MATCH_ELSE_RETURN_NULL(status == napi_ok); + napi_property_descriptor desc[] = { + DECLARE_NAPI_PROPERTY("sessionId", napiSessionId) + }; + + status = napi_define_class(env, objectName, strlen(objectName), JSDistributedObject::JSConstructor, nullptr, + sizeof(desc) / sizeof(desc[0]), desc, &result); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + return result; +} +} // namespace OHOS::ObjectStore \ No newline at end of file diff --git a/data_object/frameworks/jskitsimpl/src/adaptor/js_distributedobjectstore.cpp b/data_object/frameworks/jskitsimpl/src/adaptor/js_distributedobjectstore.cpp new file mode 100644 index 00000000..7b21bd39 --- /dev/null +++ b/data_object/frameworks/jskitsimpl/src/adaptor/js_distributedobjectstore.cpp @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "js_distributedobjectstore.h" + +#include + +#include "ability_context.h" +#include "accesstoken_kit.h" +#include "application_context.h" +#include "distributed_objectstore.h" +#include "js_common.h" +#include "js_distributedobject.h" +#include "js_object_wrapper.h" +#include "js_util.h" +#include "logger.h" +#include "objectstore_errors.h" + +namespace OHOS::ObjectStore { +constexpr size_t TYPE_SIZE = 10; +const std::string DISTRIBUTED_DATASYNC = "ohos.permission.DISTRIBUTED_DATASYNC"; +static std::map> g_statusCallBacks; +static std::map> g_changeCallBacks; + +void JSDistributedObjectStore::AddCallback(napi_env env, std::map> &callbacks, + const std::string &objectId, napi_value callback) +{ + LOG_INFO("add callback %{public}s", objectId.c_str()); + napi_ref ref = nullptr; + napi_status status = napi_create_reference(env, callback, 1, &ref); + CHECK_EQUAL_WITH_RETURN_VOID(status, napi_ok); + if (callbacks.count(objectId) != 0) { + auto lists = callbacks.at(objectId); + lists.push_back(ref); + callbacks.insert_or_assign(objectId, lists); + } else { + std::list lists = { ref }; + callbacks.insert_or_assign(objectId, lists); + } +} +void JSDistributedObjectStore::DelCallback(napi_env env, std::map> &callbacks, + const std::string &sessionId, napi_value callback) +{ + LOG_INFO("del callback %{public}s", sessionId.c_str()); + napi_status status; + if (callback == nullptr) { + if (callbacks.count(sessionId) != 0) { + for (auto ref : callbacks.at(sessionId)) { + status = napi_delete_reference(env, ref); + CHECK_EQUAL_WITH_RETURN_VOID(status, napi_ok); + } + callbacks.erase(sessionId); + } + return; + } + napi_value callbackTmp; + if (callbacks.count(sessionId) != 0) { + auto lists = callbacks.at(sessionId); + for (auto iter = lists.begin(); iter != lists.end();) { + status = napi_get_reference_value(env, *iter, &callbackTmp); + CHECK_EQUAL_WITH_RETURN_VOID(status, napi_ok); + bool isEquals = false; + napi_strict_equals(env, callbackTmp, callback, &isEquals); + if (isEquals) { + napi_delete_reference(env, *iter); + iter = lists.erase(iter); + } else { + iter++; + } + } + if (lists.empty()) { + callbacks.erase(sessionId); + } else { + callbacks.insert_or_assign(sessionId, lists); + } + } +} +napi_value JSDistributedObjectStore::NewDistributedObject( + napi_env env, DistributedObjectStore *objectStore, DistributedObject *object, const std::string &objectId) +{ + napi_value result; + napi_status status = napi_new_instance(env, JSDistributedObject::GetCons(env), 0, nullptr, &result); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + JSObjectWrapper *objectWrapper = new JSObjectWrapper(objectStore, object); + status = napi_wrap( + env, result, objectWrapper, + [](napi_env env, void *data, void *hint) { + if (data == nullptr) { + LOG_WARN("objectWrapper is nullptr."); + return; + } + auto objectWrapper = (JSObjectWrapper *)data; + if (objectWrapper->GetObject() == nullptr) { + delete objectWrapper; + return; + } + LOG_INFO("start delete object"); + DistributedObjectStore::GetInstance(JSDistributedObjectStore::GetBundleName(env)) + ->DeleteObject(objectWrapper->GetObject()->GetSessionId()); + delete objectWrapper; + }, + nullptr, nullptr); + RestoreWatchers(env, objectWrapper, objectId); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + return result; +} + +// function createObjectSync(sessionId: string, objectId:string): DistributedObject; +napi_value JSDistributedObjectStore::JSCreateObjectSync(napi_env env, napi_callback_info info) +{ + if (IsSandBox()) { + LOG_WARN("entering sandbox app."); + return nullptr; + } + LOG_INFO("start JSCreateObjectSync"); + if (!JSDistributedObjectStore::CheckSyncPermission()) { + LOG_INFO("no permission ohos.permission.DISTRIBUTED_DATASYNC"); + return nullptr; + } + size_t requireArgc = 2; + size_t argc = 2; + napi_value argv[2] = { 0 }; + napi_value thisVar = nullptr; + void *data = nullptr; + std::string sessionId; + std::string objectId; + napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(argc >= requireArgc); + napi_valuetype valueType = napi_undefined; + status = napi_typeof(env, argv[0], &valueType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + CHECK_EQUAL_WITH_RETURN_NULL(valueType, napi_string) + status = JSUtil::GetValue(env, argv[0], sessionId); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + status = napi_typeof(env, argv[1], &valueType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + CHECK_EQUAL_WITH_RETURN_NULL(valueType, napi_string) + status = JSUtil::GetValue(env, argv[1], objectId); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + DistributedObjectStore *objectInfo = + DistributedObjectStore::GetInstance(JSDistributedObjectStore::GetBundleName(env)); + ASSERT_MATCH_ELSE_RETURN_NULL(objectInfo != nullptr); + DistributedObject *object = objectInfo->CreateObject(sessionId); + ASSERT_MATCH_ELSE_RETURN_NULL(object != nullptr); + return NewDistributedObject(env, objectInfo, object, objectId); +} + +// function destroyObjectSync(object: DistributedObject): number; +napi_value JSDistributedObjectStore::JSDestroyObjectSync(napi_env env, napi_callback_info info) +{ + LOG_INFO("start"); + size_t requireArgc = 1; + size_t argc = 1; + napi_value argv[1] = { 0 }; + napi_value thisVar = nullptr; + void *data = nullptr; + std::string sessionId; + std::string bundleName; + napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(argc >= requireArgc); + + JSObjectWrapper *objectWrapper = nullptr; + status = napi_unwrap(env, argv[0], (void **)&objectWrapper); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(objectWrapper != nullptr); + DistributedObjectStore *objectInfo = + DistributedObjectStore::GetInstance(JSDistributedObjectStore::GetBundleName(env)); + ASSERT_MATCH_ELSE_RETURN_NULL(objectInfo != nullptr && objectWrapper->GetObject() != nullptr); + objectWrapper->DeleteWatch(env, CHANGE); + objectWrapper->DeleteWatch(env, STATUS); + objectInfo->DeleteObject(objectWrapper->GetObject()->GetSessionId()); + objectWrapper->DestroyObject(); + return nullptr; +} + +// function on(type: 'change', object: DistributedObject, callback: Callback): void; +// function on(type: 'status', object: DistributedObject, callback: Callback): void; +napi_value JSDistributedObjectStore::JSOn(napi_env env, napi_callback_info info) +{ + LOG_INFO("start"); + size_t requireArgc = 3; + size_t argc = 3; + napi_value argv[3] = { 0 }; + napi_value thisVar = nullptr; + void *data = nullptr; + napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(argc >= requireArgc); + + char type[TYPE_SIZE] = { 0 }; + size_t eventTypeLen = 0; + napi_valuetype eventValueType = napi_undefined; + status = napi_typeof(env, argv[0], &eventValueType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(eventValueType == napi_string); + status = napi_get_value_string_utf8(env, argv[0], type, TYPE_SIZE, &eventTypeLen); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + + napi_valuetype objectType = napi_undefined; + status = napi_typeof(env, argv[1], &objectType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(objectType == napi_object); + + napi_valuetype callbackType = napi_undefined; + status = napi_typeof(env, argv[2], &callbackType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(callbackType == napi_function); + + JSObjectWrapper *wrapper = nullptr; + status = napi_unwrap(env, argv[1], (void **)&wrapper); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(wrapper != nullptr); + wrapper->AddWatch(env, type, argv[2]); + napi_value result = nullptr; + napi_get_undefined(env, &result); + return result; +} + +// function off(type: 'change', object: DistributedObject, callback?: Callback): void; +// function off(type: 'status', object: DistributedObject, callback?: Callback): void; +napi_value JSDistributedObjectStore::JSOff(napi_env env, napi_callback_info info) +{ + LOG_INFO("start"); + size_t requireArgc = 2; + size_t argc = 3; + napi_value argv[3] = { 0 }; + napi_value thisVar = nullptr; + void *data = nullptr; + char type[TYPE_SIZE] = { 0 }; + size_t typeLen = 0; + napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(argc >= requireArgc); + for (size_t i = 0; i < argc; i++) { + napi_valuetype valueType = napi_undefined; + status = napi_typeof(env, argv[i], &valueType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + + if (i == 0 && valueType == napi_string) { + status = napi_get_value_string_utf8(env, argv[i], type, TYPE_SIZE, &typeLen); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + } else if (i == 1 && valueType == napi_object) { + continue; + } else if (i == 2 && (valueType == napi_function || valueType == napi_undefined)) { + continue; + } else { + NAPI_ASSERT(env, false, "type mismatch"); + } + } + JSObjectWrapper *wrapper = nullptr; + status = napi_unwrap(env, argv[1], (void **)&wrapper); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(wrapper != nullptr); + if (argc == requireArgc) { + LOG_INFO("delete all"); + wrapper->DeleteWatch(env, type); + } else { + LOG_INFO("delete"); + wrapper->DeleteWatch(env, type, argv[2]); + } + + napi_value result = nullptr; + status = napi_get_undefined(env, &result); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + return result; +} + +std::string JSDistributedObjectStore::GetBundleName(napi_env env) +{ + static std::string bundleName; + if (bundleName.empty()) { + bundleName = AbilityRuntime::Context::GetApplicationContext()->GetBundleName(); + } + return bundleName; +} + +void JSDistributedObjectStore::RestoreWatchers(napi_env env, JSObjectWrapper *wrapper, const std::string &objectId) +{ + napi_status status; + napi_value callbackValue; + LOG_DEBUG("start restore %{public}s", objectId.c_str()); + if (g_changeCallBacks.count(objectId) != 0) { + LOG_INFO("restore change on %{public}s", objectId.c_str()); + for (auto callback : g_changeCallBacks.at(objectId)) { + status = napi_get_reference_value(env, callback, &callbackValue); + if (status != napi_ok) { + LOG_ERROR("error! %{public}d", status); + continue; + } + wrapper->AddWatch(env, CHANGE, callbackValue); + } + } else { + LOG_INFO("no callback %{public}s", objectId.c_str()); + } + if (g_statusCallBacks.count(objectId) != 0) { + LOG_INFO("restore status on %{public}s", objectId.c_str()); + for (auto callback : g_statusCallBacks.at(objectId)) { + status = napi_get_reference_value(env, callback, &callbackValue); + if (status != napi_ok) { + LOG_ERROR("error! %{public}d", status); + continue; + } + wrapper->AddWatch(env, STATUS, callbackValue); + } + } else { + LOG_INFO("no status callback %{public}s", objectId.c_str()); + } +} + +// function recordCallback(type: 'change', objectId: string, callback: Callback): void; +// function recordCallback(type: 'status', objectId: string, callback: Callback): void; +napi_value JSDistributedObjectStore::JSRecordCallback(napi_env env, napi_callback_info info) +{ + LOG_INFO("start"); + size_t requireArgc = 3; + size_t argc = 3; + napi_value argv[3] = { 0 }; + napi_value thisVar = nullptr; + void *data = nullptr; + napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(argc >= requireArgc); + + char type[TYPE_SIZE] = { 0 }; + size_t eventTypeLen = 0; + napi_valuetype valueType = napi_undefined; + status = napi_typeof(env, argv[0], &valueType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(valueType == napi_string); + status = napi_get_value_string_utf8(env, argv[0], type, TYPE_SIZE, &eventTypeLen); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + + std::string objectId; + status = napi_typeof(env, argv[1], &valueType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + CHECK_EQUAL_WITH_RETURN_NULL(valueType, napi_string) + status = JSUtil::GetValue(env, argv[1], objectId); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + + napi_valuetype callbackType = napi_undefined; + status = napi_typeof(env, argv[2], &callbackType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(callbackType == napi_function); + + if (!strcmp(CHANGE, type)) { + AddCallback(env, g_changeCallBacks, objectId, argv[2]); + } else if (!strcmp(STATUS, type)) { + AddCallback(env, g_statusCallBacks, objectId, argv[2]); + } + napi_value result = nullptr; + napi_get_undefined(env, &result); + return result; +} + +// function deleteCallback(type: 'change', objectId: string, callback?: Callback): void; +// function deleteCallback(type: 'status', objectId: string, callback?: Callback): void; +napi_value JSDistributedObjectStore::JSDeleteCallback(napi_env env, napi_callback_info info) +{ + LOG_INFO("start"); + size_t requireArgc = 3; + size_t argc = 3; + napi_value argv[3] = { 0 }; + napi_value thisVar = nullptr; + void *data = nullptr; + napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(argc >= requireArgc); + + char type[TYPE_SIZE] = { 0 }; + size_t eventTypeLen = 0; + napi_valuetype valueType = napi_undefined; + status = napi_typeof(env, argv[0], &valueType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(valueType == napi_string); + status = napi_get_value_string_utf8(env, argv[0], type, TYPE_SIZE, &eventTypeLen); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + + std::string objectId; + status = napi_typeof(env, argv[1], &valueType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + CHECK_EQUAL_WITH_RETURN_NULL(valueType, napi_string) + status = JSUtil::GetValue(env, argv[1], objectId); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + + if (argc == 2) { + if (!strcmp(CHANGE, type)) { + DelCallback(env, g_changeCallBacks, objectId); + } else if (!strcmp(STATUS, type)) { + DelCallback(env, g_statusCallBacks, objectId); + } + } else { + napi_valuetype callbackType = napi_undefined; + status = napi_typeof(env, argv[2], &callbackType); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + ASSERT_MATCH_ELSE_RETURN_NULL(callbackType == napi_function); + if (!strcmp(CHANGE, type)) { + DelCallback(env, g_changeCallBacks, objectId, argv[2]); + } else if (!strcmp(STATUS, type)) { + DelCallback(env, g_statusCallBacks, objectId, argv[2]); + } + } + + napi_value result = nullptr; + napi_get_undefined(env, &result); + return result; +} + +bool JSDistributedObjectStore::CheckSyncPermission() +{ + int32_t ret = Security::AccessToken::AccessTokenKit::VerifyAccessToken( + AbilityRuntime::Context::GetApplicationContext()->GetApplicationInfo()->accessTokenId, DISTRIBUTED_DATASYNC); + if (ret == Security::AccessToken::PermissionState::PERMISSION_DENIED) { + LOG_ERROR("VerifyPermission %{public}d: PERMISSION_DENIED", + AbilityRuntime::Context::GetApplicationContext()->GetApplicationInfo()->accessTokenId); + return false; + } + return true; +} + +// don't create distributed data object while this application is sandbox +bool JSDistributedObjectStore::IsSandBox() +{ + int32_t dlpFlag = Security::AccessToken::AccessTokenKit::GetHapDlpFlag( + AbilityRuntime::Context::GetApplicationContext()->GetApplicationInfo()->accessTokenId); + if (dlpFlag != 0) { + return true; + } + return false; +} +} // namespace OHOS::ObjectStore diff --git a/data_object/frameworks/jskitsimpl/src/adaptor/js_module_init.cpp b/data_object/frameworks/jskitsimpl/src/adaptor/js_module_init.cpp new file mode 100644 index 00000000..832177db --- /dev/null +++ b/data_object/frameworks/jskitsimpl/src/adaptor/js_module_init.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "js_common.h" +#include "js_distributedobjectstore.h" +#include "logger.h" +#include "notifier_impl.h" + +using namespace OHOS::ObjectStore; + +extern const char _binary_distributed_data_object_js_start[]; +extern const char _binary_distributed_data_object_js_end[]; + +extern const char _binary_distributed_data_object_abc_start[]; +extern const char _binary_distributed_data_object_abc_end[]; + +static napi_value DistributedDataObjectExport(napi_env env, napi_value exports) +{ + napi_status status; + static napi_property_descriptor desc[] = { + DECLARE_NAPI_FUNCTION("createObjectSync", JSDistributedObjectStore::JSCreateObjectSync), + DECLARE_NAPI_FUNCTION("destroyObjectSync", JSDistributedObjectStore::JSDestroyObjectSync), + DECLARE_NAPI_FUNCTION("on", JSDistributedObjectStore::JSOn), + DECLARE_NAPI_FUNCTION("off", JSDistributedObjectStore::JSOff), + DECLARE_NAPI_FUNCTION("recordCallback", JSDistributedObjectStore::JSRecordCallback), + DECLARE_NAPI_FUNCTION("deleteCallback", JSDistributedObjectStore::JSDeleteCallback), + }; + + status = napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); + CHECK_EQUAL_WITH_RETURN_NULL(status, napi_ok); + return exports; +} + +// storage module define +static napi_module storageModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = DistributedDataObjectExport, + .nm_modname = "data.distributedDataObject", + .nm_priv = ((void *)0), + .reserved = { 0 }, +}; + +// distributed_data_object.js +extern "C" __attribute__((visibility("default"))) void NAPI_data_distributedDataObject_GetJSCode( + const char **buf, int *bufLen) +{ + if (buf != nullptr) { + *buf = _binary_distributed_data_object_js_start; + } + + if (bufLen != nullptr) { + *bufLen = _binary_distributed_data_object_js_end - _binary_distributed_data_object_js_start; + } +} + +extern "C" __attribute__((visibility("default"))) void NAPI_data_distributedDataObject_GetABCCode( + const char **buf, int *bufLen) +{ + if (buf != nullptr) { + *buf = _binary_distributed_data_object_abc_start; + } + + if (bufLen != nullptr) { + *bufLen = _binary_distributed_data_object_abc_end - _binary_distributed_data_object_abc_start; + } +} + +// distributeddataobject module register +static __attribute__((constructor)) void RegisterModule() +{ + napi_module_register(&storageModule); +} \ No newline at end of file diff --git a/data_object/frameworks/jskitsimpl/src/adaptor/js_object_wrapper.cpp b/data_object/frameworks/jskitsimpl/src/adaptor/js_object_wrapper.cpp new file mode 100644 index 00000000..ef950ea1 --- /dev/null +++ b/data_object/frameworks/jskitsimpl/src/adaptor/js_object_wrapper.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "js_object_wrapper.h" + +#include "logger.h" +namespace OHOS::ObjectStore { +JSObjectWrapper::JSObjectWrapper(DistributedObjectStore *objectStore, DistributedObject *object) + : objectStore_(objectStore), object_(object) +{ +} + +JSObjectWrapper::~JSObjectWrapper() +{ + LOG_INFO("JSObjectWrapper::~JSObjectWrapper"); + std::unique_lock cacheLock(watchMutex_); + if (watcher_ != nullptr) { + watcher_ = nullptr; + } + LOG_INFO("JSObjectWrapper::~JSObjectWrapper end"); +} + +DistributedObject *JSObjectWrapper::GetObject() +{ + return object_; +} + +bool JSObjectWrapper::AddWatch(napi_env env, const char *type, napi_value handler) +{ + std::unique_lock cacheLock(watchMutex_); + if (watcher_ == nullptr) { + watcher_ = std::make_unique(env, objectStore_, object_); + if (watcher_ == nullptr) { + LOG_ERROR("JSObjectWrapper::new JSWatcher fail"); + return false; + } + } + return watcher_->On(type, handler); +} + +void JSObjectWrapper::DeleteWatch(napi_env env, const char *type, napi_value handler) +{ + std::unique_lock cacheLock(watchMutex_); + if (watcher_ != nullptr) { + watcher_->Off(type, handler); + LOG_INFO("JSObjectWrapper::DeleteWatch %{public}s", type); + } else { + LOG_ERROR("JSObjectWrapper::DeleteWatch watcher_ is null"); + } +} + +bool JSObjectWrapper::isUndefined(char *value) +{ + std::string tmpStr = value; + auto it = std::find(undefinedProperties.begin(), undefinedProperties.end(), tmpStr); + if (it == undefinedProperties.end()) { + return false; + } + return true; +} + +void JSObjectWrapper::AddUndefined(char *value) +{ + std::string tmpStr = value; + if (std::find(undefinedProperties.begin(), undefinedProperties.end(), tmpStr) == undefinedProperties.end()) { + undefinedProperties.push_back(tmpStr); + } +} + +void JSObjectWrapper::DeleteUndefined(char *value) +{ + std::string tmpStr = value; + auto it = std::find(undefinedProperties.begin(), undefinedProperties.end(), tmpStr); + if (it != undefinedProperties.end()) { + undefinedProperties.erase(it); + } +} + +void JSObjectWrapper::DestroyObject() +{ + object_ = nullptr; +} +} // namespace OHOS::ObjectStore \ No newline at end of file diff --git a/data_object/frameworks/jskitsimpl/src/adaptor/js_watcher.cpp b/data_object/frameworks/jskitsimpl/src/adaptor/js_watcher.cpp new file mode 100644 index 00000000..7c1d4fec --- /dev/null +++ b/data_object/frameworks/jskitsimpl/src/adaptor/js_watcher.cpp @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "js_watcher.h" + +#include + +#include "js_common.h" +#include "js_util.h" +#include "logger.h" +#include "notifier_impl.h" +#include "objectstore_errors.h" + +namespace OHOS::ObjectStore { +JSWatcher::JSWatcher(const napi_env env, DistributedObjectStore *objectStore, DistributedObject *object) + : UvQueue(env), env_(env) +{ + changeEventListener_ = new ChangeEventListener(this, objectStore, object); + statusEventListener_ = new StatusEventListener(this, object->GetSessionId()); +} + +JSWatcher::~JSWatcher() +{ + delete changeEventListener_; + delete statusEventListener_; + changeEventListener_ = nullptr; + statusEventListener_ = nullptr; +} + +bool JSWatcher::On(const char *type, napi_value handler) +{ + EventListener *listener = Find(type); + if (listener == nullptr) { + LOG_ERROR("error type %{public}s", type); + return false; + } + return listener->Add(env_, handler); +} + +void JSWatcher::Off(const char *type, napi_value handler) +{ + EventListener *listener = Find(type); + if (listener == nullptr) { + LOG_ERROR("error type %{public}s", type); + return; + } + if (handler == nullptr) { + listener->Clear(env_); + } else { + listener->Del(env_, handler); + } +} +void JSWatcher::ProcessChange(napi_env env, std::list &args) +{ + constexpr static int8_t ARGV_SIZE = 2; + napi_value callback = nullptr; + napi_value global = nullptr; + napi_value param[ARGV_SIZE]; + napi_value result; + napi_status status = napi_get_global(env, &global); + ASSERT_MATCH_ELSE_GOTO_ERROR(status == napi_ok); + for (auto item : args) { + ChangeArgs *changeArgs = static_cast(item); + status = napi_get_reference_value(env, changeArgs->callback_, &callback); + ASSERT_MATCH_ELSE_GOTO_ERROR(status == napi_ok); + status = JSUtil::SetValue(env, changeArgs->sessionId_, param[0]); + ASSERT_MATCH_ELSE_GOTO_ERROR(status == napi_ok); + JSUtil::SetValue(env, changeArgs->changeData_, param[1]); + ASSERT_MATCH_ELSE_GOTO_ERROR(status == napi_ok); + LOG_INFO("start %{public}s, %{public}zu", changeArgs->sessionId_.c_str(), changeArgs->changeData_.size()); + status = napi_call_function(env, global, callback, ARGV_SIZE, param, &result); + LOG_INFO("end %{public}s, %{public}zu", changeArgs->sessionId_.c_str(), changeArgs->changeData_.size()); + ASSERT_MATCH_ELSE_GOTO_ERROR(status == napi_ok); + } +ERROR: + for (auto item : args) { + ChangeArgs *changeArgs = static_cast(item); + delete changeArgs; + } + args.clear(); +} +void JSWatcher::Emit(const char *type, const std::string &sessionId, const std::vector &changeData) +{ + if (changeData.empty()) { + LOG_ERROR("empty change"); + return; + } + LOG_INFO("start %{public}s, %{public}s", sessionId.c_str(), changeData.at(0).c_str()); + EventListener *listener = Find(type); + if (listener == nullptr) { + LOG_ERROR("error type %{public}s", type); + return; + } + + for (EventHandler *handler = listener->handlers_; handler != nullptr; handler = handler->next) { + ChangeArgs *changeArgs = new ChangeArgs(handler->callbackRef, sessionId, changeData); + CallFunction(ProcessChange, changeArgs); + } +} + +EventListener *JSWatcher::Find(const char *type) +{ + if (!strcmp(CHANGE, type)) { + return changeEventListener_; + } + if (!strcmp(STATUS, type)) { + return statusEventListener_; + } + return nullptr; +} + +void JSWatcher::ProcessStatus(napi_env env, std::list &args) +{ + constexpr static int8_t ARGV_SIZE = 3; + napi_value callback = nullptr; + napi_value global = nullptr; + napi_value param[ARGV_SIZE]; + napi_value result; + napi_status status = napi_get_global(env, &global); + ASSERT_MATCH_ELSE_GOTO_ERROR(status == napi_ok); + for (auto item : args) { + StatusArgs *statusArgs = static_cast(item); + status = napi_get_reference_value(env, statusArgs->callback_, &callback); + ASSERT_MATCH_ELSE_GOTO_ERROR(status == napi_ok); + status = JSUtil::SetValue(env, statusArgs->sessionId_, param[0]); + ASSERT_MATCH_ELSE_GOTO_ERROR(status == napi_ok); + JSUtil::SetValue(env, statusArgs->networkId_, param[1]); + ASSERT_MATCH_ELSE_GOTO_ERROR(status == napi_ok); + JSUtil::SetValue(env, statusArgs->status_, param[2]); + ASSERT_MATCH_ELSE_GOTO_ERROR(status == napi_ok); + LOG_INFO("start %{public}s, %{public}s, %{public}s", statusArgs->sessionId_.c_str(), + statusArgs->networkId_.c_str(), statusArgs->status_.c_str()); + status = napi_call_function(env, global, callback, ARGV_SIZE, param, &result); + LOG_INFO("end %{public}s, %{public}s, %{public}s", statusArgs->sessionId_.c_str(), + statusArgs->networkId_.c_str(), statusArgs->status_.c_str()); + ASSERT_MATCH_ELSE_GOTO_ERROR(status == napi_ok); + } +ERROR: + LOG_DEBUG("do clear"); + for (auto item : args) { + StatusArgs *statusArgs = static_cast(item); + delete statusArgs; + } + args.clear(); +} + +void JSWatcher::Emit( + const char *type, const std::string &sessionId, const std::string &networkId, const std::string &status) +{ + if (sessionId.empty() || networkId.empty()) { + LOG_ERROR("empty %{public}s %{public}s", sessionId.c_str(), networkId.c_str()); + return; + } + LOG_ERROR("status change %{public}s %{public}s", sessionId.c_str(), networkId.c_str()); + EventListener *listener = Find(type); + if (listener == nullptr) { + LOG_ERROR("error type %{public}s", type); + return; + } + + for (EventHandler *handler = listener->handlers_; handler != nullptr; handler = handler->next) { + StatusArgs *changeArgs = new StatusArgs(handler->callbackRef, sessionId, networkId, status); + CallFunction(ProcessStatus, changeArgs); + } + return; +} + +EventHandler *EventListener::Find(napi_env env, napi_value handler) +{ + EventHandler *result = nullptr; + for (EventHandler *i = handlers_; i != nullptr; i = i->next) { + napi_value callback = nullptr; + napi_get_reference_value(env, i->callbackRef, &callback); + bool isEquals = false; + napi_strict_equals(env, handler, callback, &isEquals); + if (isEquals) { + result = i; + } + } + return result; +} + +void EventListener::Clear(napi_env env) +{ + for (EventHandler *i = handlers_; i != nullptr; i = handlers_) { + handlers_ = i->next; + napi_delete_reference(env, i->callbackRef); + delete i; + } +} + +bool EventListener::Del(napi_env env, napi_value handler) +{ + EventHandler *temp = nullptr; + napi_status status; + for (EventHandler *i = handlers_; i != nullptr;) { + napi_value callback = nullptr; + status = napi_get_reference_value(env, i->callbackRef, &callback); + if (status != napi_ok) { + LOG_ERROR("error! %{public}d", status); + return false; + } + bool isEquals = false; + napi_strict_equals(env, handler, callback, &isEquals); + if (isEquals) { + EventHandler *delData = i; + i = i->next; + if (temp == nullptr) { + handlers_ = delData->next; + } else { + temp->next = delData->next; + } + napi_delete_reference(env, delData->callbackRef); + delete delData; + } else { + temp = i; + i = i->next; + } + } + return handlers_ == nullptr; +} + +bool EventListener::Add(napi_env env, napi_value handler) +{ + if (Find(env, handler) != nullptr) { + LOG_ERROR("has added,return"); + return false; + } + + if (handlers_ == nullptr) { + handlers_ = new EventHandler(); + handlers_->next = nullptr; + } else { + auto temp = new EventHandler(); + temp->next = handlers_; + handlers_ = temp; + } + napi_create_reference(env, handler, 1, &handlers_->callbackRef); + return true; +} + +void WatcherImpl::OnChanged(const std::string &sessionid, const std::vector &changedData) +{ + if (watcher_ == nullptr) { + LOG_ERROR("watcher_ is null"); + return; + } + watcher_->Emit(CHANGE, sessionid, changedData); +} + +WatcherImpl::~WatcherImpl() +{ + LOG_ERROR("destroy"); + watcher_ = nullptr; +} + +bool ChangeEventListener::Add(napi_env env, napi_value handler) +{ + if (!isWatched_ && object_ != nullptr) { + std::shared_ptr watcher = std::make_shared(watcher_); + if (watcher == nullptr) { + LOG_ERROR("new %{public}s error", object_->GetSessionId().c_str()); + return false; + } + uint32_t ret = objectStore_->Watch(object_, watcher); + if (ret != SUCCESS) { + LOG_ERROR("Watch %{public}s error", object_->GetSessionId().c_str()); + } else { + LOG_INFO("Watch %{public}s success", object_->GetSessionId().c_str()); + isWatched_ = true; + } + } + return EventListener::Add(env, handler); +} + +bool ChangeEventListener::Del(napi_env env, napi_value handler) +{ + bool isEmpty = EventListener::Del(env, handler); + if (isEmpty && isWatched_ && object_ != nullptr) { + uint32_t ret = objectStore_->UnWatch(object_); + if (ret != SUCCESS) { + LOG_ERROR("UnWatch %{public}s error", object_->GetSessionId().c_str()); + } else { + LOG_INFO("UnWatch %{public}s success", object_->GetSessionId().c_str()); + isWatched_ = false; + } + } + return isEmpty; +} + +void ChangeEventListener::Clear(napi_env env) +{ + EventListener::Clear(env); + if (isWatched_ && object_ != nullptr) { + uint32_t ret = objectStore_->UnWatch(object_); + if (ret != SUCCESS) { + LOG_ERROR("UnWatch %{public}s error", object_->GetSessionId().c_str()); + } else { + LOG_INFO("UnWatch %{public}s success", object_->GetSessionId().c_str()); + isWatched_ = false; + } + } +} + +ChangeEventListener::ChangeEventListener( + JSWatcher *watcher, DistributedObjectStore *objectStore, DistributedObject *object) + : objectStore_(objectStore), object_(object), watcher_(watcher) +{ +} + +bool StatusEventListener::Add(napi_env env, napi_value handler) +{ + LOG_INFO("Add status watch %{public}s", sessionId_.c_str()); + NotifierImpl::GetInstance()->AddWatcher(sessionId_, watcher_); + return EventListener::Add(env, handler); +} + +bool StatusEventListener::Del(napi_env env, napi_value handler) +{ + if (EventListener::Del(env, handler)) { + LOG_INFO("Del status watch %{public}s", sessionId_.c_str()); + NotifierImpl::GetInstance()->DelWatcher(sessionId_); + return true; + } + return false; +} + +void StatusEventListener::Clear(napi_env env) +{ + NotifierImpl::GetInstance()->DelWatcher(sessionId_); + EventListener::Clear(env); +} + +StatusEventListener::StatusEventListener(JSWatcher *watcher, const std::string &sessionId) + : watcher_(watcher), sessionId_(sessionId) +{ +} + +JSWatcher::ChangeArgs::ChangeArgs( + const napi_ref callback, const std::string &sessionId, const std::vector &changeData) + : callback_(callback), sessionId_(sessionId), changeData_(changeData) +{ +} + +JSWatcher::StatusArgs::StatusArgs( + const napi_ref callback, const std::string &sessionId, const std::string &networkId, const std::string &status) + : callback_(callback), sessionId_(sessionId), networkId_(networkId), status_(status) +{ +} +} // namespace OHOS::ObjectStore diff --git a/data_object/frameworks/jskitsimpl/src/adaptor/notifier_impl.cpp b/data_object/frameworks/jskitsimpl/src/adaptor/notifier_impl.cpp new file mode 100644 index 00000000..394a4553 --- /dev/null +++ b/data_object/frameworks/jskitsimpl/src/adaptor/notifier_impl.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "notifier_impl.h" + +#include "logger.h" +#include "objectstore_errors.h" + +namespace OHOS::ObjectStore { +std::shared_ptr NotifierImpl::GetInstance() +{ + static std::shared_ptr instance; + static std::mutex instanceLock; + if (instance == nullptr) { + std::lock_guard lockGuard(instanceLock); + if (instance == nullptr) { + instance = std::make_shared(); + if (instance == nullptr) { + LOG_ERROR("Failed to alloc NotifierImpl!"); + return nullptr; + } + uint32_t ret = DistributedObjectStore::GetInstance()->SetStatusNotifier(instance); + if (ret != SUCCESS) { + LOG_ERROR("SetStatusNotifier %{public}d error", ret); + } else { + LOG_INFO("SetStatusNotifier success"); + } + } + } + return instance; +} + +void NotifierImpl::AddWatcher(std::string &sessionId, JSWatcher *watcher) +{ + std::lock_guard lock(mutex_); + watchers_.insert_or_assign(sessionId, watcher); +} + +void NotifierImpl::DelWatcher(std::string &sessionId) +{ + std::lock_guard lock(mutex_); + watchers_.erase(sessionId); +} + +void NotifierImpl::OnChanged( + const std::string &sessionId, const std::string &networkId, const std::string &onlineStatus) +{ + LOG_INFO( + "status changed %{public}s %{public}s %{public}s", sessionId.c_str(), networkId.c_str(), onlineStatus.c_str()); + std::lock_guard lock(mutex_); + if (watchers_.count(sessionId) != 0) { + LOG_INFO( + "start emit %{public}s %{public}s %{public}s", sessionId.c_str(), networkId.c_str(), onlineStatus.c_str()); + watchers_.at(sessionId)->Emit("status", sessionId, networkId, onlineStatus); + LOG_INFO( + "end emit %{public}s %{public}s %{public}s", sessionId.c_str(), networkId.c_str(), onlineStatus.c_str()); + } +} +NotifierImpl::~NotifierImpl() +{ +} +} // namespace OHOS::ObjectStore diff --git a/data_object/frameworks/jskitsimpl/src/common/js_util.cpp b/data_object/frameworks/jskitsimpl/src/common/js_util.cpp new file mode 100644 index 00000000..e43d3708 --- /dev/null +++ b/data_object/frameworks/jskitsimpl/src/common/js_util.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "js_util.h" + +#include +#include + +#include "logger.h" + +namespace OHOS::ObjectStore { +constexpr int32_t STR_MAX_LENGTH = 4096; +constexpr size_t STR_TAIL_LENGTH = 1; + +/* napi_value <-> bool */ +napi_status JSUtil::GetValue(napi_env env, napi_value in, bool &out) +{ + LOG_DEBUG("napi_value <- bool"); + return napi_get_value_bool(env, in, &out); +} + +napi_status JSUtil::SetValue(napi_env env, const bool &in, napi_value &out) +{ + LOG_DEBUG("napi_value -> bool"); + return napi_get_boolean(env, in, &out); +} + +/* napi_value <-> double */ +napi_status JSUtil::GetValue(napi_env env, napi_value in, double &out) +{ + LOG_DEBUG("napi_value -> double"); + return napi_get_value_double(env, in, &out); +} + +napi_status JSUtil::SetValue(napi_env env, const double &in, napi_value &out) +{ + LOG_DEBUG("napi_value <- double"); + return napi_create_double(env, in, &out); +} + +/* napi_value <-> std::string */ +napi_status JSUtil::GetValue(napi_env env, napi_value in, std::string &out) +{ + size_t maxLen = STR_MAX_LENGTH; + napi_status status = napi_get_value_string_utf8(env, in, NULL, 0, &maxLen); + if (maxLen <= 0) { + GET_AND_THROW_LAST_ERROR(env); + return status; + } + char *buf = new (std::nothrow) char[maxLen + STR_TAIL_LENGTH]; + if (buf != nullptr) { + size_t len = 0; + status = napi_get_value_string_utf8(env, in, buf, maxLen + STR_TAIL_LENGTH, &len); + if (status != napi_ok) { + GET_AND_THROW_LAST_ERROR(env); + } + buf[len] = 0; + out = std::string(buf); + delete[] buf; + } else { + status = napi_generic_failure; + } + return status; +} + +napi_status JSUtil::SetValue(napi_env env, const std::string &in, napi_value &out) +{ + LOG_DEBUG("napi_value <- std::string %{public}d", (int)in.length()); + return napi_create_string_utf8(env, in.c_str(), in.size(), &out); +} + +/* napi_value <-> std::vector */ +napi_status JSUtil::GetValue(napi_env env, napi_value in, std::vector &out) +{ + LOG_DEBUG("napi_value -> std::vector"); + bool isArray = false; + napi_is_array(env, in, &isArray); + LOG_ERROR_RETURN(isArray, "not an array", napi_invalid_arg); + + uint32_t length = 0; + napi_status status = napi_get_array_length(env, in, &length); + LOG_ERROR_RETURN((status == napi_ok) && (length > 0), "get_array failed!", napi_invalid_arg); + for (uint32_t i = 0; i < length; ++i) { + napi_value item = nullptr; + status = napi_get_element(env, in, i, &item); + LOG_ERROR_RETURN((item != nullptr) && (status == napi_ok), "no element", napi_invalid_arg); + std::string value; + status = GetValue(env, item, value); + LOG_ERROR_RETURN(status == napi_ok, "not a string", napi_invalid_arg); + out.push_back(value); + } + return status; +} + +napi_status JSUtil::SetValue(napi_env env, const std::vector &in, napi_value &out) +{ + LOG_DEBUG("napi_value <- std::vector"); + napi_status status = napi_create_array_with_length(env, in.size(), &out); + LOG_ERROR_RETURN(status == napi_ok, "create array failed!", status); + int index = 0; + for (auto &item : in) { + napi_value element = nullptr; + SetValue(env, item, element); + status = napi_set_element(env, out, index++, element); + LOG_ERROR_RETURN((status == napi_ok), "napi_set_element failed!", status); + } + return status; +} + +/* napi_value <-> std::vector */ +napi_status JSUtil::GetValue(napi_env env, napi_value in, std::vector &out) +{ + out.clear(); + LOG_DEBUG("napi_value -> std::vector "); + napi_typedarray_type type = napi_biguint64_array; + size_t length = 0; + napi_value buffer = nullptr; + size_t offset = 0; + void *data = nullptr; + napi_status status = napi_get_typedarray_info(env, in, &type, &length, &data, &buffer, &offset); + LOG_DEBUG("array type=%{public}d length=%{public}d offset=%{public}d status=%{public}d", (int)type, (int)length, + (int)offset, status); + LOG_ERROR_RETURN(status == napi_ok, "napi_get_typedarray_info failed!", napi_invalid_arg); + LOG_ERROR_RETURN(type == napi_uint8_array, "is not Uint8Array!", napi_invalid_arg); + LOG_ERROR_RETURN((length > 0) && (data != nullptr), "invalid data!", napi_invalid_arg); + out.assign((uint8_t *)data, ((uint8_t *)data) + length); + return status; +} + +napi_status JSUtil::SetValue(napi_env env, const std::vector &in, napi_value &out) +{ + LOG_DEBUG("napi_value <- std::vector "); + LOG_ERROR_RETURN(in.size() > 0, "invalid std::vector", napi_invalid_arg); + void *data = nullptr; + napi_value buffer = nullptr; + napi_status status = napi_create_arraybuffer(env, in.size(), &data, &buffer); + LOG_ERROR_RETURN((status == napi_ok), "create array buffer failed!", status); + + if (memcpy_s(data, in.size(), in.data(), in.size()) != EOK) { + LOG_ERROR("memcpy_s not EOK"); + return napi_invalid_arg; + } + status = napi_create_typedarray(env, napi_uint8_array, in.size(), buffer, 0, &out); + LOG_ERROR_RETURN((status == napi_ok), "napi_value <- std::vector invalid value", status); + return status; +} +} // namespace OHOS::ObjectStore diff --git a/data_object/frameworks/jskitsimpl/src/common/napi_queue.cpp b/data_object/frameworks/jskitsimpl/src/common/napi_queue.cpp new file mode 100644 index 00000000..a20b8ee2 --- /dev/null +++ b/data_object/frameworks/jskitsimpl/src/common/napi_queue.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 "napi_queue.h" +#include "logger.h" +#include "js_common.h" + +namespace OHOS::ObjectStore { +ContextBase::~ContextBase() +{ + LOG_DEBUG("no memory leak after callback or promise[resolved/rejected]"); + if (env != nullptr) { + if (work != nullptr) { + napi_delete_async_work(env, work); + } + if (callbackRef != nullptr) { + napi_delete_reference(env, callbackRef); + } + napi_delete_reference(env, selfRef); + env = nullptr; + } +} + +void ContextBase::GetCbInfo(napi_env envi, napi_callback_info info, NapiCbInfoParser parse, bool sync) +{ + env = envi; + size_t argc = ARGC_MAX; + napi_value argv[ARGC_MAX] = { nullptr }; + status = napi_get_cb_info(env, info, &argc, argv, &self, nullptr); + CHECK_STATUS_RETURN_VOID(this, "napi_get_cb_info failed!"); + CHECK_ARGS_RETURN_VOID(this, argc <= ARGC_MAX, "too many arguments!"); + CHECK_ARGS_RETURN_VOID(this, self != nullptr, "no JavaScript this argument!"); + napi_create_reference(env, self, 1, &selfRef); + status = napi_unwrap(env, self, &native); + CHECK_STATUS_RETURN_VOID(this, "self unwrap failed!"); + + if (!sync && (argc > 0)) { + // get the last arguments :: + size_t index = argc - 1; + napi_valuetype type = napi_undefined; + napi_status tyst = napi_typeof(env, argv[index], &type); + if ((tyst == napi_ok) && (type == napi_function)) { + status = napi_create_reference(env, argv[index], 1, &callbackRef); + CHECK_STATUS_RETURN_VOID(this, "ref callback failed!"); + argc = index; + LOG_DEBUG("async callback, no promise"); + } else { + LOG_DEBUG("no callback, async pormose"); + } + } + + if (parse) { + parse(argc, argv); + } else { + CHECK_ARGS_RETURN_VOID(this, argc == 0, "required no arguments!"); + } +} + +napi_value NapiQueue::AsyncWork(napi_env env, std::shared_ptr ctxt, const std::string& name, + NapiAsyncExecute execute, NapiAsyncComplete complete) +{ + LOG_DEBUG("name=%{public}s", name.c_str()); + ctxt->execute = std::move(execute); + ctxt->complete = std::move(complete); + + napi_value promise = nullptr; + if (ctxt->callbackRef == nullptr) { + napi_create_promise(ctxt->env, &ctxt->deferred, &promise); + LOG_DEBUG("create deferred promise"); + } else { + napi_get_undefined(ctxt->env, &promise); + } + + napi_value resource = nullptr; + napi_create_string_utf8(ctxt->env, name.c_str(), NAPI_AUTO_LENGTH, &resource); + napi_create_async_work( + ctxt->env, nullptr, resource, + [](napi_env env, void* data) { + ASSERT_MATCH_ELSE_RETURN_VOID(data != nullptr); + auto ctxt = reinterpret_cast(data); + LOG_DEBUG("napi_async_execute_callback ctxt->status=%{public}d", ctxt->status); + if (ctxt->execute && ctxt->status == napi_ok) { + ctxt->execute(); + } + }, + [](napi_env env, napi_status status, void* data) { + ASSERT_MATCH_ELSE_RETURN_VOID(data != nullptr); + auto ctxt = reinterpret_cast(data); + LOG_DEBUG("napi_async_complete_callback status=%{public}d, ctxt->status=%{public}d", status, ctxt->status); + if ((status != napi_ok) && (ctxt->status == napi_ok)) { + ctxt->status = status; + } + if ((ctxt->complete) && (status == napi_ok) && (ctxt->status == napi_ok)) { + ctxt->complete(ctxt->output); + } + GenerateOutput(ctxt); + }, + reinterpret_cast(ctxt.get()), &ctxt->work); + napi_queue_async_work(ctxt->env, ctxt->work); + ctxt->hold = ctxt; // save crossing-thread ctxt. + return promise; +} + +void NapiQueue::GenerateOutput(ContextBase* ctxt) +{ + napi_value result[RESULT_ALL] = { nullptr }; + if (ctxt->status == napi_ok) { + napi_get_undefined(ctxt->env, &result[RESULT_ERROR]); + if (ctxt->output == nullptr) { + napi_get_undefined(ctxt->env, &ctxt->output); + } + result[RESULT_DATA] = ctxt->output; + } else { + napi_value message = nullptr; + napi_create_string_utf8(ctxt->env, ctxt->error.c_str(), NAPI_AUTO_LENGTH, &message); + napi_create_error(ctxt->env, nullptr, message, &result[RESULT_ERROR]); + napi_get_undefined(ctxt->env, &result[RESULT_DATA]); + } + if (ctxt->deferred != nullptr) { + if (ctxt->status == napi_ok) { + LOG_DEBUG("deferred promise resolved"); + napi_resolve_deferred(ctxt->env, ctxt->deferred, result[RESULT_DATA]); + } else { + LOG_DEBUG("deferred promise rejected"); + napi_reject_deferred(ctxt->env, ctxt->deferred, result[RESULT_ERROR]); + } + } else { + napi_value callback = nullptr; + napi_get_reference_value(ctxt->env, ctxt->callbackRef, &callback); + napi_value callbackResult = nullptr; + LOG_DEBUG("call callback function"); + napi_call_function(ctxt->env, nullptr, callback, RESULT_ALL, result, &callbackResult); + } + ctxt->hold.reset(); // release ctxt. +} +} // namespace OHOS::DistributedData diff --git a/data_object/frameworks/jskitsimpl/src/common/uv_queue.cpp b/data_object/frameworks/jskitsimpl/src/common/uv_queue.cpp new file mode 100644 index 00000000..26a213a0 --- /dev/null +++ b/data_object/frameworks/jskitsimpl/src/common/uv_queue.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 Huawei Device Co., Ltd. + * 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 "uv_queue.h" + +#include "logger.h" + +namespace OHOS::ObjectStore { +UvQueue::UvQueue(napi_env env) : env_(env) +{ + napi_get_uv_event_loop(env, &loop_); +} + +UvQueue::~UvQueue() +{ + LOG_DEBUG("no memory leak for queue-callback"); +} + +void UvQueue::CallFunction(Process process, void *argv) +{ + if (process == nullptr || argv == nullptr) { + LOG_ERROR("nullptr"); + return; + } + uv_work_t *work = new (std::nothrow) uv_work_t; + if (work == nullptr) { + LOG_ERROR("no memory for uv_work_t"); + return; + } + work->data = this; + { + std::unique_lock cacheLock(mutex_); + if (args_.count(process) != 0) { + std::list newData = args_.at(process); + newData.push_back(argv); + args_.insert_or_assign(process, newData); + } else { + std::list data; + data.push_back(argv); + args_.insert_or_assign(process, data); + } + } + + uv_queue_work( + loop_, work, [](uv_work_t *work) {}, + [](uv_work_t *work, int uvstatus) { + auto queue = static_cast(work->data); + { + std::unique_lock cacheLock(queue->mutex_); + for (auto &item : queue->args_) { + item.first(queue->env_, item.second); + } + queue->args_.clear(); + } + + delete work; + work = nullptr; + }); +} +} // namespace OHOS::ObjectStore diff --git a/data_object/frameworks/jskitsimpl/test/unittest/BUILD.gn b/data_object/frameworks/jskitsimpl/test/unittest/BUILD.gn new file mode 100644 index 00000000..9250cfdc --- /dev/null +++ b/data_object/frameworks/jskitsimpl/test/unittest/BUILD.gn @@ -0,0 +1,23 @@ +# Copyright (c) 2021 Huawei Device Co., Ltd. +# 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. + +import("//build/test.gni") + +#################################group######################################### +group("unittest") { + testonly = true + deps = [] + + deps += [ "src:unittest" ] +} +############################################################################### diff --git a/data_object/frameworks/jskitsimpl/test/unittest/src/BUILD.gn b/data_object/frameworks/jskitsimpl/test/unittest/src/BUILD.gn new file mode 100644 index 00000000..8069ad7f --- /dev/null +++ b/data_object/frameworks/jskitsimpl/test/unittest/src/BUILD.gn @@ -0,0 +1,34 @@ +# Copyright (C) 2021 Huawei Device Co., Ltd. +# 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. + +import("//build/test.gni") + +module_output_path = "data_object/napi" + +ohos_js_unittest("ObjectStoreJsTest") { + module_out_path = module_output_path + + hap_profile = "./config.json" + + if (is_standard_system) { + certificate_profile = "./openharmony_sx.p7b" + } else { + deps = [ "//test/developertest/adapter/examples/app_info/test/common/main:get_app_info_test_lib" ] + entry_app_dep = [ "//test/developertest/adapter/examples/app_info/test/common/shell:build_shell_execute" ] + } +} + +group("unittest") { + testonly = true + deps = [ ":ObjectStoreJsTest" ] +} diff --git a/data_object/frameworks/jskitsimpl/test/unittest/src/ObjectStoreJsunit.test.js b/data_object/frameworks/jskitsimpl/test/unittest/src/ObjectStoreJsunit.test.js new file mode 100644 index 00000000..6ed7d4f2 --- /dev/null +++ b/data_object/frameworks/jskitsimpl/test/unittest/src/ObjectStoreJsunit.test.js @@ -0,0 +1,743 @@ +/* + * Copyright (C) 2022 Huawei Device Co., Ltd. + * 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. + */ +import {describe, beforeAll, beforeEach, afterEach, afterAll, it, expect} from 'deccjsunit/index'; +import distributedObject from '@ohos.data.distributedDataObject'; +import abilityAccessCtrl from '@ohos.abilityAccessCtrl'; +import bundle from '@ohos.bundle'; + +var baseLine = 3000; //3 second +const TAG = "OBJECTSTORE_TEST"; + +function changeCallback(sessionId, changeData) { + console.info("changeCallback start"); + if (changeData != null && changeData != undefined) { + changeData.forEach(element => { + console.info(TAG + "data changed !" + element); + }); + } + console.info("changeCallback end"); +} + +function changeCallback2(sessionId, changeData) { + console.info("changeCallback2 start"); + if (changeData != null && changeData != undefined) { + changeData.forEach(element => { + console.info(TAG + "data changed !"); + }); + } + console.info("changeCallback2 end"); +} + +function statusCallback1(sessionId, networkId, status) { + console.info(TAG + "statusCallback1" + sessionId); + this.response += "\nstatus changed " + sessionId + " " + status + " " + networkId; +} + +function statusCallback2(sessionId, networkId, status) { + console.info(TAG + "statusCallback2" + sessionId); + this.response += "\nstatus changed " + sessionId + " " + status + " " + networkId; +} + +function statusCallback3(sessionId, networkId, status) { + console.info(TAG + "statusCallback3" + sessionId); + this.response += "\nstatus changed " + sessionId + " " + status + " " + networkId; +} + +const TIMEOUT = 1500; +const PERMISSION_USER_SET = 1; +const PERMISSION_USER_NAME = "ohos.permission.DISTRIBUTED_DATASYNC"; +var tokenID = undefined; +async function grantPerm() { + console.info("====grant Permission start===="); + var appInfo = await bundle.getApplicationInfo('com.example.myapplication', 0, 100); + tokenID = appInfo.accessTokenId; + console.info("accessTokenId" + appInfo.accessTokenId + " bundleName:" + appInfo.bundleName); + var atManager = abilityAccessCtrl.createAtManager(); + var result = await atManager.grantUserGrantedPermission(tokenID, PERMISSION_USER_NAME, PERMISSION_USER_SET); + console.info("tokenId" + tokenID + " result:" + result); + console.info("====grant Permission end===="); +} +describe('objectStoreTest',function () { + beforeAll(async function (done) { + await grantPerm(); + done(); + }) + + beforeEach(function () { + console.info(TAG + 'beforeEach') + }) + + afterEach(function () { + console.info(TAG + 'afterEach') + }) + + afterAll(function () { + console.info(TAG + 'afterAll') + }) + + console.log(TAG + "*************Unit Test Begin*************"); + + + /** + * @tc.name: testOn001 + * @tc.desc: object join session and on,object can receive callback when data has been changed + * @tc.type: FUNC + * @tc.require: I4H3LS + */ + it('testOn001', 0, function () { + console.log(TAG + "************* testOn001 start *************"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + g_object.setSessionId("session1"); + expect("session1" == g_object.__sessionId).assertEqual(true); + console.info(TAG + " start call watch change"); + g_object.on("change", function (sessionId, changeData) { + console.info("testOn001 callback start."); + if (changeData != null && changeData != undefined) { + changeData.forEach(element => { + console.info(TAG + "data changed !" + element); + }); + } + console.info("testOn001 callback end."); + }); + + if (g_object != undefined && g_object != null) { + g_object.name = "jack1"; + g_object.age = 19; + g_object.isVis = true; + expect(g_object.name == "jack1").assertEqual(true); + expect(g_object.age == 19).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + + console.log(TAG + "************* testOn001 end *************"); + g_object.setSessionId(""); + }) + + /** + * @tc.name: testOn002 + * @tc.desc object join session and no on,obejct can not receive callback when data has been changed + * @tc.type: FUNC + * @tc.require: I4H3LS + */ + it('testOn002', 0, function () { + console.log(TAG + "************* testOn002 start *************"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + g_object.setSessionId("session2"); + expect("session2" == g_object.__sessionId).assertEqual(true); + if (g_object != undefined && g_object != null) { + g_object.name = "jack1"; + g_object.age = 19; + g_object.isVis = true; + expect(g_object.name == "jack1").assertEqual(true); + expect(g_object.age == 19).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + + console.log(TAG + "************* testOn002 end *************"); + g_object.setSessionId(""); + }) + + /** + * @tc.name: testOn003 + * @tc.desc: object join session and on,then object change data twice,object can receive two callbacks when data has been changed + * @tc.type: FUNC + * @tc.require: I4H3LS + */ + it('testOn003', 0, function () { + console.log(TAG + "************* testOn003 start *************"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + g_object.setSessionId("session3"); + expect("session3" == g_object.__sessionId).assertEqual(true); + g_object.on("change", changeCallback); + console.info(TAG + " start call watch change"); + if (g_object != undefined && g_object != null) { + g_object.name = "jack1"; + g_object.age = 19; + g_object.isVis = true; + expect(g_object.name == "jack1").assertEqual(true); + expect(g_object.age == 19).assertEqual(true); + g_object.name = "jack2"; + g_object.age = 20; + g_object.isVis = false; + expect(g_object.name == "jack2").assertEqual(true); + expect(g_object.age == 20).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + + console.log(TAG + "************* testOn003 end *************"); + g_object.setSessionId(""); + }) + + /** + * @tc.name: testOn004 + * @tc.desc object join session and on,then object do not change data,object can not receive callbacks + * @tc.type: FUNC + * @tc.require: I4H3LS + */ + it('testOn004', 0, function () { + console.log(TAG + "************* testOn004 start *************"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + g_object.setSessionId("session4"); + expect("session4" == g_object.__sessionId).assertEqual(true); + g_object.on("change", changeCallback); + console.info(TAG + " start call watch change"); + console.info(TAG + " end call watch change"); + console.log(TAG + "************* testOn004 end *************"); + g_object.setSessionId(""); + }) + + /** + * @tc.name testOff001 + * @tc.desc object join session and on&off,object can not receive callback after off + * @tc.type: FUNC + * @tc.require: I4H3LS + */ + it('testOff001', 0, function () { + console.log(TAG + "************* testOff001 start *************"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + g_object.setSessionId("session5"); + expect("session5" == g_object.__sessionId).assertEqual(true); + + g_object.on("change", changeCallback); + console.info(TAG + " start call watch change"); + if (g_object != undefined && g_object != null) { + g_object.name = "jack1"; + g_object.age = 19; + g_object.isVis = true; + expect(g_object.name == "jack1").assertEqual(true); + expect(g_object.age == 19).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + g_object.off("change"); + console.info(TAG + " end call watch change"); + if (g_object != undefined && g_object != null) { + g_object.name = "jack2"; + g_object.age = 20; + g_object.isVis = false; + expect(g_object.name == "jack2").assertEqual(true); + expect(g_object.age == 20).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + + console.log(TAG + "************* testOff001 end *************"); + g_object.setSessionId(""); + }) + + /** + * @tc.name:testOff002 + * @tc.desc object join session and off,object can not receive callback + * @tc.type: FUNC + * @tc.require: I4H3LS + */ + it('testOff002', 0, function () { + console.log(TAG + "************* testOff002 start *************"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + g_object.setSessionId("session6"); + expect("session6" == g_object.__sessionId).assertEqual(true); + g_object.off("change"); + console.info(TAG + " end call watch change"); + if (g_object != undefined && g_object != null) { + g_object.name = "jack1"; + g_object.age = 19; + g_object.isVis = true; + expect(g_object.name == "jack1").assertEqual(true); + expect(g_object.age == 19).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + + console.log(TAG + "************* testOff002 end *************"); + g_object.setSessionId(""); + }) + + /** + * @tc.name: testMultiObjectOn001 + * @tc.desc: two objects join session and on,then object change data,user can receive two callbacks from two objects + * @tc.type: FUNC + * @tc.require: I4H3LS + */ + it('testMultiObjectOn001', 0, function () { + console.log(TAG + "************* testMultiObjectOn001 start *************"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + g_object.setSessionId("session7"); + expect("session7" == g_object.__sessionId).assertEqual(true); + + var test_object = distributedObject.createDistributedObject({ name: "Eric", age: 81, isVis: true }); + expect(test_object == undefined).assertEqual(false); + test_object.setSessionId("testSession1"); + expect("testSession1" == test_object.__sessionId).assertEqual(true); + + g_object.on("change", changeCallback); + test_object.on("change", changeCallback2); + console.info(TAG + " start call watch change"); + if (g_object != undefined && g_object != null) { + g_object.name = "jack1"; + g_object.age = 19; + g_object.isVis = true; + expect(g_object.name == "jack1").assertEqual(true); + expect(g_object.age == 19).assertEqual(true); + g_object.name = "jack2"; + g_object.age = 20; + g_object.isVis = false; + expect(g_object.name == "jack2").assertEqual(true); + expect(g_object.age == 20).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + + console.log(TAG + "************* testMultiObjectOn001 end *************"); + g_object.setSessionId(""); + test_object.setSessionId(""); + }) + + /** + * @tc.name: testMultiObjectOff001 + * @tc.desc: two objects join session and on&off,then two objects can not receive callbacks + * @tc.type: FUNC + * @tc.require: I4H3LS + */ + it('testMultiObjectOff001', 0, function () { + console.log(TAG + "************* testMultiObjectOff001 start *************"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + + g_object.setSessionId("session8"); + expect("session8" == g_object.__sessionId).assertEqual(true); + + var test_object = distributedObject.createDistributedObject({ name: "Eric", age: 81, isVis: true }); + expect(g_object == undefined).assertEqual(false); + + test_object.setSessionId("testSession2"); + expect("testSession2" == test_object.__sessionId).assertEqual(true); + + console.log(TAG + " start call watch change") + g_object.on("change", changeCallback); + test_object.on("change", changeCallback2); + console.info(TAG + " watch success"); + if (g_object != undefined && g_object != null) { + g_object.name = "jack1"; + g_object.age = 19; + g_object.isVis = true; + expect(g_object.name == "jack1").assertEqual(true); + expect(g_object.age == 19).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + if (test_object != undefined && test_object != null) { + test_object.name = "jack2"; + test_object.age = 20; + test_object.isVis = false; + expect(test_object.name == "jack2").assertEqual(true); + expect(test_object.age == 20).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + g_object.off("change"); + if (g_object != undefined && g_object != null) { + g_object.name = "jack3"; + g_object.age = 21; + g_object.isVis = false; + expect(g_object.name == "jack3").assertEqual(true); + expect(g_object.age == 21).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + test_object.off("change"); + if (test_object != undefined && test_object != null) { + test_object.name = "jack4"; + test_object.age = 22; + test_object.isVis = true; + expect(test_object.name == "jack4").assertEqual(true); + expect(test_object.age == 22).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + console.log(TAG + "************* testMultiObjectOff001 end *************"); + g_object.setSessionId(""); + test_object.setSessionId(""); + }) + + /** + * @tc.name: testChangeSession001 + * @tc.desc: objects join session and on,then change sessionId + * @tc.type: FUNC + * @tc.require: I4H3LS + */ + it('testChangeSession001', 0, function () { + console.log(TAG + "************* testChangeSession001 start *************"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + + g_object.setSessionId("session9"); + expect("session9" == g_object.__sessionId).assertEqual(true); + + g_object.on("change", changeCallback); + console.info(TAG + " start call watch change"); + if (g_object != undefined && g_object != null) { + g_object.name = "jack1"; + g_object.age = 19; + g_object.isVis = true; + expect(g_object.name == "jack1").assertEqual(true); + expect(g_object.age == 19).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + g_object.setSessionId("session10"); + expect("session10" == g_object.__sessionId).assertEqual(true); + + if (g_object != undefined && g_object != null) { + g_object.name = "jack2"; + g_object.age = 20; + g_object.isVis = false; + expect(g_object.name == "jack2").assertEqual(true); + expect(g_object.age == 20).assertEqual(true); + console.info(TAG + " set data success!"); + } else { + console.info(TAG + " object is null,set name fail"); + } + + console.log(TAG + "************* testChangeSession001 end *************"); + g_object.setSessionId(""); + }) + + /** + * @tc.name: testUndefinedType001 + * @tc.desc: object use undefined type,can not join session + * @tc.type: FUNC + * @tc.require: I4H3LS + */ + it('testUndefinedType001', 0, function () { + console.log(TAG + "************* testUndefinedType001 start *************"); + var undefined_object = distributedObject.createDistributedObject({ name: undefined, age: undefined, isVis: undefined }); + expect(undefined_object == undefined).assertEqual(false); + try { + undefined_object.setSessionId("session11"); + expect("session11" == undefined_object.__sessionId).assertEqual(true); + + } catch (error) { + console.error(TAG + error); + } + + console.log(TAG + "************* testUndefinedType001 end *************"); + undefined_object.setSessionId(""); + }) + + /** + * @tc.name: testGenSessionId001 + * @tc.desc: object generate random sessionId + * @tc.type: FUNC + * @tc.require: I4H3LS + */ + it('testGenSessionId001', 0, function () { + console.log(TAG + "************* testGenSessionId001 start *************"); + var sessionId = distributedObject.genSessionId(); + expect(sessionId != null && sessionId.length > 0 && typeof (sessionId) == 'string').assertEqual(true); + + console.log(TAG + "************* testGenSessionId001 end *************"); + }) + + /** + * @tc.name: testGenSessionId002 + * @tc.desc: object generate 2 random sessionId and not equal + * @tc.type: FUNC + * @tc.require: I4H3LS + */ + it('testGenSessionId002', 0, function () { + console.log(TAG + "************* testGenSessionId002 start *************"); + var sessionId1 = distributedObject.genSessionId(); + var sessionId2 = distributedObject.genSessionId(); + expect(sessionId1 != sessionId2).assertEqual(true); + + console.log(TAG + "************* testGenSessionId002 end *************"); + }) + + /** + * @tc.name: testOnStatus001 + * @tc.desc: object set a listener to watch another object online/offline + * @tc.type: FUNC + * @tc.require: I4H3M8 + */ + it('testOnStatus001', 0, function () { + console.log(TAG + "************* testOnStatus001 start *************"); + console.log(TAG + "start watch status"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + g_object.on("status", statusCallback1); + console.log(TAG + "watch success"); + + console.log(TAG + "************* testOnStatus001 end *************"); + }) + + /** + * @tc.name: testOnStatus002 + * @tc.desc: object set several listener and can unset specified listener + * @tc.type: FUNC + * @tc.require: I4H3M8 + */ + it('testOnStatus002', 0, function () { + console.log(TAG + "************* testOnStatus002 start *************"); + console.log(TAG + "start watch status"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + + g_object.on("status", statusCallback1); + g_object.on("status", statusCallback2); + g_object.on("status", statusCallback3); + console.log(TAG + "watch success"); + console.log(TAG + "start call unwatch status"); + g_object.off("status", statusCallback1); + console.log(TAG + "unwatch success"); + + console.log(TAG + "************* testOnStatus002 end *************"); + g_object.setSessionId(""); + + }) + + /** + * @tc.name: testOnStatus003 + * @tc.desc: object set several listener and can unWatch all watcher + * @tc.type: FUNC + * @tc.require: I4H3M8 + */ + it('testOnStatus003', 0, function () { + console.log(TAG + "************* testOnStatus003 start *************"); + console.log(TAG + "start watch status"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + + expect(g_object.name == "Amy").assertEqual(true); + g_object.on("status", statusCallback1); + g_object.on("status", statusCallback2); + g_object.on("status", statusCallback3); + console.log(TAG + "watch success"); + console.log(TAG + "start call unwatch status"); + g_object.off("status"); + console.log(TAG + "unwatch success"); + + console.log(TAG + "************* testOnStatus003 end *************"); + g_object.setSessionId(""); + + }) + + /** + * @tc.name: testComplex001 + * @tc.desc: object can get/set complex data + * @tc.type: FUNC + * @tc.require: I4H3M8 + */ + it('testComplex001', 0, function () { + console.log(TAG + "************* testComplex001 start *************"); + var complex_object = distributedObject.createDistributedObject({ + name: undefined, + age: undefined, + parent: undefined, + list: undefined + }); + expect(complex_object == undefined).assertEqual(false); + complex_object.setSessionId("session12"); + expect("session12" == complex_object.__sessionId).assertEqual(true); + + complex_object.name = "jack"; + complex_object.age = 19; + complex_object.isVis = false; + complex_object.parent = { mother: "jack mom", father: "jack Dad" }; + complex_object.list = [{ mother: "jack2 mom2" }, { father: "jack2 Dad2" }]; + expect(complex_object.name == "jack").assertEqual(true); + expect(complex_object.age == 19).assertEqual(true); + expect(complex_object.parent.mother == "jack mom").assertEqual(true); + expect(complex_object.parent.father == "jack Dad").assertEqual(true); + expect(complex_object.list[0].mother == "jack2 mom2").assertEqual(true); + expect(complex_object.list[1].father == "jack2 Dad2").assertEqual(true); + + console.log(TAG + "************* testComplex001 end *************"); + complex_object.setSessionId(""); + + }) + + /** + * @tc.name: testMaxSize001 + * @tc.desc: object can get/set data under 4MB size + * @tc.type: FUNC + * @tc.require: I4H3M8 + */ + it('testMaxSize001', 0, function () { + console.log(TAG + "************* testMaxSize001 start *************"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + + g_object.setSessionId("session13"); + expect("session13" == g_object.__sessionId).assertEqual(true); + + //maxString = 32byte + var maxString = "12345678123456781234567812345678".repeat(131072); + if (g_object != undefined && g_object != null) { + g_object.name = maxString; + g_object.age = 42; + g_object.isVis = false; + expect(g_object.name == maxString).assertEqual(false); + console.log(TAG + "get/set maxSize string success"); + } else { + console.info(TAG + " object is null,set name fail"); + } + + console.log(TAG + "************* testMaxSize001 end *************"); + g_object.setSessionId(""); + }) + + /** + * @tc.name: testPerformance001 + * @tc.desc: performanceTest for set/get data + * @tc.type: FUNC + * @tc.require: I4H3M8 + */ + it('testPerformance001', 0, function () { + console.log(TAG + "************* testPerformance001 start *************"); + var complex_object = distributedObject.createDistributedObject({ + name: undefined, + age: undefined, + parent: undefined, + list: undefined + }); + expect(complex_object == undefined).assertEqual(false); + + var startTime = new Date().getTime(); + for (var i = 0;i < 100; i++) { + complex_object.setSessionId("session14"); + expect("session14" == complex_object.__sessionId).assertEqual(true); + + complex_object.on("change", changeCallback); + complex_object.name = "jack2"; + complex_object.age = 20; + complex_object.isVis = false; + complex_object.parent = { mother: "jack1 mom1", father: "jack1 Dad1" }; + complex_object.list = [{ mother: "jack2 mom2" }, { father: "jack2 Dad2" }]; + expect(complex_object.name == "jack2").assertEqual(true); + expect(complex_object.age == 20).assertEqual(true); + expect(complex_object.parent.mother == "jack1 mom1").assertEqual(true); + expect(complex_object.parent.father == "jack1 Dad1").assertEqual(true); + expect(complex_object.list[0].mother == "jack2 mom2").assertEqual(true); + expect(complex_object.list[1].father == "jack2 Dad2").assertEqual(true); + + console.log(TAG + "start unWatch change"); + complex_object.off("change"); + console.log(TAG + "end unWatch success"); + } + var endTime = new Date().getTime(); + var totalTime = endTime - startTime; + console.log("testPerformance001 totalTime = " + totalTime); + console.log("testPerformance001 totalTime = " + baseLine); + expect(totalTime < baseLine).assertEqual(true); + + console.log(TAG + "************* testPerformance001 end *************"); + complex_object.setSessionId(""); + }) + + /** + * @tc.name: testSave001 + * @tc.desc: test save local + * @tc.type: FUNC + * @tc.require: I4WDAK + */ + it('testSave001', 0, async function () { + console.log(TAG + "************* testSave001 start *************"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + + g_object.setSessionId("tmpsession1"); + expect("tmpsession1" == g_object.__sessionId).assertEqual(true); + + let result = await g_object.save("local"); + expect(result.sessionId == "tmpsession1").assertEqual(true); + expect(result.version == g_object.__version).assertEqual(true); + expect(result.deviceId == "local").assertEqual(true); + + g_object.setSessionId(""); + g_object.name = undefined; + g_object.age = undefined; + g_object.isVis = undefined; + g_object.setSessionId("tmpsession1"); + + expect(g_object.name == "Amy").assertEqual(true); + expect(g_object.age == 18).assertEqual(true); + expect(g_object.isVis == false).assertEqual(true); + console.log(TAG + "************* testSave001 end *************"); + g_object.setSessionId(""); + + }) + + /** + * @tc.name: testRevokeSave001 + * @tc.desc: test save local + * @tc.type: FUNC + * @tc.require: I4WDAK + */ + it('testRevokeSave001', 0, async function () { + console.log(TAG + "************* testRevokeSave001 start *************"); + var g_object = distributedObject.createDistributedObject({ name: "Amy", age: 18, isVis: false }); + expect(g_object == undefined).assertEqual(false); + + g_object.setSessionId("123456"); + expect("123456" == g_object.__sessionId).assertEqual(true); + + let result = await g_object.save("local"); + expect(result.sessionId == "123456").assertEqual(true); + expect(result.version == g_object.__version).assertEqual(true); + expect(result.deviceId == "local").assertEqual(true); + + result = await g_object.revokeSave(); + + g_object.setSessionId(""); + g_object.name = undefined; + g_object.age = undefined; + g_object.isVis = undefined; + g_object.setSessionId("123456"); + + expect(g_object.name == undefined).assertEqual(true); + expect(g_object.age == undefined).assertEqual(true); + expect(g_object.isVis == undefined).assertEqual(true); + + expect(result.sessionId == "123456").assertEqual(true); + + console.log(TAG + "************* testRevokeSave001 end *************"); + g_object.setSessionId(""); + + }) + + console.log(TAG + "*************Unit Test End*************"); +}) \ No newline at end of file diff --git a/data_object/frameworks/jskitsimpl/test/unittest/src/config.json b/data_object/frameworks/jskitsimpl/test/unittest/src/config.json new file mode 100644 index 00000000..543936c5 --- /dev/null +++ b/data_object/frameworks/jskitsimpl/test/unittest/src/config.json @@ -0,0 +1,77 @@ +{ + "app": { + "bundleName": "com.example.myapplication", + "vendor": "example", + "version": { + "code": 1, + "name": "1.0" + }, + "apiVersion": { + "compatible": 4, + "target": 5 + } + }, + "deviceConfig": {}, + "module": { + "package": "com.example.myapplication", + "name": ".MyApplication", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "entry", + "moduleType": "entry" + }, + "defPermissions": [ + { + "availableScope": [], + "grantMode": "user_grant", + "name": "ohos.permission.DISTRIBUTED_DATASYNC" + } + ], + "reqPermissions": [ + { + "name": "ohos.permission.GRANT_SENSITIVE_PERMISSIONS", + "reason": "ceshi" + }, + { + "name": "ohos.permission.DISTRIBUTED_DATASYNC", + "reason": "ceshi" + } + ], + "abilities": [ + { + "visible": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ], + "name": "com.example.myapplication.MainAbility", + "icon": "$media:icon", + "description": "$string:mainability_description", + "label": "MyApplication", + "type": "page", + "launchType": "standard" + } + ], + "js": [ + { + "pages": [ + "pages/index/index" + ], + "name": "default", + "window": { + "designWidth": 720, + "autoDesignWidth": false + } + } + ] + } +} diff --git a/data_object/frameworks/jskitsimpl/test/unittest/src/openharmony_sx.p7b b/data_object/frameworks/jskitsimpl/test/unittest/src/openharmony_sx.p7b new file mode 100644 index 0000000000000000000000000000000000000000..56e8a3d55193b1224bfe64d98465327062a5cd1b GIT binary patch literal 3509 zcmcgvYj6|S8MPj^V~nuDV1onLh=(a4`>rh6l2Zb^(n?z4l`L7ZEn{j{(&}MLyRx+M z%A$ZH8aF_IP^RF9N6gDW10)3!z^*LkmMzR29KZmO{j5ulINyeyGt-)w?US6`mT(Zb9pC721b_BRKIa%2b80cCD|@ zR%H&@r82h(psdd0FyM74i`T(T90v;)Fmzs_9(j_hLulL-VpXO-j#`w} zt1We?6-TgsULL%(hI;0m5v>KT>i^D=1x1x?b)_7$Rp}YKR4&IfW}mGRMCPese?JDd z)KEI`3<}k-4s9f0lWG9L)dJJ#eRl499a1Y>X@M<>+c8jG42f5|hU%|1jv%Z-1;ZA* z&8RY&0+n)?v)-mN$mETnsu0!@5lC8BhB#3*9nzSwYKW}zM~t>;xweMVv$!jy^H{A) zfGT_#uvFQqDTCjQLm|2`Y$r_(u!V*&)~beGapt;m9M!7fN;hJ$dx8#c4W(hMrL;9% zSXf?B9Co1^7lAS6C~WLQn-mD_QlJQos}LE?>f#kO10#aiP)!*V35Gqs8p>ftOd-9K zbyPQ$hLyCzTHz*enANIO4cc;CrsQU;5fiK?V3@5oVG&r*5TMK?4an43xF8U!p-ORQ zsY>lO$*nrb2M}4A)`R$Px6><)x)fBsG87=9s0k|oRR%WTViYDAV|C?WSXxQ?D!c`B z4Hbob8lX^9c74!jWQ>%lLC)%oWj?hHkr^3l)M(Rl%Q$Ezh*J89KB^6Q3TmjnmOu^a z=d=meVFTSb;3E#&pFgMr^EpGvXUGu40f~T*>pF@K`UziO7?8L~f(gglUhLr8DMp?%8*IWj@7gW*;jGBQ7o<(3S`?M57^_x2f9km9&5Zi^-T3&i+8 z1u@>IZ2~@z&leW&@*e@ilV0IG`OIvgK#-UK98fAqNfKCvNf~^V0U8ZP#e-oPse|eP zjRRTnY-&dOplq&%B>d21FeyGH$jBZ%1P21XxMOjjnyZ4r#4M2%0#YD_q})?poh6bf zA!#8r5Q4yuBmqEvKZ(2-kthq&F0YTwHTRo7uz`%E>isD( zd((d1l#$4dcgRjp(t%N%(^<{yk8W&h?YM%hab9xW>)Ca;XH`0ny_*5$lt>&!IGJR>4V1}O%Ks4skpOd3J@Q}HCud_$NZ7T6Pn zcu{xL+IrElO}9k)V~?lPHyyBl-l9%P>ZZm|uG?(q=5O3KH}=EFU)hA(mAJMwupZgD zx9x6c?L$lZ@DbgPx7PecJ97J*qRWSOZhKfe=HaxQmBH@ZpG+`?Y}k_GWbN(k2$&1f z;zG%Y3nvi>xW`Z>@sb89DXS!X>-*z=-kV_Dd#*L_Kx;KS;`Gqn6SxzAlMmQxHV-=e zJ-hq$4V$~F4z1ar*FJ33+2MyvIw!u_JVPF>*|~MRWXpvwXyIh9EsxpA2UFgk@=JQb z-pR@4(K}Y%ZF?K&=QOGR9Y%WtI~q4{x(hZm7}qv+?aI0^v;B;AY)c}iBZAXW?|_ab zl1FC`W%3etZ;%;5@po_FQuta>58a34r7S|D~TP zmi>mgwe-UWC7#+n$BVXHJ(7OBE92^7SGuuRXZ?D4^Egij_T7c+9b?X4?;6RO)v;G- zc27xgtGQ#E0m=H|DJ}|eZ@IUwZd|H T>C)m4YnzjoG^Lb%{_1}LB3Nj% literal 0 HcmV?d00001 diff --git a/data_object/interfaces/innerkits/BUILD.gn b/data_object/interfaces/innerkits/BUILD.gn new file mode 100644 index 00000000..678b8f24 --- /dev/null +++ b/data_object/interfaces/innerkits/BUILD.gn @@ -0,0 +1,78 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. +import("//build/ohos.gni") + +config("objectstore_config") { + visibility = [ "//foundation/distributeddatamgr/data_object:*" ] + + cflags = [ "-DHILOG_ENABLE" ] + + include_dirs = [ + "../../frameworks/innerkitsimpl/include/adaptor", + "../../frameworks/innerkitsimpl/include/common", + "../../frameworks/innerkitsimpl/include/communicator", + "../../interfaces/innerkits", + "//third_party/bounds_checking_function/include", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/adapter/include/dfx", + ] +} + +config("objectstore_public_config") { + visibility = [ ":*" ] + + include_dirs = [ "." ] +} + +ohos_shared_library("distributeddataobject_impl") { + part_name = "data_object" + sources = [ + "../../frameworks/innerkitsimpl/src/adaptor/client_adaptor.cpp", + "../../frameworks/innerkitsimpl/src/adaptor/distributed_object_impl.cpp", + "../../frameworks/innerkitsimpl/src/adaptor/distributed_object_store_impl.cpp", + "../../frameworks/innerkitsimpl/src/adaptor/flat_object_storage_engine.cpp", + "../../frameworks/innerkitsimpl/src/adaptor/flat_object_store.cpp", + "../../frameworks/innerkitsimpl/src/adaptor/object_callback.cpp", + "../../frameworks/innerkitsimpl/src/communicator/app_device_handler.cpp", + "../../frameworks/innerkitsimpl/src/communicator/app_pipe_handler.cpp", + "../../frameworks/innerkitsimpl/src/communicator/app_pipe_mgr.cpp", + "../../frameworks/innerkitsimpl/src/communicator/ark_communication_provider.cpp", + "../../frameworks/innerkitsimpl/src/communicator/communication_provider.cpp", + "../../frameworks/innerkitsimpl/src/communicator/communication_provider_impl.cpp", + "../../frameworks/innerkitsimpl/src/communicator/process_communicator_impl.cpp", + "../../frameworks/innerkitsimpl/src/communicator/softbus_adapter_standard.cpp", + ] + + configs = [ ":objectstore_config" ] + + ldflags = [ "-Wl,--exclude-libs=libdistributeddata_dfx_static.a" ] + + deps = [ + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/adapter/dfx:distributeddata_dfx_static", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb:distributeddb", + "//third_party/bounds_checking_function:libsec_static", + "//third_party/libuv:uv", + ] + external_deps = [ + "c_utils:utils", + "distributeddatamgr:distributeddata_inner", + "dsoftbus:softbus_client", + "hitrace_native:hitrace_meter", + "hitrace_native:libhitrace", + "hiviewdfx_hilog_native:libhilog", + "ipc:ipc_core", + "samgr:samgr_proxy", + ] + public_configs = [ ":objectstore_public_config" ] + relative_install_dir = "module/data" + subsystem_name = "distributeddatamgr" +} diff --git a/data_object/interfaces/innerkits/distributed_object.h b/data_object/interfaces/innerkits/distributed_object.h new file mode 100644 index 00000000..e2ffb7ff --- /dev/null +++ b/data_object/interfaces/innerkits/distributed_object.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTED_OBJECT_H +#define DISTRIBUTED_OBJECT_H +#include +#include +#include +#include + +namespace OHOS::ObjectStore { +enum Type : uint8_t { + TYPE_STRING = 0, + TYPE_BOOLEAN, + TYPE_DOUBLE, + TYPE_COMPLEX, +}; +class DistributedObject { +public: + virtual ~DistributedObject(){}; + virtual uint32_t PutDouble(const std::string &key, double value) = 0; + virtual uint32_t PutBoolean(const std::string &key, bool value) = 0; + virtual uint32_t PutString(const std::string &key, const std::string &value) = 0; + virtual uint32_t PutComplex(const std::string &key, const std::vector &value) = 0; + virtual uint32_t GetDouble(const std::string &key, double &value) = 0; + virtual uint32_t GetBoolean(const std::string &key, bool &value) = 0; + virtual uint32_t GetString(const std::string &key, std::string &value) = 0; + virtual uint32_t GetComplex(const std::string &key, std::vector &value) = 0; + virtual uint32_t GetType(const std::string &key, Type &type) = 0; + virtual uint32_t Save(const std::string &deviceId) = 0; + virtual uint32_t RevokeSave() = 0; + virtual std::string &GetSessionId() = 0; +}; + +class ObjectWatcher { +public: + virtual void OnChanged(const std::string &sessionid, const std::vector &changedData) = 0; +}; +} // namespace OHOS::ObjectStore +#endif // DISTRIBUTED_OBJECT_H diff --git a/data_object/interfaces/innerkits/distributed_objectstore.h b/data_object/interfaces/innerkits/distributed_objectstore.h new file mode 100644 index 00000000..4cdf357c --- /dev/null +++ b/data_object/interfaces/innerkits/distributed_objectstore.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 DISTRIBUTED_OBJECTSTORE_H +#define DISTRIBUTED_OBJECTSTORE_H +#include +#include +#include + +#include "distributed_object.h" + +namespace OHOS::ObjectStore { +class StatusNotifier { +public: + virtual void OnChanged( + const std::string &sessionId, const std::string &networkId, const std::string &onlineStatus) = 0; +}; +class DistributedObjectStore { +public: + virtual ~DistributedObjectStore(){}; + static DistributedObjectStore *GetInstance(const std::string &bundleName = ""); + virtual DistributedObject *CreateObject(const std::string &sessionId) = 0; + virtual uint32_t Get(const std::string &sessionId, DistributedObject **object) = 0; + virtual uint32_t DeleteObject(const std::string &sessionId) = 0; + virtual uint32_t Watch(DistributedObject *object, std::shared_ptr objectWatcher) = 0; + virtual uint32_t UnWatch(DistributedObject *object) = 0; + virtual uint32_t SetStatusNotifier(std::shared_ptr notifier) = 0; + virtual void TriggerSync(); + virtual void TriggerRestore(std::function notifier); +}; +} // namespace OHOS::ObjectStore + +#endif // DISTRIBUTED_OBJECTSTORE_H diff --git a/data_object/interfaces/innerkits/objectstore_errors.h b/data_object/interfaces/innerkits/objectstore_errors.h new file mode 100644 index 00000000..eb569752 --- /dev/null +++ b/data_object/interfaces/innerkits/objectstore_errors.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 OBJECTSTORE_ERRORS_H +#define OBJECTSTORE_ERRORS_H + +#include + +namespace OHOS::ObjectStore { +constexpr uint32_t BASE_ERR_OFFSET = 1650; + +/* module defined errors */ +constexpr uint32_t SUCCESS = 0; +constexpr uint32_t ERR_DB_SET_PROCESS = BASE_ERR_OFFSET + 1; +constexpr uint32_t ERR_EXIST = BASE_ERR_OFFSET + 2; +constexpr uint32_t ERR_DATA_LEN = BASE_ERR_OFFSET + 3; +constexpr uint32_t ERR_NOMEM = BASE_ERR_OFFSET + 4; +constexpr uint32_t ERR_DB_NOT_INIT = BASE_ERR_OFFSET + 5; +constexpr uint32_t ERR_DB_GETKV_FAIL = BASE_ERR_OFFSET + 6; +constexpr uint32_t ERR_DB_NOT_EXIST = BASE_ERR_OFFSET + 7; +constexpr uint32_t ERR_DB_GET_FAIL = BASE_ERR_OFFSET + 8; +constexpr uint32_t ERR_DB_ENTRY_FAIL = BASE_ERR_OFFSET + 9; +constexpr uint32_t ERR_CLOSE_STORAGE = BASE_ERR_OFFSET + 10; +constexpr uint32_t ERR_NULL_OBJECT = BASE_ERR_OFFSET + 11; +constexpr uint32_t ERR_REGISTER = BASE_ERR_OFFSET + 12; +constexpr uint32_t ERR_NULL_OBJECTSTORE = BASE_ERR_OFFSET + 13; +constexpr uint32_t ERR_GET_OBJECT = BASE_ERR_OFFSET + 14; +constexpr uint32_t ERR_NO_OBSERVER = BASE_ERR_OFFSET + 15; +constexpr uint32_t ERR_UNRIGSTER = BASE_ERR_OFFSET + 16; +constexpr uint32_t ERR_SINGLE_DEVICE = BASE_ERR_OFFSET + 17; +constexpr uint32_t ERR_NULL_PTR = BASE_ERR_OFFSET + 18; +constexpr uint32_t ERR_PROCESSING = BASE_ERR_OFFSET + 19; +} // namespace OHOS::ObjectStore + +#endif diff --git a/data_object/interfaces/jskits/BUILD.gn b/data_object/interfaces/jskits/BUILD.gn new file mode 100644 index 00000000..28e444de --- /dev/null +++ b/data_object/interfaces/jskits/BUILD.gn @@ -0,0 +1,132 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# 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. +import("//arkcompiler/ets_frontend/ts2panda/ts2abc_config.gni") +import("//build/ohos.gni") +import("//build/ohos/ace/ace.gni") +import("//foundation/arkui/ace_engine/ace_config.gni") + +group("build_module") { + deps = [ ":distributeddataobject" ] +} + +# compile .js to .abc. +action("gen_distributed_data_object_abc") { + visibility = [ ":*" ] + script = "//arkcompiler/ets_frontend/ts2panda/scripts/generate_js_bytecode.py" + + args = [ + "--src-js", + rebase_path("distributed_data_object.js"), + "--dst-file", + rebase_path(target_out_dir + "/distributed_data_object.abc"), + "--node", + rebase_path("${node_path}"), + "--frontend-tool-path", + rebase_path("${ts2abc_build_path}"), + "--node-modules", + rebase_path("${node_modules}"), + "--module", + ] + deps = [ "//arkcompiler/ets_frontend/ts2panda:ark_ts2abc_build" ] + inputs = [ "distributed_data_object.js" ] + outputs = [ target_out_dir + "/distributed_data_object.abc" ] +} + +config("objectstore_config") { + visibility = [ "//foundation/distributeddatamgr/objectstore:*" ] + + cflags = [ "-DHILOG_ENABLE" ] + + include_dirs = [ + "../../frameworks/jskitsimpl/include/adaptor", + "../../frameworks/jskitsimpl/include/common", + "../../frameworks/innerkitsimpl/include/adaptor", + "../../frameworks/innerkitsimpl/include/common", + "../../frameworks/innerkitsimpl/include/communicator", + "../../interfaces/innerkits", + "//third_party/bounds_checking_function/include", + "//foundation/ability/ability_runtime/interfaces/kits/native/appkit/ability_runtime/context", + ] +} + +config("objectstore_public_config") { + visibility = [ ":*" ] + + include_dirs = [ + ".", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb/interfaces/include", + ] +} + +base_output_path = + get_label_info(":distributed_data_object_js", "target_out_dir") +distributed_data_object_js_obj_path = + base_output_path + "/distributed_data_object.o" +gen_js_obj("distributed_data_object_js") { + input = "distributed_data_object.js" + output = distributed_data_object_js_obj_path + dep = ":gen_distributed_data_object_abc" +} + +abc_output_path = + get_label_info(":distributed_data_object_abc", "target_out_dir") +distributed_data_object_abc_obj_path = + abc_output_path + "/distributed_data_object_abc.o" +gen_js_obj("distributed_data_object_abc") { + input = "$target_out_dir/distributed_data_object.abc" + output = distributed_data_object_abc_obj_path + dep = ":gen_distributed_data_object_abc" +} + +ohos_shared_library("distributeddataobject") { + part_name = "data_object" + sources = [ + "../../frameworks/jskitsimpl/src/adaptor/js_distributedobject.cpp", + "../../frameworks/jskitsimpl/src/adaptor/js_distributedobjectstore.cpp", + "../../frameworks/jskitsimpl/src/adaptor/js_module_init.cpp", + "../../frameworks/jskitsimpl/src/adaptor/js_object_wrapper.cpp", + "../../frameworks/jskitsimpl/src/adaptor/js_watcher.cpp", + "../../frameworks/jskitsimpl/src/adaptor/notifier_impl.cpp", + "../../frameworks/jskitsimpl/src/common/js_util.cpp", + "../../frameworks/jskitsimpl/src/common/napi_queue.cpp", + "../../frameworks/jskitsimpl/src/common/uv_queue.cpp", + ] + + configs = [ ":objectstore_config" ] + + deps = [ + ":distributed_data_object_abc", + ":distributed_data_object_js", + "//foundation/ability/ability_runtime/frameworks/native/ability/native:abilitykit_native", + "//foundation/ability/ability_runtime/frameworks/native/appkit:app_context", + "//foundation/distributeddatamgr/data_object/interfaces/innerkits:distributeddataobject_impl", + "//foundation/distributeddatamgr/distributeddatamgr/services/distributeddataservice/libs/distributeddb:distributeddb", + "//third_party/bounds_checking_function:libsec_static", + "//third_party/libuv:uv", + ] + external_deps = [ + "ability_base:want", + "ability_runtime:ability_manager", + "ability_runtime:runtime", + "access_token:libaccesstoken_sdk", + "c_utils:utils", + "dsoftbus:softbus_client", + "hiviewdfx_hilog_native:libhilog", + "ipc:ipc_core", + "napi:ace_napi", + ] + + public_configs = [ ":objectstore_public_config" ] + relative_install_dir = "module/data" + subsystem_name = "distributeddatamgr" +} diff --git a/data_object/interfaces/jskits/distributed_data_object.js b/data_object/interfaces/jskits/distributed_data_object.js new file mode 100644 index 00000000..78571442 --- /dev/null +++ b/data_object/interfaces/jskits/distributed_data_object.js @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ + +const distributedObject = requireInternal("data.distributedDataObject"); +const SESSION_ID = "__sessionId"; +const VERSION = "__version"; +const COMPLEX_TYPE = "[COMPLEX]"; +const STRING_TYPE = "[STRING]"; +const NULL_TYPE = "[NULL]" +const JS_ERROR = 1; + +class Distributed { + constructor(obj) { + this.__proxy = obj; + Object.keys(obj).forEach(key => { + Object.defineProperty(this, key, { + enumerable: true, + configurable: true, + get: function () { + return this.__proxy[key]; + }, + set: function (newValue) { + this[VERSION]++; + this.__proxy[key] = newValue; + } + }); + }); + Object.defineProperty(this, SESSION_ID, { + enumerable: true, + configurable: true, + get: function () { + return this.__proxy[SESSION_ID]; + }, + set: function (newValue) { + this.__proxy[SESSION_ID] = newValue; + } + }); + this.__objectId = randomNum(); + this[VERSION] = 0; + console.info("constructor success "); + } + + setSessionId(sessionId) { + if (sessionId == null || sessionId == "") { + leaveSession(this.__proxy); + return false; + } + if (this.__proxy[SESSION_ID] == sessionId) { + console.info("same session has joined " + sessionId); + return true; + } + leaveSession(this.__proxy); + let object = joinSession(this.__proxy, this.__objectId, sessionId); + if (object != null) { + this.__proxy = object; + return true; + } + return false; + } + + on(type, callback) { + onWatch(type, this.__proxy, callback); + distributedObject.recordCallback(type, this.__objectId, callback); + } + + off(type, callback) { + offWatch(type, this.__proxy, callback); + distributedObject.deleteCallback(type, this.__objectId, callback); + } + + save(deviceId, callback) { + if (this.__proxy[SESSION_ID] == null || this.__proxy[SESSION_ID] == "") { + console.info("not join a session, can not do save"); + return JS_ERROR; + } + return this.__proxy.save(deviceId, this[VERSION], callback); + } + + revokeSave(callback) { + if (this.__proxy[SESSION_ID] == null || this.__proxy[SESSION_ID] == "") { + console.info("not join a session, can not do revoke save"); + return JS_ERROR; + } + return this.__proxy.revokeSave(callback); + } + + __proxy; + __objectId; + __version; +} + +function randomNum() { + return Math.random().toString(10).slice(-8); +} + +function newDistributed(obj) { + console.info("start newDistributed"); + if (obj == null) { + console.error("object is null"); + return null; + } + return new Distributed(obj); +} + +function joinSession(obj, objectId, sessionId) { + console.info("start joinSession " + sessionId); + if (obj == null || sessionId == null || sessionId == "") { + console.error("object is null"); + return null; + } + + let object = distributedObject.createObjectSync(sessionId, objectId); + if (object == null) { + console.error("create fail"); + return null; + } + Object.keys(obj).forEach(key => { + console.info("start define " + key); + Object.defineProperty(object, key, { + enumerable: true, + configurable: true, + get: function () { + console.info("start get " + key); + let result = object.get(key); + console.info("get " + result); + if (typeof result == "string") { + if (result.startsWith(STRING_TYPE)) { + result = result.substr(STRING_TYPE.length); + } else if (result.startsWith(COMPLEX_TYPE)) { + result = JSON.parse(result.substr(COMPLEX_TYPE.length)) + } else if (result.startsWith(NULL_TYPE)) { + result = null; + } else { + console.error("error type " + result); + } + } + console.info("get " + result + " success"); + return result; + }, + set: function (newValue) { + console.info("start set " + key + " " + newValue); + if (typeof newValue == "object") { + let value = COMPLEX_TYPE + JSON.stringify(newValue); + object.put(key, value); + console.info("set " + key + " " + value); + } else if (typeof newValue == "string") { + let value = STRING_TYPE + newValue; + object.put(key, value); + console.info("set " + key + " " + value); + } else if (newValue === null) { + let value = NULL_TYPE; + object.put(key, value); + console.info("set " + key + " " + value); + } else { + object.put(key, newValue); + console.info("set " + key + " " + newValue); + } + } + }); + if (obj[key] != undefined) { + object[key] = obj[key]; + } + }); + + Object.defineProperty(object, SESSION_ID, { + value: sessionId, + configurable: true, + }); + return object; +} + +function leaveSession(obj) { + console.info("start leaveSession"); + if (obj == null || obj[SESSION_ID] == null || obj[SESSION_ID] == "") { + console.warn("object is null"); + return; + } + Object.keys(obj).forEach(key => { + Object.defineProperty(obj, key, { + value: obj[key], + configurable: true, + writable: true, + enumerable: true, + }); + }); + // disconnect,delete object + distributedObject.destroyObjectSync(obj); + delete obj[SESSION_ID]; +} + +function onWatch(type, obj, callback) { + console.info("start on " + obj[SESSION_ID]); + if (obj[SESSION_ID] != null && obj[SESSION_ID] != undefined && obj[SESSION_ID].length > 0) { + distributedObject.on(type, obj, callback); + } +} + +function offWatch(type, obj, callback = undefined) { + console.info("start off " + obj[SESSION_ID] + " " + callback); + if (obj[SESSION_ID] != null && obj[SESSION_ID] != undefined && obj[SESSION_ID].length > 0) { + if (callback != undefined || callback != null) { + distributedObject.off(type, obj, callback); + } else { + distributedObject.off(type, obj); + } + + } +} + +export default { + createDistributedObject: newDistributed, + genSessionId: randomNum +} \ No newline at end of file diff --git a/data_object/pictures/icon-note.gif b/data_object/pictures/icon-note.gif new file mode 100644 index 0000000000000000000000000000000000000000..6314297e45c1de184204098efd4814d6dc8b1cda GIT binary patch literal 394 zcmZ?wbhEHblx7fPSjxcg=ii?@_wH=jwxy=7CMGH-B`L+l$wfv=#>UF#$gv|VY%C^b zCQFtrnKN(Bo_%|sJbO}7RAORe!otL&qo<>yq_Sq+8Xqqo5h0P3w3Lvb5E(g{p01vl zxR@)KuDH0l^z`+-dH3eaw=XqSH7aTIx{kzVBN;X&hha0dQSgWuiw0NWUvMRmkD|> literal 0 HcmV?d00001 diff --git a/data_object/samples/distributedNotepad/ReadMe.md b/data_object/samples/distributedNotepad/ReadMe.md new file mode 100644 index 00000000..06f24a7b --- /dev/null +++ b/data_object/samples/distributedNotepad/ReadMe.md @@ -0,0 +1,245 @@ +# Sample:使用分布式对象创建备忘录 + + ## **分布式对象** + +分布式数据对象管理框架,是一款面向对象的内存数据管理框架。本框架向应用开发者提供内存对象的创建、查询、删除、修改、订阅等基本数据对象的管理能力,同时具备分布式能力,能够满足在超级终端场景下,相同应用可在多台设备间进行数据对象协同的功能需求。 + + ## **备忘录应用** + +在备忘录应用中,通过分布式数据对象框架,当用户在某一端设备上新增备忘录事件,修改编辑事件标题和内容以及清空事件列表时,产生的数据变更结果均可以同步刷新显现在可信组网内其他设备上。 + +• 应用首页效果图 + +![输入图片说明](pictures/首页展示.jpg) + +• 应用软件监听分布式对象上下线状态显示,红色表示下线,绿色表示上线。 + + ![输入图片说明](pictures/下线状态提示.png) + + ![输入图片说明](pictures/上线状态提示.png) + + ## **开发步骤** + +分布式数据对象 要求多个设备在同一可信组网中。 + + ### 1)导入模块 + +```js +// 导包-分布式数据对象接口 +import distributedObject from '@ohos.data.distributedDataObject' +``` + + ### 2)创建一个 分布式数据对象类 DistributedDataModel,用于管理和操作分布式数据对象,以及数据存储; + +```js +export default class DistributedDataModel { + // 用来存储备忘录数据信息,作为分布式对象的documentList属性数据 + documentList = []; + // 用来存储新建的分布式对象,该对象用来同步数据 + distributedObject; + // 下线显示灯状态图标 + imgSrc = "common/red.png"; + // 用来存储分布式对象的数据变更监听 + #callback; + // 用来存储分布式对象的上下线的状态变更监听 + #statusCallback; + + constructor() { + // 创建分布式对象,同时指定分布式对象的属性documentList 和 documentSize + // 表示该分布式对象可以同步属性documentList 和 documentSize + // 返回创建出的分布式对象distributedObject + this.distributedObject = distributedObject.createDistributedObject({ + documentList: this.documentList, + documentSize: 0 + }); + // 初始化时,进行分布式对象的系统授权和组网通信。 + this.share(); + } +``` + + ### 3)授权许可 + +分布式对象要实现数据同步必须要通过系统的授权许可。因此需要在项目的config.json文件中声明DATASYNC权限信息。 + +``` +"reqPermissions": [ + { + "name": "ohos.permission.DISTRIBUTED_DATASYNC" + } +], +``` + +完成权限申明后,实现向系统请求权限操作。 + +```js +function grantPermission() { + let context = featureAbility.getContext(); + // 向系统请求权限,用户弹框显示 + // 使用config文件中声明的DATASYNC权限 + // 请求代码可自定义,同时也可自定义异步回调函数 + context.requestPermissionsFromUser(['ohos.permission.DISTRIBUTED_DATASYNC'], 666, function (result) { + ... + }) +} +``` + + ### 4)设置同步的sessionId,进行多个设备的同步组网 + +将share函数置于在分布式数据对象类的构造函数中,因此当DistributedDataModel初始化时,应用程序会进行分布式对象的同步组网。 + +```js +share() { + if (this.distributedObject.__sessionId == undefined) { + // 申请系统授权许可 + grantPermission() + // 加入同步组网 + // 设置同步的sessionId,可信组网中有多个设备时,多个设备间的对象需要置为同一个sessionId。 + this.distributedObject.setSessionId("123456") + } +} +``` + + ### 5)监听分布式对象的数据变更 + +```js +setCallback(callback) { + ... + // 删除对象的原始变更监听 + if (this.#callback != undefined) { + this.distributedObject.off("change", this.#callback); + } + this.#callback = callback; + // 监听对象的变更,其入参为 type和callback + // type 是'change'时,表示数据修改监听 + // callback是变更时触发的回调,callback入参为sessionId和changeData,sessionId标识变更对象,changeData为对象变更的属性 + this.distributedObject.on("change", this.#callback); +} +``` + + ### 6)监听分布式对象的上下线的状态变更 + +```js +setStatusCallback(callback) { + if (this.#statusCallback == callback) { + return; + } + if (this.#statusCallback != undefined) { + // 删除对象的原始上下线监听 + this.distributedObject.off("status", this.#statusCallback); + } + this.#statusCallback = callback; + // 监听对象状态的变更,其入参为 type和callback + // type 是'status'时,表示上下先状态更改监听 + // callback是变更时触发的回调,其回调参数sessionId标识变更对象的sessionId,networkId标识对象设备的networkId,status标识对象为'online'(上线)或'offline'(下线)的状态 + this.distributedObject.on("status", this.#statusCallback); +} +``` + + ## **应用示例** + +• 在应用首页,详情页面 和新增页面对应的js文件中导入创建的分布式对象类。 + +```js +import * as distr from '../../../model/DistributedDataModel.js' +``` + +• 对端修改数据时,changeCallback回调触发,依据变更数据changeData和设备组网同步的SessionId ,刷新界面。 + +```js +changeCallback(sessionId, changeData) { + changeData.forEach(element => { + if (element == "documentList") { + // 刷新界面上的备忘录数据列表 + this.dataModel.documentList = distr.g_dataModel.distributedObject.documentList; + } + else if (element == "documentSize") { + let size = distr.g_dataModel.distributedObject.documentSize; + // 刷新界面上列表总数 + this.dataModel.distributedObject.documentSize = size; + } + }); + } +``` + +- 在页面初始化函数中设定监听分布式对象上下线状态回调,进行设备组网检测,并亮灯提示。 +- 在页面初始化函数中设定监听分布式对象的数据变更回调,监听对端设备的数据变更。 + +```js +onInit() { + // 监听对端设备的数据变更 + // 发起方要在changeCallback里刷新界面,则需要将正确的this绑定给changeCallback + distr.g_dataModel.setCallback(this.changeCallback.bind(this)); + // 监听分布式对象的上下线状态 + distr.g_dataModel.setStatusCallback((sessionId, networkId, status) => { + // 刷新红绿灯界面 + if (status == "online") { + this.dataModel.imgSrc = "common/green.png"; + } + else { + this.dataModel.imgSrc = "common/red.png"; + } + }) + } +``` + + ## **应用场景** + +- 当用户进行“添加”数据操作时,获取页面的新添加的数据后,将其转存储于分布式对象的documentList属性中,并依据新的数据容量重新赋值documentSize属性。 + +```js +doAdd: function () { + distr.g_dataModel.add(this.title, this.content); + ... +} +``` + +```js +add(title, content) { + this.documentList = this.distributedObject.documentList; + this.documentList[this.distributedObject.documentSize] = { + index: this.distributedObject.documentSize, title: title, content: content + }; + // 分布式对象的数据变更会自动同步到组网内的可信设备 + this.distributedObject.documentList = this.documentList; + this.distributedObject.documentSize ++; + } +``` +- 当用户查看事件详情,并完成数据修改操作时,应用程序会将用户编辑的该条数据结果更新至分布式对象的documentList属性中。 + +```js +save: function () { + // 使用页面数据更新分布式对象属性数据 + distr.g_dataModel.update(this.editIndex, this.title, this.content); + ... +} +``` + +```js +update(index, title, content) { + this.documentList[index] = { + index: index, title: title, content: content + }; + // 分布式对象的数据变更会自动同步到组网内的可信设备 + this.distributedObject.documentList = this.documentList; +} +``` + +- 当用户在应用首页执行“清空”数据操作时,应用程序则会对分布式对象的documentList和documentSize属性进行重新初始化处理。实现对分布式对象的属性数据更改。 + +```js +clear: function () { + // 触发界面刷新 + this.dataModel.documentList = []; + distr.g_dataModel.clear(); + this.dataModel.distributedObject.documentSize = 0; + }, +``` + +```js +clear() { + this.documentList = []; + // 分布式对象的数据变更会自动同步到组网内的可信设备 + this.distributedObject.documentList = this.documentList; + this.distributedObject.documentSize = 0; +} +``` \ No newline at end of file diff --git a/data_object/samples/distributedNotepad/build.gradle b/data_object/samples/distributedNotepad/build.gradle new file mode 100644 index 00000000..7a297a28 --- /dev/null +++ b/data_object/samples/distributedNotepad/build.gradle @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ +apply plugin: 'com.huawei.ohos.app' + +// For instructions on signature configuration, see https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ide_debug_device-0000001053822404#section1112183053510 +ohos { + compileSdkVersion 8 + supportSystem "standard" +} + +buildscript { + repositories { + maven { + url 'https://repo.huaweicloud.com/repository/maven/' + } + maven { + url 'https://developer.huawei.com/repo/' + } + } + dependencies { + classpath 'com.huawei.ohos:hap:3.0.5.2' + classpath 'com.huawei.ohos:decctest:1.2.7.2' + } +} + +allprojects { + repositories { + maven { + url 'https://repo.huaweicloud.com/repository/maven/' + } + maven { + url 'https://developer.huawei.com/repo/' + } + } +} diff --git a/data_object/samples/distributedNotepad/entry/build.gradle b/data_object/samples/distributedNotepad/entry/build.gradle new file mode 100644 index 00000000..ba57263b --- /dev/null +++ b/data_object/samples/distributedNotepad/entry/build.gradle @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ +apply plugin: 'com.huawei.ohos.hap' + +ohos { + compileSdkVersion 8 + defaultConfig { + compatibleSdkVersion 7 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar', '*.har']) + testImplementation 'junit:junit:4.13.1' +} diff --git a/data_object/samples/distributedNotepad/entry/src/main/config.json b/data_object/samples/distributedNotepad/entry/src/main/config.json new file mode 100644 index 00000000..0d57e04e --- /dev/null +++ b/data_object/samples/distributedNotepad/entry/src/main/config.json @@ -0,0 +1,70 @@ +{ + "app": { + "bundleName": "com.example.distributedNotepad.hmservice", + "vendor": "example", + "version": { + "code": 1000000, + "name": "1.0.0" + } + }, + "deviceConfig": {}, + "module": { + "package": "com.example.distributedNotepad", + "name": ".MyApplication", + "mainAbility": ".MainAbility", + "srcPath": "", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "entry", + "moduleType": "entry", + "installationFree": true + }, + "abilities": [ + { + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ], + "orientation": "unspecified", + "visible": true, + "srcPath": "MainAbility", + "name": ".MainAbility", + "srcLanguage": "js", + "icon": "$media:icon", + "description": "$string:description_mainability", + "formsEnabled": false, + "label": "$string:entry_MainAbility", + "type": "page", + "launchType": "standard" + } + ], + "reqPermissions": [ + { + "name": "ohos.permission.DISTRIBUTED_DATASYNC" + } + ], + "js": [ + { + "pages": [ + "pages/index/index", + "pages/add/add", + "pages/detail/detail" + ], + "name": ".MainAbility", + "window": { + "designWidth": 720, + "autoDesignWidth": false + } + } + ] + } +} \ No newline at end of file diff --git a/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/app.js b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/app.js new file mode 100644 index 00000000..e3a0891c --- /dev/null +++ b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/app.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ + +export default { + onCreate() { + console.info("Application onCreate"); + }, + onDestroy() { + console.info("Application onDestroy"); + } +}; diff --git a/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/common/green.png b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/common/green.png new file mode 100644 index 0000000000000000000000000000000000000000..62c8974e7960ec04a24e46d8dfeedfeb56a21f9c GIT binary patch literal 2226 zcmYjTc{tSj8lTg|O*C4KZ7L_F6UOdFjR^_K?lcU>GGQ!P8bfGgSMi8bwveqtnNhYm zmLZL?_e1!Nk)@O@gR!SU=ltAzpL_2g@AvaApXYtv_xrrx_xawpvM@fdUwS_b20LJC za@hvLACS!L+XJ2afZ!Dfej(c!8^Gv*>?~yL_0l)jhr!-F6y0_cf^3mM69+O3hG6Z= zFYN)PH(;=R#-^9`?QRnm#;_?vCI_Q}JZ)1mE~Fk(k{WddSk~uzM`7ywI{t(!>TlOp z-&7(#UHEA`<~+8(6Vr0)&`BnL=<&w@uM_@^j~Z{In=`P{Ts2NQCNiSgX@@`Xu%de? zouNWd!8KoG3SK+u!)adloN@3@ggv_Y;&ymYEr0JgJdA_^(f;e@U*iAy7LT1ZE?U

?%P z4F}$l;L|CL^nf3uv&?FX$OFs?;@=z4XkqJM?y20ALN$%&;k z%%T#6s7h_$rHW}9K)c6A!q6nbaK=!hp*JgsH?K@irGDGWDVdUy@q14($WLfYS@A(n z47+;Js~A09fWY6KenikZU(Vlw^36(hGReHS#klyM@Po?j<<6}H8 zmsudprU&D_BJ@u`$Vs+<0;R^U(D5K8;A9lGO!N9k{q`xzxl;4zYBdqI1us|n34^+Z zsP`t`@pZ@=z}BYPZrCxBw{qr7?jhx4PqE(^fR%B?=^>9=2$83YF;$cV-SAF-l@bVb zyN)mD&@=}7afIm3U34BN^^S6)Kg%gKg;0WWQy=wO2*s}I-nsUX6CK2^1@^#mCcBe;@`Qvv*%fN@s!PAVq)15GOK_(t zJSA0Cbfsm8WY0-Yk7j>}vcb0Na%vZI2eXnu2T<)nw^l+OLIsWYa%ew`cJ!dLlB#z7 z2ff$tczDrSqWJcU0B)lStW#rO19n;VKoZ6e^9?Q>rKK=dSphK;k<}?>xw56QwSb)1 z8J1HbGLc69bnj4Ja6~f&A*)6&4(Gak5~BT^icle;s&v9NgPSSG<=ympD7W=_OEKW* z|Bn;;bQ=r)7a(c}xHJ;zk)h4&#mxtjlc0i0sQ+-50~^LNx+ivarh@ABB&_hUmfb^q z#I%65!+^Ptm@=U2hym5o9~m1a(pJxlJaXIZ398U5C5C7$Ha+v!I`=*Mj;>dtjj zeYgrwuL5-*v|P*LEltjk+cj=)cl;$UW|-=+vz5b=v2B66L(-}$iAOVO>YUGPb`$z` z(cUd|CM5{N2`h@zabf^nh%NVXO#hwPp~R{Ek`9B+-+aZC9_bO!+_K}9tG!mL>@5YO zMV#_=gzwnHwAs1Z!f(iVTEg1w%uWPc#XVHAMH=oqiIhxJwfJyM3CpJ#;}an(!+7mUwbHs z6T7pfv&-!_8@EDcZ|N4Jm^T=iIvk;5)PjdNCG9SPMcSR%sa{G8$=K|)q4Uwa3XPF| z8@B)>8kQ+|OSG$&dw%Ta)ubX&sM#7`eD)@k&D!yY71HSY3q`%{doOLUA8u#9b3!JE z3>N>M2{<8hu|)~*$Zg6spiqw3V40svG=Mnry7N+|zx5NcGo&OpXiGh|njplSqlp4HQq7m=J|pV2xfRQJW{d2D8$1Wh!>Be3X0OSc7^N=;&;jGxr> zmr@pOgK)80)S*&}{^GE=R6U7Pe=@XOIuZS(7Ttj=qRtdicrl}Syuh%xHi zvHBTRPAos*>2X10cb|ECpAiF${|cyoF`no%>IH=w8jg$^BJ&)PU-dAeg-JhYqPOz1 z(jXQ&tKocZwrP%~N}Gln2PX?$W8r7sUFXHHvPC)+Ny@=)bV`drPxFGPAcaPy@|Hht zSz?zmD=u zqP9tY5fuP1GL;?0FeC_tDGTp+PS)h$#5S9*IN(DP{G3e|SJEeCWPH83QZ?&Q8k?Ta zRv0P?veU2LIEveQ`^W@)bF&NAqoiC|vg!X=tv+tmn2~)>+|w6Ruz7g(_v1a>@Jq{O zp0tZub?Z@+D&~Q0hlxKy_=Dzd)2Zl85dIsX=`izEE3OIeQiodAQxwmwJzh>EGy!AG z=~f)wabo`?0{X>$f(M6T&w8Abu$ zr2471xl5cNULbSw2I^p_93EFo72nId@c9Ftd}*K_di}|36W8IV|GNgD9rBr5^%pE8 X!BfG+sOcT_&w!a4SzM+YxW@bq6@ze} literal 0 HcmV?d00001 diff --git a/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/common/red.png b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/common/red.png new file mode 100644 index 0000000000000000000000000000000000000000..939c4abf1c58c3b9e9adca93c4ce1e53e11f84fa GIT binary patch literal 1754 zcmbtVe>9VO7@uyf`LTZJN;j>px}~TjI%DEy8zUK_Xk$_eiN&lH>82kOirX!zSS-gV zjb!;PZcA~)CX979iLy6u%|W&Kk=^g8d(J)g&wI}EeV@?Yw6#ceCzgJN!|lZO)mQJStl6+92ry2o7i zF%XCaLhaFcPA?2WAdqx7qLc3l+DDmv+7jp>an6IWZW4Jmt3z@ALaX;T(Tim)Pc*uX5LpDfYL8KC0&loSa>y8-N2_pzK;G-KcT!W zTXd+A)Z*X|`znb~6Vp3!q*QDv($NKOsiGz#9e05In<;X7;48fZ=Ypzkn4aRlr zIxJPFE_F!k!~-$$q7z3scIj=QN~MyC_iRv%D~muD1}kY%H%1wzpF#p0_|;(?J5g)% zxTA(``sq`)%=#+TEOznAm>emN>wfe0q(Gt@D3-|-0y6Y)-Jm0vi8sbtsM`5Ua4pwl zN#$h7|B@&n06w3V@n_Y-BOLKaXOoNshI_VbRJDtE4IAau(9Y6`7J({~BaRlIk=X-{ zZ9Yp`M=^`_g>J=XN^~(@6YqMzN`E|dCjq{M+dO3R@iH)B)4oP^9lU4z04>RQ z5T}*56$-gN9c{&gMlRS{$vpK+$}G%f7WU9NKOoyM+Yk24mf@fbe*ic2H8qoAFDKZm zvL{Jv02nRVi(AM;9T^z87MP?*`B$Wv@y9N#!N-4ov}V>B zF!z7LlcJd#m@Fs>Qqs?a-KRyUrw4WZyI4e3M3Clw9?~ZXiwX1i-=gt_ zuW%g;lG4(vL9d?(@3IfD+cq@2JcYq$JZnfUo6+^hf*!AE857uX_5gU@J(C%HNXr+l zoWThIBWtz}4ZbYCYCq=<+sVL~BoBz>gg-}g6R;m}zBXZ;aFU;gi(DXO#*obTc&u!T zSNnY?57WAB1+ycjyBTvkTG(*6JTGc_MVlZmd61j+rG~7mEF?i}6Qp^y0FaadZ*1Lh zj?9}I1n~yY1f^|$1jHMzBP(qSSrBisY(v$-?lWQ!l|;wP1qcobZ+K(R0l`S;195Am z30qdWb!)8kCz=^+BQhz<<=3n~>|500R!D-bO3gB?zA3IYW$i1&V;L0e+s$DIpu-fH zEz55~%Uh1hf3Lato}1OoH;M5S5uicg$Ky5i*+Z&H?Btk86!dOW%zBGK)7XLh&red! zR5sZy;|HeEV;AGsq3K+cAg!t)7<$%?F6+CYd+)8PjR zny^$8dWv||Gy&Vos_VNQ{bDTfv0>ilv+>We7BL=Q?5qy%!{lLUaS-Fsst#7-_j@OpU69 + +

+ 添加条目 +
+ 标题 + {{ title }} +
+
+ 内容 + {{ content }} +
+ 确定 +
diff --git a/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/add/add.js b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/add/add.js new file mode 100644 index 00000000..1dcc4d7a --- /dev/null +++ b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/add/add.js @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ + +import router from '@system.router' +import * as distr from '../../../model/DistributedDataModel.js' +export default { + data: { + title: "天气不错奥", + content: "今天天气不错" + }, + onInit() { + console.info("objectstore in add page"); + }, + doAdd: function () { + console.info("doAdd " + JSON.stringify(distr.g_dataModel)); + distr.g_dataModel.add(this.title, this.content); + router.replace({ + uri: "pages/index/index", + params: { + dataModel: distr.g_dataModel + } + }) + }, + changeTitle: function (e) { + this.title = e.text; + }, + changeContent: function (e) { + this.content = e.text; + } +} diff --git a/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/detail/detail.css b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/detail/detail.css new file mode 100644 index 00000000..73fb19f8 --- /dev/null +++ b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/detail/detail.css @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ + +.container { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + left: 0px; + top: 0px; + width: 100%; + height: 100%; +} + +.title { + font-size: 60px; + text-align: center; + width: 100%; + height: 40%; + margin: 10px; +} + +.btn { + width: 50%; + height: 100px; + font-size: 40px; +} diff --git a/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/detail/detail.hml b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/detail/detail.hml new file mode 100644 index 00000000..fad308f3 --- /dev/null +++ b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/detail/detail.hml @@ -0,0 +1,28 @@ + + +
+ 备忘录详情 +
+ {{ title }} +
+
+ {{ content }} +
+
+ {{edit}} + 返回 +
+
diff --git a/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/detail/detail.js b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/detail/detail.js new file mode 100644 index 00000000..461f1798 --- /dev/null +++ b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/detail/detail.js @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ + +import router from '@system.router' +import * as distr from '../../../model/DistributedDataModel.js' + +export default { + data: { + title: "天气不错奥", + content: "今天天气不错", + edit: "保存" + }, + onInit() { + console.info("objectstore in detail page"); + }, + back: function () { + router.replace({ + uri: "pages/index/index", + params: { + dataModel: distr.g_dataModel + } + }) + }, + change: function (e) { + this.title = e.text; + }, + changeContent: function (e) { + this.content = e.text; + }, + save: function () { + console.info("start save "+ JSON.stringify(this.data)); + distr.g_dataModel.update(this.editIndex, this.title, this.content); + router.replace({ + uri: "pages/index/index", + params: { + dataModel: distr.g_dataModel + } + }) + } +} diff --git a/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/index/index.css b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/index/index.css new file mode 100644 index 00000000..d5d35b9c --- /dev/null +++ b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/index/index.css @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ + +.container { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + left: 0px; + top: 0px; + width: 100%; + height: 100%; + background-color: aqua; +} + +.documents { + width: 100%; + height: 100%; +} + +.title { + font-size: 60px; + text-align: center; + width: 100%; + height: 40%; + margin: 10px; +} + +.list_item { + font-size: 30px; + text-align: left; + margin: 10px; +} + +.btn { + width: 50%; + height: 100px; + font-size: 40px; +} + +.dialog-main { + width: 500px; +} + +.dialog-div { + flex-direction: column; + align-items: center; +} + +.dialog_title_text { + width: 434px; + height: 80px; + font-size: 32px; + font-weight: 600; +} +.dialog_cancel_button { + width: 100%; + font-size: 32px; +} + +.dialog_device_list { + width: 434px; + max-height: 150px; +} +.device_list_item { + width: 434px; + height: 80px; + flex-direction: row; + align-items: center; +} + +.device_item_radio { +} + +.device_item_title { + width: 80%; + height: 80px; + text-align: start; +} \ No newline at end of file diff --git a/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/index/index.hml b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/index/index.hml new file mode 100644 index 00000000..f6f13507 --- /dev/null +++ b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/index/index.hml @@ -0,0 +1,39 @@ + + +
+
+ 备忘录 + + +
+ + +
+ {{ $item.title }} + {{ $item.content }} +
+
+
+
+ 总数: + {{dataModel.distributedObject.documentSize}} +
+
+ 添加 + 清空 +
+
diff --git a/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/index/index.js b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/index/index.js new file mode 100644 index 00000000..0818c296 --- /dev/null +++ b/data_object/samples/distributedNotepad/entry/src/main/js/MainAbility/pages/index/index.js @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ + +import router from '@system.router' +import * as distr from '../../../model/DistributedDataModel.js' + +export default { + data: { + dataModel: distr.g_dataModel + }, + changeCallback(sessionId, changeData) { + changeData.forEach(element => { + if (element == "documentList") { + console.info("newest data " + JSON.stringify(this.dataModel.distributedObject.documentList)); + // 触发界面刷新 + this.dataModel.documentList = distr.g_dataModel.distributedObject.documentList; + } else if (element == "documentSize") { + let size = distr.g_dataModel.distributedObject.documentSize; + console.info("newest size " + size); + // 触发界面刷新 + this.dataModel.distributedObject.documentSize = size; + } + }); + }, + onInit() { + console.info("objectstore in index page "); + console.info(JSON.stringify(this.dataModel.documentList)); + console.info(JSON.stringify(distr.g_dataModel.distributedObject.documentList)); + distr.g_dataModel.setCallback(this.changeCallback.bind(this)); + distr.g_dataModel.setStatusCallback((sessionId, networkId, status) => { + console.info("objectstore status change ${networkId} ${status}"); + if (status == "online") { + this.dataModel.imgSrc = "common/green.png"; + } else { + this.dataModel.imgSrc = "common/red.png"; + } + }) + }, + onDestroy() { + console.info("objectstore exit index page"); + distr.g_dataModel.clearCallback(); + }, + add: function () { + router.replace({ + uri: "pages/add/add" + }) + }, + clear: function () { + // 触发界面刷新 + this.dataModel.documentList = []; + this.dataModel.distributedObject.documentSize = 0; + distr.g_dataModel.clear(); + }, + detail: function (msg) { + router.replace({ + uri: "pages/detail/detail", + params: { + title: msg.target.dataSet.title, + content: msg.target.dataSet.content, + oriTitle: msg.target.dataSet.title, + oriContent: msg.target.dataSet.content, + editIndex: msg.target.dataSet.index + } + }) + } +} + + + diff --git a/data_object/samples/distributedNotepad/entry/src/main/js/model/DistributedDataModel.js b/data_object/samples/distributedNotepad/entry/src/main/js/model/DistributedDataModel.js new file mode 100644 index 00000000..9881d32c --- /dev/null +++ b/data_object/samples/distributedNotepad/entry/src/main/js/model/DistributedDataModel.js @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ + +import distributedObject from '@ohos.data.distributedDataObject' +import featureAbility from '@ohos.ability.featureAbility'; + +function grantPermission() { + console.info('grantPermission'); + let context = featureAbility.getContext(); + context.requestPermissionsFromUser(['ohos.permission.DISTRIBUTED_DATASYNC'], 666, function (result) { + console.info(`result.requestCode=${result.requestCode}`) + + }) + console.info('end grantPermission'); +} +export default class DistributedDataModel { + documentList = []; + distributedObject; // distributed proxy + imgSrc = "common/red.png"; + #callback; + #statusCallback; + + constructor() { + this.distributedObject = distributedObject.createDistributedObject({ + documentList: this.documentList, + documentSize: 0 + }); + this.share(); + } + + clearCallback() { + this.distributedObject.off("change"); + this.#callback = undefined; + this.distributedObject.off("status"); + this.#statusCallback = undefined; + } + + setCallback(callback) { + if (this.#callback == callback) { + console.info("same callback"); + return; + } + console.info("start off"); + if (this.#callback != undefined) { + this.distributedObject.off("change", this.#callback); + } + this.#callback = callback; + console.info("start watch change"); + this.distributedObject.on("change", this.#callback); + } + + setStatusCallback(callback) { + if (this.#statusCallback == callback) { + console.info("same callback"); + return; + } + console.info("start off"); + if (this.#statusCallback != undefined) { + this.distributedObject.off("status", this.#statusCallback); + } + this.#statusCallback = callback; + console.info("start watch change"); + this.distributedObject.on("status", this.#statusCallback); + } + + share() { + console.info("start share"); + if (this.distributedObject.__sessionId == undefined) { + grantPermission() + this.distributedObject.setSessionId("123456") + } + } + + update(index, title, content) { + console.info("doUpdate " + title + index); + this.documentList = this.distributedObject.documentList; + this.documentList[index] = { + index: index, title: title, content: content + }; + this.distributedObject.documentList = this.documentList; + console.info("update my documentList " + JSON.stringify(this.documentList)); + } + + add(title, content) { + console.info("doAdd " + title + content); + console.info("documentList " + JSON.stringify(this.documentList)); + this.documentList = this.distributedObject.documentList; + this.documentList[this.distributedObject.documentSize] = { + index: this.distributedObject.documentSize, title: title, content: content + }; + this.distributedObject.documentList = this.documentList; + this.distributedObject.documentSize++; + console.info("add my documentList " + JSON.stringify(this.documentList)); + } + + + clear() { + console.info("doClear "); + this.documentList = []; + this.distributedObject.documentList = this.documentList; + this.distributedObject.documentSize = 0; + console.info("doClear finish"); + } +} + +export var g_dataModel = new DistributedDataModel(); \ No newline at end of file diff --git a/data_object/samples/distributedNotepad/entry/src/main/resources/base/element/string.json b/data_object/samples/distributedNotepad/entry/src/main/resources/base/element/string.json new file mode 100644 index 00000000..610d89af --- /dev/null +++ b/data_object/samples/distributedNotepad/entry/src/main/resources/base/element/string.json @@ -0,0 +1,12 @@ +{ + "string": [ + { + "name": "entry_MainAbility", + "value": "entry_MainAbility" + }, + { + "name": "description_mainability", + "value": "JS_Empty Ability" + } + ] +} \ No newline at end of file diff --git a/data_object/samples/distributedNotepad/entry/src/main/resources/base/media/icon.png b/data_object/samples/distributedNotepad/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 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 literal 0 HcmV?d00001 diff --git "a/data_object/samples/distributedNotepad/pictures/\344\270\213\347\272\277\347\212\266\346\200\201\346\217\220\347\244\272.png" "b/data_object/samples/distributedNotepad/pictures/\344\270\213\347\272\277\347\212\266\346\200\201\346\217\220\347\244\272.png" new file mode 100644 index 0000000000000000000000000000000000000000..6729341a48896f61111a58465e59b82820d7023a GIT binary patch literal 43141 zcmV(;K-<5GP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXfs2WK`K~#8N?Y#$} zWkr>SyKX!;cJ3x40+Iy@22h02F^h4;ggNJcIpR1oI>s@Lia9Xq8^xUCC}t290Rcfl zQbXt4H>dZ1|EfCobkitkx4?U6b?>`tSFKv9R@JJi9ZpAl>|f`*y1HV1em>^r<|1Zi zqO+^V>5h&LA9K?k(b?G<73Mp12%oa(n9Fo@xvZljY0Xn#b-<~(9J8~t5uK`6Kp0zG zgWDJ!h>6Xk5G%O?(#XeII5n>WVJdW`jLA>uE=e`tX`US_o1aIgl+6f&luv2of?ij* z>vwf@r>v6Dc$~=Z?v0tLDP=2TPW6y|PV}iO-2vMXy#(0J_CRLgqi@kr$_at z=7lf5wK{4ds%1|bBCB=4jKVd{7rryaF4EKTq2s)8prv$APftuscBHU8vJEta>~m8t z%P(_w^`>kpqb<>8c3NfW01G?}3vJygp7SxQL-e|Px3jw=rbaeL|Ip$XAC+GwvT6&r(c9!lUZ->;J>MxiDBa!L8&l)5Iru#) zqne;8n;dJ7_A_jL*BB0Lw@MV%Qa$ zw35oj2p@M}XssilO;RV7(Z_H%)6%u_I#kZtHZTaM=u!2lfNa@d9Ky?HFE-dK@)vYA^%tP0Xr+CruM@P5 zTX1YQov;tYjU$>G%he&R3u7BJyEG2WbxQtD+6|s6>y&LppFU?=)$!b}(%BcPqxu~k zJ=u<^kA64=X9h0oFsnY-P}Yp-3b%|Q3B``{)AreU8kR8^4w9)u9Lj~x1T`$+HGgw!B(WRC~2ra7vHZg2UODlwK?QuAsWQt5rSiP204{?c z#d9u{7=^a1tF{*{XsFD*l%AQK)F3UV1-}r)f#Bscpfdw`m7B5}?0`;W)zLxYkR=vr zn0gety-|g>1a?A){f#3NA-xAQ!CEH$UXn{{sN2dmoZs|NXuYlvuu6)|3&`n<@v((G~ z3)fEMsoCLNFMV=$Ofo4wGpzwdGUHpEDVt}|!B(>CqD6~hVthPlc(9ty$rOMNK<)g3 z4o&jl(b?VVw^?rA^MIxce2#16roP)b2M6ObfXI)qgCtH>P+xRCdE>(NQwe;ON1C>t zv;R?pbK24}f`@Iy)S|)D=RCUXBV2UK{|k=WGbd_QpK9M~Q}Ysz+Xua5w`^A~)pnUo zdZkh5a|B-(8kc zK2ZCS!DU4U>?NS@nOGSM3rfz-#jxTT_7tCmH~SAUg z>THLjK#e`80L6?k2MW;^Y1lxIAZyuFhk*hVXmlz;_)1I;3*%FECRNuUlQt7?j#3vI zbY@m z?{v6b3rMC$pC%h>qa3`^Q?o=S0d8B>MFH%E?ydp>_$d!{1-6SW2s-2=8~V;N=s<%5 zTL_TDR#G0i+bL;l?4$IORXfGR*tqkN-GiM>q6piRA=&GX`KrDUa&iYBolL9X>5nH? z&=D@OR=x&&1*~%IcG5xjwtpFg&WdlN7s$Glt{w=*9h4~I%L(bJzUrO=;2yq>|MUcm#r<3OG(>M)B^H&}`2(gP;NlEtV6*)mWG zoU}WWEML>!_>$e>p=((=O9yBm1`|VMAP-8u1_v3`x9OuAJS`L1lyU7e zd{oW8PxUN=jh2!{!E(?q6^!s7+X$X&1pA6nXg?HX)JWTAN1|<1NBWzW^~o|#{jwe1 zpQ=ncltz@4i+mjwhZSiV8^QJD6gJ@1eh04lI)QFp6NV102oppP`ARzyH{^uQz zV2FB0NIcm!FkRKlaSFK;CsoKX++&Ig7`eThSaF;9oViY4NZ>lp_TAze^iSKBYaO^s z?oztJs9^psun>hGb(QXl2OM{b2ZqYYElWiX(#kL8F!oUf9OL+;JP%IUuuV{!;xuXs zo}Dsr$fF#G@M=hc=0K^uDo&x1CKC<;Ou)7xGhenjkfk|WK`znhZ1l^}O3S&jQB9Y?cT~6D@6P^Q>nbmY22@kXAc*przPZ;8M5%$m90( z;Fal|SlDtRN9CmDY;P|@hce{KHU=&AB6vWnP*4Ey1P=cIsRDi_o~mJ7^ESQ->6!@_ z1NnlkmJM9XM7|Em)qzadiQ{-R}6V z>VTRPJ=!||L6d*bq6E8v3y#M`Y>GZbH`hf+=ps**30r~>O$S6#RUJy}7~S*_=fZ6P zkfGaSG8Za$6`hSrJ+e!E%Li~ShsRX4eaA!YaY}VGI=GkQy7J(HcwD$xoFL2~!QcaN zi_c(_vr{)_Ri^1sMQ|iF5|Dyr(9$fx0cD{>rqZD4OnuWTbd)CG5LD(oSVOnqm3j_P za>zcmK$(dVgzI3$R*7&sdZeqak&{_mtxH4osMD~OFlXxu*LIP7=!P+ki(Cu!EQV-V zedJX>t79b~s=P8#>pJg!&`GtbQ*ooePr($4aefgH4t8Ybw`kRhn3|eQeCgdYI2f~9 zA&Q^_Bx=twXxQ1Xop>`4N;d3kv(`=_8>C!$NnOPAALV-+SA6IcOoXhzb;tqXI&DKe zItu#rH)43%l9*zSkcbZ0cj1Zv3grq7x~6h=Itt&@nxDik40T!@~|umd`7P z#NiCz+$GU9ogCU2okN2|m$%Ca!lhj}n?CrdyVKTRY1uMo)XFQun};tG7khMf$sbe) z+q!O@tROds)8MNfAbCtj@}*t4E`uHHDdWr*d9|UgJ>W_H+O8RVRfe(96`eJ4@~s7~ z!2?V*uSoL8cxFNdw38mb?rZw8X$VRkPpFZ-lFq6L1xH40>X)QTy7;o&O?2I7ndHsP zMgMS)7fD3+NMb%Z8QVyv(RHYg>2E5n`Vxk1SYq+wWs72B^G2i1O;02#yoCvpAnTD` zD2N1hPa7A$_;AHc1)99Ow2&a3W}#onf5DaA@nxHjoimfG^pb{s!!qkHLH{3RAr3UbNFBilXlS9#ze^aiKZ7qr9AnILUkB3Q|2tbpro@AR(Yh| zFDXbH8{K~jAcyUm=!PPz{wB?>!I`QfJ~=if{f_w1DWU-v4NeR?ZnXr4V&XR-KnJu--xyh~BQl;6Y0b-Ut{=z3|7A6Q~i|ZO^pt0;^G9!-7CG^2> zk+Jgf86b^u%7D>&pV*xZdf*hO{4pqL4;nxjKsk`LS&q>}(+^8cX#!CB!gIc?PyqBp zPganm8RR8KtIi7jDpJRUCl5<0-vdsEY@~qG+uI%En-pv{IJg5ac|bhkkoeoKZUY)r z)o0{>bU^N3+1<_wVx@#yYNokRjznF!6O19o=x*?yFv_pEp@f#5a)lZKR+ zN>EPQ&3V?zb@2uLOBChN8Mr`Roo$P4(XxLbbFm5K0G$mPlxGyt;bAzh&cPEPJ34wX zQ1SrFhVIlA-Oip~cX-Rei58`8kqvy&0k&v!)ulc@IcMVuYVbrLRzY1^-~9pc@jLg0 zj_8%&Pcabpi?-DE4gtFHzzKA8*iM4#8!4{^Tqw&^>L)z6HF7%*zUTb0_;WRm~78C7l@Di2&`UU3mN?MH=DB)C*OD)av`f-T8L6AL5M2iF z;wR9mknJjblm^g^JOl)&kKFW9e5nk^p6F1=HV|p)Zdpoa>gv_x06AQrQ_{3NI`~J; zDvNa}Wwj$2oW_iztqLit@@w1FI;63Y$}26>4j{IV5eiob;T9@m3tx86hcBcb{3t^g z(*~V@GZ%|?Ss9b#+|MU475OXg@~+N>+xyq(FV5B)(;L$)Du<&KP? z*3;SM;Mxbaq>OV((?mMwHlOIIU!?z|YucWP08fYGb2*_@rul=XlO}}L&iphc$o6I> z$jN>3$a;D9!-y&XFN4)KfH4Lf0;ufoG&BqV>Vy5NlY;0F)Mi5I~aI@pufm>8(AnXudu#f)~dvmxj_ z57bF#(X^rhd;}hzxX2N6Ko>}i%ts#bW%qgwib?2CL9Y(&;AXz1-CZA4%G2>HuI(xN zr*?^FunlySld(-RX)9s5Gmug$8ofL%-=k*9!2`Q+Bud50_MshQJhxZZU?V0rZc?8~ zzZB(^i8kR1I>P6l?Ey{1DfvlPCzUx(9yV^~X9u_4O+!3g&K31JbDibIur!QL;-#~n zg2(^hX}ZwK!HXKM!UbhT22UHl6W=S0s;6v{gT6Ib09O;P2j`@dfj}}ANS+)CT-U=BD9a@- zrp-a0fQGRpUKvB&CIZQoG!xj?_874ZupLDxCyoWlRk1B&glKcgD%CWTZ|mS*c{O<^ zg0K1KYlv|0NB?}lL0*nKpI$(K|mTClEK(W zT?06j>AKjlp4}&DBhpl?cCB(#tbewbX;Y3p6>s7}cT)gXB^=RK6QYTvphWKM6v_iC zV+?`AR|A_P#dLs7^CYEZNpH$;JPD;mU&p_bQTXeJmJjyq41OZHhmf^oz%oQq%XX6^bZdBl^EppQ`mG0c*$%gUqloy)`%ut8vF-XZr&5QiPTRM`z z1yJm>{7r`kH(%y801qGo8Kkl`u(Jb*=;n5NXLL6dh@oIPl8*5`5J`NYcj}s32c8&B z37eH#v1?s5V+SbE?_9s?4P(mDqsX@{NWRh+9|OMy*JVzLU!mhk3jiO&Sa^~HiJeAAefKg<;!B4zD<2(0$2YCvV8;nzRwTIqz~U>6Vn$3 z$w&1s`=7F0hAniy4kWM2FDqOZ?Dxg*7Hr;vtGsMWY`suc>EZ^i zcSFGas(?x95Beg0;emeSn?89uH|e`hNf!QFLr!EZl-nX7Fp>@wI$vjR?-pVbshjZw zJD8*pT%QUx?rNt8COF>Pj7a~%~ zJLpI^58Q}_I)$+xYQVXQow%Y*cxqb0*^H{zSiUG`W~by6-CBI`fRtKD@-&}_77}Lg8NGzjrGR0Z z;tzD3IvMhTOj;?W0aPv~E(KqQ`1svU@)f9{X;FnQ%Sa9;8KM>bImyEMRzFF1D+PiD`Q0r=NoB~y#D}0< z@Liw7w#l|}z}2;bw=xuJBm=&=z#OWe`QEg$ybC}YS!GAtviJyP9$f4%Wl}=gg!o(P zk*90xqWY$Z;HQm@tnP|9?D?IZD<*B*P}qBKWM7S68AI41CcA4ioS)@Hxm9;rM6g1t8>CAP|xR1GG4E#iK>pqLX1fFq9%*Yw=6o;yKSlNK&}7 ze67We==%CY@F}woExDstD^q{1Nj%(%jfP~PNN&!8yni<%+ogjTpiOY0j5PJ&MVewA z{>TL>jBjelz*)4+Pj-<*uyx~YacEGCEoC>x?CQXF0%&u{_5=NcG41!4k&kTBg$GVX z8$gF`7ZPsZLIWlQJj8N@&tqm}TzRaL8H}Wx^1Q_^23Q6A(%IZ@k_l@FQ#4&_Jqa49 zoFELfYkg;u2gF<#{(VfEgzNW~3qaH5%J1#as!_9M`~naO+u!XDUix2lW*z8);jovk z30xUS1n6EIAAjm@nSrv^?)yLhw!!si3v{6Zge4ceZ4cVA8AvFvbyX;W{%1DGD|+zJ)f^L1RFlnl zpfPwjiAHt3tefYSeAF_ngysWQ&ZHb%{kJ_Nh=SeFPQx)dK4Q7qS@#21)eeS~ha>-H zDut$9k-zqH)hg9ervkJ;QWTl(Bc_i{m1q0uO8qhOC0l9N^gHBUAc)O;u6a5W3Uabj z^3fGq{I7T>;|3558cF%ELcYoD)4aw6jhs}0Qs}5(sK+U#ObZLGBtbcS!@-(9Vm`&U z&<#Z6l~W-}Sz1^5)G<$KCz)qNtp+ zx4&O8Sl7&oq3=H`l!c&+l6CvnG)hM`Z87Abt6JWL5!nK95yB{~4kD~{2FS|DvlCO% z%dAxLSSB2?(6?@dzRwB-JZ(w42_fl5xiG7G%%+{j!_#dwYBRY=YZUB#L*kZ#!XXQM zPf&%^ypIe`&c-P;Y)|2VD{vb^K*;7vHE^8|fyAkO!?@&gNRK8DK$Wv@(&Ic!yGcLW z-F2{+iX}&G%cuS9-#)_^Qi-R(x`r0Cbv8acGSB%O$x&%0C8YI-%7uRRMQ12WXOMjc z(-nLTkG|f1f62{3SRkwD4|So*nLdMF!Y@_3RnLCm9-=mDk;$J+g+vWBWa{j?vQg?s zIh<%i@DeC9OCjX#9vd&)m|WAybd}LpW~j9DIPfD%3a%jQmWwTbOzO~&@;c>6ng&x%>@z{8;6(5}A84-NJV}tO7 zY`SV^PwbeqBOis}seFMSSJ~dCEqb(E`R}7CN738k1<6uoP?_oLKXTPRnD$Z${ICmr zpyfmLI83bA)jWg;VQAJY@O@UBXNJy<%L1o$5(2JjU$SYGLtmB7RSvMQfQS`?s3HJ@ z2N<36TEs+^`?6VCF6~Z*;fk}uwLl##rY3j?PHE}SHAffIL}(TKoB{GW1y_ipLvd z*p+q^s>&_1)e;TK?fM1MXA~Ig#6r;5mrvm1&#W3U5X{9?3r-V?9PnuRgW5aUB(7~O z->BEMj`7i-qe8WD3m;+t{SP@PGcRm}?_e|h4j^5C!OVOMm?k!rdzV8kQ!oH{F9>8Fs@DYB z)HYXJ<<_st#Fr-bgu^l|_=M9MJ8a%E9=S|5t~T()SXBae*%v8CkL2yPplEVRZKHf% zD-&KmJ3(3E0oW=7vf2he;=2soE`YzX{R&?JWN1RMN2YU91zpQbb}3Ig>To|&ex(85 zQf8<=jt!dri@YT+fP=5$N5z*=*}Fk97h=Ab#WcqlgWlqt@s;^wwVk%4+7k&O=_Y)VSCX*l8P06b1%ka`R@P@OehfPCKhD5LmLR19Y>E z&ua&4FQc=2F0vty(&FtcU!`YI)ON@(cL~G15yow0M`pE=`Z*6dp~LGb4B*IGI=*GW z#z4^}gSpE2SUWaER!~H!V@}sh!g@qQKE-`R^29&~W#H-n(690rxasMvzE&ONBPm$$ zF`bof@Sq@hi*C6XDeViMeFHrGfmW?o^f8W(rVfJi4fzvfol)|bIZHqfQphG6&4;WQ zM9hbx>hD}rx!4%`T;tEwryk(?2+noEsZNJIt8C6EkG54>_i6=+Z{B7(i{@#b;Gvi9MMp2)Oh! z1s*3#`-#TFQ`*JHTOc2(0jmFt7BbirvMu$?SrXU;w!iSX#s=>D)ZvgHSA6}UhV`~o zO#0>t;Mh(50X#9P+uvJ*ISOYF`TMf#?>Dx=l}Tg)LeXX!(?owBYc*3YB@UyDSp(+pyetw`GO^5so!b#4w>q; zq_^-df$MUqyqdo--t%Adxh(DGTQ8Ks8yQ?b@d@BeWNi=hMIZS|>Mma?&>X)jn-UAT z6=!{_UOua!T+iWi$RTilhAw!8P)El{|IM=bac)xNCk3*N3}er>t2`Qe418@Sg=WX84Dzvug4xiB6@U z;eo3v~pEkwfbsx zb@395ietbAEwt29rpGT&RUF$%<#k&%GgXJ7%XUI$<(JOMJ!~HMV%t=Os!IbwKW)jo zdqYcyH8I(s$z}36V9^pg&$-ubOd1MS=@7Y9l-rZavz??bZIRxoydKLA-TL8;orZ>D zMWZMBRkv4khkANF}(q)58F%r zCo?5i`0O}wP1||3 zVwsYFWh4oY>YI|?Ho>lz0mG;q-F#|t+ya`mXae>Eup;H#s+VQ-jePG_1az1dd=ND; z@)|c)4(TR8@~O8Km|uy|70>#)k*iFifxaFarj>!Nl3PK_Zz3=NTcqr-v%Dq;jsWd! zYwT2^ia|d>CfT&wD{<>iQjw_zva5hA*_!%DR>m&12W7b;ly6z?f+Jr_n#HHC1J!~N z2RFLnb9)TOXEx2mNEI7>0cd8bjCsYzoRw$V%{mJ>+k{dVJm2Dy9n&@)aZrC(Y|MAzm9!CVX+0!Kk*5m zwu@DS2M2ZGD;x(-+Ec|2@`=7B6j`i)A{xI5;(;Gfx2Y#OaE7)Hl9c(jl*b%_Y{9tI z>TkIMCP5AM@+IJ`r;&A4lJ`(-0Lkvgq%w}vB%B7eq7_W9bP6-;j5i&ATujMPUiIt$ zOACp<&&7D)H-EK9K_s^xMV7{cFJ{0f3b1VXPL5Al83T%|%pdFE5CjbuuUz?*oG#NA!t)2x(W5h#_cY?j6)P?GudZDeYsRo&ehn)@GUBif z=(4%!vx;WGA>gu0Fs)f>zjWDq>5^EbfZQWH&d3)wNsbxej_^dNrX#~Wl1HR=-({y5 z-LO$4=M{*j;*XmrV|+T>(4Uh-ciAbORx%?lzJ$GPFB}#P^v4dYJnHdF1(sX(tAjpZ zRY_AneZ$*S0tbGVnMr}{s1Cf=#W>zHnSiq zC*3-VAsb|sSL>h;`5qIBtr$4+8fdj2_=T>Z_8Si}C6CEU^9y)99EE+nt06jtPfc$D zMOKYO;#lldXeNExkjXZ6)lTwQnN@j`mCr)XdZHy{ANoVxUQM2GO!(`B54O^60(GkY z2p63EVHx3x2a{ZX6%D%!(xj5b^>o2CJ|cX}%4)9Y$k>+s){UsV^rJetsXmcJ$WJ){ z^gDU^pwn+#n-4ppe`^48OsBmN16kzXIl0!jgZ%D)(wRWVQNVm_y3lkUVhGQ=`Jyux zs~`OAwoBu{->fw+Ly_NnDGsrk19x`c$wU z*va-vNZSb)+U75w(AL@GEL+_I%Sl~i65e$tBj$r&k<9$2O+?GMl?Q2PP!3Ia@*kf^ zc>0WW)d9akudCyM$DM)-$~IB%K5`ShUqXK3*ayk(^Kt&h)hdiw+8B(d?0b0Jartg> zr&YVhomTE1cU-Ys9JXYq_|@90g@Pg?jNmXp2tXZW&!NTfrX%hazYyLTYpzTqxsg>1 zxe%JOaQXU`Kfr8J+HS6HNoXrnwTG>?j3crswv36O)VqWm=XCq&PySq3-d`@6kD z@_H8@u9%otATlDa>5^0`LIkyW535S$ z^B~i*U025V=;r7V%~`Er=^lJkfTw?NH9Q!%TeZ?b_?pSdxO8+Z&f72&SB#Cv)jBrL zbi{huc=h;XTsb-szgM8&Jgv47m)!>YW0!%3{cp4A;#*uemN}}aU&>w{=ydIe$&{C! z0$(YeZ6Uj)i~{>#*0HSo=L~=&dL5d`z{9&W{v4>%1&0BLPK%fX-Q4z7nS9b)(58d0 zpug}`&vqcKwzCOzNgaIJ58YvK+f3Db0?$gjyIN<{G1DT7|0uncJ_5^QLAcI5kC+f* zQZ7B+r;rC3R761Z)9FlL%&-7_3*A;o|3xP71r?_$13((TVFD!mMK&K4tL>q34+2F8 zqbr|SC?DnCGJU}lG)*#%RqOf4w2RBcmj{;pAQ(+1AK_o(q)z2ZEcq)NV2iS#u^|M= z1|Iai%jYzZwK)jzmA}U_ZnbzI?~G#x*s_lJ313wkJTqe1SA6d1+8CWPx3+w?J9mlT zh3~V&*{S*-yBN=eAy(N(l<%l5C^9z37lY3Dbyo0sQf05$Z7AM$6%V07170M>4_lD- zIE5tWpljPmiGqjSD-Tb=;eoNSm(G?!KGfM@vc|0}m6x#(X{+C$t*^^rRxU$VUD0=- zCA#K8zcw$%6zVEpM{~TOt*DPL>+C)b&7_uq+@Kd30Lx3lc7hidAc;SLpY}rsrE*aK z-H*JsNz;QA1Dy=DvM~_s+>E$s=e7!Z%S`X;$+7s{Wf#U<&ikchhstf2tcb52|KND-IX{gL{^6WB zeA!O%mE#^5A5i-B=l)!6+f=vNFbMKrN8T;&yW6c}-!FYwm%=K_o`abd2^UsQPvWaK<*inse#n?!E@9ICB9y2c*$blcA z0c;!bXC}<l8qt$4a`|*)B`lavw|%+u7Tv ziHFL>=BVY%W03;&?Z|GkZoIow~XSteu*yOiRA}RVn=3jxHWJV4uowJ=Et( zz-HAwbN#4S)J%jdw&ZjtCWxYsXxFyqpKU0w_5=vJvK!^*B_6aHKr8^KxjnCP&-7&{ zV2f>hkEo(S7PeGAdU%(j@HUVq67FO*Pv?ahE+}eWniYNmkNx0sUGn@o3|nz@ZhXN% zK%R0LL<7G>SD@1HYFYVBI_R&%A8)~U4nPbdS)BJrb5&t^&{uSjo#CsFx0a6v)HQwZ zx{&%db@E?<>m}Gu3}%`zqzwta*h_U7jEdO4Wd*JS7bIHf3ddjvzznLdbG0>it}A+; zY?pSxT%2orvC^C2w{9t?%}ZU|#bcI$O8Arfg>MIVmyX>wd>(o55A|{C9lf|OUa_n% z4)5=Z#Q5i3vi zb!@m#bX@s*k8nTZT6r-vqn)rcvFcH@|`+(%uB+$B{sxqA}vKR+j z>iUW!SH*6>y4LvycX$fx!OnQ&?)~x7%f{lgwVFf~xfAe-qA-5vNnye}_Zp7-F6xQ<{&74mRQs4V zKxN^grK&&Qj8_men&r#)l$!Qog$Ko$JgdB2Fee*L3y*Kd40zI6FT z&Z`Y%8FhT};&bD}7yUXu^oQR#fG^cR{?#k~5byuP+0Og00^)}*;{4nA(1pKIuvGfu z-^OLUQwJ1!0R82@yY3b5JnHVTvTq>%AJO~ZCFdx>ZH^ZlaHLk&#~E)+^AwO5q3mP! zJ}3@Q;3lBi^fG#n2Cse%(huD4ka*y3ds_$YV|J-*NUKR2=gReKVoaU0SB}!hY)l^A zDW@8eP(yO`eyv`y-*fgqJYI9~9pZI|9TTs;?VaLv2j4MHy43-02s$`t8jwb#F)>u< zyaErcr`2H7VZ`fyuec;Wed$HfD_rC~Rkr-r>dQPxbo4ODs>5jD=+exPeO`X_qJCkC z6_ndT4z^gqa=*okV*jN}Vwiy%{j|b_*DUW@s}2vV@cX~W#;yYc8Ly!cbS<+2RKIZU zvg>lc=KG7QSI4=VM-+VJ=&W*t)7RUr6?%8<(cc?;4-YxW?xn$Hr7HB|XR;$5V?u+# zxtqomcsI&!opIMy!!e9YiAQyE`=WXr@~D>Dlny0ZX<%jGotw#L2mGyX?4Ww?7}WEK zp=;V)IIP}nSApLJWkAxwPtWnGP5<^Ppn#CvOMq841FH|whgR#L573Ub56A_!2)_CU z)X9=_p(R^(GN^P+#m@Wfp|fNpFsR+S?FJ3CzuT|o0fiTJd^ACRCcCj);UG@hhCoNORvMlZFu{D z7KYM1^v5GO`9;ZfJCN}X3~Jb3#~{qY$E z(Sv%+=&E+|L`n4Ssi6JbrG4?W(dl^0nsEi(u6WsUp1`O5=i=on`{I(R+4%2u8GKvq zWPfvG$!{flZcj^U_#3U*ss-|OJ@Lk_OoC7zdkELNCDZdUJwd;x@6fkJg@cI)ziTT! zVVPg#gNbNKS99W;G}-mK1y0^&AkII0MZ9j0K^2K3ehCyjn!xVBKL`@!Wvf-v^X)5o z<1@Q9WW)3W+re!nKqsYXPd*Uz%$@t=oQb(OZR2#x;x^w+c@T+*Yjz4YQ=)slyjqc>4#ftjTw>~(=HBdeHN1u;ZopVO~$N4{xe>vj|3RD+p zRkxEA*$N~^qrc(71{oW2z*iq~N3Vz&Xfc!@Xi+D8#_k8Go#~P)VRou>{?wuTgaE~$ zsdSGz=3-4kcurP^&!Jyo8ecVsJ5qp32!?Cv}Ci{H#V{w}=eKelR_DJ6;?$IjS0ZLB607i%F)C9xZB06vh-$UnGPP+fnp@@OL zICIm6*dShmi}D^60shb>LvifR%j0${m&QIz24mkvL$S|rf9$h(IBvIWQ5>(b!*pf> zVBb;@UOzbzzg)91rX<_`s?P+(!BuVHzzZHhv^HwdG1JNvzPDu$E;btaHFkHd(01|R)7u#H#T1*g(NPSY&l0FT@PcUPGon&hb@D7Y> zo2xJUqXtD92Ac8{KlLf~xIb9zP!ISuZBqeXRh{`M4Mr;CK`jSDuappCK|b2LUV8#E zI~_fZhIR9R?0VuO(a@`TAX_=8&$;d;x{#~&~P6#j_M+ZaAxR-@>j}J zHt7NjH6%8!deW}c1Mt-?pbXBOtG$gSAH&AJ*K2V^zuK*w>62`WcWSbzOv$0Niri;p zi>Iy_i2E#V#QUxujdyF+YM7q&&-Kl7s9!u~X(R44)D!PpHy-a|HCh3EMCVAY=2-2% z^s13KRRQD2W77)e(oc4Kj8@i1Xwv$oHDlfs!46x1uCaq{Dtphzl+BR-U<9MJ!WO=v zAWYwN8*-_Ga&|`Q{e1J}EEe8z!Et3vCZ^mc?eQWcf2KXrv{INTlYTQeE&x|T0%30ZxF$xyW;fd4#mSx@ZI(-H4jKkeUc z?(El!fQf@-I&N`q{P+;9a+hjBByq8r_0`46l5Je+|GGx>VeU3wI0%X_TdpxsrLVX) zZ&5aVPDxK#%QYsd=bGLEihbbiF+wad2m3-}!TyZ*j5N*w8KB}GgNx!LcX?2JY3^?){$>=biB6JU`$mqJ60O)1gG!4;%UZBhss3 z2h>?ND*#_5SwuK-#pAWVIV0}*gVSRMJm;sn@F&>R0q`4FUmC}1Qt`084iF3B$m{sI zv!~tWFhAh*gKI8#{cfRS1Ev&dJ}!Nocbi}ux6}7 z!t7_ISq38y+BBf(LgAWguhHPsL0 zFB?Wclc)m*d;m{IbTDbmL6(Xt)4>BvxpGn&2QsRjbwEdGps!Y-)0^3CLJsoMufWX- zYB|iW=z`8V(6bIq8Cdg}VnsL0ktjt6Zr9_>A27N>*5W6 z9>K)PM**104wD|p{&aLE?tRI|c*8a0S&KhzE3wmY+0Wf1p%WWIoah3Yu zkZ7?o{@&)P_=Z-;mJ8b;BleLEu?Y(gk1id;nHsk~3;MPM#scmqD#t%;FYH(S*$sgr z)XgB^AkE=C`o8iJLE{=@!*r;BusT*dacj%<68(CCAZ!V7J@sV+V~@Zxi9yP!v<&-` zO;d62-*1RF{&{n=kGe0D)O?gXMA&q(W45rn=R@DxxwJK+@$G4yaoNTV@x&i}F@}2^@xRBP7$@9n z-}vd8%i@E7I6Izu_IHFL#^ya2pS|?lIP$9>i2Ho|f8ze%`-}!+1u_k6ls$gmL*nWC z9~rC1H^+<4`fj}PSKp6U|N4jV%3uGWdA#a3KU6?dz*0c6(l*Sr{6dm?Wr$1>-&X+L zrGGF^JLdjz{6Zl6QqBn9y&YDNB( zJr0O_?zD&5+grRWc>HVzl--=a!$Pd{9nbV|9o*=J~|RtPi|I&YS3T>qdIlK z1dzL1_Bv+O&T-66J8M#;AX-rzLS=Z%d| z#ipxPC!Tu6QOlRbu38aaqRGS06^zeczacj9P|Jh@d5U9a;tD z2%3}wtSS_k=@b}3Ht`S6bAt&vM8`I@O@x5GbmpmfWWjoV-y6EZ_h83roEf|OPXX#y zFg+dWuyyj24|M4o{h$jv`fz9kFtwT2od~ExrjIJW;&c*NWoQBkPpbQ)^qS1gX^{5o zgxCXK3=qf<2^&|3q06t5h5C2;542@h`4z#7fvv{x4A`=d@p4eZ9-5jK1kph*<=5vM zCdVZYdv?;#&s?CRs`Eas-^rET@`-V1OGny^_ANmGMe}tL!k*g_rp zfzH@KfcRYI%afGR=JHFj!M7g@N#w!xv_yItgMKBP^qYj%bSpT>q7>~fFdo;=gZO-W zPd56Kf~r_F`wzR29Xz|C36gb`JkL?^T_zdcwr(a4>+6oUuIhrNW#ub>K(2cvV|D7z;W*V-)yW3K~IqKV5re z-2XeDiqHJ%T&-j-kNfSuUp#)F!{XfgJT)$P&~xMB2R~28bK;VRJTHEEzbD7X@A|+v zd+pU;x#9p%-uJM0-R+L^!&(#SY^VSE!ub5<7slr<`+a==vWt9t;qr^)^vf>Htyc6a z9RS7L*v^zZYM6Ik@T++1`M-<+v*(XcA9%faNyHAq>T(luMZX>=gI_Qq^ z-2IM>=N))dygr9?#nQka*#NN5;tq9ucq5^-hDs zF{stVPJP2#kg5m4%pFWJ(*ZbhPfT#N0__9N_-g#;1!qb9+*&^Q>@(ugzxZy9;WPwM z0ruhkEcRh=VTCxX!FWi=pxSSkZC30j4Oy`iso9+19~>y$( z^yR2KH2Imu(nk8ncC=rW!@ejj@G85|aK~p55Diu)u%(@_A-gmHGMQe>fxW#N2B0O` zO%uIz=JmchCXi3w7Doly+crl}>N>Bryd_;|SHpU>Wob*>7aM?=ftPhbX@?d%DNnf& zq<8BePhu=usj-0Z2l-_q>|~!7O-~>yt#aL2OHe9PY-;b6y3cHTun|IrG-+@Jlqg7vL5 zSwB%L);&wVg|2E8IZepN{g*W2PD9;sswO&om*5CZVji|M7kc(DogcBJJMJ*h6+hFe z`rogabO1l5zcb#qlg2Ro(zFFSH-WTuu(%0aW4Pku3$Sh5DU-SWxrd$I!?dzf;e%H z{o=8=J}BKxd0oKITajn3CBC^>nZc z!Z`z-5>nJp%hKkWa86Rcw14_Sinnl3hog$k1c0_neJ!I?N+UZ*(~tt`!Bv%q7R!8{ zb-2%>s|ORZluwq<-nQd93-SyYrz199yH0X9L^FkBHAp&tAVFf|w_;>ss@ z__8yZgW4jixZ%9CnCYZRi zu#7cN3E~q>WGfzyB0{`{d&Jn5a-*pnX6%&E-tylL9!zC1BR_xc&V6y=#B7|pW>Teg zXw^!YESu;&d3kUAUO4~q$FX>?=)dI3iTLo^DXp?Q<9-U*mZkJBA6YpMC*@Z@@_Gv> zS+GreVQ1Py?aEmVnWoYJmX2R*(4k~3o!@zfd&lz+I6BT;b9MatZ-1y&+(7Iyut)*< z_wm_F&x>0PEr}J4f%xwWS+R+rW=^c0KYGb;erh6Kd)QqR7+1%q|MdIllap!4b3GcG6(n1O zYQyN!EDQb|03X?4@bj;JsMGZK^_%{G9&voUX8$|H0gIQ3Z&x(r42#u~cAo8yA<3~! z1H-UvzCf72(AJ>L);KE&xr#cW>T@5Rhl0RWy%7yCdWWN!mS*ajjwJY+? z&uD;AKjDWr=xCeQuQlJ?CQscBl6lhzKW@Jec=gAJ)6z}kQ)V5!N9YxHx%QwWn#Pq; zCdPQkMrEo;;Pt!SlC8;4;IxkH!+CaK!%3i(XvS-;SLN#A`_l|ybwJap=Mj5uLfrDX4DSVKiuT}!}s+c9T?E7T`UjnURDU^578n|W(rq;Ux4qr z26z-cMX!Z(87H8}P@=l7mBlSIN*(f@N4g+6za@=K*tOL4S@~SCbI`F_NR zO_sRTL%^o3Ss@nDfK*F=jEbBsg#xz$c2U0D4H=s_DZg!o{dGtaT`QlRD8@v51^PO2 z{7Brbq0+2;@U_Dj@B_UM_90~Bak{igo-)O>tv zMAssDJ62=Z8Q@gpM5pv;Y)Bi6bLoqK%S*Rvh1$1hg9_xNqsVcp1CR$Cym&=CV9))0 zKau<&UVUkN{13lYK)ypfc&`KFar+z|&pF_ZTIHUnmF4+1_?(>LPn*`otIz(v#KLnl zSZFl7K&#J-6f7^=ye=->cx_y`;hOlvDAyaZW0gij$nI{QfM&~6g6e2!1@-*>Zts<} z2U0Zff8(VEpS2k)#Byyq2w2K)Pp%@mYHpZQjo9CMKE9UFc=PP_QGmhU5XzMm#7 zcT>X-Yw|iNeH5f|-npq5C|lbNwoYW5Zf0s??2lZCI|DypzhrgB&VT|aeqVohfwDhd zdAY{?4sYikwOA|8Wvl!+PdBSH97#CdswSv$?WiImI^zI1ODEgJZda9C2k>+)o8icA z4>~;{IW3y&5rtv5oP=YgWhk z>(|7tLxZv3(j~SvlLPZ&1&5rlkPUX)a}UvF0+2x!e`6KIDv^7|&4EI442f0`4_K+A z`JpjDZARx|aI7E4_aH$L6TGrgGs8+tK(d3cYEf5m`GFPeL5D8tQ>{Ls+yv}s*|(;b zvgQD#GSOl6JZ%}W9DJ`{nKVgXR^(OI+OYv?;Is2GWxFsmF`+RO-b|vPO&tbk%RxJH z-NZ?~SP3#nHCMqJVAScX0y)c7E@)tM1i&>E|Kyo~ z0$zC+ux|(khlYg14$ze3s{Tp;(&!-j)8(2#na0-_?>rc1X%+j4HDmF*-G+T1_$8N) z#4k5b$E$W3jD3+!GL4UDVg`HbI;A|d=cF?DA{5UZG6ZUz?ciNm?&TL?BH;1M=8+AQ zXigZIl(SogtWCS2e;%D01Z{3~r|(G)#$VF`M|HhZg6Txp?GC6c?N7QM`cvzvkfp|r z1MDa{rO$(xbZeZ8_?M+UaprA? zI^w>I)u+Y(KX&bpGxuKAW1T zKkp~;jVmvXw;X==c->)ljqkAvhC0^2k9J3QWW*zwWd+ix7!teB5?dx1Y~ zS})!-wrwJRQCh)^_jCb*JnyUW3p3xi{NlL#*FPEOZqy1(Fy&cHf*nqSBOm%;s~4Z; zSDm;QJkZx?y1YBwt4@jC9lZ7LkJF>^_#d1e@BH1*;=m=#edF)=K@JoV_; za3;qUd^=-&d{iApl{F|2@NkwK0Vn8@zP$`QC?N1_Fs1W1+eY@7Qx-ph&_|FaxcBw= zsp%QD72CMD2*7<-zh33RF~2b8Z6w(?SAm*jNGI-R0yqc*7J00KeLoOka6$oJ^qQ6q z_zL%GSIfS?-Lx?-8y)j~RXb#vRJenxv#;%M#5o%`#p>~V*U_t44$;7eYe!yv*`+>r z%eM9dsXzyu80W_%u!F7>H8yYFobp4X(D49+3<7VzN{r?Tqy-l|{L;1rpv$C+Nfvg% z{+Nr-$pInI%6SWbMxo=sN}F~ouJX(mAfL3(&Gyv6Yey*?9UR>gzuF=10}Co0`3_H| zZZXIU*tG!{yWyK6kQ0YOa|^u*R29?)%~f}iy~?)50Tel{BQ$H7+fijYd)pjntyjSK z_6^EPCP2F4(*_0XyM;#DEPWNQ#DxxRh057yth@sX?d|)SqCtJ^YZ>5Cb+sj-8eQ>*D>u39)=Gb6{X~3b&3OFV z$%lXtmaJn~it#E-7MJbt$^-%aK<7kDCWPoq0# z6yz+ajMXitBpkD?!YdyNLkda?e$qXIw;WA_7u&83ei#d9QJfSk9BtO80BF z+|Y3=V9gu0uUk74d#WEvw5r797 z0}1h%8I#WZsJLkJkFyUz^{|7t-~en_fC1Bb;3H~7eBSre9Oz|N@lXE}vL`7Eb$M{E zM}glmaM1VUqqKoV7nmpQ+U)c?}XYk!NB(0p{r2?D&xm+|| zUi2sEaT6+E#mg0xq4+`C4|ebu4ws7Z;9F+O*g96eX{sL2C+sYHKY3+;d|};0{A?s| z#auEm6UPkq#KBszJx*m)qJ7So{FZ=1bNB!UHdQg>KYDY}PJU|v zu#M*9gKH)od_Qn4pW#)zDO=QK0(fLQ!iRQf-BH*Ql4i9taK7aK!n&zE)=tLhYbKN@ z-2~_HN|FLJ6X^euZU5;{qjBm6*;o92BHcf~c{&f-{PfZ3xJvRt`SeXw@l(;|+w~t? zH>rtHXMA*4-Yi=EQ#-p~Vd)T=>r?eYB;;H)`Av<=&sS14i3@(n>-0rUN9`?h}#bbW>1vC24F(<@#?r~DQc>g>2)gBL&2vZD++!u{(jEgp{jSDxdi9d{P zh~4{!;=skr<8~`{^(#mdG*}&ETA_^4LQpS|Lu_|LP?@L&i(R%}yK6As>d z%95dCaYF%Eax9+jij^X;XrNz#IAV$98rIC3$y`5!wD|D!Bpc1g4la-VhKAKiW@GcF zkyz2uux@OP;|X>#$?1Vb0k?NR1Ew&#d1;-mXldYW4Dg_ecjzkj%C{ z10z0x9fi+UIQN9>EZIM-uYY0}hc90odp7!FCk5I=RxByB5eFKg%}QFlNat2_wiU8g zo|^usxh0zh>)DC%Si18nQKv}tJt-3|bVbV#u8B4ew}_hmc>qQQIh}PxL)3%ui37-`%_Uhyt(5)c6X`VvI zhm`hKH1>pN;h$*G6J44>?co7I_Vc7zP~T_9M`rj9?&5RU185!4Ota84aJJIrx-tfBwZSRjHGjrAJ%3gSB{uWMhuybbYI1>m%yXd5l@j1OHy+Uzeus}0>h-A4uFm3E_F z`jD;gSASxXezz^ClNJ8cp2#AZ-lCQ8e@ONrT|a3>BkrIH(P`_ZQ~;m z)KMRxYsy*i1l!8n8tjjfrz1Z2${am~jNwp-^J@<~UO}ikUh%8%#VLm$7mwNJu=v5% ze~M52@tpY1m6sI!bmS9u-!C4!&!KU|@}1-1KRi8tweD&Uungd@z3s7ak6rhUqrdi1 zH&hBX8*e(|?(yI~4~T~H_)69ze*DF_Oe^II>1_G5=gd?N+T^jNIzzC*31omFW^Ize zL$Dfx9}vJWy~?-apbw3&2Iv;A95Ck`XdA-Apxo+Y00&e%x<$|{V0Z|q&o%ONsYCX1 zpH$cULeP;bca1mS;h*D0zxa0i{Msu#(6a5v`go|T5tFPO(OC`5FWC5i83>EVG> z{5sVm`1!u!hGxSP{IZbpH7S_m{m!o5*rZkT?RuBSVY}`e>qbZ8=Nqn7AL)pD?zCsD zot%uXZ@5BNU2)`+m6EtC&RV}lYR<-yOLvMst+IZ$`U)+;X5*;kt0cr+oVjj|R;7GF zDSKLO*UAtAwqiYz;a;lb_VSGZK2fIG*) zM-(%@c#GvMMfHG;3wac|C7d@^6CY(^)XaV3S8d9R#(yM46K%sA_ zdCRn{x^9gaON9JQ?26npUB zfZo_vcCiiAU+LRTTA(V;QR!Ac01|HjYFW*v8{zYep`MsYe&64xdo}fiiOTe!uI|@W z;YGR1ysCvRBuy8uXlefng}3WMPCOjTs4D-<`~_YF9LwGkvuQ6)Lalk5wf1KY00mW5m#sMX`Bp zc`c^k=hNm%!-EHbnz~9;msx!Cfy6@J0gv)>nL0{l23A<93|vo4X^NdkLiT8- zSA!j$(sWdezS2MIOP&f$5DLvo!8jG5i~IyO4%*ZD2w#WWw+TwiShl-qlrr!IHi2}n zGBg(!E?>|!>F7)naGt6(=>j%zo|}1smSi)(*yiM2^A*LnUYTR6?WVTZpe5Ql)-h4x zTUNAZr7gGCsmM!ikL0L3L-auhljP+Uul+N5`hi%1<(6&uIl`asIS>b2JR%dM4H-A9 ztj)m9fxR<#!F_7m7D$;hZ^1STP|E<9>(cs=?VS<&gwn--Ezd#~>7qYkPCoUyhwjNb zxFYBBrkq;=yvMM=S@6 zpe9ZuxDgXPIJ==(sVoRw4NC(u$a>|<3=G4V4rJsUMQI-r4kH3KN^wkY8vzB!{3(~X zHDEGO@msVGz@o{QOxT8=7CHk8I{Oon(6?VCFCLmAa1w}3M}s1(WoUB^I8ck0uZyn9 zvkaih<_GR;VD+k3JP9Z&^)EtA%uGfvpYl_K&Coea8yV8k>naaEY)da49F9$sqjuuW z>J;4j?NR+H=?5+`qf`CqpjA7^sO=6FnG8fo@Ik{NAhN0MH4O z_4E(M%*3QD$BqiY1)<#2jO%tq*3uUCA=xfpJQ*u8Rww}i z->kSz!a$*AaPU8XH^&y44mPr5B*(LgF9X=!y~P@xp6(UM91xEBX}MLbz!cPjDcv zJjB|PxS?tP7I}dtjgoIW>7Nq%uN0GB0F~vdlrtV@;48wp4lsr$Wd?f(2;2SK5N(tIEUwOpBj*bt!IBnquQ^JFyz)TrD*{+eo zG@6P?`(4}GY0J#fgt1_WsGd=n1`V9du~1WyGGpVu5gi6P%r5-9mGbPm0(ED?XaWKW zAI88yGpf&5Ub=`(a$p|5AvpJ_UZZT$Y4{KSJOmFKsk4x`SS!YH1qWzX+3NjcjwucH z%xuuJTQ>1ir3DZE%ui|u9}s_n1F!DPU?&=1vQ-^7Jh`tV90G5z zf<;68MtH?QFwoZ@;5B(<5flewO;~iSnPj|!ww{_Vbo|=m_yc#5YUR@fK z^Sdu#chT+=&)eR5pZN8~7e-G{zwcA4ab(v4$)nY*ER(O|$SD+syL+{&mApLMgRaoE z)2XwwMLRpm1692q$oQ0=+t&^zBP+;23%{{b@_=2hX6)iA=~O0~JP|Fqn6mct4CIs5 zxS#&`>sWjzShiaT25JqH=kuBvRat5?Ar!ekon0DutXm2FZ1A2 zPwC%+Ed~zZpr?IW`Bi7Yyv$E|b0t6!obVU;Ve10T_6C0|KqiCgJD+0y>B<&`#0t6lF17Kfh;PaZS!MfI>zX8@)qe%bI zK#Y%U7H`?oy3p5)E<8}fjEIweEgce>-@qGdGK%gypqs!w*1hd zqcdgT`pu#G#b=s_Rhbl8bi!-EEcIe|fG&tiC5j{#j6%r+SC@KYXR;hV=6 zUNqP`_?7VFhHxqpiu)=5DL+4|(jKeGsN9{G z=F}k4Eq$g`)-i*lNLJacyMJ*^^Zj534_S6#cqqm+Iq`}|fyEZ1V`+ln;D}9RJ7(BS z5N7zA6ss$tU^71f%9E2U4KOGDSnW1zFIAA#KTqEL9+h~qXCzz5DzXvNCFSc13KCL2 z#8~gVIuw%6cKeLvee z%Xw%h{Vsig$&SB##EM$Zl4B|chcCecTbE$1JSH$X2tuF!qE$Wph-Vy-OA`z}kvYi( zM|Sc&2{DB0T`EtCQdb9)GLg#%Dd{(o%Wb4{(UXaF$3Ra`oTwxGI#79VqJEV}`JA@~ z+egYJu4o8a@oWpqY^wyWIgD+aQ8#UBUBPYADCOiqOY#F;du(W-i>`|o567lWoBd>O z1?uas)4m@kupLU>X7DT5t@-`{Oqpz_Op+77v+Z@#Bzabo#18cl>iIX%xecRs%@weG zfbY-)jAO!qiqBOq0H072dwTNV{OZ&d(!K`CmUdHG2M>@+u_hnl%eL`kK>AvD`-%QbU(2d-U<<(EMHdaKj>zj^tX#&f zStj^Qq;$m?&t9YZw7_}^++5jtt^>_o8BNey{oDA($8oD>b#z8-aPV*GkYk9LRC^eZ zx@HN21|l1ui4n}41}tW8@L#Qa+U+p;OWfJSGfmFga!{s;rDmD6hv9UP)E2b zXKR-2Z|>vrFawRmzCEuQb>o;ixCcC-a$d<{6)DPoeTR4GdmBAk*)pc({a}j(QyH|u zM<{geZSYWzR+i$)$3_+@08LD)GeXBfT@Iss2G?Fz>Nu=Ou*xR*63C~8KQqfp7T3yE zG7}ckJkhIWup{=VKC(6B%rojdtQ@(oieRIg$D&WG8MY*c7A=aYDFtR`p)!cO1;S$- z$qeuL3GuWIA~tA7Jfs!pgz6r$=iae)-C8*#WztW!$JSo8Lqa<7;rL$3uyWDj*fgp^ zthS?M#A*2|7j2R6g)#|31Mzmds14ZO6@A~=6`mVXP0Qo>d3%87VkZIQ|O9voCxbQ`?4Y&oH>V%H-e>0}>;KLI-@c*suh zsvXK5Q+D)wlx6j9A5*qzNVl{WKw1a9R2N+PGkG`ybX0eXfzmRI4|akeB3V{+hKB}0 z5*&C5b+HHZWFID^(Dg*e!A|++DGf{P61p+jw88`B5X`57`( zP0_cEg`Or#E`^rzC}Lo4eFh^d3Ot~tEIDz44|vqggHq>nja*#9-=zGf&;f_61wKuF z2J8S|>UPD1{GSTg*me_X)8Jgw$Y~M;1I95fbeNqB5`>ozQk8z-x+yPqBFI+1_4m<* z>;B*!82yKT30S%PtZTIk&oWf~^SK6)ISE(SRUA8wd8Y2p2WV$q*oW|Idxp~R0-RTM z6hIeTrzNk^N+EQl1GE{##6@{CF}Q3vCV928L*t<8rk@IEMPpqSFxpHm|y#J-g# zd!>xo52$SlFp~Y8rcC36ZOJ9M@M`r_vWzVm3*G`?2l7nKb@A~cN%H61QtqU6DQVXs zj_3l}0Mjf$mJiAkhjA~{!Xs^abGcB@q@c||^vLW|Z=pPeNXRGukzZ{jKj`Xs;GIu# z2{;(FRY(iLrI^Z)gG(1_l{H?)#?_NOK#R|a6otGWa~>QdHV=BU*z9mK&}bdUq5WkNAD zIHW+!dwr@W2UK@;J+`XZ@}aYPOIP|14h|}yO`9)IDkBSnuzyaFL0&R2At6xk-fpjE zzWqIo7!#cd%{G>F^u^|m>F5}rjIrs-=+Wx5+rbubI=o}R!*H`xl11`P$);md6MokZ z^Y{0&4LntvL33aoE0!;hajkGY1C%}Ew#K7Rv8@(f3e@Hyq7I*X7=C`1cj3Um|~?# zP|tSBx=BxI^ajE5QkLZl+gOw<&(5mD$RtIHy3mqu_@gDIAHmDzrpI-j9U#zEGC2<$ z=xiB5ArJbN$E}dEH|0R))W`2NE3cbRdr~f19vfV%oS^4)IRnNq)pEv$D(8RUzc6vg zBe4r@=PAc$u1U}?EqS1p1DTxV>eAKyRv8d=eg3DdyiKZWd{t8DT7jmA$)w6@51r|M z_#vZ3+F$6hz01{UyxYL*NZ?rj{f8cOb!3}n9{!gX;F3glUz&Ea60$o*x=bSJi*Prx zN;n&67bcxZCcrL+LWF!DDWmg+Gd-r&wB(=TIS%|JyK5fiqm#M;@x1v!)^z%M(gQ7N z(%U+DXwvdhPDTA^QiD#=GM-N8$zX#l(6Rl{H0kiaRky6nIU&nZfBR=PwEnBzq1UXd zGkk0|{SjW%#HN|?Dbb;yysEz&O1URd@UzcX+jCBS(q7kSPkyOw6#h^jn&7I`GE&Aa z!3js7A}h)GY-ePJ1qJ}p>k!H`A8JQoQ_)`rM~JB$Cr1QtOM`>qS=}g)LCbE%}@>{ z@f-bzF`voIUlf{+{SP@X)~;En=9Sx)SDYN!WP95bf0Pd9?~boBNEUwPD;`W{Y!q}Q zLB40(~1Bkx&2=1(dFGGijQ79J5Ckc8zz^!?J=}A86>i|g> z`Jgqg)5jN}RCYTRj4G4!Dx|(Hk{@|M)`c<5muvhX^Rpc(Q*T5@I=lTv_5?>Zn&*T0 z*wuO1UG%6fo6~NdRJuJ}EqPa7<}dEnS9w)bY|S{rnS4NX9N=|i8(|;lC~aLJDICTf zkH2PQJfF)c>v553In@GR0plEGeanOoj&O@_9>1a;1@LpQs{RLZxkeAlG@cuWMS^r> zjAcAa-tt}gaH(fIkwzBlqk{>YE^SATr_@ziXV^PkaH*M4Q%!_&5|Ii`6#1p}c5RAn zPbDQQq5bl5kHkk>^X;(~!{g|08uHdW?&-GrKSnZRX?dB`Ywy6)3=}{h>(s-WJ5T3?v>HFmkREJYeXYWH6MB zVnyIhr5q&Dy<2Bik+V1)GHKS;B^epq`K~g<6?Y?;rLtv=d@SaQnj$5d(B)wu=t(su zcz$h32Z6`;fklsC&Fz~(1*XNr!?9t*#^}=^*`rF(W1AScxksEUSn-k!zMtVOWLCiW z$`B88(fHELe6lUjRl!2|_8AS_OaPFF_pAYAotzx^iq|$oXvxL4H~0N|#D|Bt1_lRW zYDO!+Idow1B2=CIM7Lzhx+1K$;Kv?%dwQdX)sO7UgI7p4h=Z#0^-14`@Rl_CWApfE z@H^DQi-x_E!1M*9G4S}EQ{guQgWFVm7<|MEKd4(*tON+O+~;i$D%8_JCov7I$Tp3P zXeEnJVPn;&KrFr5#MhMJKxUhjpeFg!C0WtuBuE7qPv@>^@XcuMH%Z65H;l14i#}!{ z&8s;$zcPF;nl@J2lR#*rkiY{;O63Vv>P}g6jBc7dc!vmaILpuB=k_Q%L&Ga$p`;B+a7%>=cGl0q z22ed@(_!`8c}p25lt+Jt2K}{DOnBw9KFMx+ zW)1-XdAjg8EZIG&mOa3={S?fwTV-RLxEg;sXB*}fKtm){9E+#4uTi?BttWZ(35jG> z>?j~x7fzrLV-8iD{h^-AsU@K#w$L^GQT}7Qp^GIgW#Ie7bzVB(Wdh@K(0`CgaWo#n z7BoaMgR^JbIyCfob)p`vd{v~lDC-kS7Q>naMAb>VlXUwMz$oQ7ub0rYqr$~nVB%i9R+hCSIcQ23w*x>fbz0J8fc<)J+4HYh-ji;S%?#XX5hH zPW2<9dU6CEq0YKuF0TXBopLFQ-ypVnmKkyUyp~?jUq_fFXEEt8r(Y%z6tuOsT8C~%JwxB7ZRA(DM)AlhNfCSR3GM7#w0*Ng5)|eFF zySK9h#)bwzFxa**exgF(t7ir={o@yhcu2_KgjIQE&yNyxX+Z82Ui~#DKDy{YE#7{5 zR2*RI!OLg6i_dlvrK!gcEAXywK1G`Jc~XxRA_18OC&;pG+@Z$AKlot-K1Hec<`tJe zN7+!2v)uTuLcQv7-qTbqU@Ml{qsYgKWh-LihV|w_ zP-hFD6)!gSd(JXSHaCB5QS()??54AL^W-%5mC5f@!LCbJ#3t_XYKF_hVXPi`Xp2YZ zCe>Mb2m0JDJb%1?bdy#`3MOik`SHmZ7Z2Ve?vcDqpzO}2bNS0u7+N?q7MtySNrxxL zgNX<$W$5!j850VANP(>;*~L2#8WFv1Dms1$%8k|)OP4SAPxB2dT`K#sa-he7CL6-r|yc^bGFrW1X?hqtV;n@I(d!;vW)969@)Uf$ZZ|C-pef0bKwc(Gx6Q zy2L+En0$nyvj-^f75J9#yi<%%D98iuAVSiS!Hf#_gv@iAOXSZaJjE=_beIqAsx*{R zmqOaTzS1UBRqX3Y+gtSF1t}(*1Qx#@rVMlTA1dlEALyqovPR~E2W@1KZOpG847ND{ zz%s~Lm7qlV*qk~_Ll;CHNlJe$X7Lj@93|j`hitmUMgS)=kg2wli>M14^b@E0 zNcCaUhAfL6{Hp+Zn;vKC3CZ?AU-%Y!=CuVtj6y*<83?4lrcU+^Iv0JAIu0lfVirbc zc+y(zCKcYUEqSpevcj9bj}KA@Jj!*Z$nud=xzG4LbjsPgJJgdu<#RUD(iH^=%LO#Z zv(7~Zx5rlCVoJ+JO3J&>P3jD2MLF}`1{cADlZYQj-2sDgBQ9^msftkPZ~qC2HD znEZk*CwFM!Gr^HUsT&Lmf0%=By2AHED_wnOR8v9ME)a?!P3bi#0ty0B1f>N;M5zi= zqzfWg=v_iWld6Ce5$OUVf&}R`383`ed*~e!YC;l{8{hY?Z+-XX$E>WAoH;Xl&pdnY zXXfm=E>=yKoA_m_Z{u-(=gl;KbC$Rydlz>)T&squqsVfWpHJ_dpmohF8dI9O0TI8z z-Rp_Objcib$r}p|*>rb5nCyOs`sF!NQC)a&=Q=p)62e7w-uoWFs_@dqP+>ovK>)%6 z+H-aGaPyv`kF`1Yt#txZwe#VF$=wY-AA!i$R7Iz-PC!>t;Q~Fa0kF_}zEGO_W@gV@ z$?Ut#)rrU7;@GY{RIU=Yyx|rvclH#2%T1)&ilM@d zds8Gr;+joe9(9-dd{sCelwvd-Eld6qxdlpbCxkv01aBc|G)i7smkeTvOouq73k9W{ z%K`m)`p&kZGV8&%QqDz5b(C9vP`*QG2Wy_10tOLO!@h$Jze~}Nd$uS>gb-w|=T&3z&jYP=3Gb{w3{BYtAVjA^p|k>>|> zwYKr$V_GNV3=J#xC-p)_b|^VmI004G7|vlT+dEtv(c3 z$+mi7x^jB5PJoRa>{jI5NEaj-Iu4&wau>GrZ1BjRkJY`%`j+pfUOG*8a@|EW*d#pR z5sx_a!oEa|k?8}MBrY+sT|_Tsx5u@Pnqzc|+xJ(>Z@7+l{OqN!# z5bww@Za@_Z9(1(u+7NB1Bh>FO_Y~QB_T|7KvtJWX;BOrFt;6)wd#zpADFA znVOP@gGckCcBAcab}0i3zmrJnyN;R{1y{ed$0m3b4}IR;fWAcVuw(pfS(CdKF=*xRc@AAB|kL$}8ufDx#n=bH|p}NFlz9F1` zvN}vK1!|doYIJ48=^$B0u0%uRTRB7EZDTWVA-e=dMgI_g<<_)(#Pzjgy4!BL#~gQA zUvqxd+XUfsWDaeM*CTW_-hF~89QUOG@(fEXV00ZT%-Puz)WnorW>ol?v#0at6X#%=B zuEWc34!IeCBU8%snsy|CH~VY75hUl`Tmad$9L<+EiF}< z)|jZ6ySO9Y$=fl4BRR;)>>me0Wm5TWPeo$*Y=VBdSd|(uYuqm4k`H-W@2k4L4mOLk zevf0b?%`z>F5x|lQ>ypY?6?Z)8H^}=%~56op))Lcc3Yku!_FyX7=FR!aQd`HMpD;9 zB~w{poaUm!-7wExgXKYOTUr%y26*~zE3Lw6yY*Y!h-hXtX6>YFkFkEI9mz%U=R&V) z_XL<^rd;R=+q-FnmR^HA;k*2Sn3TZ9#jY~Qg)ZW^v1_wEw(4>UZRtfSPDT8IAAb3= zn2$N@f3}C)%&0m9e|&RwXmZq(y%_b}1_Di&US5LdxIRQYIaq1GdUsRLz5A8R3wvvC z=@rvYLJGgJn2}AO>mPAM0i(1E2aB9Qv|ylvO~`m!j}y0H{-#{%cu&?F*I=^}l^ok0 zd;hX(4}5N=tH2}o#tZr00x|d2#?xLIQ+9v$4D>vZ?x_GmI*|a)$&|k%TLuhjX{Ils zMH(Sm8Va3ku$9=hm3HQ2OOY;*v2jGtSm)6jHA$b4d^t;b*D8MQ z^TtNbVL_a8Q3dqtB@h4g6QJH~@Allcd>{ClKA$7RR)v*-=>Fo$W(+^A^Zb+ur%uEAR<@ly4W6 z7NI`sZ?nwff>a*NawJj30f1S&r{vFF{r&;=Wm%=rjg)=9TjTU2pA;&e65?=ePs*Qf z=unZK2DN|W@M~zh^98YO7LxmBo2h?KX^JD>TskrKlVQ(wwU3#;RI5&JeG*>KFTQ_i zIRxZ?0zHbfdN@5fSCyh0HpW5b1}?4Anrp<7FCpK5h371O{hKiTT_;*RY0^MO^J31< zR7Zk;5s@e_<6Bs=IB5f$M)n$$eVi1^H>RX%_TY%h(e>2{Vjmfz7 zI**HOAARp)Y|HEK2hvTXb_;b{E8>y)51gE*q*$hW29kTR@a^-xCj^2Cm_Z;< z#&aYHhuYe+nDJw^?v$ByB3oc_Cu6|x~ zxSZGXQ@i9xI=)9dl;_>%j#fwBJzszIv^OKmt%gC6TMwOR5Jy=Y08;~R{F_Y4TW4X0 zRazRuYZ&dDZQo_LKNybuuxU3jxplF~cKl_-JtO|tPl4dQeqh89)c9>iT&a4~Y87Tf z;`Ub41jDzafUhQ-$=UvO!S&pXO!YTUvL>DcKDw2C!*oW`&o&G!n(j@!L})nw;Ue}{ z&tv5t(B|QF4#n*$Xhphs-rZIUC7~fm+j~;VL}**hc?`hX`n#UB1Tmv3Hiio>85hx; z{yOb;n8G4xN_}T#t9%pwl`O`<~$8kxGHPWrvo0M{` zmkw9@qVNN0x%sACQod+})A~lc7k}}mPI-}{9;=^bnb)dutd$L|bp3~pvaDEO&0pd< z+E-66<>5H;0o9LP=CE)V!{AV@J1nnRMd)7M4y=fK-qG$~#lU*L;=;hFNy}R+g@3j; z{P^l%+yu~r9cL9Ms#e0IgMQXLs;8b)6r<=e7B+~>SPAJhWxw6vP znNB)@cFQ%x@4G3Fggc!$3x*+jT>2PySH8Mrwk^jiqHOz1sp;Ar>OGrFUfR@Q74W_YIABMy`YQ=VMChE#QaRVUGcA^4En_95Ko=9w1_l~C$ro$C-x%u9Ao z+PUMZ46ZS5wgo(PxTm!dk>TR# zctZnoEb{8?fbZDr;zcvx9*27n|Cj#M*ElkCfaeD_rf_af)GcGJM0y`^D=zPG9j1>e z;n)3WwcTf%jW!x<*v_+P+f~?IR(A0hDDj`G#WE3mj~Nt`+xjL!BcRdryS^;?X_-;F zFYs=^gG&+z+&SoCm&D>79c@zXZeLixy(O->>LyYXM&vxq3V|&sJ+yN7u)T|Es?F<} z>bw4pD)k01D74XJRy@=?vCmTlsM)Vlo0-C;OZD~b@)DA=lui0lSzY*M>TS>WGGRZ* zR|ZKUw24=Bw{1g7SHGMBl6q>+u3Q(y=f5w250npPyis_FvLI{#tWF*ZbA7d5OFc{V zUIRqFxGs?KVt%b-kl4HW#NC$1g|I8}P@yU3L8Ywt#68ka<#Rq4$zT)!6jt zSm7*)9c*Y^0=^#R5G~VSvT7%lla0VB7L7MXUJlWSzB`2~R(YzUG=8Z&>-s}`gHoUK z-%$FazwuE{CJJUA=UXbd;O{MeQ?&HLI0dfC1H}&7UBAiYX|B7uW`xP_+-OW;dhIlF zK}F3?y(p}#*tj(%xR5D|G*PXh!xk3b8b5xiBdKh8UFpw$$kjfh4wZ%Jy{953l#RG5 z2q!5z%F!fH-u=fS8vxmdf-7W!~aOV9D-Tgh~+@JuiwNo+fm-zNJ9cStTj@krO4W0D5sjOzFIx@b33-sj1?N_aAUMG(RIX3*rY1=pn$jU)m(Uq1U50K z7Ri?TyNVWS_);y!#Y_}YMx}3~7^J!c=A_P6iW0xmPi@AU_cA^KGCbDvbz|PvnCpYF zAL&NDAJ$)|z*u9@C^^zDFxiR}V>qwta0T*O7i$oY1SagVF$N8^>O{Ns6-LEI?*|LE$cJbjIzZhOk=!xI3k zt|-5i%e%q*kfzc=6OQD#(7oK793XVvk4I#-ZNE?nL(7nhTvy~?1l#toUPOQ7rhg6E zw&Wc49~uD>fX|4cxd)drx4#lrURv#%UkTxsC#@WYIcq}})BJ0J4!&U)3lC%__3$?Z zlXq8B`<-LESWSMj7|CE-cAx4+%YO;|@xlL!#uo{?57=s{diDpb(>fM+pL4|lA4vID z0k=PB7Tz67SHoXU0eIdpvGx6>^aD~&r^wZt5_Zge_Uk=oH(AP--aM_8&cPu_nnRQS zdUbN0{4ed+lC5{PBClK;-{iWe zMjFb4BZ2c6BUS1IE36j!BGE+`grBPtf!Q}5Gzx>`EeJiQ2)sE37YBF~73vD(& z#5whk*GQ3XKk(oRyVV7L;XsVb z0>j@cfygomZZ>IeA-GG{thl6>%k=v_fAL#WS*&oA`b_bT^T=b*LCp_?#;WWb?6C~< zdOoV5gx&MF7=^Oq+o8X$Y?W{CPa>w|(H!LRB@KMBh<1x$`sH%94IzMSLLou<{$s#=j@ zu_9N4Un4hxys3sR_a9{2ftyDrrf)ho&55^v|5<$MoD>FMD-4<6|5oI41iuL}mL^n= zQ1)g!FH@tJirVFF=QGC9r^pzZU_45m5(R9G%64B+mgk=anAb*gDrRPz6qv?Dearh?2@s|@#W1ec5=diN~BedM!0yJ6iY&kq(f zHa?z{z+Dr!cG^xUr(W2yH;pUQRV3X_Kf}{acRR^HyX@1UYJNlqhmn|G@y4!p!}4A& z7nH$%{I~s+N&MDZQ1~;MTfDuMwMex;_T6p5-+soibV;2Ho!$6ZIW7K5G?a3(@qf)5gP@m1=Qf4spn?>)C3R zOrdn~fWhh0{n&KpVJ%ftcZ4kN+9$-CriwN5v!7)>y7~;pMKHk7jTeS>K@(T_pGmkS ztzVk=?ot`pcqm++{n)iZSn1O)S6x~z>|$Yy{QNg9ftiYP2jAW0En2MD5Y0UU#-(V9MaV~9EbsY^IF!{FVAzPB3dt^OXs7M z-U`f6*R!b7{wuB9QJkTCIlg^%zFr?%mipyR=F#cl0<5-#eWCL~NK`~6-~h$`VfNn* zH2?dyk4#8r{i-Ut^OW4b-&1nx|88T^VQ^iL8a;WB)8iSwK5U2;={9xAvt7M{#m!Y) zz+aKwwYb;`-wFCy6Aa|c6h4pK;|7Ptj%qkH7Qi6cXY8x9Ls6Mb{~qGz+TIUXUw2$D zt8zNePKY;51kd@iG+T#m8i9#W$NBt#n1ecmYVL%awtzd0E5oAF+^Q$(3>s5f-uT^S z9=w>WFEdK>s@$gLc{C-T{P7^Q9f$teTJ86U*xs);73pK56sW4L}~@@YE)UwCTMNDezJwKj=93&RcgH_Y)?Aubaco=q`Cuft!xnfOr?*hrF(!*L7%5( zmcPqv=7|oOY|k%&p`_j^-{nVQE8ipF5^75Hc4sE!iKB@j$c57XQ4-0DYCVEqHe?IY z$pgo**aIOrw-q|sz_?LmZd^>yo(I&jVMYU!g9R%tkWNjrAt$Bwd9iKnSqpZUFP&=2 z5d5e@@AzrhV90)i*VJ={w9-tVjRe(E>XGa~^D|l$Q;0FxhPiq9FH;*4K1EXv@D%W; z{ZDy;SVZ-qw`I?*xFZ-v_?>)&ZCwaPNy1Nl8K+TtBcSv)jxdjw`~B{u=J(s_5!|{m zS>m&dt&JWT5{MtQ=ibQjfi&P}pL=`jRl57)xw%3;qFU?jp;Cl_0NLOtzQ@wEOa4^u z?Fpg(O4hKQi^%#}1`Q18;Jb25=VU}ggq)({;_=6@nm3b#(G;ZpMWk_H+r?#F5I(2@ z--&D&_9q9n3)np=_ZQ6a>W~E^_$hA%JDef=JRkxBKuksEY7W}|{r>(Yw8PYC@%^9| z|GxpYd%+bHIUWV&We20q_Y)N(WIg-jbUW%0=UcdgL_}aj0vfzA>a`pzLHP+$Lrg7J zo7qyDlXq~R$A2NVrF-PG@+5imXPuOOA&pOq6|lHX$qm_2*FBF$PDnZIpTXCp(WBc~ zMSC=yP!72ezc<=6x0b-v1N&fBYF@vKU#vr@gX{=1b&7TFSQHdE9&9EE#*pA}4IGMm zu2=S~s@6say!Qqbe>8~b+S<^-K3YPVE({>st+6fW`QGu`Fbh~kFA9JluEAFR%0gO3 z>teVtV2i%2^UNt+9*6#ZKMy{aw8|TJaPcbcd1CS;1u2aG({*-N79{O7^n9|eGaY^` zZ4V4wgd8kjNxE-ms_*8U^`&>NrQ34_?>7V!H=yvV?oh%kUoqFjuUYn7;_BAHKmep- z^}gD1J~rN>dAZ?t;DKj%!xAQ(r&$S3T=P&ppG83enm|in?Ip^1d|~6=U9`51j<&u> z1s~k@lRKT6>|C9MKlAp04~2AT4U@w+?xWSo4ML%_720_%bn{U{OdC1DT58)*2HojV zstX4(xGvp=buNq+!yXTiFr_M(B-^xm7`Q5$Tf(&m?Zls)J-!r17&A4F51w1O)W6u< zb7+gk*9EQ!*dHjzYAEs(U{0b=wIJqk0$cK|E?~JD}wVParHm z8+>;!%XdV(+?3qW*6&Q$*Ry~SA_gD_p%8yHbP&uR{}MryV}FT!e2VSB5kOD9-AO|} zyV*{Qs~ZmnKk24sDOEowXa&>FQ9AyX1Nd$;>|mk}u4Hs~ZPkr_aA_$&aJn~V%gx!L z|Er&s7_^ZOoT##0ft#7s;4_Beok;tO!9L;6y`KXs53quDg|?w*{RMKQ{KU_t*7TSH zc2f}EtAS!;YGtb}C(?spf?)&&-#Ng#lqDGp1$~qSFJ<;)pViSt3wnnF zUpKZ3xok(h(yXT>cf$+&=GL<51LY2 z^EO@KpdScMznNFLi5F$(lDS*4Q%-2ttn<@ZRrin`b*D+nnO&`6>)$UR)m;6F2W-+7 zY@?4%jIy~pY zT38nlVcd^K#*+eroh|!*;JK6XfaC4j0;i=6Q}YCuoHGHa>RGA%xjO~X$BSO*FTOFW zYuTgzjfU4Hw4Bb&qnQHy{L)6G^KVn3t4H;6>v+KH3g{ESOKWXaDqZl3luL(~1mdU7 zymua;j!{J<|}EfDv{c1vHL+I_Nfrm)h9!SOGAnQD_L3e2_9@4VcA1i7U=)+fnT+ z!dNvF2Nc&1iI?W!5{3N?w_>yIk{Y98f45=8aCXZZ8v| z?V@b#(LhrdHZ>R9EYZ0Fgq{Q6-Px-iJ-0@v(gZ$!yVkR|wvAu%$t75oS=G#0Z$TON z!5a+t;IYBLj+N!rlw5Of*hLh6f}F;d-5iw35t<1ApMXG@0tlXvm^E%XA)(*Pl^1(; z{c-}&s5TlfvRpEzedx*o;$s`CSVo;@DG6Oh_n$Wga=2mhSjD^D=DApbv z`BIv!1LH6Tf2~`|#Y9GT`s7g3S5&+HNOfntO?AXRQ><}V_)kxiBf>*`S5e z8Z9nlxoVJk>~+AD$N89cZIA_Bxq)6hjbD?lI>_H0w+{%bhZC$!hFD;|ECF<#W6++wf%RA z6w45=d;v|dV^Ih07clpaIzc3a5o%2)0Hdo)xe%75tF|1run(W#WAzd?mZOM5_7w1x z_!%Q*pOJf`8BZ~n$J*9q-k{@rnDI#ECn5l(To6l!c0JmDCV>diad5NACemaevDpMKeA@>1$qS@ z3#P$7{6pja1astEhFbrfmT4EY6*&m@1L6jv;|`LdDP_9V#Lm$i{}Cl9=Q6%F{Va~#zQxgRz=%3`6+9TMaXA1% zXkY2~fS~O|U^wxxurSvwT+-&|JRGPr{amE!bZ+U#Cj9+ElY4tV@c$}sNmApcS4d~g zqzokl`gQnjE{P#0F#R+HO^6&N${c{mN4DAJL-U93+YK9bi<8Nu%b2uZ6onOy5|aS- zJwGu?(Q}=sI(o~r!vsnKTR?w^(>@TuAXIG+#!hE8ia2W0!O^yUn<5wjR*JThyy$zI2o|EhKMl{&Bp)p8%a-lAve0JCL zKZcfV&-h6##SyQCBw%V_A#bw$XEu7qPXXrn8(Bv!-Zf(Ej}ga39)GP-c+x*7F(_Nw zZjLDJ@98&oM{Nk4 z6LVKr%LYXSq7X*`ho*Cig+%(F$xoezqOoW`?9OKFbUcYY(@5>c`b3$um@;USm;VE4 zLqiImdyv|Eiu&g)-lHHhrGNcmR2GXjTgurX5Cmd!I?*}{f}iXe|BRGOH#@Qihzo-7!?MH|x2Xxj*eUP7P}6%X$fF(QKe!TL z+(E!b^nLe)A)mS5a-N4r-w2p?@ce(m|NoExS1{=`nEfX7WqX2^2ImQu%07H~{F}h# zQ1WGrX;|)AAI}N}^d1qnjME8DJZd*PA}Y0YNo29rm5Qy#zQME^vZ35vYxvpknSL9T@*U zyGy&|qc>p4Hp>LpY#N`f5Y0PhH>Y?RTtP%kJI9VzG4*X#c2kYc!X(M~IW} zKHR#y;Z?p<_wr_Nn^QRlm_ls`(fvbx3OyhmF+kZ&OezMgiL3_bz0>Q>EGzr;z zQ*46}+yDG4K_~v(K^}+WV#DBvebGa%a;PFZkw96~I95OK8urT!(RZgbVfw1 znDML%Ef2xQ<|`wvyxaFS@ZGQexw_B0Y1w$KF;Qiio)QjKKiBuk;gNk@1}fWv24k-Mg~oluNL2^jg1)4#i~9Y76!KplYv^Do*6^Qd z_E-nApW(XUO4Zc$)xp<4>jX43I=AZfG*E)WoFO^Cy|e!+F#jKm1>(F)vUgALQVQZ+ SJ)oj|9_bj}FTdvy{(k@`W|6i4 literal 0 HcmV?d00001 diff --git "a/data_object/samples/distributedNotepad/pictures/\345\272\224\347\224\250\345\261\225\347\244\272.gif" "b/data_object/samples/distributedNotepad/pictures/\345\272\224\347\224\250\345\261\225\347\244\272.gif" new file mode 100644 index 0000000000000000000000000000000000000000..f5b3fe8c4290c84e7548ac7154abb67a540f3593 GIT binary patch literal 2135829 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 z

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

FOZq3sk@PYw4U6e(X~woL))0DY-7Ajg`dUG*L9B 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


<*>!ki#j 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⪙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

XjR8hLgRz+G+p7<8GHhG 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

SQN>z~=Ot~l#kKH^8j+8LZ-qer4{ zromBd0w@sCuJr>eT4{5{p{+&sxTI-wv4K|vf)dZr7@mhGCC9E6CLX%shL3wR^OP_W z{&Ntt%O_PB9;?6{0sAHJ13zIDB%4JtS!=&dJ?_Dk)p&@ zpch#%zayAJtEzYxiI%gFV+S(Iq$e_16UyMU7>IWrSUOn8sDjlgP+=*Sbe%-!wP8D* zVmqp#a%UD?wO3&(RS~L}^gffvJqiJLpKG_mwZ9#?2 zfJ~SMr7_f6+eJLCM{ODs9O_#hgk!nv(+rrq3U9KmcS0Up0<84XqTI?O4xt_X#a0$J zC!>)^#H$-*eWEyb)gD^H3bVUD3`DH9L9aSXJ;5Qyx#TqBF}z;V9JK*9p4fgU`6WO2 z9vEa6*U=^|c@X+#R1WJj63adb(@(X59^h*rX%SvFqCUivHTO~@{S_cNy`$b z;J^%FRd8*)7h1u(@23n#@>_fXZKD-LiHOTv^eoFn5UlHs2Jsd(5;668IY|Vpw9><_ z+Q5MJ5@)+vS5d)>S~@IYW+Snjh+#JfFj)8EsLHLM(RyJv=rsxk(Hl%EJCHsCgZRIdj;9 zD~0O}m^&g%{4(6JBcB^YF?k($n=zIg5q?5JRW?BCLuEbLx`ihZ0>QlYdK=c^dS`)8 z#s*})l!$foC%Lq*6Ic8+#+fsz z&o;5A534Kmp%9d$X*L0xtJ=@5YE22L5)mA97(vbRIGrh6NS%<|~MYqC4{ECzbEZAc`^?85hQy!YlcUoEEgTm@MT;QDhMy znPDqoV{R6NNG3~~+L0bF0l3|UE4@_F_^=A7fEv^?dmDX8N*R-TGa1by()~n1F*&Zt zpqJ(9JBV171j0b*6{N99#*rg2RK=7R%0D55F>69&yHOV5)O+aSak<42M$NjYIWH8| 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

b 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�T7R^#3(aJa7`cC~dK68OLD57fsXBl# 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%0A`5|cCc+p(1h4@!OLFiVi&yxrV{$5 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

dqU8CIn$)8T zB&nn*Uqk+&dJ3uTA_iZ&>Q(b+0YR_rV3f; zs$si|1+T&sgdy^POs#3q0e|%%2nS0x!=lZEiamk~K?Rv2JT|gKnCuM4dD+r{=5L!_ zO=oBG+1uz%8KNcPJWKnybfHqu9p&DE$#qbo-T^@~CQyW_m_=%F%b!KK|bgG`}Uq7>mlywt0hjA(!Z z^e!?;o;1i%1%k;anZl?JneTibg4FvGDZd(#sxCl6CII6vl>;WQ{|>B^JS3QaacZ!G zyZ(aKvOJB#wx+P3I;`QkPH@*7-in8VDF{a7AqCnZaidJ!%oK}|#U=Cxj5&o(89SCT zHlA#%c3gxSx{7Z;{xK1XuudW0>NdPGf-k-?Ptq!xSjl;}HuM<-elkwpR=x(7wS3#{ zbeW3|4KqYrCm-RKIX=csv%nFA+%|XgjBXqdbd5*H2~o&jceXCU^vGv1B?)D1bUfuecZBagZ4Ad_}^lq6lrOUu{3mz;=|n0g9c5nWt)qTn3`fw{R=+ydv%YH%54+fJK{j|CaDrR-!WPUXtTfT~{_JSO z*i+QzCS)axs><4MG~PxHxHLB>0Wo^+U@RlqgLL&ss=W8QG+T2CKv_%Tm!h1 zTP=FIw_p~tz~ci5Gr#W4v#Sqs^|f#^GWS6B;X@hB5HADWtGaRw(SaxqFp_#`;sjFO zg19>dn1VcdBzjmFzF_j4E?VU~Vq|;WD{1su8k6>Qua%Z>NnT{h7_$gL{YYJElANFe zqEl0WYk~n)mjLNQBg8G6&J?>sUFsC3n%3hle_wMr5M4L6wv;J$yO

dujHic*EKq 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%0DHAZ^72ao?Zb1pd|+tBE9SlSthrwT7G8Ee58 zhQfY0zaeIOd?OW22Wt><4+Xc^hjz!*&&4=z`-N|p#AowFtJ-n!gG^`|E!c}xB6S8l+Ut#F^T;B(pm!tBj3GmT(Ovl-_a2Gil;Z= z+0S^oHKM^kJiB7z?ZrH8wiJB1by`4_tb3>P6dDHs3HK)=_@hm(-MjW@QdcbiJ=`PL ztg2v@n3yE3+ZrecUC|okQgmy%Ct z;8|mQB#2#%icCtx=Q|{mVgyg;q!R+f_7=?^Z>T_H1IhyY^;VP-_mqXU%u!AH5B{@A ztx#c!5m6m6KPIWZ5EVXIRLlDAJoPebjVAnhIe-~g8S~oNZ#faquvMQDvlsQlH&;aK zq^l~$knTE?wr`QN{77GhWA-)!h4{oGGJ<|mxX#D&nHuuL+ze--k6?VV#>t$J08Lp{&7OgLs!aG=keF8}!J$j-1>U?=>J+~MqO}$k!TCSHc zttww@6qcLjredf-H&l;1gZ6jP#g*qMfQ9-qE~=#q?r%#}G--)_r4FbiJ`q=#6O;Tg zV)wlw?@=Rf)&s={8+pSB&zY7Ynl$cCFx5^oqN`QP;+|KRi7jj|w67t4&=*&m9``f~ z-7xHZzf?}uB_9*dCofu4a*N;MIN@ydg_{k9g=P_FP6|t?a^I`*xr@Qa+&OLEDuy76 ztOz}ClBs7YuEkFtO)8f2DR#>)j@}M(eo;*D(G$qQ4@L=zK+NAo3mSrRqWjGy63hBO zlo(Z7vfXnzQnUP5)IT=6G>KU)F8@#s+vKl|ck$#+N!k&5UvhOfMobwZ82cloa=Fa7 zF`M*C71@YX_#wDkiDbnHnLRAGna*h#Bk>zWTb8Ovv zP@1amZn1T*M&y-I?k);+86-YW(hYy5BQ;hC?V(^`b~0yh*6)L^Jr6B3vsL|ybeRlM z7>|Fe>F}P%m(-uXNK{x9l2?9%T*qt*SFw(VK1&`pKEU_&UmI!VHN-L zgfDGa60sOu=C4upGS@mT>3v41&wrs$Rq|bY`Ahz*eBjE3=gX_@QxtZ~SW>%OcrRdzVXuq_4KJ z@$Y^R_iLjoy`+@*Q>RCp0iGR z+a)INu&TuvW(wtFR@yKAV;?2Hk=`#v#1a zrz3!Db@(7PW>;d?jsFLKWzN^!-jZP1;bviT-apzHWN=sibAbYojXfVlWQxN7orhx3 z6v^hWk;`|J_3jTj{0C!C-41zMvBqi^oXAe%q)Y67lSbBK=ss^gtF#*Vp24;W*$V;M zSq-hPUxcMr$two%X-;{YjCs1%_Q)SM-Eq+cy^4r^={rr*)*8Y1g!SrI35@(d07gK$ zzqwX(m>t!@Ad>!NFW`5nG&>^Rbh8SB9?2Il!6*!_#T!7l~}tmAX8hZWk4JOCCD=w>(#lhqaGfy9E#$Jy)ZY$ z^NFLSeJG-pR{2b*7>gTdYj{gE(0WIr25YbQx04i$f~%Uxwnc6^PpdPq=OvfRrZ>3) z64U^>juV2DJFx1?IjrJFw`-SJ5{v*wbedyesZ$eHnh>f;5;zEsJD9qIhE%OtHKWst zP*ggz8;MBwa~JVw7qP?BMs_}Gc)r_>k5_n#>$!P(ycChVR+yV|!5qh-#JNQxZbUEP z8!)zqoc;@v3o~mXQu}0d7BHE5W5(f2S$kOzq(IGSE^kP*?E4xti9UMTOO(o}%C{oL zc^sr9k)hOxOCnbU$|N2al%uGiKlvl6QF;_s5VsOiPxleH2K5cludVv!0I3n?*)R##a0~@;&ANHb1d^{fHOEE3$ z{w@I0$;y`=@J1vYxgg(SBlh9Ha;2xK1bpWLzt)LYkm$YvGao@)V>5P$*m0@w;d0bR zBil4V#A!~-k$R~W9<2oo3;Kz`*$Xw{X{RwqezVmnm>Ss@B&lUc|$Lz+w=;fDKM{6?zjJ_>?EB!z(@LD5-HKX_B!#)JKA* z6XnWYtWlzQ1dhLu!=mFXzQqt{jDsr#btxR98|6Ag2uRMF9B_If35pRSG7F0SF>U}d z(g#t}=K^oIV9Naq%F@)e?fZPTC(}A2$bVQQB55*|1tUJzAUn#vXu)~u^ctwW{M)$u)`S4qRgXwgcT_=_YL9l?xPgVz_86hYe5ZuC1Z$Mc^=1OEs?|{#*@AN9jV8mKS6S1ZKYb71-COozAf1x1A`(m zTOC5OJ?Z&~oX$<7Y%Mz?E-=m1-!wC)r8B)+it1N+ar{64O2t59H?vx%TE*s6<%qW- z7iC==k7g0UT0rkN;M_9eS`7?t_GnJh+i65q4-qUP%y+iYnK2H{vKAPfF`ET=%xZc# zf#bVlELHp%G(t|9U343|+9i&g&i1WUof;R{^zP?bRoXhG1~ zPVg0x8hr;B^ArtQliGF8j4>=&y7Od|=QKi4EA2GX;oC&*&viJ$&Wk(%B4VusPV zF{PeUU1g1SC*h>;Ky=sh8`;F~L_2CF%A@N41zQSeC@%g@zPK za(XmuM!}UdGw9Z0cgAKW0l{}B5YgZy)QmVP5mT@8ipQbA;4b5Kn{Fc&#L2uEKNwLp z;V5OUmPANZLJp27rO{LOnk;cdK5HCOQx%G{(%*K~f? zM-P9NPfq4WxMx{17Oy#Xlt*m0k9Qg`yhByHZgEA%{dGWDW-kn*r-t!j8owqZbdb%AE1Ii?l=}GS+9b&3Ueg7(#FXitr zzyursLCaMuTDfw~ay5(Aty{SmJ~Vhs;H+G|3|hQ3tKlq-y)srb2(n@?k|ZyB)EKgt zFP1G!O3dZUqpxTlZ>{_V445xmw`l%isS~I!T)K=tEXs=|u7yNtJ$;I6CCi*Pdqs-| z3}{K5z9jYn8j@j5Uj|uT^aU(mzO+JH=Hx{S7cX8#b;WYE5ZB(GyoBnK73-E!!VG-@ zrK^-BU7~4!=~Ale7Ou9_ru|Zinb)sqnp^t*N~|!;24oQyHZSQ@aqgch3YAzBnVsv%qu zTF{k-1bWJqyd=`-7FSr&=&4*DY^4I*)}^df36hBgar(%Af}1{lTa(j~2EEK~I`<$Sr! zzWRPkY{1oqLM=b7Aj>Jz*J1-r7F>X;Z$O$-H7Z#AR9kE=;fV8z7oV`Y%_nQDaqcmr zXpv5+>WnI?we6hh^pTD3|pExIDLEw^9hnvW{uGIOm}{g6!!GF^?6 z$j|Erw9mj63A^*DKN;!?p^P+QDz6erlu$zs6aH|PB2BzeFOszM=)w|Ptmw#uM5ahX z78eq^V^@?!kjNdsv@yn6F#ZxK#0*(%MM)+Z+VIIDRf;OsuE56l3Y=$wFmV zWK^_2K2d05P0|?T{w{`)3ngSPH**D31_L4(DTpHo%EhN{qNN~>s5rL}QP5&0q!{@~ zL#6oG#Rl>-nRtmx76Y1)Y?P3qb*v#EWW|$EbU~+4$%Qf#$=kLxCX6NQA%Aj7Pga65 zu^px}XmXmD09TMQImB#fQk#?}<|qDHOJMmildDV@H&?l3Ds4H4Ph>JU7RIS2H#y8& zG80CgJWf=Sqnw>MC7R4_ZWl(`1yd|FKG}fARMxWGx_0HC^l65F_wmnd2PxM>hsezY z%R1?U)lPc;az09>5*XAlU0c$EOw%|EGV+5D7!UK9!GtDu@UlyNB!VYSG0b2Ykr;o1 z)Jl}lEJIEU5<&=~p;;IOf>a`k$Uh9uMzhqQu2U^$3F76KBqrAcNDtrA1G8Iy!@?SC1>39>A*5-bvI zQi9?r*4&0Cr>V_sZCi~^(AEn8n#z#95g2H2@~ZXt29#MbQ|NMW&ox?vBP{hvHQJVz zwJ57pb(D+#FqKD6?WJ~-(VQ;+C{_uEYLC{j3+faIt!VL&ac^;pt=g58#-!#K*0`i| zbQk`tN!c}6MfD3-u(huA8Rv7<(FrdsWX$Q2M=sM~4y=p=AlksjGLL~5tu$kqUmmh{ zxyfWtfPq(FEpt8QY0or7x0=ovrLph{kgYBiJEPD=B6e9-#xQ2q&ERrD;ato(m7<}i zG|h8M6{v)C!4Y?s!WF0sS&?!Up`6JLp9o^0MmOlAf69z$5$f11SV2$~ipXt%%2G!c zsF8`aB1RJ}3NAJ@i?kpYB2SZ>%WfFbb6!q~emm1!1P4{Jgi$LV<>8M&=BBb-t4-MY z5Ev~&nV^_XDOq`>OoDRRzl9E4pHb>U{N|9SHrN+ZZ3tEKk)L+uFFJcM)?GBE$NuDrK15B8_VIYMQo~?>nX=jbM?w zm{iv8c&GD0Y`prSyKr*X@e2blyRzHsfH zOrhC`Oa?*;-Kb|wd+$aqd-G zhyEPpGpm-sX^-ZxFmkJg=6DX;AT8Novo3oRVp$3bvyY{FKCharZjvBtnmSSwINWjy z8nGM3NT+_QCnjkXouZhp$p}_(1+{Ug17VUH`H(-649a*AnlYivVYOJh7^GPU7-Ti9 zSrG}5iJF?QpaPJGST-LK2p8cgTDT~-!4m|*CnIVRJc0hM>#7o|_!^QxqA5%e3xW!f z;Jc~VDL?tN$vX+oSdXY$k6tJ}GqH&bICkdyAEDH;Qcr#scpUdJ5YaySwdO$Ntm++V@(V!U1*^_JH6AhG%|1qneQ7&>B zC#e7@%c~f&$fp!THHi=ru$aM$VVV~-5ve#weEwp*8(boxVW6I&J0Gl(Kdcd%h@13c zFe(9%C907noSGx~nZvmlJOLVmOqlD62`Y&QvrCDU`ok|d!m;=hK-r0@_%D#tLYX)> z(%F}*sKennqtmM?njn;D7@jvV4xE^tvPdaULa#qL9ELIojdGHZ(7ocQhN_|!`j|Ls zB9&H=ptLF#Vw^aZSP!|-3f+koTsS7LIE+@A2F0QcLpq(-2{}>xj@!5$hx3h}avWTM zGP?l{{O}9S(1~(6jZ*P2?a&F*h(4;=6;FDKUnredDZpr`q|xG*Ogf#P$PLF_jQX<% z%s@<+izNp{6=x(K*(j~-xD3vylwGMr{_Ehzr(g_w84Sz_M^PC^shc!~u#1x7lp_ft zPQehlfRZeXNBsCg%1D=ras{x6!*r@bCxM8FtU-H>AkX`S9H9{yY#OGi5u!*)t_d}; zX-FusO||(#iR_TwA_Ellp_o z*Fr2+akI!MwYglfNLe}M=t^904+k8k>JdM)TpsK&H}MDxQ#?#ktg`!|3){$vx(q+p zVU@uYk7jW`*8wG2inz#WKnPX-&t`OvS6Yvr@C9m!EU;`AdZE%e0>=9rx5RV~>Nt(; zs0L=ro789tYowICn5M;O7-gxB*>uh7Lki0oIEWc1y8$w$Tp0KrZtQZzi%Dey&Ao)$`WXd-^5DSqC6*(vgAv__RFkhp*ig=I_SvK1=jOmn; z-`Y=)FfYJ?HTGmEszVBf^1HA*n)8CUhCsfQOq=i#C)Z4+mu$A|u!d;(lLSpY2DJ$! za-OU}vmJ#=uDsIXc}w(?!>hrFg(3*}0MUjJ(R_$9&`=)}y({{-8@!Q~IMp-YNy~ln zvt40|;1D{ccu|)Ew3Yso6@URHWpuJ@(JSXr zPsPLv$l?~Bup7uSK+z!zb2*>W8an&Sp5>thG2+*gOD$5&pZ!s$v?w@8avgT9(+>oe z$brpp6u4iFnAifR{{fAHx)41Tv0qq_COo2{0K$`Lk|jZyhsZ9aT#|FlAcav7ypxKW zfDoB!6;Ca$j#w^H?ZA|XFe361@-(}fYSp=$3|QTf{y8XGy`UCZJWz$yG^vvUQ`9Kh z8m@RG|2!P=n~5y^+A^f_Q=MS)MA(qmw+I%d&;?jzlk+iJHF7?ji@D4%$af^+J#g73jR7t5_P9d0TeEkcSExQFW*c2??t#$iw(3 zKY>Dxs2M3_HL^%2IPJQ;*vCo@oM#OikTjD@#ZaS|3R?-I!-)yUbTFw{xvAic=8y@Z zq)dO&{#ZZRu;<+}DgzWkDNrfy3__W`u7e|-$Pt%YIn&9YX$akXNWYKOtGc+W`%^Mh zWDWJ{v)Q-|!I(-lY&hFB9o&_R9X=K;Mc_8^p#qYFUoE4UC0 zpRA7*B^6Gc!2sT0tUO1ln9TH2sncwdLUL>JSSNn%jGlbDRjsL8-E!Iux#v+d4`Xo~Ob1sI#~c8IBTH^&yJ> zf?K^s=Q+X2|KUQeIioo$B-=0>BJLHl5gT+{V$)ovSG0(^=l^@Hw8it7Mf^xvWag;Elu@4A^)#3k{uy%ZgU%jO@7N z^GlCoktN5tmFZcn&k4CyVhpcL6+}jiWAak19LCHLETAAAQ1S}#5G>0Wp2?U7``Z?5 z;FeCd8}BXZbIe8uJdV?9i;OtGUy>k7)}H!xSw6!QHCm*w1XRqxi$kl6J)SLa#7k91 zieiNdaf=H|-5vl@2xO)R`I3;6NEnJ@+QG0$X_jER^-YjG!69^+qG*W2ff@d!foCP8 z5%+o^52X^U<0&+uI*DATMOhk!kS7GuP9bb(t3i?KB8rN@S>o`T@WCRNKnkwlnnE&6 z)nQg_@x1{h7^+4lnYd7$^eKiskt7}-T4ZH@ZN{)hY3gZA58LTA?BbKLZIaLlk^tSc zU=7JYRth~`yh@hz*bG36QMV`xnrJQ^d;lPGYo(}Myo=dJ>uilnbT0}advZwvT`gmoR>*&*Psz>{>xC6s5q0Ucd3K;2Q9&R%Hnn>j9!eSaq7cHHREP-MkPbbl zFq}V?3NPV8nzSNvd!qtd3Ns{bq25u4)iU7KX-WYLmr_G^TS=XqSB5Me#!Ob7$Xpw5cT?V>{Fj8O93&N_|u{#ZJRcH3Zz=}LT6~5d==@b^os@FOK;4a zkg_y=UMdT{n;@7pViV_C;!@uZ_RvyU(iT<^+Vr56Q$KJs-3#gwh*y@COO%k+AdP#8 z20KHl83fT|sh;#vMp%-}mEAC%m=E^GE7DART7hr=qKj#x1|{kc4lBht_2UfUsNKlo zol=`${x~!Wv#Q#HtT4W02}z%NK_qF{%D-$Edy$wpqmH3yzMoSU#K4b7EAdmL3yH6V zps$7JhyLbw{^&0SkGB-B3N_lu8{(oQzEFujdJ0=G1yjiQTc~~}d4loZsCNVq@hAV4 zuaI543l+p*SwIDt5BmJa_~wWC=hy%I2Z&m;Xx+Nys+A~HrfTKNMa$K|fx2|jnso~o zEmjkC_40))mO@m~bmhWT&{s5D3TyT1=t~zaU%-6%npW#3&YY-F<;00{+Ao?xQO-me z?I^Hl)2dCI77ZA%UocU&JQ_5q(`nRbMq~a8wJF%9)PQn5O3j)zYDi}y6{>V;G+)1t zYV|6&D6n29qej(x3#G4KN?-oMmM`BgUb=ws!Uc@lSjK;=Q7fwWFRxp?rZxUm8ER3F zxU}AirK>To#5H}zy7h}OubHuZna|C*K-G-%$ZWj39Obt&iVGNa1$by4@U?6?cYjLPdhuK4j!E2a+<*J5jNV*`7r zdHj34orD}?x{OEDd-a_bRD5z7SKLsF;pZ7=x9C!wT#a4G+iScfxRFW%SW7qZnBIC<_5zHVXui}QO)H!DGl@K+RViO}8g-OedFB}xO4JPc z*;`6UTN5_fP|G%5Z2OXnOIWcj9ZfSWniQ((eFxc7nF6+#z<=jL6mo(2Vpu-f3Fuj> znz4%DNxI0g7)+ii_nuUaQG*e#2-frqLBz_%A#Jo&cUx|9FXo%TsAHEfcc8u1RJ3d{ zW!qe7DHR#I9^TddovxMwHITv4PwbJy1Ji`ty1Ggj-$n3lG;f2bzC;tg{}uNysg~BM zt8J0H#neP=MU^mov8jbyObE)0;hGx59$sLKCpeLXEqc3@mU%KBN##Uq^uS+ zo7qAbn6z7b!e#5aS&jy>GngVlSM70^ukym zuofzd8re{Rv8L_BCoRzh13#sUn-sAYHZ++6_q8JfzT_rTtVL|JGetaA3o3qF8{5u; zmN8bw7m%6BS=a`hr35A#dDD-j+*hZE6{l`(Dc4=H)ws8yDpKPyj;D%JoR$PDJ2b%@ zZ>pym@GSmkkz`3^OhiYzzbI}@u`$)^9QLo@03~*13`%NhcRLB)LN^}yRDuG;7xIwH zIOqwBM@$#DtW-;R(8=R#60;pZ>dH8AaYd5G$3BcyuXh+(m;dhbuG7qCBM#JsRy?9B zd}WU&4U)=i+7ilHIS)DeTg>lzry!#6FPRe>3M(^1iO}024)-u~~e2fy9Miuoela8PS3U!BV_TOF_Fym{1X=fn3Qz2606T3GxtN4(%bW zh=nI=6bo8pW~BL~51S;ov|5BnS2;3}NqPawc7Cc&B|2J*_A|wf#05W7%tcqMGDSJ^ zE&eV^aho-;q12_G?J-+H2^AwQ+JmBx-oE&TC=KVe#9Z!9*9yn2;qs3ybA6MCU!=Yz7z?!BbE`SsjcJg_I~! zmFosZpoy9FDOyR?DTxy-r9`KXhz09a5`&+IZSP=G>F0goRi$;x?Z#e|y0xwD=`D<_Bj54@wgU;~cy0MbHV%`R|5(J0zRApDm?s;> z$YQUA_3L7!QWMc-2)NLk27Z*Hnu*np7sXvqdBih2q1^8_1Va~eprR`2+|H1i-HnBS z_dRm0c~-bUZG7q@7hKFDCZJ-BJ^6zibpq6Z@?lr4A{DValN3XJf{k!Nqm|<7_PEG; zkT{)5E{gm|LFBqirZn^m>rU8c+I^DEG$Yr)EI5(_amf*%gbS!ejYPHn26GCn#l-~qxO=l*djZVz_>=?nLLppA?k{G4D=+K`htZ-Y9}vn@*WGCEI`?=#faOR z>j~4W#6>-eT9R5Qw5;20YAM_))dIggk;kcyDk@l744rBe3No`&aK9q08*22ia7FFK zQB% zMNBHX=nABNo-9a5W0S-Y3Uu04y;i;omywI>T{dOl1VwIHiLCza6>-ug12PecwfL`v zN>Y-7xRliv%F93i*O8X&oz}c|Z!K2F3KPbHfvkAQ;<;@=pBY(|s zb^b|3GqO>0d5Au11ifY=TG5qOwj3Ze`LRB~IKNCR%WF&(f}}>kOiWV9#!GEam|aHy z+{LO%2(GY4xBLs9$i}+t6795_W@(uKEr#MK+C}gkDE{@%_FNscSdYa~3vVQa*Rj%1 z%!<6QoyU=s*+odVcv1Pami*M5fVhTrErbAp36IQ6;+X<7!O$%T)JBAtr!3fF$d$0! z$2~p7O{fS?@x%*-RE@O4LAU|~b<`}(LO~ToMBRkuIg#BRp^IYG zZs@`k8AwWPiSC)qPK3y*q@G^T#1k>i?gd5c`NHf$$hpi!9zG0iv>v)S;_gKiQn*`F zdC|5=$Iuxc{-H|ZKn1#4U-LN%cVq=xOro>o#drh?S&@;!@eTJ4M|U_&xa`ftDb{kB zA5h>{vm}SS{GD7aMhpH%r0^AP$i;;$N0QM*{!^S!Vx$Ic%mpX~2UFk@gm{sMOaySy z)@*!DMfA^td>{CHORanc zFfvmVI*fY|PA}F(W&oE=Bqmkep)wjs>GU7!I0dM^94`sbC0UFCVh@nON2$z9&iUNb z&BrnAQh=1zs#Q;i7)V^uBblX(g@A`vz=nhn#o6hKKT@B0I7a*oWIKjk^wgq<#LI25 zOCjZw=5z;CoJLZ(-RwjUd2o-;Oc4;3jeDI2u~i;W| z%PfRI&E!k536Jbzm?4O`fhW0v9wDj}1PxJ0+Cq9w4N%xrt#JvY{K9pW4S?ljbg+$Q z&<*3L(S>r6Q*i}0Jk|9nm45zIM_O>zg>sd(KwpQ7n5AeR9~sJrQk=~Z4#N!GgtE=I zT+XO)S!TGAi^4=#C{A_W)+IUSp!{ONNTy>@Ut&Jr`4lJiR7W*x=bU`noLG})^c1{w z2u6&ds+A)-u4Z+#nt&M0F&RvU=*k(*M0W*Ox&$CW2q1C1%Gp2%IksGLa$}!V2K;E9 zA^A^D^dWWt!MQ|{r{AefcSVRFqD0q$tPxyiYT_Gzl5zVYnDU?E{ z_C!KFR4llRk?@Gggv6DMDv#78stt*O!dI!48Y=K-sXge%Alvnj%G`z7Es)xRnoRZ; z5N9dqlFVuXj%RruoBk_lXqvhuWQ@^Ty5(cYi5q!nUMf|jWF?-E(Vj5QDJlhsir7!M z;#r~!yHOmA)yb)-#bxy#o}gYYR6~x&24^@1#cjpaDOvVGnlH#gVVRMV#l%)5DOi<` zp6W(pR7p(aNB}8DYTyrjAmC7_2~gBlX~M(+iN>8i9i1$gg#?<^ofGQVMpRfVCmP2^ zbjT{c#9t_tQ^*^9Tk z;5xNaPWU9}Erbs}P5;D;&{2fLhz`x*3q-8rL=YhqNy$p!LgL{B9NxmmECkqGOcrus zveJyeKrIeE{*mVC%ID$g(E7p@QOHY#2o_~qjVw_u+$B@G%v&@k4<-l}1rOBT?M=+Y z-SUlfq#HKGMT&t~zkMZLp+tvrMP6n@<%+0UeAtK{Meu#AbwrB>ZuNpVn+*l+-B^Czr|^o=G=BPh?#ZWZ+Wa-xk45Z zYSk(XN(CS!wVcTUg>deZXgFuYz~7Li8Z)g+JmsKqtVcO2TCkK3V+aTC@Iq2$#czn_ z`=nBW$Wn5A99Y1H=JYH&rP-44oWqiAIHg9>qDN*Wt$5hx`^X_pIZ#9iC)w6W(hW@k z;>L~sY!H)etrr#z&tR;~@B~GKgb#V1b}$eLVesS}A+ebZPNJcv(p^pfM(fGPI@wUo zJcdV1(F0}}kE~P>df7A#$&vD#O{_#sM22MjPNq!CSY(@`(1bwktu4A@GS#7oMYSaA zgc4j_5S*m!ZnD8yVK42~=C5-3W@*)j{cOwmj0JZ{)7Lrk`Uu)(G_9?MbE&PJlDN!h%?yv+ z0z$wcObD9+P2~;0$Ecc20r7%OHs#S&>P+gzh464R=*89$MqGq*c15V4g-B5bQ4y0~ zjTlzERzp2!n@CvdrBWqIRJ7Uh$WBspPmF~d%Vik5(HO&+wq_;gN(usMV`RMJEhw>V1W0mWiy_sAaJ&Y!5M6XcV`(LaE-gsSIc$^$EzN~f zOca-v{!t048Go=WJkiM88XFQe^T+VWyP$K@v;r$=5D6PeQMQO;6i_JC$lDo-Mj~O@ zfF8~fSbk>4Z%LW7AZuv56LAPJ1Suj0Gqfof*42@kdkK-oL<$T8Sb&*Go`A9CjxiZ; zYZ-6F@1|vnsaqQdpG%YR8Q~vT&~)cQ)mX#{Q?RsHoMltlkL7StP%H88X2Wh6HN>g0 zZzwhF7GJZBnKec4f=Cl9uSa8;h7{&VYp`4mPLsow#)fbs)j^`S{yl8@o{GnT4}Ul; z>+BXuwKX_3^F8I+ZAeT3w&GFnk1Bf|MhNHs9Z#!OLpF5Pl9di&yOoE`DDOe=a?#nr zB+^CnlA4)mb7om(1Lnjy^)p`f{UnC>1)uMfNFZd`C12qJNO`~ql84Lw&k zgg(V|j~k+o$g@UYSzgOXx7ArV1zHrx+1QP`VM8!4HKEjWx1L-K<|Vt8ky~m7#qse{ z$ah;!XkPRJc(A1?E^rp39)D|=bGQat1{Ng|AAE?T9*)lbuk+7>8w@;owPqBmL8Qi& zsAGlW3M2;LfNkABaXA05i)eJREdPfxeRw3hX7&D2H?0!?aAOI!IH#ZlC}IsRP{TG< z#vJ`7brMH4`nY}oISlr=TLl_{phn4}Cdpn_cGOFXoe#aA&ruZb5UvxTE8^Jh0s~UK{`0dTij*DZ{do->q;&k1g zeqApG*Kml|()r6}OPNPp*}(Nt?qUN|6sK=Wvd3vmo8quqD_moYal9J6;Yis75>dvMP0z6+0qECS1(}HeE9-K5+OEgB27`N zHgl#mY}jB@i)fAK&1n=hqQV%InoXV2irTbBEon56)Lh<_T5X!dqD);z`!(%oQk&VV zfy7n~snn%TBWm5swc5>|*w8X9iw$Zvmo?4Klu30Ouw1>`N{pseC0UaTbw$gUZ)IU$ zw}9P^rt20jV849v(zSUrEm;qD?b3zobLLwIM`wZ zW``*s8sx?CU`k(GsWEgI5g2FLm^T}w#Y<;qkeGR~?#s*aw9yY8GSp7kxAEGzXyF36 z^75~U22}#%%CxFMU|T8;@BC-${*|G!gBa~!QZnRr2BIr0R(nv-aH3F`oB%{P^KZb6RMI-JqGDRR$EIN+8Uto;MJ*pV0 zQKMR(h_WGGrcvm!%zBCFmo5#e2q;s^L}j>XU?Qnb4kb#-nru+XiKYT=!bu~Y4&jk1;l;sGr#To;qn#X?O39-ts8;w8GWZA`+(6kNiH0Usk zrLxhCobH#=K)THrYWB18Ak7lI@G%(zoYz~d;Rjw9Lx)NI6+G-H^x zH?dTYFah-y6Fpt3S`bhNtrIsdRT$3U222#?^D}V89%N9}j(ogp`1vEx#&g^R?N|i+Avdx5ED5a8WgR&r8 zD)Ob4pxi79CXW7kQJJZ;Zv^wvni%CNCt3>xCT+5ls|Xlg(9YB;PHFaZFalZa)h?HC zHI*)i1dB*2y9VQ_sz#UcRM-P&5_KE=lq#xMUYGy%uG~TLB^SsjSGJeGFx2;!Ulzj- zB3@`g2u)mA#Bjg!b&+K{-?Tl>AY3Sh8Me*1+wmdt-MhsVen-QMMe$UUlEGO=u+1`{2Ftf)Afao9t+(;V2;24%)kiIFgtG4>$k7p!@mhU&9Dx~yk` z+~LiZJAt3rhy8f+{loc zd=yh?#+8>mDk8~A9a8%8#lb+yNvPly?L1|bM#)ZgE)nHYjPex0e27(W;pOk#(mL8j zs(7;7#x`EXx{2bDn$~#ee$5NmP(#jyFO@DQfYh z!Q{dfqR^x!+h&o|DC{Ac4B&B^JQ&1$P3z+c2M!XnX<}&xFm-=THn^dbI zt*Xgd40f=S(d1z0N=c@4DMUBPi9ZVorh+&Yuz2O9DOOgaEaGlLUrdTG(i$UF_)1d2 zD+=>^b-d-tgf6EI%QRNz6khVmm%XA&s7{rXgI2>-#>9^@g9###s7fNxDHbmNO5z&p zm84j=z@~AkVv=rVhc>uyg?a!)KA(<;7O_~3Wq(geUww zsC7}kE{dxMPDzQqq7l&YB}Q0mV>fM z90kR$HnQyKP=e*ORc)|-b=#RyF^w{#8Mf4@NP+6|*xY>etk^KeHi-Uo=F8G0bvEHG z%`$=t-ZJl1XrpZ~O&hyx9Fx*x=~OPSqAR=bE=~$rw5WuqlS6+6)SwC;&1T{s0S=dP z&M67`da+1q{za(3D3D5~D^XlA5|S1($zW1 zQS$RT2(y}+C1E5rk;@>NuiD$V&c_m2bFp#5STj5F?)Iq7P+2(SBSmWn{J8v$z3AX z4VNqCrjCD*a?pdOY?o?@S*~oIje}nXDhrX8%~XOG{&7a{LH#8k{U3O)isHgt0g_Mk z%ZYtTkH^vjI*z0IvPClrf-GvIuYfN3Cc#}r75S14`Mz~YLcqVKW+SD42vi~_c3Y0}=VQFKC6 zt^$-$tF~z9P|QO7PKbC)OTXO1@fc(<8Y3n7c!4oHZT4zVH_C?jmjiGHfj5Q|7WEMslBBQc;!9KS<= zre$2(!`xiK0^P?CGhK3pgV;pf1LNB}=9q^QC^{Nwrh zX!f>(II3kVIz(E00=DiVRKSA54$m6;h$C!`Q4FUVj)^1*LqAK zp*(1zV(u~+>A*&Y5V`5|W}_hxaY4X_ySfWD^v}n3arzRbJwSpoFvNo-qB3{_lLX{0 zb)hI!3Mvk2EG(!mLL~a$(ZAM%s9Z5lCPP}Nl09I^8X5+numoGgZ8*?v9UX$VRIwII zXzY^XK;CgOKZf{X2H>2r9tjEvY0!w=hk-Q1Wbn(p9Ei&L$2-9B4%0H|0w!T#CWGX1 zU`D1LKLX$yqZ{Q>IM!pUW&t0)l6*MBG(IB2A_^hP>A^OxfyNCR_k}8JZ{niq^?su^ zh-W6&<=;3WCUJ)){-ZuV;{GP6;=&RF8-S@mKFyZE?m)nz7SX~~7UJKIN$-?`SCXkG z=q|Nt(k8(IwVH<~uMn3|>(l^m49BpSbP_H|3@eyt8U{NQ=BAp;J#m=IE94ImQr4nf&PUK8z9u9g^aljDDe_}%z-EA+Y5imQ1Et(@T zY9|<%3iz1AG-;7vxb$EBIePF`U5h- z%fMjk(9W}{goYzvk|pHPLN@F!f@Lo1lkwa_A~+MK^bkc!jUiys?@EidxPnyUZs&e2of@nBRS9`8q^O?u;DUNMa2%JLXd@?QUp*jktw(%)8>P>nvXtd#X`1d zdva(_WT-Lz2n3-5rYbT>&iIrcX+mRieO!L^p8;%p%)h0w{66jsC?W_V0 zXaBV<=`x0*R$Gh&=163In$3Bo)L}(bRa_$GDh$LPgfb}htT6V4L~m{xE*txHV-ztm zEad(}v_gbz5ixQj8!v`DRV_f?g&H_tK#Uq8?R%A~jyL67(XNqSi5qHkC&Lu@{0)9N$IG(&Gc~ zP=^>MIGinJ)<$TRCNb#KGWY_A$kP@8TR^10-=a-947Lt#Ob6=LWMgx!WiUo1ag%aY zX2Q03i!4TWd34oW&4RXy$#ahUV^Qw?PQQ_+pYqg{odQEY;rU0JViv(*rfSt80LR ze%St{8m1?JZAqCNV*a@5Ckx^Mgr~s@tJcwR0NWKoLZKES-*Y9-n<(-p=X8}Z7PUk}` zM36+NhaEG*jMb#aJ}U!^b(sxR8MrUy#ZB4RM76hMXpnDK^QT%+8S zmQe*JX~cl=-nXxVXO&T%4U?kb zn)d67d@nQDmf3#e!2D)74=_P&if`frBn+p2kApLL7#ox5hZFgrhu9>k)RQX*?t+E3 zfG1lYbaV@3(#E1e$q+v-mqU57q_34)KN_UZ!5nB(i&G1vZ-pmqGNmV!{+Y%w?2I+H z+nAWNLZ(Zpu7@YKS0%TMT6iFrXS4EHa7Pc@(<&Llc5$W}79-ql%rfB9vb)(VVZ#+3 z`D%Xc@pj1|KEyWqNv1cKGGJ>ZJlWQ2&n@(@LoN(vP${wbYeAlGhdjh)s%2#kS0Und{!NXwvbap`CTxg9Qq<177Ivn^_7KlLmD?=HI6&L}faME=H}A{?qt~h9(Fz)ku_hm?Fbs-HxH@ zLGXKLIhbW&{6KEnpU{MbveM89oH25Fa3X_tnp!@3ihNikWa~{n>=Tq4;zP`>pZ<-a zDPvcMcDJ`(tXVB95spJ%hQD~+rh<6TT^v91qifYy+gwMU1aV`?ZP_qLU`RZ`Z)dX| z0)HILh&s+P24?;!iefPpK9GxEMOXgE;>=z7WiIl=K8?j`oifpc zFd_piVCK4gr7Er)45yp!<}SLyI6yP~&yHs%67&ken~cl*)2lVqKU%d+S}HDmWir&Y z4y`8QQ>RkJ+Q|Yb7DX^oXu!m}a|{o9{HX+d5mTG(B-c;;A#hTWnk za92urbTL&8>}1FXgD$cb2OWRABcLo1exn&+80N;r4KjckV?S(4`TiuWUCspQj%iy} zz*CoyMO9}i_S4DZB?{y}aAL5-frS0ydC4ziZvGivGgmS5FP7e7Aas4}6$g9)icn`VvLv}x6>RXcig z8ntRek4E$LGa9g>L5W&ZXf#?hr%J0*^Ep%~Fkn^_2K@@m7qG8vrvdxv_Sf5`5``|c z>+{!EhEeANbto}kzM;T=x#iUm;X+=zwgO}9%a>udKYi(1`!y*wYmcX8`*llguU>Gk z_M+vBvd_qFZ}F1&E3#Uxw14qJEHUX@{@P<_k0lyS?k`=wmXgIrO|I10YOO`%rB|y` zU$}7blI-g?FS1b?6I45t;o;)bZm)gH7?-bVz^tdeEnDk#!o6<+Q#cj5UWQ}GMiWX< zQENE|-B(IIr%QA{y{DFc!Kr20b=jrn%PqL*QXO;?GDci~mboXDT<<|g7E=@cme4P` z^b(nfg?%>-Hst}snJ&=9ww7C)33U}Um=#7=VM_JaAALqiR@5|dxiwWFg?#8OOMnuO9v6^V2ZPH;LD(@Zq2lo3ohN%Rmx zKD`x`R74?VR5b=#rQ}wECRJAclv;6Bmw|B6M-(+W)g{$`Y%TSuPtq+FrBz5V^;S~< zWol?qSP3>HR89h>k!3Ujql=B;`B>RyY%%I1FP?$c3oyOpLKaZgROqUrtO0Y2F0f@s zoTr4zs!);+_NQE0Km}KrjYP?{kYt5zH%)WpC5u+A4R+fYQ-;QdBW;<|ir918ePtPj zzHsJRVJ${hnXi97h0A>lSsRsBMJoBrQBOYh%UAzBMqH*+!8aB`u}L-@aT6+eVR{lS4`5){sjI>7&ZOF=>=sn+45e zTmxRE(O6Ux_LHH@-G~0rSX`R*b3%Q3O=d+qp^0WkA-R+%n`pnOlAPy&RHxYwjm_sp zW?4G-)o48_(N%pjg{aSZF{-LrQUTr7p_4L67nqo8x~ouzhIbUy?i$s1(58C&pHf8~ z#cEs(K?PX2IY(0~d<*`>7-mZSd6;Jv2CIvGKOY+!ZPh$QS~Sf{lyAjg3B?O85!00@ zlP_W$%|ar2n_R=?Hke?!(gQ42x*c}st8?4RMx=kVUS{u9y{2v`z%PCm@KWMcXz)^` z<~UT!L&atsP_!Bb7Omwq?8(+t22wGC z9LPhSsfM{q7XBHd0mUsv;g*lUla>V;tyogy%z&8oo0M&4BKWyrc=+-60s(zD_TH(wh5v^>>eRpY!sv<&{z6A;{^6I0esu8*vl1?C|yH9y+bR88U z2s51%l(w?LEYjq{K93qmE>a_%r!>ZQx=2y2E_5CW77t}f+7AGCGdONN1~El5k%+ca zK@(weE(i*tRTw#lh#WZkXgH|&$jMbLonPbtY zJS0-iqSWZTwWKgEoePf}TR4`b4eDAyff9ZUvyh)K^D0+@SwKQsNkpPSn7Fn%jp-#HeaRWac$F>%N{cn?;(yF?soYfVa^+eJ;I4Qnsa%mMJ5t_m znDj=hU}{qv6{FV_lE*?RX_bGP(su>)`n6eLP~Rvh?ZnORH^bddoiEMnzSAdrlx4C0q4cKm%aD? zM3X;c8Eb%|lolKHsD3X3SoD@DS7$0oW#CZ?Wyj(jf>sD+{=!g)GUlDK3g<1XVb4D2 zb3opxQ!lp3PIb-`z7YO}sFbiDF5om<=X|&7>2#NtZf$5X|*0 zB6q~fS7>6nGp5>=1T+RCAm}C#xz3XL1Kv~TvK^UW$0>G9;W`dd3JeF zgN)P9MxH96Kw4#C9Y$KjrjHl+f(n+3grFEvIXnD23Nxo_Q-dhvITz~Dg1!kJWd^gE z>zUqer4yI$;39VfNlUZd0{m{I*hkfk;6IjNCZaPRe$*wkt&hsIHi1X3xj zOysy&0`2aKgAj0ea61bF6j7ZwF{lJ;hGA)|cCR?(u;k^aR543ht{4=m#I(CVNpC}3 zS}lWwExt-T8=ewU-?J%kB5;BTe4zx9mxk$Jn44Qny98A#O6-e$yOdJ(=n$rOwPBtD zqTw1=Ej?lw&kbd;g+IK}O%V#y>|RcaRm`I3CHE_Rvuo$}HP5li#3!Rg3+u4Spo8jTby!l3;ypw+e7W&p=bYYnxb zl`}i#Cd`@jASAqJ1YC?N;v8!LTO-0IQ+z>sr;i|e8&{E2fHS)IJ4ySDG|`CPg#F*S zW!u`Gnn;7Y{kNh##8BhD(tiM7Va#3X%=IJ6=BtuL5F1K<{-7dp?L)-N7D>IKp%YZ3 zb&?GadJ9V$(+ZA?N29YK)j1ofMlKq5VwDw^(oli;PcE5pb#CjJ!q*?jBgh|xDNJLn z8|}KUHdU1)MxD33jF6PyB*Q%D^`R0P&R?%^JckQi^c^6V9ki7i+LadW;WCj?G8*$8 zu~8inQy9SkN*UH$!Sz~F$1&zXf9(Mk8Q5py=OtiAX$k{$hQT#dvuP}n77-In3KtD- zClOA<9|vM7Ug3U5W;O66KU5((bEij4Q#qINY0L#iXLM#NM;qGt)BU2fq^O_PB{azGjL6W`==D@QLEQ%{fx zPu)^5K@nV%!EI7D5!1jTVRd&`vQ>u@cr0{>%4I@c#3}GZK~&)`Yi2B&QzA*VW`d^> z3}$ABM|nVDQiI1_7U5AAF@^7ic-CNr%q9L(Jb`&E@pxhLUPEM3T(S}X_Ff8MhBNhT zxdCF~K~aN}XnYZe8excx_cc(}H$1X=GIhI}YZ=cIjnS1Q?nh+4HCItYqfWy%+yJt1VHG`MfGqgcLQlpL((im6y6*%HqnGzQ>!(_b$VWY)j*0?i|0Tukw zbU~7qHwG0j2bRgvO%(`yKS(MH^M7}@8W++k4tO0HgCjuaTIrNxH-dh!r7My%4LSB3 z@B)D&!ZS|BjFpiZS@&f?LQq*T7QQ5QDfmUZvu@!AVcBzruaXUO_!z|^QC*>fQ<5Cb z(_M98Ec$4BG7=+c*c9CsR1KpT1pY}VViO|?i5v^LkzwMHW@9&Evk{g^kqnuIaH5g> zwUIQ@kr2reVKb0ovs8MRdZyBDm!mDw$YF&tctNBX3nqKWr-W-aHH=bwh=UZ&6jnb8 zUacgQgCQ0ymxG^XBLMQ4>NGBsaYH@@E^h^i4I*K|aTG4t3%3w4+0kXa!5iy0Td~3+ zUgnr1ICaICmQ&Sd-b9%WgfFcqP~sApAG#^3bR3|W8_k3Zj^Ti*1DGZXEy}ntH|ie- z^&UR8Bgq&s9QPebX&W~}qXcms#$jkHcXWLgR?TBu{zI513NPmf9EW!-OlFA1gd-Vu zMG*61c7Z8FG8ML!B(vF%{yGO#Ut)xkQ)`zphnvTTS0px;(vh~=g*$Pa7y)0xb`)nL zc^4@~fvP572vTB*Ce|rZYmyORx<~rhhg7+CS5Yq8q8A)yMT6I0qz6O-He$Ng6h6^K zrDAxRC`W+EeEx)INOhm4B3HWTGDjLcJR?~jcuH8}7WsB!!sk}ecVYyy9x1n1GXqu( z#9IdQAs_;0E|OUs%66zYa@dnlFt?Vyf*mM?e%?hSgHkoYlrL-vEwz$5{-QC^!hqG7 zEvA(+v2|eOff|3JPBF?cEi$F+17UvEWl8~O$dw~6QamBJbzGMiwh1@}!5L0ko1-Nc za+n`pDjr3s5lxZ)T>!-p@QHc{8zYVrr(vkDmNJlMlxwP@oR}w57}Zh8cbsu@vS(uy zBjtoLA*h0CCme}iVv;7%xh9J7Oj!1ZkIIrrp(T_mVPHad;U#(=rc;F?n=KPTy5mKr z#YURauzi>oE9M|NIuTfPB?Pw>Nzq%anv|K%**TsTq|NnrP6C_-YAbM{lYL9iv_MY>ikTT6Hb_BxV6 zdwg+AURE_t8;IA+yMXgnRC`YIbG7}(B=%ykJ2!s>BrcW_FfVgIG;ez0P|_*w;WB!T8mY5wn2e5B3VXdF%<})FA^2D zaSNfbF}Hz8u9ad1NV(&Iqvj~C=9HtI`zJ`ZF&d{ZrbW6mA|vDUTop%Z%$Qny@fgN) zgZ|RAtw8fR3pr0QHesXH95RB%JvBLP>bq1zQ8UDP$RT2W^A#m%sWj;|*tU-y3r0j$ zIlw8s0#Pl**^q2gk@Tg#<{*Yrq!4QJvU1XqUpPfjG7(~frzGJuh2kkrQK=2KQ^RE` z8PQPGMJQ5IMmf1{ndBgg!&%YsBw%qvY-DD6N5G@;x(Rbn1|b+4CKoFQjJ*YR+VQve zDzC*7m=Xprxmv~Sfl0a0!MQV6P+>qAQgF=3TjMkk1a!CNI4PQhGB|QVFbtr!6pKgc zfS$WEWLB2p13|SCwkiX}LhQvHV#JagqY0vQBNL=X`j;EXI-%jN%y>+Y$Q?j&{z84` zR^*l)6*n70Tp)&5Xe~1+XXdGYND(~rMNd^h^5hnh+I?WkCw{V7rnjj;bzlyK#}xL_ zGS-iO%rzYAu{vcH(R(H^q$NW!kwJB>R&)dY6HZQ}bx~23 zFS8l9rdY|8iw>w@L;*1^Ll=B#ZlIE#Xcw?F^p!M{4LO&5gELVBiK#E`yUuHq4wi#x z>!z;m z$pjzbthZZHbkq?V;6xF#}yI>@>x_(b_{QwnJ*=1VMG{YQz5di5_b)r4ik*$>AFW zxEZnN9Dy5FIC93HHl`wm+?E41m3NxWjS%w*-Mcdp9yF%CX5F5L9ElMW+8q>iE{6@_ z99b%3WlTld{l_B~OcL17priv zT07O@9=t#cd0id0667vCS1ghX*XKtp_Y~JBt)I5!y7eG620`7oO2P*u6=FNK@#7Mr z*cvkAvyqJLENLM_9NZ-D4t+>{(Z9v>KalaQduBX13jTDK`>!I>*urt7yXT?)cH4n@ zfd5qCU7;jtju>uiS`6PXNNZa^h?940MaqkISgd-GG#fyHX{P6zw;qOC#PZ1DH+A09 zP&2V>-li@|zaACnRaA!qsZwjR-b8d?LEUU~^646(Y#S-mcS5L)jC_0gQ4m?2f8#~} z4dCL25KYmr7%O2&Yhb!wVzNfYy4|AY)rZGTyv_rI1Z?bmvkh@g7{Z|!jzS&2!Ys7F z_ZC7Os8KE!k~&Sd*p7i>Ypcsh(W@2GFuwp-!h&MLCoiOROX;pl(3Q8hKnt)4ORGdc z!?fA}iaNAF9N1nvHqs$t`G~cQONO^HBOKed{_#n_wOfS7#C?l({8ME|s$4*FmlR_d z@Uo+Va*7hQOlvVuq$4ef5>}2HjY)x%)V78L#zW{vw6RFEo+l7~0rNSf5u1A4o0`8s zgx^5}HG>!SO1}?TxLiE;&vOissB8fJb+&0k}sLlou*|KfhmJQoL zf&>i?G>8qGwQ3Qg`HE%@qD71rBSxfV&0sZZ)S`{7ma$s2U(rgIbZ9N)%aGGHPKzdU zW3_2HaoWULttCd5*lw1D_{-NcYc!()>qT*+wQc$G0UHU-mr-B9B2ELw>(;Gbzi7Fl zRhAYnU9l+o!ljEBudFC}g}rKR;#FV%aYyFN8Ld~ZiMQ6~qR2Gw;a^m_0!wTanANO~ zMdoe%__42BXD8~?+V$;cw7T-TwF(y)E?T(Is`jNz^;?R+;39skGTK|2G-=Xx{cD=9 zxw3V+zJ)9CRbVu!GH#r>Z%xM2L|TT6lv+;4z^*@*jVtzC&(N*Itx2uqv|rS)S)GPW zq;#%1!*<-cI5$$e$)7%j=o8yS^&k?6J%|nx%A@t7*{;0lwszIuz6tXhN1xydf`B`M_c zysEjxe#u3bUwRSDuUm4FG_TpHq6RX$Mgs1*lpwp5vhGp~OEmO`vvs((-Xg85tWfg~ zEx3S%jWNm)bM7=;l6wrh$9ORgy-HUr)GFt^q}DRU%3BsE>W*>=yQ{9t6s?quYL*xA zgtC)PY|!gyqV}%it0we*SqVO?6r3=>FXMFcB8Qf0&mw~SiY7`Z)iY?M1s$r;y#OOT z6)Bbs3W>rvAN+G{~c&fwG9JFGFgxFPTO5-k;)Y5C7UjGsKbZ+z4BkC z*;I_Lh{BQy+r035&>X2))8!Uid7;WE*El<^EVsO43tdN3n(()|aM=AP^Q^*(t?K4tQWAqod(8J~Qwp=?M^rcMGB{fom{hV0EEoba2 zWwpzCGvBC{Yq)9i(yp)0m!gW{tl={rHwvNkZ84+f;wrJ*(4F`p#>(@F8sDrT@Kkzh zLP;x#h)+o;{xYeG=xg)BHjt#R3Hq%fjoU>qTdEuuyL^YLNzNjNB&;I<9GXBJ!Vr`d z=w~Vl8P8JWq7`AIMK3y1%XZeJ9?`63BwG@nM6{3`sVrv*d{W2`__J&TjYto%QAYY# z6OCM?KUkm+Lok>^p{)>sUjtE3)bx|5xW|Sr0g3hiC?Pmu zt(qBHW|X1`Av7V6PA);BMrdLl8E!;E*5Z&9^8q_v(84WryA!E=5faS!Epxsh9VU%A zjiy*7kiWqrVJtHdJ@vvno^oR^6q%A<=;AL*6&_6}lby{xl8>LU)gW&H7u5)oZf^e3 z6(jXxr!9_VF0pe9a72TZ$lQ&R@u|DS1Pfy6jov< zC#|tf4y$HFhvXzF+G&?_l(H0+tZ95YS&5f?!Vno+#2_>w3Np7*jkdAmBm?uM(+=b? z|ELE_(isSg{MjU-jHqQN5*Y)1R3v-4)0`SZCvCsRvJ#e)M)Bm)I^`L_8u84Z1Vk9k zQur|^3C)`;{Ou53xXu3b4@1&PUQmQGlxbpQn#)v^M@8vNAI{{LGa+W2L`xFLj;R`O z%Lmw`9df*+$&@Z~tcNKpS|S4}=^(3)r@YQ&S_w_atY)rRnuuw} z=cP}Mt#zCM2}mIG9Kg~wC)8+-R&>KH?tU$o$zuWUDh2J}HCqyk`JEXwiG-HX$cWnFEysL6WUZ zXFh^Ta?1&&r)lgWJR(Tg7$V!B$q3H}}CW4B0fRw!sNBUB& z+Q4ZiLlNmkmwA%oDRU!zqMKFd<}6;6q%^QZkD^@MMbmxCIlp+Y{!VG4C(INkIhL|Y z;s}G&uTUfxp=w-(@%BhH{yJ|h))iH4g-2C7t1`qAWk?+3i&-!#P;@a0Yr0q)Bc5kC z(3wz;C-S`7(9T(F@#J){vYfO!QC`#AH$x|Ll=nmmB;0Ls@)nd`q2Nb4?aE5|l(@}| z=kTGNrKg8-=rM)bq?_yMUQ4#ru75cb(k<~Ui#}ToN~`FE2(hy_b;Ra$j*~<_NfB~I zMCjYX6N82pE~15N5Z%tR(YAdjb=iy*+WK?Pdd~FES6JRQSJa>x`c{Q(6l##G+T$C! z(o9IQ@E)?M?dt8{N{)9Iqx?m1(a45WdW{*;cqbyIaf^cfevz3D9}*>lY}8ulmM2{- z1=`;6(&n=Am%Lots2~2ySK87P-as-jz;4ay7)e!g59e;azzMmGXFP1FYBXeli(AlG zZ%$$Oc=T}zZce40Z#gwRIH4DF0M+9%8P86qDoj?AH!nx=bt`QHpL~E(zO->liONyA z!ziX8p`^s4d?A{{#0aJSw98YiU$$Jl2a_OA-b8f zG!Fq0g2}CHkcoC85tmq}8@jskM{^*d!>2@ZnF~CJ2pka?87_o)pbC^A z;c}UPG9dF1nxA;a`4zwzb)+no6aD`Zyi?<+2W}`f)%AO@V zm0z$3LVPScN4#pr2Lgc8CP#f<#iYJPi0U|y~%!qn%x8+C-iKxWFqnDr@#fzbk z#KI3B(g}jnvZ+Cu%euOm(2%?;xod=9*bAY^oi{s*MSYW%JY95Z(kw7&GRD5^&4`m`5Pj{{*M$y^xn z0f{(y3G^#6aRkS#;U%Bsj=MWb%;Uj4={AbYM?F~zehfl?{1xkQs;u&!*HMiPgCy1K zvC&XShSZd%NGggDAIzA@2NNn$NkWbEn+J=F{z8u0Se1vv7V0q%L(ERj5HY{0okpoW zMtYq;5}xd+$-QQ|JE5VovPj>jmx?YNW1FbzVSIO9nVyLhSJ!L4AGee@lfe18R_!=k!?84W5l2anjn12k-{|0WlYQoGBkcN#+PZ5Sd^{?sizP* zI?D{vLi@Q?V1Kr1_ex2w`U?qIy5Y!%01M5Hhm`ed--IhOovzU$)_%TNyh2~Z$< zlats<6Rn2%NKoU+jdz&~dVz_lahO$$5Y7^{)O0z;SjUr42|NR!h>$ISvAGegv=U9V zqQSp;a;E=ikJtWkkD!5)F>4pSgb{VxCv!cBzw|l>M9fF?Q65#sW`sa@ZINY^z!wpx zCOOid(JT;5(h9l}=d!uqs;z!9QR~VH?K+~0P{BP3v+{_+${Wk7pbV5aj4a%?jie2x z;G7-QN37t~Htk14p_{fio~|ekl>jkC3XQTdtGI9$EVLECh=xrmJvTxlqH-0i=m@qT zv5`P9wD}L`YoFVwh{Y(6=@}l&Fqhk)yn73{`LqnmIF)Q$4(_P8s{*V|y^1-J4g5?F zr(j+BU1$?b0qJTWSKy~lVE2D{k)dY&8tF|A0VthiON6&JlgD`0K1Oj_RfSPbR}qe3yC!x*H< z>#?wq9=>Re^uWonUA`we3FN4b_<)c~W!raIP9(}J#+cj3kV>c0kB!q+TG|R>jlmf6 z{+9SyxQ^ne|Jf&SM8Ieao5gh@gjk;x-9TGyFZUrc|3I^LG%juW2${>EfJzxh0}@0F zF2a1*(^Xx06;d26C)eemBx%NW{omD zxZoP$p%){|3!y5GxlyFy*p$T4ixWGFW>pn8@?UzhjqZih;>b3vn44R;zP8XbR=97LO3I>vLPe`8fSGqQhBQdAZn3DHgM}8(cd@BP=Cz5s9m9 zT2iSZNr~V-6cN#((l+z>yM=p@1F%A^g@|HNAHiLvyE5`%b*!5f}%=?U!q(H!oY z7x2rtIYrvD!Aib8Ruam_9Ep->bzG40GW2uf!kxJGai8CU5-C#{AqrP@;*zOLKp1(t zL<8eOBi(5fCup?S)K%RZZCw&E=y96YVLY@KiIE<@G!G0RA{m((MdKjhG>aKtyFim1 zGN92)8(}LIEuo{l%A`CQB#i{**QDOc0~gz{jXbgGshm{4lM35owyP?R+QA)bV79Y} z9@tQ6JsMNi^Pf7hC73geq^$~v0I`aAqTdkV7=uV7VzNzzHDYND_3XI$(a<&#+x7I? zXka|+BfB1miRyf@i*>`wOybNASqlD(2-Cbc56-me zZeCgjY1T|?52TqB#Ojd#K!||?puRk!B3aS2r0g}}5?RuX3ALzvrd$yuG`S?Rer7-% z5z>Yp86h2LXPm}=en1z=;?zy(lPTSYw(ZhA869?!pbJ63Os8s;z&}$VzpeYlKv0Y^U#CfDar0nt8r|` z=!hAXmxpOZ55h*wX0-I9Gb9>O8Uk&r5p8@1In25!B}F<&BM5>iXa^ipW`w|DL`JCV z-O_D3+-^oKe(iFakz}Mgdqoj1pJ?5(pa`;&f<^AH3pJsj7MwtD^?3@NOhn@BqLwnT z9D)`;X0bsQTeP|En|`cL$zE#1ile*+slb+*v=azv$%8o!u$Ugj*}d1hV0)q4-FVd# zL$JkRz4hP}|8dAXX$+*Tj*A&^UqWDLTH3o1ELf$X$j**KOl!elnv2ybRkC%e)QGNd zGT)CllNsnY!PfSEv+F2Wq|Eq}@PsKUaT(B49A{pF|e38BkLVnwr@Sgi1~qt{~`7hhEx>q7Bp#z zt-zM8uGE(+A0#FyqPQ}9A=iTdt+Sbz+VB_t=n%i4)NvS;6V7}=E0GB& z#zhiJn6f14m3wC$;g~q~QvY#2=P_|z3Tuxk)R9p^m$%hmwZulfx)&xesJ$OdR={at9Pa{yT0GudQ;hn zBZt7XgfaN5pnHg9SiYVl+>Y$UCL z9N5xjL!v*iS-WU(WlL&Ry+S=&(jv!^539Dc*>EVfZP`k0gc=kp+@M*lZQHgo>D;46 zvzZf$t*_g`g$HNbX0C8vtcuM+jx5>SxyqIEQnq|#lP6b(oSJfA zz@|R~KK74RAt|=8^>PU8M4fEW8 zphl8drN{I|&=+&^9>Y&>Nu7DLUju*j>LtEcu3YlT->;T0pZj0FcuiE$NG18A3ogL) zmJlx{J@;RO;=wdscoQ+`UjBRpQB;jDxro=FOwm{q4Ra82D9}Vt2{jQ`Dp?Z~K?J!( zlt}~4NDwc)v?S6)Of}RHjM+&?jW4*&L{^I;wU?n+Rk@T@M<0bWl8-v6#8Pr6J?GFv zM-3GrPhLq!5mM6K6jw$hVN?(`PQ_&sj8uK)5>#k$XHt_GfyHH28yz$fQ6v#3R8x1k z^^{*?1t*k4dF{1PUq=lVm|Q|xv)f@obq3ltj-7UzW}7MYny8C;R+&+mtyY?5s(!kf ztF65nRHw5gHkod&YZvd)qo&{@{cD^`eV>^wk%ieKP5n&y6?YqD!+d6{yfkL-Jyxf6rlgkWM9i zJ5fjtUT6_X{=JChL?MnSqIe%ZRH-!!_ejkzym%xPOf?Dw9e^9J#G{l1v6LRk!03{% zOWz^s(V016s3k-oc0?6+B!_HKLGC@L+(tG59FtT_!lV;aSqaS|pk7tvl}Uq|sZ)~` z`RUM#C5hzYP9aL!D_sX^)Mr){{oGhm8U^Lup+F}ZB3^ItRB5hZMM|k%mELBQVV&7z zc5I!pYFenRuJ)K^ZlRhg;fsrgYG$RfCM)2JNv@b)O$C)$u7mBmsho&ml_<6)VUE?V z!2+6|Nz4)cm5_KRIrLODNm{iXjMYl}P_`J7H{N|R?F(UcL!JwuO!mch%PscRw{E+- zK#$8UL^c~9FY-S0OUgW(n6P?3VfW$eGU2zAS+q;%ABfZ~m{sGkeZ#-*a(vJu&6yLAsx#OVxE}9 z=Xd1u9(CeXE?smhUA<7=E$Fj6`B-B=DT5c0(z7%5*#sf^s*cp8b3p9uYkj~Y&rZmO zK7TEYeJrU??*<4G@nFp*_nVRa_VptCAtYKfImnqpmJ+MHL?sE#+F6_iyCofPYoS|a z1)CMX7+D8+)jQ>XFa{kHkq>_rX%GI6Q$v}lL_m=G&Qw@b6@ZncMlOjU4Xs2sfN7*A zqeIc+ur&{tF|nrEv5lntFFj6)TD-sUL2`v#{PH? z$Uz2i8zoh4pm;=wSW2Qn?2VY_!nGe7tR{kLN||(5vK)y}LpX91-ZB@lu>dVhDjA8jA##;I_VgIL~@dbe9T}q{0T=K$V`?< zv#~j~*)4+!$YGyz9cC)F;c{m#JL{|$Shn@iIIv{ zSAS+DBUXac*t)~hAr6o?Nr@n_RLC17a_%5Kb)w!N)zH55g>tpBO&ML&{y31Xs-%n4 z;^DA3(l3fiGbp7TYe+>J!MMe!6u~Z=W=fPb38#*V$`Bg`HK{VC4ye93Sdqw*IlGkb zg<;YXbxtIwr(%gYz__0A()pa=UC%&T&8qUidW%_r)sq~G#x?dM&LL5VjsOgbkR}q7 zojr50C<$RN>`K0Z{p7_F;^RU(!n>JlXdzEYrjIr&|Ff8 zvQ)7zIoX6uy5mY>w=&Ygt|T-HSZbpKll}n7OAbAbN=)}1s|}Glz`*X2C>x@tdc`I& zWZydxm+@J;7k6ww=gN?B1~`i-9|7 z;>a7`_HNqipwj3S1B21rXfC6Hk}t4+79z5pj*y_MZ>?pS>o#y0Sm+W1ORp$FeQVD}jyn4Az_o#6|FlR8R?cG}RW05DkGum}SM7y@_@ZAE1`zPZATk}Evz17JG>?7g4_*l&5kikV;zzxK2vqFH{s@HfU=oB}L=+|njtI@gtQ)^n zj~YsuzxW4D>{Yh}$ie`O&Y+{b@Pt!Hjg|;abez`sQKZn6g&A%Q)_hM_tjzoX6wnET zvs90iF+@XRO_hL&j)cSlS>nHpV*Rm5PwXU4P!!wn-U1PXT7U`X9h$Y!-q^&*(K(t% z(ob;V#7r8}QHUa~t(jqf1Vugm*H&s5R;1CE=nWMG<73DQs7xJ50iY}r&ek=K){(~H z0ORCL4&tzstwc(y{lzkB&ZanHZ$KlW+)=HV&Vi9&Z)nLwHC^8MnvaY~H3VVHMWUI2 z50h1g#_*t&VMO@o0*4&WJ4OpS$zw0P%Z+^@gpf_8n3(Vwi1A?Nw`hc0El9&94g0)F zPEh1aoLN9M#a&U}vd~Y-?9)NON5?ekX4Hl3vWMzEY>?=2eM{pVS%jvq~fXtqS`NtEv;2Njyx zhNKuBfzW22%R7$PbW9swg^;tACWZv2i488XVW)H23C6_;M)+BAI+#dcg-%q*l!#m0AOwQk zMSVf#uk`1PjO1`o8BtPPpCAQxD92Gi+)9K@J{p^lF2!{$P(f_OK+MU}fCVE(BSN9v z-c(wxV2as=hW-Ga-KwGydzocQNvKCHhA(auY3QPO9pEl94sD2+N;RNdVur5%&1h^_ zh@xQUIKBlxVbrY%T3Zdse~IIacB8T&guE!5AMNOqT*)M5$5d^Q8}CiaIwFtzZu}L1y(_XY_iY}`Z zsS&Pt{#Sf~D6WWT8J!Vp7~`m@24_s2tAr5)R_GY9Dz84^1Oi|(+M;Z1*VHyEvlhy; zrVV~g+RC*eiyl~U=^14u)I9Cz*f`tkxQ0w?)#sno@fF zAcjm;x^NFkfLouC#JrqMPDo5lfC=9bs`^}nz|P>8Zp>I5`q`TJg@&-zj?P-1$r@eU%wQFg&q~mr z&E&PfkPRUQOC;Kx!7R|6&9sab%cT+}zT%!?{8+F1@1wU^ia zMqt?fuWZals03@MZP5a@6o#7Rs<7=W5}+{xtF08{rC4yY>Qo~J8kdM~HnwP&b!q|4 zEceYQSLk3L@zE_z1m<<&Qwb>~$%luC6)xPMDUr(&#z#6CBFT0m>7B>suFIMx;eH5L z4hql+3FS$e zZ%B!_jzD-G?>?0&*<&X8o6$3U1X5K9t8fZIJ;P?(9V3|>r*9L#TS z1;$MUc^u}@;zSKCCv~v#NmNSAQQpty8a64Cj1*iLLtla9OGchy^7Uvk(-YPJg<+mW z(QQ*$s2t}t3JNuE$-D%|rNqW!lUgm1cQ&%dPD(Q-vivN>Mf*lX847`hg(NpbcNEL% zWOB3CbhFYjT?(s##zrlMa;;dA7?o6DNGRK$nye0WuR5>+b79;IApOvBd6%sBj9eaKRW%gF%Of4S z)%28Uv+zuSn9LFLu$Z0GhoB7etP8@-*M5i`U$>ziL1E@~G$+avqId;-OyygU-v>^I z^7iS!zD0!1#H<}JlNdw>wIoX#v^PPEWAT^HXo+voP+iEH5A_iGd8J-3@qoniorS4{ z^v4-{^jC}pLR}-Rif+UYW*e8Z(l0aS9jqp62wJTt8wMz2 zRH-B|sH6t2&`NEDok|V$TL!f(!!3HB)OIc7RM+ycrrK3+gGS7vUH(YgCW|0T|Kzk9 zBIg*2cm4#KuAWQi$l&@EgABo>!k{*%UF{* z(o9^mm>fz=D~4`7MB0qx8@ZZAIo)s2VuQw4TFN4@Zc6{2Wl(!<s?bu!`xG0sLxkpwN?jTG(W+-Af~O*erXqOJ@P{*4L<0qL-UyLrgk^YL^N z=;ak3-Jn(6qqOyPCe;Uo`}LiA++azFK@8A{>rro02lB))0HNlJ|48)=%Jt1)Ju%J3 zHFBe%Og?f1*vOvd`50925nC0kg$UW%Y)~+TmWpBKSi}U5Sl=1KE?f+EPYlLx(@maP z^0+pY+}QDTtW%nz`2Ki^VZ|v=)TwtIhTp1ZecDu;RZM$8P3X)lfzO)$;a5heoNoED zCWocdQ5sqDxhgMcrbO^bon5HessXR+EvpJKl2={M3fH1qd`)1VYxQ2XEz8$DS!O!g zDYvB4B3)P)wmeE;4|5;x>^`mJB^CsKQg2ScM=A0C(d$GNyi#6WXY)O-YadCLh~wO^ zh0_jqbDso>kW_B%3LH@AYr6Ugh@g&aRZJdLOLJ70#F7Zjc%0>4OE%@`fZ4IUI0e6q z2!JLp3dQs`4UvkK1s4BYof-*)@$AH}??l(dUiA2tzr`d?#qF(R_Hta}T3>g7XU!c4 zAa0z?7`MU`4gKWpN|U{s1R`<>f9Yh4!9ht7_u1g`CN-)N>on# z>0PlNdzehmT<(m4v-qspP;C9Tc%wTVVx0a?2cM88{VcB8=Y#PAM73$v2v)lZMJ(SUJGY4M^pU$=fG^aac! zu!jq=S&KF;V$X-!uuTIBk($71)Eb8Dm{H?Ehern*1*z0pwv7W7R*MF5BU6FYsKJcJ zG+VV_BXL^&y7AZ9vOlMN6?t(Y-GK>#W{WD3Aj_j(IsRNolq}MMUJWwr`LiLzYYG`= z%QpF1+G^R%ZL|D&n`h4EI(zP!EwpIMsm+};XPGqY&aGWXno*1HNfGsD_2+l?PS_Y? z!omc5`!#xh;!=WjTb_kjH!Jmsa?Bx?;t~iYmU>x`uE0`Ste~$XjOLdLqlpMZpZfD^ zqzZqj=9&--B2cOO7=nqUUOuD{q-^rZ$EdCfGRi%vDsyNjYpAKqC}4QeX`-@F+NhVG ze9pbIVoD>jEcyjKzt-}FE(xo`DNKrX+_5D=-=YXdhP=8e!mk>26fJC$dT7$X7L)J7 z5U;^T(-7;^^s>yBb1yT`{-7&u!1As`4Y=Y)la09AZgUR0-L}cfJL|kVNLE*y>#Vm^ zmy<5oUqLI4JECkuE?4lvvrfF+kmc^zX|IE*ya6@ZOhC(oGKwdn=t~uwoW}KQP=#=m zsIH$LN+{3g$SrWfmqhBNtw$k*P{NjQ0PwdLWVF@~>h8FRZg;U=F)7$_XDD>pY$&{feS}3tGr4t^#Ap z(M1>IX>5H1{_5cVPRk3?GEKM9^tDYd8|}IfqcyeF(qiMAHd9?gmAc+KTPU_u$K&<2 zZFox#SXRAzt}W%(SEwA7 z+E}Q=TzBl60&!x=-wOX-?5cwwB_5=>uR%MSXF9~mc}>p-$)OY%Y|<-|BYtaZr3@=D z#-`flkHQ}b+HvF+O`b`Y_;oQ^OSgU!38QKFL~N;+wDj6+|4|sY7E?YlwQNQlX-S1P zQYNK2&}n6Q2!Gb1l0^|pCGJ^R*3w2c0u2pE*b`gAkT<-mFp40-n@kF?7me!0Z8h4^ zM(18wD!TrKigcQjRe<#g z{o*^v45%{RS{DfASgD|7R6Pizrd<|vAQo2BdEY$bHB^cWm%bFHF+C(8ho{Hw-69u8 zipwPPu_1}2?rJ8os5jCAPMuPcj+9I?klR~II32_r!t+6S|*zs-E9O+&i^_7`+GejyiWT&7} zSi?d?u!oGPOOMf5#~uTwg-qdhP8HC@4e=nvaucLtjKB5=^!+V0}9PGLL;ez2uMenO;&H;MV;0C_L&zojYHDtm$3mx zMs>|hdP0>~ej(C&D?M0C7mEzX!UDYDeZ_dmTi#c|qOmaD-c3rfpd{r~EjQt5HG9I` zG{Org<1q?#HTKoU)CeOBV~YOHvece1gd+}-<;bQ%lv?)aCKKZ-R~ksX19i24V4a9Y zfN8dUfsJBfqmW6rrjuYaocsQyqUy(NcvQ6@@2Fj|- z-u9meMF^G4Qw&F0YS+ z+-OHXdKE)V}vH6J;)g-jllG33(^^9Tl#O3A2QaB@tLMmQs(iO7$dnvnB8b4LkrQ^wJzaZGJ|3`Osl z$36D(k0IS{ZwGl4;0|}BiM&nThPj}j!ItXsx#eS3C!oq&qOs~s+SjNPHK=tQlMf>- z-4b-RVxFzA%FLNFpL2BL%$B~d!&>p2<~zqqNL{Ui8P7N`(AfU8Uh`I%Lcn$^+J$^+ zqH~;WZ8I9kqfq(ERsQW&th~`fMlUknOKD2KNr5~aQ60m@P#O*KP3e*APpFYWGA@;C zbLv=C2=Xym_YYsGT#8jl^0W(OHLVj8$yNS`p+26m<6>LkWE5G$L7P3~BA+&6AB$tR zV|%M|)-K&{1^wgg6&{ zljJdrxrfUlr{V_Q#Z~>SiOA}VL2@-J$DPg`ovJv9c8l@5X&l+f{R(a)(vti< z#3Ex4X0PmLlzqFk=PtFs%lyZR7w*XGRqXhT=8Wt?=S0i2@2aK%+b4&EBNtw&aZhSp~J4 zqd1@=wrcC|E(^4Bg9AD5aIlQB7D`yOLvXlEIr8T80H?ZEkDFSLAi(Lx%#EcE=CBmY zyKYbSclBt(fCNa9~Wf=c*g5l_gk6id5~5ajgFkQNIPr>+0O zOCteL$5xR5!x1EZPYaMQ3>6R^gMk$OD{;qEk`&1c6<-qfa!&vot=s~}J%nf$nWI_k zEH?DX^R7cW_{KU^Y@9sfaztlx!Ypt;hcsr;%3v@lUBf9!XD6F7a=ha<{01xUMk{Bi zZmuC4H3b{P(whniv6PVQa<2(P@d?8)Bt=pSRIVINk{!Kp9Zyajhe0p*k}vym81hmb z*-{M2@EDTh3|rzFI1OVcCO+h<)0pZ)N~B-df+8g4WBOw$V6Bnf>K7=DnhNP5FESDz zQ8fAQ7&dYv%L^8E3=_reEngD>kq;DmY`oZU7+&Ev$7?Tj6E}176<`uLgY)fL(j|q{ z6xH(Db}z`#iwHeMCkJG3ZqWV~y@R#7C|AxY;utT>_N_H&usMjxiljxLjKy+lqc>KN z86%Hce5Ev8!*71*2eC13pdm!0!9LG&#t@AOlhY$LF&wQB_~J4XOS1RS5ii*hK@$`~ z{jx7lkwF8qE#ncq3KL%t^F{n*KQ<|=(q@&`q?6(YPbP>LK*pD(1SHl1g=Vct5GkfK z@;8%{6fe=yV3S50Z8k%ZHi^@`jMEhLl1G0ONP|>JVe}PZ&J|NkbfELzq;qh(vpQd^ zw-!n#&nXypRlBV~y)agblOvR+VRPR2K3s0`R0+tl}vK;{xUv*Pp|8^K&GH?y{VEOV3 z#|yHMVL=)ee7df=#HBZ;A}jRqVt)Z-fTm=40Vu>rC**1(y=yg5Hf1602}`$bX%l8+ z)&YqW6$ut+u}#r(ELedRNGWk=iWN0ZgxGA@9qyv%PGpT6$P*I zX`3kiYJ;P3XbWotju@@A%wk2%vN0QEXk5wFvc&dGuK_Y6t0+26pvp4g0H&Jpo0at(d(m{cveY{33nHUfz~wB zYwgk%8Gv^`c`O3KNj-UzO985!xOF>@R|SU=DhtZ8uy+NsHsHKgw!l}0q(LmTL5dAa z3A=I87;RMz5N;2Ubd%408&Gv&6;?|)R((^)_Sa7ZcQ1*-7~I(H&=^?L>wtR-Z1Dau zmx4tzn&N@yP}cYaj^JW*LufOeXM;($W`i?@e{>~XcXo+Xb6pJi9Q%3u#h3VRFji$EJ#W2H)WMjg|mcx_9bn#o%30gA4@;^*^~eIrAiZ^ z4OpN8xgt0D+5|IwRqjq%PEJR`q|dP(%h&;#S*2B)o}Jm5 zg|s)LS*Gv#sZG&P1NanSx<~o7fN`2FzG`3)#V33kQKrh9g<4YB3?a&dO42Rg(q+RqM-w}e+uNlt8TZ=TjgKL|eODQ>Tp3W@ zKD)uoUmVQCLCnQL9LB-S$3Yyz0UTr;8kC`|aV)>z*o_7L*jJz7MLGg9Y=%E7GkboT z!$20q$^ub_&HLK#891_%2O7gU`M1*&hDT15C9$#8^_Owjv6Q>gm7AY`*CM%`#k*XF zx3g9KwN$&$7EL9rq*<8h4zFY;lXM z(rRycTnWv)1h6edmwk;-yq(;PgY~w9_EtF=6-S)cW3o^Uwyd+<)1~3d!ND8Iyv)m? z99lgc(mfs2f!*8P-P=7K(4ic}0UT!B%?X#=@qI7roYOew4daBItpV0jsUnC@P;LT4 zFlAAqXSrQ6tOX0KdppFL+Qv!Tcl$YLEnee+yR80in9~XR8BqM&KfTMnT-3+%#l`&A z%^cQUoz~TX)?;4gXP)L`9vrHaffKV&@ye6I z+j*JAP2L;4Asonj9L$~7*IgahfgRid@B`l+;34q+e(+;{@cmx#6`$SFVI0;R8hqD{ z^Ih@{)kgswMy4Wi+2xF6#%kX1m56Rj068+yxf(>SR1r6_AU@*Li>yoBG&laYwVd{A zU%1`8_OrYga-a7NHp>kb8o*t~yFAQK{r=R;Jk|BS<@cW5XTIHQp4M-^=6l}fqhI=| zAN#Xk`?o*l(E%KEoivL)(Rp|_UV|Eh@;Swc7q2_EqW&CMD%qKR+!QdLSvdFj+kByY zrFBoTJzVzJ9w3YvOGdDu!Gp;JDqJX#STbc0l|h>bO`64S-Mo1d$5Gt4av;m0OGi>2 zJ9hBgy>rJhCA^m@S)QCplO4;OINjBp`A%KBa^R#vBS@?$v4{|h3Ox2KUp`>eqD8}o zt(rA#)~sgRx{ca2Y}>YB-8#+cRjk0GUA4xxTC{4{u02Br>y_PC#*jVR%eSvzzQ_U# zMuxCp;A6)ECqsOUAkxQ>mnmB&{<)at%a}7`ro0T0=g*))s~J67OO`CDx{*wWuG~0u z>fXto-E%g~+qmJ`#%-JS?%cqG3&-tlxN+a_(1FtiZ7yKJh>ao6)$7_ebJ?zEqn6E_ zxv*Tla}68z`*rW<(xXqGe%v{2agn`RMSs*N`l?jj#cOvVW5&`&$B;4o?PuVD`z?eZ ze|RZ`pnnW8HdteYRd|p>!(=Fsg$OBw3`G-JB%(AJffJ5LAC*&*Iwskq(n>1jM4V11 z<#ba`Hs*L^jWoi;j%?_NBa|}9I0Tt8#4Od+Q`1BP3{`AVrPX;^Ww%W>*lbmeSx^E4 z%vn%w8Q(GcC8pP3eSx_C*O8G)MkGOjtr-zz%4lY1F`b2`=bnM|>F1w-dPZ6rgjQi_ z6;x86}^8l0r#Fc;@u6H2I;byj9mO?T#n_Z_R` zndhB3?9IwvdfLnd3w`vZqMt(l4X7Z411@+MLk}(mV1&^HyA-hhMQf^Ja3aMaQAR11 zq+=3+IO0VmhBHoz=#*=&Na_42n@cU-^pcG;=}04wImIiljxM@OTRP)(0}Y!=CCQXi zz*IBMSJ^~qOI_gIIQ!a?on)Ko_; zsc!Ft>Kv)13iix3*;x0THRO@E6|Gm!YF>Hksdwvp&+N+UDtI|7?6Ai6N1%asGb`=i zfFni_W7ir6@S6v<8KQ{Xln9QACw^q^YURe}+imQaRNJI1A%`zb_l7RI=sxZ%q?<<; zG;luCSmV`J5ii_&!wGMNWyI7>d^$7qH;5i^usCbOQ!G~>-{Bb8eY zIo~(~h#H>#$+ONj`}xQB;D;~%_>VRZ9rR!F0|NZz|igj|# zJvSFJi2m_fT;k##Yc%XSSArF*)W)`3we1{onU8%|p&!*9Ls%Cq7T@|dpuj~7A&sHX zVxmPD!dU2VBpX>VB0`Z7xhx|m5=TftGC3C6kVTi{iRX%Q64}^mbU+kczJjQu(#=L4 zLOR^wmgKuF!467InizO=2PLXZ!*{o{Ml^nbFf3IA7{2HQ$Le+_kQome%s}2Uioqv8 znNMe<5r#L?^%8cF=M`34C-L56Qu&al<`AIn} zhnz^&Pipwfq$ZD}H03nsf20cFs0QdTuzbfmX*Qt<7WU# z&_1L=4bC8mP3~B|G@#+3evGI@CF+NYT9lCCGvr12DA7KkVUcNYh9gt55}rg7B#|KO_i`bMtxF2A;B%hg3^M8QkS|^X!%lb z0EDUu1(P@;L55pw;tVop_L)Hu3JG9Ef}+AHlajDBCOhh;Hn-_b(xJ7j-b{!7H~z&Y zf|!$auJc{&TvwK>V5vz>Vy8Uk3C~~jqF@)hMl^C!7rGJ8FBAbN^0L>bf&x;HfcVBb zRJ4wCtm7Naa3~rkO4^B9w4xaO!$r$i*+2GCqHB0)`ViVaceGgunMCc>{y43elil2q$FRU(+LpQiX*s&?p+42v8KNz%j{SJr)sC;nf#^dZa0byg?jg1x{VGD+aocvlD07rtSNzb?Tcx>6 zxLDStrr;sdzl67palrwpQolLY4o!Is9R9fdTJ)Im5at=p@? z!UWiZN!S{{_(hg(SB+GUSg~@$%V5lSJZ3Z_W`7PcK`Z)3&&;?T>A+(ibG&06_t?kB z_5=5ZTs@B#xsS3{vPj{<a^4F)C(Q_)~=`EZt;l&XZqJUd~F z8+}gfwKG)&jkd;h8g?dVj`CW_eE>utoT2Dyw`154EiE6WZtR9zUC4c6di4My+BK}< z$VpCv`b275Yg@C*zS%80mPVT@{}$YE-xR2%VeatuCEeOFVA-(L9kFZ!x(%b%+hRG! z07*;E{}xO{0RGQ{FXN0MYlh^Tu?%4>FW2UC_>WJ^{)c3De(Io)xF5ehar#=^>G0~T zisBVuB8FVjB#*u8%RyHb3jD!NXoDMd%81-J<-tb*y>jI6Vt2h3^jaGBv~v*8O+c)8le#!K@J#L zJp!e4Mb}Y%;0F;kb*D8xp;v+?D0&m+brXjKX}}Dqmm{4;5{=e+uJ?K*M}wI*BcqcJ zxSr7b_-oQI|+VR!GX$2d*V# zGR9<|C2T=H-V17Llc4@E-j%0RFwq?C>8@rVbR(3D- z7l^lk9OLi|$)IBYvwY96C16oI2y{DEGJx0tHU^jt8Z$i0(r;7M&O<^n(%+gbOo-6!U*r!4@^=MH5yP_SARSa1BDmbBd^9$K!ll zM}~Vp5bqw{sv*mT*KNAy^a_b7s#hmXgWQGenLOxI{m*A4?okOujNG-5;g^C`O( z8^iThmqw9+XptEC92>caBmQ<3;PFIZQ4N3h9bnTixKeL=V|a}*Dv_mCprLDhbXhUB z42o8hH7SpFIZIS2hgXA1G2$cT5`UO! zBbr%%m2{DZsFBNOd`t)yVnLd!a+=#PK+gq1ba5<(2PZ4ZVhT5CtY}d@lUa+lo4lEy zdk~yN%9FELNX3bT{)jn`@^y<7M<~UhXfVM|oi&V+W}TFVomr`!BiEfW;x6dW4bkY8 z1k)sG0Z&$ujj&@dt8yhrNDYWZ71=Niz0eEQfSUDY3?EYw0HumMR!~`2ZA9}~)+3}k zX`pOan5uP{cvNGS388n`45QIVKY9}Xm0OjBNhIYXtkIAXDK8;Pf0;^m6Oo9^$B0a5 zjlbijE*clvp$+sVAG{PGHoA$D#eq8NNA`$@JgPHa_@iOy3{2{qMM|VVd8}$EWLgJx zr59~JYFk$tL{&OO+gUGCnU&Tl6S7x>Vd{fqnh(AJ48On&_GD_*@J{QQ7Wh;a)39M+ zLZ@~bpLweO7yVQb(vU`;L7=2%oBz0&palp*nn*l$f{D71td(_4S7VQIp))a3?Vu5N zs1q!r5*S)Z!Eq8k@guo$V4(_kqxvDuac3O)k!2~TS#l-T0bQ;dHuRQD{vl`dDiOAJ zpKdg0pl7T{*RODhN5e|2#TuN)YM|Vsbxg*rPnrhLiia1PP4ogU!uWbn8?`l3t&#R| z+X_US%3v%co=(9F@rrzPk&%#P5x1sm#U`-k zlXY)cwAM3pxyoa$2#W=qpa<)p4O$NR)gtk78>o0Atq~iTYO%oKD3CIUm^Oc$f)kqB zu~zCSV~}5n_x^^oJA$#ib)np*sqX-h!ojhbdlQ?>x%MY1`1c$zYf}V-Q#g0BW`kx|vJJ4x zx&U-ki!mw?QE)NiP=Hjswriw`)CWYnvw>igzWcktYplYHyFzBTsOWTkOOS-}tNu)8 z5=KL{PzjYj@w`wQy=yhSZMB%FhrLGwjgWCyw}1;FwwB_W7G8q3ZZS_+aWHH97G5z8 zhV_l{VHfoI3?w2A_h~-*Nl-={k9jn^bR1EEi=bBbJ&F1~L&l&%tP}c$Up8`~HpCi! z*lq4N9Coy@5j>F-+&LC3cNkovRFz_4gB`S^qGc0Z&;_fpic2s%7_nQwJ-ai5X@WUv zeMKv5e#xB7=|?*pygi(|T34Y?_YGVZR!in|82h~56oXO=#jq#EL{!Dtdn3AmM78IG zUAva4#!gNm757A8xpNlr^hF&R6~7Q-QxT4`%7Ad(4U$KjFP69C$7JOo{*QlVWV%Y2 zf4q4Ohp_Dopwm+h%}lY6wy;-LKf+;hDl*9uys;Zg!5w>6oOFMQk%$|NZaQ^l2ZTWD z;T$b78A?VPB+sYb(y-XkGJ(CKh39 zMYqm+3#?lQ2t?N>$0oQ9hfxiOb%>eIPuC2^@Sl;Q6C9^G2o@;=&BzSxr63v{dHq%u zOn-g-DH#0Fn>Om{9N*Ky=CW?ozVqIEg?R`Z4_iM523?4Ol5v9_< z>54!Llr61woaMam_zj&QQt@EZjAlbkJ32A(6OVGtKYg7+jZH5|j5cJ{-0KwBtgaFR ziQq9|TXBS1biSarMZe&L;eXledNL#3*nVp+q2z7Z2$oWXm^*l&vJ-~_C)}tX~ zMDs3oR4yg)BRSGEd7a+U^r7Fx*9mROp&Hm1?75O%LSP1O7roew%}TJO(S#Ex0N1B~ zS)hKLhM7&p%@(jKeXOB9i$mL}oF$CBOk?CQQYoPhF_^?y>pt#)*HWv*GI6cBJvzH> ztv0lkzx@>B`y~srDhab{*?1PJVis4_7F!|}sfpEZyw(0R+pFdyUuuorgo>Yb$;v%u z)@Myf;oT?x$)H;d4bB;b$DZ-_@K%3y2hc#EV(n1bmCZs@PViqby2(m^Ynpr@qR=e#g|$f!qcm%832 zjuY>)8kqLPx1FUa9z-i%wHWK-S)9~fq82l*B}14M@f0RkvCTa0h4n&=vz$bwYTq(`@z)oh2CAnkjumjal!p&fU>TrRYu>Kc9 z-Y0-e$LQyy8cq(M#dXQqQ6gQisl|^#=7vV=tcghy*xPNI`;L%2JUxN3QCgm7`#=0JFP z5#8yWh2>N0@i#eS^lrJg4Usl=n#`yMdpCfV~nG3b>NSzP#WQ~3$r%!aUA*m{*>vDo0O9Ju6 z&~W&DAjpVhXjVRB(^KX2=y?IW@)tGigC4M(4+sF!>{~m6?bsb;=T4zQcn#sXgQqSX zI&|zDHe8rd<3^1aJ$mFAQe?)DB~4z0c+%rLmhT$AlZNcsOk~KCC965}nz?g5ef~5T zH0U;-=YpQoMl;#5V>o3#g9dH>88bkD`s~4)bx#|vUHS0wv!_p>K+Xmd9Qe&ypgwK2 zX8jsh?p(TZ|M-#HwX2`8&itM&*iNm$z=7A5E0@k<%8?}9r7Kr4BtpuS4>ne4S#v_n zojrRFJQ?9*Y0H$IMhz`rK5ErmTf>IU8tiPdv)#@X8@KLk)vWEd=K9*UZP$<)JC<|i za%jz(v2EKH`W`gZtEa)P##y^{YW)UEgXZlXKjqDzN1y)uANK9twI<-MW?U;exWFneEORgR7+R?nPT5JO>`Tin z-&~6=xNNq$EjB-8yA97iU8`oe;*LwM!sVoq<~bFmi*5}`^TQ6i{buy8#_`nSwA1q5 z12w*~Xk<&g`|zvEto{yq#~qh0N~arc#7XB>#>DK%L0m(EFu{}@YzdwTV_NRQnldbk zL!XZ7Fhr%6DiJCanPF?ixLSPis!;2dN2L$vQ(?^S z(lXuQtkqVpltvY0&`}WAUO_^bq^YkiiKT~z#fc`Hj@2nyWt%12r=%>jsVU{4nYLQ3 z(CrGpK+bv+)A}U6v93>f>``3z&h0zCzv#2BIzYDNuHJOkS%))?95=9`bjT@(K!y~? zJo7HArm{+0F~3(GZ`^z{PKoIRW*S0cb1fT5`Jgp*L&~h`D!$EXqWYC=4h)U;wF_;x`8Ji13!~cYpglH9&7U@bd@D( zx<(spowgZ!*`mbm>Fg1sJ(1eB$lZfQqUSD1y$C0%HGE55c$rJj{-8I%0ZB-D6CA0~ zK)^Lv2Wa0Y2SFO=xXv7g9O*#QWgeF>%_)q0Ck&y&-XXe&K}=4mn?~wp0~>6#PGi-u z*bHYwjcL$OHp0mrGG4JemG$m-))C8C$TGaP$jo?|T4Gz4H%0e|iWz@7V)Ud3BPJDN zN(o5_O3I;(VQ|AD4+3BKT9qqbq0oG96jrcWvc6;4BwEiHPW*bpEczh@8qdNX+m?v_ zz`5L~FM%nClKL>gw;ix9J)_Is%yphf`XPefxElo}RUP>N#vmi{96Z>8n9Xd2gO4j= z!BBWY6S9hhEi@f4S~Dl##7=dt>rI_vU zvhRI;1PVUucMY%IWNj#V4&c5*9u?tj4~XMVcc?W%gKkA#JyIYc)z!1~giDF@sSiUn zxk)r2v>gi(;lA7vrFDpMa~U}$N>llzRkBi*NLwK|TBwXzPJXquiL6dG z#bGogmebkbFqqdk1~H2`lSUmgnbcWkGll1ttibDu(`1WzC|XVP{LFWl`W^7LNi;wZ z=A%`52}RJM4g-acBkNS-z8dsScw$YS>R6vLEOe7ZEr(?AEt*5u*i*SQati1NWMB!Aa{s^8{R0X zNx?Ki(X3Q6Ep;j8A`%W|B;#SRq?0dxp*ljP4rI3LB@BB>r(Y5Zcfw365trIjWL|}% z%v7TA-m=JewaaFMY#@8M3R+Qp6;t$~>{ww$7Qp;UArnD|d)N6^xc;J#jqY?~UG1v0 zcc23rzBO#3GDNcZ$&YNzGFxFslrm-{mXhTuXyMYppUA3(vYK=exxNCbb0H8f;r-ir zLhFn^l(w|gK^&Fz1t4z>gBarI9@0(-UE)}!)qt-DR<48qX%ce`ipN?ciMGQ0&oA|^ihU{5ayby8({sRlkFekufylPY0m@>Hy zY1LY}V`vb=*FesPPp2_t9o~{pv8y4GeS@3Y0Fltq&55EKMHMtyw{X?5(Rkc4ma1UN z8#Drv0>`X8=go+5XP%~2gvR9A-)ep^Iaot9KH7SkK-h4iPf^G)!0*RK&JFO&2Z_> z#vMPQ?LHGxd70P7x)!;)q)U6pktgoP%1 z*FCN#(TKfi&JleB3_<)x$20!@jDTZECxpfgQHZm6*O>zw!x;*p|A@Aw2pAaae%`E#+ks<0(u|~J_C%7( zZQSg`;mmSw5vFM(Y~3225n^}xukj=4qyOH+)GG!7J{iN%tHMi}ks-8e02$(7Hf$h{ z8rl;+DZpoOxC>DRY6%1b;sYDGw2F%+@ACc&(8CH(8@vnslwF@^A; za`+z4z`fiv81mu0gVCJd3qE2PzT*3-IY~a}>l2QNog-tikhvX(n?7mdhGV)u>{GiX zF^lq}5fuqLaw7}#qL;-XznWPRE$SwtNvL#Clea?$xZ@1@n?E&@K{^YG`g;fc+q3>t zqkc<cFG7hG{tw4!|;g*bT9^0xWhp6C*`0e+qoSdVx8Xz z4gw>@V&SU@L_}&)M(d)WYYVMM%sh#j#82xgvbZ8NGr<#VgQ_a4_R@$?TpTb8lNijs zJ-fdg^F3DdH5c2#7m}D-Ji47~vZm9a*8myWpcs)s!eLx0Xy69xLq@f0CbJTZpxKm3 z>Xhsn9K^s9#F!H9fQ<863uG#u_0yonkqE>n!*?r16>7u!TSqr)M+tdHV@L(xqDKs= zkXaluBDTJ1K&D8J{(-DOD?`66%L>OzNU#Dsfx;07YRChM5A*W6Z&;{ItVpbY z7XT`Vho}lVjuFDJ#7UjJzJd&`Bw3502{BJ0K}=yBDKScKJW6a#gC=3db5n;g#1b(K$Av-8tE>z+ ztgo${OsiR^cN_*Sm`8f7BV_P}?99$y@P%2t9iiGo;UL69v?I4nlnEqA3UZlj(~1I0 zq6?~-yd)$LYsiNz%JM@VD?35C@uYSz5O&bWGYqL*Ln%^R!_NT|zq=fSctIPy%yei) zS3F72OdW}togLEt$D#rzJIND^v7MTft|DrVWjMyyRL0mu3rt!!loJ>5D+_6AMz;IJ zYUE8RbOx%plqa#5G(iZ75X0dF(jWy=%@EQtw2UI%4Cbt}=d_ZGh)#3}1}!iKJPb5Q z>xC=5(p=~ToH`xP+(VjtJcet_2%JE&&`|)IaYAn5i#7A485ymxXipZ6MoziS^YRY1Q%Z869CbLirF=vg zwVZ`1(qR4-QX(BzVhvK;syimdjE?|3RnP)r_%G|M5G>7wYR!dOj8u^+I`Aw&F*U5p zOBBpAIh13fMcgj-qE)pRH*sS~211X}s+V)KQMWlYtUw&b8I89yPA>5heoa(Cy}eR8 z)LRP}MO6oGAO;uH%tsZjVef^{}Ks_=9vsJKxbDqtsP-=-FS`dFNRDxBhn^T8wNZ4Ov28NYXUbr1<07liB z4LgaUY2Z{{N_y@gx2)+?P8U+4vqd4{X)R)7T8w^Rm* z>sq*d+s%DXfgoE}g+>lTTOIkVwKGb_DF}4yz_`@Jj)Kal6vN_FPTljpofFu;&DZwz zhQVFf)u7Ou(ghfj7>rfiJyf09sgubil*;|s21BM(-P}}-*%Y14{`ssI0b2^{{)3if zkFkhZ(IN}@kgEO^-xO`Lk75ZPhC5-p_(Cc+J8P<_@MzUAcs+u^5Pz z15g1;3>0qP85~rW+S~Xo<68^k`c+t(;s*S!#bMJ0F8zgC?9%?l#UJ9KkAc3Z6UI3ea#D|g;b(1H>ns*)y#-j9gBwLeh2R7FFgzVu489A4tYEQ2 z4^_2f3qvTR+2q*$WKX6H6`p8N)(lY|}7*GAn9PnBth9{6t})HkBc(1ZnVaN=uI?R68$KPHrt?R7DE}M z=B0K_$t>7y&OIP?2XY{z)aZuQ;00aK1>q`Zp~9(Q%PC%BT;FIq9wOlW=7oh3t1My5Ed!<%u_u$|uE^1Y^pj&J!sZ2Ep^`=&Sier&6W z?8)x2|Mn-#&V^YRa96nOT+m^tRr5leJgwbYmp;Y`E^V)ki)Ny*pOkA0ht&tFB+%+- zro`mLsh2Wg-SU$XLLKfCXY}{|Tg_Qsxj;H%WPOT-% zEi52c6d@0fgHBzcQ67&|ktSsASbcK3&gVWDwG^vzEB?>&iq>_@VC-e>aw>^zRY(Q@ z7W0%r23QdARzUMISMz9K^Usb7Dm720AckUq@QteTM&jI56O28V;Lu%zAaQLdHeH#G zpr~vdgBV}g7Gb_^^ms3!-v*sATy(3Lw|dN>_jzY=KGk0eHnoRarIEJM{ zh6!{k@fa_1FL%9Z3+#(zW9E2H36(&&qIBSnlQ;asAK@);?3HJTco+v_rzx0sADJ(6 zSE%_izlH3~`DbYOB7$?KFovH`;L;NM2{&1rULX)0s0lv$xzMUdU{!OGoB4=(wD_vP zI8H#l`rW_gdsjtC@6mI+5@z6cX7C2E7yERMhT!P*N{v|AQTx={Snq83wWIJpAjTt+3j3BXM%K)W8;}FgMnurqD?5W7-;>A6IoPF!qu3g7JeA=|Zc(Ki% zi4*sjH0j6Bo<4tOzMLs?WXYHtIezT<@mrvvJU_Z)_wF6Lr0j|+ZR+$X)TmOYQmtzB zDpsuSwnkm&)!n*g!-$cE<}F`7YteoI1I8U%5|%K`(FO?b2Sz}kRbkq3mcABS&?N+8hHd#NPdvySsN;?cb5+>lk)S!qr4 zOEhi)6W3gEMKg_Dz(hlhTtxO&*pGxAR?RinDE7)Rk4gEAWEl3r8kSkUq*`a7i8fkL zuASxwmsz?78z99vQ`# zH%p?7-tt~Axya{Bq`g4q9exNY#L#FCN%S9se+*dQNL(hUU_=&~l#&-~s76wR5@yIz zM;!$P+N>24b=90F`u_SWuu!G=D{(Fs)y<5`n1z;FZ0T4HVL(!2*fdD~SS^r&{rFd5 ze>M5!F@{b_Kr~xfr5*S%y|>rY@K5c8Z2Z@!CXhy_?-T zTZQ`W3vj>e{vgh8i3jWHIG+iZ!|*Z=f0x*yu(WsaESqO6DW$z=-BvVBCidMy#*kG^ zF(jjrVbH3=S`yFcipui5F5{$9e-u@;Mymi~1S=cG=S=%WFv%q9WB(5+;zW( ztL@~an&0h>G^X(zgRq2}dohC}>F^3~7AL_85(iiZGZ^Hc^C5UFY#7pjhUL72xx{GB zJ@E0I332objtQxBrURLG45GS(w2nV%*vTJ)h7+A7FMusWUQ3L3JeWnKQz`-CNrY&; zSoQG!Z#?TE^n%iip?GB~*2^9iv$s7Hbx(2LP)7KeQKM-&ixP9OoE$=h~{89no6|dZ_#&&nJ2?IIeqBl{nk&Uz>7JsmohhrZbm`-bbh>l`CeknMnf?v7T0xFV0AeSi4bMwB{GU^aUH(=;k-U zsYY)$seZ_~-`n(8r9ku#nYJp84}mG6iWG1(8FGz0eW)&4&SY@TSjY6pxh2&I zbC*RNjTeGfyUqwxm%4MwF@^F#WwMBw&ipA*s}~#>C54(~v1UF9bH4KJ$c^CirZ=024E_lgCHp=-zbk;F4lVK;i>ZYrYI0H6g3Q$T4n1;2oRgeP2DLz|sub==TUxphf zUV9@af*O=@&0Hu$EBR2Wea9FRC67hTqn=#QVit{M6vrY3I$t~^m8xXrj9AAhc$suw zD8;EhJyX-1jYJ|wWJxh;+EOOE3^PGt#y7ySOrHj|w#^)>i*id;cNmoz%l_!bHMho8 zsydabSf%PVveC_M{skM^NNyarN=L|eH7IijcH1Hd_)r~sxD$qLNwXXroB4C}fCre@yjATg$LBJEPXOPD{25ygm&$ChZY|K*j zDeN%DqS-$1ZFz*`VM`EblF_mOBQN!ot1@$wVAhnifU``7n&I9yMddcOeQ|AZtJ~Zz zg&h9%EjQ5em$nF(xZHfMaA|#@)s?)|Se1((zD+f<`mGY9`CTW~>(4Zte)U zxA3Z=%M>ba%iy{hMBa;5&HzMvbDJCaGIFncwXa{VmtQ?KRIvS>{t+<(j12t2!WHFN z)ML@pidMMdJsD-Nltda~8UlhcF(H#Y=@qXajus;+ahIp?7+MnN(!>dTi5^|VOc%qr z)VBrBjCEV%WsGHN&0v;V_97%7yHUtNPHvH{QRHw68Ocgc3|F0u3@A^=Otq}CPrO1_ z3>^^E^xQIAyQ~I4EW^zI6P}0#Cq-Iy#2Mb$DVxJB?l;33U`m_w;}Sa98Q~`xdo~m- z2#uaaxnj_RW<@I;g)#W#PZ{4KMlgaQ3}G}nb~gp>Or9v(N@wK5d#ELP2Qdg-uJ*hu zeIAO10#Mtu*wh}!qKs2bJ$ICY)nSBnj;Jx~Z?+M+x5h^Pkq1-c<^uWG#|ZWai49iP zKs;A728lBoB1pZBhRf1k#>&1?RxgD+508f}iZ*wKFdtM$Bnpb{EqQdAQ&HAaRN-(ZqQa zims~I@sBfJX(3PbQp&-MR#y$yg^3n%i3Av__64glp9_(InBOwQ|PNHBGl|bEF);*Nrl(E_^n-Dco>g8lEJHr|N8l>%L6}o()S; zgS-OYtw8&|Sy77Z^sGZUREW|A!dlf6cu5VJ8$xNF za=?Q(1OqWZ!;R&cfKlHnoZa@70@^Jh6Ox_v`57!k$um$5+$Do{1cPi8oZUfD07Y8+ z)zn3pR>}Cy{Lw+-(N6BnOJ?|t{_&sx#Ub|iMgT5Sn+zZ`2q9VISU!A{VSH1zxX)lT z;9oqT=8cW$RbVtY#_8ct9cds1LprozPXrxXJw$k|7410#IV6+swV(^a z24?6H)I}W*t|BYu)$#ocZTX6Fph$DTLpKN^5l+oOBtv%;#4D@kUmAL047#EIeV`ncqth6NZrz4% z?G#uvgB}tCAI8P3sRfaQ4PjIR1$q;-tWgB6k0LT6&V`5SL0o3c)f9Ea1Em8C0#r{h zL?@mg?ZKAO=*uX!AnuhF4E9X)e1j^sqDOwDUiB57#NuDoA~$FOM1c`u4FfL%*f|BG zFgl?VV&B=pS(T0E-Uj#Pt?*dw&yBd^s(uEm9t(2oUPAnG{=G&E4L zrHcaH%RznvIqcpkOC>lBoi`WP1>X~3WQFoLMlL`G=5qkMG>w1&rp&W zE_v8d){|Q$98)&YDGCKf%7%*VUz=Q}RW2u1s+m^K*j9MuR}#Ykep6fQ*pLuL%%SBs zIajstqg-r@{463|I^tvg%%wBbrCqvKnJE$odIL9jgYTVL8We*F0)k*7K__;ZFci*L zD2{Hh;9h#eMH(dTab!md6gn*EWHu&!nehBpy*Mr;t`f zbBfw?>Q*J);&dM11D=sJ?1irV2>bY$ceYR390ryG13<>*Goa^WR0fJYC2ZuSi;x31 zaD&o0WPP?nJhVeGH64Ts)bFpdI21Dpz`8Z3i1 z?BzPZgQBjPfO-SIUSwVZYDXGsp$=-ICTe9WPUEczg=Rr2fRLm@-)AZVlz8YWU}~nC zW+}wv6O!nsx@M@3Do-|=Q({CU@|=~iYFXhZjn?RQd177F3j1LgXldln{M=zy5j36S zeZeBIa{f_qWaT zUY&4!+;Ti>6(nqbc^xlesHI*ih^_)DSZu|%Lffz=c=#JL0$j)LT<)NTh>a|(;-);o zYRaa;wCQSjT}i3XiyO`$nD2 z%E5(R%-pm(E7^#rB5LWjrW3g^L+n&U-HfRNDQ3Kq1K92-y0(KeWC0Q!K^3UU#+8FO z{*;5;Mvy}0DPN{DvM&=G2KpfvFiD)3I=mxv}N!9^xR%~ znMS0+Eb59JJG8FrmT-ui@FfGFk8&~pCR@+}|q46d(2{_1E`?- zp1~(VXb({RP zGK4Z==fl!w%RTBEU5s>HJnN8=G?9>#{Gc>-LD~8J2d1r6?bWoMg5camp1Ti!4 ziHL9X@;Hn`PKV@eaPjyOYBalc{K7WkV9#N01v9jOLj{sh)shQCP{dJ&SN6_W2s%TbE-~n;i8F9R zj@w3?mG3i;gDdj6xaXjs$63}Dg`lSbhIS@1Vj-d{dbmI=q|bY#htxY)dS_mGqwQP- zZ+btq-U{MtsCU^=&?l*<^{F=nSb*SlD_xZJURGvL;@s(x&pNHsE?-ZyZP4p7s5e?b z)vgVOH_=6X&$l&TRXEYluV+=Vk2(HG$y}QLIYg9ony=Lp{}Y>g#jcupm#N9zYCBl0 zS)QYVok!h-k2}({V!30_ilqCx7rMI}1F6FMF~H=!1LLEsf@s1WG<;B{>$bpQx>KgA z?QG-+mV>}6UD>9AkJD$n>L(yf@UAGu)Wt1ZZ#U%8;w)mt#J@VhT6~E(dB#IIuCGJK zS9u?LlVLUU6lEIhX_KUBe=}JoCmpAV9?Do*+%DHT62fGE^I&%4<;w zeMl0$7OQ^Jzkb#EInyKNG6XJ$w!2mzx-*jM{T#yp2jimyf4y@(cYHmJ6WqREt|X?* zMz;Cwkqm(A$~Q1XyQ)F7E8gB$^7;OHar#DG-U|zzfq#+*$DK9>Hya1w6RF^r_eA^k zIxvIB_n}m!@?1a_kcAU0V^wlN6>?R6R$)FsJcIS>)iGqqo+W$+jhQn*Xdp&}n1*6S zBx;d=W3SMH_CmN#+c)VY&qPoF=52CX@i zXi=j_Yob)Tlxb6^O53q>$8K4cRLX`a)2cNYv}D7K4O2ys;IUV#R;5j~Htnjls*D{> z))g8wWpHGq$k3INkL&-J}Q;adk zGy=)7$@c4yB0w^dY#B9>$fB8c+zE|0sYp}K%;nBJZ9t92L}@5DZ@bMn+uTe^Dch7& z$~6+zeC;GY0iCm^{?^nnv%#sFk#4%`z{4&(YRbz-Jx3u`kGyQsTd$h-%z5t``JOTB zm4gsc2%2Ub;?K(e0K6#6s7^~qLRmpFX(|j?dI_a+#PLQPbZ`Zh!48Qn*4Sf1O!lZi zYm*Zmcb2I_ixjuwsztDjQH92_wCz?E8*3cvt{&^UYZ)Q=Dl(8GmsGMaKl*U;8z|Qa zY2H-(^AEEqn-S(qcs44Hod(JD&Ck|2B5*n8&P*8A1&gYaIM)VcYS5E>>+{TwIUZ9u zW|=MOwTsVW$)l;bQFItay#vo0NU@VNyzI`)W>ZYn8*dtGaE33M`9vkv)Ki6 z`-~fgQ(74QvXvwxDp%6V8R=J-RzgSEW4Z3y>z<@;NhJ)CQ&vupEtWIPXtmf&D`L*g zOBrLrBCD;o&id9ZwZ6^DE4qLTQkfxxQTNC_5J&u7z!+=Hq>dn)mop^tMWZt{G8^!t zb>ab7qte`2*v|$NWU8ak*z_ngcvugZ^=F?In9QkY7ntMVKJE?UKQ%T}_l`a0-D0$x zC8~GiI%3#0FjJ0hW$V1V&YEewKdpU@fCUU2OU9;>vMGyrX*q1C*G-X%Rh*&_rBWi<2$43h0iNlA zcRSr6FE_ahS;PvpG36o59o`6r7Nk=hYs}F*)c{6!sL`|PeTRw7lXQtq$`OtTpxRzI(FfVlhl2*<|q`#~SaW8acAO0GX znRe6)ir-*{8Yr@=9%_y^Tr3Sd)L}zg(IzScYDed|p@kz5fe1w?!V!)@1te6V3R%d4 z8q^?+Vkl!7%U}jKxX}%glteHg;AaOYzjViq*;2Rms0bDM`z*dR5q?b$YU6?RK`4&4kOY&_W>e)vZ@edDCxg) zY^@yN*d!G@YEBNAt4lKMk_==eoF3>7SfXe;v!qLlb|ek4KEoKJxWX#T z0?cp?6EAluSCEofxMd#BnJ$D#Afzc8*Gy(h+4N5uu(YO(d6P7_1uyU{4%!({D#){*T7Y9PhM`YpG^2?mv0s9|$-0Fq zlyzZ3-4AW6u#TbRVaZ#gZz)8r>M`$RP|h@TEH2>5es`aUDo?ihJ1Gqsss} zjqp`Bra+@9BJ08poc@(no~Z&L$N{DY$z&WB!VoGUa~pJM@@h?ODozI26Q=GrsZaH9 zu1tAKRYvTptxV7>V;PLM;iX*22B|kfyqXI#}P=tWF!0jXJ_&-(83?#f#0OmDiCx zO`>yR0*;{~B^~1k2RK0eE3Ls~fcL%0eS?bM3h}p-|MhQF0X*P06nbR0VH{z&m~Y*1htu_!BZ=6qmcy%?A$PC+R0K=&icsm6Vj3Z$gB-s~EK^F; zk$9{F2^_HO1<;*tbzi`;?Y69u*F9Nwzq`mrUO;8rZSVdeK?HIDWI&?xBuhcQwC4Vo-<)$T=VCsVBx zR`=J{|HTyo@f5}aYb z)4*bkOB~}i$&QiCnUg1>^t6(Z5uDc1q=892hyzp$!lQU>c&9j(M|aaOy07S z_x$HQ-|oy~J^~8}vXB?hZVvoA;7?Wo3RKWp9R7gTo${O~J@LtBeTwooA%kX& z4b#2sr=;BTw^q5XdBp-H34YSP%A8 zUOH=!R;)ze$c`RK@wxPv?XS_V75N9A0qd+d;UUg{dIVS@&u55Wr>a&G+S2}^EB zH^yfDU}6zNN$A31==f_WIPuh?&L#D)Yx*y1n93(S@e@T+6zxo+8psq0aI1!7!CH|4 zU$Ky`Ko(yi6(%q)puxd>1j15i?iNnj(4ek%2nm2Ouf$|Ih*1myFN(OxO=_jK*Z~H+ zXenlJ8W*4(wQ&Np@h#yJ8_n?>dvGp;5H8no0y1DP_c8?h@-G211mZCsr;spffF2LC z9L-s5}jkh&}qBVR!y zA1%B9kyYqvo`iuM+#&uX;(;FMp&oRz9^hg9-XR|90XKy+9<=5idUO6TF{y$oSY8qo z_p1`QCOK!4fIv~ec5-6`Z~zme03#?U2Zt5iDjA5u80LahW(o_Cl1A=ETbPnZo-%OU zY8U$g5c&WjUZ`>aBn?(fnrbU`Am_~1go}d6u*!iQo&_af(2Jyz8au%A-ZC8@6hh;2 z-Rv?5`Ev3y;4XL2E!T}h^O7(Bat1_nL`hUcL!d-O^aMt9Fy*ldb3g}lfN}z{eIQdZ z72`&0RE|i_<;deYxQEbS20jF7JmLcnyT>B;a5ca04^s>_agQ6Gg&fkM9_#@htP~&Y zp*N3{9_*n@z5di55NbE+0XfkD9&i((CP_NgG&%8WId@`BUy@j^?ta8ZmF6r^d~!R1 zQWaCkE5@@t58^!2b15tE8SX+Fx&c1xf^g=uKC80Xc1RG@pofwuh(ZT)j`1}>C-CY; zN@b@kq0uSQk{XLJ8__KYMEw#3 zP_#s8U|5N@SckO=Vblw}fRXy3Ssj&;D8gA4!&#{{UgU_5vZFJjXHxtKW^4ueIWY*9CmJ|p4-@lz1zsTk6WQ@QeBChl{drCuV$KurTde`8cr?^Qb#SKG2x zZB=QtF;?|bRx?xsqSirK6<2W;YAfI`LEu+G09b={28=aqk2P7BH6p6DS@!@9&bC=^ zbZyzTI#TXDupt`qC}t#uduV1o1j$h(g}S!yA~VuHY>FdaQxI`aK%}u8onbuY-Z)!acYBS(zp;l^r zuxig2Rb{mTvQ}%mwrjr@Y{j-%Yk+Je;%pNqhTL|6-!>okHa)bfQSKIRc_ustO@htg z9L~X00Jk8})knCY8PwIh0D%x$hc>H}aq(eH5pQzQA)$P8bSF1-LwB}p_=a(KwvN+Y zBay#WEwc1`tGWt`HfA{?Z7nyGo3XX0suBYPX_S^V>3Iu40dttUj1PINa;z$jxZ zhKdc)ijPh1f_H=LW2QccAe3@F+4BPJ0vQlh8kB*I`Pq#90*#^7y#AI!7-x_jo`%i1 zSu~gx*@I8nj55n8OF@v{>jXq zXlGZawn)Ro4(28xW{HouV|2w73uTpX=T4jE6xZ2z!Lu${Bq0t08hmY@lhTWg!ABUZ z8~B;BeFWL&^C1NKuWFVY07g@n5wWzn;`~!2Ou~B;&o?Dn{#5taqBm5fIa;OH5u`tF zq;b1`O_jD+Is;DhYb9A&XPO3T8X|J~xTUo^sLMUZL#We(d#(o$uP1}gL8!r_Q#Kf> z_i(8ZNvsg{nyP?ZT_uUkFCDtUmw$O*7iu_$~&J09jQ9JC?7^V_UH zO&ikMzqtV#hC~apV6+U}k#4V^a&Py@%o#>vpn&gjqG%(kXvT2ow|7_m1D zFBXEa9ovJDvYy+b7N62RE&EXG;-8a^vjy5B&g&bdI(gnWc5sWrr->Vsfg5Jf89|k` z^B67nm5`hY`KL=TGQRG9|~ltSvhk-lz(Wz@69$2W(&-50f5kpdM+@;1YZv8P1;^uFM`h zB!G`}&Tv!FGB~1GR0oiE*;yG9yHD|BA$-kbBio(};yt@!#WCB(H(Tds)-$lAQbPx* zW(RaEwH@3j7+tu~|AS5-L_e{0*x;}?;m9FeI!9eLHt130F+ zoLJwzSiKy~k6X;i{Q5XWsNF-&i@MGER;d2}Hc9=^yQRS!m>~=12_pn+VR#uHQkZcC z{m*eT&<8z-5&hyZemHIT;_G{c5$}^6eXRMJ7*xU1-Khw2VD>(}(+_hBUEbvf)8*-L z_Go@FPyU?_oSy7yxq?B=m?+|Ks8Sc|ig*K^o8mUM^RPj-Pd~gabX~%basmtD0)OOC zFZ)NJp&OI|pcg4p$uBbm=4obQ#`fIS%b~Vn2Yj;)+k+!aYH;1=H<07fR3~&BX%$1w z{oMJ|qnCUFboIBdJXnoYris-CGP!K4lHPOL-k&=?=BT>vHe6Sk%`+u>`u0eZdQ`gG zySw4ws{kVaFIHBf;qg_4gA>pxUjE}ZKI2)R$RTijh&GkDGgo)OvD~{O2n=eB#BGgd&vY;Ct4%2paZaR@4tQS0Ybxt zfdeNHJcux1LJbTXGR%-+f<%cBD_Xo5aiT_z7YrKgIFW+L3?XKiJc%+TN*XL#&S=@v zh7K=Y(AZqlC#TMxJbU`g87N=AZA7=3+g8n5wr$w3EhXBNXtt&2j%LGV%^EhNOs`!- z2J02FXV9QYJA1YpH!WJY{;gYAF5TO7>C~wM7mpr2d-(eHwO3EC;Ch4w8#Wv-@8QLa z8#{iCII-ls^(HIUYgzKU&79Gp8wai%G-ax)P$`llb!r@}TibYTqlWAnDr(!ljl1>< z+p=HRH~}0u3KYOa7&jgqg>4rvUJxllCffYOw8AT-@5lS(kc2vZCoY1l*| zg=wULM22ZF(@ux&xRXv??zjWqckaN0;)o>Dr4EYlocQ91n}IhXJDiC}BRkn;(ZK}` zRkYDZ31NT`LkJ1}sZf#!P2faddz zUV+uamtTO<1hiD*_54e1O6WM-=m6hb3-@mRW{M(~URdqyrB->NHvxc;$4n z*QEB0J$9yMpMAEcYOl@MWSC)g*2#$*|_^`+w_kjpREYnUr?dVu-c-~!`Z567d*R3+`#Rsm`2hxW{x%!>EU%G{| zi=bKHK%hn!K6R1Spw zll}!wl)=R$Gs(~dCSwHt`4$D4Ws)I@)UwMk#Z15aES*Ucg*x-he;+>gIhAP)ZBIq} z+0cqYz@=%WfKMZo>JAkSb-3dl0kg}av^I`z)B_)YL7N8&Q??J9?JX2}_u|eY;=eQXJOIXN>+y{!#jCeq-bL{vW=!hY@ z^gyFqrz^{7R5z~H;pcU*`%i$Zh@f_@E*!X1#e;U&JKt3SbJ$u(h=^CC?{KGJDDq5; zvc`_Ay$E^?Qy2vw2C;VVXaU#@(nu)2~JVT1~mp~6h1BGDqJB-Rr=JFY!Gl8PlH9&rgoNa z)Xh{{Lx(uPu|W=s5QHEk+cMFHskB9tF`6==HRVRCQ?(Ep!-yejUKJ}G{OyK?+o29m z5*!r-PMs$(2@ruOf_K96oybuEv8a%_4#{ympTiauM;8ocQ1M$=L`xQ32fJ0I!i!+E zV!Nyu4rv4c_RoWj5YJngAZds=~?3U#PFMPyMIiBw27k^-4@-vkip$oSd*?xV!DP=aZnPls0 z2cHSpXI^u#i^+^O57bR0d=pkQ>=ii~XU=v?HaI9rCuDz95_xKNvyZC{3NYXTZfLHF z`y{OsLx-MW2-I}SScVk~H6Z%fXNw74=qr-ZP_l@zEaKqCFer*fdu2$Y9DNJ6`b7@8 ze6Ei)GAWG0L!$5ylcatOShd=rg$@vsVi}of1z-?^8;I9tGLS(G&Wqmks#mA%ZLfQ8 zO4Oe!)w>l4UqV(^$xc4E4j=XlzuVeEI2m2b>6ayAAA0%vKG{YIth=v&6oMB^Wwa#)z z7M+znCue^wPsphN1sq^NFh+aYB)TR^6)BD~?3)Rm{wIoE43csLw3BWFynf*B35&=_a4FxtZPY~?jus%g; zTe*rkZkdg9oTE<@rm!|{{S~NT7=oLrivIq+W5wg-7)=uW0D*KdE6m@oN%<8g%2rN$xJ4OY6rTE1R>}^0y>5k z&6UvqTzNoQZm2lCK@5s6qZk;)C=#jw2#&UKxlK%SoJquCIFmGCcBb$)c)5Y1nSR7T9LQ0S{u6m{P|^wqG-K z_GJsU+0I_fq}o1h%f$HF&>%*(i?G-ngj1Y2{!ScZ&nZfDQkLA~Hunio@Bs!?Av^0% zvNEdBWOyftEU$%zy{|yq+KR+=N0uDzE9wxAGqjcRhpg5ZxyCH-746q^){wXb*o41)>@aV>wRq(&XgnUmf~w) zO>B0?mTYRbOl;R;(Uf9^H5sRZZHkq5d*@g=_IEiHc;VJg?nHP%mMe&d0gI;{jpsy< z7kR8@I-`*cs6!x@cLi5~1^8BFw?#Gn1A<%KAa7d+dj25{%-{?KmmzB44B6oh{8Cz^ zMHgzOJS>thxmP1h)H&1@NO@LB!&ebO1#-)0g;%J2&!>eY7jj(CgLp%Yum#`ehI~L^aVUod23Af{K(SOn=~or&$4XwY6!4d8R|gIBmloY1Z0~@7 z9JU$d5D%8}R{{u$kr+Y*2zQ4;i3j*%iNS!Qk!=~WZG6{vxZ!Q}18x|Yft+P{9O!{= z1cKhN43L)|tHllM0S(Du49SoT1u_=Ku!5PFZ^;mY2Qq^-_;~@BJ38n)NZ^AW(oxjX zIa~93Sp#e`@^HBq9v74&(gpr70ONZ-!d+5Ag~x}5&%1!bU)>8OtF*pBE3 zk15x3h;{>PsB&Du2KlIu`N#+T=#T%%2ZJDx133siBNRtrhgN}yMN>3PHx&%z6%!_P zxONQkw`)`b4&$(4UAGzV@D7al7k@=|0Vs))c!?`!i6=Qi*aTyC0~+lC2pOVnjO8~t zCW@mNCZ#w7rwC+*CjuOR0qOREt@swNxGkk~I>KlSw>TEN*o(%%MO~B(+OSQXCk>Rh zW#fkU(*e zvZOS4$aK5(YDW=Ec@mLEF?H8ak-juL6SzY$18(KUlNwkY$D#%(qI!+j4n|2j1BE)aXbi{D zl+VD6SFj2dnNYDal~t)*^kGHfpp{(N3|={0#ZV4G=pA%{QaJ)6?@(8+hmFL9d*Ojc zcDA3_)edSPm*+#5H{gYK387!eh2;2`eOaM^iJ=&(m>bHW9qOTm`38|`2O~pSd=in@@P4@V3>7IB50n-esSd+bH|6jSy_uUN z27qFBY`fW;9`vLunUcVni7jbY(g0@2nVfwCiZ7S|>p6K+gA7)LDq9&0#85TaMrIl39d_}S zHew?v;-An6Y)nX`Z`M2)S1pyApbKg;-xXe45TOx@mk_#+eYuw+m!TT!p{?qwiuney z8V7kmtF>yYw~DK_N}_c@2m*PKyhLF#YNJ)bCpWrFf#MZ_*rQNm7Fl;F;(!jZNu*bE zDQNfpe}3f``H-zb!L3W`tt45JA}LMPM5Qb#86Igj;lK@9Dw7XblQwyiI2oN~N^a(c z0vGTBYAS?DW?JOI3|VlL%n%)VKaE$_Nrs9z<@@5^@btij4bt9=nDqS1NjSA``;OKGS6=ca`&Z zYls34p%GW*;2Dl$D(XP3XCsMy!L4oUwr~5k-zvAbIe?dlfKnQFlYy>wwXPB(lYIW8 zcgs0W5*S%yDnvTDlR%ad^%@)+AOgz(5Bmy&?a&MWOFCCXu*$HM`4OJSa15)^I$~iA z4hyl{lpqr;9~sFY47-2czziJ44&^`&vpWvKbAOIvLAocKyL+k4o#7dtfhzC&8Sv|p!9%}f3!wh_8R;OdWXGFh1GoPRzyU14LV*wWP{8+qzzKX0 z^AN7iRDhYdrrpsm&d>~jkOqflGa0PGHZ!<@GaG|bfg!9Cg%=zg@VJrN4*truZgSxc z+yHrb<680I9?38a#^4|S@d~Oi3M?qPquJ^_2a4o^`0mUmxGaIVRdydXK zp`&`Lf;qj`i^r~tz1n-U-RsA+3ciCN6rS-8ltFe4T#3RNH|L6apz>z>%fH4pz>`eL zaI3(VjLDgdzz3YbyID>2z>FR84Ws-AYd~o9c!uSr%B$>ioF>Bc^J9i*!pjhZ?I1i! z#x=#$4OyTCQiQn!I}ERo3JEm|qmT+VOuB&TF2X1u(f|(6Fbuebhl@Bo0V+Ql&r}at96$$Tfra?_V+s|?fJmwnpT%3~5UA#eggl(@2t0?_gn!NWCl zLBIZTpWeXB?v^?POT)qpJ50S$t1t=<`@^PNJ5eL3wg`;gAP(g4c|;vNsDj1f(8Xg- z)@9AbTCBxb{Jx(@zrt{@!8au}^~U$yyeF51@W_`w>(6=qEtmrB*NIuQeQeO&i_rN{ zVhxShiLKa63K$Y?n*xXr7cJQW%+Zx?*$E8NAx+XH-3gmu2ignT0Ea+$zt}64krSQbP>kYJ;sTMve~WO9(J;2jIt!Mv2Q(>G)vdzZK`?gj>>n( zf9=T-&<+Ass#wDAEdAPntH&D`M4BR=B(>p%{cOAH4xurgf3s=yyV zY79|*F2yY4`|-mOmE32++{|qtG%O8;91qJ=#-2gHVI0&Ti+@M1z`uThQ;FwDC>fW&W*j`>pmZLXgehZO*O#4d<5}-~vA2 z@=)M;z~Fhl;1JH|ea`2jjhK(wn9z6O3-drxUUXK2ldyEHyE#|md*lv#Iiygp>eYa|B zZ2E2O{2k}vKIfV};CHU)=g#1MuI}p&2XFA;aS*G|PTGX|m~5~GF<@1@;laFu12vE= zYA|riAn=kt>6MNs{Q`@dPNy@xlrA3CukZ}okPM`5>ZhLSEhLPC%G?C<40jdli4oL9 zZojym>y7g3Mt{D(sbSbLIF7t4_?9A?}&VIew8|~6Q?MoZJ-~PW1UCG22 z56jf;Ko9QW4(H=u=XTx+29EAgui)%X^;PfSpdIbnTL)`^?_i?ufMWwVkOMZ5ELkvc z#n4)i&J2@I>6MP~T2KrMPYiWhi<=AT5dJR>qdw!N&L61$k`vpMxK$R`_VMu$^44AQ zDX;QheE5rx_~A|Lk|ft%Fxr(r?;ASvnV*g}kH@mQ^F2@Pgm4H*Px{x+K}EmjN?Q7o zt@KMj=S^?!Qh)ANPy1G%^>JVaY|!;Wq)xm78>>Kk#c`@FBj$n!c`cKf|B? z@W>zyF<$Z0zY2a|l?eL`2>TuuOT*nD>t$T)D4+Pc?)Z#P-6~Jkaxv_1UF_i11)vT8 z@gLfjPyg}$s+zxioNv9J|M~CT|9}h-hX&;%SkT}>gb5WcWS9`&LwoeW5k@Hlp{!}T?q|MoT$;Xh7B7ya^%2q0!5Z$!isHGwu~9GW?{LF9b3*@mQ>1=aa%?Vnp$Ydh8a_} zYSgM%$ez6#)z|OezN?OD8>bE0VPtv}AAS~Y+}>{S;=xOY&hll;=`?G`+}ZPI(3?4b zE?3(0Y173}T)2?I^=sG}EWn7-CC+W!xpnX6o%_vi;K7CecH?HwapcL7cVXV#`E%&e zi%+Ls-THOx)`8!~)7|^`JmJNU|BgqzojL>aJ!Id${YUum39^q^(f&k?`8mob7?M9i zlmZkGC6)?==_Q$H%1NjGp?r#?K?oy+!ziSZV#+C~qQar76R;9StF62OODwXuA%~kR zf>~y*w%$@EuDR;ED=%bL!2&QGS22b#!w^d*v1Fck1{rW3dnGc-)bUKT%|LVV$;+7R z3^giIQ;oIQvYf3pJbLr;OEA3zj*dF&FvO5U4nflpI@)y8O+n&}Q#m^8th2f|veOg0 z?YJxNPeA_+4?XqVGxVYV5><%3dg8h7QG4*)=pz9Y&1XPNF)fg#17n&g4?Gr>Lqei% zBlT3DC`^imrZUWM!>Uy95X54Z@hYrlO60~HW>U138)b-D@x^3Rp)0Vwjnk%ek_(EMc2Xhc~(m^Idp*`;E**5Q#HQ zeA7e(5k?p}gb+2?T(gif&ouK*gxge(j^o&Pj*U5h+w;0U;|TQPKnG24zC+!!^y7ja zsz;(nOTK8og)+TIQI%URX;T9aOb}F2N0l&+-%LdnRaIGSbweAT%J2jg)X0ifTSv^5 zEN{G7CW~R1S*97ahJ}V0V^s;vm}6eC%UQr)kp>)TtWD;aX@5)%+ri?Yhn{!J0e9}X z#YLywa>f}a?{df~?c8)>s{jUe-HrF~!`~ni5jM8iVhzaG5G2h(3Mpg|LD&$)azX$; zBy%<(p8g|Fg7-yJ&4lexJ#^@}fLQh6V$V~!+%UGiygUfyxMTJ}23cg1OTO=9gQ5mW4vDFez5ye<(asGL?-RDt!*6AXxrP=0S>v*O@ZO6%w_PVx4emi91|=?3a}Ko z3{YTjg+p8i;iWh_2x1F2c-$Jc;Dt2|0&{ENf(acLIW`R9b6nU07g}gTK`bIoo#UaL z1kom=w4n<(AR-aF;JDYxPKi0I-R+#1yN#`fcfIqSrGOVa;aQ}3$y*unxTvz_twd&@ z{(2ttq!%ITRquMI%3iB%FclmG&3iQfAJWKIK5sNb7-?ySt=y*!X~fS)SeRd3?w5*G ztRj)l(oy}?_CEj$P#Vbqhuh$Wzy)SHYVkVV2Y)Gc;mdQ-ph3A>jM42?@L(R8cQ;P=?5X?-{O>b&5jLqAG9=c=Bnypi< zL8${5Vj$0_z||=yut7cVIlixyZ&%GAhKi_0jDQYfEy*b8C=$v9CM-d*OJIURn*|uV zJd~n|v8dWIs*PuigJkizTSz_Yz$%fHq@bMONk@sn46f90E^X;=j^n}-mXHsJV8knZ z7$=|p6sRdw;ZTW+xnU-?n9ONv?4Al$jOBq2g`#Rz8S)-!V%3`V!D9YZy9!Q{fGn)v z+!R^$kcT+b3@1CW*;>`w*0;(Pu24Av4(uwvTHR4s=<_RJ1-lHvcB5DZO{f$SyI92{ zwz2(X=v@w)ii-l!vW3Yk$>{Ofxs}FAR1)oINlQT$q%>U=puj0t%i2`Bb`CUr93M>R z1r*8Tumu^!Q@v18fqOrM&wT22EX$~GU%Mh$ZV;5e zhefOtoWKM-+gZSSp5GOiu!;Z%a505JhGr-7U`aj0}@aMBMoJ#~BOq?wWgR=>G1CNmjB% z@FCVGuOz$D>>^0G*xi_HA`{{94tYYQ=N=U;5!SLu{ZK>Dj*4@iy{LW3^sH`5}ip--p!MaE-j=roF!9o z>C&xLTpc#O1`@h(5RAa7ZdW*CTtbefCXC^XZ(7t3-xcF-|5^$Kmy1PHT>!i{s zslMYs|NB2~kOu5CI7xer3K+x+*b>4+zz`!73w)ti`WzXeAr`8kd%>v~Y8;(np%}`* ze<6g-86gk60T3)M5gb93%bn6&ITZdx!Ixvf7i>isj0bzDL0RO%?aH}=C^lf*K_C3V zAQVF1BSIrgLSY<6v|_@Wzymwj0_Wo%SCJz(Qh_P7rz+GZUFn-t5C;4LEVcN)VL+tu zBf~ulN5mS1RQRteFb2wM!#8|}IHW&~sE5iJjnPoFk>a~>xX1q6!~f$$Y3Rp(G!Z~d zmkMx=LZppDM8pn4yi@}wd|8A=0G-g8oHV%{fe4*K_#Dl-$PDb9%xMEpwNQp(NCxmzq;DKUJcBm@8wFMPFDaNTNHPX= z)C+#AKX@#Md8C`Y6D>!(N4nI*eB4L8+{dx-$A1*Sz$?H43;__J4K4sLggm@CpaVp3 zNPOv+d_e?FY{*3@1VYfr$kaei>_`u64w0;$+$blL><&{rL6yX{t6D+HaLJc^8_8In znS4!`z^FiltmHr<6vhR5NaHhqF+HS^<{# z$x3MO8Ze|Yvx|Z#IJ-Stg|SSmvQ(S1LCb0h2fD+LdQgXtf)a88rAD*Iy0puDjL*C5 zhWX4(z4Su?jDSES{>VWTOv0oCCdwIiDZCJCs>Cz|$jrcfNzlt%PzSxt9nwHSr~^&= zNTqU}&h*S!E6LCtO;jvRmOD**kc`w^&1z~*WP?rElueT;klCa`n;a{7z|EiB%{CxO z;LK5C6i(+;%HoVdX#9}7im#|73+AkXR9K&GXbZW}%2)8l{zAJv!%jPkf^bx%c9Q~C z0MBC(PrZo4%reio0j&peG&6lq_>@oiq)RnbQ~TtGZphCG5XeEafGydA#H<6@aRcD6 zgYyUv-B2AnxD7!Klh}Eaf=N_GO;mhYR7X9XfXTo&IjR#HF4wVx3>Cr96it-_QBQ5I z5hYO*J<*u{ya%6C(G{J=O-Tt>g|bXxo*H}!pX5c}14Ey~Nb+;+aPA8yJ#F7FmMXW2>QXp|dwE0qS2-9~=hjGw5Dlt=X zJySHrM>RcHHf_`T>;^a`hh=bq2$<6>sZ%?p4KGMcL0uT56I6fwSAY#zf&JGqc~pZv zScF~Fgb5vwgt6bF15C}-<1)$I5wcIcSVIZb5*^jnG}Ti@)lN}Wuku)r6}FvpRbl(s zCxeMu1;SeGJum3JTg_FPEy*vFfnFufs1uqK0M;|ZE2YsDVpY;)l}_qZx5IMQDuq@i z$j<(0y`RQnf@|ecH@qlq00(G52lE71)9|QrC|9rz+dNcPvPIYTj8FT7S9uLQd)*Q* z;DS75nB<^?On`)ey<5B$*fQB$zU^DT{ae5-Si$w%go&x4V*@v#&|S zh>Nucco5Z5jX4l{s zX5?8UI2x!d(y?GpD^MS1fKFxg8nJ8E>%>kg{mS$+EdRQKRDeH7s@fm9+N{-xNC8(o z6kF^iS2V@mvQ5*xR9pR|SHUU7*`SRZfZIAqghWu7C8)extsOF`XHndv9(AtZrhj(xczRTXQwJ@-~Ua&1&`P@hF)d2AoMDm3Jd<~{V zfP_TY*ExU$_?6%IEfe)!nhm}2HD&+VH*|76vkCK*i|s=QKdwJ7lvUQ{y2ddo<7wPLgdKzMzD z2;hJ%=Hl{=fh}MJhN%NH4r53-4m&tw_5If|7SwFs<}udOZRX}RR^K-+XLAPNgvsA@ zR-H`91WkBnc#dazcIS4kXLpY0M|cE4CS-o@XZ=XUdO!z6Hqm-e`&e2S+1214<=ksJw24zvkU7*F4u~?cTO@&m>${ps5{xZ8)#!h*Y<^CE4 z=Y7A6!sY2*;wHXcyb)VqhGI}6W_=7mW_W=G2!Z`9z=EWWEw}{!3Qb@2wc9$F18ojx z)sgCM9^-M&W^o>8bKYt<2H-R)82<%abY|-J)r3jd1hh_TwO;E@&;+(dYfiX?efDR% z9^{k^=*qod%>CShMrelqYgOG~73~DwglMA+#)+nAJfMS%Zefl7WH_pUjvmr9BN354 zWs?3~VlZhZW#uT%GbMP|<9*s8mV!}8OPT%=npWaSQ3sF0!<^=6*)~^oz2g5Phh`A! z4=Cya1cAYffi<{f4yp}wgahlZs*o&uAXlDMQ4TO-@Qd=><;5d zm;_6BYw!;5O^EBcu50s_U=*Z>aj0O{^6#0Jhx zrh^;6Xcta{jfNu|P=U#=LMvPvKU_}CHdbla>{UkT!@4sk2yM}h<@@O`DZm2MK4R6* zWt$F%ym{i-p6%K8X>h>H6TyK6_y7$EoC*MeJ4FPh?q=gwooNnZtX^*B?s0LB?nIz& zB8Ow^R#b$M<2X)VM~H+-_-^rza=0dM20m{q|L40#>hItD?*Mmf0{`R@E}N{^lazb;3Pzu-5MVRpTa)1Wu50Ddz+#zj9^Yh(kf| z_Rw-Jk9Ny-6fzIta2j)LFLN`O34hT9L}-LIN9#=ZE+>NXq?7X)aO?t)BOJhUJ%=wo zS2K7Z2S8T_LEr4YGIYZtzw=Y{MW@zAPpC(?s7Rl5bjXOC#`N{n=?dF)(Nggh2lW#f zbq*+XLhRyHZ(mk#^`~Br^le|{Sl`5y+aXtu^o?$S<@F*5b~j-YmQQCUZ}MU{cJW4b zxvuhMr)&J^m2quN01LkOx_3Z+WPlPUj z+r*p$kxyTSfrO4n4mxP=fmQi8CX<(M{jp|pNFa8aulZ!p`MTDKX6NU42zq$v{h_xc z$&f)ALA$bVdx3J{1`IvR`mr zfrmd=dud?%EX+b^i2LCsKgO#5v!=ECC|$c$SSWUt)NGAb=PrZs53r z1sBj@eg$W%N6lc(2xQZ4pg3Ac58^>Zr%U!9T)Sv-<`t%bjsuTkxOjxmE%f2&fCY~F4^6tD@(V|Qm zv|z!Ay;r6xl`1S!u1I0Bgb9Bq{Fy`{g@6JwF-j^}T!9}JuO!o8gT@?_3^LMaBMmxw z)dSB#4^1=`|VTTpQW?u1tgN|d5O(vOT?Z|`LYJaYFnxKPX2B@K`x%S#yvenk8ZMi|3NpF+x z794PuT54$~g#Iu$9SxsKM_mopr6G)V+jX(h8ji$kwG7Z810Ayg(Fdi;-rX?Mjd$sl1L<(loE|L#<0aD zFhSDePBi`0Bu_rYgcFiS3AvQJLp=#4mHYDB6_)=3EYn$Otp%pRaEW;rnhP`Bu)}G> z8K<1_Xk+J{j=__0WbN=dPiGz<8nS4F3M#V6p(P5Nqbole>1~w49COT?93le@pwckI z44+b`0;#5M=UuAdHNz@5-MG_DddtB2UVN%(VXJ-m{pVj4z4}_9t-`8Otg#VJSmA|% zF*{<1{?JAX;q3!S~TL}{;Gt%>8n9rv~^dJ9J3no*5dk~ecb zB`WEH(vF^_rN2!D5;O5vB4)xanJ_RaklSEZCg;Jh7{YRDDcI%?R+qa}CWR^--3nX& z=)x7&qlIrejOiZs4rzeHe;Kiehsu$TZ+xS6x*H-9i?}=2sO)#M2_6$CB`M++uM&)~ zneueDvryGQ1tNf+^n|9W>K$zz(y&4*n5K;GeNSrn8DIILa6Vt9?>_4*)-l`%4QWW@ z9T9T2JRS%i4 z#1wIsGf*{eIwG)vH&4|a>Vc6Pv3QEX{!0nK83*aJUv=T=`^aeti15}d2B>s(pHngMsDC9yq zISaCjq-(K+NyEg6yR0;}vVBK9F4mZgy(1m0YfPH-5+~VGrZU|a#xRIM3}F!0xXRrQ zAOJB`c_=ly(p4sOT|<|pQWvVz-7a^P+B+p$(yGGCDipW62pM=4XJLiTSj!4d@Q8Dq zgWT3^AyQA}col zD$;HaedGt;>l{{#>`pTHZ33xx1*}i5HsW zC0-^dQM2^&Y&y>g0a?2kPVvZhzRR$JD(;w0_vClKs*o#!;Q5~bz42Fiy-$KhVT^pb zZ?Mo9n_%KGKY~2?T8$-$V-ITKgmMHVMPR6m9Qn|^b@;cp71f$?0ElmLM`%2Sv(d_-3_9yH8ScTee)*dqKLa?x zz#{Y&44n+X>Qh1!TC`#5F~3Of50DaG2!$bg>5Opxs~ei;bR$0<1I^;(Z&iUwsZZ^3 zji-7_tNx&?Bat~+!}u;c?$9|}?(%~07~uv1hpu(q>%d?r9W!rmAMKB`xmw-C)mx)Bpm(c!q;0>=;ubx$KiyLQLuR#dRT?}xlJHD(U z2QlEyjI(<$8OqS}D6r6H{t&3`c+C&6ZYB3a(;eXNyL$}j(O`ru7HxrlbcialfBp^L zg|&#`QJhL#R!exnB{V|DNd;3#-sDl9$XOo1U>@?#MU35=L4aQ9iJn1_p3Mmv>Ji53 zHHJF8gF48LJFvq#xI;Sx0~OR>4Bj3R4BfLm!XOMEmNCL3@F4FMUu#%k5Wd*dRaNsj z-!y^SAZVR7O<(nK2ljoD*pUZ03rIav7OtkAN%c@`}G_A0ao1|!~8K? zW!#_ZyiOZ7SlUEOiu@l!Z3~P1(*EITm;o9P0#y{00Es6-MMpiLiA7-KOyIv%U=X5I zk15s$?oZ1-*aw6EAW}zy+RopLpA~y8m37Kl|wk-Uk|zA>%d_gqR9UNV2VhV9U7hnAf5rP#EmrG zXjR2iOpxC|2_Oa{Cw*KYR>>h^i6IP(i)r2>uFg1|!y~?&i9q6FN#f~MVm+WkI;aCh zQX~qlLnp3-7J%X?jv^X({=pXPAR(w?YtRKJU_v4!-P6TlOI{l-5+QhnSuReKCRhS5 zN}n$}2le5=6N23^%9lqNqbgjXTg68^?O8LTUw%XZH1-!9QKJ}+f+}QVRca$Q)}Q@} zLpY9O8*7rHxz+8=3(M3UQA??;VjNn+)GZZi&sDeQqV*Ng@tJO<5mFV zzob&;A>WmCUh5=S-)WEt=?9#u^?(SH9s_PFPs(-@|PLICw*Na)S|| z0` zxaJbVCUnRq4$$UL+U9LqPcvu%7NkN^s?$*dr&6{Z`Me!GO{IO@+pUbkkwWJyK*)rk z19ip&IuH_eW~X+N$VCWJm7>TxhNm}-XL&Z%dBVUAu)#!e7FD!oXUPjp%;#q5$a`{< zn?l09aF+gl?x$7sXO^@WU>0O2+{Hvd;sye0fhK5&&|J-#UYh)xO+Bb?O=N_&LpfAI zg?6N7<^YDSfk|Q+hk__wY{Im4s))8$iHhop;zo)Rk0wyUYqn?&$Yu^)pLU$pFtSQF z99b5mLNoN{yv@pdq=H8&K3##3uu~%TBB*Mfrpsd{|BNh)ukU$JMF7+^JZYMM(u= zYuv@3@@t^BoUS2gK_n=zjYEZ$-Z&s?V>;+WKBy0+LpzW|q*B2VOzJ3BYKAHxCbR`7 zsQywVV1mYS&c|jMmx1c$i0Y_TovG?#C8+AEu9>SgmyBwk^%$G2;wpUX=&rgGf#gbm z5T_f})e>BRUa8-I42YrCk+V+cVMPX%Hp_LoA+} zfhM>mlMuyb5e~avlv1$keadT;%qw56SXq$VT5puYoKa_%K_|xy4)cZEFM(| z!kUg=JZYn%LsvpK&(s9DTI6@;}g3^ge(=FZ7 zWgcCu#Ux1GX~Lwa%EieRA*pto$_`H^U_#5TstSYv%))9oiIdSRgBIATG~{Y9{-}b^ z9>{>`#}fF56F4u>@<*-+ZGHS`T@?s~7=zM6t7JHBLO`uoO6}WJ3mlR|`I>LldV{uh z1UYy^x4vUaa4igY0V3!~;KU1%2nkYjf#K|LlT1>(s;!(t;M;1&MOogxs)bxEQwiN| z-h!T?{_EZX>WY|N!Sd5J5N?^w31sS);(7xTIBpT-Kn_%BD30*OnxZ0Pt|M%$BP2p4 zTp1?NfgvoNAwb(DkmMyO0u6TpDjEV!)NSV0qw1=zTfi=O!Nn1?jO>;w?Vc(o>;a4R zB6OU(Jz>PG*AKEV@WP;()<=3uj zQPgLVG)Whrsk;m?0T=KA)2#xxRs*}7Ip{3}pF_<7sy1kZ>Cv1{Ny9ZXX$@^K;(9O$ zJMIaa@CfUG4rC}=SV9mxf+ElXmv90lgpecX@bF!NASePOID!uf0u3jEAS6O3L;_V* zP$H115`!~1GqE%|v5G>m6z3!rTXD?BYEbH;0gb-4qPfGa0zQw!ct(fBXF!h7J?#hNhdslARq!FU;-<4 zLV7;JHD|K~C4vNnZA^x9T&LzZ&u%$WiYA;hnrYJs@bx-laoFKzHxNS?-|W2EGx#X4 z^g4kOq_G;0&k{(%E6^2w&=X(%8y&w7!ZK|Yy;m$u%WVYP#@Az^An@}8d9^4;&Q`MH?>pO9#jMKrFL#2u+}6< zkW2(I9qe#@bAlrXLL<=d=AQL_3xXqbf=-wPO=tpK%k_aTFXtrdigC0x7XJcz< zGsI|@12aHb6@&+B!*+Ng68p;b`=Z1mH*%N?g5YElyYwLg9d|yqv;sZ(11dMY#NwC`{zeS7;OdbrAGME-BRRB#;hi^nBY}EL zR3}(MTeH?#KtkrKWHEzoBp5;=U;-x?0wx&3A{c^JS2HF!0v*(?PRPVe6gYxw`VvJ7 zrz?09Gx%<3!X@wlgrgaC^mP%STRUHPoMpI@1p_xQgZFrNJN4>Q`YeeD8h0-r}hY-~cHyG5X@5^L4|g(8HmR=Y4+b3%Ilupm_WJ-+p(!|rW(`lqLC zsEhg~?18CIF%?%a62vI0(`b2kL$zl?taH!2y@$N@sBp$pkO~ME7(+FhU(sX1D9o|5 zKEtuwRI)#Wl)lbDQ5v*AG&fX%8hpbtRKYT|LovWS`&x91Y&(&M%i;+|MJ0)j3X*PCG7;e4`Q8yg&|@|<+L?)!`hdN`Iuh=Du;Q?nZv#VHJVSbE64IRP{^Ans(2@E z!M6i5Abc<9z%P5j!gInbIy^JAg(Y}C#b@eCURly9U?ZGG=0pM{gwm#4Rf3!RIHUY- zKmw`906L3G4a`6Y!2H+AycGVnw$0^>R%jt$8)2MN{#{P*JA&?XUBurSbV#_EqYj(LAH_hC<6{}Ju7`J7?NRuX(ELbX4 zqp)DPaz#pP*`R3CetiYYsAH=}_4e(1)iGqRUKvA%OdCyl@NSB8E{BJki90P(+a+f($}P zA+KcgYNCoR+D4;|JOW9iYeF)~B$7aahNYKkqDdxra*~dxc8bA4sG*8FDk(t}Y0#-n z62UT&L>6MG{wjqy>4c%KW@Km*u+CbOEV|r$6V5o{;)~8Y`}!-e!t^|W&&3*hEV9We z!z{DSI{PfN(Mk*EwA5DX%{AF#tBsUOA;ls%-gx`1x8Q~=?zrUaiRYbn(7BGu=&G|$ zyX?4o)j99H^9&F`s!&6i@VpVHJ@?-0hCTZ1>uM5VhSF#1*~$wnTxzO|eBfUQFoAGu5qeqiwR$amSLhxdzB_g8We%B3*(>CM4@I zRi{+TX-60+qYTO@DwS&F!89kSu%JpdnQCH1Ho+>AO~{Od5;7ffSWPxXc1zBZO+Gm- zJ5{#+)2}u1)RQni6=TdX$Rwj|8Ot!+j5E(b6KzpOlTk&LS2m4wQc5c&<G+#IQg}pp9^jE-n-?!8)oHXz^^heH`*}C7)dKaz%tuVs+Q0sNHSgwMZn9egsl< zd|C1_o+E1l*lU6fKKNjyszihfIv}|{!GRLlB$6&2u|$wWBC&)HM=;@}%ZES?d1N+G zeja+dR-S&ZmS2XMh8eW?v*ti?*7-7?5B*uu(oQ4VXy0Cw;)>cpVVY^*ax*5CsaO8F zU(>0NNv7(nrJ*Sv>_A62!htG*fo(fs8{2itW;S7{0fNG4hBwyJo?p#vK6Sg>e)yv| zz9AwKbud?k5@82{n6Pjf5|JGiRG>@{2y!ue9OY&vArDAQ95N zMq;`mrOqDo@E7X@)($ni4t7yO$_)fjhyg(YEF_{sB06!9ItT)IiKs&)f>(z|h%k8# zao8rB=e+2>5sr1C-W(xu3727SFx%4}%>Z??LG8>?p2_MQs#Hvs9;!=Csw| z50dxWU;mOJ4SI}99pmUm0iolya&S$7;t1se#nHfTe8U;xlMj33)(y_g=l%kABbI)Q z1p_LUkVCjMVTA$*2|L`*mogM)8n);x^bk+iBODUP^M@lG){tzVSz-0Hc^Kz%^?zWu!K7W!HE%di4N$5NE^=*M}Nj~ zj&!tR46vui?eXz3e#{x6#3x8XF6w-#NS|*MS*b{}W);SGWF+w?oc^&XnZ=ODU^%e4`j>W1AY5 zLBU>KkUd9;zyxlJ)4l2RSs5zagzS)s zs#PVzIUt(Da~(M^5WPA&YT`>8Va?`D$V%U{elo4mS!-LP_*O!60xD)P;q4$~i9{IK z5xeVBBEUke!4`H+h?Vfk6uUCU64bGeT})&Z3cf0M?IQaV?g7QtyKm%%Ap2Pnl!M(*`r`R>n zfPU#*!YOEQ%cWKq8ss3`T^De?+-`@wTV|tzcdC)7#CXYDCW-(FbX^6~5Z^13_@a)! zW!>7>@>>pa42-Sg6bh8ydI$q+BAsLrY*ftkm9JC+omfO?RP>2X2pe|7nSN7+Ri=qY zG?p-r1;JuO@TY_dYN3Dxv1Kt!8WW>06`dW0ir?2_NX~c^s^H($W*o_5Ji{0^#__1i zAq{1OqsIo64v>Q^t8YMX8K-ncu7V+-RtA^Jv&{Yg1&He(ok}P}4q`4)gQ^KS2q<^? zP*88(PRh zH{b?ba!?`L#M(Buy?p~Vcs=S0xb29&^W>`g;uJ#?8@F1)W3m#II0=tZB{S&_c3PL_h8Q zj!s&u9X)mN5QnjO>|^}0D(Yh^TQRcWjB;F^9Kpx}H^c!SU$KWANC^9VI>_w_l>HD4 zVqu7Us)^`I=-uzO7MBrZA-rD~g&O2S=)iHttna!D&AKEKCV^rG&&_NktKtmt=4^DT zX(Xh9Nbt<@l;|eL0j(6S;no2OC{K!XU<(MLOBQ4-Dh{0(#GU8>j3yy*m0ngZDn;_soaqhA+ib%=nH^ z)`Fw7itg5s!TD6{8KQ5bcC5!-J^ZIf1WQH;K3$bYZ-Xd4FJIz zPRSdnP79jhJ>o_iFo+6biMaOgZvHS3!|g!qFR3yTL7MA8=#9F-4F}~-%pgYI9z+i+ zk<2c!hLEXCuxOb&(Y$U16hn~$7w;Kza=unk72%;35y(5%A^k`&2XMd!YQO{yp%8V@ zmxO9~@@fw7Dot`naq_7`3?UH&!5EPdLSFE2>`z46q-0XgpLQ@VtkEv8Q5&Izc;0GAu{P{uD$k4evNZCt3V)+g`cezuQTdi{9{Hy@3=<#Yp&JyFF#(dcBvU&iQ$oALI}#EQAV@<= zBNaCDSAr!EL6ahnD+UBXgzP{MmIa1lQ(NebMK|O?#w8OeM@4Q3ha^omAFww+$0mWZ z6m|08R{pU#j}s@rW*of39mt^w+Uj+1;5kk35d=pUGZF_G%$*>icP2zi|7NeQGArvq zJT1o#HsOTWh+5JUsZ^6dQu0MxRfQ%e-^9f>UF5qm(J3}DM|tyyw!w2GFyMStIEiyeJ@ERdLssBH zNukI|mvvc>(gZJoc`{)vcu~^Qq)R3N62f9cdNDx+VM0o8L_#D+P3ZI(WGu!~2;rzb z{@v3WvGEY#R8HMe1?seq@Ke<|Lr=@2GYW|qR3R0f20&NLFJH`QV$47Za~O<)Q5{to zl%X3MQ&KNAGA&gg*TdQ5qih0_Gs&R}FaT6Tl|;FKx%42*qzrHbuvJ;rRblk0d=brT zv__Na@0`Npa&;zqlUFw)0=o)WfpnYVp*YjQ1C#Se{|s4)Kv|i!S)o%pv(+r(I}8F%Si<)ltBVP)1d!rHW5C*cruAdlizk6>Mvz2*y1sgUg2sKR$-GN9c;qb95zxNQ(`C8QlEo@zJnPgBQlnO4w20M>pIq^ zLbU=&bPK2~03~Eu-VH)fc4bFbTwyieY?MVT=Or1?W_hTG;DuLvb-fNxh(^Lkesnm4 z6llrntddidb^;!}fe30KN}SXNju&~4*8~Y+grs#TeF_%DYm32s2n;Yf*vS+ee2pd_5w6uSVA*%k7aXLb(iFASrpME=b$w= zL}f{Lm|F7hSY%dRWOX@_-*i=WZzN|y2fel-Bmi#lSVB0@p*VNftc?EDq{;yvxB&-< zchHP7d6oADZczZGRorw(1;Ju>E=^=kQ+wqiEy1?J5Q`e6$4$>RZOeDV&{uuuGH&(t zed*R;hwnGAz19p#Ha(7o$;*XhH+D<;Mto!>*z1OI zcN|g?XkYmQtHU{p)*YO|D2vyKkGKYsm=M;jxqPZYG+~|UAX~FjiVKU2)l}2Q){4Q` zUB@?0v&St%pa#;n3Ex(H@|91|xIgPwjVY>)Evo27A&&J|YW`N?Z;gQ%5;l*aVV(UL zLTBrMDYiRyg8QQF8(50!gke1{h%+Qu4@DF|j!Omxp%Da-RZn%^Ug$(GB$G9{Rb|w2 zXfy$F^D1<6lwr7(d9@>0f`;SF0#7F=pXm^umRaRko#8NW`G}9V24r9h1VLn7 zb0EI8^Qh(Rbnuwjlx&rm2U!n%Q*;sr;Re1~n(LFAt(oT5H=Es;U(@(*!#QtxBaSge z)~04^k^v0;cNm7D9Bjg!hXJ0UBVq%2Viz}7{Oo}U_>@p73sQkS{F#EP;BxtpB1d&^ zWPlMOAw-%ha9#*-IOO#pnuH~KMk}YHJ&w(+N>{T1{+c$rlt03wEh3de8i!4>q=gig zjnr6;wi#TY2AcGxl~<;{)k3CKJB8W*s>R&)s;7OrnbY;KQZz-qcBqNksFAw%s<~zq z%Bc-So4J9T(^0DBM?l;7qRJWhAW1m-_p8G?9-!f?{g@}tx}?&YLM;^M)PONr3a+(a zSLhm`@k5|9zyb*R5CF@UmgRO-Gh5UGu(QjsM;Ebam7-PmRwtpcndz~6)rU$MvMIYr z(95z(QKWNnvq|M0P};MLmK@-r8B}1jmA15XK&B0W(Rf;0h3d=JiA-TTw%us9X*;oO z8yevxx34i020<{oF{zc>2ACSS$+&&XgSdD80GmK$zfOLm<~8s(Z^^j}moKB90lJ4_ z9Bg74z8V^$1G^`bo@1plm*LOWW1qc)ysIGW&O5!2g$w9JITUI&wlNYeR zb?C7pI~z8dB`m_DJ%?A@oL5f=cb{RwOGRsp^plQt87N$7a~Y@W6NV;4fZbH za;LfE4K}e$TvT-Q3`AyEltV^X%ngJk&+N?p`5U8e79-xpUFe+7-`pHnLfs)dBXC5d zkwL*tTF*^|&oMO~is1w*+<0~QFfe=zFu`ema4ddF(MWE@rBy5#K26{Ac``j+IsFJf zJp^Wa<~j^~c&3a`y|{&D_$G=sSiRQl_l=o*3pM4}W?f-xJsy%g7?dH`uRHsCeY>|~ z>L4gXafKP=!~E_r3y?ij*{|8xKr2_YiGLXY?V#fFvDd){*r9ZVV>O652`Ti=DD-%C4r13m_}z!5Ss(HVYCuGi8O zo}cu7?ehMohuUN$zFjZD#Vh{hY@q&DMt$R-8pl6Aer0S^c*A{GffY)@FHb(mw=nsh zf#ruG7{tLQ_){4=N!NEhyA^l!G1WUXl`?M5=ivkBFCYT+aM?5W$&8+;w2T0y%Rx#A z-6Dj#tP6z24VW7w?krh)_$ zDpaVap~Hs|BTAIGup+^U7&B_z7%|hOK{9BF6gi`$Ns}RL09q-mSg~a=nK5fdlbcO$ z-n<2?Ql%KTV!?t29cC=ms{U82MvY1e3KXfQte%oum6U4ISFe<6-MaMHGi1iXssdLp zp0Z%7l!+@>Zf#p}aO29IOSf*gyLZKfi%YI=U$)-@lgpX2n_oGx=ZXWIO^PM1V=xPYEhXO5=NnnAzrvbqZ$&2(1ZwnJKT=+3}{ z3m;Crc=0;OlPh1&yg70@(4#{~o+uHdNSrol-_E_e_f4Jh<=aNyoVjb&uBl(o{u=pe z>9b{P-~Q_}Xz1$Edp8c9|8W2Y2q0X2!K20rHYBLvf;QCPpbnVPQ3rH-WYXDfb=V;g zZkpLK32zx@D3FB7{!KCoBz9Dnh(QA##3Di#CB)H;G*&2MjX37m(MKVTM8ioyrnC|u zExi=eOf}t<(@s78B-Btv9aR-mPCd1Xl~gU2C01OKvehbHJrkBOs&rGPKF1o>v0 zZFH!{;cKk%=GteqLC2bHzO~k2hsLF>S}Y+Nk^S^*nMZ~cj1j!-g)V%SB-q? z&F9`W@LdBeed~RN41Vp1eO!efebR}po20*$QfrAX0jVXn~7@2CZedmiXbBw@v7gyRkN@siC2| z>e;Kh8r`d~#_IO0j?{YV+}h>3>v_YXm(4c8&bJM(#tMt*FQP4cK~BHHHeB%9nO>bIWRQQ_1V(Yuhpu*WVtMgaFX62S!@ z>{9;13nyt)P7yaru~0;*B4x%`NWrmGQ&H9YRatTMWy)f{LZ&olqD4$N=+G<|<2B<| zS6|H(xss901S)hlI2CF%(iu#q^ksZ7QU@d`&MKiMvnB$a|KFMrBuqyKLC^sbWW&vb zxP}_jl?yhBK$`?ZL_xNZXhLgio7*-xovkGzZrP#R-0b!@^Z1Qdyk*X*{)MSa!IN4U$KFo?9fBQeZjOOlwADn_Q`DJ6N!lZsWIH?rtyq5!LaaawLSBR=NvpNmP*pgTuC3CPzXX%%v`6RUHt9P=vS=&)w!@9`vNp zSnBy33tgBjWFW(Eh(iZBqE$lyks}@P$YHj4$ir@N;S#N3-GnZrArQUNRJ@TCyNEbI z6T$LZn&6EU=^(DKY3Oxb%%?s%^2OVQaRwnU7)#2iFeEihNjg#E@StQojJ;8LO<~?t zumrs>oeX6v+oLgt;YTzL0~~^+%YfoS$nPDpPIO6(Uk0OwDlh|*g*nV5{tcC=OIBuS zd*H+-@n=d$G{O-;1?o>tS;|Hvq7gL}u>w#;g)qGO1z zdiASa-KAJr`BUut5}4f~p?S=4Lcj41nd#wMuaG4yl{pid@fe3Tz%k8g0;n9=M1l^r z#myR=pmLW`godCdqIlN9v5+{CW;#iS*W89(b}%I!Oq4-*@+O}0ln|tzhR=PP_OuT1 z$R1)Kuz?XQpt>_sLC=WLU?4$wPYT8`h|$obJT#7SoYGZ{A-#-IUuAwD)kFD)PRI#3?@*5l4)YHaDfRpCTTWhR19+ddWoR=6seDhuY4bY z)cV>Nsf@_4A{c>6r&{%@i12EF4N(X~sOYKyMudXF%HRffwGIzXj)NUshzeWy!Who5 zg$YsNuo}1#m$(Eb&WhIUknnGB{o8zw9@JL%nK48`1*bIM}j|cnEfz z+@wJb5Zj1Ju*e|zOR^=FjG9fHXc8WbEGtoC2`GE8m5De}9ke`$0j86*7D26;^Ql_a zwib6G$U$s*XHYciHZ?M$W1rj6Nwt8iQircrUBb*r_$>i$`Koz-3sJ0osQYg-5V?7u#Q+Sji3 z1|Oo>%ueh0{ar$QJ3%e6*1S6Z`BWILE{{8(9mr#y0Ioj|6HVxw$nn1Xay2z)1P)JAwP2!n@*cdZR~rF|YA`bW}0vb(bsX*7jiPuX0^o=-M3JH6eZ$EeiG$dTXGDBxPE`;OvrF^ z?-viyaC3cBe}i;?(v^P%Vkh0uI}cNYfig6P@pPPaUYDdaH+2~exN55QbzoP4uK0>$ z*MYKVcD*Kowy1U(wrnS;g1ngif-Kl~e;0$oD0nk?Op@nq>|ubbDcxJCvePo0_*ymY|haxrvwawmuZikfzS9B?$HxG0#^fT_lc@&$`J zIf1j-leOk+wly>)vE(mzTIE>;!Zh8ZGdD9+HsY1-yOwTAoL^zFoRYTT@ z11eAhqNk1BvIgT=mS+BWmfh%YuZNarsg_n)3Evo(T)0VeS(kQsmv||U^SEz%iF*48 zhq6-;b|`%wXA&S#5+G-NN5Y5Nw~!49NW7H_s$gdmStSu=kr$aU8L0}#;7nmL49O6Z z$}o~8iIRF@GmqvMgb_*H&@eIel35Uwh%%E3h*O;4YFU??61bDR8H+w?YljeQK?#By zMwCW*oGi!)A?9~Vxs)*|9_t|=#R8RkBRI*_9+AglDsz?NU>I6iO@FZt?ckMQDLM3Z z0^3NIY`LE7*`Dqhj&X^X@;RS$$$Cqsj#fB@RJfn~DG7b~pZfSB7Do{RDSiKhmPH-4x6}0 z#Snmv1SsJJn;$R%6c9hOxhSZ3o2sarsMec4ih(^DoWM4mY*(DhcAQ9>oWR(e&PjOC z$sWDJc-DEH?2(;<0}U#p4Iw!Wn5LDCHZ5$?mFQ`nJahtPYHu+h2=BS3Y}%&rNuPGf zg>l)Qa#^QuSqc7$di_a-|GAg=qmKpQh6Irag9%1q6atBPkc-JM4Z3|0`XmxcCKPI+ zt8iyhk)akj3Y^KI7j;}8dUM1enz}WKC3;;aiizaFe=o%hTA*}GM+`v|iix2HGg_lJ z8b7v){+m47qd)qizqtrPns!8riy5YiNNSXYa0siGluasw`CtvzU=7%S9&_bFZxyB4 znJg*;4j~y1T}q8$$`<7yo;ZM>=V_)VfTn_wg>Bld?pl^_3a9n?u4ied_L^_SuqJ3}WJl z&=`M%1`TXsnsS1gso4z0@JQT{Ffh>!>LaVnv=ciqrV!gKsl@< zs9`I3tjMaYeaD>5>ZJLA4bqAY*buEw%d}2QAM;TxeRG4(I1cdu58%+H-LQEA5~kz+ zS`LPFuIOrSGO(`ny0&YZpYmFla>=%>XNB+yj;{BG>L`16d8c`sxBkhel8|HpOPGcE zaR#fH3#zcc!>~w@1r8e&5G%3c2T{z^6quP5sep*mW04Tau^V-BA3Lhhuwx_(7pIzk zD*7;laVL=!C^iwZwMw(4XtV33qdB{ZJIk9rE1W>PcEwg%eVE>x#chi$mJG z#QM90P$zI#ijby}b9JHK+7xAJSobPK<4xu0AZV*87SK~k^*Jiv^( zNLr9|j|(M|xxgCJz)xYh7HK@IkPMt^T*t+c7EBBn42c@73>@6SCwjW$(-*CoCwu}E z%Fum+QW)G|!nR7Aw7UkJKx!+z!Yyopw&t51h;}kuq`ymd$cDqRZ2q*x3wS+Dc)^sJB+P-Jpx9WU_c8bRKD+#Oz$1otr9~S~hFh+`+xFbh&d3?Zt2!4I6 znQ$ag6m=9xu?om=$m(|#iVT{=@D7fQ7HUzdCOXODlSqRh3_YVWNvCwp(8+`%v!PtG zlK}%TK+2|k$~l|Ltjx+B=*q7Q%f365vy8)puy;$!!@($wyPOZd{L8=))V|OQ!0^+* z9K=QJLfh#T=+Mm52o2PjV;?-t)qKs{627D-1A_q0Y|F#p{ydIzInH4mjv~gccWcgY zi_USW&g;y#Y^{3yX@&AUeQ0LSdPu;p%MDu4xO*Ik0lgIC7X?y~XI661SV0vD4cHy} z3Jq<+=m3eS5DclH4B=3krfJa^Ef;liLpq@a0{qzmObp&24u_Ewpe(zzX&ET3(znaf z8R*g(h;}g?(=$xdw2afY%)>m*!{YJNLH!HBJ=8^Q)YEXx(ZEdS_6(2x4$dqM(9m?z z9L*>-&E8Ctb>{z0#@t+C3@TXGhzxTwygGytvJrI{kvWtkb>i+rjM% zz97uW9K=cdZA<)vkBtt_{M^t04$>mk)7=ivVBLk4%~?GN+RfG6J-6OH*6aDj-)-LN z&DP(D-hS)G;jO>(4BtU=0!VPzcg)Xxjo(F~-r2g#$dtX0NIl5 z;1G_95{{yC@fSB#4&(q7j})VW!r_PV;R+byBTnKiY~r(s;=HTkwQX!SOtdc^<2ya$ zyzSFQ{oBB;<4K*o$Xy>yjg`**+(zCwDcSxG&F}#y@Re7M)jfp8P=40nJ>~RD-c`PO zz3#=pzPDNqzh2(uUM_q583|#I24haQP zItnI($eD}a790-oAPo%e=M?Qdfet5rVd#BvbT8!%+>mL3V!|Ho=qbDfw;Aa?>e{PZ z=_j7zm`AX^<1rqCG0w|I4a~p*AIUtm*f6C^z3M{#)J6WK(#;p`unZ!g0^S?G zxDJ-OjtQ3F>*Xt#!A{2Oienf`qGA{8GzxT*o#6KO($ovbyU=7(2 zwN5J^)2httfeZj~4BESR{^+C~geDE#IED@56jW%?my#wOMH-3HrAKID(11!~ z##E|PL4-JgL>M4p!ip7Jwv5?WX3d&$L(9z@H!W1EEaOHj*xb3qhNV)aYSgGzqohQU z0!lC`!k|d?-MdOLvR8ZgB32yP7&2tTh^gup4_c~Ws)m6>S1#gc(xpwGj`$EFYKh*4 zWr3|Fix%3!q;o>pGTkme4#>w2n~WCU;h00g%B}%bO{qD{+v2>;$)Le{v)tI13Aej zA8f3NCYoxhaS#|^9IOVIXsp2on`<)6Fq;Ld(eN4(MSKRCW2Av6o^cLh5uDLfTaBD} zgvqEzi#F=$hDB83u}2?O!bv9~bMk3Ooqj?x$)SXb38|!(dSs2KK#;1cs;&wFiDJ0& z3M{e6GAk`^$YG0$EZ~xhu3+rSOE13sVj{4?2qO%!$5vtNO~n$6Y?aBLF-9(K;*o|k z&$68Q>*hXRaL`euPd@|y3vfUJ0X&eu1Q%@ZK?kFm@IebRwB{NNJ6zBjYpNNML=&}% zM@1G7axpbW$(aZc5p1lHqZn>rJuXRaNd>MP%vln2t5-^k#j} zR;%H|HNXA(<5j?4feki6X^Vvr!ep!Y1sDuPl$Jwm9(=IcYf3x@TWz;pNE{d;BBva9 z)c(MVT#RVQF=Lppm@mHv+D?U-g&YVpgXy6UTMy-(|10rdLUVEq#t!Ul^)_E=wj0g>&t zKZKjw5$U$uity$T;`QxsffJlX2%9mXKF^8=t2x*w8=3O zqai6?XPDS&>`jm1p$~y5#CH%e7(zT^5{0+Ky*<&HQJiAWs(3}AX|ZTsIG$~e zF??k-4}01}D>Tx_KFVR^*VG9|uFcUx^OMjo=9kB4?XMct@Zl^|oibAI=OCy4mR$Nu?GM9?r1IOqWn zZ3L8%g;Wi=+EE3B3T}a3Sm=@*3PFfU)Ub^6NkuOzl#FU{qaFQdDM5;rkrE7rs}vJC znxRsbs%aQ5jUg^8Rv2E|$(N%b1u*+G3RNWIhshuYGz_8&pGy9NWupmRP+>OIW;&B* zP&{g;q#3>7c#5e_71dLb7FDWR)w!-Jqd3cFKCTWzjbGhbSi{=Rd^~6xbZihX`f<+lZ5&p};mNjWvB>c(>%MxXs!OJZ-tV_XE`j&}d3>HF+!%?Kd7(X1@ zGsMwT3`1iaEN-!)UaUlB$~ZN?t=Xk$Hm2uQu>w5y@sF_=WFbej$j=p@bP18<>cWS~ zPJS{w<(LoiR{5UP_^y_X<)CTd6GI;Q@`#{u3}cL08f7-~K$l3fH6vkMCzwG(;mnDU zDAvw*mYkmD4HG^OrNEUq@F$xLSr`y{vRFyOgD-p1%uYB9jdmD@ZRr>(Fd+&YuC!q; zokJ|Z4u^g5G%{dI3}u7^8o}_XsaY&zRj<0${t*FJ%EjRg)JWI z?zJ|5?c88%mDt5Tc6O8PKK?BGK~ruk+2prnXj8))*}yj3}@jDqq#? zX3a-9ce>F%LK>VPD(l9?obCPYb>Vs5hP}5Y{rqRgIzrGe0AZm4c7lK-I&_LIIHN86 zXuN1R3J<4r#8Vq$ZfhE~mv&6XftU+Q?qZ)H1^r7>r=wz+NcdJYd=4W{t z$uE{Qlnk>ZoX`|vR5Ti>z3z>%H%Q7mPAMa`q zYRHhKle)Q~x?@0xdO!#7GqY|`lmc2a^GhVCFqbW8Ke>ay`1_Inq9pozm-}lhNx;89 zR4lb)EdFze|MMuk`=AAzfe`*sfq*+91T?A9Q40nfya&XK352PKv%opIw5r>{TUrIq zkdzPPhRe$fkqf`hL&4HWxn-yVB&Y&Fa0by!J(?+oSEK?YXa;f!2Hyyn9vlwoF%BRU zLZ>1^ojby+iW($a!mZ*e_c(-Hxdg6}LSCUlYA~xS%tAVvw&(jorSqR>7{f9=!>oG- zXi&pe>zj3O!@f0VmSiiA#riGA}Zy(6VX zY(&d~#L$t%(c>%!{H#lqw8OJNN~05rF@|H%K*$S4&ybXm{5Fuo3upkURb-8nBL`_y7qA0T2MjpZUQiE5BcT2uk;$jcQ8ZmJI8J~xzfvlcT7KYJG*$~f;sGo z#PJFLQVM;nAb#vafYi&1nhAsaD3DsjMdUk%3=4-;z)4go7rHPARE1ZtlfTf!Inkwz z+>^=pM83$2&kD&Of(B&>1}!MblSE0=P|0%mhMROrm}CZLU`?6)24`RfKzK!$)BJkgp6w zaXbcOc!#Tl{)Th>&hI-*7{q~dQ=s&#iUo>+x0Ew`^sl%)9G1vOySyZz=*PXpPa$ct zL(qZ01k4j)L^UAHNIXnPNlXW%rAvHFi^ND9+98nn#8qg_&msoOGc(unOf5J?(HzYb zR7sgEhBOe-G*ANp1%wj?1T{zlm&{4qyiMQGP2#EmC72;IOQ zGKNF`tx(sJ2FlCOyikTy>(JAnNttAZ)vJOPWzkG^$&qS?njr^a5P=H_fuI!18I?Vv zyiwabO72NY9`#WlUBV?JghP+s(^}k{L=QULwFO@_%zcqov53@x4c}_e;kti?9Xbwk{Q^D z6Q}_JeZ;~9lLBRi1NAIg%8NeD#L*&!Qn;80J%&&8v_lOP3td!6`OLXchRusq*6=zQ zbcPh2O-+Tx)>DhMPzzAafKesYTML>ReFR5HRUAD=R^2(QdR17B)!>^|MOe-#?1cVY zHNH4PK5WcJXrrU&!`W$gmOFB*|3Ow|W!7_a))-p{Z%~18nTn|BQX0TkFrCZzB-6J0 zR&d=z`y|(L{ljy0({u%?QUcI-ts$;s2nzzwN;pv+4ng3qlefkx#275{kM{t^Gquc!~bAoUVmaQVN(55dPb)@QQ{c z+Z8ffSkhB`ZA?440tTH-nF=wv^~kyfqJj;{MD5#PNQQ6_4OA;R-6Ea>Qb}%L&9Pw3 z)})!`VIFVLSji1a$^}`Gz1-gnGR@uG;_Tcc15){Lk3taLX}r~#WvBO9HYwXmV8z+! zj5ca}n`r2TV~K|UIR@JmTJRJo-dzW02s`u(-l&L2K>$#5- zx&Q~!AdSEkT;3fGnj{CA4B*|s83NWB0~P@ueI11@?&9>F z%w7uPy>L8?BjaQ!SQA6z&QN1E#;wsDT+)oeN-fb&MT?xtV~yqG$pvJ~733j&V5Lb$ z;#3brR>IFs&SCSaMkWwnG2JM$2AdUOo9*QA`UPL$$_M#{Ug(8|?uBO{k=hMq7$&5l zA_sP0h7b5%Q?gd|RArH1%Eez z$bexkhAA!!daVL8X3TM<% zK?7#dWiY+mIA=ZH*gm#^KQ>ijd}or4=T({KWwhr+#^*HBXLKSy4h9=YPLStB=uVyn zOQs|7@>OJEXorSq+J%N}!QHIeU5jpqWzcAgIAxE9+K?7$$RX)k2FSxwX(MLoO4wzX zj_H}MX<)XBfUyA+mEvOV>9YOlEGUcdy^L4r)4p)#qwZJO5wWEP#ZEJZGM+rUkm}wc zhN^DkH-@p8VKvnVy>H-#ZvX@qEe6)Ux055NgKbGPI)@3!Ttqt6hm0J2E-QS#bzdQXlxGH=tNY{ zLx9@K_F?7CKXJ`$T2_*k*4lpjY@~>3nWkw`B5f==?GsIH0=-jL1nOo;<`=3Z*(U1R zt|5O-YTKFHjNxrux!^UbYxQPttK3S5hVQ-h)q;*r2w~`Vf@tmRZx#9P zq786&Cj3-pSVkL?MHu{@IG|u3XkcHqUomq<`6P% z)Fx(Cl0>y|#VT-Z5>K7ZK=C)(;wi9gR$%ek@sk(dAwNlmV?g7m#;_7sF>n56lZzQ< zvf~haNlm4KU|=4!Acu=`3NL1+NEzZwj#%{>~Q5qi8r^A^{f$Iv=~pj>k)w?7if3fup&)4RzM^?c7?h5_dFnD5x@ zuKE0E1W3qvok#ZNLP}txXJ&uvVH>AsbdRF{8fyRYWozA~59~>9kZCyKEll0B3HSbf z2dp=<-8G(vfOB$ihhQN7@O3}scJBoJSW~i(yIkgMwC8iRcW`}Q61ac+d>eSAXe_0` zgiE;lDnWPAmW%C6+4mP zXN90bEBpisL&l>^jUYK{`sgXts8Xj=tqPS9)~s5$VpZf-(W0dj}5MvuCiOfeQ!jHp_72+Nt?);>3*L!iUeGLF4Ep zPM9!Js^t7R^yrkPQ?G9QI`-_;qjPsI={xxD+Q(;)Wd6K)>)yeCuOunDrTFsa&u2er zlBE0k*+*J_X^;%Y8AqIOAcT;C4Qc=)41>iev&=HgNZ3p>I$7w=GF4QOMTEm_w9zmj zs-ns(3@HSP6jFdP5GkaTLdrk}E%eYa4K-8{i3m|tQ8yA1`3;m&Ldi{q-gI+ePelzSR8bjJa8y!DHRXs^WtwRfS7~nb6CfQ||Wfl!EoK-W8W|oy!8EL1XrkXab zffJi-vekAQZYF`Vn;IefMj&yIO8fLMzkX_(s-lHL_h|kOh`o#%j(U97L~y$9X0%lfJxet_R+-p_qhOE$J3XuDvy#XuP0gcXik?uH#| z1fqx}nh3AFEV}sOy(ZFVBfmNNNRhw<|M;OY;&@a}IUzkf`;siZL=H1)=)OA=NF1R> zO&){nlgL|k`6W^(kGV4QWxAXun{CFth*{LrjMmNB(~0NKdiLzs&xQ$l=+KDfQ`$7Z zB;BZ`o&iIRHBLWWTKk&DW_4>>gNh8-q?Y>9IzG)x?~%TVuh*E`<< z&q`PFM##K0vg4UdOet#~8l|$8N4P9{(|e=!u2&ZA(UC5D>7HKr0zRMxjeO+`Ornmq zKK9WM7}bcM{GygA`#l4HSes31`sX#10I&*UBVci0a6siaP=U09;B@Au9bQ#Xf)&gl zD$%n+_8^OIAEZy-L^#3{(%@PkNTCW@$heBVFmmIn$qdhxE_G!@bMO9&mx_3}iWdEl zMhp?1L5g@pj3{v#Obmu9k^vIf$!?0YYnVx_NDgPHAr0kB0}|4ZF=ceaO5_UT$Vg_C z!3>TuLqr^DMn~=8NoRQMnIe+H$HDlqd_{B2G<@L;_F0A+o*@l3 z;x|9~k!EUC!yh{I2ODHaV^scY(mIeJ0^nfsI9uT4c3df+1%gtP#Zp@-qftWljaM>yqRgK7JnU7t zvQI^1MSA`eOIZRMr-5dVdxPjx_x4fYh zgt9~`8cs-{rW#0>4RTym!zH;31+%J{8%8ROsnry5m0qP-rdOW{k!RAkAVnnWM3RA4 zwNm7A#Bdl~yD8U)(e-vwQbX_5P!629^RHSO<6y&7Sj19zpKDxGRvrskfL@QXm8A=x z?zl6}a#o>;Vd!W{YqZlwqf(TKl>03D+KWofH22fS{yRdNn%b7MYo#(tIo3f1-V%@o zGJwHvFM)~lBx|O;VxV!Qlbz&FP^YJCZU%qa9_dmSmevKF1%HMH7}S!fPTeJV!6n{b zQcjpQ3{f$&$~h^tmtOF72oRA$U$Lr!tVsM3JJ&i9ji`bc-hgXd2Rzr@xP%;0Du#lJ z!QciD@4mSc z%UOQz1i9Sh>Po^AvJ7)l$&ATc=HRJmhT(Yr$yMGO5)-}Z<=hTIN09i+SI=TKNT2ar zus{<-B7`2sp<^dtMHBe8F0pHrWCA4ye{6UYDBe)WC<8_)?~I#HW2d>oSg(Xyjx{Uw zobGg45$~x_hiElm#7CG-&^*?F%!g?uTA3G9#xh8Y6h(cGDPa5>HC7vkuv0@)|4mXH z=~%}RE_p2h$sh*LUXBK-{YqpwWQHfy1bJp z%@Y+!z|veAEXV8laeS)D!qIcAh6Vmw>O!#PPJLpz_xAGgrzrWeGp~7Q4MnJ;wKy6- zrwr15F&XvwVlEqbn(tYeTajatGZX{HV2tBPPEP3E2^0VYU|difj|IfQ zA#~g&7}n@9&-3up>4n;;ky;(yiCny1s^t+O90DRJRLsd7XyKk{(VU7=O8O*?Xh@Xs zJ)!Xh9Yrl4GBlr#)mHQ|UH)y513Q#M4ycN<5r}}qKnw^1_etCLfnWGN&~V8|mC23R zF;@nuA06fm`>CD#=?CFlg4@9W++9lu)L-4XPRo{?Hz70Sx#d0R^J{*FZUIrWp zgaO4ax*!bx;+X`aFfsx$&J1N8V?kloh{;|v*4{JXUTDqS%~1+9s1NYzoUOIbj8PQT zJi|C%q0*V-NueV-5W#P>W7!xP8N#CjdEZcdlK71ucMMfNhKKpdVL$$39R?(`3?$${ z!XzM-7aZgtvWh|uB2=lFAwFaxHiJZ3m5BV^C}4p?TqK?CP)3r%MuO%l9K$MTCP*qo zDh$IWu8TDOnB<_LWGZyS1V#}mwj>6U1Wby9la#|Z2t!__lNu;U#^hBI6u}Wx0W2G+w1N7L5{CBhq-~&^5|x zMNKdCLZgf&6qzM5WFgb3B@VP@_ElXB5JD#)*E~vDP2pve;UjeY%07$eWx=L;dnAsVesCqNt8tO0-Ekz&t>B@cw;oYC!5MAIbtDN z`p-+S13BOTJMJeLBEo-eM}XSp+YqQs87Lfb8$VtUgVLdcK4>0Jf`nqiBP1pa1c&?) z$b~xQyG2gCaj1lR=v9SCh>j>0T%?Ka5R1$Ud@TerqyiRf&WxnyY7zr9NQ3IALXD=w zO73V&mf|Vurjw8pELH&^G{fy^AWRr35)>yA$YKphK<7cJE=p-VNoSQ(1yTxQmQL1| zaw!jb=MVZ!c#e+|7U9ip#%ZPLdtxKcrDvm{r)VtSGtB4n*=ezP4HxcdpH3ZIZvI?6 zhSIpfP1hN!P`#4gDC*ucss%CZ-|WYuLMY($N2MMlCT;5dHRLbx1VoA|oTZm4m@2A9 z!9dIljKs(?xvDC}Dm1MEtpbd#;_5_5Bua7vuae>vjl-{sga(pR73h^tX5Pa+98MHN zvmQYf074BEz_bQdE(&D~48oLRt5l2$l~R~r%peVZg;Fl1oP6tcCZjSkBlwgnnObGd zrEBk@X=Jo36K3O@Hj2B(#%Tm42|F#OfiXS}Ji=D8@3RhHh*zm`gK6$X-E}F+JadTskBq0af2}Y?1K0t zF3M8{tf16htC?7>rd8>cdPQTUo?5IPmv(8mF5_DOVcDM8*`lqAS;{n|C)>I!uKir^ z@fyzo!=}`0ot|ah=9W8fg9`ku7{a9&TtdJGfmIq#bRov3P)pR?C0iP=u$xzjIO9&l|+unh$2%ItZ>P$>HxZ`h~kTl%r3x$2sA*$ z?W#iV&Svi($&Ko46~!cmXh9aJ!A_8a6?p@x46RFWLooyo~pLtucbD*y=_3k}LY&9@>^EW3*T`P=otAVe&!BdcJ8#iRF&nExt-$ZF~ef zG=mEGhFg|d;Ksm96|MmzZUQr~vJ_|otKZ`~a-v4=!%}Vq=LbP@Ld9OH26Ju*6alB= z-!JV%2#+x7maYkdEEc5l6)?dHv+&8bFerp3K_Ej6Z_YEV%SaBxGT`uQ>agzqZq8C5 z@CL62dP6ff&P%uiI4E)Qx`fBr#7i(kwBjNaPjA$6f+G}`7C%K6Z*zrlarZu!w}x@p zJ|!8mo*7qV8t>j3uhv$w?eGzjqf81%#pjNN6n)xf)2;qPPksQAl}&LBav>*>d)%Sc zC9;5i-2u}F`K8|@J9H#ZF6B-_1y{m#VY23KvZi+OCl_KU^8^xzFbSii>5fP$!wV~~ z@QJGGyex!h(lWn*4VuO8=CIxNHg{&N8ZG+h2xJbH&gBQipdM_w*VL+{i>8+Jse-$YY%+O6FsKY|V* zqy{r4M|&^~ee@`gv?-ISNo(dRxAIEQOY1%pO#c5+B_4y~=`u5@&I#{yuS(HR53`O6 ztCM_$9{Z2S2oJfCLo?Wb6g#c-S^x|63 za3z)%MxYfh6GLl51Fs4*Y>Na?(-lb|@z)sjZRGakGy@L&wqON?a1*ySOLf&|@l*$+ zR#^2v!AVnwZy{{8_(u0vd)A53oS+!W{)w$IX0$I8a^st_7HMb(jL8_(Tt;n;_c?@A zc_%|jX`yX+LprQCUb`b)J{u>{<)bn*eJk>~8MY)#uEYN0Vl(!@I>#k5LS*C8Emi0Y z1mb~vF1$_7f@_F_N2CpnDkz#ND|Zfxwr)clLoJ`?DBM?!K8TMLlNLY&4+pa_r{YT7 z)@_3WjCV{;$T*G1lW-6BQ|EX$&$>5%GZ$ay7k@7~hppK530x$(s~Lv+7D~}nh9LD^ z8@HIQJqnB|68ofvmxFn2iMfj}b2$iu3WNaSP~G-f0wu(U0DeG$zkI)WmnqPjZyTMD z-o>%S%MF&A*f}i_gpl>h-8+b=+{^)qQMDtd`qA#MOqws`BdS|ZgGNEjxU%C(h z(E_fEGN=x_=rE`QGf4a}#owrH)@F;lcy7bEG0%9HSU@~Y??F0sBtU|!)B05B6F4sd z$t~rO6M4?O-mm*iunRlx5e;Kh2Bky>vOh+yZ3dRNCyhOOTaSZE)@OZE`$=_;>tK7D zza?Cnp|^i~xQlyT!j09#`MG;iy01InEHIx7PJhJvywiJd1fnM+xOnAzqVs#GE@I|{ zY`|Y+ow*1r#1{}*dWMJ9D)?E5?1;mAdc>Rf#8>?-ac*;)&I4fn#fAP!y!~9cfRvG&RSlc`j;(R{zTtxN4i!D1fQlrkVmJ~w! zIe-IPBSSLm{+LhF9+!g}s6czWchq;n)FW`#ryaQ`Ke*Sqbb;NSdp*1N4R?q=CoIAZ z$osrg_7U7Wys`bh_dDDdliX(}6R_75NMb3p0^ZZeD}aIw<4fO@)!)Och>wUCEW_^N zERh&K#UnmIgcCRrTtR~e!F5yEjax%*-kRZ%kbz=_3KlF_)OZmF3_+M2L4xEck|UER zQJ#FX@{viFFJZ=%IrAkWn>TUhT%<_RBA-8j0!1|RP*I~tktRJizIu@bHfXjSJU<#iPIq&FhC?%4*7A25J&(45+Ugj(G(X{z$=szfo2(M#1!k zA;!T6i3ui^RJ2eAopQ<8ZK^(`h5lJKwA%qrUsG)AWDW-;rlFQ(tj5gv3 zhBZJEiKLWxbpDAYm0M5uteHsc8rJz(QswtzIs;Wn>`ic{-Hqlb6 zu*NFutXzJXORlooT5FnU%8bh{x}G^^ufF~g3>m@<8WAxyL|}|L$RwkIGRrXQj5F2{ zP1Lj6Qd88?&2oc{(bOWX^tInG?Jc;}hAZwk<(B)?Iq0OL&N}S0B&3l_>QP;kVgi!LcUL-6-Xxi1(#a_CJ&MYGrLxj0thDUPOHRNv z6D?oL{siV1U(lSZErjV(OH8xkoYOC4Fir-UZos=H3R?Ye8|yYR*Er=NhLk&3;N7`tst^4C^6Ux@1W`uc>A{oQg zXxtmk)iK?55r3&ic!`wvNP6oH>fU@QUy5I-4DlCBfWPW;V1mUmxR+l@Ul`%C90tZ0 zh813&^tviO=N!K@25jSwuO){ZBtGb~WRoY0VG)&MF5dX#7qzrz+iq@NlFl?OzUQC* zgBBVD$A~t%)TB|(PE}T+es$_#9JE@M_S%yo3a#(64?nNLPVg1?;ztm`W79)hZ7tXq z=j~_X7Pq-g%Wl-dTS4^J5Wc-=0^bUpjc_CqbRmv{j`-2yVA2T2vBYum;)#+XH@V7D z?s87a99911CC?44U}8c`FM6>$8A@y|s*~Xj%S5r*&2AimImYd-H5N&Zo4c%%W;2!=sI2@ge zB!{aYk5;m-c5N_TIeCQRJ_y2kiExDYqEb={VF*K@5KEW%+$%$;78uTQbumoXTx@tl z*Zo3=dXZf`#&L|m{E%Zo)SVHEctqeSad?F~)Dxi@&6!1NdDEO?q^bxUMPYFgl;8v* zV!*{NUT+533!|x&ma3*bP}vkVSIKTac`PMInfZNEptNmrO}Z689rce$tbm{9xpoM3hqI z>y%0v%9SqR%2y67mSp(_40VaHGLeofr$b%o#Do`Ljw2r65N0xniT*o;2+N;suQh=n`&OuQrVou6|qPOOn_6IUHqc<#F$R+t@CNF%Fa7uh;RaJoaF@L(a_mFrmf11;d?7Q z=bDOnUV#ZwaPRuu=f1uEM?haA#T5Yi3c(H(Lj2RhVH2xR{%A?7v5q|vL=)(cW!PXM z6!=>t$(31jb(FIewjd`zYf_$!wzQ^2N|mCd+E#L|wOSDiHBd|qGvShkYdO=z-h`Lk z_V%d9ct$Y~Opd1Z&bTEDlvFSC8s`o4}0A^1^DJuYxC{v9s7fY{O*^qX!Bz#5c@U(%Z;H0Hp~GN+>m8R(Ft3a&_ZHd2aP0Jly=y|sO(p2Ss7E-mN+Y7sm7(N@unB6B^qqu^qMpsI~ohq z#yDOoIk-LiXJ7S8|`64C<3N2rLT=<%Ogtz6j1n&hmJM9!x88(UY`H;-yECj!wC?vC@RMtgg!Uk)t-5Uw$`NmA6V>Xj)1-P*#@fZRCFU9f*kMw2^emSV=KWB!NIvf z@Zi7!`V#Mscf37I?|R!?a`?`7zKHUqp)CI5O0E32iSr@wYFL9BW=ZkFY5116P~*~e zfjBI#p^bAC)fmFGxHdeNadI?61*`sD$b($+mOb-{C(l&Mx#>KXV{tW@g2W`I8uOXc zyl7PbHkH?!bDi_t*i=)`DwOS>D=@q0`HXfRU$OMB&G8EU!9pr34s{4=TMOK%I^3@A zp%H0F>jc3T*CXq+h!kJ~AgC;aZ^Q|6!Tao&OuO1|!uBP(-NQ{mijxpQ+PKHR?*6_z zCjvh3Oiu&TeRqpZ`ET$r&G3)`7PMjF81ER4!5EMM@(Mz@yrCSJK?OL#PcAR2Ty42D zuQaNOWw1{7V|t!RqJ?M5Zt6?CeCOK^pExqO^q^oZ;&zOWg#A z-OQ!@3T>o1iQd$&?NsXRjKV37!byGt{^*Y-?C1SmiRT+imj zD+Xte27m7M(kutn=bm`5eUPp{fKd3nrml=ou-c5Emaqw-?+HI73ajr#!XYriK^zds zAsAv+wvhW|#0$j*48?E^{>c#i&QK`WZ>8KXDd2D_hJxSZaMCEP4h7EdF74pV!qZj^ z#t6^=c`F{YVGtS60l@(R5wQXl5#*vu2Br!VB9Y`E(E}&(^Fj~=Z)Ws#CKFBY^jxVA zIszkD5EM^f6w|5{t&E*!(4A6oLRL`+UD4U}gKMfS_~b~R((D!)B-^@-f0)n->8AP| z>L9{_7)AjaI>sARL?K{>8Jr;pm=SO+3xdQi8VQXIK?~8U@eQxBl%Aq;UaH^j5C8H3 z)A-I|+(b=UjMLr(;*KE@-4PxO@c|EU8Bo9;k1GcBu@m@l0~e(OT`nLor4mu@61k}a zdt(zhfgx3J1$&MDXd?0jDN^<>^1NatJv0&rb+Fm;iWEv=Yk&_Hv(jtD6?1Zv{8sQ{flPIb2q(UeuM+n41 zEDr7N{wxjC+9F{h#>EuoD*rIzxKbMwu;LmJcj!?pD^QtcAgXSFVA4`8H-jyoh#+r9 z1m%+S5V9^6@-FjIFIlfINm1AUlM13{8ODb&gHFwM?~Qs8em)Xx0OT>rW^9Zho^tU) zQZfsm;rXCXGdI)v3_=`;;RvW;7`}r=yn!@{pa_HkH6N+Z!ewx_AShcCaT?(@-Q~jI zWi~ggHsSuzHcx9`v~k~@k~gE0@G?zxfOFG^leS`X9S@MGkW&HUv17yy93rq7MBq7D zEvmer6F31AC_yzg5E5YxG_32oEHTMct~(EMG&C_h84@GNvy1prBEw6a7D)!xlRVfn zBZKbC;-j7t^XP`pKVAVD9+Oz~lRr_CLI6}ipM^jRlt&L#8J55Zs9+g%>_Mxb2ws2) z0D&8dpeHlH!7OWVyg)-MX+u+@Lp@Z3rcv#VGNnqiQ=g=AN{A`iF#cBSDSNZXuy07kGh}X!LcW;Tm$m0A*DHX_ZHB zHCJ~PP=Y5?F=1zw^bwg;VSqMcb!Xjfwtv@)_1p4J1fN?GledR*OiP{OFgjyYG8RM z@>*F%3&u2hyR~YoEk4($=s+P9wg!&U%xm!pe8qQs9ps(tG#SbPeHRJ}*B4)jAsLim z8K6NLQo#s@AdZKC2<%r4nt>ejcW)^oVf%Ms0~dfFc7PApfCm|YXJUa#w1Lx*rKZFv zGS*`+S1WdQlD#D0D)@rYq%H&xRXYvv7UpzwVJl;n)Rxm$m1>29p@kQbcDEpgZ&xjC zI5v1!5{DLNcsNof5r}ysJdJnt#wi1i_-UCYOud1L*Yk;~Hx>S>Ek43*KD73kzvc(u zbQJDm6#mLry4Z^&WNq389eQzIrLaK7!84a&3AFVJte^bs`3+K=(y)i*!O*D@iy< zk?P{^Ooh`y4T{KxUpR?c`6_}HmZhjko9LFMbP|%)ExE}qd)X6XB$%0Zm}SqHQ-K+j z0hw)2na9SB)Tf!pm3^i_3ce->R{4&f=zk#$gx+R$%3K ztK(K~k$?*DH=h3{VIP%Y0a=hImVgVHCeRNjHkDH&cm67jB6E%+bL<*|xl#V$kM2?k zb5pDuR4jy3wHI>1mKbK?%);T$WP>5D0Vj?gFxtj~Yooto9kPG}WOsIBpp|#Fq?dGg z+7ftejc9pehh6%mc^S%Rjsj>piJRAqnAi$(S{YJddTmb?q>UBd%bD-vnfJqul6n=; z3+WIa#3p8M}7cmCb049EU$uz9J#3(v7(LBUJ-x8~6hebgH;JHny!$1427G5o4D95qH;r9FIi zcjI?O9E&6fD~$KVQ+&mPt;N6N#cNx}yLA|7e7A4B6}48!>F5WO`Ulk&kAytAwHXOh zQWcO~x<91J#o@`pVH}j<2#jE0<(Rwv<(QoV6$v6>0?c~6$s2IZJIoVz%!j1R&HT)f z689T^VY92{FqEN18|ZehS{DTZe2EMiEf#AMH(;m=7}8ji~1O!;oUBONmP z<9sZt7Cl&LxQQOUs&ZJ;Cta2+J;X2lN+$s;*1(s!lmc)dI%;~@T$~DE+_vXxJ*ei? zZJe2NoLtA|3SRvRNC83a3y+3;Y;B!FxJ}7*-9vcY*Tn%EZaWpSpbD&j3WVJVx;l;p z)-;Pi0=m%Izx=$VeUK~G+8F`ctx?LSBFMb2d8F8 zbwC-i(IFn_JuvLuu`as`G&}y${T(eKJ$DB_;GJ|558kvvT<;g2;jzLCAijD)apFsb z#ci5ZL|v!NhvQAX#&Iy!-D}m!)mKRV7=BQn?rWY>f!6t}<@u)@U_QFBZszT#*TEq) zZL%4bK^c@GLf+L1V4n)IKp1kMkHtG-k3Q)uwVw}}>6_l^<$95!M84VX2s!^jzfdVBZN)%92 zrhsyIk(5JH4Y8~`Hva00Mp9U=U}2cb*g;|}Te3W;DwP&3+{%p`XY;1cojb+({0S6j zxS>Re{v79xTsdyan2{srjT<*<%$6;ynq$I*3|uLArBESkSPL*skRxtx&GtEdv zqI6kMVMQv;C?k+61sQ~pLJVO6$`uh!MA1VJjdamMqkOcANE(fD5=tr!^O8%%93<0B z(%4i^PCQL{Q&3j=R8%-W4fT^Z-H@{k6${GTE-}4g)n*=XkW7C z5v!j5g3bo$UU|m~2z`i?Z@LO=@H5JL;4a?naH6^zhKT0kQul@4bKF~mY?*=0G)7(s*<-9$yA zh}<}XL;-AC=+&EWnuS)KD$Av3%Xsp+S0jJU9O$5g2Bv7vhe4{Cqda4#*=D6(ifN{& z?S?ecr{;oN(!1&AbT7ytGu$=MJ%h|K#~AYru3qEHZc;~B*Q<8jc^88q;?>72degp5 zpR#PXjb66bMoS*t2ky5w-_K&3?Y7;5`>lmI08*|n#aM+dGaj$ot~Xg&k>ZL4xk&ym zLJ2YCkQ5PFl;gf5{rDp+3vsjxM+}WZaKTHyv{J%aWWlh*wvQ8W?p$KIyHC?GbHocp zq$1s!B~~GF1|~007Rr}IvU2olxlFxXedZVIxy{Xi zc2kgiqy<4}`9~6eqn6zS$B73KjzSrjKn@VMA;mEcM2=HeiMny3%E=3JFdB%DMuD#= zfNn=R;uq;wVGJLQ1PeuKUF?wLx?wblb`87TPIQ;MEoDg@(}4yRUho1V1pdQ0?1~)n zB%r+Iy{UOO;0*Mnx3cP~aV{~_-u495J)C_{QQ?Ek_{cXYp`}lKMbnhidIPG}bgF4l z%gt`+H?`qt;~e?hpE02EzyAra2pkB3CCkR0U&TOymZ$^+9r!>98c2e}0%Zjgbiur3 z&~F>;;CxOA32ueZTO*862~U`~#eFDUB$A4kVyGfw3}jyQx?xEoA_a`_h+nAtQ4kxV zk&;{iB?wC*!j{Oea)`r;Jb_}wcsE6HkfR$~C;}Fb5R7(pQ954?qZm(C#^<3ijccr4 z8{Nnk?#1yhbEIP(4?&qa7A9z%@eCi;r>Ui-<~F_|WYT z7y1&0@LGm2he@wj*l=|=Lgq%6S&EU!2#BX!9TBNnO(elii8H~1GNObNZg%r1x|3o! z#o3Ki7-0xQI07-GL!xiA^POTLqZ#L^vU=K6FWJMVUi7)oeje(d0KFp~HN%;K&uirqAlqi^rl*WVPIp>M6Z$lTx@4hTu^pIawCEX~V zxjG*y#E1xcUBQ~<)hL10c0d`2Pt1E1o%9Zz;rQR~)=`dB6oIWvP{vc*^^GhP0E~2n z=Uwp{7rhGDuZ&QHA_{EaeezSVJFBB%8}%qe9C$O2T`glB>)3=!cEg4&WJK#nQEh1B zqMn7uP&O(+B9!(zBF)a(Vju(6da|{%W!7zFsvz3h?ZyVG7H@0Y+uznwLLm@txr$NT zbRqYbMHOa?nwttA9g_)6@SNwcz)X@9i6O>##p=Gi-K-*U%;5d%c*Uxd^KLWER}u%r z`bV*(FoOzg9YPhFK@Mkd;~Q$&uRGt_JQ@5Kz;^x;aP_W-PXim65ei-egBL|u@h#>t zl}QvqF^uU9N0!5qmEUDG+bUc=1EVEAaU@f`;*hpDlQe*_jA>kBV)3|dKt|<_U5Vpb zKg+kA(CLsPROBKQh17elL&zzANnf*BlX2tR5ul1Wq^GmY^}9IcR?Zl7*)lgLE2Du2ho=x62KKj|a| z2P>wKi@O8kLyQO3vBuA9b&YEt?-p=4cm7r-F2RW#0GDtfpn$N6TkPW=o5?9s1~HWV zkWBaPAI8t|Er;2hDLmW0~vz4@j@{Km^OB34_xJ{`9w%{{8JQjbK<|34ZUt|Eo|Pg;#Ki2Lm#&8E4dZEc19S zgJ^&vc{bBM5NCOrM{$#Z8K0MFpQnKu$37m{aZ1A}rbj=nqIz2cTBo)#-e3bCAOf;C zdoOkajDUN&r+ewad$r|za07flCVT)=37Ehc#`kOCk^#!ce2t@Y&qq{Npan)%ewov3 z*GDASMs4CJep3j3uV95^Hwp^lh2<9$sUQ<$s0{Nb74r9fT%?9;$cAh<6wqJ=dshT= zIEO@FhjUnThQ~aLcYq0)a0>`|ZuEcfrx>5h*(&WaeUIV$i0Colsc@GUPF12H&*4OD~05>vI7gKx8gxYdIQB3nRs z2}1a56;OoB$014gI4=}KP55+TSbpE71rk=WW^+ep`4FVrYJYx-l7fMb1v88$C<8T#x-kvFz-bRDf~c2KocIjQVRyd74WdYjD`tuyfQqVU zVXWASZZm5Y6pPa`i?mjY{c($&@Cdp%WV~pEzu0_9SR#^xBE%RIlY<5KrV3bK1)CTX z3SmrP_l(PU64Cx>g(qQ+S*0XXmkQ9pjZL(TX@*|j6fvRYKLS)H+|UfnfMkeM1tCxX zV9{TJ)`#|ZkN6mfeKCj$MtS{2SQEF10fmU;Q$7^-c?ek;im?cr_B6c^4ZZL_4|$0Z z2@Mm899?6E(%=oupaLPVkuB$uwAX50@Cc(p36vm`BN<91nUcq1i?Wz=D!Gy~)sl<= zlOtpST%ml#R+GW#OE}4d%cTl&xCBd(B23^`$q*CPg_K9BZAobiTWAuaKtocOIWLiR zGocJOK{0NIC08Y=pjeyPnIoBlJGM$J`Fo|=9;dlMskxejgLoNG1;a*@NGO|;g9SP1 zbS`oQMzD8OkP2~#QDSH>%ZPp7=50iT9LC9%%msx@(wydIURdc(8$}da`H9`|i61f| zdKRAI36ExJo_vU&>A9YIqG)vSSMCW%aU`FI2$1-BpB9%HdEsacRtSVZr-X0_r!h6D zLJF$@928}smBloZm7u_(KgS?)4ccW7N`JUQ4#jW+qgaX{(2*C4p)s%p8_Jn2<(VHE z{+h;eq9bZkJb0Sz@q?V8n#V^Wt;wP;syM*7WZp2NOjx7CfCWZit4ELuS+E2>8f8EF zWnMR=Lt3OuIgJ8=elSunX_o~|iaSo~tWheZqSYMYzzy$G1y!)D{+1y=g#;nM74mqV z2$-IZz?O-S2zm0B>H3~=X()$SSa!sw0fia;`L2>tr*wL!`H(fRFdQ5K3rsKy+Hehg z8lb9Su(wbuxk0F^cc`%vE6Bh_U?UZciUN`usk4_!-*Ks$nW>&hlAuYVo+_fCYC$|V zs-y~ZhmfM+(wZUAqOb|0OV$llK%>Gm3|U|VMPRF1kP1|g49Z}PzB(ho8mvVAY7E;a ze%=O#1i@^sqpVnv4AA+kQyZIgkTIa0H~q6?!;EipL0Fs+Q$?uIQSs zW(uF)<4+T~rtxZ*19_KpI;Zzqr-gv0&!7$Ca1BY|1kWG^ORx&p@Ctm&G_PT>q{2|B z0t-{4H4S?l588$`Aq*|JkrSQ=;heVJC`lk%gP<}odoYG_xMLR3fNJPqpR7pwwC?-dM z12`~;#f$?Yz$V;l#oqh9hPK7gJid+afYe;RipH)D25AG?7>V&1bvefPiN>TY73?D^`Ayqb-l*sqtvFs7S7W~Ku zBFPIKqLSblgOJI;mH{W=$uKLsLIqcoQw3T;%35H`RFKNhkXAE16E=LqIP7gY?2O)) zj6K}Lx4cEV%*$=oO;Q_o(pqM8)j^Ur-{Ey>2&hmQuT1TUZl%RtOVO~r45)E1!BOzqTYyTws0)#@67{$$mMU9)!)q3 zj^fqZt;VA;1VC^EKJWuWumWiv1Wv%e2|K9%zyWf33=4bw4EFpAc74~*k=HE9*O(;G zq;Vb(Y;%e&s-;_^h)s(C4&VyHYk}a{Bjl}+9m3~w(a%Q?CTs;8-O(5$%ppxq=~u(S zO4>v;49Xz9S~sM~X%MSDbu+PkZC91&g@M5GKW} zy-01`%A-7F5d(*C2~9n&VG7mO{M`E(-BrEK)qUOAo!w@<-Cung@y!H3V5>mT11-=4 zKfuf;2P{8`3H>eD|83v{?#KkbH+`Pir8@}; zzTiZc0V)6pzNk15u4EEU;g^jNN`(F~E!^4A;0;>9;Zo?~V^_2xKH{EEb|>D$!_*|} z=WY{2+j1AN^H<}q&JDq^Z%dGNzg-1@Fx)}Ti$hN2hX(^k?#yRPJa>qcZ3|iMBriM}x=W`BH zzJfMuU*pS1?+i`olmK)J&cU^0NfV&xj4r~vWRvYO;Z;C69Q|~Z z&eKD1>1m}zG;GqsTeMg(49O7cC~e}a&B`;O3eweBG)&|r=jQv@lZ z+ed%|*&DWhw!KCE>qw5p)&5iLKQF$>4&8+)-Ba%DS`E(PEbU7lzmXN7ypbxYvkIdE z3kO@6s`Fj zL|0@5e}w2VyX4Re41d`V@6izt($Uuq)@Y2J-su^y3dqp$B@T5j0u#!*tVFahQ)-bY zpYof}`SaHe#K4Y4PzACsJp0DAK`!JsFMvkQ^UIwl=L+;dFZ6Gz>^n2vRDRviuJlZw z=Bmp(B=tn5K{!8cglw$?v_H>}l!V>>Pa-SEX!|e-O($yi@=X zM~*O2B@J9RX$ciBWZ2N*LWJEUF672dS~q1!h!C+7tjG%@MTq3caU#RX3Mng`JV_x# z1q(1@9J=Jl=1rVAb?&s;$mdU>jS>wdTGZ&#M2eIyW!luKqD4^`AwndT>Q$_V4pG&r z)es^>VZ~}KRMza-vV_#GW!u*6+qH%A<;zu<7Oh*iVAaigYu2p3f$QdiRd{gS!my4Z zLw3xVu~)C4X=_-q+z|~SG=ty_V#a6CG-SkBW6AVs)Tu2=V$HhJYuK@2x27$-lIz&C zX;0$a+jsB&-Mn>65Z+-*(o!p~y8242u?|6NOt{K4 zvn{#u(Pd3Gb?K#-!2*->mc!~qtgyuzgDkSi9O9;&A~M^ov(GsDOf=FkQEj#5Y;)8& z+i?Dy&A8txEe^U%Tg$XLPCNBbpy83h`6(kVwAX@aq0i6vA~ zh8P&jZ4sPq*9eTwoZ zp^T#PVWq6hvMMdN+zOGd4*3#HL&zHQtTRI{GtD;Hd@~n1^TI1HJ6pa}F~%H=OtOdm z3{Q>dfvG}P!Yxde?;P0bEfRsLCR z_0{vzqqSCUZq3y`Re1HazhDC-)>vheRTc|ooqaYzX{QYkLw3*!2OfBmq2h=oj!*`i zRJu86adaDZ{Bg(~H=F6F3158VS*VC?DS?`uba!2Ek3{^ zMlgbrwvDVU6>Sp`fed7~3C)de5R$^PD3l;A1Smrq!j?3+0S|U)VF^Xh{?Fk?(T#AR zOL387VGAd>B5z-omu!dnk1a4#r z^%(|$?lT|*9ms;vYEUYWfrSd*4R5I!1~D{L5Ol~$8mYLK6^@XFVORl$Dr{jdA@{RyJC1$gU6r)}Km%8K?D&|g1yW?FJ zMYcs;+#+~x>V@$HgEE%6jCpu!4D_N`nR4KOjq`hB9Ou}nJnB(50Zkeo`KTK}A_o!+ zWhg@h`A|Y~B7Rv*Wc_mBwMT}n8IlCx<=`_wR~j%FOGw}nlCdEGsiIhLgJ1-em5OAb za+DXu!YGVsp8*An7+Ann7R05D8d(7xDfDGg8}~vOO2Z07C{a_7Km{b^>jV=J-M=W1 z5@?#PNz;Q#9f%kSx$4FDNTpzB0G(@GBL)pW%E3S z8SR-yKam3m7rC~^LNQ_#m6w6c?^ghahS(fOh3 zqF!?qBsp5ca(vVZmJ}%!5_p7@ssca+Do}!EIyd{Yl$6TKU@8WOg)C^~sZY)25r#1i zq8hcANTpoam}&$bC2%7-PytnY=))jobzocNYEGDR6R<+btmB0WTGRT(t7PnAU21Ds z-fEV(o^vjI>5CWJnX*}|EKXuv9x~`z&zTt`MeZ3a&iIK~#d38!go;y? zea_PM=wS0Lv}uv>ENHbRTGE!bf9&a~H&R>40=D+G$q0rPW?QUdDM&)8r0FVUYC#G5 z)Lu3!91(g2U8ZJ%8{tTdj3@1t6rBSSQXeHYQnni9;^{sUY&Te znG~~{_qr zw!_E*G#Xf2881i*G@hG=~xxP4G9Tf@M|JuK^o#PvM-UG|zxLGO@BFu$*P^a`{PK9_vt=;_Wa?rMzRVn0n0|PO+r95NkfuA?6#Wy1)s}$D4Cr zPj=@#>G>6WhKRc^2WZ1;XV5ViL}H7+QJZhG-V@ASqx9Y(q2GhrcbizPPgjQ zBmN(1n?Y(^r5M$(RYo$b;A#ijcE+?;)+kgl3L4LpLRavq-mqW29C*^6%XrkUMlayMP< z1SdJi88G#(vz_b2ynH_|z|zo$Hd@3D%)Ov`2c`j`4SjHg6HVclm8^s#>~N_`8p9K> z`21Khfg)s_Jf;n`i9cRykq0ByM1XBFn1FH^t6bJDtv)`$c(j0EF=w8Z|~zs%CG{|i89f+o&j2@ODkWnh)k(kKs_qy=oiDwvN4Tq;#l z24$dv35+qBn;R{#meZ5D4pgZN(Hm8GJ!2ZO73ntyHv2L`NykSj!-<2*!Mf<=rQ9OErx(7+8$mP(u! zy`e2FkcM!W2>umeE`_Lw6!9|v62&5*f(+urDv&{Fb2=MD5*&=hp6Ifo7%N=-!P((O zwF1U;%f4Y`i?XY)I>|3(oHM-Qufc$sWB4!hdxd1sh7w$na_|Ne@WR6yxPm*hIs!-X z5ywH&jdC={$vTd8{L0EgD0gfPNMJ)skSH0LM|wo0d(41*v_Aj=YFXM2NY1T zDSJhg93r6jDzDPPpPp7cr`*Qui9bxB%I~qtJHkrVFg!F=4szVe;h4_o1j}_SAN-3>vqVd^ zq{q&{M?Dk|V(7yJ{GYijx>@^+{2^_$I9H;?S{FjsPOitjc&G&y})Ixj~lvbjbbw zbUle|(_+a2;8KPGHBdTDk@oP2kSLJ_B?1S9P|l>m3AMq3vrwOiY8*}gN{439M&!&MFx=4}O%5M@PUw_Q zA*GG2WWyr8&Lc%qKk80Nh=he|(kHb?JEWf}ty1*F(vDlex+Fv|1=F1S5Bhw)7>l(t z#ZUd*PYUVJHk}sOLl8NwQ*W)AZpe`Z-O~oefz6!3A$i3?ou&&7%|mS%qFB_d`%s#E z)SDDW5-ri$%+yN#J{1iNys#6)XpH}YjK_EdWIz`hwVZTV22%|%BOW6)Iu0nSXea#2KNS@>QzP^CMbfSeW64>%14By8)oRo_NXBfgISsN(kuv5 zVn{?pypRHcQ)%7VXlY2Fz1D0sOrbp-#{5>ubQgcbNTY2JZeWIr`2IcM3kEe%gDS9r zDQh~G&@MsX1Zg57sY@8FU7fA<&_)%)uf;xm&DUbg3tgxSUBHFA2%ex+nYL9Pdg`l# zeFeG=)#1e3VtCk0i$6oVO5H$>!ZnVKRVc8m-*-G*hq|yy_{!F3Tp9qa0OXDg5P{0o zIIszuggDlhjoH--sQ?Mx%QObhE8P|oS_nEl+ggQ&v;v=H)7O2|dx3_Fq|=BfmyWau zjEs>OdKb?9hGobBsv^E20V0{;15StpY;&CqMWX0sqUq~etf<~XuwIb)9kC^Zvg=;$ z?cVJTqq_*-w%aSkaGpIu-^N*3ycLEn+*^SI!$Lz1=A>W!{?*u7rPawg(lgFZ{v}-C z088G0pFn)FVKBMU9fsFK-P9$$D@aoiwpKPh zpllVvRuczznbU@th)%pagn&K!P=RI^Wj4c;wiZj z+9BerFk&QzFC`Y+CPtZDC>bf9Vs$EJ`x?KuokBgCne?s3b-~+U*e8A(;4m~};|SdP zP2+Mj()`swTuqKQMqIIUM^4kh9HmKqT}y@r+UsN!1P5r)krL4YZ%~H*DuAj%R0BXz0~}ao>}us4-r*(b zs@BoABx(d&ehOQz7+mhfuRY?AL6ar^Wjfj3HL2A6!V6+9<|=+W|8kx)(&ERloOMux z_nV&qh5;EU1aB;3Iyz%*UPEpMU~m3da1Q6BAxkR|?2dL;;66r=c-~yp z666TB=iq9p53vH9^*{}7WVw0dYF*R9q-4Fhk9)}iWe|s-J2G_vx)%WlX;^|$3^sDe z2Ar8 z>a6NXfS*B_Hj@p&JM0b>Se1A7V+Wq+2CQc<)dGAD27M-FwuVziu0REWTq>N)0&<|saphAS@Y9oQY=Kz=$fhQbrfkvtiIBeRT>Q`vtzHq` zWi?wjUlwgBtdrCZqrT9u!@x7wW{hO`@DD$RWH5*nA~GVooOVzH+=f^|+ijUB1mBhf zq)uw+9Bv}rUp7w1aE5C9ed7sJJm-dPt*$s!sqRkeL+qC4auDP}JT(LI?#l#k{(y$@ z&Vp#Ytq1|=^rn`&wrls^7WtNM`u^?)`{syvp@RKxP~wd;Vh2w*2$%4<`171>;?*|W3lG=~XKfBQqfzLo*JGtLQk-$vmU1YEbZCbxSaBBb z3>TMyLx}Mi7w$CPaZx|q!`r{6CS1dHD64+%tUlQRoB$)=M|l22ErpkKAZuKjSt#E* zRWP|=sq!lq=q!)x{Z#L|R=s;khH^lzFfX+Dr^j48G;u0HDKGhC7^;y9$i_f zf)&rSP4Dz?{M)Em>QP^K{)VrtZ>DPGCTE9Rbs=~4t?`alk@Y06b#b_L2+o24IUq&G z=U$)kDlhL_JMRx}T??UWOd?@oSoXbccH3K#i==`fpaS029Gt%;;XQ&Jxae)4vH(}! zj_!$;tR`zJ_XW@MTuy0qk9sxpoiv$~cR$;jmJ@-k_hS&27f1oHPk|B``?2=`vmg5u zZ~>6OfiKSV?BMN1K=^?Gt3Xu0635|gc)Q%-%cEdG>UI^>As#2!1yCyV6dk3 z5+etXmyeLQk6k~4k`I=`Bw>_Kc?yBV*6l#L{!bAk2613^nm4L*0S7C{dH1OHAwU8e zYK9oRXmsWFH$QsrQhIoe{#T}7ip-u~BbNH8kFTnCcT7FoIiXYz|BqC70T)Pq7XW+Z zcL5YA0Tg(C5fA|sH~}0bc%qK`+E5NxeW<#>d+;aqhnIMDEN;JVZcbqJ=>D+5w*m4n zPs7h##IGU6Ck6<{g2rz#lGo=>ntV%^Ab`MvbY zFk-CXbjcCZrcN6*h3cpg)v8vnHcF&*tD>%6zk&@b*6X6OW)Y!Ht9C70LvG)~g(;JW%Ybp?Q6)*XCDp#I`*x+=yMF@@ZaWh3;>IB%Pp*79^XAT< zLyw+Z67%5Fbwjs32@|J4FkpbNAl|}j5UMBOC{|3EFk;G{ zw_pea+Bx#4VE+aSfCb5Ah;kjOynAo7FvvwwXYd6WZ^Jc6l-il5Q!+!vZ`$KEQ``kds4OCfTP6_ zL{Nc67Ftj-L=nY+yOX#-4yA#R=N8HCky5o=RlHBq+xV3B;;WXvX!TpUT!sMrFCm2t z9A?1~KdcKcqbqE1nWj&CjAh6uLq!U6ejK~(AN%>|$Rx)=s3p1e*1PY%+x#1Hn9^Ll z@s-{-eDKR#irgfTT*3(&Jcs9Vc^O=ggV4md7audt*k>PpF}d2`f6O**Ak;@l{>_9c zx?Y`iuNOL$;V8qT!lBp?r4met6P+z>AQ@mt+Lj_0g+p$0o0}1eK!hR)qi?Ah3b}&h zlOdr?U5As(R1ycT#mUQYj3hY3vZ4mXP04RM%f#hEqlcv{4w%BmwgOXN&? z;OW_?T+kKJVDwDm2hLGM!**0i>@`W*y+`rF@!`u9Jx zkth`cB;WuINUfBNLls1r!XakBEx(zOkoQ{(=Bjln1)5HhpP+X5Mg)3+0||}x1&K5x9~(n^=463 zv|<;N2eT|Lr)68*CL~to#oa&xjNF-CYsN#Ko|z{DXj}$+*62O(g>QT@DaRZEgg*bN zk9|kbLNct73Tg0Be!u!vLo$Sr{1FmZScu3WcLkAdaDql9sh3m|zlP7#k+WF}~y> zFef(5WEkOf$24LxMK&^K#=x0SjAk^Y83}6MOnKZyPI0!nRd4=Y>Wh(33OTE1PV)Fn zojG7eJG*zPHpUZ<-k>KP@9Dp%=`$7m{AWP@co432EszK$j3Ov?sj|aCYLxiY7)14%RYln}ZFW`Qcs0A) zeGXWagIzF^m1k!?YfrMr*7gwXoo-|m9O-ILS>1JgOVF!70cw#x4ivD#5{MuZTG)n4 zB(c(nC}RugSjZw+B;!cN5-Piy2fsiIF)2n7Mp}VRQvLv?Ln5tdSL)JI!j!eHooSU^ zIosLRmUEj^@oo1qCK~pV7ro#`E_@4I$mr0hmVw4Fh>%?6?#Tu?P(jFUHv<|#SE{|^ zja8R-U2twU%2Mv`bc8pFNrcx^;~h^=Ca~6aruVJz6dxR2RnIyO=&n>?-+cFKU;O5G zAobHw{=z1<{~lICS{QJFKSEK9LUts928S{t(km-Kt$`r*28B~Iflg+?q!~_W;a>V- z4}K7YBYtgcWqV?r*0#lBnxPJB$O|0$_A!om3@&tx3~8uh2tNk0kcsTv^3VXu(VePq zrr4<9J=vO49(H!GC>$(bVtQKMvIWXpg!8J0{*7aPV}AB?W@h0t7^ztE5!!48EpTH* ze*LSQ1IypR?yo}qE1R}2q6KJ-qt6C5AfO+6jcZu+8LxOoyv5NCW+;Og!B~bjoKcJ_ z;2;B!#;|BHSn1Rv&V#H)<)-J=%1)o~({V|$mqcw#Qm5&ssQxWsXfX@MFviu=(1HtQ ztz03WpvZwbvaYva30^aKH&?#$aDr{KDr<_^LZ_LrgM(}@h?m*voWK#FT@3h$dA)G7 zc3lnWzf>@R&HTIqxWzrcgWOpl6|sVyjiqh?Ya1i)CUBn*4B2x$gB7!=MJ`Ao1!Rn4 z9O4MLGL-Rj`HTt)3Wv0$HB7irLL9{YA&zOq!Lo?QVO--I=lBaf&X-%Ri7<+}MXEV( z^3D%eGOLru6;j~xrNTS|w|318j+|sp;M_L8zRja*|0v-MJ@>}DVmE(t<FWt*&iEcYEuj=!UrI7xn<|rZ1A!U?99P(D1BLRJmYltEg zr6>g`KmiMFi~}53=r$FoP=)(fK@CXIz`}`g;Yd%K!y%sHVcNt|T;q`iO_`A7@eAeY zl;!QkEdf`m=@KsZmT%1h27;ceU5x2z43OCg4xE7MDbvj98W(gz?4^v+ah2`a9+c@` z3=$plC=Yi0-XrM17%3g`8K3@o#Z|Sf!k18PA z5>NpW3Iwb%Awh-R6)1}oL}7=hTlZNZMi7HIh@ZSUmPl-2G>F0%hM_2c!WA3?I9P$+ zSkfg~5)QQCP?!`s8vb5X&Eba;;NneO9ws1_m>3HEp}z#8UJPOc79wQuQptf{BBqY& z2oVaX00@4JC0^plXyO`l0ww&;bYYisgyJZc;zVW&4fdYSVc9As-FUqs_8O%rY(K`6@=93vntlrm08Gd^S6MWZ%cLn(Md zD1c%5wI3OF;{!d=H?CnDUKj-gMWmgh99}{qcvu16p*wa3Y+VUl_~ATaPCd@W=G-Gb z4o1lJlEE|v1~OtJI!2qUjxr?G2ZErG^~5V;Vrv}PQ3%39HstTnp3hlTS5f3)Mhfo< z4P+&;X!R{)D7mP#j)D zBxpreCe9t=Ap-6pmh_=m(qlcIB_XC|Ver=GHR2+|Wn2;$5Gez4*(I&vKu^eACe8o| z_$6REZhdG%+4L#?;V0;niWT$(PR#dv|T29m1Hn%CNXwqFkpcZG66zl zffbadMSPuqsU~as*K5Wmh0Nwp7Faai<|~i_DIg^%ltOUEAKt9lH%?LxwCBQAfO6Jo zIgZx;B~$`dN+&yt7+1n0zQjdX{vlb?MR%g5A*P9B+`=Me;4HX;lfI?Ih#oTVn5?bm z%e5y7t{w=q9t|ADAaug)_1t|{8GaJaeyXYQ?3{mg6VL4&Cj{t55@-f+fPsc2g2t1A zW+sFF2Qdm9K4k%DVnHxeC^xKRDvSb!l-r$cXlqsoZ06ZT6sm~&nMsh}v;>+^KEpHA zf-8W+6ue)&bptfOM=_{@8e{?f9Vd<2=#4ffI${DTS(>{j&W{3VkP0bSY9}BDB9bPl zBCcAa}OcI2G&lWegO;n08TfWYwB_t9Hd{A+Y}9 zfVyH&aDW<6rk;uqpYDgB{zobhLz<~V7Er+wFu@fVsudt=Ma&PRs^%)RrfW)S6;i4y zSn9luM5b!$V*#2_0xU9I6swQ}Ipl{kSgbQt!3iv!NlhgM#Ht+5>XCpLb;b)~C39_0TvPS6zo?Mk$sTO!3K?d2hzNfXyrwklICrrY&ax3nAYtn)% zCk#RkqLtDKMF^+?sR)C*B2BfWgS&p)Ff`4)*6Y0*h`tg7hU%9S4#X+|Y=>fj7EmEY zNXSCbW@C|r!m3EaB3Q#dgE2@0G#G<>D1&7>L%}%%H++K{r~o;>s#M0R7ydi~CWvfm zQK!i|UWx6h%C0Q1ejF|}4Cpn+TN*1PI_WPJR~76m&*CK}R;!oxrO*;BmEA_srm51( z?iOL$AjAfOcPIf5UPAY|HL9`Ii zNzm;{*lqS+6mEiM{UJdWWPz%x0U!uN#%}DZl9ai?0OLBY$lBpnn(X79EXsamullO5 z<`m{;F2f|^V}!2BJ?VH-DKY@06-;8ZR-(@W3bpcOuEBr|ECMIA=>$`-CRp$VV{is% z@a*ay1_K8LgD~ti&u(1s@7XR4;BM|_zy|;VskoJVG(&<;T{-~o{(lJXtl&-YhAlTF zuhw+vzXt4UGE_wX5Tz;>BUx|TXm7;A4LGEN5in(D6=x2tA#zIEk0`GCvTu(z2?%9c z2(?z@U8j}Uua;1r{`Q3gE(Vms?90|HEz~T`u3DP_r0WbM&xT-;F|g|9TJ9u*CS*b; z=)oWlvLF-kAtQ1kBXS@!aw9wPBSZ2dM@l9zhbC;724gZHGjb+#awli93EyrCTYylA zKnu51@3Lz-Xss4BO)7j16X>uOAaDHiioY^%+IA?}92TW!K{p^Vrbc6AsRTGs7E?M9 z8sY#B$fy-_EKp>zQLJy{*6J6NtmJkDS||eL5Gm!9u^Dsz92$G4A+~YJEn<_#@vufl zFGvPJE&(M%lQ!6@J|VcAXFQUot^ zgZRzu^0`TFb#&vvpgubsWHTT*LKR z%k^Bt!ClvNUh8#Q^EDp8L0_k}KMS@!8?;~>^kM!dazZ<@9wf9TFs%%n)sLLj1*kwp zm&!%2t6Y)8M#Bo%buCEW>)1(yGLST+4n)>CuS$~*fKh5pibLDXbToRyFdzsKJQ#5j zK{6+^2uQ#ij!XJNGy5_EQ5UsVbg`5mwM?CmQ$O`n+ZK|t7$MwJFHkqbP`58&cUK<< zljZ_-cQ;qB^JNT?>54U%UZU;DvnF;yTHir?yZ3v)w_D5ieA9P*({&uQHGao6UdO>* z`}bP=H5|x6fCG41!vS0aHXhV-U@v%r1NK@Ec7#i~J_B?loUl0+#g=7&3aCeWyzuv6 zG@oj;yK-GF3P}D?L1=H=XvYtQnD$AJLjGzK7KpGmM#Nr2jCsRz>!~*$ zz;~i6`l2)X9oYAyL%Li0_oUzT8&vv%%e8>Z0j9I{T}S$-gZiU8xP#YoCh$QFL^iW! zzzKACo?`aZR+}n!wicMUAf@;fR0I{Ucxwl-K*V@XdI*h|4T)RswLAjgzxubi!gZa9%d%LfD zxz~XmV7a`v`={se~B}hqz{w14ozmN29oZg!B{_Fq#?|(ibJjwwCEmyM$ z7Mw*3VZmFu7$So<%!P^?6Dd}-NYRFk3?Vpn><9uR2pTbBxWRJ=WlEJRS)!El66VW0 zFk8Bec@t+&nlAp~xN-C5&Ci~0;Pi~66An-~J5%bESrckhsZ&$pv9l%y3=kH$cJ*o@ z!vqz@gq1C(Y}qnq%$iB##;sd7Z*%9CBd3Lm6)RV?1Pi80RWL19m@Ki9DisxG#8PP) zg$2tM$&)EpmJIod6w8t|SH_a+FJRHCv}DP0McKG;tHHH){Tep7aNxv|3zns?8r{0H zR54uzcyJ`chgU&zgvbjb#k@KD=B!464AU!Aub#oW1sE`H*vS(IPaN^%$(J{O9({WC z>w&iS`9A1-pY7?_w|^ghe*OFT)6bXh7F=#Yh?W3x!9}2fW^rX80%=jGA%|3n=%NZO z%4j2xH2w@}q!`{fiK?2MVk)W;O{D3iIN}THjiFj}<0uqgN(v|vXF5?O8$;X?DmkoL zV>=iu*y<~;rkkJ!vdltDEw9DhoQ|-9>Fvb+u8ig3_K!;e6iVFnsH{4mFxrqYSW8|AR+M5I^*Dh?OHjY(TM z{>CG*+ijbAD%*Bj`Z35Ny*e_u z226eul%W)*C?(jyP?quxcr#JnP>D)1z7m#svldG7u!Rwl@GB<7LFAa!Ld!*oE>ud) zUT8=#E`9DzM^J{C7FN23ITMJ{6yjgrM6lQeGH|E@-_n}aDx~?O3~(%pddOHu`xNCqcg%+_@`#Z7 zwa-C+1Z05(8KGw!(tj#Q7&7PG^X(xGFR8Ni)sYwN8E1BBB3~tLRwv1Pibg86bD6dKT zdLc09B12+w4r9l>p)#3Cy3(16GeFFYS}m5P(WD}Fs>r4`Kh{lhG?91&B&P`8R!&I~ z0i8Gy>QScgm-05{6fjtWo(8TtrEK#+kUiG(D1&;uMSIR=~F6u3A2ZE%TOT;ggH3}5JJ zDuZfCp|t!Ym$>C~4*HAq4N{z+H7_&6LagDh7~i@sdguNam? zbJO%oFl?C2WojW6%|xpYqq(}t^aV~BGtCuJ!J677afx^R<_Gk;SKqzt1IHVJU~!NG z5ww#!(EAw-6dOjTWERA+JOg|LCm(Q$M|)1?ZBtp?Vi%Xv4Oq*XLnj*1ThZ@9s3oLB zs6Zjt{;vi$z(H&`1d{0tX*)64!W`x>R5BPB%ACAu7^obEmR4B|Ro3#Bw~XB}FxRop zeez4G3}rK)8O>r)DNlV`lswpB3qcAAk+@==u>4(7lIjzEPXoVo}WF2Drf z`8@4(n4SyF?2BpbV!WAeJgkhMQhANZqW&7#zD4dAh+XVrC-)1~1;ek;AY=GRbhM+YrJQDSXBo>b*q;p}~ZhO0X z%4I%xlsC!KROTTOamM9%$!nbnN+v(DDm5p7AumDAUTCc?8fC#j(3^kahH!4_lt2I@DS5HsZwKiq1 ziQa2ZZW5C}9cZraHgsSgo4RDEZgl={s%)!Y9qU=Yx^iW#3u<%i>tBDL9{ z|FgkGDy#wmeucv%>1nz5{ce>O_uhH$vYP$f^h~N-?}5+vlEbxdI9xZLR?{&$ihQt*R#61SSb*Uze-%_L(wG5Z^t#5_z+v)~h z@c1s?cx%i6FaU9i>gw;z`u^<<1VP{!=PMcy;e_F;AdlfB&tE7{FS20b2F8X~VHl2J z^B%*jK2GEy!>mG2-;r24DyfWaO ze2&922l=B;t+>zF^GMjQ)&IlYGj7n5?&!3)rG73+-+GzQ7C!5Zwmx z3GHtTluOvEYY&A_0by#V%3uz>VDTc*&JJ!FC=jc%s=c_XE&yf&=PNKiFa(DI(eeug zFJsXt12RTIzf`bI{vr)dTo5-LDBAppv`P!Lkl`7g zfe6xYBG%9i-!KP?0G+Jp2rP^PL~72qLk41?4)2ie_H7RzC=ZhG43h5*0?L#CFC5cN z%^GMNC$h_q$_&2Dlzxp5H}dZ$5f9<53}he^y(0H6pa?oqy%tWZ;KEt5p`&BGO~d5O3yU3fHqQ3<$7^ST5i-_FBr)w_SC=`uBsT; zA_$w&)#9%H3>xwdvrH_33(GLlEZr>%+pOuj@gl8nx&|@&sLva%(CW+)K%#A;4nltP z@>j+N9+hDT=+PeWF&|H@in8G5a^M3r03bs`AY;G|!*V3`kPlU?f&6VD9Vidxpa9Qu z{x0b(@y(Nd>ZC5S4<9HbMH1e)z$B-t&T@|eRFWkXE~}mdhWe}=ydf2^z~roe11rwr znBa4C@(6}uSXi+Y(*!7!Qw2RvGK`Y+8Y3x}^1!s|a>$_w79c98(jyk-1FTX>)FKEG zEBA=-86h(@xvbtolRe#X+0@dK-jdyvP4A{oF6Hz2uCOlugT}-r4E=~N9qJvSAsGU* z9x48e2=MXLjNsIghb)Rf0~oV0-wy@`(hfzE5-YRm8j>U5%z^CS4nuSP$e=x~ObjaX z-&U*)Hk7kcQvr91H6gIza&H1;(-SRF8f;S(Yr`9e0b}rs;s`A#Q}H;pAUV-07DvGZ z`^z(`=@#ci3%X$(K1StQ<2thw7`d|}7)1@ZAv}RFQz(EuIR&xA?Lx^6-z4(hW~#cb zECI``L~}~H9!Toi@*Bai%I5Mdqid(0uBh~{PVZCe>{4vv$F!);APPc28zM}Izy)5Q z4HL95i-19k05|#q2QDB3PGCack0j0%LmBa>+<>zjk|ECkM6Iku)l@`Llqgh`v;H>A zMPHQIT2m9rv_@C*MqN@zW3p0~sp5v=UWNfD3quNYatVTA8Su+U$)xitCMcEE^Bj#c zuD}XD<8?~TG^CVDtF#-qVM?-d)U?zixYSF@LOcsVQ^k}_$>Z_n7##Hl>`|})3$P5c z2&{k#sz3{HKtkitF)ef&F%;gEZ^Vd#Lp8I3+#ppG@fu;aEKAhNmaAnMsPBM}w`NrV zzaU0&={0fn0*>JEcy+6M)f<+H7%1*uh~Y1Sa|xJ$I91^|O%5}T)L~?C{;isGGOo3n zoK#EZBn!AQJ;JODRwhmb&bcAQGZM{!@=0L{aHSSb|{)iY3U}5J8jU z1@tisih&41c0%{IWL49tEb}415@mgYGp`IAS(WNkl|9A4X7gqvRjiUEa}s^lBpJ_V z!a@UxwgN4X8G2PpcvNYd_FkS=6|g`!r8Wdp0U8jaStA2$Jwq00vE#fJW58Byr9m~A z(mLUS9F9OcrIK9XHVdv&y^?X&+zGMBGjGq6Z=uX@PgEft$lvDv;34U>J#*@)40k@| zbAi*+aUn8xAvkg+x4QnLauWn>sI49Ma%+So6<)wtEEZ$YX@o!53W5P-2}lkr6f#?P z63GC9DrpR4cg2bVL~ZwWVTw;fR77DIc;O%qHZq`IHTa&)L`9N#fi`G2@dA(ly(-XX z-K)=jReFgb8JspSs`q+F!5EG}3%0j0P*9A`BwDvN^eE$6T_41~Vr)1ZG0!nrz*|{jV z94*+P7{nE7Obkg2Sp4!Cv;c%%;9^nDl&h#?jerQ?Duw<<)-gY|b%pobe0bL?lOY*s zUSk%gjxLvPmzIC3$vDy{0#}ynFNuveC7T#4X;WXEcZ!o1Uk>fie6*UZS2#!E6^>vO zz_>KT_*tD3GsG8-`GR1GC=1+JeUk`n!Ih2~#cf02j*U?m_BaRb*N@p1Z`oClsnH76 zOhXTuBOmB1o3D{^iaq7Dry#k3DA`~qn1U6yKo$hra16%+6k-V}9ce{x8uk;TLGhu%ORcBS&rM;oi#+Z z8jMk@z!;T>8FGLC`gniyS#LjerY2Il99Muj5|Rg6fx|H_=khEO`gaA|A&1VAFF7B? zW)@;Wv`lM*I~b#%L8BkF!cfhlsem}BU=r3-J_VicJ-Q3H?wD-Uhq+M?m6xKijVYz0kA0K=N4Hk-c~zogTf zy&0OsSu%XHe9ajeW^xz7nw=AXQ8=KiIikA>h*6LrkJD?d2|xny7oP)pZ`YGw0~&&o zFRv{rwXd;F6&V`=+pZfi`8G1y3VXWj{_>$7M6?>V`@)cOAKMCw1yZYsvae`3sDKKx zU>M3F9y(ifX-f`0H3kOy$(ArwC33EJmn82dBt7%Y@KoRQuo7*XsIOxJa(lNe(6=$r z8@P!aa&ppn5nll;1Y{Tkh=OkhWe%{~da{JVnh5~8+-z_o_Y-|^2M^#veR&?%cZa6=W? zf(lST0y=dLIV>6_-NPr*(%DT^_8Ao9VN>X)zfR$DW}ze3D&&< z3nFgVDQ+~Z_tv`@3&0pL6sFgW{MVBlI0n+Lr*>mb<%K+M8uj zsDK;vI2k2CQ?)&B^?Cli$J}qh{ilNuBvI3pNc5n~oVC|I8=X%-^R<)uJGtb2KMy-> z^pcY+SA*+EkR0L}mLLT{xCZ+D-%srbjKH!PR2jU13Pj+uIn`NKn8SPz4|bNPQ2V7_ z+S2DQ-fjY=*mCN08QrKp#S5c#${N&qBtXx#UlNFNS^MYW=uz{pDkx zdxw1Hg)&K9@#9X%G(dyokn#$W0p+MXN|>GKp}ixHUPGu|>6yL=BB1A@eoWuh8VB)j zBe9d<-KM(UUz-o?^EB(y9qj{FVY`p*JNauY`oNLl8n6v*G>ZNG{SEbz2r6}+i{Jwe zo_89_@Jr$(=Kk#u7oYh4A0WsG97slt!Gj1BDqN_L%osCg;J}eXv7*E?XU2^A0;Z!! zFkwQ997&QPLXsj)s!T{wV;MJa@Z@1b1BQj0I4kJXnIXdj4poW?Ri8t=qR);$j(v<;oSgcBSaG z%XhApRDXkc{rWXnsw`Qs5DOO$oa4ukBZG4al^hiU7dUX}8KQIN44y-a4)Fm)5pK(b zE^AgKf`*05m6-58X(w&(@i_=~lG6<1qz$yFC$Qt)LL7O8mIreK7r;?-e^4FgVNlI7_bW!{ieMFN>^ z2AXG_85kOY3>48!YQ?Np!~_?>HXCgf+@v5!C>f;OrpRsT>8Bxiq)|jHjzJx$$#oR| z#&)c>s-1W3(K3rG=G}tJc(T$O9(&`>@}4%x5P`z5_!WyEfyjRF0S70DploLaiUvYw z3F5}!9S%OY;jp;PhAZy4b8E2SL#=7jHODc*f#w*7~>3V4Iz4+#h#l4 zhw9PNW0bLNx-Y64wQtqA>UFEIwl`~e;^l(tEM%XD-Yvf-GsLhc7;7xDbgTZ*tO%er zYiPAMT#N1B@&+#W;1qRh_~D2rPI%xeerxXIkVh`Lxo(^&$KL|`n3KT=Pas0V3LAA) z!%jUF6;)P6iA7*tU7Rr!qr9>)mvN>Aa>#VObyvwI%awAR77ym77BDj=^UQmi?#-VV zXtr5sh4wt42pAB-%re9X#aaZBW)PcBlqQ|q7zJs%_4(+pN*&eCahp-r*NOD?{Pef_ zMcC}ks;k=M?TVhPY(s`N+*Se+#7%C;q8owih9Bu2h;M%T8@2{F!Npndf*5>I1_zh8 z4tnr|AT%6BBzOmb5sY&OLzt0{Bs!!tgDItRk~hlHjVM`RbyRAdD*iCW1S!OB30I(9 z?QVxV-OU9ry&F@ULn2KrSFYg+s@LOQ?)akXhj&o(9-HB$2t1(kAMuMAm`{I{DH?6h)i29ZY8Vm z{10ugfW0P5P=D#TW5^LGXo*0M=|JIgGflqQc7@xs7$3QSINr7nUa(vd}Rsq zum%`t&H^axiRVB!l%kC8g$`35RA^{J90sE+!`R{LerN?DI>`(U&U45_h9?)gzy&UNMNt602OO0|fd~+& zK+zN{1fBFGC~pvlQOfeA6>OzUXG&96!t|yj+|ey@$vItqZiT-5B@7?dCt|9Ob#UUL z4`tU(k3F-9V7iN2uBj&9X|stgd!j0&7|tpx?~3L`XFApCMP{_~om7Z`JTq{{owU_b zx8VXFF(iH=4`63?w>k&9d)102I}0kQIv$#iyxSq+Syfh@58Qard6r?9uu2%y3t5)X)FlE&3m}7T5$i&-GxhFw9@}PC5wq3fg>#(c z)WA8lI8t@KfSvAi1{De*&$fCmHg8QM7vgZx3GEeM8nVz~_glGc#R0Hy_-|n8B`(K$ z5Womdu!0xN;J?PfJDpwRc+?XfTv+z9;IZ&VFMEs5epVHtW#9?yhO9#shyx=%K?vm8 zvkb8IHaBoBZf6YB+S-_=G@daY>`+UB@ia(1xxfk3;Lhv?)rCwV>I{pU6=Sw87+Sc3 za*eSBDnMh2A7hJjrz=%4o$M}Ja2a7(#>D=YZH22{OfM ztvvBwJ>wdOc{R>)<8rK^2OYvf8!it26>x(O+~7t#`q2x1a6T@a3tCW?7nNlsEi}C8 zTjkojoEBQ?Y#@sb<>J=9Hpj4z>5XTd%2>yC$36a0E`2iOA`|1t4Re?b zjoM_S9y1kE_92y*aAoH{cNAO39d*g{WiVs8iB@`ID!L(#G^-gZY)+~4GRc7yD1#Zr z__Z_aJb`xS`MtH_vx*E&?|K&+(fC%dq9M)ie*3%8-swdzc!B9gipR1V#cW1r(P8m` zTGaX}ma|T)GtrE6#o{K^i$f`6TmE~T)*#=Y$8n15o$h)9y-xS9g9}VyJBipg3`~`V zf$S+Sf!V@y*|Vb^vT4($+N-;E@VugJX>{A$;B}sAdzRwzasUOzKzF0oeF1q+0KRQ? zt9<)>L5bk|>tGMN*b#m2v6~&y2;TR<*Ut8T2Yld_ZKQe3Mp1@m)D@Wxpf<7);)plP z)Tg$%idSu*R>Sz!Bd2l57jMDD!?fhPHJC0{KxCBzyX8uTxiM#cCA*M?3OILamZ2L> zF!|iNy$iFK3Nv(~BSs6*IC`3s&H?deT<~-2KnbF*8me3UXlC4c2G36X;*Z_y#t(bj zm(P5*LpZ{>(BFFgC#zW8{^GKDclfWc;DY+xN8-`)S*kJK;*G}`@s5WrZ zV10~~cd+mbH3V_rCt}f}Ndux{ks>|S;sqTCOYFCVOn5==S8}&>a`YEE41;;XL|pnO zC&RFXGDWY(*QBl15v=br-~fj`)bm2ZNG0iEki-uD}X37%Sj$gV(oF z*Jn06SVN=seIq6}DW*3caB)T0Gu5&Md4LB^7>lxqN+X8{nUHHg)>9K80aKW6zN92p z=p^DmLlQGFTfv3L=7s-v1kgYXR5BR0LwaT?G9#00YFJfl*d?vkfQW$w$}mNTpg@42tB^1LIt8aGI&Pg{xVBttlo)1e-Q=1!1NZOv#N|a0SE=GvE|X-ar{yKmZzGPUfgU!ifT+ zRh-9}oL<>(%Nah;iAsn@5!9KaEeL~Xx1`q@gZXeP8pR8^z@6WDr5m+rbV;6gnG8iR ziX+9I4*7$BDVRFbbQK9oF+iW`LZEJ{PyZRGa=K44ViI$Dr+1nmZ@Q-%0xo4R{s%H( z6PL-75?}-E1fdHfR1^AgplOp?Fq+5o3aYT7{)d{)sG89jl+!gPb&)Z85t~VwjnBw{ zwrM9Z+Eq@$4c?#ym{EZ_>MS3yqb{J1RdAeD(3L_eq|K=lwq%b+N|s2Pq;Qar`xuE& z%3$8{ow`7!w4zZL_MMwXaJIsw$Z!o_3a0t-kn9<8?wN`uB`ET_ruE6E`Gu$E3Lv!$GvsDe7Ex^%A)`fCyzLoX){Cwhz+s-d1o zum+n9a58$*xT$y{7FNKwHWaZ;X`8`7vAQW4ltB)`FaW-3dsIucq#*(*_5v<2xssa# zB8voqc(Oq{0n8~IEIV5-8@i%K^wGJTC~1!E7W=o!%zg? zH>Ps4AG~`3nL!#HODH`dUst=Q0s*IAYa<6i5MA56F_OGCVj=#=8@3cOw)0vkmwC4B z6rsPE6u_ipGvv08nhIP9qD5J$L;wwT3yrK&Ktz* z`oqyXA=68}5kj_`b8`1;dEKiNLnTAu`?lm;zB?pU#&8Acs|B>>{9y>`;HyFCLr^s%y01k!eD!th0;4z3?8;Ukdg!S_r%{T#Z#<0Q&PUkRFrd(#X|rM&~Qx9 zsIYo_O=3L1QIeu*!Ia6QZQ!8B{M*09umD&Tz*H+l^W#>+yXwpIAcQd|z=+hi4E#l}n) zSdh$D2BKbUhD14(@GHO4oE6l(fEIJjiLq_k{9T9J%@Td1aLgGuN+7XX1V@ksRj|PH z;Q>eB$L$Qk?>xxyyanj8m~Lv%`SQ;l;?Hx$9B2&&dxX|*?biE}tjc-|u|UuUJ*B>I zSqY5{(D0?Bh93unN%x^2HE;r@ywpuywYg!@d`bq$+tF>b(UL6&kR2i)tzROY%R?<-O`<*R(`2j`v^jI!$R^z87==qS{ksg_ zye!URAi*gDDu4q-;7%=k)md#M&EU@dT|FC$xGq@xpL_Mz@Z&XU&DPhA-G0O&bY$Ju z-63*a(9No>25q#skkHQn4Z`w$fepiU!$5X(*x%gLs?69uT-oTl5}q>I2%+CceA()f z**rxl2SWjzy}kLWs7)e6!#LU$Q^m+=%&RaAs;vxMg469gl<$iduOq*+on1pm)T`4C zYP{QC)hE7v0l=*p!hHgxM!CYU3@zM!C2$1Fz}#8D)z4ii3L@QZ%C2D0pZ;kofu!Al z1W-yIP=sVJ;0@OtlB@=~aCTk7wo;c=n%;N0-Y-DhcEiwsnXP_jH|{CXZ)|%$zS!;x z;LOWU{UpR}?$`o8A@Ul9SO@+-LfY91{=`Wp#lzUiLaN1a`1w)_=+(0Exq_BxG zGSt|qJtW&&LEEf1)KFH`x$SK;+9%8)k;;O~Inx>5DgrN{$1acx%%I1TivmWlLd~$_ zC@aVjyt23UEk(!%Rf6pn0Y|XsMInZ!zME>vsBA)`SuHzg~0A4vML+ZJ;exF~;~!V25FbQ@C?e30v>zq@J*%}d!uL0?TWq1#>?g=0Y|(+?&TifwT$k7 zic9a+xd=W1?p_SrizM>i;KEq%`iJkMDJEpW!o-m1cblmN_{>t)Lmw``39pUYNIY!9 z4G&L6r9%$OVDYA0f!fZoE2;F3+wqLi3*gHAlWs z{EYJgg)dU>th$h4s9(a)x(m+w3q?;187NXs+t6ZaH&2iFP*1$y4&eL^HL#rZT93r* z()9;}nanBn{$roEW{+g^?%--~T#&kjZqM&T5ci{+uywz%*W{^rFBWW~@DiICT3~b$ zIQSKx02%X4wUc{JD<3^4gzkCE4a?Hq)Ax)k{nGz&Pmih7=ooP6f@85@KpE1_K zFc^%n#@KgBGxj}u2#tLSA(c?g*q0%Mgk;~7r0g2|z9vb?PLidx{%ZPro|pH_`?~Jq zxUb{-e$LOiHjBRhK+d^2e61b*RYI;v;pUewZ$3FSatd1|X=I+nAN46T`}UBXU8C;AqHaX3+1b z{<_A#qW6hG%Yk**?SH|`zf-s+Y?72o=~%Pc_v5zb?>3m4@yyQlQ~Qcs|GN>g+aiJlHQmhS?Mj3=Yn!tp{R|)JCkt%lX50he%dW;NNqD_<@+CE8^vS%zkLW#$STpEn_gwVm z<-9pt!CPYUjy}NqNK5?;+RN26o?VytrdF_RZ&U_zFz}-e(>?lQ1@sT9L8iiaG=?dx z5*qIDyh01cc;<8xj3S)A;GUYgg!fESpLc{|q<$Wb_v2du$*S)Q>y>=u#%PvT`%#Hl zZN1mFFJ9A}23|0%-AoLuLIRk#U+Ud7vzuAvKgZU#fgcF6F%2q^V>Vaa3b(z`0c3|- zo4pB`6F471O_8phE-k#Cf&)R>c|z8@J>o`QfZg;Y?jHbO@#i{vZt1GwegDB+bTgX^ z=j#K$0<9(7f}fcjKfD2az#~OM zRx9Wt6Q*f57`PI|b6w(lEV$+G_>+t5Cpi~AGSs{F)2`kwNcsS3>G5?pG231KCYZ*@7@ZoG5Io6eOA_Ht@E3(5!OdTGZP1RV%=giK?fY1MN-=qBOTR_hw83np> z=Im;5h$-GMGsk!DUzeAfXG^&l==v(;$610+XzEKYa|-Tf9m0?9bl*&zj9Vk^*GvWLLy206{9}8H(YH{1F_5iJ!3g5(wYcmcJyL}=cMaT!p zj{3cy{co^C zWiPqrvO8(#9%o0@6!^WHq%wUJSI7-qrRniC!ZRiJ#1yx7Sz1Bx+d)S~7s^xc3q zvLuAWS-*8ee&TY(3D6*S&~wZ(hG6@jlB1B(!IA<7G z99Y`$CH989v5Bsfn^IkTr`~$+!1!1r^>aF`8#2RWi}B^^R`1T>l2O?!xUBPj+OT_K z?Jih**P0debUn@$K~(4mOXfZ0Y}knrIrp*OVPRHa`zS|M7RYx0xKD&__X}cCn``F` zbC_NARl6CF*I)cgKY%X_yT-=T0@7S=7fu-mEYvR_)a#+icBp7zy&t3#!v*zetuk=F%BfEGJb$j6Isdo>o< zN#^?;XS$Ai-4-{n4E8RA^dPb~5%k!wEXknj-Gi=7WTgZ2@o$KL{k@mXjcLczKG;HM z_?LN&$>VD`WZB;T#w<8)BruyY9Y3wu8SW8p$TF~kU{wUFW{SQ;>(5awCe(CI+mfjdA*dD8!ux+i)spf3~#M`LvYg%Zpzc)2ZCIMG=xFQMpU7@jV6^#`xVTn zH6zucC`j$k+PiTi2Ru4-)OO`*`gUuas97FU)Sp@0W5$1#pJ7di5I}!A$;roL-*Aes zu~pKh%i|$;4PDi%&Z-)dD&sAs^Sx5l;F8_c?K|+9ywRm80iuTyew+ipqZewoAD-wOaME}{kaLBa(?P8(Y z-SB6pF33QE!+&sk=kwf=k7r-~clOZ{liMm&uPBu>ga~gg;UrSIi-~?&^wV(=+bV!X zO3fy-E`%(=p%|-rN>CQ{>PXGE%xlMuiAjM_KZGK8?*F~R@>_+ z7WHQ={*LD+V;+WuoZHPj_wa0*XJePOBXnsD6v8MjN}d({K+HDyb&l|-Y(A!I-7|fI zIl<0Y#{&a*Q`V|GhJ{JN@s91S{DN0{y9Ilx_*HcwaSL^R#0Jd=Hw!nR6G0{6ILl{g7KK+$G5#R`_AdqVupJo~MD zv>u!;ZQ{8~Mql%7e4D{sGjkc>zH--nU~djeSMwbq@0i6qqHEe%_YEe&tFg=85$cJ7 ziME2y(AY|TvC1BuzUlq4kD4<$9mcuey7xHy-r@Ted()NcAFB`&@qT(H{DP~!I!_vb zVm~PmG6gCHRCP=^CsvVJ*AFNkOg|aE2Qe|EH6+IB3$-;7-WwA;^*dkpo6q(C+tBA) zOTLs5pRYoEm~FzVYhdp+fWiPdk48`wfP;$x;)qhFJ^JfN%w^obZPVa#oGC{fE-^|2 zwUm;0mc&)qqfDv~Zc@0S&6UA5cBO9cUZ+CfNza_XvvEJsoip%I{oXufqww%vJF<~z zBPi6ky4X729wAJCn6C{R%}AxqI;!5q_Jv9%?&F3(uhPO4V&c5c%xLClY*G-_RB0j= z(n^7H08_JHdQ7818H}@xOC}x6BXf2WZ{}2pb4_k4Bh18+wEb7Fy-XhTTjYf^6_%Sg zwT^K9${lTs5!C>RDp=w%F`LoUEpyAQXdrI}J8uM#-5B6vu2B_NHx%1B_;o0MnF8`*r!01DbA1JG7r0b>g1 zDVEcDZ)VPTvDsqIYl8RDSy@jD)BRVwvj##BEwan?bDAxdPOO(`CJ)O;K4d2c0a2kS zq@UHSxIuy!BgAId80JlZjab1vFbT8OG2B)ZHV*WDxCZb_C)=NahG(O7bCZB;Q(rnY zraH8rx?E_a4XVWBezI^d{iG144ON{*UxtgS#+l7X89gj(`=^osco7p?EHt-k2C=u2 z#hS}381-q7S0z7l2f!_Uy?8&%rX>QGUL8ldoME@P2eJYZaWOt-loWmInLiYrY<-@; z=U;12@CNnI)m!ksc+*jL_Ty}2;HdTMlUF={XWUtBxJpd&j--d)@dSR1cOfSDQs%{7 zZTySpgAwon{lu`4qHuBouOmS2+pN4`bHhi22GZ*q7DLS^+G?tjgL~+atk=b*w*8H^ z+TL7@7tK>T2Jld<5u=mOewP(6V5XmA1{REWyfSCd27(1y$wW-spY1m7vb!ABy)!ng zq93D1xM+%`YOq)nrD6`uC{uHKFizmbijJJZ37##r^MBqvcw(PnXTrxi;^<2A`14M^SkXl=>0YzbzgXAK=QC~-PV-&*kKedH-s%rNUM|2af?Y&J zEfyL3O7_XLJxC?LfU+&HA7NrOM$`!SD-aO}4bsH8$XAAOkyUJ*N&%FR( zp*uendE3@*>Um;w26oAPyz=l|Mg5b`FSl_x{<4hYbz80e7Z#U?YXM9gK<#*z=r{FO zQn}J`;D035j?yoAJU>d=+Hr0v9jWt~kYLFP$E+&hr#mpkdxNciaJ~4Sbbr9UW^IS5{AyL|l$0 zgbe9c*~u;l4-v~1ypG2<6#h$=dKl$lGgCR;HhIQjJLE5M=>59K+S7IK<k~g z20p7sa>lG_t8($9$Zix{9>u#(FJ*4iW7X@zV3Y0q8*aCEwsKo~;k#VjPMGxFrFd@v zn6vQri3|D;Mb*jNF-)#2-XannK^aS72pHE-9Tqx(BKH!Df9v709kOL`WpLf2L*vnR z|1bSMC-xIT>6GB$jo;-CKgD&!h;Wa*jn3;Y^7MS)M)Z1;eM2~0QjlIy!A)6+7!j&9 zn3i5mm14f`H8PM6PbVur+7ultVK&Z5G6%4^y@{7tOK9->E935X&b8a&UCQ=5g01(` zo87ee<&|&C!6n{Kc_yCTSQL}L>-Lu09fzP|{y{JQ2MHhZco>Z*KPSCkx)oZ!eY~>R z@T){f#BfgH^BW0IJr&P4Qb1RbV&R66@K>s~dX_ zmA)tMrW$!Z-OGPsw+efn?$W%?Qa=I$a=dN{kZED$KcSx%Dt{tKImI7rcKEp4sU6Z@ z?tX>se_xqN9klMf;SI{W+NZO>tW6&%GU3)O$;bG+3`4nc&K_u}L%rCxQ!jp^2 zLDmKpXHIzCwk^@q;#1IX7O35QQI4u22o|JQx^V3(=%$;jXBLxvE_ zmE#Dwvjvj(P;TbBZVctr7`dd@7;Ci7i4)!lbEi?`%^5X&r+n{HSRw^w%D($O=hQJxJjss<4?7F<46(%YTg{NG_*wIok z&uU_@)l`2arHY7lQL~g!|4FbRfE{~|6^Ngvhn2~7GINtB&C4eS(cw(RUpUo6bHF>+ zhqQ2w>(wvQ`0hgg+Cv@GLH<@S-7jOS)kbTrF@jIO2^po!(7(xRcb_`ew$Fy`ZPUn- z6p2Lrc>V3#1>`gzh21K)B~y)PMP*#m&xyNna$_0W^}U{V?cLH1J~;~K=NngVCTl!4 zaY~9ukwgmiCo(JJoiuK`9ze4tJiv#F7O2FV(=l*uQwHbf>0phhAG6a3-IMz|jf#A% ze-m6ZV<=Cl*pNR%Q&fCLG`2Wepplw*lYtDWJ-Lc+lsd0s_VnkE&WHdP79bIr4zHHB zhxsJkx4y<_dowgE(a}@m7Qqo}uK|<5#hkhRE2{81DB;$8^{oP5P$Byjy?j~O-CxhQ zcSfM!34UrX5BSUO@s_`$j5|e6XREoN*3MK-)Ly=t_wC9P$$$I??mcQ>-w|dYh^YUz z`}6Iho}Z5@q}*kEzd9KDpO>jbTcShzD+dc;Qs_Z*w$#tHRX8hBULg>(CZeCjDrsZq zXWgft%6-OiclX$HD2-K7%$NckNELNTQN5H~k%XjS1)MiI@Nr3n_~0+&bY=a7V$xj( zcc_PN0yFMGO!n%@nYn74Dw`+LYYcY~@Jthe%Yl}s4@^qzy} zug&`?{|bYP?kI4s(x}2U2j`$?-yB@RC$3*~i~9EMf%J$ADbGFW%y&#-n(U2u?r@JA zlBdfVljmC+Z|Kb9$Re5^P}la|Ik0KQM$|Ml>v%s9?Tr<9PEng~FPg-qm9VCadj^`hU&`VcPSgW=+ zWxf zp3%dHX8_Ox++gFd5}zuex{{)Hmp4vVEFRTOWd>ZYpU<(&YVdwA@u|_D{nO*dfVzpt zaxLRZV@+KuQR;D=mS(bAZ?niTaOz#Udy9a9vql?QR?NmY`9d5OVXR_ndG;vVb{wTq ztWeYS$QHSDkLh6n5Xya0;9*q3jt=RD@J1`_TPn3QA_KzXd7Z{pxQ=sOUvBYh?m|Q# z3}O$x$E8OY@EcInh_+sn#~qWyyivlXDWxLwPOLIdVa06KghmxUT|@ipzOn`p{@SCf z$n3YK;du!weZ?Ururcd<>xsQL3qgPX&98_)f((yd@(?I^$B7VbZew)}xt_E&%)XvLz&2RM0_z^b)i~H5Eyh=w?bc=a+1llx1^~R=Q|_`2zfHJt_Fv5oVs= ztI1=yckSnII<#;d#gx}@xx9?H=TKG7u{(BMICC`21Aca!CJ6$CP(KqzS-c6ce6tyb z{pHrrUS`Wj^l`l&JepP96hA}{(PL0qz_dd_iOAQUERPy5KK$f;Axfc+hHf`{K9yvM zUKeFN{>2ur^D*RH5OUEeRB z2yF^G*NL=(P@!Wj%SJJ6t@of0<9lExSDaTd@UzWi5Ni%DBxjx8=JP^eI+Md7`8jX7 z>8=J>G5~Hry{wd5k|0`*3(raMSqspu5#E9%akdhz%&Xc(ax5Ql9gSF0X7wd4$#iqFdpZ&>Hzhll{!`A7_JQvc2%vPkA5JsA_ijfiZZet3bT{;2x zqN4U#eiF-4>@q5&)}$`uQRo6W*MrP-kG21#?73h`#YoF2XGXIq-8Q|c3P|u~)UDbM zfcO>go*niwmwt@`pu;+>C+MZ%TgA)=Sr4Mj(lk~|Xxv_Pd4u{Adc-*?R^yawzd>)y z=*cL5%jY6$QXtNZ3{7;PoCrVF{XbeI7p^-a$%x=_7W|2;7*XM3+3c%-#DuEmU@^60 zK?8ZLAgbl-?(eWtq$!Ww58~k^{RLy=*jl7#qhje*ZIFS{@5&tS?6QV$QX{(gyjebL zNM{Xy)2Q5PxhI}{a`somB$IWUJOE&Qzl#@%TQ7NP9m*<;x*uQb=!os!ezNQCbN`M^GJWM0b@;`HiL$u>0E-~Im0#U`qg~rPD zkfZ#!LeA14!O)ZoM2;9fh1MQiHJyAv8;D`sZ@fTTKlkaQmgE~-Z3xHd)j@GQIU+vQ zb#A%8_1GXd!y3DFRG-&G?wsM*LkFo0N?Dy9vik{Tz7;6T)d1go3$n?GC6x0RpW4_d z;ELpvcY%&em%fvfTJLAo=%#c~@*_<#8X=Z%>GkxTEKU2FG|PrsJ=CWL&JN_J{c>bx zYU>*tUXE;>d?eMZHuqxeMFS(Pn9LZs-<7y*?Me-kq%G;2mao>NaiU{)X&F{{`J09WH>(7;xS7eN@c-renahoZ?R5X;Ugs)^P=}Cln(^zbD zQ?g&0F}$b76T5?py#>epW-2c4X|CDWjD5Y9|DcC!8`Ru1&S0=bnJ3HaX+iKt;spgC zv>!thmtxvB;~y=VI zY_4?fKQ-Y1^kMqElb7sH!CMOD+|%V`p!HsBWy!9gQE#=;BbYBvqt97meWw>>tS&V_>E(pzy$rgT+<6nEH`X;J5ood?`77M3w_Yi%zS@h*{crq&Ys_g&!79WGW^q22tRr zc2^Y&NQzRql#r`PKfHz3HOC`Q@$g<_6auUq-KP%dtzM(EI8vY#3X~2&&;jzZlq!%+($U5D{w^TGvX_=kaN_Tn?!b_+zW0iQ$n9Ly9z*AB zu=+SI#Oa~3DSxn(r-UMyjfKLVpO(ief{Gm4WO9iTTMG*p?(Kpbq#Tj;YxAku|JsfUgi!vo$7^qU-Thebf9i?>q`WzE8GGwX%oS%58&DqNnHxO zn9jjC<>tLe=z4dw-KjlZ1SN$>VWG>KTf=(6)P}Gg~#~0)%$D3Fdxucp+Vc z6OVF{@NwJ5aRS-ADt$e+eR!yT>D#`Mm45T$zLY_=Fw|khhI#|^0i{fd1snwO@^)I) zdbx2{?Y(xl;FV=h;Vn_0{S8gG;>Q>pPZlxK9*;&p?lcOyHjpg_goDKZ;1GaC4M};G z0#I@9_1>%v6fe#aj9KT-lX{qdLbvni>?P@jaX?l_HkpM6^NtqzUNW4* z1~5$YaF^{tBj!@$dy^8Jrw5U4ox6Dz$eh^s6ef&(gVQf~gnMbxH5q*v7)Ep5N3s&RW8d788Tf|PBI zi8ckEaf!Vh(;Gc+vp8pCzF2O$E~d1*N#XP>N$RtHInc<-sIzX+Q|1PWX7w}sk(9G{ zpBY?fmc}VNdRMIqJP7kgbC4AMnQ5cDmq+9IJ$g`^E6gk;;YvCOmW1twNCcg9bweoj!``8yy)9>~%&+X%b zgoHfZKsOsIy3juW2F{HZEU1hxXo6ze;#K0HK}dn%J%b`2oWCOJ>Lk;$Yr@iB|L+{W z=iP83B710JY$@kvu;6Byp>%owTV1hyR-TIq+WTQ&fW!}OL*oUXRY+x z8$nT#^ytDqReG;#eGE4iOrYs<>h}q`54MN79DwY^BI!A9Hb73R9_(ldidN|t<0Cxihu6Wo)|{?qsRWFT#zWei6&XrN zZsHG?*cRFWl`{W9xE^1;%W8x?ZKCTW_V5H&^*mPAh$S4z{Q~MLYy(_0&%hBx2)Vzd znu6M_=yN#pRqg3lt*bm@4^MpjbDG?TatAeI>hlvapH&$A&R`ETja84+bDusl=7#g% z`lu)d<|@~H+$%V_ZZff6Hu;5stnD3f=}|s;G~K{Gqr2xgGiIi_QLYfwwN8f;0H|uf zo9ry`VL`a+gEu_x`L3H$999g-ZsFUPJAm*;_|2n*=nzmuBOp2iK5n$AMLNOtNon-3 zOOf7xkKw6gMn6HtoCQ}>}Fe{X7iGD!~;I)b6f z2W4DPSJ2BH_s>m&{uYW29?cv#jlH^!YMr?Hz0sU0y&zDe7A>Y%(!6^A*V8iHfHfus z4f|T4{3#s-0RCYNS2K!lYCA{*OU@)`DJBlpkzKESTZDc{fW?+0?kXTIJx4r%Oe)f0 zWTN_2fX2;h$Z;b%v3qKSVQCE@#~(U(^XEcRMxX51b1$nEzdknR&u59?mQo7lN@)sD zKgWX#>2*X!sIh9U3p|2i3At=(>3{eFpu z6WD|k2kN-~)vV=#90d1|8&?Ma?95y>8D|T z7VEhg&0i)LSqNiI_un-A!5Xl^ptDP?V83%8o=?>$T(AK;)#63@pB)Tr`T2TSIxd|^kn ze8Z%PYIdjUqP>+vW*nx@1mBsG!GRrL=_(obIo<>_=0$~*SDmiRxh&+$yd5tI0D3z1 zDAHo@pH=`(s;Wm&u6JkOKK=qD&{5$?PFgrwCNM&Ko_x{z9S2ABQGS%&_kX0XFyFS^ zI_b#78xl$${$thqoYVl{%FW(2fy%c0sS*@1IN!D>S_3FG{7YIN$ng`v(Fjsl8VWMY zklXK-JBlfmOw!&6c@C*Q_g?w8*l0j`tV%0|X?G>0ih}aRhq1SW?dXTD5!G2$uIc^I zEhCC4eZ20ee!n+oyPSeoD!m}wbJ0VEtzHfec`WW=I-xphvae`c$WCAq~5*96Xe~fnMU;gW&zE%myUg%1XbKkCndYGGz?79 z4ojro2Zl_iGF4c~^@X|>C*A*fw&*9i9HdeI^J>7{qvnW^*?6Wegsf$a0B%#$Uo1fp zdQ5o`Sh;;L+3Bh|KiG;?Ro%l}4NoGp)w>wwD6hf)eeizj8;PI{-#i+=r=Z482NwSU zVo$e~p8GgJDp;+(KRPP-E8nFCW)|RX<*$wJom2m`r=Pdw$I2D%=&=K+9F!Q2BO?0= zg=14!byrdj@cR^(dc)pcC^D=qtW_%iyYyzP76+KA^I}C`1)L!Tpxux;yxnFQPGR-} zqEAjjjPXjc^tCO0vB<@$iu~HoT|EtB_iE|F!7a#s_tOK9jHT(WlQPt?>S33)k%$ge%`eNtI&~FRsHR=Sk>Ci534t;krOnke1i$y^Z)zvw z@ch3Y{}^T3L>xyot_KK)v$NPuQ`TZ(95}fQ4HTZil*%iwivwng8qzp~G`tqbJyal= zv)pC6XMp*E{ACWsuzq8U0`(xCp0;Z{sK@917t6Ech3^TQ#MSQt^^MPv={_^<@n&xu zT)P|h_O|LSHn?;Zozqjx4SeUjNUty`$PK#GygXfjckhi$#2Y2B9EZ@2b40|gipf^^ zcz7?>IvR&3q#{xGtgEdu9s8iHSRF5(l3o}aRxKcoN7ng^7q?tUE3qCAMWO_CLKbL_ z`rGVy%cRy2kzUYBhwK#&oj*;VhhBJkc0Y2`2xj9dzBWyy(Bnnzo2qBz%rWVZGj8O! zoPij{ul?Dwwo|lo&wUQt1|A5%;(Pv$F|m84MAv{>{pw0P)6py7z?o?xc*vx&FsyeC zRv0#G;!8rjKO>2jt{xlG&fK%Rxw7ug7nk;(gzVOzji_T8_Oz~84Er-KflQ1ygkhQaK+ z`0@B$7o{E`gg=5DhmtEbdu^dvaV7r3#a%l)YZLkjkkdtqB{s{_xfqfsbla}7IHLBA zM6ur!rnirSonxfLv#jt*44kq5R<2unK9aY#zCDGvw)G3r>Dj3Cr@ZG2&UK?fuV*~u zI8RyOJVLV+i6WghQ<`!;Z8#={-O5i^fh}+t#5BZtc2adSu+JQd4Ul4u=R(L$G%u>% zH;44QjG4d0Hw}Z%ib=`g6Y+#eAUy4+443));Ioz=hOlh5;H7Y8QY@2)C=tMxF>RSf zZskfyV)MYO=X{yAmFW7u6njvfT=rUcfvNHkKdy{s3_Dz*|?!pDVM^ACRaVT@Zn_p>E zl%i~^larRLrFy{J9;4@OE+sa;nRmU_mHgM`_Jl9bMQ3;M2LYwYgAXnR&v?K4SLc!A zEixu^xy0ecV3=%BP%3pTBC*9BK;In!SYH) zv}q$-0$QI;>tPvG;2PvmJkmY=30l)sfu%@|pKno8FB?R01TSe9OrK@h{nMCld~2vV zL?6d^l*_aVHlFSsZ+%`_&=%*?qi`W|F1ERtDacR%D@;8-;W40sS+DPu0A{>y4YB)7 z1acZ_P6JEA>qP@Uha~hXohG^Rf-nX)?-kRJkp}!5K~B4d(%Y5l0`Fvvl(SGDeyfQ} zI08fT`LSy(58`FYbwg?QJ+p=0vWA6-LXl+3CzrQC$H`f+Z_IM3TMLjqIvt}q9>a{G zoTjHp9xBHpcWA!x*9{0BkIdttC43hCy7wQ>21=;DP@s90uOkRQ=T5pmPK21=D_m_} zLCUFu!aYVIn3A#YrsTN4e;^T<|5grs2I^dv11|Po(mnt8J|0~6qGS20$n_3hhGUQRsWJxb#xc%q zN5brc9^6VmxfcD}Z}Vdw%qZeB^+{1OROA%np>UTGVH@Z=M_uM#|E@68!@{YKxhgpW z(5bc)st-V-7l(Ab7`72c2tVR|lb5XauVZ4xLR1n-*{RZ3?zBYAH`SM>COgdCZN(Kg zYnbvt@0^saCBY%yydHfgD$piobEZ{s`ML&7)e!JupH2n{%hCgFit6DA#>${u0X64x z&FNyc3c~PP114xCmY@f<+?xa9rom~wDjc9?yKRt02eaba+P-P;o*No;yldY=(GQyD z(=SwK_5J(JuXMruk*w1Xp>y;aGy)3*8EHsxmo(-u`uGA6rCv|TYG0yvo07RR6^k>G z1dJzlQ9Nqpz_4sKYu>2x$%({sitGH#`I!5+`R9GiF7-DJ2sjB8D2@4$_8Y$6pM4>B z%Oy=sra25CUfe6m%Gb(H9Zu~}dU5@^fa$RtDdYa%z?;dpp{%a%+$k#ZktLFryHx4O zbB*6$Tp~{TnMEzWPj_RcKl&VYM;dzb*u)}6oy)^C(AB;Xgw7?$MU6uAHuLvY&vJi~ zmyVqX{Qhw24{L~@ajX>DEtlXT!hBhun|H!_URO3{2DIJlET$`lf84XytMjo>6lZ>Q z!+ZVc7Y}qpk7i*#xxSP%t|rJqGPC&>BU(Ps2yu|oWwy9xn_{qB3$Q6o$NgULt$NvkRKRD4C7=B*23` zEED=G^}1uFS>$qEd@v<4-A>Qj{hw$$Fq5Bn^-hOM`OM*+IwQXBT)FxWkr`XT9B&Qz zvv_lk_j=rQvcpBc-@JW(*mJ1*-PUzo_VUC(0iqukbwU_lORj$Aap{1+b0f7rdwl!z zbBaRO*X47b_8kKMnpK_I94W2$A}Ew>86n z;MF57g_Oy!aQahqS7}tHc59tu1fyhmV3}MlM2C0{K||`J!zBAhS>+XAaL~pMWilyX zL&eJ%VMF3?2J#nCCt}=_dw1faC&_`ML+SN9DYSUgeQ4e)x@e8Rc?~-Vi_amY89iXj+dx+kH%={|$$`=_q;gzf#-HKx~P0)}a_D=gmv#{~4HO?j#g zj##b?Q?Wc=g55oQvjCQ|3&AD{EcBUw=Qqaj~7o@KSm zydd0r!SXRgh5ZVqDj=ZFl+m2^%#|a;r;lo%9&AmgK-gA+ZbX;xDZRkM zz?G?Xnio%OP{&KNcX_LiVJtuMEc6|yOs8#bamT;z01&iecm|5^(Y@ah6nOpB|H^|R zwY=rLs&d(&YFdeV#BNrg{)vGAK?IsI`g+OKP}Ni6Qv$HBq9I$PEn~%pdgl#~b{TM9@i8f!+PG z)l3G$tQ;dr)VfMqVBKqEJ8WG3(%6KsIQo-uH0Xn|kvS%gaq!(cRxm+whoY|c zcQljVYI(hG7z>Gs6OelLRX=D>C6nt|o{ji39~EUn2q}g=|E*m5p)=KRfMk|=aGv;FHr@GMTB;#LY+Nl=v!^5D)(~od9dh*!tzN=DJKnSy>ad1w0CGoC z;1nWq62woVC}2Ik%MU=DtKe$#nI1&rcqQK)K)X^YwU@RZQ#qc!eZE};(@f+?im|ka z6HJ08S3|ELA+pOMg1_r!mpR$N7IMEb`=p~*L+V7ZS#k2c&uy9$0OU3ukz}iWb_M+ zQu$U=Y#Lr$VTkxU*~q!rQ#cLimUZ=K47&-*6**RaD}2L}Yw zr1Ck@fD#W8o?IwEl;%_)!yE1{EfHLX$im$N&YTi*dI1M3B<>y{e~f#%jR^D78gYOc zV+WjSZ_;U9`xLw(`gFAR`&Pdx&OTxl!?}6t{1Tcb@edlfYOSh7*2A7j4?E25;5ZQ( zB!P?)I13H{B10WW0=?9+LOF;W8Fa%Fr~?qa?#D3Z+lZBulJHA-GHW^BFl$l*Gs|BK zG`z5}ci~^9K9)}6Z#}s%1~eO8d$wz3{$Uo`{^@W#OP}W>dM?}I85zCO%<+p8RYB%1 zCo@58k$2=n!e!gFH-jJ7T<{!I1b_RwsLS;!UGVI&71+|66v{`Gf^rZ6Y5=FlM{R}7 z{zU7<$dC6MPzNIZ6bh#}(B`*+lcnrQa6d;6FfLZbJ*%h}@1?g(d*` zPmiiG(?}Zb_KJK#66vAOXp%#^dpdYP;$>8E&!se=VQw*^ezmokf*{_Ih-Wb{nrk4bO4sh^YoZBX{<~c6}Q;WFFZ)E#_{_ zEAfA%rqkMNTE)XM))Vk_p0$g_+CL|IIDKV9slE55zIhzn9>{#e8N?K7IPYL7ITiGX zsE{f9V)uqgoLJh~Cl~l^@BICK75L*U^g+nSh;O~s%$Wcv2M*|hWA5Dzd2r$$G9SpC-r@psxJ2Bhk<#BEM?QOd>G(n@XMc8Q3qW+w<+}S? zgU1>!fH%UOcLmM6{zm6LZ|3UTG$U5Qb$9O4D!#vCuR{7IDXZ{=oyX=*(Jtv;r&trp z)8i7Ei66tyI8*qH$S0P73{2cni+C(v)=zd22opijMMxfJBXSUzk3CDW&J_ zG}u3(WSP)f7lIk5Z2LtmoS&@blU7a|n)UzGkW?{G7;w+yy`tOvlI=PmrLWF>rZR=g zB(rGk2xBqR?#Mu{bJPAaqzz$={)9E}`EdJ$n&^u0=!ItxjM3HH1wBgXz#pHH_8~Qk zR`2VXKbYcOC~e_>7jCkC@=92)1ynkZ7LlQ?rsg+{$=2-qeTb1ndfpBGF3xe)w~nwR^4f-7t2J9NbA_fvCiL?Iwp zcElhT05bIjyU{N57<%dMt2k1S86}tB-+-$7VbUqcFBsU>5HIhH$C*gzt_Qk7gqKnb zr7=Pa?*sVSf&AfP${<6nX$TaXe>QTPX{hg$WffvuI(+s(`c?$9x+1Z4?OfieiU%05 zf+=5lHfI`ng>gdU(VyxjVRjoUK65Zp`VYrXnihVfJnq5Ri14}S%fI=~@h^uAW874(rZrrscGto*i z@>Y>aKz?U==KVSZkKqm_7-aHeBpA3Bqg#6x31?8WiEnwlFAS z65Nq`saa3IiA`t9@oQILcEbtq72`O_ctxYC4+^ot}g`i1ExEhywuWY@sE5>66oHln%JqD_P+Qs6F71}fs_ooL;FiXbK zQ|(j@Z1-zz>-_1oTf8e=z4ahWahp^08t+n$PF01owkv_Z_0qRHhw_4gMIyXg^9JeZ z{aZH-8*#_^l|T;4NAiYZ0D!3pz|lkp+tUZ_kWI0@m(+ioguzRAU7%xB%l}Xg08hb`}GoBeO6;Q+xir-;EYx~z4Q%m&| zxeeTkBdBGjxp=k6+G!?L--#Mk1IMnEZ?O*SLOS8QjX-c86v>8-u<9LF@SiQd9_!=9 zq~bTat?Mb1_MRBBHk{1G+fdMasML#9#x{@%Ls!0YlW{=e2w%I7`foQMgbft3RA6Zgwh@3hOrjLs-eq?oqrLKnF_2XI?S`O^gnx_5 z{(VV-L~DIEX+PpBl<71xrO?Y!MpGK+D1$04`K{@q45~%`N6~rsCAI&5oGl_CC?d`f zM~EZbrq&ntNDa+_Gu)#gnyICA0T<3(nXAH8Xj*DoR$AanQ_EFZX`!iESyx-v#`XUA zo&VsR$9X@_`MlrH_w(g$G_F60G?~m8_27~6UFf+BC7C1()ze>}AI|NMWfYLnD;|4S zOClnQ%^Na{i$8YH7MsN3I1o9%;6x~%5n2C;DiSn2(z~Jh6=^&|_=+$c6M!D;H5PKA zW!{BKj@8k|oah=)s?j5bBgMLo)wd?Ab?_~1#%`1`WCYT3eMtVC~Wq%>$==)}Wiwmdii!HdTj)cM`ty_fXVvipMk%gbU z10wEk&M3u9XW*4%8yA~BhVh4wZiQXx|6!kSBorHii~F*sf*0YuKoGe8lt@aoI|GOH zRpr=8y{)7<@`m(Fxzc+C4erRKuu+Jm`TZV-mxRl#{l@UTYS|?y=BWPH)Vg6n|7? z=JsqW=KsWJk(R9XUyrc*A1DAR=Olq>75vC!E7U5N>FcBOPgDn2sfTlze%i5{QD(s#%kfjwj>SeIX;;45#?(-)HeH0!WeZ{WWe%Fk_E!ySo34_yUiyXuW~JqhOcY|Do{yb z_MB;nD9b#jD8P(Clp@jCvOe_$rGcKIpeL$g@C=G>6;E?xD{#Fq-gM?Oiu94qv zo#*}6-8+BJZ-}$*Ej)#J_-`(f+r-y>lfi0JQ{1%_0hB_}P~w!Te?$N3>!<(i{o_jx zK2!|kRTTlGi2$JGx-qhF$QQDVm!g+|DPjP0{m|uW)PenIDh;Sb1>u`G#ro?;#o9zW z*vY5*SA7a7SLR&oJ(c&DWdKh2lH>8zrW}d@i0Sz=h20Oe>r9qCM<&=|U0kxZOZY&o zG@$f>Sde;fu4(F$Kd(=O3L`v41Tb?fGT@rTwah%13Q5z(%U_e!$#a^^HjGS^30~pi z``5D-N;&gGB)oFy&e67rx1IUB7Dl8$1|Le0bk?dIa3DBSXgoI8c%>9>05#aFBtJmnfoKoDD*K^NF{Qz$SgZxTkL`9i3C zuJ6lsTh)f^7F`lQ4yL{`^&{OdRhJY;1uGXGsuRQgCx+C%hQ4O38CL^&(fTH8@6)39 zYTYj0)!wKSz=$xvFxT`Xah4!99id(lU1VXv7u#iCSWRmgzj6Uay2GpGEBIM^wKWF6 z1w+?6Ft7Af-=25~cxWlCM@BG#IkQ2Ec8B$KKX@gazR*BVB$f$f-PvH9B^up~*1CD} z8<-figGVr;y!m(Io3#i7*qqX0=KPaxb4Sq80z3J3eTeVju}W8F_Du0F2BarA$0X~0 zVfYFS;_CGwr(XX`F76;k)?o?%R!I=BPp`eTIj!#DAWKYFj>u#+e zl7q2f)1y%@Yx?7nP{@s1}VjC5rx$UH|$dlS9#1=-z+8P*Cc0SRAM$G+LcxRRZ1e1ON!vi)! za68b61FI3|&JGnn_O?B7_0s)vPv*y_aF?W@YtLqiZy^FD&H4dwB`@vV;}G=th3AIf z&xPJ3JQn;mZtW1sO5`wm;Ux&ar^0J=Z74X|b~@kb_O;I=2zTDGqX$Ma6|Zpnek)He z8pTGr9M1R3Ej`j~W+3Aqd3W}t2gxw4vw%_f3xAV*bdOIv!{EMh1RnfwcxTE8F*Z{E zOhJ7B;T_FgrviXU4jgQ2iLa_8*^p_}9(N9KH`wDITgzZqcca1inmJRMs3%Np2~6Rr z9!k$o7kFzk=H{{I6ENRV4vH@P21U!92(Wzpk}y;lMd$owILf6Se;@Mwmc`_hMyA(3 z17q@xkn~=gMYdxpC9%evSXLb@Hj5?ukL9;!PT5?+7AZpea! z?J@rWBnX|^M5N0m(44O2OeC(Q00Wo2kdX>X+L3NK*q{qL*fcGWw)lUCy+HOfXtb2N zF46f?*OB#@Ky6}Rh(?-usk~pOy4h6f%cuG!--4E>AX%n<_6?&la*b}E93LWg8DL?I` zkl};=4AA;4L_HSe&O4u7jWzJ~l2--LNOS;}1*EXCre4?#9P-CIA}-uFBiZ6><$=4V zBnU5|62#)lWyugUjg#Ev2pkR@7Ah_Y-Jx^dbf5`UoNvD9O*%R{SL-E&%a^xmjtI^O zM;?Eh_z08qVU8QYg-QT7QfioqY1*$goGNDhk?4CGehQNzA`j z!srgC`~pir`k6oyP0N{T>qzxJHw45t1Ld$-LAXKKsNR(Z>_(>d($^^UdWGr47;KL* z+Dr9Sip$HBY3a999ZJ9_s=?_N7g%>vpKt4>2Pm}K#e|L;Bs|B6xd0SRPg4j|d#h4q z2GJJ-VCpdR1iGwXWk41?YLR2L!$f&nVi-5#`nAVh#JA9@=?YQdQN>OHPzm#|ZHKrMXF z(?QZ%4b(WFjhttbzo2F-Sje9xvUgaDE~P55rJw2Oe|t*(J3KVV+9M@MMv^wB47$uL zqs%kAm7v}LL=z3+PuGbkv!+&CtK8BteQ#~K9p+Aj@ZK9b2;~@j=y4I=A+IqV8}UEI z(9`u5$a?I3JK}>cK$$5D9%{JomeL}Z7W^K2!buJ?Lkp}2I#kB|4~q-x^hOAq5da{W zdcaw@b4FMn^5;a_^LcgOC1W+{$^C2lmQDbhKysX)DLNu7D$Ecq1Ua*y6f*SwQo2N- zVbF|NF;hf!T{ib7r-sq5YM?8k^KY^9%dj;yYjJ#?_@)$@mOC{8-)!2$!gX@eX~Ozw zV;z4$p`TfyStc+zEbYW-?NzAPbfSwh)#TcDz2~X&_mkv%dWv4*PyCoU4E<*Mf^dv` z``U`4epnjFaue8`1YGzDG^4xi+ARETi9FJX^aUIzmp~U4*hD$fvK!kaO&45!`S;l6 z{7lwkA|L##B$f~R;#;a5tGUs^Khj+)`=QiZ&T}aeIlYeh7ornJO1g2#nm<)`V#lsw zPYZ+`*q|F&uK!{0UBt_mK5t`KT&<_A#Tv}#$@@uFMK%Jtw|VCX@;euL0k7i^Z2k;c zAYOMC9Qg5qL}|E=bg}%rdL1QwV=Etq++AwUu_BJ-?)nw9__qKHOFI?rSr8n-fuDD&5rBkbj`+`Xu0w%BP zN;BmQG;^*u+_VY;eCpjNQwP}l5rd_BNwaRgioh$_MoLow7;(;S7l?fcbcVIhxYfxJ zD6i#MiOd=x2lseU_6L0QVqMs;xwPNlyxBDxBJ3W7CWN%Gp?l&%2TBk~@8FZuw{mQC zf7L240a~u{98zRz>T>c01jdctmTa?vd|&N0r`~p0fbHgv)mE~#f{y%rBLGRv?a#?Y zc^wLP3Y6(Ej;@wytdTAN^AFD6fr-U$oz-XYIA4J57C?3bBfGE(LJB28$?APIZLQ;@ zyD=aQOisH0c}eUPh&guQ=$I50AhXo2WSjsbkaXlosNp=+cg3o`eR-Pbf4G`*Rj{$z zK1VKOxa9CZJ)FH==$UIb>Sjv~-`&_bjZ9A>C)7isGWRuq)i)8LviHI!7U~H(_f4vj zl8rmBiIbkUA-==41NmB}AMlIptRJ;m*Y_s>vev+ScT)Z4Bzc8oh=AG&fMA} z^Ha1>H@3_+Ro3`V)bQ(vgEk!3HMGkr;iqk&tLWA~bAtzbUeq?~SdJ(;`h~0Sd2K8Es3wjcQ zMba$ZUJSMYoN?X+hk_ssHu9lZ+r1=!;9x@7yc^2f|HtFg!oYsbEg**PdV>n};_vbj zg7pCqQNLJ+32os+j~?kV{?rMwoz18xI7Q|wp-&(1aY$W_6Ofnv+TyW|sN!MGBB;|} zJ6#A`2jpFW${kyk15~vIe~9t@$GgNJOAPYy)L}popDM+x4G@Re34s`OX&;3G_tis}D2&I+LHkGIJCMUuUm9GXucFt!NXXBAi|=E3 z|0uotv>G}NfR77N76aedd~n*OcVNo+@VUe0n2gtqb7FBJQWsSjl61EKgu6c%NXF8m}8@GK{=E~mq z>#4(xZ-2W^YQ8wP43-U0D|d1&QLzO;XktS#_QdZC!N=V*k{h7|!ZT>~Z)#Lir&-Z= zaB%y1#GlT;PDg%;IkVz|#;6yk>0K~Xvq<>#GRjuN4uasJC3;-OG|5qcV#%X#0kcPB z9lGE$=0^#gQ44}J!WYtk&hYU{qa%LhIu>=3Xz-O6sH+zIh1j#2AKX!8s*ZCRdd}Ze zh-y}S?#+Qc6uGwHN2PjI*Xt}3gZrcE!&ecNH(R(ZZs$KdedRw=xT9tr?Ovb|H`QG@ zFrz4=OXoB?BrZFQEe+&J?TVi0r7nxSI|>7P+@7x9s(uy!K0OL&%BpufrxWt>Til%Z z^yd|w$sW_=B3={As|k)J+4JTNxr4(6ioJ=*UB%%*4wOLEXFksO$%RbI)6PB4IL!ru zAzCaBI4uuCF2-?oXh0BHGZ0ywzIxs~d366Zii$o1Pg2EaYzswy{q-LRN2j4+rgYk> zDV<7#yAuYab?QpEdD_=4brgIH!?+N9mvB=l)-G$L=c3P%B8SVNHaMJtL3o(~$*G5UUS!+r=$3|@5xsn?*W?Yl&3U@1zil>ss ziR{f8IQ4{HL%xRjJmBui>Ba_xRx6*2AlWd5VA)`{d@qhpuFrZY5Dw&P@>sxZi34^C z;vnxhN9Vx|IPh{7=}L9VGu!aST+-dxL!N3@z+Bb%DPjG48_IHXb(Po2>SzH0AVunC zyXsW?MCt6lm;#FTzkCPKH8BQHkwr_iVN-AQ&1NgTtkD zez|EEXgG=MC6$6pbj&^+QB-^UOWBoS#_7URCzaHRtz)XGkgf}%8M<;~T8>A%RE1zg z)y+i2ZSV6Mg_a=qkwndgL{eKxI|Nd5fX}maWT013ZaFldVxTgPw11b>O`gUVch1`) zg{QcN`~#MDN_x?Voa*q+6Qfg}=?`3>s`+#v7;8xcWTHcR)fiC~8f!nJO4gYcX8Bm1 zD^cNhrTx3pL64q?^aFSN=A%w@*ygIvID;@(?+4)XVlGYsrK2gUZgodwxVfr&cY8oG zvSBd$!L$Q+)*2A{`YpyA5^FP3a}RuGJk*sw5J9md3{Y(|1LXP9&raf_vaYB|X@*Jr zmUndD8BrNbvjyY$Y{e!aSerkYz_wRi_y+THqya|_#dYO*NGmc+1j2|pBnfOMwJDS- zq(UU5Gt_C+P9d~|jS^o0Pk7P57&=!28K!VUSQAr%Mf>WD5dR)hmCJ|ouMT?^gFgVk zE<&(o@Td6SzvQugV!&z(>1enL_JudRHuRIjv7;wm6~-rP65jKU8x0H_d|s0|{B6Kx z)TOL!hhJYL%AR56*grfUxXGp7f>_$nnkf<6D*Fuh5P~HQ#N&fjsJTD_Q&>%;gP=$a z!hlOneQaax!VvXf+gn1Ta@THNl@PH!WoNa}Ssab2hwKvL{>pgXQgj9YCT|P39Ozj! z`t}6hHai6;?`#s$R`*%KSX=zSzTi`PGVG+Ebv1l>BAB>Acthv?<|+KIb>iHa^ORhR z6yxG#zNXqH+4;X`v6X->sQnH4FtyOA@UB(_p|Qltn0%<(%Y{Q#$l~|;mqY=SXd zHD=}XNxDX>DjGj!i%u~egsO|zu57=ZOvo2QaGcS<@|^)No)|vK95K%E8rC0rzCT_7 z>S87x_A>)t{<-41-M3Wu1OJu`fC^}@-W|;Pz&ryncxd%#dPh0YVUGCw=DMQ^<@)U^ zE&CCd93z(+ge=AECU*T_aT-kjUOVG~ty%6OL13#rP33I{ z%K2z0IJCK_K> zc(@kxi!8A6yK!p%NRBd#sP|{a;lb}!_3b`ebb~$^6X%kt2uIRYiM9++AVjVq zT;ZERRXyrF%o4#ZPT~)${rP%X!Egg#o{=0pLMQS{w(|9GIm7s)g$K62S7pQnBPeGs z6!>r5@EPqjF?(iIoLV^0RsYFhpN?IoddW~BFeY4Skl|$c1@smp2F3nMt1TvhDh$Nq z0j*rDHy!F5DS~Uy1B~ze;oy>Kkc2O60GiDPn=;`!Pl+stT%T~3rc8tt*K#SAANcjf zlY?MWFl3QGe_q&gqrDveQjQe0Q}}E;H_OBG_?OT4rv^8>SdRKhS&b8Zl{YW`n;g6M z#cd3yY%WWs&T2U<8-}boDCqM*R<~cx$xwubMxoc>!Xxb;k#s3zC6QQWsj(_^Wl=dIr?+HJEvrGtX=n#~l+9&3=qgt^>(P=1%L>#xw;ReaY z5v1ZuKnj@`Mq+8h+r3f^7fu18Wd&SR8MnNklWkORDQWKLdY{#LU3~ z_T)3}fA-!V+Q&P%g~o{i3jC@SEG)3){0~wNbKOFL&ovcX^1=QuWmfv@+uN#B98<=F zA15THefIyjr}MulJ^U87D~1S95=tKhswTxgOkhYIXUYGU-dn7!=*ve&mgMLt<;Il2 zk}Y%n0om!D7t-r#h-BRX0#ZixPU*cC)C6*(q|ApgnTMMQ^fT70&&^};K|NB~dRc*4a0r`|vTpCcW#L(&#&@yZXaB;%H z568J$NTLWi5O#m=W*2n3+3Y}r3Jw_ESg-6oY?BE0KkE3#q(Y7jAkA_wP8CGEWVe02 zzOz1@2iUWAkLRa<=`)S9w;r_7az0AXpf|yhuexjs$cpp19X}z>r{e`vobM6;`==m3 z0{Ac`4beou%n8g*-nhh~S*F-LNMPhpHXr;a&p+)0#i~FfdV6EYbw_$($1HP?FrY=X z2a5{061%nc=s}sjgFAgs{!ARiUPF$LQX_~tkuEuFUNG$YoXVRn(*#l13zrc&>^W8U z5+9vQgM^3^jw35>A!k*v$& zFps*(Kphh9wkGNCDL9f@)0?RY6SIx#L9barSlYe)htMWV*KRBWP~vlFGKaG52KA+D zC$?cr?LXQcutsJ{AcE2IYzBK=wRAiL?r@;B&vWL0&eHg94YNG&+(;LW$Yx*c7vq4p zvXaJq_0q-u4*)G9cXXrY*cU+El9wXuDv4nDxQS3;#NpZ2*m`jsIfnamfdTQbR7~#i zSoJ#}j6GU-&C`;*8b18z;<6Wd$Fdrd27C*)koe(hH;N?LRy53mbgRcJg%&h=rXbey z$H$TM-%erGo*z`@qXDWn)^nmta!%sv8e(BI_>-Me5L%)V?5n#js-{Gbk^N5=)SCf~ zWJ9z6Jn;og`3g#J$7cJ-LQ@s!>1X^Uj$1}aL6UR}*aH^6`7Deq9S6^ap+uK38bu2( zIcHEKZc`9Px-XUcG9F-HVvZVS6xjkLOzF8Z`(CBFV|i7<2dvhNA&zZR-Dw~FC}GX)8oITYH2;kRoVYTlIgN1(AGr3P-r ztC=+*wkxh^&v|>JsGL)Jv%MSk0qpTx6-~F04)=z%`U+>YUOXIlf8as4PSm{N`wu=l z#0gmpED4i?8^w*NUZ)U2AcEmV+|KnE65}!rKw{@<5)4UJw*=>Na%MmhTi-jkebKMj zI3_I9C6`6dO=d{%kK={>F+{WO`$qZB7I_q}Pw6OdO!fj+yArol1Lw0dA4dwFU#MscS8iz zx^BRZ2s3$}DtV4e+WFAa0>SLg-uEAQMs2QL-_kkf5f{RIdFK#X{rV$-tGi~2(F+gy zwd}#RRhYHDMcd}f&c$p+?5o}CLBUhmeLu4g17JO$v~D%aeO9g8T`hlCJNw0cZ{LxHmFX1#X+Q++y&pvmKfXTM{X550xcHZ{r_sMp z|3&zH(+oA;wrI6{w&F`Q!&IIWdR}KbC$Lah60eSv+o41Z z2$8QFQ>nX?Im9m-j6U{k?Npe{8IiKSn9S=d{Qarmi6m)0j&Tytw0k6zf-i-jN||&f zoU!le-djlDu4UVD=sH?k8>jw@9U2aJ+(xh|tyi*g6JwUZ3;<3%24>jdsN6eNllWUR zOJA-uD@o|xrQT~jWi3Vev?1W}!&PST~qx5fTP;u+Q;1557g)|&U4H=s7V z0`3mE8<|rc6@|w>>;$N5Nqmg(Vp6Qfz(TnXIX8?BbGf83-MsR7AaZVUz3;yHbob6m zt#njm-J(7{n`sML=;x?NM#!Pd@yKVV>_Mk%SEGTJ|1=^NwaM#?2rq5yhwrc~%MRLR zW_YxGwk-Z}+iQ-vpoWtr#;8IH79*A#iKgWAT5>+$(#fe;#C&}QJg z8TX4HTgMIvWeg}XK=~Q+_-8N9t?A8}m0ayhK-1A!Atk2dQnA8<`sWvbrLaG+MsxeT zwXi`<>0X!exSE~Ywa3TnbYAH`)(;hgA07>7Vm=t`G*RMSS$#ink}VayQ&+$LM!>>a^NM?B%;&bvlneT4P=QNeYR8@ zO)3qrc&`$^HhaWx^~j3_wU5uVHwXa5Q4kM>H6Xyc9X6uxa9RkDyZ?mLSvn^$U&mG- z4Lpkn-9&I+Z5*zICBS8bKlfbS&ivmdcm@q=vpj~p_plJm-NlREllO_pC&u`J2C9-@ zUHP_P?vfT}R5K90TYwoS#4LR-(6@ZIy8nBT4*?!BKTrHcQBtTKsEYN zv&#rmcvg3X{R$*Gh9N0_gz5aqj{_Z#Qt(=goYY9?%hdT$SY0qmQ&TBgQ=i8yH8?tmA5}^Al-B& z^gC8g<8mM>j+$au@Hz0t$){dQrY&1!xdtGUZLZ1H&Wa~(-*p}~vwq(?la&Hrf4hY- z*PG*N2&;dunGjPRxxc=)3eQOa)ScXhnPFnVdsrv@i%G{Cps8`@J_=bbwLxCX-@xSB z>;Gi@Z?E0g3CjI^Y#!1KXU-ahqq8SvjYOh^SQEOJav)1KF7*^oU7f~i;m z;6y2~9D~RqNb_t#0jyc1#bJdVdj}~bmC4VBgtKiqN=c(ju>VeO+DaMv)I;6%49ZC+ zsFUEy{Kuw^PG}F;^9T3en(rzowt7h_(BHc@8c|~Xp`l=p@XwcVlLM0FaET-}6K(a@ zB|zJ1m6^ACeqHc(QU;@te6b2z`l8fsQ(6dbz=+mMq?Gh$PVUexrVc}Cjo0cNQHhFOrQ9P_baj7H| zp{g%2o+1qCSya8wCM4d^rxYq@dMZ)gCw+6SyyGzv%JVv)+f(6l$Y^!WC#Vhk$vd>k zc7qCYf{1+7OfU#43#)#OmDuwiw=xPMqKksT@ zE$0=Br$+m?8DF^5S`w{{A6t3WF?icnHPP}dJvea)ooZ5;Ae44qklB^#UY9s@rbGw? zAHB_%a;7hZ^g1_9T?WBvVh%{^=yO#NxT(ZZ!J+q^EkbqB-vJ+>TKWy6jj4CUBB+4r zliIwr`ltBpc@fZgp*mkV5SO_d(H?j#EH@@~TZ2tXW?#yq=r)GuPnv?|Egn;El8ZcF z@EVIB!1p#4-``l5S+e@_MdCpR%r?C};AY=^-Zwhnapig+_#E~@w(i;-v|8!3?XHR` zk<3m2?!*x6Y2K!tg0Fa!^Ny3xazJa)X|}L?F&I0TJdX}R6Nn;rlhz3T5-FX?#Ab{4 zHjsE;L*;3+mMfPiAjBT=rkzXI&AR~XO}U$LCPNHPHa>nMaMr`(=+>O)AmWI=T&Vjs zpPLiL-}SqvLDbz(ALaXXHMb`~!ex&!n7e+lDsFXa%ZSJqV*NT1I)B)E&bmIow*bA7 zHg=6#VC&xhLm~Uj)&Y}zAb|`qSK=y77&hv9;PE=0DsFAzZ-w>8^iu|NPG%Bn$2}j~ zMGt9SVIBoB0keG~WMz;3!Cjeb5DarkU_b*%F@!)m4f?&8m}hRy2IWL&F?Sdm(4PT; zvxR&%s#h$ja{3G891y9^N4%cbH0hcuOfqeR69g`5Mp@>vXIzd2*aPLdiO61qOr`ND zkX)#9Hq&xY<+00PVeCrw{r7M}!@{(o(;b=Ne^IV3#&ydL*rJDf4U@_n>ng8A?oFE- zMAW@4fA*uj3v1$RSA|w#7*2Mz0H#Ua!uNkzVrZetNO7^J=3@%j1t>RO{BNw zTGdf}TOJKw%K`K3{-7Lq)Vd`$vMo&84$F`*Lwq*eC)Szj=a2aZvjk7#0zf{n%Xe!c zwUSPY(hBJkHi#MP&;i1#^8xabX}*W`0J!QFM1qCG4L8|rx9>o3ND2-3=pE?Wx+vGse5D<+Q0O6^*Yi$+4POKJ7^p z6=>d(tMn*?Sw`zN2fpl5{V=L6JsP>V^0RA4lP_;u?ahK{A%Ud9EVZYRQWV@&IoOOQ z877IXkA&WYDOhopyEeHf#&a+#miBc>qxhsMkR<=U{HlA1Iq^ox~Y>a4tb-up? zfeQNH?iJD(q#{1^R2Y&U*BXWxcxU@Rc3$01&r zgxOR%%TAO`BMssAqqs&k{_el!ASbcW)J-{LUy{|0^z-z?w*S7z;Y{WllSZUnLt2sY zeuI$?J?8XwN_G3AK{dCDunuha$-mzE-p?uAmvsNhLlzUF2p~fL`r@SE#veJSq}5s4 zj&|tIWY?+zK=x#AQukC5g&gC#^H$eRTEb?@_qoax8gNpHwYz1HGJW8;HlnWjmx6H` z1YdX#Rj}z|hP-hRY?gj8sB^zX!cZb4Ai?<(~!BFd&PfMbOC%c^wVwtlrd9fNi z#o}+85lvzq0M_gm1Cz=DAb)_f#UB;s69a-~*T{yINjgY@$_79PbdY%$kOToA@Aifo z)hwaVyS@!>1%V4^G5U=w?7zG^1viabr~kWSxwhvuSb2u_wvCymy1b33gm35W->8?} zkbmBr<$mEpF<1KijkccT~PDUMoskq zq6m!n!iLerz^C;}kOn8KPo>2RtFTmCb2>ChKNC*aR6Tu=2vQkMPtB6Jh;=qO@+<%t z0^f0BOG}rq6#zcL7|uz7@LmYs#-MI}SY8kexW*eA>O_sy!xxa5#+GyLs&nI|s)h9; zPl99qmTdmgvzy)X%A*UQEnp!ugeX?6+$JzJF#vO>a-zL-9#gbxNPR)*{qgb?!U(IM`~1vwV6NC@lk z!j|u%(i{%}4R{L3pyZ&Xm3Fn;Rr)@{wh$nf?+sOD@em>!`3gI2TbT9BdJ$Z$0kgxaDNzS2Gmt zVnvp{SrmOH!jyU0h6r(Da!cKyXN5#vIG6{>>j+(F;u%+*v*)PVY0v#P%IxAZJ)2a0 z1^n?i9H56IbDy|th6U@ec= z!U-o+016f0%rvM)pAZzu)3~#&w>F!LsZneJeguLx#H1bIn;4R<5z^6bn}Jrnd^6A6 z=*o<#KrX=}a}QOs%<`>?m1mDv;dd|edEuZ!p%7E$LXG9*xn!CWY|lE71e}`7y64iA zhEAd;!_6}#`OtUmbz}o0c|?KHZ)nWwigH6v>tFUI?4|m zPlLt-fLefB2-u_}RLj--CpB}&1Ynn&xA0#f)5%z-C|S#~bHPbY;8NM-`iOY2j#L>L zbbvHY zQb>TRNXkK9IYX;5m?Fip5i%1Sa6h#bq)~}TRFrmf-D%-7Qymkyy%B8J5+FsN+U4FU$;f3YrmU7A`PD`AH_C^azf>>EZ zU!(QQHxBIOJ!e#;^=*5G2d!9c_l8?IN-Kghq>Qs{!~!X~?=`WWhQ(!`#oI4T8)44^5CmgVo;2049kPK#0Q7SrkwK;6b=U!_$Ew zJ9o}M@WO|C>OGqp!?tBgMT@AcOc^cVrN@DnXO#8jYEFtTDf3k~wc}qcziBW8ckUfD zrcsW9z|8|X#!juTlj8TVKfLjNCGc!KKPPv-FVjd>M6NqJ(#JK@8l+Z9CT^9+ksR!w ziJ7y`+eRL8&44SYtcMcov%Wn(R0an&aQNQNUr)eGUZ!1``++r*6z*hgh>egY{ z`hARw6M}5!9ZZS6czX*E`c`}(dIRB^73pkrBhRd$uT`9HI}U2M?SJ;Mf9)e^cwYG% z$3DB_LB1og$x(p_gshT9NrGI22JaWZQ%8wO3mN2=$eZg&Fcyap3g^Reb_!EqMLDq8 zWc}h+&}jbA(JKLC+)T+sP3>#3O+LJBM0?_d68v-~g#p0bQCK)FZACwsN=w!XZ8%1o zbdZ`=6yB@+V7X{e4bJ z`(>ROi_^4E9S6~to$~jN;r15SHEFJUW3r1s ze~65m=;3@1{TtLN>c7kA28>&@4eI_1CYsWf`#jy_MbksW|H%d1%_1{P$oGc+?sI_s z`&j0IL)ea;#($nU|HX$<3hhu&=@fS)P_e^DoprP=2Xsy9Y+p-S5j8t`^MnN5uzv{P z3$bNR~02!)Y zSmJFFazB*O1_d8+bcBDq6 zui)UPNn(Gj>p0D4KbabPuv)(Je(8G;TFWL6U^_ecSsEPj@Pr;W+2yTb{Ye12>YM!S zq)dxp=ip(BrZwH4mHHJk$Bk;2FvXrn(g^AC$LussVc*4puE+0#@!dj-z<$_e%({&W zvJt|+O}+nF=e;Ci)z_8qkCm__<#g*`>*;KN+aU7+8Gyq2k@myY@CE zgIF!C@Mw?Ek80)vN5QZ+hpo$h;6%|~>K{7~q9o^3{aatmZ$H}CJARNYIY<719vwX3VU*+PXfi}`I)3P8lsnA)zaJyq z9DMSI0?xvUn3{BLF-d-Bp(JJSg#Dyc=0DKdUnfRArI56gJ+u@2+>~#*`+qy{2jY{r z0|<_rr%aB8i*m6@aAeC@Fp(`ow>>R*h`b!?$P|9da=8~Lyvn?09q=sUs{P~hgH||j za_k-KKf3zdbds+FbI&7J>t45y>;}K_Eon49$dcuiR;-n`v<@;PNFPk4n4WGuXkXk@ z-MUh>O;#0|u&hQ~!Bzr08DVt-&KfJsrjRpD$(bw7pmTrAEBN?_=1gA{3#BZ6@4NL% zI(YX$%Xde1&F`q+y0-=^-fcQEj@`b1>QNtc+6V$-gwh$pF}M&U03@37W%$AjKX!Of zB<04bZU^(@JH8d8?LZaA-xuGUQi=UB_x6tAe;jnfTtPF3D*II-IgGDTShVa|A2U^d z?s_dQ2;YTcTsYmR7^Z**L=kTpy{Fp zao@kq(gNgYMxX;$05YiqHaTj51Ot3Q@zVbrdd{s_S( zO3C#dh3!QQ1DH;&Y;L9*#Q@P+?bg67I0r1%Ky_h>rLxtm$hc11EK2Gpal2kh6C_8L zw4WswcGTNttGlKU1IjXmF6G;aWE6RsokT%j%TdU3$^ymd%}{iU}`E>*>CgM|$^CKShhU zt8T5V^?{-j|0<^(ebF(Tee+oQk7J8V!tXy8+iPzh_jMyPS*nAHFhW%XgALpnGuE_J z^hqrSDEN^3eS-Kt_~rmEj0|C!r6!R<+7M;08l0e=~}8SgTu!P zz*rxlB#?cM-k4AFv8-<_cDYLfqu^S9OY#b)2UMajm2Ya6)u@y@tl5Mg&%{|+8am2a zu{vQ|C4A}Jt4I5=4^_X6J*-queKG7D6xj)NA#s37ip-1`!-~O?qTvhV^!L!}m{tea zxPH&jm+B+ki6}LyC?kdtF*WU^B5o?Zq7rwh)T#QPV73sdDM+IMG%GZm3c?Cm8c;cJ zemUY3jx2&h&xvVGMVQmyyh`_j-aFzd@*dVEiCefFh=Xz5&P)kDP};}aR!i3oYgi?H zMYUL<%UP{U_so^P zmLuA4z0N-%bJuC`V}AdyUq=>*s(KSfC(7yWydiE{q0uP0*4_B@$DTryNqw1PrU{3X zR=ekfVaIN9QR-LC7n5FnGJhk)8ulLlKZ?%9pUMCIAt6dqZDUTM5kjgtAEHJoq?$t#9VDG}8d1`TQlE72{q6TJyzkxb$L{NTy`Il* zntb29VNw#8T4@u(2~=(Mf_$=-T)6g?C`p1U&|Mh-!e_YzWtcy9aX=-X#vQ8{t#VSrxe7#zLvci*H?)8nbr*spSWilz_^5u4cWHpmkmkNha!d6Irt~no?}e2@ z2)pOkNu#QJrUnrj3rEofnum41-PY#oOx>my|BY4&tIheQVsNP^bz&sU)>?XAozRxftH1gd;Wis7&D7rN$c}X*bkaHd@k?4fSBxvX!;`r10 z9J`y(>II#J#nF9nPo9f}6_xApm(mk<8HpB^&H4j^oY4+|Y~SAq(;HPELqDtCjYRlX zW$4C)mW6vpzvEYDh34*V?81n>HWVgdKe5SK+lP`a(Q{WuH(`~$ICwfGs5B1Zyv}mO zC8yRdk%TZ;X|@`X%u)a8X=@q`91UUc$&;oSXKM~%%i*bvb=W(ShA>+6vv64eveko2 zui|Dy!U1^vuhC7FTo^$E6>mcuk}(zXsQ5aB?{LjSTd_3D+Mw||Zrg^9bkbtAoIuCn z^i0u{jcU(HN&APry$|^5=q>EscXLs_>?p*VbZ1Wa2Cdetxv?oW%O~Y*R7k?4_jt-c zB*F8SVok`SniLLIk!J ztN4RAFU_bPYvJJ=#c+fHh#NcUBz3csn>Q0y$5iisuodZS!2$JN=sDXZx2w)NLqZCA-_a{KHlCZQpFdJ^=<#fYk*0LuYscH`kmKN%d-id>1@2@&w zg=ow#xLf%7agN^0gbiovg!th2Q>V_lm})l~Y~Q=t_t`w2e|>h(NVfolcZ_vMf1LJ` ztaZH?KZ-H?#Rz^ht_hI^;LbGg@H&>2!;U+3-U-W)xx2(AYe4RGQ-=^EjtjskA#vW? zp~GI7TVM=D8kD*r^jLuWGe&We0k54d;wog2oE64+(wAA*M0~0GM{#1b(Zuy~f{)7? z=P1g1m-5|pb)|=PmA#G#$FN^+-Py`crpH zZY0qljw8I_2B#A1wvO33D7Ok1*sMW#bS?v3Iao}kN1dI8nK^EZ5GY!ocu@=%9G5rR z;qLZTXR-2Pz7A&hi8LJ>q@ksJm;mh7Yh_+O&y)MKhkfLW?_dHc!1pMD&~6(z1Zzs} z`6<)A8GKaHaWkdJBmOSv z^R34X1KZ)8>KOb~#>*0gCy}-Nshi~g+(Ykv@@l+YrR_c$05>P!`_{{LMiT*OiJ;I> zv<>3Sa#3DdO@w8z3l)~dxK{cT6$X20>!s&dcdV?IJb0#mCJzs6v> z3V1SxgPH{GBYN-^Vs*BZmCO=w#_W%FmwR<%Of(fY-H&@zJBstZGI^*yr>19XnRA<2 zb+G^LZ*Bt;XVWl~7l9L*PlASciUHnD2?KiDMLD?`Ee5DqzU|mC3y_n~h698_l*g|H zth`{|!t$E%W?ra#lkmfqhBNAa-iM9-=sK5luwnj0ncPEL)yu${EQ#IMlzOuifu$j- zjyN(>`D%{?=vnmAE0hjd}o#BxR^<` z9vJM$3!yn^L9J|C?WZ$khI#psz?H~PRnnFN0Ldj`=tC2&K%U?pbgYB96otyj?{AmH z=97vf?VEjsL@B>CRRUhnmr8!*blG4Mh-@ce+8HoJ4r4O|;zRPS;i~u6!PXIsDKCd_ zdS_l2zg{KOH`tbmjhJ zLzK^kYb3sXI?7bd_zIDSr;VG!bA`{fB&fLisI#uILeuD?Je2%#GTq~k6Um;%fMuBf z>G^uCaOKeS-dB2^uPDb~lwY{`df(}M;P-(7)tXV&8xR=>AwUzqS-+R{OGYjUFf6+D zKXBYo6ak!W0<^t$5#8rB(3o!Vs7tu%l7pF3hZ$x0ElptVaKU_L;nov4%qlsw20(k2 zECidP-Io`BS7#ksB7sVOFPz)szQ;VeRE>#W<>yU zrIi>av`ZdK_mQ;8i=mRYtiO?fnivj5quM3Ur82)F#!|OHZ8re#5vtC`LwKXA%f_G+ z38u%8`|k@#;Q%?^XaPyS?_vo{tp0?&GElc7`}xp19SbdW4x5Lfy&rAdo|NT`Q+b&5 zC0CK$wfd<{F?!Z54WeRUekJO>($$chH1;D9x+Su49b{bE5w@wccWo0|w6L88!OO(mG~enh#6Nwcd9Pce71- z5KfMe^Am@1Eduo;&7V0#$&28^0!hgNwsqO!+XC5}4*wsVZQTmDN~Ll=!-GFiHT85- zCdViy&UamOk0sk=B{RB7(gV$_WZk8dM>Q@1jqx*uVz~lxLCrm=JPF=IA|@g1yV()p~<^&{hKV zA|YjQrYZ)w_hql|@l@$X>OQBW z{by_2Otu2Zu@d{9xbuJ+W=5nckidT47uP!2oboj&TyUwcrvb~t8xgZZ^|N1)5PdJm z|88yh5P{d2L|R^;|1PEH0Ct4dhK8arQ7I=D|ZqM zuSYdMaITtmv%|Q*|MtDL$NeDJ%=cdO zcDpbMO1kFr36IDp5nRg87qU?yV!dO{u*|6~-TSC>4X|+&@2-8`SBw9M!3|G z9qyVO+qlUwDzhFTdbS6e7K1*)@D5`zQ%QdM956=gg1(QL6z|(X@IO=UpDMnTrE9=W zR|u;(8j-Fr2@J7j)PudVH52u#B z3o+bkQWhLL9IT39Kvy?8_+UbWkR_CN@qMJOGHO=l!-Y7=7peub=i!A5cKCH6xV9i9T`flr6Bk>#p3tAGYlr)e;G`TNzoyl(Qs(@eAN29ZX!%>upEJ4^B)Xr^oJ}dI`=Hadxyz;FT=ZLb5nSIN2LuH$A+3 zr7~f-lPs3m$Q-(M;{@63kqYjz$^(-Gc_%nf5si@O-Sdp&4Km}ANWbW>GcGO{h%$cz zj$b9poQ+7{1(#uihLa@8{k#YvnOHU%N(L(Ws9Tf1rLXIRg(K})oKL;U=>nl@E1SAm z+&K-v6Bod!G|&{#k81TjH%zVh5JhAV2kKO7!d1iGC3Kkx?B7NH`EN_N3CANKDc}c3 z-F!9E>7?4PRgDkZ(zq)1;k;cJd0q3oQnJhik|*)@)2xPy0uM;m*Si6+GJahC3->Mg z?5>UkXfA`8!|kw8{?jbV^$LDQc82d{T2L5mSr6p!yyYuO*{^7=t@bdJdtVrzDA>%V|v1^V1f#xT&h%VoA$ zbs2q>z4H#AzG}W-ArgG71m$zm$k}Of7J^Vu`yWkVYCfabLnZ$fc7q6v+Vh-LH1&-d zHTo)EznW@dqoL*ZiNq$X;X#tb`Q^a?fuXHy--!W7DbbtoA{$*99sv_VerN6oSKWFe zDcx46d?-Kj;c)kPaYr%$Z&{eb+axx^%i`4eld-s5S6b;V|8r=RMx9B@37+7qVwy zn^nf}MjjWg#y%8u8(Jtiuvlpw2+Q-S%OFbh9(JIlgkGdbf0WH-kzsX$xbw?9D?f>a-lrtXtL} z4r?Di9z3$Gc0~K?@a0Rz+U{+ag?+2>~1qz?z1sA~8P!tr95)M0%r=4J>R<6Bc-(GctP zJ+h<$g8c)LWk94r_}&unx`zZHvF%J(D+PO?chO+*XgXGM|E6b*lMr>m+a{mW6+Neu zc&l2O0c&ON@e{95&fneoPXAQ6W(||ktKe;vz&!Hj^za_NpuqSz;}q9QbzY;RcPea8@rgM*;9IDiRmJwXO=pc$G)=xns(5 z4Yu_k)~|WlQ<<=ppLizGc{CDAV${3Uyl|a)aq~>$CMi0ZyCX@ZtMSOjoiV`7VjD{8 zwwSBz#p({eg?EsKKmU8ZA>@sBOWiNcw#yQ<+R_N}tOO(drc+{&3U6JK6h|i!%EZV6 z>7x&H_wW51I*_@3{_x*hSPOC;3H_vre8hG`vk-QQfM+n#K4)b}7L-5y`svN8Gr#Zr z+c18vcHDm!o_GgcKB(RpPUJCFE1Bohaxa~0MLm1b-W!Qlmz4jvDuTj6KWk#8_*W4C z2@#^B-iZ#brCa=k%Zp12BQNV?0FiVN2I!+m zx}4j{&^LC=ns3&Qyis`l_wy?>5ccnl_WIl1T0665y(95@c{0La?Kcadrb};Ei6kD) zo8shpQV&|^$$X~cUasfctmCGrJIADX)jFjSapmPFw5wfJFB?|4oosLkA52{s?@E3f zQHa@gsrlY%>y**Ehm>j`oZEUW9djx^zmys@G0U%OV5wU+31F3F>^Bd{y+bN&6$e&eM#N7iO;d8k|rS=>+F z_29d0F;ToXBP zh_9V7$waEBEr#c*CrLlGw9}R$2ns$0hI95~I^)b_AITDOaI-^LKZ%IDMS=%jYOKsu;Rbe#cF#zFvwO>^cGBG-Mwn+Z{sHhMLQ z^@sGTcgCf_mG(Z_^Zv}4paZR7RagNFl)>W3t*3)vCvSxY z!y23ke%AweV2pg77-Cv34d-kd@ZjTUv;$6iF!W^yW&gnHpy;L}tQ@tMf(@d?2JgFd z${z)9i#OdveRs(2ey*3gx4%N;Tii`TsiWKoAx}eXJ&g7=WdDPOOB44t?T8IMa4oP! zGyR1>^6UZe-_#AJi$8d;4|`rxIVZh5vhN=magT&yOsZHzScNnb4$^tjI!Cy+z9^lH zXUQlNUJD+1D$^1t2l*<9wb@si8}AI3=@v373jKSna|{O?gNTG6Y}X(JLt13!#cD6} z42S(Lpf^mpJ}NRau+*aLqaGCjkU+0$K>m;%iw>31VJL%W%jKQu=-|PT&sW}D`12*L zzTn#DfU)QAmlLl{Pq~l1nDJT31=U)G+rz`*%I60x3b92Sf44R<63ud&PV3bfw8)r@ zd~o#6m~(4mWzEUpx3>o4&iitP;$6Gi>&MqS+NQ_L3||qqS;tJB2bY{U188Mdu5*XK zY&gm`(D|r^zFb5>gNC2*2~I06M+KX$ZVCW9iyUB7*>G16X;RCl3#L-^fwoNqhK2N~ z8R5fGx*Tz#EpRPM6WF~?I%51qE#m5mEj39)rPd4%RZrS#r#UEB&R)60>TtXi#8IB{ zmQU?y(440^WV_^6m)>c%)SD=dv1eyXM0XLROs~`!mko;&*;vPq&kypNHSOK?W1XO* z50g{Qu3CP!yPYw*p;FA-IpDZ+ma)wy1Iq!361XE>DghyJRPbbYfVl%&@hum62;~Y| z4_WY-d~#K1(uo(qj6G~!54+}?L{?-L1gBLgYu3U1>f9x=*{5JAl84R`ARtY;MJHzy zhgpaX%{Ee-1L~B<%p}s9z8Opw!!=w1oM4#;k@CA_h~mH-(^l|uwoKHx=GUXQn_n;7 zKbSK2=uFX7Tg#P48Dsmyl*?|H*{Py29JO*`poOCUfCGB#BJ-yP+`uhe0R>+>>*C>$ z{iZwXi|iZ2Kl#u5zc%-y-yp3@bzg;|L7+#><=v@L)TW+>m^M~-QjQ4Pcxh8R3 zLyK)n$&*nUpL!jjkP~Y?LAapz!OC?^9PL}xWFK=r7~Hjoz9ETXTgmINN^bo zS+uF&sdY1^7#iy~hzKLej+V!4)695=vzL}=2RP{Xz)_3dx?DM1Z%aY~3v3s3T}kmQ z(xfzub@%pL=HA`3H$+tlRk>GsFJ9b4?b%RelM!Gu)3SH9}{U`oE) zHHtlmdXSN~d2;Z@K{55@Trh}}WfK%+XHf>r^Oz@vM3OlG#wc-cC6+L}@SelOUtHQ2 z*sI_qA#-Lrra)Q1Gwl6#@#p3**bWE&B{UAw{&sr-r-$pM=t!HT&az8l|I@Y9&7F zYY)&39K_}kE~8d0Sz~U@0-{93Ba{Uif&N6Ei3?o{ zA$}P5?pimKq5QANSucnNrHFlzLobr0!8VvF_B;kJ$_X!0 zZ1)$r)7zJKzpvTOtURCr(^?LoKU5Eih5Fu5nkMb4X@z!R1elG88ml53F3OU+(kyYCCqF#QK>Yy69JbWpx~LkG{JOrx^N)TlS!TLEsebh- zWxn;}W|!Mr?v&Vzj>{c66RyAI@$<-^|Mg;mdns4kJRX=d+U*S-m?O4Z^KjOQ*K}Es zNnPR~>i-cI43dd6jcGnm$5&(*X*ea5nx^-4N5OWNk|Y-UjzLUCjr)>+u=B{9?!b~| zfJ{pVcw#urD_pOoSvysaqDny>VEsDIbOeVWUOeHhxB(c6dOb^96Zvk zC9TE>d(sE1gv+g+GKg%2`7X=YBchg0rpFPto_E;VTJOWwKX9oY9u-Ri{Nw|3qZ4i$ z?QJ<=Q^$~XGQP+-=)GU=X|nwLWNk}x^fo5Uco}eH!CWL@JXde*2UrmZZt_vl;Q)x_ zkO0zlXCG^WN#M2xOx6Wd5c-<}kho~mn~m_7LV{UxCQ1^&4Nr3ANSB<_c#7AR6&ooK zuVawXnc_qS<9i+!>H22r?a$I+K?m!yQp*$WT}_y*+wkyI;vF} z>6HZc2{G>3TJy0-Z!e>5*XhT;Z91mI0IAEsf2}8e^MGR&;O45NAhGN{`eB(=_cwfe z`@=X)*}hemO=(Ix`{15!%N}ZTdpdl>s((=e!r@NzjmuLMGggrdK+&Oqt+A#oUAcM5 z#%*)Em!%w4P@2FM!)V-wRL5y;4;D)+V83gdEf?f_e%PN)VWFE? z4Rn^XwVh51+^pHo?Y&*tGk&-=F4`K44A0$5+{zMS8!0e!c)ANBPo12%Js{8gv0{Tz z)}qe$NQROHBww1_b>JT4KLZ^&`8kK=@Y8Z*%ji3RT)P-5^pkdJU=`WME~K3KRI&mC z=5G$HKl%X{9|RT@WnF~8p3>xh-VTmzP(jRohEJ(a23q9vA!>}`q40#OmBm)W#rFFXGgmd+qR%|Oz3pXe z$p_-utAY{{{ODA`-tU3Oeq05O*x+hx?0>B!h2@>>OQs9(!)D7#%+!s3So7B`5(?1y zTppq_#Ljlvq5tf%<@LHZE}>?QXOfcWP{1g)nu-^kGqoqeLqrhN02+gu-0H1 z(!d36I8YTaK$8$PSnj*Bt-a$E-=qe3&{^v_)H`?~KoUJnreg^CCtC2JV zGTaRt>H4%WL?*p-F3ko}8EH_tpaic?t&FQqw>yv?`%5ONz;ORBp9675Ms>#h-O-2o znv?bMGeA|^RoKrJS$+_6!oe6UcX*rX7ARIWWfW|*EN~H7Y3%`=v9eA=U?~SU_!C64 z@n)0Sm!8YU++v%@o7FCY-Kw=aF>O}2kCj2hI)b(R6t7t@?NuBhuk6ZWj`xi53( z$ynC&*5U^{k1!gx^^O-0ES*`X(wr^8JSAg##fhSYk`?n4Ms)DYp%Wb$t-;IEcL&%F zW@3f@+TVBHoF=XQpuX{k8!pNX2|yHmp9s1!%AufgCoxY+_U<|Z-sME^bM34y3=pO{ zSfg}wIb|&~>i@e!ridWVX0)sDZ9|8E0xq{DhQx|WZ476(Q$;3Irh}|5k~<9Z9&|Ra za|_hpoC)VoDSE8!_pWAn$}}?D^?&VWc`~rC8q)BO?LusDYR;E@H|0iISE5UE6V_z5 z3oC;UrZ<4iNo(m;Z$X&Qb&dUXu5Cm= zp))|O^ME@161ibq*iW{&yV&0At^OdWc&eay{oc+qPyS0_QWKk^6ML76pRa9u?48Kb z!CYW;Jn=reSm$cXI3_Rh@{IPnL3^R_d}wgcPX6Up(AUXt$u=r2V3 z&C3l>`k|U~M#QpT#Ik0bjjjEgr``9>&AE91adc2mCe`VV4%qLfE2F4`y|Yh#&dZ5D zU?yp!4JBii)xhNTtF3$5B$Qai%%y{|r7DM#y7jgZY0GiS9OwOorMkoY>rE^qvxfewh-$0jq74n+L5;_ZUx9Co?&l(>PIU6#Ic3(uwENAJ0x-QLUbGy^)v)Xmqo0>1-mbx1UzYv82jpXrFub<1ho(u~`J|3!pH<0U_KSDIyvMzlcR#4>h%P0MGgO*J)U738Clw?nd&<&b<0AK# zm_ysl^#Vn(zg-tTu8&{XHhKMje~!NTc)wId|LcGEfkb_0_W$;ca=`ps5T-{(;?kz(*ao6=!bfC9HJ%lOB60mzI_5e7MJ?qsQ}_qCu%F zeWmA`#qgGeQ~0!DI}&_RIBc7BboXKHz6h(%MeRF$V;y0a%s~eQy-_G9!-?JrvH=ka z0tmqEMUHZl%3-x;P?EBp+o%D(x9xpzUEQV%rptrH-n#`-E4*I1r+H)}#E?~dI=wh< z^7f0~Bc0;D#?y&c*!}5yHB(Fy4;(qOHCuC7PqX;%7~l46S(cog1fYsw6A~#d8ov6@ zVNZnKk`K79c~xgT?fLj>=(}#g9>;wy4EYi4Ra27P{AS=pyrZ8)UP_a02=Z7%=;jBAU_4jHRXq_MW|V2ls)jIh_9cUfDxrEOO5PS(Tcj&xj8Zsm&l4pbN3fg{y|Oj`(ViWJg_6!P3?7?>_tN z3f4)$p3zXn_FeZL3p3UGaqX45Pa0ONqwY;y$jl;&GF>MvCjPVNDNN{V?z>a@^r+L* zC(N;o;<4eSzDLZl>6Q&mq-W=kTV7{B`}P#{5F39P^3)N)Z~f&SbB4c(mLBUh^>H#$ z@IGb^(){^E>MU59Sa3T8bYaK_0-lRvxtH{sp(7_e&!8uhUq~nB(?{YrULfmzgZy#7 zA-?y1wFw9fhYyovQNFMmZ+~l!mlj7=-r}_Kt}enB*%49IFdP5+-YfC_`|}+~KOVgQ zYU2LeGoS8w!TTFNjo;8i>cUw{mtOc9NH!RR*`&W|Fxd2BaJk`4tXmXAZ^(@fuUnsd zxcb(Rz1a{Uk4AZ+b#PPE&yrCrBg1CaF5J)RVKW{kA20#tWyoFR43o6xBn-Gi{*{&t zWsD0U+t6v~kXfy2XMl06A6?cDfS7R#*817C`Ji`BtoO^SG!ESsb~}H|{ADKneCE9C z{aUSW0iH?wc7q9*@IgLa3;suoZExP*_xWij=g5tXW9gxqhfKzz?4QQUKPw42@*(h< zEgPJ02U%9Kfy)JY!klkDrkU%>9^eugToO=4H(@o?nXooeCsqO&Gs4@&a<%(m3qo1z zGo-YW%k2W$h^t$zH{1RG<2Hb!i0!x)cD!RLbizl9%cQy?J5`0Dx~?m^3i!Z#z3z?( zWt*yGgXl01G3@^O+Dg$UzPaMrbDyp~y54m0)4dD7`=N?t@}T)UsIqOOm2y)Ut!dNw zNhtTy4&V!G^3U9jOJ_N`3tH&+@_9?^O`pTIE^B=?ww(f+;CC3t*N09Wb&a;3Rr(V) z92IlfL<02|_6Vi_i&@J|TluI2FcxwS;{v+F2w|nMThjpdoGvpFY@OF;K7T01Ls?Fk z45Z|sOQE1`IYX*Yl7V4}^gxLKf+!>y*cVx^%sG}|sqUYil|~>HkRBP&0LV}_!qD+X zR&O{IO7Ig%TEiW0dufIZ2v=17T2EQ6eS6+bFskfW59kt%m;J!Eo3vglqevV0m5*Cp z_ojsQdwent>ZtW8F2p?T411bUL?5i@6Cy@ht61zI_jcpq9zR@VQ)8YyUrt-^`b6;E8Jr~#T`cCX`6KX{@ShmIoa)h>%= zD#W4!z4Bg`qrzzp#R=#9cSMPHwrXhoq=CH*9Ie}o(kn*{~j*}@GMV#xAg{pH4%y>QJU*3f115j96$+xZ>CO3tezPBg`x50zAR zggjJIZiyROxvg^5+vQHmG_Kr$L0-5`;!oGR>YQ*2#YEPWr`GCUp0@CK7JBU8c=8SW zhX3eadwkWSj;_@xFI z@xTAgB&FcEvA61R|GN&RtsQ*jy?X|)^{`6P@*C6xq>a_(Xg-;qO>C365adZ3!#Xi+ zU4y&#wCaIMDbQqs4$7#pL2A^jZ2%T&DUEVUUH2GMdO>`?0KF&QQDfYduPmY$`eC!z zRqwJ~=+Z`hc!wv(NuWx|Mr@Gu8tN5V@aR4Cq9=Y_9Tk!oUZi~fq_G_2Ek^1GI*_Bh zEU^tZ{NgA~9|m9h%#`~!_uq-Vl&iC3tolYS)k#|vrB0))7zB`9-X!ZooP<94V2;tLK~;$ zgI8X^of=f_RsPhcRktlKxOd$8l}>IH4_CXX`i9a>&Ylgl;Sp?)u6N>Kygs$d5J z1-b*yRm5mAV9Oje06}>cEJC9W)n6Bd^V^Vv*N^}@Vh_Wy(n9JqzGPir3Q=6z3BDGR zjwwRqJ4w7!_+JH1N!l2gLtr`|Wx%w7X(WyhDlj+@LoOhLh~!H}^l;Re#NS47gM2_; zbF5uL(sX~o<@#lsVWP89nDx_r1-eFQpU3?MhK4m}hKvKfRZOx{ib)sP_}S&$O^X#H zih*2|7Tw@M5%7fTU#Sz6Q|&KJSSiw}n&WgHrczL-D`LaUsv2>v9Ql+SoR#jZaMQH6 z{2p0HLc4fKu|x!uMr%)uU`H*`&(*6nV$3@!OOEzdvN2J0I~+-OL6n&LkuHSQIfwyU z2LDBF8FAFL%z17srVXgdZh@y2OK>zNCR&?+ z!F74s;e$&lJo8EhzFn|=(~YPt&wf1e2=DLul5yL14+)AgVh=69p2h@{vb_}co-L}o z>P=~sK+NcGA>{$>DyS*AsTHK&%bkBSV;N37^ta~xqMdq z5PAKbzuAlafHwx^pON1>zgm30cDGN{x7zJjQ_kGP_;u&3BtoWjCUc%5XHu0S9}|)n znHH{aYEFqI=%ZPDlJPE|8`f&3=SSlE6_3li8hEV~?)o&RqW)I(zS82ypKVPv2>u|X zQ_1NxQf`k16s5-=THi9dQWS67Fow~r{@%C(>MmuXbTb%DFcJqkX$G&$8ibG}Jn{g0 zV!n%q%b%=O1OQOF#%Y4_POAvTa2f~rw{bZp&q{|{yqbST_uI`P+eWg-XE8_{f*@Ee z?BW#H`k?NX!Wb^`O|@T>j}W*QMmS&bIxWlM-iIFA`bGD?!)e zFIqYqUSVN&o%IgXtCCfl2n1zdlYu6*c~qR~kf(^4c-P_`^j3@;x^h-dKX??@RI8>G zvq3(cHhd>^ z-4O5}Nu>R$NyD=_+MP$D>#ZsF{jU()+lK9Q6DjQB8F0XOioVi^X4FY~&GAwn8ejQN z-;Oxmj>SIHm-O^0z3S7V^rceu=M72|RBBMawI@H^HMivvg4Sk!OpV5y+MEx{O=jfA zx*{S;PLUb>oq>Gq{a_j~kDAch;nAJM-hiS5p+*qMh*CENW>mrLDc${$3m#ab( zE^$y{1_c#yoKYVBYvy&l5oj=M_~OMi-LdOGp4q+r*n|IF*?~Hh2sxE7tK81Iu@Gs$ zEBVG;wteszJSq+XWpl%yl_jxqDH+nAj@C<8_-hn`$_5N6Hz&sH2dkt?SWe+)o)Hm5 z(mDA>u3AKXOEyAhrd5$uB4xbB2vlAa${F{X=yA%8S@0ymsdIPa@C?pdshhJfe=Hj? zOs2%35V=+PFd`sNyYkTj0>yH0bkL;NM9CIGa1O zBevLA@8$~gG8Ze~bz18eSLfG7O4LR1Lxrn{)*Wae8ao2%$YDLB98v8_oB{@JIwF#9 zpwsb=1Ni<8<$bB&YOS2kG<1@%z(wAm2-s6HXVOcTwdL!{0CvTN?Am15(k-rLJ%x-rFrRBJbWn(nZ`!$8{^44RE@M&*Q}sK znfF+pU28Zy*=5cLYZ;}w1rulrH7QB(>I7M zQK#$tRhO3mCKa^lCd%w4nS!RC4yNj~T6`wYsY0*Tx3RblWB+~U4W=2ltCC}62dlX6Lt z+~{A_TjM-31Ar|Oo`w|2zq0HpJn^sS#Cp_ZV7`&>_Xq!bi3Yb@UH3t=4i}c3K{pG| z7lfjNfAM4US<0Mxj$AHTbP7*AQPa-Ll-tVF*&4BdSS>AJU>lIh4{?DIWeNAviZlcV zdBnpg^?FCMb5n)Lj5w|2Rug|BT(1}Ow?;%`^D@21d=rQ)MYO1S&gOtqm#qfP8+O@jX={@U~_NqqIpQ$ zOYB8&o_3Yhr68_mGN8QtsG(xl1wS4wA0(#n;(2}WiBjx`>8Z;i#X1k{qvo?pEta1g zJT-^Uou+%8@l)+8)gtpp%6S|8P|6z~2WjUBDmqcb)Gl z4G)eJvyOb{+&+EB@L5UZJ=4g}0Ha^>mVYeX{O;aCMxi86E_KqpxE-%K0Kf*?6?a~S z7c_QXI+f3O%LdNY@!rMl+!l0yaN;cU^mVvval6Hp(W#p;?A+*elxobfm-8N(yJJPM zw!DFRJJvYQUjMN~5P}lSt&S6p|1ax?-^aj2$gE2JGl_3uQx%-e2tty$9uJEhc>w3_ zy$A+l))1^Z#2{&TM?^NFd?Xb_i1PIia_6^{gdEo{bSs9oROrqt+S?z$CqF`|VQt-lV0kSIQ;i-dfG2=Xr41ayR`qg&3&>jDBtfGdEQ1^_i`+c0l z0_gcvj7D6(H}HpFAZb-S?*O@Vx`s8o z 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 z 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
dW6On2mf<*pnogEA=!dn( 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%0AZ@Oy|9^mx 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

*Tugw|Rd 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%0DNcEc-Kv5=t#qYH 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 literal 0 HcmV?d00001 diff --git "a/data_object/samples/distributedNotepad/pictures/\351\246\226\351\241\265\345\261\225\347\244\272.jpg" "b/data_object/samples/distributedNotepad/pictures/\351\246\226\351\241\265\345\261\225\347\244\272.jpg" new file mode 100644 index 0000000000000000000000000000000000000000..f1c583f8db6359446cecec142587f958ee20ae16 GIT binary patch literal 39743 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 literal 0 HcmV?d00001 diff --git a/data_object/samples/distributedNotepad/settings.gradle b/data_object/samples/distributedNotepad/settings.gradle new file mode 100644 index 00000000..4773db73 --- /dev/null +++ b/data_object/samples/distributedNotepad/settings.gradle @@ -0,0 +1 @@ +include ':entry' -- Gitee

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φEdz2ppC+jqAA3TSU;~Rs2w5<30l8;p`} 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

h-Bt520oMdm}N_B8Y-PKF~ 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%0A-G>s0vIaho$-SwzJSmfJ@#uO1LB< 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 z zJJiEHOz%C^13kQ~%hE$VZ1486tUSyE_{sy#{_fJu((L@iM5Yc}z=5UK1dD@^BYPsP zf=!rX<)8jp3_EaxF2rLf(S^9^VbxkK)?Si7UdxZLhcUF>0glV3`2yFLEv5+T8L2I> zEe5ge-0aO>*(i*N4i~iEtp~e9HS~br`mNy-E(#AW1*|X&x9|$1@ZbLJ0aq0~|#!a!_wQv~2mREcaUPJbbVA zvaHMUG0RTsNrvJ!%0wW+@4!`M-og?VgGv4Rv<8Z zsmCD>utycJsR`sVEmkPuLCu(4cAX0Qgk#X{A~?K^95i)21IiWr|<;aXt{1PH^4aeEtCft ztMUO-LpAV%^7hnY{-!;(!#v!>98Yf@yF)z8Y&-xmJ;*~wyTkYHG4^7#Jb1K5WA8?X zbohF-%$D!?5^~v0O%^Je&es04rjFxG&;%nxGGZNPLs9b5GHr#)2B%WAZwjryF5100zS@EcLIJlmYHc zC||oth4u!vUxUsnK%2R4hhcwakqVfS@jkGO~v^Isdb24nzaL$(L> zxR3vM2Lw3>3^@k`c?Z0J48TAT{EKCr?aw^KXNNX&Bns87c2_|sbXKSDNUWkTgKX3G z*X~R*C^SR&l$Lk{nIhRXtiwFu1NBmGNB^V3Ixpen=_@ea8 zg5p-*S*CtB{-T>5r&ico+uU!o>CKrcT8fPQ9tAnT_f^a{C)+nVA#YTNMdfvr&hSix z2DoiwgI0fn==O&Mhc$xB*(_IsauJ1tuOvZ$X)aIrPgS_Gp&;Du(iN1z3SU4A12&EK zHH{~`vM)Qc)3}O1JG4VPV59hoyZE$Mz_K#{12nsczqpR~cn5rdxPL&nk2|>=Il3Qt zk}tWdJGr})UNuF7@jZjY2I`e-ClV4$bHFx1Ga;DUHpr1VPmO0UaKk#hF;XNAI?%(L z!+Ff|am?Czoj132SG>hLH=y(R#b^A*hqT4Fv>>B{QX#rFXhWiBL;Fxe+r1fWR>N%O zkPhMgy3Q?E{;W50vUf2dMgM>dCexvOA0ktW3-uW48Ao+L`eP~YOnaC**rM_;sQQaR z2Coh4=1d`jBiTj3ksbjR=zPZPP57mgX|J1NiFzbE1pBaGKm|~M1pGC&kGR^uJ+nUm z-9Lb`*FE0Ty#zcv-bcIN|2^QZ__L=twCjDcGe84qd$aTP253N!pL@DTzT|g6lE*+0 zOvc%whURZR=f}J8L2MT%%Dz`DFdj;47p0f``{UX6m`?*QsAg~({BUthN1QCfH+=Pq zv`YK&awGT4c0BOwdB*Q?@Oyl9UpJm7e>>nTc%PkC;%qid!>?w76f}VmGbw7qzGMDY zsch1wsl>c5Z=^;N*X`gunkq%I7tc-NK3q+hH+5 zfx-osFE3P(Fq39Yn>TUhtocB~1D+Lt1{FG#Xi=j?dCqLGltF{0IfHK4;K4%&t5>mR z)ha^X$KO*t8`>hAbI1X}hdoA$BbtMRn%LnbSCq9ld*}QQxaaPoBJaum0)LTRktH zJKMK$yNegEb?@GXLRl^d50+cadsbUkVE z<42DkJb2Kcv4TB&7A(}$;L+oUkS9-|V8ODBmoQ<(h>@n6j;^_e8*jYPs56Tg1gXIX z*U3o12`R+L!V4`lN5c&_#3&thxcTG?UwqkRnqq=M#+MY4DW;goCQC+}VlJrylv7Rt zMMoV&=~0x%C~NUW?~F-CGHkF3<`Z3bk;a-~1d?N*DXAo*AkETIh@y?S`Dmhv8f1r@ zX;K`g9g#p9Nu-h(Y$v6aT6#&Qm~^Vs&O2>7O3yv@tcig?n+o(M{-1!7sw%AzO|*lq zz8XU;Iv|Zy(n-^j^sKiGn+uw}^a`ghaH276uV};xC$7N?>l7Thn4zT<#vIcoGRZ2V zMi^a);YPvF$_b4fbH+hOw$x6mryk*elg(J#bjvL_-i|BIIAo=L4Y_N9Tkg4Sp|j0R zbj}GEJMFlmrbWz}00Ic|%;RB(cV}qtgn8#R!CrgkWiLMY=mVmO`=H3|4*vWLu%m4b zT#zC+8E#lh2p?o~Aq+XJ*h3ISB+*0^ReVuM7%9VsMjLV5kw+haoXkaJh@9rgY>HV@ z7;C(NvdWvS6v7H(ym>gC>bz7_O>TU-0-1z_PFkXfD$4Ty%{X0>3D2pi#);3XvBn8d zKyPxu0YaxbRMD}mVl*r{C`~) z)dm|QlR=W2YPhM0oX5$@&YW|A71mf{jb$xa+32CIIcIf)b~xpTv$k4mle^ZpY+I`> z^xb;nmO0!U26voo4libxE7B#eyz}4~FJLSN7N(Z~ zr4evI0t@WkzylFXkin29&amPRGs1X85~<&0xe#y~fAAfXAMwKii{BR73@FAWE>xTgFmQB5SsiW~)1XE+$g#9^h>LRBX$Uvm(K%(QCXI8n zV;z4(x;ml`rr5e89$_aO=g6a5w`o{(&`}O{Z)GHiLH zT%L@VDGMMl=Qqq^iUJwPROUdK{ux0ARYzeHn;1zRXU%I0Et?nm&^qp50tG}mPlZn?X18%-zgLqzAz~C^pgTk=qD7CcD6WtKtKPfl^1A5pl2(nK@UongnEjh zhp`Lbnn;+5Qq*ptx@bmUw9#aE^rNJah=#(24$xF*8{>$_Y?QT{HO?b8ojV=tW{TdL zE(fRUy(vz)6}#Q!#$n$Qs=0!J1t+KrsqRv0Q=LjLtYY;81s3pA0i56f7ns2x!0J{r zZ~+P^-~bp7 z7Pq;@%5Jmbl_2=`x4}i|TkQ5kQ2os=a%m#pmRr$>%7qp<0!HDwgCs6mS0W_sRdvQe zj_jCRA*EN5ASSB=G*LV5`!r!KF5J zflV+kQ7iHUr*^f2m!|;?SQrY|kVrbrm&%J}QtSEopYfQ4V>iX1u{cba^vp=sA*3j*NEnPI)T2Yt3$tqWc`C8+MLyY&yUD z@%w9I1Y0uNFa{{@cM4G$ zTSxtqjEh|cV{L5X*`k<6JKSLnE8xu9cRh05a(9DCS<2|{tGMDm_pSR^Qb#%;^Q`4G9x^*}2Bg1rlIAL^p zUB62zB3C`?q${2KA2`7xx9@7IjGro~K3?)v@Put>iRx4TP&TaYi%*0&gRtB>{km?y z8;zapKME4snQea7?%7TO8{kgv=uQaiu0hh_aTY{EFha=s401kX@Q%zwGGZYzYu%zo z@vtU_MrbGK?X(;Zo{$2b?2Ynh!k+d`oNB_~Hh}Z4Lg26h^bD?`MvwIH0US!D;qZdZ z)~r+->TV`39K@jqod_6K0Tp5|i}VbnaL?n;0bFp89L|CFisj@`4)}zxyofJbq$Ry( z{!Zp(PIdOEje06N{AfFpVfv2B`hqU|NUi(mu;|E-{Jsj-?u7(Opagh;7^0*7+>agL zZ$v&}7kIAz&dxCq=P~q8?8puj{BIFmq@x&-fW{#iMBx;&!8Wd8?yAIr7Vu#tWW&~B zS0r$0h%B2d(A_>=>VRLA9p19oCZ(qOKRoKFoI1|8UN`g{(lt}l4x@B|2~4iWMW$uDgF#&4@U zV0lX51Xy4fq660i5#0ze{^l^_~Hvn9vx&QO^o!8>}!~Hbe?Z z0}I`83)@i(5A7YnFbs*~jcBU*v}L`T10VUwTl(=IsZSt-PUw(Mzz~uS74i-lk}9;S zA&;j7R^SfY1R^fdL)PIMGA0vzfg@qbMOut9g5eUvP9#-K6oMgf=12a1P%=nVavE6D zCF8*xtRM(t5(@4>8hVC7BCyARrm@gXBYHAv&J8FjLMUlb1W^kDtn)gt69SHM7lpzo zl``IpQu3Vg-Xy@D)F~&Za`UV*D_Xz?Z%}P=uq%tG%?zV2rco^CrasM*FI46Ays=1R zkBhRQyXbOqJWfN#!5r}N9Se;M^HK{F?YtT-jsUYB%Ww=Y)C|+GAITvb+ORQy$G@;o z)E;RvOU(mJtuj4eDqKhd?$AY16s-2JGu?z7Eb<~dBSc2TH18@H6tP8u!AD)>MSh_Q zUNaJ9a~iZ^fNV1*$3YudavXHS8?c}tawb1`ra_L=CId}je*SVN%^{m`uQ>_tCr>dt zPk<$y?3=0+0yyAJ;nX_cG&||EPOWp^=+rwMPdw#`h5S?koWh3i#3!n9J+Y!ad+R;7 z(#+&@KPT?Ej_WTT>Ql(FJ|k5wfB_ZSGUJSdi?%^7IpjboXB@7u_h4fkfe%7e6&{H% zrcUQLGPHCCbJ5hRbz<()6f>wiG-d)44nr+6jmJbAto%MeMJ@BIU{oRZkOg$XO?E{P znR6U6X8vHtM>{eYe&H8_bY*^_M)nU$e?du^^dy~B8&Dw_q_j8hfRnByON|rPBuFD5 zhfO?Wt<+%($CONK?-fN;7Sl9NRIn#}!a6&k1q}9J{t-4|TL3E}pkW&pPC39%t(yiV1O8uUR|l~pA)Ii7aC%ApE@mHYEtzmcWCCp1D6Lpm2sNuly}!tVPQ80mUm@=%PgMv%;3T> zu<=u|5y0ve0~82Z*NKNU&B_h-WhU&RIFu5dPp!;Dh(R2?)`SM_}% z+4$`D_^@SG6O(q%A&_F`2|TT9rTZ$gSP2+e5%_>Jll;uj1ZDsbPq7r|7M8c9 zf_dSsh{;;vM}t?yZ;_!$t6+o&*JOS{MoM_=5Eq42G8=#aU0JvtUf6+np{z8_MlT|o zUJ=O}&>?IzhdH<0Joh-+VHbc{P8x5B&3Fs4Af45Do!Pma)tQOm8J*pEp4AzP?fIVZ z`HHuApZU3;UBD~MI4qdadHx}VZQgWsg;Jn6^PyLJn(( zDWkK)ZM<$8UfY*aI@NsJZHtyXrC**k1lnAtDHtp7YrSVgQS&n2PV&b&c4F&)^IWu6fz` z8KMEB%K;tgs~uDu{`bn4yCAxu!NDUbL-x7|kg{Pwxhs4hhr7ywg^RB*OKw<*1s{mz zxQivFQ@Uw^4nAVNN`_8{xyakw)@i1A}R;vX}s5AJPmyPvD0AKiM`m3J=hyN$Ms;@nZ4Pa{n?ql58k3L&Ai&#w|!NvFPob= z^vHEs7^dkLHrO%D$-Ugoeay@K9_(S=*?ry3e5BR)&E?F_X;ToP~4#og=gCGWC z_uu|w7tdF1&tKHfF_Wl|dZ>wY&`qGw4_!e*1B{}29TvUOA6>uocI?1<<3ZsPBoP%t zq2qPr5=b7?4LsQt{Fg!o)Q#aBzMUPs=oIBO(Jv%iHr&-;{ncH)CPkdY1sm66VAq8{ zv60=^kDbPiJ?WKx>63m9*1!#*-VNel>f;~~tp4f+A?vSx>#zPTto`f39{7-Z>@g>e z?AIPCw4~A9+}Xa|*WK;gJ?`ax?&+THxey-4t6SCqLGpbIjM(otn|1$P;Eyp;D3gFO zb8QuJ=}dqJ3_b5|IPz`uXU3s3iiXjxI=|m%zxmHb^osxZkK{$);|pBVQJykZX8ys6 z2^n0THrm1cVi^MUnrQ^F+&Vm5ip(Tx9bk(B=!gFI>AC2I-wcNR*O~tKkv|RAfDNLa z>Ye}jq5t^>!Ry1m`mLSpvES@(mHFPN9@_r9lqfk zx&adY01X_Qp0&V=H`|I~pr04k%L?o&0OEr{fgUs%^zgxhLWKhxns~^A*g9?$D_W%J zt>QR#ysqrhB`jO9V8whDLl(@}sZXF}Z00M^= zF|ydbi#M;{y?p!nwQDA@;K76o8z!74vEjsw8#{gsx$&AcY}{CmgE_P2&73pi!&fh~ z=+UH0n?5c2p6b=CTelWXFP?07wB5;$Coi{d+4Jgs{tY}haC^ds+dGc@_~+!zn>&9F zJ-R)3)T>i}_wL)bZjuBAvdm`kWXFT~mT`eTz54a)TeyG!zJvVv^ye?UkFcS`6A+1c zs|MgT0uD&vfZVLp(Js4$6bwlwC6kgbEP)crDaIIcN=q8vgcB&IB%=*a$shxiP(+P$ z6f#N0(-b-5ya?ks|yw;gwb#9^5mZ9EETqm8mbsd#IUSJ;*5#lUH&Fu(v`s4a95 zL4Eo)bV8~o@F2`J#1JD*tg%u9V1U;Ih@eNi6q8MZB^3jqFA{2q%220Np&=+YRZ)da z9)c($i9?x)4T?RDQ;v(-U_(u}-d00RHsX#;F1bc+W6`8Z?g82|FPu#U^uXP0KRt?1v!^ zC2deZO`{DqBlR>*HtMP?px^LrBY3+N8C7oL>kNxxu0%rf}(EGHm9<4@XRK>!)38amMbnvoUQS-(GU=f4+n{?d~ua5Ia4{vIMTcTdF_;}f6+a3s7!2W{Gc4t1C#9X7hrkb2}Jtym>1Ryh)m zu5uM1HPA*eiV-))fuPK7j&oG%0KhF*!5|SS%=!XpZH7kGlvxzA9$CCg8&=|k~AWWu# z4V}0nH?u>BIkMHjASLO7Q_+zHkA%TY{zC9b=jfy-&ykgNG)WjD9AW29n8H*NjD@3n zp(`bWx*5{&GxVq>4sTfx9b&9B^XQ=ufB3N>3NtxJEN1VNm_+R;Dh^JJ;_;H_GiOTk zQEuSF7VG0Qr)f`&WE93O&X`8>bqzq<(8U!Rq!T%sEkdAR)*XGK$4l@Le|`iJHhS`t z3l#$x{{z;xrZJ8?xdR>LDCh=hl%sMaw4gXDs6!QM!Aa(*NSA~pjS4!Xc92qEBZSMp zw4ln6=7p6ke5FZaDa$j}5FfQ{sV(Q^(w9z5rWJ$TY3LyjUzTO3zl_JqUe=tHxlE{n zGNw@th0J6sQxZ*_<}-ojv!AK{K@Cn-UNx`TMf!YTn^PkrFTM#rto1^TvYJM%K5@=q z#L*<{R7pFl5Q<;?M33`qR@`)w$N}z?pZ}|tHj)w0*`T9FGCJr*AL`Hs8k8IsRct~x zxL6mxh(_sP$41rCQO?Z;l_LF%^!h?lR+{vcC`~D6RvOdPs=D zm`_=D)ZR%ax4ccN61BrbW>&R$&>U_QugXO)HYAKw>j4(N3Ql44Vys+?m07PbjZCC9 z8Q4(g``pT`xEA9VZ>=lbp0$m<{$#H(`QJ3Q@z;PdazxI3w|wTihBPwqMqv;oC2~AlhL|M^eja&` zdz9yS<#x{j0)wwGNd|lYs}zmVcSe)7k$(HjhNTqlv!7pjVGL`S!yPU(?R*O2M4#*(@G!A&t%Dt;I{LRLW~y-8 z@P;?6SZI&>*<{%8;^SI#R8JGgHfwz2Goo?2;2{0NoWnjS?suef>+gUI9GSqabQ3e3 zX=zUJd6Wq?6l44}>`CneEBHaGqs!{*O2e$x5QZ*xkqlv|_13u#mQ1QJg-e8jCdq0! zhd?2UU!39>RK7K`fq{%=k3%UqMaKuH4fHWz``X+*tH9lA6U;z3Ls0^J=>A#WaNB3nJqh8MZ!!D5emFDQI5vmuR`? zXF`*h^VR0Tm@@l9R1TiY;~d%GMr4By=E81p=pCfUxwvt3ZLeABOn2VXe-HfMPlgY~ zFMjd$fc)g^ArH)FJ|3Qr2Xa3~JSW|H*Y_Ou?6Y?4WM_@p%W)2SqvMmE)bKhOeI2^@ z+nDNa{}Szv;=Jo!(^VB3Wk3!1oC&_vTQ$K8f^cJCm3XaYKGkqF3-Wjk5_yw{4Zv^; zp|AuIxCBK|1V#{no0oE(_jxckb0lGN$;Lm~pbe=v5A-k(=&%j42YUxadn#ycEjSMT zMQ0TC;tgk!7HT#^{?YLl4jDEm;eZYj%2!+$gghmJmQ)vE$+NcL; zfCeMwZIv=6ey48N7YAZE59yeWWVl-DSc18fhN~fO?!Z`W2#5Jq4(VWrKILeD=#K^G zXp4qSZ|8?6=8uB7D3*a~^JiR8rHK01h)!by%7rRm1b|-!R$@ho1$Z^#BMo0QHj_6E zy#@-OD2f$GilA5o7l?sN5Cv3F1)|`Ax5iI6rwt-Vf+pAww0K`I=z=~egM=e4=)(&% zaDz$L0>IdG4p)rEs0V!T2Y9drT0jTShX#1CjD4^NTPc-ifCqcru|+54fFk4I zNe=ZQ4wVkx;0@oPRR2hsd#HYiMt6EBJeUC0h+RK}7c=xhRy` za1Av`0~U}LNU4 zve_Ud=L<4nl1P9ACyAR9Xo^$to4?>IH@8p5aFf`OlM#xX4dj!zXlCi6i`zn!G;jgc zxqC_3aL%NiLZ}B)i3W6_1!ph@WlEk}Nv2du2Xt@-XRrlcNuK(72HuDU@Ts2nsi%FA z2i@q6{RyaLUlQV<1Fa7Uz9dQ)cp4A`Iz#A$+T=z_@Uli%u`?lP3= zP?TAkSrx!$Jji!n3VcHdePqfhRJoPpxd&zHjDJ9$^SK9f@Sg6e1z$O@Td9>`X_aW; zmVwHjz!;#P1~gb?j-Me92bw1l%cv4Nu??z*W!MfWI9Z8hX5L^8k(r@*N3tc0cbiI? z|2AnH8j$T*X^#*HdJqR_z@KP<8G^v7g;Z78lk|-I0(JBQNSOup*t*uxL*s84>V-DR)i{IL1v`3}tGOoga zr8u}baWNO_>ZQ=cu0j*0cj}#Iss&>(26X;Fuin|FgA2G^`KECCo@ENK^Ln2Jd$0%_ zms#L#3oA4Z8;1BWv6-v6o12ak3q-w$$7ql4BXiV@ZoEZKSvvFHxx(5=-brD)rN@e3n|!?r`| z4Q{JBar*!Swu4NEX|Kv@QFXVJvV2+)17-TAXPUtDYM$tcrtA5gQwg8mi3a(qm3r!@ zj|;hQIk^EEG*`qKaF9};F&i8MME)s!JG=2QcJingQ%mZYCw&eYFt~79m2$e3 zZ;A)t`KD-!uZ(-2c?-oI{K16^G?_-iB5Jv+^BKRS%e&lDf%%|Ip~86*59BZnl+!JY zgAL%~H~=yX!ypOV5ya6f{>{@o%|mR>vrB2&ti%9GnRxdI?AgI(z;(ZC#pP_y!K*V_ zEJi1Qyj`@;GXTcT`@CdaiPwN2WkhSj!p5TD##0N&%le7Cxdc=|1yMkXR1kB>cD_}{ zIDovZ=K!Tn^K&D?=r}TQt;F-WJy~?aipROFvaT&`|Cp5HN%gl7ktAh`QhCpS(c1x|)JXITo z<__=ZQuDwfDtsI6unoyjIb2;nk&_I`(3;I`2af>6hk(|HFbHeS)@|+9Zk^WDEZ5OY z#Q!*%*bIo>012`Fdyshd2d@mMS&*vdOxT5ua65CS#mijUBXOw$&u1jY)TME_5;n9Z z4f;&1{Jgyps07N|y>Fb7QBVa1eb5QL$H`V@h;$ASYl3GN(LGtnFc_5LDlW*Z(UOz2 z+`o_<7b9KCDC&^NGr%q;+`^~Q?U}$hO$Yc2pX~|C`iZCbddd_$m0J+7JdK}p z`qLgvxj{qJtSZ8?^cna_spoBv<41n=HhzfJ4)PGgGR(`f({bWs-y)}xc2Ea=FbFtn zj+`L~0WRPH{?>492#*lEL>#i;AhMa-&3Fe1d#%@y(7J+H2Y}t5S-=K{&Ee;a*jk|2 z%oRoh5jFl_4B7KcfBM)yPW$K+c7xXx`@n8{y3B~4PhPDmBZ0ky4!Cd0a@O^Ar0J1iPArVGXwmC*m*O- zm)sPL(`XQt{|cT}smWV#!Ch&v3S6h^xd(f?-MZV|g8JRF{FzuZq9u$E;vf$2A`UNd z=r6JjgbpJGWoDGhF0g2VL>>=MO()%Nk5C8QEx8>I>{a~XAui%tyj2tz&(6DQ^sM6R zp#BRjE)3ED<2!mGq96*JP2-+Tfw#%EpfJ8YZrU{2$J|v8tXvx1Mnh z-RlYG@`>kss-HBw=Rr-5Q}pK~9O&eY57|oSC2#VKJ`N;b@-SlZ9b^>Qnyow6+MP>i zOdU#4p<(Pm3|OEGcYp;y5A;_c^jKg8P(TK9AjKm(qMvESO7HYf@AQ&_8G-Q3knjgq zPo7pE>#Y6r(Vg7sUMYaZqryfpBKNuzRvN4I@Fds-ZS(W zClCB6AN;|8y6}}Z)e;XlMX{Bxc3W{3>rjU~@AFnb{ncOnP=Nhp5U1iz&T1d*T!-L{ z(2QG&uU*ged$8BAKI`Y7uM$4QKm6Cej`rT~{cI27Z(r=l9>y^$qjpaYcn=V~gh?y5 z%a(URHC{9>NT^WN>I1AFg9i4dXOCkzV~D^Q?VVV)eylP6GwjL~u?&}Yw^wZGxs-TQa&;l;a&^Qc=SNa^E3 z`U9!k`}dIc$lc@KetJlZ=A(}rNs@m_0u)fdN6={Oz{Rp)Lp=Tl8+7nN?%;3(4=rGL z;X({E#PEa_gy4mjU3L*jm|l7z=0s^yOtBiAu((1XX@oHdA!LeS$Rdd(qR1kQG~&pk zC_oa4q+);x1{frZQHdp&V3NtEnR3#pC!T_`@=7d&GO8#nk)r0QsHU>&DhJAR-~k9C z=!%c9+H~_PwBS^0PO>1=qb)uL)03_}{?b#hya4laPd@-F?6AZTT?Bec?3J8e_aP~+4!abTNCHf)kHbsTwanyJv2@uuzS8bl2cNx+yhkK~@BX~=BDvPuZqj3f5lFOsFA{Ru z+k=tw7y%?)a@%!Rl1B(nd>7(7C^U*u;-1BOz>&iN7KML)m>ci+g z0}VbNg;cG}vg3?d&oEt0(@k58yEWH#8b>y5OkD}pc~YIHZ&zVe$DDF%rS;ZanR{Yb zU>9$qiD8L7quK#0AMg;&4;kcGW!Vl(LJ8jt{;RwI-33sMfu7`bGVk3`8BWTq*nNqS-$=VdB)0?H?v4iI>l7rn^CJ2*l}LTHGA41y=T z4b1=w9|F!V*^PPAgnt3n4QW7;@24GkjZVZm207swPxB(Z)28p^{Ge z4T$p)A`yX8E1HlaiO+dr5F%&KAOryj^^Bsn(0ICQoyUp;C8#8_h^*OF>!1HQQ7?L{wF%R^!C@K!yvRho09>zyz-v}AQoo0-9sIK$b^ zzrKx}$v9^+)Td6rwUcm!gXcKpxej40uAcd%tUiH2*%;6vTWAbuKqU%0hN5wxpCxT+ zKbz1;`~fkIISd~c!ANw{Kwg6wyJG1f50c1WEE0Ph zqX-gqMAJy>6sMKkDgH@%dcI_+EU4}nDp6hLvZRLBlrh^MHCQCo3yjGs0<3^mB9Opa za#gJJrSE*rdf)r@Nx%E$?;i4y2f(;RPj>YK1Q)Rg)W+)&hA>1S3Zcc#p>~Gh1QlQ_ ze40)X7M%wy9+!@Nonv1|0>?LQ7qEvt?ARr^$3PacAv8ziHf_cY zn>liWF2kL#Y(dGj{nwLs%g5d#Pho-N#jbWC3}VR0AX`qZC>Z%=FpGK24!NnBqkv|g zmSoMFbTgdgA?Hxixz2YcIR30u-t+!Xy|J_^R87*my_X*O0u{5R` zq3L*i{J;oCt*7xpH&9zxHKaCm-p-jmRePAbtsXIn#}R8`%$n9IzIBT=yKBz|JJ`Vf zHL}V6W$a@UJJ|^h1Rw4ch(b79bAk|^v!r!(YV&S8+1|Dtahuz3gIgft1|+%7jc#@O zg56z)H<;&bk$Q{o%)iLDA@|+pH|OTxvix#2rI@l zu7hxET*p8QbJ#4=P4{A<>p1B(PCC+yKm4OJg7c3!a3UJ<{DbBdS(*6?cB+o`XSLxn zZQ%aZx&8vLe;w=uGv2|-j#7s00t#3VNV-F$b}qvddu^Y{+Z|HoO5!`;YaU54va9}j z?jj|>gCD>XFTq=-J#!_6Te!q?xLPW-#*4tOdc6ILJjt6p46L}i=(t6QC~|oOcfk+L zOQ=kX1kd|CZ=x`57`?dBuw>ANyz86QE1a5ZEL_=z73%@q>j56rK_1K}-NS=y!Li^g zLen|E<3l4PT*6}$wi^Q%NEo(8V1(zZLI#sM2Fn~nfH2OQosy2yaq&*2b4ffT(qsxKnvWH3=G9d)1^zx4>WR}+5Qnh*EvBH z3_aEWhiGWQ!qSFz@V8X?w-P%dT5&9P@P<3MwH~~^9vsFX2*UF)IwK@4-#a=cL_#O@ zy=II?9Ag9~tU_zN#zsg4ZFIgyP=qUVHZJT!>6LKEW7c@#mRSXwa3^j0~Fj$mD zm7qmijI$EUMVoW1cJKx)7)GE3%3{=lJvhc@Oh$JhLS~f4B`h|ke9GgS!v1gRKu7qN zsr;60%*t(41g>nbE5xD&3r8*-M`S?+Iw-?5OiOn}!zW`yK9a}t3nXd40(?Z6edNcr zo1}o$5qX=pD7XZJM94whH-_{nhlI0uSj140$cdy#rMeP?qeMSDya#A6py@~m{76k4 zNzOD$QOt`=>%f&nt(G*wlLI}t=_Zr|6<8!a(|EZTtVO+BH92!Eos6em{K=r)%^-jQ zqBKe-M9QUvO5!X|X{yf|LUMJTp=F{)L4kOeHA?ICBTZ z6r}+?D#siz$Q-^!EIy}1#-c+u;|x-$WKJSw&TVwg zM0ifof#K02l7$fbVq2-0}eN>@9?$M}{GWE2vBN`-ENa$K=vW>`?-~ zw7-o3O#D2^d)v?cT*yrVP#6_Zb0AR0Y|I0-AB#*tjI4kSIJ~P!6U>~@&FsvO%uvtV z&`a~s%tNgY^p6>G%@U29(f|k22!~gs$=LL_Rf*9Vol$oFxCSV=(V&C@-|Pe6hGzL3um3GL}#VpXj>r+2XPy{qD$sANd zwM;dUP()?C3QZ^XiCx^qnz$8fI8{^~ zMpe~MT}Bkb@VvQY`%d zW^F@fjmtQc);ll}86net%vSl_)_C*7C8z>}4A=fDkW+F!36e0^RY}Zr6{R^VP&BJ3nOFH$BLZ7+3x~L|~v= zc4`OK(^GaOz^yenEm2Tc;sFg1)Oxko%re^uMBB7gTMYbHQ;e4nblYnAG*Ls?)l`kS zK{ah~2i5zVy?wQbU59EAf*F`!!5!RT6#iU0@=e5T%A@=z$Q4rNoJOl0S;?K;%C%f= z#9UtmU10UxB@JCj7z5I6#}6>wW=&o6(-kXlUD$2U_S`aG_?{|Qu8WAIj8Iy@oPsIH zPbvrn;N?H-B3=Q#+I3Cd<&B>|l~-YaUI(3CL4yH0u-?YQ-iy=T&x{KW^w4&3TLGCN zPE|py&$kKkNfAB7#IbKK=L&H^@4ZA?xC zPT)9B(v>~Z%)R54omocs0|}nsck}=~5m?;_nsMCKin+R>2gk zTf40x7tDq#mIuFapDdPGn)ur;_Tu?1TpsY^{M7<77SdQHw&HtSTWwnaAm znc;E}KvME#13b9M^q=S*WlSt(Q1OF|ZGjHREWkW|w~Hm)_0a&TZ>Hc6xY7##fN>`0 zdGyjNYu$8P=h+QmKvZG=5`HA5UTW{To_Pit@44sTEeU-NqIN)sp(4fTCde% zQLa}91met0==!QqhGu9-_2zFeok$%JOwm**wq;Kt*eO1zjke<3@MtL6Vq*9Kk}m0! z7UPtT1ZXDCBP>psZfwWCRh!Oco3`ocELJ|&>77>A+?oL^=%aEL>U2Js_GD+I9$^)J zyQXI9?^)sHn(AM$2Ud$|9#5G zZg2PgmdK9d$)^5nuJqfYKw~M4D zNZ}<|;U$oQL457k4qn-&?Q^(o1I=xu0_#xr?J)uFgBEV$j<`=e?o_rs=Tz?HPO!=e zjc|wtPBmB?H&}6CITZy5Qj3Oz-68GHhIr`iRN*ei>f84fhVnM=!8Yvl4&e2E=J$r_ zIHuIc8)3$N?cN64xDD?Xs=t0;UN~kZCut`~>@|+^X`XVIp5r#YaxBmCZ6<^%z3=?? z^0n;g{`PDD2kj@=;MWBa1Aoso-@|yT&$crHri}tO-kNfzKMrX zH}b#6iE+3UUP$#Ohk-L77ib>f|6N>IZ|pffS;)3^ZKh+3XIVuc1di8_7>Ix_&+J~` zZ1w}Upe|2_;e}z}g>*A^yi{`$R`wie^LS%gFRLk=0tJLXh!;i)zLB$~>bq^{+CP`X z{%*X zd2k1J;AMfoiJV9WZ`cKdS9OJFcmaOG#9r@+f9Z*5&WxXIjQ{&x-T02*gBZwfoaOJ5 z7xMrox0P>sG)MN;mU)cHn3@lT68^G{c~dF?h4*~n+L#A)$cAa4hHMb}p(py?cB-RK z`pI1Sg(LU)f(xjR`gEUqe`R;8$NE@~hOHL|76g81@NsAu{??rL8~1c$Kp%f^6?gFN zw!eu`QU_n0dt{b@7+82#ugb@D_$aqZz=M&zk&@bb}ZSleVPJ7w1^R-NNyuZiYs?6U3~bW zK?4^K95`{}z~RLUEgE3Gd-)Ic~ zrDe#7amMXhM{FIzjxBpO?b^24vTdXrH}BqT7Xc40Jh<=Tj1nPFu3Y{(@8pP{Lysdth+rApV2+^5s9EpMizfE?(}13F}2{J~3(brB$oO>xq?J#DuY@ zi-5ia;|nODOtHj50wK|$gANu%P!*ymsGvheC0glTAq@m1L4j zE3&APOfS}C6HYqubiq##1T_>xZ;Riu$+A(^C-WS#X#T5GlS7F|=$ zWmhzWi4#sZerXdJG|v#mSC)GnmKbA=Jr>zG@=#_OX66WN*y$#nWam6hcsic!$mz|}SR(k2B-2Qck2MiH}*8zFvo%ewU z>aph@FYw7n-+lP$$De<~?6Ql1y6jTmfe9+e;DZqYJ7I!REKzI}#~wT3L>*f6VLaoE zSmKE(p4g(bExtHYj5p=9lLj{WgrklR6m?WmOAVQ>x>6b0ZdOae8?RYtsm0b?a8XI6 zm4qGUS2$M+rWapZYN_SG+VFLzW5%e-W@VSf181C@af1?_*9ft~3VE8gCmoS60_dWK zk}O+qi7LA2Z;m<&DWxwz#R{?L+WqDj=Mc&*zneFPHR*={?d zx8MRLu2AIi=%iBX0uH#lgZCk?;fG)4B%qK)=~lk_;!{|ee}##cz|m|uX2Ibg435H( zMOF@*mO%_BHxpY-r!_&W@IuF^otE0ij6gYZ$%gudvTl<0mI%wk8AoY!Fgr){ayAd2 zDR#U^&z&7Uqz--@fQpj1(8LJ$^YzSJ0A5=w$$hfM`2 z8!{UVH)ki4Hi~Rd`?j?(jbt0w7$A&n!Yvy0`)_v z8T?RoAX6D`Zda(>=`M-HgPF>f_%iFn40(ufq9UrO2f>-BDpZe<9zAAjJ3V0M79;-0FWm%*c_lpNP9Fq)e?2mt* z@gIv~at&@=LmIshMt-IdBWzG3lho)IPjr;G29}Es5*%f^C}_$HqB1N^5*Y^_=fS!} z?n;R%lL)VLLKKcLVQ6BQF+>ML8D7S8HoPI7su2Vp_Rxnx=)12(kW%Erv=|Ui{(*!dR;@7R^;iixm{oxJEVx;}@a` z1%h~GwK{qYep6^)90z)dgC4XBm-r*uf>DfZT&9r7IAkIf$;kgb@*2Is0u-PCAS_UF zCYs!&MmssK2J#ICq9i3tr{V`cn3962oM~59xj|RHQZC&9p)3>QQ!&|cOt>`DE*T~| zdBl)(rmG^5# z+Ifd}W>0%&+=UaK@I79HPZtEzCqKbf$FNx*w# z;nnXd&zno!=JBLh%_`QcI@{41I@ek%QF*|va9ve=?N{r3+;u^fLf-qiUK$4C& zfu%)hC{^p?Kd`p7?s~1^@L=QE?txxydBkmT8yDSzgSS5&48Zup+bs=@mV{Yh=#U#j zV4{PDovD~}rNM*f{z`Ycd)k9lZKvJYaCfWV6=ycfOP%qG)yy^nZ}SL&i1wOCdek$O zTi*&#&C1h0`N8j9^V;A4A~e8YD=?u6onTNLl);7tg(y1oiwHaPe(u;tF@j-X3|C9T zF5!kWpl}CofY=+vAhEVWt5F`PcyBE3+lx~hV;LWb#__tbSI(lrNbuOlKekIagzVGG z4J@c)$`T2g+(IX}1DNNS*dlQ#jTuB&%RYob47gl7FMl~UVV>rg)7#B7t699^mKmGX zO9(|U0?y}YE1lhmJ^JE#d-ruhJ@xD70*OKth(4&G4}6Kh9uyP&w!}hDvF`&Re4&hX zbc8$fA$Mf{qd>)=2BzDlX_jyr3cL_?W;rg5Yt+QlDfXmkTioiFz8cnBk@c)^oQFNw z8c?|Iv5$58ue{Lmr+W!5nTXAoD;fJ7$X2$TsBE!x5Mu@>i0X8yZEfskH{06AO(MF> z?Macl+gjf`a>A(`^oCav&*9Em=+J_6b`fYN2*O*j2cP=p8CGOd!%@TI1p~!No<(UI zH&_ENWY%WgK`%T37Dxlgh1@1_gSU{}=#kzTnBM7`;E$Z#OTiTDVTJ3VQq1ib&EeMV znb2WuPMP4K4VDRSok=vLNiv|ro1g>4^^kk#td8A4CFkVB7Vv z#iVGTSACnfeP0)X(-nQ;_(ey$onJbs-}-$(5V+r+#h={Civ8V+DD+n-?2};ySpI`y z!W)@FCcQCRshkU55DYRx?bV!Jj8O005@O)sPyJl*p$TMQLlDN$Ih;cg z7F|l5P7Njf_=TTyfYWp=f;N_6sDvY_grf(Tz&OGa+{v5#(H|V%UEUc;JMtU<1)Mz2 zV?Bz(Jr-a-4q`tRVr2v*fg}S#GUCOBgg1DDB=JHoY#?QIpa)9iih+_*fZ|kKq>qrI z3bJ4ea-=G?-dALh$b6*j@l*-P;?JFAPo1P<*rGNp3`@GCJ0xFBYQ_<&1UHbwI`G0U zCR1s=fDG`YPo7majbRaG+cPdlG;&{3X2(*(OjAb67mk%wY9lxPa)4D{WqD*}`@J9h z#oakVK|0{!WgtT$-eo~XmZlAmHw=(9SOct3 zR_B?Tw@AQ5;z&h?;$r?y>N)0PLS|%ACRe~*Wzrri<`j_i6fH{5uMG@n7T0KM!)3Tb zIc&pb#N=kQ1Z%qH2_TbH$tG>~ zf(mnfqjQGi2?)W^(3e*pjr{3?6ZD5zuHz}>n|A(NfN|$L>SG_Gf+#55;T_0>RRjYz zAbQH7FCc^3u&2dc)FZ)XFWi9@sOcRLLrx4PCuKl>8Ycb&@uy4&Qp#v@beE@IM=hy}g=X;vuCRIVbv%wT2`*JdUeEh?%kF-D^z17pF- zN5sjB@FfTRMbxNYCnSzU6#>Z_{7 zRFVNVnuo38Dv+uQ(eP@K!U`4uD=#Q%A~GpH+96uT<9H@ovpVbHokB4H0|WZPw7Nrj zR;!8NC8kB-dtQS=)?YOU17`68CXN(I?L?gBr@Dp7RndjMY?S0!QNzYLIoNz!-*>WJ!biUM>M$mw*n!j)OKtDu-g+HZ<(R9wWy{?56%n zBG@Psd93aj$H#)K@QkcdmaOKQD&?lEHLffqi~+5dN3CMzj|SvjTOvVsC8930VNDZ(9XF+ASm{xPl7`oelfErw0)BME~L+(9*v!%bF$nkGXv zm@Ax0fCP-5x{_@Rkl@*3G$`z>HEuwn;nHUVg*pdzU zMJ+CdHb4U}A?!A2gTgBA@^Ncud;kwQ;SU|-4!wZnPEqA%?B!xEse-N&g)EtME*E~+ z1(zzyj&2yPEFhe2IxXkVgyROhfX3Nb_Q0Q(G~;o z7Vk8~kkbakdSYv~`VS-x&>aYYG>pRlF&`E@fiS!&jeu?Uj_rQ($hnBGRFto3ov#Y6 z;3>gv1p%sozHh#+1y11>2>q7+?%nTR2$}xw+?Vtg=okZIp@RSqaE6XUhaNB#Xn+Dw zm2B=11c%`g1n+I1Na$M}!dD{kWod=(6*M;yGj&KR5F4~Dl3a>CPwD9XrXNTpi zG}JB)=k5*T@c%6oD)2Br4Im&6@1s4Q5Vu1ysOR!Ztz?A+W$nTfI5A~;!(~YWXn~gY za<8~}uNH%^+1{xak0OAIu|~Gb`m$iX!f%x5nk2Y!3H_FXKEpHY9$?_bGvtLffX=~M z20CPeHqgXm!KWY-G6R!_13$190D>bRLLw8-B3E#zS~4{?<;8kkIBhUky$p@^Cgu(_ zab~h60|yDCu6UfVd1U^j3bU}=klh@GxOL2MMaA5e?r9<=V>*MF&V>)8OyC? z2I?BK@hiD;Z`pB?nKNK~iJ^M+myk0!tTRNJX6Io8;;sm{V$2{n;XUJXK0i|gKc(eP zQzKvSBUc^RQSKxI=cI&T=(6g@YOE#qbwo#{T2QnJdvXV4Whled+i8!@R)aKL0k8@K zDqkmo?34Z(Sd_kF!2ND4BU~V!0x%SVEhlfX$#fCZ^yJwzWsRB@5JP<4fu{kcB_*>^ z6ZHaRF}p(ZyZ%N6Q@@x~yDd~>v-{SXa;I@x)XP<4^=^SlkfAegg^90Ow@IEen5Z)g zne{n*)-+6mT6ZYpwso|@wF8qVB=EE7b}%Db@Iym1UxQ&nml-C5Q>#`TVFUCu1;t#(M6gCgEa_&TOZ>O#dYLr0c>d9UB~xB0^E}x z7K80@J=(D0oq|IZNVT>DJIFMcis^`xDT{3Dds;&;h%$Y>hev?cG|2ce=SXRBK-qHf zjf-NA>-diI_*BCjWomOD_<`c6h5Rn48>2I4awfpYac^n&I6t{D0E1*$#^GW^m0$Vt zG4ASc`DlE(dc(jLJkyxNd11@9ch$8OZJ{O)&#N9~iqb|v$2rQ(IgXOBp1XUfEawJz zK!SIrFuX8+NP`tP_%7T5gzJ?)C0K(sSfV@Fq3`hEH5lSax-BCEI#~LbTC1jKn%hwR z)&avOU&5zgmU<)wL2r}XCx(oxFD9(BOH5f1bI&@iJ4qny`jF>Kk^j0$n)9%OwK*Gm zcPINeEEknq`HECbmT!46sB9wceym`tfeWE;lo&UiY z!27#fGzZ8#z0<6{d$biGX=dv}zY{vZJE_1|__EsYDVzerCz`^w5r?n#!#`dzWV*yN zQpLlke9q@JoWwPBElx-S5P1CF95s(Hg2<0N$rFjm_bFAYJRq=q{JMO;@LDv){AV_K zZ;dmNA$gNO12S9*WF&hz#Lza-M3o~Fi)48aaJiQceQFrJwxfI1KXmwsdpG_){nZJj zZJ-RdtNXfR{TQHMp6hw)Zh%I+?%RoduU0nMySOx*y}uWFz#}?$5*$1V-X*}YqYvIb zE_{c7croY| z^*$LocEvvV@>ML`IBxs-_xJz*Kd%HF@S1D9spbwV?hu9-EU@5ZnrpD3hC*tpxkj2G zGLS%m2Ry6*L=ZiE0D=fwpu?ntR9tZ&h+KRT#y>2YQKFD+yb;HZCMxLN$j88r3BqX%aN?VNs4_0ffgV$z|jW%9x!%er| zd;?B6;*3L1Ip$6Y#X0Gyqs|iSw&Sk6^ThKmRaVnmuf1RFL*|_M>boyL0CnA!zb}2| zra*6afr1wU4>V4gYqqSA8VrHRfPoGl2$5P3OKd@o6j|K%ActCv5nP36q%p^G&DBw& zgaAotC6-iSFXGvW(E() zG7C+#T5zE)M%<)}&yGD7Oi;%N8F}Q836txu&OZMB*kqCKM1&F2OjFH+nptDiQAlZ< zRJYxF`z^TPiaRd3`hNK|)JvvVV$@SlMb$j-P(g*fsMGTf)?n=Oh1PTEvkyQ0cpbaH zhQ9=sn`*4Uq8e|w@#Y#~WW#3J3NifQ0uD!{wt^9_9YbAi1y@ns!ovl3T#L?KJjaRD zO=%I9fRqH@A!DLxr+PD&^4_3wp#|V6i-M*oaGC-JnE2wM3Y%%LVz}YAA^u=uGb=vp zVlCL*80D3H->hXtNFKgqljBlkPRD+aGkC&68#y$XWnQi3ni+L%gBc#&W#`*^wiM`0 zhvw91Uql(tyXmU41QSz2;ST=tqMrJ^@4)^8kJVp*(R$W$%%M*dxDp%L$YwSHabqhO zg2ElFQJ@0#;tIQR;6IT(B%!qO3qKHW#*RGaS z4qh;!2~RXfx@Y(#DfoiMF+dj-(cwgM1VbG*+7S~-R^h? zm*rhCFTo?;7D?ur_-|{$OJO7fy!n{ z18=LGRx7Wi1rN${a3-mxEmv4VbfIvU95G=p|8ST^M9y8pl*y4ux0Im>B`Kq01~ZA0 zuV+Fdn$s)?5?zPQ*lClS{@(oN6vtUbcbfBe=uGGE)|rTQ&XX`>EKE7gIGB18YK?o) zC-rQ0Jvjn&d)*U8L30B>X+R-UeI(jK?cOJG(>(gkx#V+RKW>#s4?oLA8whr zq$)LW32`b^UkJmg7PG28SrSu{q6{GBq9_5Q5>deV6k}+^9q|~e!p!!#^(`g5cd@9KKbbZ!yY!E+^Y>i^T?auJmCp^ zR47B$W4}>R2cqx(LzVp)8Cub5w6yjK25RXe(vr4zY$@H!en1H?=(bx5 zT_{7)`Yqpt+XCVm_k+ky?u(Y|2j(_cm*;XWQ>P0|VGi>pn0v-Cj=>Wu`H)}7C<-&U z9NsDQW^yTee@i(utkQQ=Z4*d6xqF6^)@u zFe4VMnhm_sur-^(9e5$(fiAYfm%{KB5}FB4EvGphj?Rat08#F2hoYA#F-D_<;*PF3 zwJyenNnz|C8FwWc1p-zzh=J(>C5%}S>TLstT&=!A00Kt-DU!Lhrlm%S_{qKG-n&J0*jNVjHS7-JjheY5M-?4`4DV;ysj?^*1O=Rgm}O?$qg z$^6{K&dlX8jp(ah`ed6wwFBR#>jGJ2LKTWoHGpt|2J|h1B06@mkId}j ze#_Z0f;P2rSuUpg@<;A+iFLJ2x-MUp+&+W+_COyAfvqldRx5H z8s86eV7~U=Yrgf(XF>Iwzh)G8^Y(sdK^Yv4j7T_xt7lk#PP)>HWvtIEOpJ>65jn{T zVE%J9K5C|cSURPG8i_~F>Q)Of7%0CFi&Yy8m%sdzF&9vQq@kN$*rw9K$V$$svGbn) zd_=$nI*CeDbfa%eTS*7k(wS~TUK-URkGuBdEGI8~(9m8$$@-F-(Jy{sH7QJ?m)OS< zkFs0k>}fxsH`c+6w$~Z%7{l}2>0bBzh79k7ICS4zMt{G1#^57^+29dnz3UzR4t62p zD9oT5@A2*+0FwjMG%jhDrfD({e$pd4R?YLC;}k-Ve~N({$N_*ziW+FGwN%e5YU&z@ z0Sbfw1%;pqR`3aCY8sfOS$NLpc2D<&D*}A)_f{nMgbzlDulObcM}9DHmhWAf{_k?I z%bAp<+xVsW&P@8GFDSUr`>LXK(9Ye|&Krc~1n4X+PQVM_4(?(A4G!aEc*p&Ghu`pS z{_c+rA0z+n1QO^#;rg%P91frmP{Jl<@xaINo`BPc0SgkaQz-AElx9>kD?8xlekQPy zq~nn+5Cc_g8^)mngW&^FFZD!li9m@Mc0m^|4UeRO$9yc6sv!twF!zkjm6WZxaD!nWFVsr@$awTP!1W4pB~HuK7a@Q@KEsz&^IW~ z3H~q;J#i36ssRtt;}FSIC{VN@=>pqB#a61t*5eXUPXtwJ8}`G2;HKAJ3Y2P!_HGJq za4+ck=Ga~d5nM5-V$nrr@dtOQMml1JSSaeIE|{)tCZ10zaKb2nkzc}VUwmi@nZX#0 z(Y(4(OQwO{kYO3U;%%C78qwqnrx6UTaS5NvPjU%(J}H* z5nv`APXkdRtsQ#+9zl_@EX6m7!3pXSH||jZGY(X+!vWVPvpx=^MD9FJj8z7be@t&1 z4$_oB@T3-UD>iYIFvLMFq#CLr=W@n^55bDDg*4DI^!I_ zQSbK5WD*UH8Ur~=<}euHD^X)n9PZJ`G5{xRvCfhYoxl&dewKkoP7t5CUOAfv-Uq0zwb<$((CKL83yR>O!rHCYGdL zJhWb%NkhSH%W^@?gi%sO^xR;x9b_{ZoWex8g2qfW9lQYxQWQ;6lo}aDDbvUs`A*+r z)X?bUM$@R@bo87Q!AE~|D}_{8byfa~R2{|gpO(}*d%;O9tQWM?9;dXjtn?Br0ZYlV zkRtFrPXQRdRJ7y=RK_$I#x)tv^exx4O?Tx@{%34Is$mzP0K-sl1)G2hERk7&ET;@q zL~;+=098;2RR;@IArKWpjcP*i0AUkWsZeqe9<_2>Qc^2bV)dmNmH|_tq|7)KC&f-G zjtDDAH7GqPRaf-{&O{ntwHjSuH;J`4jWgiJQ8*6_NB4_YI|jfU<5vvCORm2sOvI0#zB^f5M z*4DKlfkhhb;0bap5FI2Fb^$11P?hv1xcC*>S^#i5GEf0FU|@t7wll9_4W{g+V*9fIBfR)V%dtOT{{} zbvw>e)qo*uwN^b&VWYH0_Nbv4JaIXYL2O}+Y!isr8fzXGlNx%AxAL_^=GJeJtpj{& z5l-S?^|o91)^8(J+MY{r6;?+W_HYpwA1<}bB6o6e0lc7u7H|O<3b@!+59mI;Q@kYP*BPx|P+g2K3rvT#>V|tXMZ(j zWmkn?1`t|UXj_A3HsAw{R)z@>d@Zbop8yMPSX#f+TOBa7s&z}X^%AN;6oeQ|S>=4j zm6wqi8I)KTn3%N`k}#tfivP13UQj?S`L$VUOEbf&LBn7(U02>sWyI*qs^p+@xZx0(p>srI0N+MZHj;7a3L?8IqBQ zMkQHpwmGNBljc^ZC02sD60o;mk; zPnr=SP?K7zt63oAcYXmkaPRkwpNbK_nI;t2oyWPH-}sHwnHI>I7VbEY-I*5PnO5AP z9InDC>X~z`K?vFrk@q=t85s|FQ&0qB8`*G@3Hq*svm7xwlhLT5X_q-6dY>lR(MFll zdKaUA*M>Rz{%Jj0ENEAnJ|rcc$@zGR73>Kb+d3w%Rv z=Jg3Ik@kLC8iIPLfvc#a`39Rxsdt;VyO=Vs*>AJie#@9}0fDNm8XpY!tLwO&(^*sD zn9Hyla+^DH88|1Q0v*ck?ACg7)h->}x*hZRC_6xO(+>{{dhY}-ukUZKS+}ocR4aYd z920G@gLHTdC88hf|2RODjnhuK%bQ&&&w)Li{={C2Ky0>HeeHXMrvAKSe zs*KJ4n468;a1Xa7*?5n^I*yM)8OTh`(m8U`xl&mZo|B;+w0q6i8rNnFEP^1s^;sJA z`(n;}Pi|Gc&rq)i+P%9RzO~XuWp{S%8+!0NcPDy|K0pX88oyttprn|+)nj9ibqT2mKya%bJlJk`m|E|85u${DgD55&r+ zQJ=Y~yi-;W><M#Xr4nL*3yY9^ygL#tk;riQD3P++o2Pj>B1i6WG>i9scAemVtkP zz|oTj1q=6}@ZT=IvtUh5mJ6x4wP0=eg+TAGz5#=lws+E1Xei4~C6sll|_fqV~ zwLLZprUP@dBVFy+K8mGb7ZA`vXv+^xZ$dU5sO{do~a3f2TLN16BY0mC&xR} zGy5P#E`EPoQh=WuAaLb6#*Ep(f(R2bJ4R5UF=NP#0ZZ1dT{dJGjTJ+NZJWoB*|L2k zC#}bZ4=7W1Sjm!Qix_+i6)L2trXrd)bLwowv!~CWJa;zAIaHCM{-a2fDqWg%=1f9R zp*o#flaL~-GmRS3$(8HWLQJ>XM0%)fS(#JOeqH-DDk4UIv_#;*q2)@tcsEq|AcU7+ zzrq6dB1V{)w8PS>RpZs<2^PXwbV>ds%9k%um_c2-w50Qt&zmh-x`YYSX3dvG6%)0& z^=VMBMV5XTQK0wm;|BsnzrKAy^5@^*&%eL@KL7?O;DCSlVc>xX zCg?{W3^wRsgL_0M;e-@Mh=+xEWO!j79Okh{AAbOX$RdmWJYu4WlANgGiIU)h4=xNb zgbR!X4WtZ0Hzvf8LIgo15k>826H!DPb>vY%uGDBpO)}gm7T$OgoM-F<5J0eAd-=&%-+TMz=iju{(nsL6 z04k`hfe&^IVTIsIc;OxzX1GU(9D;}+i7QHy;)?z(x(H*8xd>F_j?FA|W4;SDBU!|f(llcVTdWlixW@WA&oGO>hcMs zn2CZ~rJrdk8fl?w7K$;Sf_jOlpvdNnZMT)Wi>j;!Hyo_7(kgdzyI#Z1uEb!mOEuS2 zL(SjaQ6sE)1tj&2@(_#q;@D7p); ziY>kv<1x9=f=fa6HuH?W%`M~DD%ZKt6}r)tsC4f--6K*b5QgNUUb%qX>z*+?X546Y$Ec!0n2`}+XrmqIDA+>| zcBCW?uXx9E7?fP%JVc=HE7QZ0{`GvKv5$GI5M#PoTfj$1wwOsOUNWDR!q+`8t#5mR z{2o-^62JMi1ZW)CKm+o}i#zboXogB!HFm)?T@ZtS2Bey#C}jjoO<^>lA;bf ztRMa`h(a9Vwu(s32UVzC8)9OJ0ztYdnof#Sl-EF5hYQy+#C6PgM!s@X5Y{zBM?NxR zJI;ZPhph2;Z4{m|d$EEzJ_%w{+JYV3mz9hWWsldpC?C60$h8piqlUbSNEO+#_Q6aM zBco^}--o9?$j_4X>(d7Q9Pj}sbaH=1Bh=9VILcCPJXcA>f)&R41YJxc8!i0XG$l}hYL@4Q*wkix z__ITA0uc}a!YhFE3eIeaGePAH?6)R_PCTINLm?W`6Ws|<6v;~_}2-UfTjz*Q;lI&;J^rO zD|+CXO}TFKS>3d&nV97HAsEpsiFC#Kz?oZ-My2%ViXm%0Ga&M7uFo-dkLy(&8+TFl~g3mJ&)@`bxG z1`M<@9_?t2WLg`0gBLIG0P>P&m*#095!%BF9%oD2BEQzijO-{&nat$$HN`3sac zqLZ{#S(1}XM3%SQ0lZuiXcc&Yn8}<|{~?XKqrC2#Q)!tuqlQuod}Rae9I7hdS zFWJgg8D9iC7=xBtq2mj}M0+HJUHym&uMs@74p`Hi_H=@62-ii7n$-8HE2^nQ;Z<|1 z)#cnUht0Kzy)FV0sB>2o+vTFw$!K1@CJ5`4G3?DQL?Oq9@idfOq~#cK54tvePwxTIRf?GQ%wD&?=eT$-#kq- zc7fXFfwdWI+j8Bt|7eUVB!^YHztbke({%g^y6ijWPkYAZ##u%0+(G<1AuUbc%Gqn1~@60 zatT#%2?}@#4A?d2^=H8#fmKC;7KmsV2rC-sfmf#;AqY4lXnLoIdMEgDDX2p$_#Z78 zM1J5`s}_SZxN|l5b2o^CIp|oq7Dl@^FEi2*SfnHKM1%z)5l85J(C7X;(pPdw;)GDR zW7Rf=uvHb@H)Lg(C1=NlY4?TDm}FuC6-JhBAyZN-Win})WoxK+d{TcefCZ5DcXP;p zbr>}VbQyVARC`!}N7WjEIEa53d72R$6_<#-G-$uXh*|ZB8Q6J9LWu%7kS~-N(f|de zSAv?DI4;MDo%nj6D0`t;IWHK48g_~tR)dVtdr*Xf@RB-Gjb69bc?w2 z5WujDQ&$X2=p0RW4OnmjI!25xVT@v76IG&&=A#r^D2CBElpxg>J82VVqHbiUCTyY< zjpTM?qK3=GT78ml;)qj{26%L+W>7J0jV8|Avgw!keIk~smG8g_>d6!i4z%4FSsBLqL{6Qk%jePBNAe) z_>nH+I`z_PCw7v`pbYf@l|X90l357RnYhRdEC~$RfP_;8letk^l6W^*U;;P!P*UOo zXmE_x*cDRdGET`+M7f)~Wl~P5lXeefa@@nP7qW z2MtM>o*0qVa+npCn2c$PxR-l|1(}P*iYYR5xHeg>Ba)SMV*a_Xne{{?H8L+NIhxsU z4%@InxsjT9DTy3-4OZ|0uen-uB%9!8WW$AkW$8Z6p<8_ zj6etRhe`C;Rx@yw`6r!$rzod{j&4>q+PMTpPz2q%coh_nzW@pfS7-1T8xfcc80VH# zlb-7di6J+Mcd1tLc^H0Kp9glI`Kh0$W|;m-iiv5F4C0vRN{v1Gqd-G{dNHKXnU&>;D7sLGgqM!l`58{y{*I&Zj^ina;TcP=@l$ zx&WqON|&^P9KeWHASjR~q!`vv4U(#+Yua*y>8201f^jOR018+bh9Lx+A$Xctq=TTR z1CoClMkY2{gPKKL)C^(tPh3Ps$k3A8a1IyIp&sg}j~c0yI&IXp63ruZ8lKC-$=eGvG;B**~$W7_-WiwMsQ`hO69}HLam3 zp|TmkP^DN3OZ7;3XOj%X>ZP9$4B4QbVJbJq(Kme4tbgMxe6uT$=1GZBt=7t>*{ZGl z$yfgQt>YwE13Iqd3Sz$pe3Xd~Aql8~3L~8UNvQGaPX$r07;&#~Sq?&iHo<^UAPT9H zN~sSsumsxzE`SG*Uk?=*rlWh6bb@}s|iPEE_)gYcOFZkd>p4QY3k5c1 z4bjRs*>JQsmR~>613}OWzgwTdyL#H%Ls2WWEr`6Ab9=GIycd$E=Zc^TN<{;4l9+j7 zGGb4%!weG|ngL@B+fWV{;k~&5zTz9V1*QQTAZ_WZzI%&E?t8y(;-d1KTPwxIVyvSN z+naczv2?MVY?w(NAOmAYX0SS41Z=Vf94TSxU7oRUrcsudfEvF53=zC%qVi`qJ5~CK z!L5sdA-5dYKnzeY9Z;ac)&Rmns~B~YH$o5uKVSvY5UD2MX%7I(Z^Z#Ium|qJdNHSh z5m|Ev;;l3spx#0ud4R*stNyh-+{2I6pqKfeCmBZe)K9*~Yx5LPO01|(d>i4rfvY*i z4RghJ>jHU@#T6CCV+_BY`o&;O&D9(;>qA`b6BUE>7Ie@8bHOvKDpPGdxzRbmBr9;8 zRL7VR!R)xJ5m#r8=f{97He+)^r(hXTg$&7%y6&~J*r361v&bJI9M{k&WFQ1&a0kO+ z4a;;w-r#7w@W??B1W-T>lboiXCd%bO%6qT~DVWOI#GkDE%2jK_H$1KedZ5r7FJ6n8 zyUfeJ{A=^nI=0qE<@1sly*v3*;-%{8hM*j$v_ zEY(RtoLOC*U?E8!{o$4W(~bS28=_2ddX9jFEc48<(e zL@mDK8xq_g4M9-FlzPQYEzP}YxLD0H4O`XY7uC&u6k1K)aYDws_0?g$v3XI}JwP;) zHfeIK745uW;h8fZMX&Yp#IXJI&L(Z4Bd}p;I-~$PC=8N!-Q_o7IN5(Hvw-Io-~E&Co54 zS3TX;eWTZn-P4$Ug#gxQ%-!AX0pA@c;XSg|fCWI%OvLccypi5!na2$raeT(PcAd{+ z6AJWA8}{8xs=MF5P}qum*snqk0p1H3o#1?);3>q>@kyBsJ(L@u2`%+nXWhsDSqOb9^)tu3%T8EI}O`D&5}x74%@(9Ppq0AINZg3zCJD` zc-#I5d+X%%OXPfe6umKtn1mG=cTF&KO&I@dY-UtNO zV{R!8#K(LlD)23y@*NEI9aZ*y$iVa)hHdA>u`72&43sS$maXnWaDRvr=no#sg)ZTT zKD_(+v~LQhl;h|y2y4-px#Y7RUMI+C9!xUKeyx?8lz$gK{+grWjbz7`yQ0 z&o1Vh!H$@+HRg5Cn*rYyq}TNA?cWaWzR};zO72HuH+N$V(ong6QMtX)$wG^11pWpE zCjiPNm>!4zkfwI;`o8ZOo?%CHPK59=!~FvlaNNp+eJ`=`zP@cEzdcg@_>v!Y-ptKQE~>w7GQlo?ddFOEcmjYD zvNJ#4(s1+5zVp$p-apTHrNMZd%PFX!=C4sU^WE2eefz7s^cT2!PXBSmLD9=J(S8#+ z)3CHBZ~=twX%K)c6aK9VIJ8t-hZ?|`=@f`0yemk*|Kl%9Y@rrBtCXe~!hBE*`h|nTN{&ZSI=)gf? zhlC6ha@bJegb-f5c2yLXF(WaK#H4+cR!tg~CQo(|RRii4qm+(#$DKmQ1;1 z$r2`>J44C51Pat8RKS4F6becds$aicK85N_*C(pJsEy;cjoVhPUA<=A7FO(7vSr0~ zy>`vp+O=t`QJWSQZb&F6E>yr<;X&TK5FS9_5M$4xK!pt-Mx0pjVL*)?KZYC`@*m2T zEm!uFS@UMjoj3OZ9a{A0J*7>T&U0Gz9z3mEm-Yh)(V|61}s=H^2oIQiOrT?eKu~ydQ|Z6 zLH>M)^;@)v@#j&YLWcbF2e1%D0wl0N0|E3eK?N6Nut7r(gfK!0CnV6p{u(?aLk%@_ zP{9HPGH@V73_?gD4(jW`1BfCrfucJq;zgs4Isz%AkxDu#rImDP>7|%vx@3x)aMB4A zQ!v@dlu$6aB$T0wauUd-L^;K&sG^#xs%fyw>MJj|GAm3m!%C~nw5+j48e*!cCN4Lp zp{AO;?#kMQ(Tx%`0K4hb< zwnuc!jknu=3vRgLjH5+57 z`946Aq5EP0WWNg^Byd0qCG0TT4xcrUL24r7(qfC36$YqR8mNF&Vy1s zEX95&BSp>UO82;QHlc?m zTDRYlUOE?CWxtHhQm zrG?77Jxd@zJ&;Z}c!*bSOG1mNlWCO03}L@td}isektIn6{2C33^>`zv!N)a%9_?GC!|xIQVW*P+7=-yxS*j9RY8B- zV1SfMphf&ag!M5IQ*((zsP>YDRGrr~q%q85>S!f41d1j6)l429X(m5mQdp;1&C?aJ zVMr{7OV;W{w|b?V=CtkC&{-zB+I5TXd}ln}6sKwcHX?#Gqn!+kSj957WsZdzWGN%r z$x^nmc6`)9HM`k9HuN^+(~W46M%sxU3l}hJMj5}etQ5S?GJmDDh0TPX|@SiaJSPgTSGaml%qP8cgJs|W^oBbL1I_pr-rdY+sc@9Ng ztSV+`bhXk^A9I+YK1puJJUQN)t@szGf$(y5No;R7Gs6tNK{4X9dDb5N5(pZOxx+{%m zb=_nle`)vU|Mm})13r_P*ustUV1pVDuZE)`eZs+-jM9lk#9}l3vP}m{!+-GfW(*dszd8qpB6e4%|&r~4Gd(6B^kj!>A|hI6p60Yy8f(TA1%`9rDbKoKYn|&B-A}VpaPQ#i9KaK9=L079V5gF1|JWibI@H?Y!q<`T33o zoKnLV-E6&Wh(qqPg+~~o*~=>KaG@S`YW);M)6USf`E$XmW8Dx|KJF|D7Z^Jg~`~ z#Onp9S|=;$7$JB9t9ZCLdAx~}G&8z5KDj)O%e=?EkpWzS{7A&OdP#+ki1sMzuTClxc z!9ij38Xg3oAN+tI^niy*oZ)K(Bb+|DAu1&##%O6Wydi`qBoHZN#%AP?Lztzn6QNt` zvZmrfFPtj0W0Pta!}2OaeQCcmY%^yvlK7%K(E$bf!?&aeg(}gzf0GJ5)}wPU|fKtp*OM?8YFI>d;Q+(3IKw)u zJyFPh!my9SjEKCMg6e~c9KnkO1QR>GjeIrQ8`(v984=tg z0bRLPE6~&%$&ai|*{h%ANE%m!Mb9*k&^*Zt<-z{`!GaeM7S!~z76{RA$u=s3y4gfg z2%?Y`Jp}3Npcmy-af#6x{mm>CP8==2F{>(Wgn}mU7-HCkZv;}RRG9ZG(&^-!=v)~4 z0~jD_Qfzus?uNGGY;A_#dW33^qHtuBo5juNkTnI{^7y4e6&S1mvQ3& zN5zBIY$Xm+LQ1XFxw%wjjHOQH)ZW|!8eOXJQx_f})h|r9$U#+A?a@~K(PVPFDo`(G zk~<^Ki5=RZbVQgTnu%RCB47R0PY{M}Acy`b1y5o<)ZD3t-N7O>af=6xoO-GNzNFS_ zO;c>uR>k1fHuYA;gp|*dQ|}>H0zKDPQ<_lm$WtU0T9CPsv`o+Bx$~&dLbcZ(!`Bng z*J3dd5dBx`lT@cW(Sqek6-C&!Z_MX zbW?C$l%B5UQ2qo)*n=3j+Y=$TyxrRsDo(!@!zT#bT^QWuOeQD6 ziNl3IBy}_TYZ8RPx0L;+m6hDr35INVB4O=Ap}Q@$um&%>gLazBJAjwYHBZppfHFPE zl({%YFx}JL*8J?&i0p%JRoXm=(^FHZr|k`wGn(J9-9Ponk6apDJ0#BhU0tD9VEBcU zJk++VrvchUv+Y+$_#A+_npnWnw1y0}}NT*{v)F(?JR*tpdo^&Zn4M|Fv8`#M}(k7&(t}6dp3o>Ylyh3xmE94ROasjY z+UrQGP0-?CtwVyr-;D;Z#lc{h;b}P3^}J!1M3J*~R6hXNWr1E}tYsvV-YQe#BaV<= zHsZnAUMK!u#Azz=wc_$C-}8$&El%I{)dl9%sv!B;F}78!tgj|XW4x<^HeOk%h~xha zyqQ%?u|1Ps;3D7I<3ZFbEa(L+vVs?gG=%`n!jdq=GUP)xS`0?oMdn~efnd`5B6BvdySl5q(km!v=PiZj(4Eggw&x1Q=Rjc7edaty#-4u; z!O;t0OD<>>qg{kvXi?rZ8gspwyE)8cmEWD{&}<5e#^~Ex&$=+1j`q-FNtOpe>9v(I zC1dGh?B&N+SeF)Nu}hp>@&KAX=69*skhq3kNL8J7W?g7sX@=ifWiyn;W;r~aZFbT- z^k)8*V?fi2a2}_q9xyF6=ih+>1s*eE=mn11=PK4V8Jf%MQk6| zWo{AT`nEn}3|PswUdg80?WF^lj%h92fDh1YzU6GR^K8(TAuv+kGHi(;9a4~u-y+2c zqHf&QR@r`w?W(8-z#9uc6F|Z%z|XELcydaK38n)H4cRgUl`ZsRoI5(*Rcw53cwL3adh$qX*h8xP)}ZYz=&IMEFeS$Jb@XnaT~`2 z9MAFS`6oq2Oq>DoAQy5z65;GV^7TQ*6%^&_Xmat6wa(m{Ql?O3nDSx(%`0Dz!QOKD z2tpxTh#igrwXw;Op4Siq%nOzH`CN1U(PZDp)USRzox1 zxB6x6Lf>$u?r?~?inO%tD8fheu&uPy;y6?f#8TUg$kd#pwV^*DI~^-VR{U^&9N<{`C$U zZm0$!;Bxp5mSZ2j+F-h2q+Vx__MDgOL4d|-kAP|)I}x-261tV}-ggpSwTwan^J!Mnap`r%q#IS*d{>ngWJ zmKTCyhi?wpMIbwo`)C0zaD+%u1Vzv~oloLHFnz!w{nGFG0H=0QWf6+l1q9c2zuiV* zaDpbd^Ip&eEKqutcnPHl<0UWxYBqQJvp1m_^mG>rCVg#(QFMtJxU-=CV@$eo+X@SA z6b7bd;%9G@W~eulu`~qr9JPQ=8=K=A%6Ld+g5mBhQ6bOkswu zj#@~c7G`Mk1_*81wjujv@K>>E*%mTncx_xYAtpY2@Zg~X2M!%JX6(@7qn1dK6e&7% z$dJjDDp#^>>GCDam@-ois+qD-p_w{&^6aT{(4j(z=)ht1u@`tO)C+3(Fl{YITWfE9%meDNBqjsXOJ;B}@oX-I?3AYnm5gYwfx< zwb-%0R>N|l0`A-oAn@)D;b;pykPrnbZu~g%y71CmiF~9(Wj6ejTa!^9rG}O>F-b6H!MHhACfl?fRBoawJ$v7j8 zE7b&)NjTNGBabivB@|IcA=P41OgROWR8(1ol~!GO1(sN3nT6I`Y`Im%6qd|Y*AjOf z1{hwTfD)#dzZiyCVv98v8Jm-Rl8I%Yp|*{jb#BHPY5pVXxln1Zxh7j|)6jOu3%Tj$ zTW`P#M_hA_I{IiJ(n%WKb(K=*9j4xiw<&q-rNMYA9~IA^KYc!7;)pq$Qgi>7{=-Xao#D&zr8c?4qK zftJRgS-?VRLt_wvFjPGbU9TV@;$GFb(TneCZEG577DieCHc1$w5b0|l8q=sonHT~P zV~C&p#MPAg-LG!?yBk;BB^E4P#MIJ=9z2Rd#s6Qp1!EQmo3 zYLJ7RBNhm;0XmG0Zeu3&%>z6ZGCZt~g{C}?3#S9aOUaNsG_+kGa41Wf?NE2-@!|05 zb044`kBC8AP|;u|%%wHYR!9D;&@qk?MPO|R8&y2W_V6^KYp7<69!Qo&#Mi8kv|tNl zG~*h>Db8@V@r~em+qmeMBs=y3k9o}79{c!5T?BG~aIuSAyo8s2SppNLI7|c0)JRY~ z@-YaD;l z2eqI=$3drt+UcPUiT&Qjuz>hMX#C4%M?m z9sU<%`AHIfYMOi z@`s-VZSr8M+SAe$7pbkqYLl5-3m+r4vQ0)%g@r{u?N%5)=%N?9=AstFV5x>^s&Zp2 zW1c9&jY2wS{);>iw;5o87j(;CJG0`XCwWz#RjMa<MJsNx*uXjC zQInb_H8v*)aI6#^Z-B>DQ4)~%*A;g?;kxpgXCOl%rh68l$%H#(c=6&TP)r$3ilIUk zuAJTqW+E8b*z$Whc&CVgIk18)v!I9p1vH;7qbG!b#~ABaH$xWAaTf5L=?u%;9XNM; zRu6do{s8De2W`-WUXam@hAUb*NEysX?W1A)Q)5iJ(n8H5FE-u9@hN4vemLK8OD*vj zH^Ro>s%ooyT$SneBFI9Xbv1-h>nm7A7r3^Un9RC_B^MC9hynI^L2-&xj0rAysRFW< z9l6Ox(ihd3Q?xng*J@{DqJb*2G=#D3#CAKQh~6wZ zuxLqp4Awdi-?K1ngBxZ`2sLQzidM$hUn}tH83P%diTm6yjp;kA0PjcDNQzw)1HZpG;(>oZ8gXQZMHv3Z z&c!k2Fu7%UUr=x%84GA#Km}_NdhX)U5EnB3ERy{0TmD+mNA-f)JpY?tM(S^`7tj9`H%Z)eRrk$&$?2OuU&2&wy6*IS7Su7=+XU zzfnl^aTu*g5h`@wHfTdJ97Ool9r>AG_;i35Wdw^ASNp*q6{?Z^F+%-a9R51tT@UD= z{;|_m@lOB>L*>y60bZWU5mJ}vh2ba;c*O-?w8deJo&-|hSVfL5L=v1}pzCcQwGka_ z_zCDxgC?jN#)s{xf{Hd3L@yz@*z+2O$feC zkAgtR+2vqr(Lzr}+7No55h9^6h#!d*787O@`aL0w{lVW=Auq0vO%wta9vftW%p+oth&;eo~ z7Ts$M;xr@!#_$3Vn1BQ{T_`ys1TfYjMB->SMX_<8%1qKWg29_0SAuco%rtOW<_zVR5gqsb^vO|TOj2TuG>{)7K4sHM zB~@0XRbpjUjz_y8n4b;gS3($o#8lIq-C9DC(bNL0@Zf~Z%1*gm5xylV3}IaE$z0lo zT{eqF2$vQt0$++6V2)=`2qs~YLX7827m%by{a`sm3*&*>*Z1UgqU}D1vyBCy>g7 zdDcW>Y5`RZqf%7B2LwTT@}J!(<2&ubd`4z{Uf%wF;!}Qh9$%={UjXQ5D#m~wjARt( zX$ob6Driw!n+TN|HB^Iy(ql$3BGjG1R9@)GWaw22UvFO4Z`zI@i0E(5x5%lsW-qnp}N;0_LHW=8YH3O+mkW$xeQ0Xg*RNB2+RQD8!s; zZgpU5Bm*^!25ey4Y`EzPDZBOQreue_5zmPJ%&8dUF*J=> z3X^F$2rcm7F(?D}8JbTq6TwBQ-2DQj?*565LV*cz8v0QLr*;#5ges{nY)cI3AOK?@ zs47yhDhJ@klC~%QXbt!TD|tv&_r5WCaXjm#J`f*Tna3~>L^}zYQp{~!!oSHLafAQgh^WLt4dd_ z#V5$^LdV{!(Q1Bo)Vb2{*J3j0S|e^Om;x6SK5aVsmfQf>wt_R5-DVYxaijo zoY=mlLCjVbT?5)4)kdf--)RAUlo3i8f~dYNU=E&1oM*&RRjP%H-C``qZS0_QtTR># zAQfO`4$c8Gpk5@P;WiQ#C~iR=>nH3&vX&#W*6cQ5(I)`G6(mE^eIPaX0uayu7Uaze zNI+tRuD0T%)RpdXoGxXhE}qR2e6+6XI+!QU?$lOoGh8k1zJeJ_N|tumYpf zZh|YF`2q3$Ca5&6c<=!QD_;%vN7X7rF1!LP2-ET$h`te;DqoAV-#_9NQlY*h%K8c$)flE>;$O7(MEZG4H z?pb|Tm#Qqzv9Cdmresu5LMf}4Rk0OwPB1tD5BxwTPy;z^LniP53y5(5^TM?nu;{i~ z8Z$?R3Qrr;5H_z{huVw;g9jbMt{uCS5CMpSz=A6rD!ks*t>{}J2cd^?U$SC@A_uQ5 z7K3h^DF^7k6CeWH~S z^ji7irGc0ub;34>Z9O^Z;dxF%a-V-pqhbdmB$P z)=vYqa{g2^8&eih7d1d0HG7nlQhVYj{tSSO9S74Cbm|+?REWFg>(lJ(G|claAOlyY zur&NY)gK@3=LTqpEGzZ%Cq^pnh{MDMkBy$Jzjo;9-J z5*IFb752ac4r1F?Vv}aV?6S-vs7e#d&T=(p95YQ*wldSeGB*J)NCQrLHZ(V4)RA^+ z3pJgkwq{nNHOSdG61)5SB*DF z0~aB;3u8nWxq$LEw{r(6bPwi@7{YW{H^ufw57TwVUIll5g*0HncdK&v>gsqW@ftE< z{+8&|mv|{aNsxQLH+;W|d_V4ENA5OI!!BIG`}#sKB*T95fPXJD5cC2ySwk-nc!AgB zfg?C_nD$T;^(C^ldT64AI~au{&o~PWEv$knaBzor@bY-LE3ks0FVBF8_|rhmiJ$Or zPt!Jd12J%s)*P=sZxh%&0*%*rK(959*MuQ30(D!rQuz3h_?UYN`CS+Jk>hH31Fn)8 zP#ZGw9Lk($>XTwzSz;@;dgax8ciFU|Dd$W>6wm+<^Z>I10cP{J81sM=2sxX3wgbHR zZp68qJBNZ37=z#WgQpaEOt{pl%AXTjDU?E?uX_h~IDxo=Dl9sqJGu!&dN4@-x_w*v z_uyScX!@onRry3hP=Gp+Libt&m5U$M^&LSeksG-a@DVRSW5&N=2Hs??S70f*6*Sr|lyc7ON!_Hs(0|0%{ z!#SN6{WKrFwUTSbR8rF7-L>4(fuydoW24(R{ePKhcOQ@@y5vDTF;liH0}y z0uNxMEpmVtv6%Uc=i38x+^@AF%)Ph7(Zb`#3FN(WIo_*N{NGEaFML8-Rl&w9nZ7ug z;WLovEq>!8#^Zj$S zz<~sd7biZD@Bl)KGm8)f5;>A&Ns}j0raTFVWlNVYVY2iC$WKk1H*wzNvy*2ZpFe%- z5jvEp&^$+x4m~Qg51*QV0wG#7HPNC(kFY*URCUoKeE4$Vdj2(b)+}0Q%cfOp_AIWm zw2Z|y1`OCQV92x~3-;@muV2WpWfMGD@HTG{FL+SA*aHX0k2$o6fk)CI%a<`{*1VZ> zXV0HC4-yTUbZOJ4QHxf+T6G~sjOf6Kz+pCI+Z8IDfWqs}9bUYI1sB$fSaE5`k5{WE z4NDUzScLJ?)mv9}U%ozl0##*7_a#M^bnkA-()jY0E~QT&3RNie>OoxsCF=V1UH9+% z+NEFDr%a!O*=8JW2pnfX0ufA5!3DQza2vuP6pR{QoN%IvA9`Rx1%XuPutR}X0D>5Z zB%;WH6H&})qmDibNu?KIjIpGcXrytboN&yEr=EcN{;8-R^*|~qqn2vQDX65H%Brg# zX=E#|yu!y7S5&F8N-C=?r4&_GVP(oM#|rb6RbHVB7*tTX3matW`sI|r0wc^pZoCm@ zhQt_C%r?g)!y^*Z01XsS)dVfHP}VwQP0>YJa}AQ%YTFYs+;rp3H{gUL&N$?xQI0w1 zpp%X|>#zf5yD7^1E&siI`XN>#M}v{K83x3qGVharX-&57?y#+bcMc@wZ{ zoNbmGYObmC&O95Fv;`L^GgRf3FC&!Ympwz2Ge&7v)KN$!wQYk6UfAu9U4F|{Q(`#n zbh+l9i*C9*s?+5iB(@E54}rX#YDYOPNB8dDl(xDS6z42McPk-*@hehg&nq7 zVvkKW*#-xEwwi0MSx%bbq7#M~ELM=9q7(_Qh};&_O*~z88Fv?69OdQ1C&_`bw*5+jJQm?>?#HsWQwUPV>p-PnB$K_wsQlLOSbJ~JRX5r z_~D6HS?17cPF}Q|Wy=||oqOgDXiWZxD{fQdJViRxrJH`*RPLk>?^RdP(>gs&;=6?F z^uPYEKKt;~kH52nxn}JGg?(H9xaA%UoBy%#pifX?8V$nR0lAU4UO*vmg98`g#>FCY zDUO0LVjR01_c)zIj)Q%$+y{T8IjBTMNnObb=m;jd(YXS3Eo4gzS7#<*xDIx0dQ&pu z6rpQyqg%TpnaNy`hesHqctteg5s{}nB_>Z1k7}Ot9D|$a{VZs~8Cubd_NkSJ>|pN)zSxwQ?O4xRRG&3-p7mlz;733lU6XYL5^+U&p`Tn+p+i;AplY% z8{4=753WFsZRD*Q)KHFc{{9v?j7$WAD=L?ZDwxTUT#%DA(V&hv7&#D*awt3UgGl~m z!mJFeD=Hj|D;`EVSjKV{ETjbtWhg^q)Q~Tz2u3pc7cyR~z=wGPq8^5bL}k7#dBR)b zGZ}S=CgKc=xT(MjtRTHA2FH3ud!!b($F%NYM?2t~S`o%L9`dd8J!)j58{O!>`tU+O z`6-*UGU1Cn^0AM8WDp?ZhAcq}NEZsx#oey)F=|YsQ^c@>Bpnzoa9Q#Uj97#wF&cW|~iQ9&RpxO>I)oZ`Vss_CTdHagr)Kr#dGRz%w=RjI}%O znSwj3&_;Mx0ghw4&u{8!$9(QHSZ#BgKNSSf2FWd0pU_YbGO@!(e(Y|-Xo3kYA~;H> zD1sibXht=;(Z+RjgCFHdNJnW(BuS+zt9()lQyM1Hu@qu36_X740*qb8&ZfYWn_=!i zOrP%VrzVSmP>Fg(qYA2-Fk9+x2hj*8Mk;4eok2FKNY!p~>YLirY8RhY&MD_;GmSAoij8@iyuhI+sYZfxUY z46Oz+pm2iz#6FO*6n!jYD=1kEQr0Gx1u14hC_;ZN=}8p^E$UdxC6=;qOk@&Mn#`2i znHHm$zD$NTUdxR;oWO@0aB*$-BpKZ1wurk8RBxFHTxcFbxYT@V1uifHEKF56RV5m8 zvAV_SK=+H>`D$2KMOLwnwY%R1-xSCz1uA6Xyt?uQJ?|seT~NclZG*2r0lDA85QZDc zhyp=a00ds#ZK28O)&(Xh(TPx0DhN)nf@{R!20M5s5Ee?IL&&eqQl-M5MX5?jyWuZ+ zcq|VCF<)9&Q#768rmww*6--df7X$USG&Z7*2ZdDM>iC&F9&R;_JLF3h*~s2ZayXd` zwJQEZVQNOGu6WQ>HL-4&yWX9`m8l>FWrvN9=S8oWsd1q9o_T)_+Ao3TE0|y<;|f0@ z2oQ83jch!!8g|07MF@_|Z@7t%nCAx+oH!a0xAZm9LcL zm649vXer&{ON*GbsK^*FTq{VtSwRCm^#F{07Xv?-y2hh16pmNMYNa*;HZ6z%1Yo@Z zA@gRN+_b9Et^v;Hj#JmXUV;&d03Kjt4ZP|MFL&2iUMh4a%TnNSv-cy%FynK~_p56^ zslC6vLWn@i%7nLh@PkYwz zpo=Y1B7B&zHg^K41@RxY`UY`L)KZlpo=lx(ZKNY|z-=b&uL#wyBc{x1}SZVI$4 z*^JJM$Yzf0=n6go_=J!6_NRZWO&EA!LkI%S20|f*jQa9y>^h?C+->_}r2D*Y?bwc9 zpm1``FYb1NCHzJGp5%nuZx!;63lm1eGK{nW?-v5E7zQu@00SEo%66tfIsVTBK439i z%w)D;03&bmHiOj)&;Tj#@+z+nACQS8khmnp0%dJ_GSI3XDfL!w$!JbHrt3R$&bp@T zx+-xAs-SCDkOie63ZkG2U@!)+V+Lz5_?|9*a4@d|DMH2p8Gc|O7$PA!gaxvR2sI+z zCW`FLP6=g%318y-*lzry5Z`*@{P@5MIR^_pCo8_3l)1e$N!`YWPwS z+xn*!UvVL1F$h(_3WNddHsTf^pe0=r`*x8REyx$!#RNwA_B zSK%zi#1u*a8l_S1Rsl`OkSSRQ|G3c@zHus*LkKRQ9M2IQAEO1Bh4%oJ+fE;i5y(f2TKypvPmUh@-knNMGj0RXObp8Nzj53Cyit$Ge^-zXzr9` z{g^SOXkiu7!xUf>6GB158b-sW#Na086qpiYPC*nz;S{8DDs3lktWwlQ&3Cv`5eQH$ zbu1ngFahbYETvO45&>bg7&v zS%Sd{fZ$qy;6gFv2QCx}u;5NG^Ft4Z5fUK@IsQ{!JQEn7a7TJXa`Y|2_@!U|jlw*~ zbG~9OmS6}jK^)ov9kc;99{_4VmA|G7TdKpvaMN@X(|Xf4ffF3ra2kkn@n$O>AEN_K zrYo2802ReK7X?eFb4$6DI;}GwVNC;iz&GB?^p0!;84*1D;}e`F82ZC5?=n2pb3F+! zV?=>H*)u+>06yhYFz2(a3iG@Wa~G0fkFr4x6)I8tYbqhs41?iO%`j3W^-&-7I)cGc zgP|CLArq`%`L5*$G!#^UKnR4uLt9c@Uea(hGZ9MEL_Kp|;3XK@E~GSPCKipt1cv?a z;Y^kQ38nxQqTw9S0U1o;M;(U4ew3whvq;I%PX4(O8J2V!6e@3m-~w9g#TrjaT|f(z zvr4s8OA)0@#Z_Dv;SaRnI)AJJ#FR|^(a6ped(!j@l%_heqfL1w6I{>}r68@=(>L?f zRMcwdsGv7pP}%&H3a$X36!t$B_H2A1LSaW!B~?-*mWC=eQXw>BUuP;g_I9LUKe2#m zuwZ1dzzVEjWM^OlK-48wwFSn&Dq7VfUUjl^L{_CT zzz9%5jgY|@*b`#%Zqgh^Vw#m%q4HS=Nl~|<8h*f9FAiI?^<=o!TfcQI$8~JUk`NYQ z540d3v6BO=X1~C9(w>mQ9fLAaqBO7;pumWqUfH&XTiDqv+>_GMW$W=UdZ z7p!J&_M;#yXBRCIde&!6DoV7$7?uDBd;k=}17I;>8mq)hZu4m^Eo$j+YJUNT0LXSe zW*ejd>}ZQ?F-}{xV5qz`OU0IKxmWYdb`N3zOw-kE%k&=sa&Dyo3Y1U|PGJcy;mV4Y3sr%5 zX}B7fl1Q0Sd6`!Ymy{c#S8J*F#TvtUvA0UKH#)m_iFNFI!*^}j7B|SZUE>yuI8c3^ zKwdMlKbnAoo8S|kz!#E16zsQ81rvaat_25H6v*>`C6cYWh-^l2TDA-s#sQ!TDcf{v zzvjzw|EEE7DjTM?8X|ZhVvzY?IgznfRBL ziVzq9e8so8qHcr}Y}1V1f-9k?FYy7P*ldxg{UjgFjf3n=n=@*^*88 zgh|RORQPtk!Uu8=FRQ=^e82~Wzz39IhONX)ZWu_HmHynsPGfml`s;Rp*p|1JdUJV~ zdHG5+<4S?KrIkn!20@sKnF8AunakJZUM`EU_zIx8I;43aen1zVU>ByrKM>Q6{FIxy z*#-O8jU`f?$2oD=i;mG*zTEk>uKJymMWGgHo=MV>4H=)kMT1@Pk+tui0s0poXQ1=V z&`QamNeH1=IQ~j_*9UNb2YkQ=Y~TibfCw-lS*>JBfV7n}W(=VcP83z4fSCGj*`y<5 zTZf8>mJ_(ZRi#(jrNxyHgc%Q>7}nI)rl~lmjmDv#d5d-7rzLn7uHc$`B@>YPjF%dK zm5pG*8Jw5;Kf-f>71uwmAYqGPj}}-5vs$b9iW{t;f_os5Nm8s!gsf4{WdY%=V>Yd8 z(xaTBCSKRAEl2LA!ge_sAM!c}eBcIB00mOu25?{rP9a%om`n!S8u9N zdO#Oe{yP~MH=E6Psa>$>GKLCld#MwaoYzZ$rNy`R2tlm+s;w=?X*|uw;R<>H2!5uJ zCm4}~V668!Ga(=X#()tR;e$1bx<3;qObNSfR=Z2{NDlfcyju(LVG7WDyivfsZNLXE z0TnV#mESvuUD+-=x;JC_b{1-(@LRtVgTEbnN&#HJC!4^P$iNSLO9=rH6r8~&rKTS| zv~wDgDEtX5ylFE0Ab7w(SR0#Pdz;Vrj7wap4Hj&qx}3v|w=*G~-R8v=Sb;qq#%nyd z_sa@+pb4bm)3Oc!qmirf)MR3@pLl4}+Gf)UEM`xvJ@p|OHSchx=q zoNN+70nFLEX|F_C<9iJ2^p~QQV+t!}OZs_SOixY*4d#5nm#VVEvSspI&-r}8i}^7i zAl)GV(39DG2mQaGzzLop6M9+~B3RM+Ga1$t6hJ(w(Ky+Pjy?)jso_|+S6qOxnbT1* z>Vg|WELWir?+LO_7*zcSeB;M^AZ1GzTtF1%h@25@9jyUc*I$&^XVuq%qzZ$*%88v~ z=7M4(t!ZnC@2rvVpq=o70a~X$)gfYfp_GUlW81sE&cD6D-*Mc_w%q+3-8o?0D?kF) zwcT-AZjGiFmT%tc-3h)r-$kKU`2Eob9=5ZLK6Z`LwM=hyJGTpX(@(K68HE0FD>t!2 zx?{ePV>q<(;?{v7AqFv{0=UEu)8hEOtF>W#V#09uLz`b3mz}+1&1L>!8&tYTf z9Ru`m1L{G0o0d5oh2iQmAvzFO>luOva61`zrK?=~U<)^mT~Od_n{W*l?a8@|5 zlp=qV0pgLsM2Q~Z!}ls7Em{d1u6p>;)hbo1T)lE-F(E`$98aA>_5SM@Dvut6AxoBQ z8cLMYq+R2+VuObbG&gX#$-#rpn=M?RX=JF7p+iKA8a;|MsnVr`nmT>@6e-lHQI{5_ ziZ!d&tz5feC6tH}9T^dDn#BnLLWKt!dfef)YZotG!s-&cD=n|yYJ7z~apEMCFkhcO z>3YJ01`Qrpbm>aAtMDgKnW|8sA|#Ef<p>GFwenfNk|Z@>{(9CC&pis(wy zKtTfwJm~V=VmwF}M0IOD;J^dgao62~`C zv8g7Tmr((yoO9ConQE@-xhJ1}Ua?Cu)J(&hN{N1)(r_UYO7aynsK5diR?x5l4bn|F z!v+;xrzxkM#_;K=I`2&0sXn)-YS8$#N;H43`j;Srvl>`ygAYci>#hwogpowRdf1^V z8BI+hiXe%^>?tN8<19)pUK8!KG_lLUwK;0bZQM$c>+O(q-)-c%Ng^Uv2Whc=(^~Js zCGWiT-mB${T|RLIzm7%019O?gWoBUocXs~pnrkl1aGVd9_E{C5h~kScqTpG2>3wEm z+GB%~414Ud!_FHoPc#99$~>&VJMWWj@W6GMI$$#d-r)=?&&sd&v(G|5PhZhRAKhQl z2QK~Uf==TawLlh5eNojP4r?{B7j^B?*C>i2$}eP>jna!)NIMg??7E$;-1Rh9`Dwt+YQgB_zhO;q3{bq+e?BNbMmc!uu#y2hLg*1q92Piy2 z2tNpd5O}Zw3Cv7Wo3h#Qa<;rIp8ltKr-I(|q(?OB87)B9qm}k<1-0&d?;^#j8brwF zArZOpA;|iO`clL`lNf3m@}u9{*wz93>1cn1JjAz%a!CFKaFJLM#30N?Ku8$yZ-AQ_ z107g6E7=Q6d{JBl8HYjEK(2!U1JmU&rx}Q$@G_)x7!5T;1<_%!Fp;?e6$JE zQV52|1m(kE@?_sQhp*CgjwAPH={~7vC^mN8r#6a2V_z|!1V+VjTmT;41rj<6*OF2xk^(ExzNZ4(4mlkC;}zAmWp09ll9tU zUO37nrCu;kAdOt)JV;W@S>`gEK^U7#sIZl`^kFZ}%oQfrr($qp9J}R4GIZGlCp;z@ z+sFnsaAVw|DtD;<$K;q01NDi2^ZK3^SLSkGHM zrR8D0*<%nPp4L6@;X|&jaO*^V^-lV#q7)RNXGp-7qEqA*8#vt9HN1fZ6Z})KE+8x+ z3c;hqCU&um-3nx*BH0Y%jk1=-Yy~8s+07bGf}ibVXr1tcOu)r3hvDF91Lg}mRR%Mt zd~KT+7Mj_iiGyB=ph<270BPkwO`%>5z^R~;}?eu)Ri{XymxWig*bIU5-XJt1!=gZdm z!gJmcska;H+L5pQV&4M}YBjcTjepNbqJzy*gbjZ1g+I2j4ez_dlLYZGMBuXQf&j%W zK5D3Xwv#4a+?VX6i;$ZZV;O_>j9H5rge6R+pa6v{VJ?L$^vMZ7Xu=hwfaNAc!3i15 z#1nkMjp71b=tBqQ#(;xVASfLOKj0wJXE3P`T-&Nn=XTVYH+8R8T|ZavX4d13?)`K< zt!l+bh%j=Ic;EMph>SgPoKD)aZd{{5i> zNk({j@ClUJ0}!7{iZ?-w*Kv<$7?Kw`R1g_DXL6_ndJEHepI2L_A$pM~Z9wn@y7vQs zaZ8_p49aF)K1F*_=2N)mT)D>sO&}MMw?w@ce8U%J-W3CQ0CmU59?3^u%h!BZcRgAc zed_jJ+|zE?XAxp&5!?4he&&5hn0De9VC3f;<`;MA=V$^46o-X(2Pc1gcYj?-75P_L z{Kqziw-x|cB^ft>ji*!w7sC$kQIV|u4pMVWbCMdLLOt$wr$kd2D@B}VV4SJyqLGXgurXAUqRh!6(=;4X; zv3$%IiqbQR=T?g9<{$(?eFSl5tSE1O=1v_lZ{gFC{Z)%j7#halY`i#yIzcVLcoQ}f z0mE2~g_MjnDK767abnmm9IzcTQvoj^jRDwMX?P7C<#CY_8@m1%IogJC+%zp^lLE2{Xq*KOk~F5Cydng3e}4L$q8!^jyV34aEQiG~fiq5RfjxB~N6D z!NYXLgHa zZy^kKK#+BriPZFT3(1gui7J0-b%7~X1wxqPlptM)k^Tj-n2ZT_Q?p-M6KMSPk#fX+ z$3kcTmLjNt3{qB1o7tHFHzP73nmv&RS7@3Dw@9g(VdtWnU(uR^_nJ2mn-3rXvzd59 zS&g{KB`D@`zQ7upAZ^p-n@-@ACKZ)ZnHp4Cl@POIso^JkvO1Zd3&pUAKvbPUIy*vC z3{4O@)HPka$3fy5gWFL>W44zzh@R=Go)F2Nu;QMCnLX=PpIv%qY}Ho$#R~g*HNhf_ z9U^D|YKzGt3IwVRyGTrMCj=LuemFrBHo^g;`JkmqNSj8Xl4YS6Iu(TA2(F119LgPQ z@u9R?B_cYaTaufDk)kUKmb>7h#o#9fb3(hu{&K>GoTY)4CxoN3AsIVL8&o!=sj8|t zgbYTSWzg|5mN-S(ftT85e0({jKvShwdZp<#m|99se!!(&x|m-Irun&FfmWt|CQlC$ zXoI$5uWGSV;&#NY(f zHeHh<9V;MwUzTm&1*M#rs}8B2z1p6?>Vt+UtQSeF#oCzS!=GfjtoQaI`vtA_#HMa4 zREky&L6EI2`I&e+ntA}CT1c+rGOic?YA#J-t{Qr%>)Mm;`mRDrVg$%f^m?K!rc?R4 zul*{HFsDi^cM1YaL!kOf2D>^4D;vHrGKBImg#t2ZI~;jI3{8M^NoTRrk+I$7q}!3F zmIwxt0L=WvI49o3kWEivi3=YklC`z`mzvVHG)=)G%KwI3a7lW4b|`iJ3Avf z;kn%kw0ych12s@Xdn84xngi6D=(?dyn-+>X16VMPj(VFMH?^Onn^xNd)a3-eaH*LZ zqbUbVQUD6401BVFLItawWotPycR?Cc9Kq4HZW}xLL>$cY1X$K6jlu$o@iJc4q-n7! z$Fm2F;IV$It0616NRy={D07(8qoKCG&4yAst{lyKWjL=2wRfr&Mv7B_RNQHQ?i zt11e@O;jwHZw7sa8^4W^xEr~+$eN#W6%ob4eI@x{$>K|h=49EB#xuaiclx<+9LGU1 z$3juZm4*~}j7WRT&3xR)Nx}$#EQXYns6JW9XGpu&c)RtQL07B7zVLdGTp6oXTbkz? zpx_IhN5ce5CuEDm&fCcw^U0u`st&6XpRk=g3bFGtqShb{P+$T+iML{wkWq?FvHV3} zw7%?%%d-NX`*E^}NvwCK3X5xm!Hf|hGR)0dnIRDjzmPg&ql-et4Z`5e3)-_g(E)7} z1AtIi)a=05{!EP8jEvoEy5F3C;tZQh?ZFjr&UN8TA}qDnV5#hUuZ-NgQJ@R*EJCtI zhcWyM`h3ZF=+6kN!vU=l(z~icN;1@t3{TJl($)l!Xazvf1WSbl-N_3=@K^rF$~}oy z6@9)^T+0w?UK{PJ?Mo}~+jSyspCw(tTH_%mEfHrrPyfZQ#Q=^@=?gxZTxLlP#Ym7DCmpD+#5iHP{fL)kzLLY}0;Pz_f=U8Yru;(aL) zP%|^r-t3j-Cac9f{N5fs}&eb=;xj{oRpfKb6oV=$I-8pXR1s-&%9%Vbk>X1`i+CE5`5s?&7P%IRn3P_k8dPf61bQ!8^%TP zDdrLwU$JA7-sG*9y+tqS0C^Cry-%qmaz z=JUwFC@b&+K@a<|{{kOC`=9&mPA~3?5cP30^;O^XV6XMS&rl6j=obw3W8W1;TL_F` z_Gqsb4Nw6+KqZmh+qO&TP@tQ7LVE8U1(^WwmQ(k3e`Ca(_j*rT{(O()Iebgg0D^%J zRLe$820nDdbQ_TK1G>ORCO%zHkk?R9>l6nN6&?&ESV15J4iRABffUH$Lx>S2Is{bF z;zf)ZGy37!(c?#u9QlkSS<>W5lzLRIWZ9BrpFS;R=Al{B=1rP<_y9trXwjla`0%Mx zkRJ^Tm+k)I+FN~xn>NeU{Zoa)i2SEi~8m12ZxVk@tbR5HmGW_aPO z57KIDEh*iCORl`K)Y7iY_yVj;Few`(v&zae)3GrX1M^Hl!fY(CLLlKRG&&&|$N>#{ zKrOY^T7xaN*=oDywkMv*M3~+_(dCmTg!_Rwg-+Jo3mh z4?XqRv+qEAdhyzvVgw|RMIgDcDWp^>4+(1u0`(zEa*<=%x zP(y|BWl`lC9c~vXGMPpc^O}neQ<$aD^p{Vh@YGXKLsf3nD~>ZYRiNQxmDT;?qk;z? zWZh;PUTl3L*Ij${71#8;6fUA{PD&cmVDuZD=#x)$uL9n z%*h*j7!o=prWmve9LNA;UQ`pNm)2Z+tz+73!!6{I$20j+U#L)-hq>yeI@OWi*3g6pMGfP?_gEnJC86ng_J! z5p6!7@WFq8-~=ZCfd@2jLNBgS4Ja^3YUC^5KrDnl^{o#g?c?J`-X}jmj)Z>n!{3YW za7dc$;Sb-!5#3lJE&@i9DhiYe1B;=H9>5ENNs^!k>cs&IVz32TNQB}(cuEh3P=qIB zB`X618Ooq=g)S5X3_(Xj8{W_X7x;k=Rl_(ZdEAsrR3A(x3^&cw-#JMQ3&z9(FN&y4;hfPI zSC9e|dnZlm7;ie(tQj`JqfO+Ur)OQ96*x~7A61D{KB6e&Ila2hcFJ*Tz^Z^}=9#{F z9_yZc9ML`l*-wA|^CkifX#W`M6jqdCfO=ghQTp1@s}utZG>GU#DSA;U*^6-V)IuW^ zu|bf6RHUy=X-WPqtJ2D%6mw>ZB};8N23+Q{bQ+*(#(=poVR}qZ#VphnkU5@Zo^cmf zh{7~>5ly5@r&H9a!Y_vB)K5Vbc~_X~H?4YAtzu&u)4+x=Y}Hk-Izbq3w1ONjK*wK| z^^RvCR$AA}){nT=t#GZMTx~*EJj|hg{yWMl^y=5Xmg|uLTp(7Uph?4)rJ`z?Qd|7% z-;Fk+qo<@xWL0@t1e5fYkBKG2RyabBNE5I3E6R; zbXWu-(*7EV@A5Ic;@u~CTe3E}(sd5%eH$yJg13b7^}ThQFJRqO*!(7GaQ|KGf3G%5 z0YmnKb&+fc6MSbAE_id9wH!+&9NN)Q$ONU`p~n6+Ob-w7waSsgh|j1C6QdZnF{9>m zs^E*7!5Fw$%;FZQieu;Pn8(^!nl_N}i*^OW7c8S~6RsePYJ87DCh(P=%4d)}GPw{> zj&j$+MrA-+c_sJLLzV+N2RaPnTL4U8DM&#Iy+WI>1oSds+tsv)N;J(++Q4Gl?B*7@ z01c7wC|(E=*~hKvFnDHnyH&W^$mkgnkeDzBVk!_52m!+yb6sjXd=1tfGsHCmLcyId z{@W8{dQ>WYBBm~#=@@gGWZdjoFpLY%`PfH3S;dAnrU8s!Aj8zAxI{7D6Q5}mqZrAU zhBS161tGj(0}rZDc5%JrMeLf_z4lOBfz3~058J$kJa)2$@>lml`=DYbppX{Wz#e3K z+gE6$y~bDnF&=Rb#5(1kwqYYCJNy*T%LeB8 zqWM@A%X>f`Lj_Bqgh`kLORxmV%LZx)hD^`}U+{%s5QbuK9n^ymTeG=bV}#e+nu>t3 z*i#mL5}O&MJrl7#Yr(zT3#i@ey-CuMFathGqOZNV0~$cSx;Nn5c2Ts)`JEyvrYZt?|eXu#%q!2GBW2^0rrxIjm=1XMtVS;>SR=z*XD z0wIC|D-ePckbnxXH>$b0t6{P{;+h!zn%T3#XTial(7_$VgB~1;0Roa&2*UV^B)ti& z!9v2Rdx7PfIbd|cC&YtBph6Eiw<}DyzYx30$Q*d{LTLQLu~V2=B7|-k12aU!I(dN6 zSi`z=!af(IB4>}J;ansvot?!yy-wdLEIuj%X+ z1*K!Ug&LrSstRBzhAUtJUYxpL1V*fL!nbe%VnoJ4s6r{k!hyL-?kgehi$Vr zhhc;=ggZLn#s=`l4AU^w^0(TUKmBX1T^P7bxI-s^rY1;+|3i<%L%hV}t#`blcvO^7 zS(HJf273fG2s}CY&<1fpg@44rDOdtfD27kyf&ZvP>Y)LHY!K~|5C<4cZVZ;I8G}+Z z#Zyehi@d>%%)yPED<1SnNLmUVDTPsxHfc*JNIJ=1NJ%SLftE}&2dMyr->CT^&PseFPfsLF}E%KdYK zB6kV@3cbLxR3 zJctw=Ooy~12sq5dEEbB?r;cdM_|b{SEVjrzFPb z%_KCesxt_f6vmm%gMOiln{3V3gw1CBzS=BNpnM@l_=DXPEeT+N6JWol+%UY8ziP08 z4^z(aaDwK1&SHqpuWUH$paM|gZx(#gv5B<=`5z)e+RBFVW+cZ&4ZKc^P zAwoz|Eil7e3R4%gg7(X>9(oOGAkG^7FkP_9Cuo8uaL!Bsf*x(6>3kv~B~ncb%Oh2k ziTe#LO3o&I(l0VKV@Ot1xP&SAg=E00WT?szgr-ce0!`$^(jm++rB8x5pD_T0G+iHj z`lB|*$j7`1IMqQ!I=1yn7aiGC0fN`zo1|R4ia{;Z3hfF+RT4#2J=4_C7Knj9fYd{f z)Je@rDx9TG-BgBEum^*T>;qL%wW0nnZGaar)j|`_ep}UWY}M9!)#Z%USzQiW<%NaI z)l89@NhwkmYd9nw4`Ce@U6=+ZHP-qVH6BBTA(h0X8Ha7C23Kjh*Iz)0({+9ef^t$ zeUK)6zPE@0KX?QW<&w#1SiHTn&RVI2`P4s%fr%ZRFqME6=&6j|FpgEN;?dU%bXI*Tk%b>%JQ5~?E}C?%Fr0x8MuRt5l;B)SjI)pPjK9k zt<_?XT=baEmJJ?IAX24b$LavqWa>`hQjeN7RtVg6i^#Y86vj}P<(4SOA4n1Ki( zOr-7B+l`AsaN0HX-QSItH%-@#M6ZF$TI3Z;2K`#O5!)Pj1yt+NAIP@rg`l<#Mwtv) zJ}B79Dwy%b+h?qdOg-Q99aRUAfcAY~aFpK<>)f3YS?BzO$URxz>fipYj{n^)m;FHB zSd^GO;LsJ_2rQaJ{%j9wI2sn90TyUugX{rJ%ndBKuMDnFZMDyb!p814iNZ=EJ-vV-Ddtu=!)D4P+Cp+Ct9S17Zp>d$y*S3In1=27)>p zo;pf?+y8otEpUT4h-Z18XL_z@d)DN;g(XjBurMUbAtt{}buhx{z7axG)qMz-6UMJlzTDFILi#7Un}t zF8PR_`yi0Lq#pNt=^1e5nJx%iv+1VYqh4#48r*4}$Y$F^SI89JLo!|+5o*p%WLJ13 z+;;v~21?0?YU)WoP3?{9aLWQWcmv~pgQ~9T<6iDJXanbdZs?9~>7MTBh6Cp|Zs<;K z?cQ$dc5bl->+aTWuD0s&PHwIKZuAE4^u}uL=5Dse1H0BnI!R(4s41s(=nm`aC;sb9 zh*dNi>|!Wv?BwF>0ENYlINc%OULIEEp=>ZZV^+~I4%`JDmDLejBZTzJ@ae<`Ac(fP7j{X>Odza$ohU_p$qeOy z4(NcHGy^zbZX{1~C0}wTZ*nJJZs=x%HDCiOuktil11z6%>TZK7-*PYiaxf3`?EbFm zHfV3{jsrAEU-_Qz3P7DgdrA)LuyOdF{SMh#H56R{0;~}5h?5S*W^BZkf+`?j<><~W zl0*g8$CGo9{3se~K!&EIaOx2v9TkEfSOE#>aQn11PqZTu&rh{lD-^%M6IpRW!ZP74 zq!`y)9}%-Yts8e$>dy>t9`A7+2JRpq@*+R-C+~G%|Mgxkb2yN4D#vmuHv= z?0#-yXM;8Xc4?n>CQoi8M{|iu=%g%-)M;oR;_L5`!->v>D9};C&htI56tMh5>8Jt& z2lR~|bjUXJo8zCYJYiYH*@WN z?y8pZET8f~?M{cY~gXd7>}+6@ok8F|lreVf+C{^@*ca=c7154b6H=+$<=m7>`01hSyPdo?_ zujVL|c%bWPzt@94xDnyI?WId(zq+d!W+1bjP?JCTM4dSx|HYLj@|NdyqrZISmU-(= zZs)G~ou6_x`202C`6G|^pAULw$9&b7cBI$#&~Sj6a{6C_bJ!97lsS*pCkT(o-kGW& z*;*mN<*0(K_h>5cdU(efBn|1$wXsC!k5st^4y1u{9RvtfOsQt zAi;tL4Q}H}PF9>q zpZ*2=_U_-m_^`nP{rVm@Y~aA5Lx&bET%-X9NdppCphgTa^bkaAm@yGWY%nAcLlLF1 zhDQcsm?21TxPg*N5x5j0XE6z3lTA43#FJvx95#+LMLqFECT=n16jV*jSk+aTWVIDn zX?5kJR%8L0)>>^XwNxf@QE^2u+1%Balu`z@;{IZYVTl+w+~~ps54r>;O=XugAc1EN zboN&+efhXQCzxdJwdh4wRpQ!Q8H=lj@d9Yu9{{<)z9IwI}Ype*;N~^55;+m_j zyQ)gxUuW23(eYwtQtb=YKJY1CSr&%ZBU|#!sL|VPAj(fqESyI zwbUo)N^#?iJy?a6kVOhPl5#HZ?oI_e82uqU6Xr_QIUe*nVjhOcUf z5w_T2e_giOXQQ3A+H0F#Y^|?)&9>TZ(_OdS%;stkhbWB(qO~l+Y$~ zefKwm|4`39gU@JnL$gvecG^Y$H=U(PYZ?qyDnPBC)a+4BD%SYvx1WEk#vyKE2m=AZ zV88u?g)0|q~MZ~XMVMaRaoW4TWuhI2o z8{_B_4Q4Qd9`t|*R-8cY*%U(!(Af`{>6%W^YhFqlkmPhqH==FOiB=q;n!wsrF%K6xfl@`&_X!@=(Qo_`BbG zKCnOj^^Y_F1X}|Q=!O1MqB51LTqP@8>B?8a5|s`VL$5G$pf(7wlwk0JDHrI=Ujj3j z796Go#a2rJy@Btc!*V`b%L&)@1&A9tU!Sa^s}EHi0nTpn@ zX|iw;h@v*N6SC+ZF{(3-Zge8UQkg?C>c^4dmQW>C1<|ZyG)cD9eefesF?3-go<|mZ@;X>Go%GK){)v4Ouu2QoaRbG-cgKuzFuS)3!?rs;m>NTn_!|Nwyn^Km#Ap=RHYaf)#9bv!8uHLa(Jz(rDqds-1D&SbI^4AgV!x ztWeBqi^z)RHn--Zj(1$)NJy@al1W1aDuPjr$DClf?@_LDsn%Sp@w9(F1u7S;yWTIa z61`&9t}Md~H#K1OykkahDw_&s>>`ksZV*I#Kh#3b)Yl5c;01o8=`9-8@V`h=Vm7Ie z*DLq}2vb;%HxX$8Mlmr&d_@MC`n~U$xCbUlX;-r_Jnc% zi&muam3P`d4s3b4U)bfFyA4&Su6fO;9BT%>CI)VYo6X*Jv$wa(Kw8pSzG)SaX7aq} zK1UO-jR^`j{2QY|2lx|)j>;8w0R$>A+R>Q<%V5t^FNTlv!4a15Trgc>O>YCk7eLh~ z>qF{~<8uaBSlu0F7lajyj zK`F9b`abbAIT%);0hOaIZ7wn$#a;rVeCQKh{bbt(T=+t}y9$OcfIaM7_i~uiJ?2w_ zLC$Cfx7pL(_3M6}tbC~Re695W2Qc6SSm*_xQK#>P@!P+3eS#GT?YMzEq2T@pPlhO# zl;DOF(Fl^C{8!#3adpDw($u)Pg#qT+jdPr*qNmTtNzG3l@WLlMA;vCvpfNeRT0|{> zdCX}&bF=w+XS}{%uh!u6-^-1JA9RkSkW5KQO^T97A5VFf-t?!>z)PG3!V`oLgc&^7 zr`Wza2d4XWRWdMyAPfW82`KiWwq4xh1{)ZnntpM6A?yYeJN>!v{c(Rm40@pZyszbe z2Iw8%;hQw=7vKfn6%d{j6y7fA5L_T0q5**vFrMQ*+T}pn0sJm5dO`;4DbnG5Pr}w zjTQNg75#+)@g?6RT}O0e2TQS(a9vV)JVDfn(bEwWWUvty@WLHH!3RDx9>h5q2S!*E>B2N{LkRZ8U;ITj z#M9@MVD`PD$CVyG(ZC%LLm~=8@MOlyv0SWS1hd4=4g#SN5~J{ajYp|W*$fad(O)w< zqcd_rG)m(%Qe!o49ndu)(IH#$amN$}Llsh+v=I~*^*|Ho{zB?t0rpwj7)qDcnIU)? z)7K0TEqM^wR9BnXp;m=o`{m;ut|2}83i$zKJ)Yb5g`Er786h@+Aqt@1eapTbM!zwj zTkOI_o{K09Tn{Ch6C{H#D4Y=)NyB;K!xfR`NZb-3)?9poDV`!K`h{Pp;$O7l1%Mt3 zjvndZq$hzGKdr#Xtq!;F0uM+41IS3WnN; znZO0~WKHlH-w7oH#GpdSUJGQw8U*7qLSl_H9$}D9c985mTZYMtj=cg0%tD8XKoV& zj%I0&MS?BZip~oL8i{Lm;1x8Br3Q$ef9eO9O{lkhUH8tf$>0xCiBBb6={ml~>43Fy}d`W8wd4bsIY5YYaon?@fwVo^Z>!4L3&emac_+$o;w>HYPo zVh(Cziovmh4f{3gvN9`!UMXUR0T)cAJz~|NQfQJZh-C%$T>Ws|c#^04P>eq>Y@hZb%M>d zy<>$+s@*MY!(M74R;I*$sEH&)i2jynMv^KJnCg-s*d|8Wkg#g2l5CN{syZ=A%C4*` zw(O4fsIJ1|Z;D>c-t6iiVv-`K46X?c{zursVOGuVdDU&v7H0V=CiyKbl{PJyZdW5Y zA*4(#(LIuIU2WFVrxj^o*aE@xLIw5RqSM%EZ(dsjyHyihfgV+U8%h?90OJP66o&((DAJZqDW+1GH|Mhz9Jsz;?QsvhwTg-eJG8 zUC~-+7xLo zh)4kHHKgHosFrX8F}#Ilnu{n@K_&h#X)?tC3-Y5K@Z?^gV2Opvu2Yks6EZ+>t-7pZ z&}0SAY^a@}224N-6lpMYYE4|m2jAW6I;pnR;m|H9nc*MUdDYPh?I|33FRBefKy5bxCfaPf%;Fd)M(IL#Gyt=IlQ8~tiEtiw78gVXHh z%>L9A16BT!9iRICQYwA(3U?PQg&WeM*%yd$VBRePDbNIU^PBZ)7Pv9|x=D0~7R`!N8=1&>rgLJRU=6>>t0Bq#a^1E(Z9)y3z6ZX{zt2H+~k zmF|blEPi0}2^6U?bSh5#z)*%(XoT=6Yn}Lxb9SY$Esu>JKF}T(kQdvuw07w<7Gc~D z&_BX0?^f%VQezhY^Gc~F`V5`&Ewz%p0y1Z<3Y^gjFt54(KoAH)6GZbh5Q7!;032T=6TGeAmi{L{I_q>kR%1`k^ctF9P517Y9rY-j zkI-#L(m;}>JoOO&fK)qOxsqcLK<^X0r7(CkSkEQ@ko8Xi5VEEwo>>v zfD5=UJi!kXgD)Jo`{@KmeWdkx%8>e{`Go; zpP#w`8kqST|KA0yIcwLin@1!!`u=1LWpUC{|t zmQAM0wQUU>#KEgvmH90YRyre62`16HUHQ3JRdG35hdi03a9Yoj8X(ISz`z@`1m0c1 z53sr6+ArYY_qdP?fA0dk%R3d|xt{a+I^8>iFSunJk!I&Rzz=jJ+B=Q@IuV7lY+zIa z!Yh0%Za~9#cL~yL#7}(w2EkA!r%GddT%T|(m3OI+jpu`Wiw`E%ryoz}a@d@FUuLDs zbC>zJJfy(HWlmw zz4L;-qbh?dID^ZJN%}iFL70DcCg%dgl`B+yW!shwVM2x3ur+i@4I;H`cQRbKumR%+ z4;nC5@Tk#4#*7;^U?}lKSV~?ggGeyo;6cm>4je$Rc{4%_7hL|l^z``?=+7=gi54B| z1xy$*YQ!{k`bDWyr((i*F?tni)}lSXfbj}cYuKw?$R-VQR_YhEXPt%tFcoiyO$wcueQNl!I$&yc;h^zr~WfPS*g7ok?!r1?_WoM@_}sYZYVzcXl`Efi^%o_| zmrtT_!ig(YbkU_j z1~VCmAcGcq(V>STny8`*MC#0=k3JI#B+N`g2|bovf=MQtzKbBIoqFmbs3v`q3n|{9 zq6)XF$7kJIlGGyaMBkF5<$HF2PhOMK4zRf(4dW1QX0o#12EH z7{+KQfdmpJlk9^+Kln@o4>aSfGthPvjkFfBI88Ox;J6giOfMCVHR3$|6jV0Y_*B$V zO+B>@HemC#(l*}s6pqHW7JDi(~djvz@w=F@>EJsJ@(v# zPrhsDvkyNjtk9*uUH%Jjzyf`dqK9OHNfE*b{wbW$!gMv{u)`18y>LVZOFS{fPXwaE zKx|%wF-B}6l8B-Ua?FUMfjt`uG!sm6;yhl^Yw0DKW~#|MBz5A+Dkq(U(yZU8LhEC; z&ie98uD&=9&7ermimaBy{KDm!$s(Dq!0w8(uR8_HSet^H@6bTtk1z_&bkLgCz=RA+VA$dQhcAe@NQ!Yv5@RMcR#_~h&O{4Mwah&8tCg?R za!RI{`vR#ajUP+q=v`j9O;=ROYp=cJ%wCnA5kuu?P=bbR&_f+{RB5G8=IzNC!7EN{clZw0jw0U+U7Q!z|90gFptC1V>i6%jc?QX8x#VUt!))9 z3Iw@=;_3w<$5kjp6OvrJKBTUCiQ+^kVjPPML=Dd=1azSTmq^js z*valC6{y{jaJMAgZER&5!x- zCM1Rs*_$p*P{RtyagL5^ng>MD(A`$UoMm$QBjHgT{Y4Lenfg)_E zGDYV>6Psb73!FyrML2=+O?b(Rp8R4M@F9Z=L8FXiOpquxzLRy8v5ZA-IY-f0?2d%g zXC48#N3!uTkp4>z{`OfvH|U{QhYXJc8ejq_G^}oSdmtp|Gm!~aaB#lBq$Yi_L2-RD zl%fP-h)PL95QQjS4E|CjNCk3*UAXX-3;~@iud$0-(i4|F`pltp2pSTY#D|O&=IlHo zG27Y1Vmo18mcWutrONmW2-*Qc-W)V!%8;Z-mK$pCD5sYxk-Zx88&Q;*4Fz1wl zIya*kL~-Pu7HR7|OJ~pbH+hekC%Z0f-iy*%waO1sI{|QQn{cOs}O~WPn9mq)*@BeP}QnflHPW? z+ubm26E9y3{wL15sEWVX=}zV}hB1u50u{(pGwsws1~3v=MRCR=I&~jDMkmz zw6&{Tkw88n+nC0Z8r9fFZZA+HIRY2Qg^G-DcM**9q~y3ciiweELa`({XLm_<=5$GB zm6oZ+y0fWfbG%z+D_@zbbdggjzQ~H~<;;3zoyA{trru%{K?RSQ>t*1{#tUdbeIB5v zJu!mMIN-6sdG_aD1*>O2SM9)tHJ0s)+<+4(vcZnj8-y!bS^QkKlH2;$X$g`E9uz~v z91gAiNHJ8}3K_0lh7&c2P@G~Et;j+xKFBL!OpR^uq68DDvA`~Z;~YySP(SD~1A8F^ z4ItN;Lmsk`bz%mpG)KuA!={yy9TezHQw_jU6*WH=9cy13+u7DO$f|d}Qpib8uBek; z#7qk^7sfEZFh($dkmhwt2A(9rOj|c#Fpb>M&UpUwz6T6ge(yWb1wC9GAEXdmb4l5R-UA?Hv^H*9KD?`ImTej$wa2J0Hy zxVN_UEv`w>gC7657f|?hawj%1V!OZ$#vzk)ldUnb{6yO?)ApFAN zUiGHf2J2bZ`qpvPrdQldXMO>U^;|KFF_(E2>JG*a(k!U9!uy@=B*}g2Z6nWwCL{2> z^S=v9P=EfhYklNSLlUKc#_4~ z#Z3!yqx~1iSbGYD7Mb`Cjm;XUj0WuP+d}0~G;|^h_HKUkZSeRkpb}64`~KWW|V^?$q{Irh?MP>ZH5Ddc*4Cz7^XaN@Z0x;-A7S``hV8QIrOE3%r7?8mi9D{t; z4FBM*t@!VB4CVjwP5|#LZ3dAL3(*h{aZ?sh5d&)991sExh$bk|0x>YrB9EdRuJWEh z1T)GPN)TUsK^J(y7gSKwK*@1PiU@!JQX9sDo z2Y0L1a4b=DuV^BJ2!9Wlj!@^2%czR4CXl2FpU_jTkXAm$R=Us{uTk2r(F?_p8@q7~ z@nIIs@Z0F5PSy~;`a%}YE)El84m06T?hw8P<^JsN1o*Gk{Lp+LMF5pg8vD^7{}CY7 z01*$7YzERGGsRN$4O8O4@E*}^B9WmaU$L>J7I9GrV^pQYxV?`=ZhtuM#VJg$!rm{8WMd9MjNd`r^FOOAgT~jp%V6 z;cHR$ub%wxXhNcnTp$aULJI5BF7FaAsV^Y+vLCYs5gGCU4-2u51tubL2JWCDQ{o~y zaMAeY8aA?8uD~PvgW@{zv@*dLc7Y^U$`oBNB{4*DGRY-d(r{oh7F+A&Y>^vs&~s+5 z_I9$yGGHU*4Sj%;7)M|zi}DDMGT4-Ip&Y;&ozN+#k~oXgI7=lflT!?9CKk@GtOSEj z$ZRjf4mw}KoQeS%*6K0bPM+jZE#>J0Xf08Q<}L3@0FOiZ()0PeLi(CdI)*OWq-Z_e z^V-tWKASEttFRyW-~o|jFpotN4>K`0kl`SxqMrUB;wIA=eW40YOC&XO7m$H7TM!jV z2{buGG)L12oZz&spww2+LGUFLieWW#5r$?e#(0qeDq^=}Gu|-6Er)Rkag!*IvDb#r zs1%Q(n(-N-F`$kUNP|=xm6J%tuT5Zq74kx7qSJc#qE77OFRl|AqM+?q2M^cjJKu=b zE`XOLLv`ZvWJ2aX<&!<<(@f74P0zIGeB#>D)J@rQOw+UK(qIcIg0MhAE3-jgk>Yq@7m{H@Ij%z0rCmUhB{h^l4(CHxY8OHjUr5wMPZTF<>NQ!^ zBHXFXg3(auu>@$;2z%iPeoX?1Pbqg40{)oN34QcOh16DWb^D4`SGBS(kW@+AF#WRU z71R;k&WTC`W!~ZuOTW_uwsbsAK$pN2J?-;NtJPYcZ%wn+ce>S8UNsP8z zzco3ULR`s}Fd6RxcwkQ5nR;5yG7Biuh4t7&vNnx)+7*1dUG5}P65n^SuGE{*6hBDz)H3KrX0SdFwjPGMZ zb`gbRWRJ5~ys!&XR&#-+W&$G>S{7JmR!-bd7G~Bj)Xf-f7Fl<89_PwueG#5I!aQBz zWT^F8f#OY-R(F+_X`9w**+L7(6-n@vYI|TM_;leQi=w>Y8oCw=GGQ_$jr3lTY_9+r z&US_F1z#F9LqHNz-*!VORbX|YL}t=p5mxs0Rv1=50RY!w+32m}jvfuS1P~WJRCRVF zpm7(k0s5CFkOX9Db#g2I*EqA03pW>mITs&dp>+AeWz!E&W?}uJ^A&)>7?1%70EGmS zm1nW^-JF$;I-&v~fN0gTce{0WQ#jd9*mu8G474ByhWBlLlcC1r2aXqO@ka8Nw|OTB zdgHZ0F9bk(;0o*&^a@sc*%l=;R8qxvZdIv#?RFsWmepcJrXb=Ph=F|rH@_g3jVMC} z(#(DpbC@!=Rr$An8#h*)a&jq`fC)H(5xD%26wJh~PG)vkWp-FA7#WJ82ZV-o*=aJm zGah|$w=!ackxD0O*MwR4XqEOTu0n-1#*wRaOkdbe3A4}`PXliF#~O(PcbL(@SSPSQkE7RWZPfbu^9rca0^tja%c5ZB=u%kT0cd3g>uqk<=B|&>REf72NMH z#*B}Hfd)pv?a*>)vXrh2IU_1&k;Qa6D%o1KRd;%4lAVN~-Sj?RIKdK+e;FVHJh`!w zSCpfLlubE$0p#LRSrmey2c*E2$v1mjinQLPdppFIM>B(R+0^RxZqb)tP&A8IpkWR9 zeKDgaXP^gYfChMg23EkCq4^0gpa3+Mnya}P12~(BV^p5fjkj6*eB}zI&IHJVKFaSD%$*pmR4{OD3R~cCF8}pbhIk zC2*4&K%p5rhuhljd|1zkh-W-g;a!8 z4RSRgp}IGwW9hJs3`-X}1H%oknz&z~7R>Fu(vA*U;H#I_JF}FA4tWDOLWIwnk*OoD z)6}1_WyPt2a58AE^4X^VWhe26~|8zb|8L%l+(g>S+*X{`t`%+b;l`KS-x%Yd) z{iU(wjWiJ0%?2y``lZoFKj_lTan+qJQANGqXQ7r=|MaznNH4 zCHp}J9CJd{LlHcEIeQweVZj^R!A1K5YEBr|;W5*U{%C+A8EI9`_>5)Snq^fxG6if( zJXe4tFRLTPv2Dd8eY?7<#j%6Wy?9F%bmcLvqj9uoXjiK z%>Nb5M2do zV-@HW9jcXMEs*2UtxeMRo!`BS(p~)0Go8k-8cN@=oKQjCJ|Wb(Q!Twy9!Uo_=?T?i zb&=bX)~jlFn9PZa?&8hV*3FxSkww>eok01z%DLgnvz*w2p`tBB7uF8h!92fXnX$R| z*^q@fA2T%(AqqK%#1VxE_0 zp4pq-*+<$XGYCSSctZKRAh6v|xhm-!$bVtw$L3h)izTcxkbVIt%ie>@%^dHs~4CjP(jg*~tV1R#R>$|OY! z6;Z)}I7#M9)UI8hK4rQ>u_Bd=7&Bs&62*wej~}B@$wV@uu9GNFO1yZ*S2k|hvTch= zGbXm1II&TiHtiZQ8VWLGxInaM1`S9xm^iUi#fm1vps-M(q67;oydLt3YQlho1`-^I zb!I3rY_yOVCL%G%a<>gzklTd9!$8f;lqd%D_+dl@E3ge zV9Ao@iWRI_uq02;49gX)S)gBuMg`1RCKe=2s6N4(wd&WfU%S>&w06-7xYM?X!Mkr< zz=9id@jE=Y@Z-n_`;MD;IdioLJScIkwg_msL_N}a0C)a zPKZQOCMKnHk|--t(b7vX%~X?3Io-5VH9i3~lx-IXb(B&jsKCNhKnP<64J)i*6;`|O zvK160cm%|gp8qP+r4 zX;)MQTWhh!x!MaSAO)js5>%jD3oi8O=bwNED(Iku7Ha6Bf@--Xm5fr^WEfg}7hZXk zo|oQw?Y#$|eDvMNqJI1J_umsxVBw%IsG>^#5EWG{xR5UoJ(!7z5ne%|MiyT9krN}8 z^ofTkh1e1{+{|R+i7Bc`4U0bob*E8CnLq;%G^hYYHFxlE;|W(~wN)=f7VuSAVc1#Syfi@x~l??D5AShYWI`9V2S;$tb6+GRiT)5CK_~ie(mh45-(hd+fzW zUw!z28fp`xLUDzIyQCV-s#P4wAgm8XH0u?aRAJGr8+D|sN4##xA+WkQZR@b(7<(d$ zDN?g+iwayo!37mOD^v+UG(oK}>+Jr*W3^Y^EDX3Lh>$rB z^6j|iuKVu1M-bjwG0%+N2~g<$p3a?m3Mzk1Gy&ifL&s|LCkC}zOx)8jEi0{3+e&rS zR~zA<6dabsb=OggxC=F5k6lx;W;08}0Tis=Hg32PReuI6SaC<(Ch>Q`1DvH8 zaDpfF0%tkn)O?h;JW)OG3ZE#@^e$q(=1n6QvGSUTzBi#<>1urC>kzOc@-$Jf?^y1G z-`RdbztiQZhWgXM3hq#i`M9GURwzN}RA7ah4XH>9jAEtEk})ncNrKVwg5fOq1$TM! zlc40Jlr%`nQYr^BlT*dMAO|oqxq^fx1V%BOkb=&sP)-bM;frLH0Sw>?bu|p8FmdBU z9SU(g$V}#Vh{#N4`jCgsWF`<-=fr77lZZ;BChUgTzy(Ticvi&Xrgp|f{qV;>Uksxd z4G1ae{1JtJ zR>%fB+!2p;Sb-@ReFA%g#FYX>l7VEQWD6__K}f_#dM*iSaMuvRLP2( zy7H#4B;lH5*@P0<@|Le50SR&m!x+uTPHmf^LxU+X2UH+WH*3~hvtkDE%NRuS9Zk|U1#Ji%VdV#zx_Np7+d%8!zM^sPXfB=nE_rK9P)p5;PkI-Q)b~Cow?IPy;6b0v3274Q`|Z z3p^5P6*oHmq^>BCT)}f-#+Z~Hlb*C)omA;cVTzb8`1Yl|Jr0*B=L%I;hLvE->A=VI2Rv|s8Qho!Pq>CKfUp7#=-3HDdWeo(3zi^PN;fX}!gbVD z{9(=Cv6mhHA!c zn!C&IKh_zUe{?Et7&spBpm!{K#tnGQ+h=70x|!?^BBCd5E(R|8;)(|H3LZVzy8f1v z(gPk`suV$E4I{d+^|2>HxgtN>(%drpH8GP>srgOHoEq}2N*U3 z9{pe!P*{ObF;D_zXJr>UI>C>->rI(KTiVqo90jrMZN(X9N>bX(29-=0e92-KvJf}G zz=WA|i)N=lSa)mY?i$#9*#yxgbG`4x@BBrq-*2un!}A2&Lnjlyc&-3=7q0k*J01d# zceq;FdT5NBWv!N9G+7dAKrF^biyzOAH;7^BkxQ@S41Q-6QvO$mc-Z9|f%&QFD92#Y z(N>wT`8IZcS!e_*R}50XdVO_RqDCPGaVv|3A+zTzDe)^FRuP#{3^#{!Ij1bkB79{g0rqx$8jyT& zk^vS#0vMnG6JTs4Fn!*lBP(!ykt8wNCurPF8+NTzM>!UbKRQsviV>6aMmCn&fB zI~;&YpD_!pPzs(wImtyPu(S&HR|MG|L1@Iw{#d_0nfMobQ4gB1ZaSWxKM=W zZ+up8lQdsZba{wJXA4JXji+Y@CxH_vc@;=s82E{bwpJQA1C8W)9(Nx;a}A?cdL&ph znl>xeLj^ui46Vn4-qS`eNFf&{gXFUzwWkXX0U;ETgS*FjmsK`DIBRR8P(x^4Mfd@) zffPDc0v4bE7O((Mm~2~t1uLLpWkGfEm4)8-eb8|@TW5|@f#p z3UC;IvG591#!8;?3adaGq7Vg7-~{mm1Oy2LKhOg*)&mNOkTk#oE5HIYPy&TGkwKV< z7TI@<7fj&QZ)VX{H!j5dlf zX^NnSip%2-!oZ4|P=W{|Si#_vD;JbPsS#ID5dPNLM&aWhr=~Tv=MY`fJ(&;;GGUA( z_IuP|Vn0xo^^;Ioc@#pId>*g?Yi0sVm||i{mQV);Cy-Eo`Hf#;77vhxjlvjo87OwS zJMO4WLNrN>Ml*r=pn>_9fC-@wssU`68LB`EmSIx>!7TI0Z};VFV^N!wsGGZKq$!CcmG_%0 zDWT|LF6co4Fe#H+#3`NvVEMrf$f=ynIS|fS1wQbT(pd$Pg^Snua@o0^zF33a`3c}D zp2gUE$cUZ;;B(FRP#XZB@~JjTaRDi&{s8y+bi4osCO~Wb$!N00Cv6)%f0CQ1L1>E_rqVRen5e(Q6r^lEgEFul~T5Hu+ z6lJz%Xtp*iw5bK_bXsvy9;L7ns(G5Hq-zCI6$?^l5sE5_Oci*s80(~)ny?`&vLsuw zrCYjzSpcBg8JUr?s!&`sg{m%Vs;WA(Gz$zhTeGDxtLBNGKKrwU7_>q-yhGco6=$^l zW@v$j8##)JP5S__wVVFWi=(e8y@^J(zX`cd`j;=6wK!8({{@0x%Zdl)ip^OCWgD(& zs|!&RHRg&DW%{=08U=A%YEb|+zW9rH8=iYhul0HpEK&_XkYamBxb-$8`jY`P02BnU zxb0Dvj;p;NJGz-F!DxAfQk!|asgh+Op+5$#qD!HtJHjMfx}bVfr7#MwyRxxcQ!Hy! zj%m9x%v{r;4Zi1l+rV>zc&iIQk(^~4rdhl;nwCRafQZ&vd8V7+Rm59Wc)@eM*qeD4 ztgs2xy~bm$TZ^UKFuu-nY2}M5zu=|ldo*WT4D3s{?@LDUE58Lnzoljb8dA3r@hfy3 zuf9MHNk5RY$cjV;;PD zc@6rp7L2JS+{q&hsxF(trQpIXJiDy03ZfCBGAy%e!m1<6qGOZ8J+J`J7l=KatJ;MW zFCxSj=>Vof#ITvXNxM)A*l-HhP}G~6kQl|;8_7;u#WUc&GnvI&yro?nAl!;zsiL-I zI}kju3u&u0ZJWky+{X1AK5?ACbIfuR0X27=$9dGd)gT0ZY{1MI$S$Nd2e1M%iU5E* zeTy7*9}KyV9I^@&t=h}Xz%$9kYJsvDn-h#4DMrO2?8z40si3;D`;x*MI=e6XL3GHE zc4)h-{%n7&S`1N16TFwhJP?+~2D}1z0oS!}ylli)_@5$K#KH_;YNk|g*1THf%gyVn z$SkD3EY$pFXbSzT&g{V&$OF>6Hy&tL#+jwoTwvLpU=88EWt#~+pbN!7t~95{l2y(z zs4MC0E9+dxjui}e?7zO}gV#_EeO!El8-)9l&lFGqD!+g<-|u_fE!tW441)>eQ`~_y)=^nP;ET^ z{Z*V*y&qe;tquZ=slwGO#{-`b*5aHI`HP0#o*enJ(@neHV+fTwB4GMYk5^%_6<@dWLyKXxt1`f~vDO#=&H-%zLv`E3A}J)xZJssBCD(c1pOPA$Pq zdcn=Cy_l-85*oT33!x0I*^uE%u#lnSMrHWevJ#HU{HSFazEi=l;T*mV;~)$)0DU2T z%cbewCVt|s8Jj*j>c!iYGav+`bq7$;12eD!%QpkEKI^nD>#%+TdP-Hh_5nJcfXmv@ zKYq25jOXYvJV49C5a<#Hs9ncxd7 z3fFOs6JcJOLI~=%wl;cA0dTYF_6-Eu$O{(*=M?a~ThLBQ?9XiH0Dh>Th_M;y z%oTjub6)74e8T*q=xf&*9$j3iEa{VuyOz%3nZD_Oc&oVz$e~`-rv7e)!Ajfpg8}@E z1;pSDqXz{u@B%MT>o9NYF3%cTsg=7HpW+o~G|o^^9PAl8#Xo-mJ#Ys?B2L#D-J!uAR3Oq3N{<1RGW}2>1;Km+N1XrW= z-VU$Bs0+ye&+)9Y_F)C1Ie=u|T{8V5J3K8UfB+M4bg=^WE6}lro!NLF$#rhExL>Ju z-uL$Z_X zj0Of0B}rgFiNOK|mMvYflt4kkOqw-q-lW+8=T4qB8!%uHlfnfPCP9Jg5}A{3%nWDpX9LMr~Tk zYAI2oEHM(>NDZNp(FX!0WHSgdwJvUIHu3FvR{2O$7 zkVGQMq!v+HX##J(rA{UtcfxVp92GR-2P=3XrkY-aDDub(E~wz9CzYJZs|&W8k}E6! zUGl0exvUCIgAWGQzzljYldm<2fmpCM--J`n#vaS0RXi=@EE7;f`Nfw%K@)8>Dx@HE zHP=>lO;Oouv&~UKA%%3(D~7Z5QvT#J#pbzcI8{K#PeYYpR3t&QV1*MDm|#3!T;=7w z^kPNOfLeD=Z!Wm}!Z~TyI=J<=#0CmLpfYe;uf8D0J+Q&tOpKdd=ERsh$v74Tl#eR|0??96Gqkmp z6I~+uL|ukiwkV{?>`|I`8%;Q5a@JY7OsTmBI_X~8j%cHiWXXgTRQh2rUG|dN09gl= z??JN5j$JCV_s?3e060Jb{_<}@b0JpR6o{-3$qhqrdqv#n_6mwXgcf+y2uG|%j$-iO zZyyX?N=!vK3K;Hi>{A@$REU$c07U^SxPv#^;SFH~;c^{Sy<-) zv=Fg~Rg#M4q;y0oxz2xbkzIbGVj%q3C5j8XLlyy(J849$Fcjm4?|}D&D`ZS~yU>&I zHsgyTC~q{(JCw_86FoV~@p`$*#P&Xcr;>TkBjGy^&#F9ffNGC(T!VPFL( zi61ZY615pvEo=1AAK9RYqZT4?fOUx?*b4Z`{V8ZxyHcP6Ge99_g$P6uY}VUkQ8&9; z5O2k>h~7NH4RRpp^DpREdaVcD3GVgPrBuYR7C+NYt?f``zoS-Q3$`=p4 zqQeJtMJ%c7k`W&;&Mq|oiRSDk!D_-fCw5YbKk;8Yp`sT5NPtW|z``AN(HAsDOfX*L z-8I7ayNlJyjIBarWlkXqhE~Q2Zd8rRqEJWKOpl`0Lz(t^!^b|Bu^8YBAJ52ljY5j+ zkf2KBB9E2<3Mk+NC$Ip+{6fDO9H0P~oKGgZwn-b6OLhh9-xE(cN}_sgY^Y2n+E$4V zXkDn4uyoe5E+mn=-7=RN{0J|n0R$9?Km@=PCNZ~#LX#p>ndSmYO*F6qwptT&*uiEt z^VLJHbQAt`Sm9DRiAcJtjFX68;an5j`MPbHK8|sUPl59{XW7$PTJDWz~aeH^dQzn9wiC}daOZecxHROS+he{(qi){?4J3eAlcw4h*9UFndAxPqz>!5}Vy zfedX}ii01m(fe zdHyXMZ5^v;1l(kgW~c>D8H16HZ2&Tm&3VpYI2Ed)ptL5oJsPMKkbx6m-~{XX%N>Ag zE1i}SlvB(PQ0p?(=63nH48r7eRY{?VOr#W}D1|FwIgo@1N~-mH#e9KGb~ zS3&4r_a^zIh@5YZ@H1PQv?M5^s>xK0doR-^c&^5k3SUR(00@jTD;s#yq|Zr}2PbTb z@HE(tz;*xy3}65&fWnz>=g$py7%}AF#vK?l;u3$NJi1upDO9ZMU*LvhE)H#s%<1#sk)oVdkCRnfSOf>Hr^KEMMV_`nNfFmsyI z`~@-(!VFj*6~Q{#ov`~43qNfWpv39`1BmGzRNYSvLv}D8zQ!G#00k&m!3rjBRjwDR z>r?!q#GoO~$zJ>#VGkQ-#V)Z%edDTK3>PcJ!By?_75P(1t~jAp#hz(B^zWzhEek9_0XLmMS}}lM z2rD`&8V9}!dJqI2%s>V~2!asEKn6K%BwsIu?SQ+1&hE0OHc*6aRpi6kbN6HI14z`OTBR@2X6ilg&$ynDzw5Yw89lo zftTV!F2o`27zzzA0k(uA6aR~>@_8Eh5flVeKwSfwDsaF+fj}6Oz>9K?w9}(I>8NINt(_4cpV@^H2*Gd} zirXS96tsYD;|sz&yva+vvxyHIl)-fHO48qbv$CEW z(<&L&$^;GUF=q>@UL*`&07hYii9dtGmhcuDz=C9)vM{4OX6%}0Bp~FP#xA=bb7RJ9 zgpX`A24pw|P`Jz`Kmtl2g*M}^Bn**qJV%R2{>Rd6M|V6CZn!gYV9j($hYu^CF3O!W zQIo;zfi$Fm69|HQ$|By}N2>!)f;7nR!#?6%iizX_9_WGUTY-%%f#-zI=uCkW$jA}! zDsQm>5_ry+VyhLH$hO)}?)-r}=qD_&xeh>24nWWK?0}hzCU~L9hMa*9fJFUZ01W_0 zh;+zAPli+qg)~V2gh-`G&K?i~eas?#S}d%DEZ*U;!ytygu*$GZv0zxhV!(_C z6g#q;MOws-w@kLw>YlmWK)ZacUVIK-SQ@@`2{=TYIkbQi5CXzA~n)uj75 zpvKG52wAErn1ag`1yoo9BEW$oD1~tTREW5NhzSx+(<=sJNXKNLvlG#VEiEtO*rhNU zhif2)J9vU700M_}$cX&T?ASI6a36|fitdzCr+g`-#GLG42~)X%l+aGU0j9qp0TfUH z7Wk1WOimV<&OxPtx)U5?0}mCRT;=nI91Lc zn1Ku!fVj9R23Ubd9mrRO)v5!|Srtww;LQcS$9%K`C;s`K!O~=0VSv< zh=5XAm<3vxAQCA}D>a5>;4y3A(k)Gnj^KuCSPT}}NRnVmm12oDyrv_mj(2_0mc2ch zFuF{%fD%xF6!07EL;(~S0Tf^X9%xh`0D>n_Eh?PO6i|UdO->$gR37Ng6Ic|TcRbbq z`^L{+4i1hj9sAhF%y#U}v5(m?vpYs5QRf_o%p5ah9GjvLC9QLi6(u{3k5$^CLCg2& z_y7B^`|*CQ>%On&1(fJ+ZL=W-cbU2Vc=|b$VLUz|o=P`}P~|1`1GG$#l|bLZg`2hB z*M!EE5D%{oFT9>#2>P*byniqq^yoPjU7uL0sZt;A?enm^ma+{sd5#WJg`tD+Gwq;o z1dN0)5t004@uH9!6Qv%AL=j z3i_Qo^*2Z3k20XNM#D3<2$;%h8Dx4AvyUUPWS4jXD*iE=;0^div4fLWw^C$?y-ZTM*50_>59!8dUXrEzP8kv z3X;fq($r;n>$kwae*zLYp8xZH@}_zGdO1*=R2o7Pi|vM;q)=kV@4Rt-B0fW7?>I;` z9-3Vb-)*Zci_yr!xe=i)w?DA*a+H=q++dE@A&(M10Pt7|1=rWDQ5y zJcs|WL_1ftdS(OA7B&@tsvx>dy**+pO1spC7wc0)M-!TPmgWBxr&je8}TN}m=!iR5Xsn}IJ&bV<|K z8wKsNW}~xlfN-MirPv0#z;f{Cbk`24;kM3I+tE3<+;9i||Gt|1=N);i$#i8aoPLxF zI;0p&w|h0a#=L8O@-F$tF?eH%rM-j&B&595^od<0^2finB*qEheZ zr)?2Cq2g24u&8(1`g%Fiv|L3RnDy=zUs!>yh!hc|0C+F4O!wF}Aycm~$-(b_I^Vsc zQSoJCawNNwFMN|U?TiOr(Lk2Hn?vL1wYI}LSgTteRwXc=6nVb!&)rEB+p+y?XeH5}QPo#=&A!)y6zlZ$2?=t$e_^mx;{s z|2nmj-Z%ym&R$8cd_HpIUP5Ou5zm!Gb1Sn&&+y&nZRhy7kaf^W+u11;%A^1+L6KuR zTY0B%?PR3Q=t0(}r=YHhgEhn5MI(Pu#YEhh?5!sUaZ7^n=UUb8&25RU0r6~L#k*Z` zg9mDLpS@NroQ9UJ-HHFnDgyZee&N25WdH>S@ka23{|@4FD!%2|R!HF(5(v5k8{)~% z@~?#Q8Rn2HvjSL9I{9_mAg&-PIyrpo^J@>MqW8r0?D$kryrF-gTCTcU?zXVikHu-+ zvVQ$MpNE^3zSJS^>bP9u(H4izdJ1);OK*UTQ!6;KdY89KZ+)z8qu#JQf60oskvzn!lpE<5OCxv0~1}z*KQ^A$auG ze(XWC2!7!&!C19Ema~(>u9*P#7~KJ#(rhxGsHAL1js$%QHQ1e_N>5&oJ5|y2&QDxf zY~9{LeR`Vt1Xe%ug&5CotM2>Rc$5C03>|C$#O?2{2uJM`O0la0Q9UQ;nc8Gr}EVnj`O!@sL=zCy$R%UzF)0+w@ zQ(shSvz@jQG`uerodR*(fyc9A@|)>O`#|q6uC@u@_^7-Sy^7m~oA$4xZOrJRM3M}_ zTjS+)z*kGb0k6z5uU`*N`kR*wh+6RzG|Dqk&}%gP`AcX;>)?!e#-{hDk3b>(0e}}V zKG?q$zbEh-yCZmX@9;L7d_in%9g3$OqW58`XEF?samJo531ad|RF|cfJFy&wK$DPh zaRKcFesp>*bR5!cmAXVqwF%kJu5bUCyI%JaDq8FYCeHn&42{bum$17a( zLJ`iEnVVD||E25u>+)Er^p9qv`&@H7+0+~Wp?)ipmtuJVhIT3h*%)U4!nxA^ zPPI2rg(|r>^ijzh>S9-Bcg%EJ{|8}p|55#$>tPxMM(h-CEc=6IaSosd{r1_UWV z{-XQqkcNs;Bh?{*vex$eLK4o11@;=GNLsZ(P_KH;GP_-(SNZ)u6^)OOxR^mI_wX{eCGRdTmyn>AFYLb>vW zv)7_iZtv#5b8_34#X62(c<}GT5}Rk}8g4n1EoA?!;kcF=wq_l@nS5-$|O zTqfu6>Meb0WPn6S+cG&a=Mf7~1%DbaPlQb1LQJSXGm#>d!~+HhE=3^%4c|rqicCL{ z0hdg^EXx(!2>xnfnSY4d09q;f(F#f0$}J@@0tZlpnm2e;?x=QMUN>J2^{5e}UO1%f zWOIWu?{0TABiz{me>P^;QT@Ibh{t^1#)@CBWdVH6JoS;6d#>_gnR~xcIlZg5dYcO; zhqS1=a}^ciy1TFAMvk<1jOJr>;)F%9KDq&7{gSEFF8JbZ3y zG6Wk%rB~>Zd-5aim7Rw3l7G;$?zJi3m59CVa3FLguJ5_Nq!#41KCd3~gq76rm<&81 zX`|p%v^3QC_cxGwBuiM$2`?4lle}0!MbyMK)f|?Bt zCer?xhEN%2XJR}IzGsO4<(pXA6QbYJb6s<=kHlJl7l6QK2+Hk-_# z!kd#Z9@lThsJv;_7kI}Wm>EK;c5?*tEqDy)Sypv!G10b_X#};4ca4;L5LE6Rta^{L$n(tZ;o`Tre@35B=4ARxwCK}q zZdm82@Cc9ecG|(M6Ten`_$^I;drm?4 zYvkwzxn26_z^4e^Qt^z00VaNa%N#UYMNdDS<&2Yy5}F(p$o1)KO2k_$RfHyprxguMo;>iZPbeuH)0*`%Wl6~$^}S#oVaYf;_1cS8j7FL(EPd%eB=@U2B_ zWvsGdc+D}>_eZ`ZIwYchG%HOlJ(k{PpY(NbpZ&yIH zqWx{smgFYkSBLzMcipN!H`rNwerZKCebrwwl?a~g2jMwR!MlPm5hpODd$o`D<&&rh z&{lGD@on9MhHPDoZDGUC&HBkI9JOM&W(!lMYPmdtyiq#jhR2$|HhiSXTAKlQ%QSv& zz^5M&(%cJvQ=6cIvI2!0%W{hD3L>%{4p}{*@{-s*y^QVom=(oq;(JI{4q%C|dlZE2 zcd${90hp<+%_J;NA8eIQt$8b(^wHW>K$b8&t9P+wMf-Z{V7-9NvRnr zcevX!Wir)UK49-U#-md$U(n*hWA`#AU*}Hhk}Qh8_SyUHP;9eB;x!A8m7{ZP)D<{@ zjvBN`{BK8}6{%p)&}*uW{3kQE<`e@6svBa%)@QH(pn-SK99rZp4t+`qnn?$lnk@rw z_?~2Z;WVmB`hIIXwgwfW_F5E%%Ugx;2=NK(<#cbTjTtVAjRsChc6}fNd7iI{pXvLS zUzWR$ePG~6({?|eyM89pW~S)Q(<3_n+njo6`Z0=TA>n~4#B!B_EG~L5>Fq2W_)qqB zuHQ&2?Xmow!F?dI!U$Lyvp)>M>;OhbT7_Qqx*j6$)&r#sJx|?1hM3`nQ`UHb?12cg z0WL3^R0q_( z=i(W%p7pC>Vd|k@sXIn(dr-HZEAjq9H^{5&X6+Yd8;CB)n~IdXH$*RlDpjICC+sax zJg^-y$*I{iI3X5J{b_YZ zE(e8F$)**HJx56oG%sLaJXpdU+$q(D$6#lqO_8B0;hQgFKy=1uMaGNdchO|y1IHrJ zr^rCCK3iCRoMB%Jab;_=FJfz?9=9A;hgcX-;eAIwn3nN-!u$MEvA6H9n%Nc#^2j_m z6gV30H<$5p&cVWt_t`%0c%&DF00C>p!8FN7teUG9xu7LOM+&`m*#)KU#k8eD2<(#Q z4X;*QWDF^p*zo6a?}xfWtc(Z1hV!*S;g-}?uyLoA9xkxsS8aFMN-bKN-x`SijMJoW z`P-lV9uV0$C9$FY^hS#GW|v#-HVwPa_$Y6s6$s?zFuiMNaDXqhCEr<0v{PVMYIiiL zC-}kYiZM6XBxv~44nr4X7Z(UN>##QaQRf(#`5Smtk1_+#m=Q|@Ye^m5g#xjkm{_&{ z`g?rOu+bCyL$_oyX$Zv6Q^(JHdu7lLj!%2d@4aH}-+6J@bXAyLe@|HKPecx3hFHTH zS;ap2+$olSM_B(3S?Dl=Qd+8z4B#iM3K`JH)G6R#pvaV*2;slTmm8+G|3!w6LGoTq zc3;E-!mZuu_#qz*nOI#5EM7s|Idiu(8c92r>%K>elRKLu;mfEe$F zj;LLhK9C8x)e`WV548`#A4KIj3%*eL4ydI(g2&Myy2Jw#?XG=eG-g3XU{$dS#pI5Y!k;- zrIZ5xpKN~FM&ELxp~nfnUCZykK!;w}!$+uDq}@~JSLW9XGFnbN{yxUY8M086fBc>X z9}0w};imoM&wp7ixF0wj(LC#LX(RRJGw7=s?sA^`bD=mp=-~f@Rki2e=vqe&IfJ#a zR@&sC_t+4%@SuaO0nnCIo)tdNa$v{?CxYc`!HPuG77ptI=)5m!yxYX-eVPDmNXZ_^ zzYO5nrU^*}Yk(})Gu$!D&)u7NZYOe#{u1W8%1ys?%kzz&c)Z53Y^=Ezb1mbG(uD_77cAw;KAQu1 z8>Kzu1TM1(DDG1zjOr%xThlegtg;qWdjtZQj#2l6?6t*)v(~{@@+jppOG|3dR(O!g zez{r>Uir>YOC0n0vxt?B_OL=LZ7er`C~7kL8h=XMn9-$c%>bZU;KK=I@sk|)vbfqD zKCdIOxMC@Vgp5fgae?J z@1aw~>S}3)iaDJhfSSR&>(8QV1q%p&o$B~5LL-Lp2hnk z33!1iKKl~m_N8b^Z;JeujCk$(w*KDqA=6$6OnC<6;EbU@I0>;qZn`$q)yz1}^9iKwzb%2KE; zoMZKoJSEdfw+D>eI*-{_{3FY8eadiZ&P6;Egtw;R*`SdFM)Pmbfyj@;suOEINv>hC z9v(6lRu^lU0X*rJ0>40*@9$DLkNJrfB>d=;-iYs0gxY-oGz?U*0-clrs$i`Y+W|1= zY$taBIFM#koc;a>NHdUEDPF7g|VT?879Pg61z>=lqYiyK?`N4QosLfZZl(#~eLxOeu+2d@-NftYTZmjcJzPx#6VM~JPk zUI@UgJ^MA}j@afXIq&|7Oknzt>lautmo>BX9fwL>t#WFwys}*Wv8f*QB;*KK2M2B? z^*(g{v|^JJ7Lxg~()wY7wP!U%Kk6nSY8&`B;9>5loUjB`OxYRzD)PP<5AmqpKI2{z zM#KWhkFaRyDte+X&HJ=lqa45|309mxY=!{@aX^86sus->RRG}4;mIMcCm`N__z5Ff zYZ>qG2nT3^vl^9R5|>eSVTR);e0sJcSR<@Naq}_p;YVj*$r}4xq(s8cG>_f$?|yB0 zG~-pGG*1Fw#&MkoaOoz=^VsX_f;r!V{=*#{2l8sycZfvDm6kmV)R1@T4SM(?bN$DC zAAIE(b$((pN|(E|4tw{7qFnl>Gg5o2LYx1C+*8Q724YwjYc&~Y5mbh`?CnuHEv2y} z^&^k%Bd~hO%p>sScR3{2dEBWN^FgQ`;0+Y7Eg)XO`w2P2gC9&l3Q)jxWWic)!Tp9T zkKQ1+VY-6*Lsu)dwY7TAtE4T<<%XL{TSMAvnc7mBr;Z=fG08k_4p!Lef1Ahr#3^Co zhOuH?g+PYFrk1oD_pDID1#9k$c)h+Qe|RQ-L-|fXeX_dN7CKX zp74LV%dj6L9ZBT_p5&ZFkJ4cebGwGoEU@cEuj2vUR!3c)Xni+5E=>Jl^SLKO%|f_r zZ|wn`oY^mc-c?Gq6W)j8?fT~oGMWzFtSe08s@?l?sHa^PBIaj7F{N~gD zM1K#tb5-ViQg;lTVa*kyJr~kF;lTH*K3C4)MICeRq=!7HlBqU znB*U^=^as~9+HG}G~r=H&E$>0uLO}bIp1diXxG}&zA&L>C`QbJK*D#r?%o&Nvsd4? z&)5rAzilYjVD-IOZT^6f;(1J?JGpzq@^tbUyOcB9&DQ!gBdQsb`eMVFeNJh;uAs?T z0W^0n$U|#SpoRnCIpm+(51(MgA=Nnb0HDY};^8KwSoSjC@b@2Gzni#J#>6s!QBx|$2)P1$o?klPF3zN&dvyl0#vdHjj@trC=d>n-yD@R0{ z(B1wEUOEu5ik-3LXKbb*zRl=8zq4MU?(MDlzvV%H+R+|+jtc-f<<+Qz1jTiuno(}6 z(CFd#UQDK5YM1cxk$>Fzf3=9${3X@t3w~Wxp>WICKI4FmvzLAYF#8Od(|gw*8wnn% zeCuIlaEOQMwX{xk#f*ZkD>V%JPYzqgeTeNp9IqXyt%)e%g9JY@jPCrbwnKZgp;284@xr}k^9d;( z@(65`Pp8S9U56!&puu$LxAF;rhzUWt6VK|@dn-PdIC&Rf z{j^Uz6w6q%aLS?bj>3l@Wc)5eVtW(c%@jV?pT1nCRK*N=tY4KB@%XY**xbFU{S1QuL8@P(AQMhYwaspIy1$-f!g)nhy~=QOuH_DLObKs)op$yc zlqW9$BNIx`5s+)wG;pO`?7;-(Nl~w*&L%=dyk-E5H?FLs7OQZ9(3A)6_ygt1^r7%5 z;ztUPDDz6V0~H~M=DOQrLqf93eS`fX%6+36pp{4Ikk-nh#l>n@;#KBsuf&xFCTYjt zvS>v}t*Bg6yS4b6Yk~9PP=M=|HvyRh2U3WEpyT!|LpoL9ZJsn4PrvAPojQaZFR3Oy z6mmA;x|zEmuroxYy4Q@^|4i00`47T%pIL*|8<^O1#2fK2S1zQ~vp@@0sS-ydW=W@! z*Fd~tcmSA=(niPxpO&$$#--&)HRfx~Mm1eBkZqMM^HOnWIw+&WAX{ToU2ZS{=ZQ3*9B=QFaIO^y+5W0zb-xgQ$UFNc~b!-Bd;WUqDh%?u@}-`3EI)wuad zLiWb}-@jG6Z{$TQ)up~%gH=&dCN}d_(;G(Zr!>xy*Pr0uPOVMbOMdQq#>$e-f?~Rf z_Z4aT4aX#+5ZBGu5$UdUFdrqs7!`Gk7KtiQwyyqHc)=`T{VUDA1`m+jBU?Pp;)`4 z3SJ(PtN}>5Y<~kx%>62bmo9u-r#uRE7=OYO$ux;22|=^9Q*MW+MoC;zbJ1qFqXpOGsQpoo@-Rzzc%vrnLB9aWXLQgzVtKk_mb)VIr+5ec-yj2jeIr088=J)=n7YTU=RpBd7r+ufvR zo~MB8gWzCY42a*ELPJ-sdg<=dF|=)_F`kLL(Fo+Pfn{A>cl5pcd;CVpsb>W-Dd$Jp zvt6>aE@~CXAaoTw9BTW8lA6Kz_v8-UBrp`BGShmO6OGIa(dcYe7004KkaXI)P5!wW zwh=L&d;Ow<)fj@;c9Jlor!a{n=QzZ&HM%8G5MI-NG2)-h=*y|$XXD~I(Kdy;ogDCh z{y|UP`G2t@nk>4xTcumTx8&o|r)$2obC3_Z)w3!L84kGuB$`xI$EpKR!X(6e7R$h- z;q1ohI5B_ToTPyRM-@Oh`8+JYl-8Rx2$Kf&qNr;Z6z9s)K$okWVcO@bUZ{S0i<-k^ z3!AOUa3=7=9N{l20nhV%x*LW24-K%vT0CzbfJC1NduZq|d6nE-6=Y=3IE~VIFoyX1Uro>uJG{B?-g(4TKgqhuLV zfG9V_P{yCf+kex#3c~+vASQGA!~_3=1gR2@+BiiC3fDVwbg2Ee{Q9>yR}V(SxeNJc zTS6&4`wIF!ql8bmi&|0icRJEw@h_{-*d~u9->)XRrUw?jjC+iF)!5TzUiBli<2(@UT?k;x<_v|DO|f)a-3x-S;I6ozfBvK6B_1|K zcC`w?E<5H+y$#=qw7DzJjuw#?B8I~hG2eem7yVMVw1c{A+vt}7nOOF!(QY_!GQEjG z?4p?I(7YaA;4p;P=VN?4%h;Vh{TD{mLt| zW$w#Q7yfsr_|j{&)nm9vkYb(h7G32*ZE~ul(lDfGbX4Xl=oAIKSH~8u(&}#dqoz8V9kY zbXPc{tYymD`K_2-(HdE?b{WY;Dd^s-H$caGmyO;66G1>JOCBB3RYno%M-z^qjUP70`v<+Y3y9| zDkKGy5e}9!36*0hoM%$y9T|^0BM!t0wh{RWqYS`%s@j;B;|H`os(hWVPBx^VAdR>t zN?KD%mr0mcOL%~p%t2jkM`unEj?WSvFS8y4nh<~a5$yo+aqf|2pm?2Z5ab5`FAQcU zY5P+_DJl^%k_~5a!hL8U?Nx{_?YdOaPF^eY#OecWfRK#NGc4Q4Kmeh=1}7#$;wYMV zlw+Tq4k@4z&xCt%*+(TcSviVq21SOzXky|DnH*F&ixx%M!Ma6Ognhj0LSpoY+E%B9| zBNI1Yo*t5#|17<5Amobf5FV5NZ`7LjSB_=!#Jrj&+D$W^z(;RCmO0`|(oTRe0X$bN z@R6%E`j=`gXCTuPa^l!aeBVKbhEuUsP^4RR8s*rBe6Q3UEjJDd9}1sS@=eXkAPr?W zhdw&*(UcS_wn@W>=pr4iBG1SwosN_EWF0UFRq3LtTw~WA*7v3d>eejJ@DtUh&QDWM z9G{SmFDCFhAh6b4-X_|i{0z_Ih<1laRZq40@JK=u!*mt9Kw8ezeNU$PO4B+p4zzl0 z94x*GuBJQh#fpXS3L&R!q)Q{QG@!`WouM!j$YYkr=m`=BewB@?qt*CO3=q+|A5@~Q zj+-CsqqUq8qI7YHZi=k(tUnVslSUlx1_Z!v<^kldzr7hZ1>017b2m^JO5%9ObKnEi zk}}U}DMg)jdP!QW(OvhIP9fv5MB}4diBSzN=AR}RMO?l`H>vISd+PZrfoQsOV{d!s zZoAbnbt3R@&(ZqP2eUa_8G_643b&h&6=k|qs z$X|aot(HKDPd$5)<*0czLxqmZ_%PpvPHTT=5dEema51xcb;NlpJ6bzo<2R4R zc{VaiS3+ErwkgYPI}n*Ci<2ye?F?YS})?bGa;Hf~8i8 z>i7U+#w@Dsg`!e!@d7g$H2?&eh^^)DT;ag;D)`HPG7r(KbCZV|+M;P}Nb@3y%s=0% zIPbp=8E7&_l*;XSeUtqb$#gq5tsikc)1#UeHGK*auJ|m$({Mc|!SiP9&D+^lU0QQF zH(jTr=cD!pQejaPg=pXEp=Ier06c*Mvwvz7#|h^jeWu-fIWDK91ehEao?JUE;-ySB z7EOt;NJ#|xEx835DTtGfoth9jLS8x*v;@cc6?ZKhJ2;&QO}S09r$O07iBuV7W@n&z zXP>2$_m*mp<7#l`mU$*Ty5_QEYNx~(VwzLcnsjfO`D)s)sEQlDAoO9kJ&H8*kx%gn zN-9ru{RcE$BO`<0ca<)YHY=~v=-1gIs%R;Hes|0I!+GUJeAX%-b?8FlqYM2|T5Twy zz&X5Y7hB?tm55j6$f6`t(Z_#gejjqIoO~7c^RPRWX%lkdf0hJTljDt)nm%ikH8KnN zoB7zw#jo;q;6NYR`iEj%j5FJfE*3fRQ+QfgFc)UV|M><<*kvCKzCO$v!n+(Mr5UG1 z3qG13Ox;^-r;1PJ?R|tg$F~Mlo6EO+x7;~^Wz!P3V1-tcMWe4mQtDzxBUTAfzJB?a z^!9EuJ{sxy7Txr;4Q(hoD=!MTlLD<`@rp|~2p2C17Pvl5;jH`*&xDtO5DB8E6p7gv zD7fz%!{0k0C_Wd494;eU=m=yMb}Yy|A420yxqjdFB0~Q6m~`o4g}}LU;xv-PT<$F- zUH*ye)l^uXc%vh_>Y9b`>oHm9Jh2t9+4sweyGn>q9HO%dAB{HCxZ^)cm$PSx9UP;B z$Tc^2?cCR}_9AUHl-qdwPhR?J6PCR7j*(l2%z4t|C!r_}3r8CZ!q4Z=v}I660|?no z6r+LtZTIWXUGZb#5`p_C8p8|`2c_3XZvm!W+!F@~{ZCB`*cG+zh(i)KsVhgXfFna; ztFujK9aXGdFpAVD7e5+L>kN@9Nd*|+@;1Ku z9@$MhP0e(|0;q3B9x$yuNHi!7pi;;7vZq-5l5j!w4zDmp*=#ua+e48S86o2yaGsmW zcPRSXVZ1M35Ps-skx2`Q!T0pqb-s1QR-KxaSZ=$3z$3zOPxZXzV;ExAlYMSi-mN#` z6$Ox0 z`?v4U3>Kf}zbUzW>u&$xpS!%_#jxDSpLw8a{0zwxi0^ZI`r;lhZkd;D%(!nXa&n*Q z`3ysOE$0p%-tfi9F8+nuLqn{-r>~~_E^b-7F`703PeX7|BZ!`+R~54@nnQ6j z@SqVNhJ3hX`5f?;@vVT!b2RfcQQ0G^`1=zoh=Bvxo}kx$M6@DF$SafUD= z07e7w0iuH=y{kDWz9g>{3o&ha_$_O~j&Kir&)m3#6!{z+emu4>()~k!f#Xwn-4<3O z&o5!Qv2tpY0<~!jz2-hVVfiHU>48$N1jV2Pw5iYnmvLC zs%2vvCtFZVc~E_r3EpJQPRC9MowbDvJG`L^QpQ6Ov$Vx|r4Ow-==0d;pf! zx*;R?x;`#gWe#PbC5_VeXPCL7N@Df|iv|vq*Yxy{5B)s(_G)L+puYsxg_xDj_p=f`A>jGf=v z+}n|f(jL4?`#&sn2HYJzUA@xxeBvNx$u?Wk zPy4tzxtk6Adr?^JZt4zWDATs+C@K5DwNqPmwlFlBe2nxjjsE#1f2t;76|fK7gyV5q zYXCYJCDO?bp#zZ8Zp}gGEUi$z&gL|0_iKy7Zhxr^YsDxkjsa7Ve+Gw1?{nc2*7ydv zi2ndLOPXj6Ldq4e7y{#B$rH*Gfk{>@kFrZ{g>R#8ijU_ z)dlnK98e8^!lZI0HI(=t*@w)1>`d-t0j;lEmc>|gIF`{N=s0US5L-#~*1Sh9F2Pt5 z6X7DH=Dw0d$Q`VZi|(tGX?G2QIVU$FP@xR1;+gy))5+>H!$(s*~d{tMk(Z+xz35)n!z!grbS1$XZKJp`&+QbM3uvY4nf z415UeKDB^Ump-!^zjfrS!)2v+$al(sekkWa#;6VDOZsMA@MwQ^SRp01w?b3q2Nl;A zajc!hyyDT0$(FqL?`KwLD4BM3^|4W^pk`g+&Lc?xRLfDNBCI{H8PAmdMECH!w)W8se7F5N4}{-~-@h zA|<=>9#{_DxN-g(QaTlQ>h@ce2RBGBFW4O@nVzU=daB67xl+TUYLx5a7E0)69EO5o0Nyv5;xjGiGclRQ+sFu5KH@3UHRBL49a-NFp!T^KKBx zYX%~{48XQ+TL_SV1)g49glo=_NES^6Y8oXY=x}941lw?n8(%8o;SGCN!H}JyqU3gg zs5lPznO6wamBjNtj@fjiaNKTxJWE}{?vDQ< zrhw&{pfx^)&8d4=Nt(jF7=*^=680+5N=|KxfH6i3%116p%UU3vspJuP+Yve09UX~p zHZkoxBYl>|x(Yha<5R*ODUNtTbq^21)}>7CS!bvcnHyAklfkJV7A1e@JBRvH3zAyw z&OYFMI{O9DW|O63DBn#(U~n|Zq3)4sVk1B-R`8-VA|^ukL>Lr?ueY`Z-}`w>jlKzV zmx}ih4*_A25Q;=q&z2H;iD+D{@SR22dDXOR#Qidu)Ow$EXZwx1zLN7(VNa)VwOZ|} zDkJ`iOsNMXE~vEwG}t;5b4%Gl))F?cwT&M?Q(9?Q+BVf z&GcCL(?1of$Po+-enSSmFIC2tAa7fuSFXZ%zJi3RxIEfKI+{ll@ejOV$6jy`C35}4 zsq+_$f4CI zq^)RtgQbV>xUACZBXv^O5Vn9yIQpTRfP9e@A~o#&0AZU`KEV_KGPQbxxt%Uv*RGh4 z`Y1o>*S{BKi4vjEx3A*5)6er$V=8LQJvGlJGglzm&cOER9>BO$QKKsjZ~n{y2jdn}lyU?c9kI>G||UOrR2(M?)R2IbzZ zjcLZ9Z|BIO@I*S+TBbS_(&RKs%$~3fNBh4HU3nT`QhGl6vyyUj<=l}V?Hl<)+VM+L z8v~X|YhtiqCoNqJC%tp4E)FF^A9Km(Z?py{k}Ds7#9knzK>Eg|V?TFvg)3ktHU ze5MVGFY((NQ6v+=)Djs?Yw9C~P7LZHtpRJ_4ij|AETLlkXA-?I!9=wR=Dq?jFfsq4 z09TBYiuPyi^ofweYW#=g4J(ET;}N`(iD6QvkCa`{J!8rhktXU5V(z;FBaT~JV8`*Y z_|AeiYA4FC=I!kA-yMV~;4N}&yQiv(5LSl>jp>sKe_|W)Tk9Vb#gc*mpj5m2PXBqQ zJ@)f6inRp`iTT`DM0`T30rbAIXfP$3M2imrXw!xB+;Zo`q|xZWsA}kGH`*2y1Cm^3 zqd1jASXu*b7>%Lf8U+$zCe{cOD*JD{)|(wn48)3wteJbaTOR6bje1JPVvlWWCVH)# zj`DV_tRx72I|gr|r17PUroXvdA^s&X?zYs6oS}=I9Zx^l+_U=1l}qFI2H|E_0UMuK z+usf7Xy>0EDg-R&BlfkCaQe9en*p=;Zy_&d_};Gy^81?XZ>sFK(opYIBolGH44f!2 zT71=X)Dm+S0R1Tk#TpKE)i0! z+mdA;@wl;5e2JEWEOEOQ%OtMlA>fKjv~TE!`0YiQ&09g|A_8uEhbo=M$XVY%eL4&( z5o^o)eD!otszl$K&&d|aA)K#B_W?kSY95hdZn+KFle2W8o~6Gve%%02m;uM{gJVXd zPuqjbTM`PCDCtR{N#I3!3_xWDtU7bHUNlXg0DVM&+LNGd0MUtV9+#P;L=1#P)Q2d@ z2<_(fkID2u0u#Gq-MeEY9DdIm3?D7Dji9k_S6dVUQ9M%`HN1vgd z#mj)r8^V&Qp=t-BLaL*oP%jUINQMhn9baB--s53phrUM?9HCikYGYBg+q0>$Bo>M>x)9?G^NkMG6tLF=WbO$ZE@a;S0s)pZBZxtI z=&z~4ygW2*ll@6-@mwMT1-4jC=9l--$`RI);hm>?+O{PRQ{bl$foQB0OW{%_Yi8p% zvXP47P|zcy@hk&~6-~^vuTc}#q>75P_bYR^_r7ycyrx(@X06PBT*C8xR*#Hi{HgNW zGG}gtiv}tmW@#TyS2Cr^Mm^{Or7T>`dK;Cw3|6545uBEho2kTANT>Y)m^5SaI_u2+ zZ;F@u&+@PU2u`aC4q8RA_g)1X;OxzaPE`%d3zC52dlH z1R^dp6;d;#3{fLRKW_(QdbkQvL3tpl1*_VFg{!Bdpl2vh6RO+aQ*J1a%R@7Kn=IZr z9PbDPno&uDGDvT$y55Nk{I4G)Mz9l_fqYkXhySu!AfU~FV1KH|pXWTo1T_9 zVF#Fp0Oa*L7BP84c`eyAGR0h92xOfplGh+U*l;8-{OEy^rnW~Z)g5if;JxLJX0r(z z5={hu0?VT5qD&bA>~pz6T<)CKT@pHKK_ak{S2dvAQsRV-klKtgv8|n;N1We5fE5?&CVEfdMQDU19GVbsbKp21_9^RU29JF>uy6#aUi?L ztqL=czdNxEdHZ%qEA$!9v1E8RtX+Vv&sf254V50ON$#X5y*n)`)fD#-gXUn+eFpiL ztm1F6I*M<0%x4D{$vHRV@pyY=l=~aCR$mEc@Vaehbfwko*?>RnU71BVoVUbhS4e*! zbysn2GdVPegd{noQr(;LF~>rxIYti6sgP>UrwB=u3Lzxn+d(Iv-+uqWcHfU( zyRPf~dOe>PP3JnpdRerBwaH!Lkycf0j3O_C z)(7&X>Mlu7b5Z*Bt&F&v$3IKNp7IP>ks3H+1;a>3*g$TcN|B}CR3|BY|L$AR3(;+m zRNz3MvXGJ@5NyZFc@cWx4^`nnODaOi0Q?CZegSDMbmRD0-bY|Xhffv&K0c6e2be8a5Z4FC(k`dpD zzV^P^=t*nV8`qp^xpqyN{0k)LbhIO*=!B0tBnZ!kv*rH?y1rd~jFWlibL;g=yHSZH zaFIC}OJppTgUFL6tLXfk{3fMj7QzPcTJ3?hjEnlllb`6P?_{6;RekQFh3Ez-8@Bwf zIIff*QE{MWu_T@-(z7ov&yL5NX2w8M+F&dUS}}^dcpa_1APWtgFj>*ONk@MU=blJN z*Zfg8i7)oDQ0452THwXA#YY3F`UStkySN0?zsY7@JyHqU+6T8JdS5B%O7O|5_x7-O z9as=KvhmFytylCesuQnT+Jd^Fp*R4v(AMS?c0vEI=dD*&@7-&kIzv?Go-wk=EV4jW zWuY#UhbGtCbXX7v^?{)&D1`*Q>2vvK$3WtypWoKN#r&&x*}-{+rR|Kt&Pcu?x~)S% zM?2t?`>}ND=upm?p%IwkMRmoZ&oX(!!$YCU*Z4*kaJ0AhNCFYGJ_Ady?aJ!DqeFxR z9f9gv@UhQ8juBx;a-ELkVp~#2K3KqCvcq3T@=UzET46jopf+FaGm21{=aW$r<+35j zp$``Pv2dvg@3H>pcs1b%FVVOs2|OYO(M$CY`Wqe`XuaZ{n^s>lO&m`URzr<**Y#<; z@td9OAAhtX{9zTTQ2ktJ^c%U_Nzrl;ABz}wh4SLZ^ND-Fi+gl*IqTiq@pm;u(GUOL z$#ine6<w~FgZ+q zW49DN_ecHBo{N@lH1^vYb|c)Iv*Epn*Nww}V!ox!s(&4O+kI^OmE^*l@j{8yf?h84 zDDjfd5g4|Wa8#%8n*XJ}@Jm<0gsc9Sp1uDZaD$V;C=@!PB^+(;nOowBfx9g7whLXb zyfjIxli1Dv^2)&?N~yJ4oX>^T7j+;lekC;Kaqmp4GVFL8f$sUyLgoExO?#`-znYgE zA;&NB{na#x{B2JK&Ju49=%k;@?nJew*mDper=WaE!o*GD{;Txi<0r z-e1)tTtR)Af8k)a)LITQ9UFwfSMdwy9p9i*n8HEK<4bRJ?`_6j9UQZvEY!z7!roRVqEwe=uE`miB#fY04bICu?39 z>7wqX9qQ;toN;n9LsVJk`E}$LZn)Mu-PZfQACR1KLsCVO4N7|b`av&Shr!KsV#Ks& z?!TIT_@&sOui;=^YSvbugn03PIZH;0NIrtrLYUX%hhIec(_Y9&sGXizIFtA2>_?Gb z;=V?wb?%nPSy6FHj^-zSRoQyh*mcES8Esoh;Xiada$#62C{6IKQNq^ZyIV&iWXFm!(yD8ll~!TB)D|b+^VcHEs`pelcODGC4Ko@2tuZTXj$sVDyFE& z7kq23J&@OmoVLbVVq}t)nkE$pClgSL#R0j$>IfPIJv?`PatsDVP8FtC3+tS1uC_b+ zA*S^PEg%mrY8ukRF_ScG;0Qm_sjQ*r-G!@HE*F11YUh{35-@a%j$ZPkFx zvtcSV;iCWpqOISP)JTmtC9t`1R{<`+G`N^}LfMYpGZ-08i2B5_)qL_>Tn@qEC^V2B zVc%9{9To|+^Xcqv&bl=!`@fVXL%q*it&^5&{6?>ig9mgaqH@0=B*WhxKKdbL?Ce3> zVv4Os*Gf+Gc83DXEXQE8;PDN_XWp+g>96?~13%f0Xk-{Qm01$G+`1ioO2Th9) zU1c*7yjazYO39mTm$z}j9f>!I4wH-A_=d&ww&Gp&kR*H=8*6(eS*V5(8nO!C)B5my z;6`9nsDiLT;D)h-cSL!sh)+~cvdHm2Te!)9ENjbKXE88uki@|E@E(MIW({VQ>gkc}DUoS5DtIlK(s4JE&1wBvhzc?|R2=RZ#{Z8$$9g!k%H>X*3QrLWPm#DlW58=$CsLHY6T1Lbq+L&N)CMeJigi2{)-Bu@q zAZaIXv|J{(igiYo=!a^V)02+pM;b>;@Z%%xy2Pe)l`u@5Z?V~7I~9tBD|s$ikP({` zd)8t^YNB2pZp0gltlHS{B zvzfxm99YDe6|UZ~Ud>4^?lVk8rc>0hw)Whee!~pdPN(n{>;u#RjJG|0D)Vf4HEYkb zR`LyGsvX8437%z9xO@I7?QqLpXj`J?gP!b>oeT-dZ`^3(q?k66QKfLqJ}fRTy|77# z=kSxBh=4Z|Z++}idU^;Bu_2POTZQ-ZR+bzS(dYtrvytbpIf*mmCa>gG_wAyfTfM%! zN;>Dh_e#&?X2&ad`7V#6GOtJj2u4>)1Np3~f@f7hqS+v1M7vt*W(SS|>S;CJcP#qz z+cO#YBSM~VPE(_m5{g_>%)PSpi>RjWZ;;+{6T%eUCN~E&R=#n0ZvEP%&@o?Y~`BJAjot$3_#edAN^S$U(;PG6OM5v0C~-`UoURAz=r+_PO_OfPShG zUw}|Sq0VDcUAl9Fiv)Y7+9<8g?jkO5X{PX~j^9L-xn7Ip&%%4h{%VCeS-Q!J25Q^A zOUXYwVXpXg=$K>8{lZYryeiap-T$^8qU{Y)d|^r8F~YRzaSan?Y;R%j4FY3Xvb=wN z;E}N>UcIwZQ2z_Sjn^rDjccBE(&jIY8|tCrf#icd*{!RATltvVA=&DWrY@{oRGo}s ziXH_0gdbZ^*0uaIN(=w}vSeFl*|!JnIovyQ&%swKymK>jefHHyeJi~T4n#%1(zE1~ zs1|21QRz9giAQV7JmH^_(k?&``o0S94d%0PAE_A?dJ3SH>%Aygv4 ziPlqf*F9vv^f66*c)JBO1fZNaFmgv?cb{pa#hc4~yfHVsF^Q8}-y|wPp7g};Pm24sb8_@HCiFEmHM+JekZcb%@fVy9PoP%7?GqUmMD|oS685x zBYC7Vd#QPi##BYZiDLpXpL6VTJ4?q(ut62LZUcoegWI5k}l zrE%w4-JZqWZr-=w{4O}&b9nr1V-zPv83b`IMubyS)Hkrcbg;k@*mpAb5UW)&gvp*q zN$VpYn<8OiLIG(=nSC&p4ZJ;)UN8rG_dh^z7=(5@#7mZ5HcM0KWQMW%)M;Q`{L^7m z?1uND(^}xm`5=pvxFli;cm|mqnSUVZyx;2G>I%ULDif5d6}g> z2^pGHPJPTLut!S6@hh_-V7u&$KQL!dvT9^DEWC~Br5| zY7l{M9HMK93@>7|*kH%HN6wXUE3oHwpCCb9Li{gvqAN|cUJhpfCa!}MNu?fTxy_07 zvd?b|kpUPDP%o7ujKKb}>-D46?x}!jCA!j)NRfSp@0gm#R&SMZ+96EJa4ll!*MKhz zY~zAmw>2w6ifqa5{f?lM-l+mc9o(Xd8@?I+4)l zlNhLCiJam#!F@ZyNsu7<&V>_g6>R(g_mg#`#8Ok~D_qIKBb}@aZ)3}vVbH!75c}BT z^hL|o9c{J9KB}B>oEBfW@x)<$QrZbNCry;eJG7t3IL~+hhiS7ghP@X$93fmP47UIMC zLb~EYiwwnLjlL=d9(W{uK$I3vW-4KfLNBSCg&ARiqhDRSO6bQ&RW0lLji{y>((9vn z>wo~rRxFnxx<5)JG#Z)P>UkBk293=!lRL_iM=B3%R@-tl1-_;{;X7bFDEVyK^%3Pl zwKVIYzb$tQ-Ir|`Ip_#*ONF_1G{{q0i zi$pHwv8s0bvF+VAg-Moa1tTt-tQ6k75XxSTVSP$+ z_?1Z-CnqMp%}VSwiJh4cM3;ZXlXumd_o|q(WXEqlT5s0%i+jP>13fVrXO&WuU@kw$MoO3JzF@>G4^lk&!$kr>pU0S~J`0wUFz)L24+;)tSH(yNrmM zL#PlKuy|J`qEJi0BdnJAOmw~UTt8+5+{|KxHZu?H;Q1Ea%%7a#4jTi}PwiHc&mafA z3pIae4eq18n-4#d%Io`dgdx{15{_~>if0Nea^D}ZhxRHw+`XOn4DRCY1s_6+4|z8p zIG|mvqJ9xE=w>0vi*(%VWqFUlSQVabaGICn>2{xzfEU-YWSCW2xvei&hMUjCQZX|g zmG_6S-)1vmuWP1o=WpH~D~7)eV$GYT-;Sap4za;*lk$yP=?2NCQqP7kRhHz$F$~Ez z@iW#IzH{|zk!r+v%CbA-i8v!X^mth2E&u0vlz%|I?ow5JZc7*hSbl7jYeVqMAY5I# z=X6(GBP3wT0v8WPDT5I78xSPGx6XL-OVh6-QF;zQ#7pl*lVq<@<5%BWA6v zRj?9as$s2C8p8l#fD(1^x#Q1UDj6?%jVc3$KpO|LZWHf61*{qou%Wr9oO)>fB~^RE zBE*qXKVGe}CFj-N>G(hV5a5S%u??Px;5JcS&QICO?||vUq&czF@JMDuO)GnZ@~9K~ zJl`n!$zu5mTqzP?-&JwCp@pv)6fe+lvFUWY3pX4C+Oi&y@s}qT*#mKCMlhPG5z=}r zMA3SkPZ@~KybET5#EI_8I}Uk~ARYifvb}S**Drwi_5w3$?#ual8=K+0hHR)g*$4eQ zf;(J#HMe{@3sQE&M-mh{y1H@dUuel2pDdxx7q7IWT4z*XcWggAgqmLZ%5!XBIigz4axhnUus70Zbh{8>Z8ig$1+M*9iO;|Ot; zs(0)Ew6U)%DaQv=j@c(IzPEBco<3|bP%}CxQa0pwa^%L=Pu;P?_VBUyeFb04hNI!0 z>sy-b-7g$cZZ)r+RpvQMV=9nRtkthuS3!s&pVh*|V{g3iy8KotlDU3`DV^J%u!iYHE)KP^T2V|xD{vFW)_SgP_bMa+EyH%miwGg@ebGJoyMz3fU zYey3PbWDtEy(JEZ#eNLWh8%3On5esfuaY-;Uzhv}(cU8pssqB<$@He^Qkj@iZNu{@ znQ#9&0dYnbqD=t4W8AF*(s()dKeDrgrjg{cE~!r|hd@X5nMdu@Z~1dG=GHON*2vz< z8^EO=TgpQv&6Arr$ncFl6_f4&MDXv%lZPK{xvlSMItB-Cf0*q#d{+OL$IBF35PZQD zar0+>5U7AYoWuqzK52P#5a5ex?Bm1Fax6FCftl?g=hn+&6;sY#Zm#`v zIW#|?dC^?#$)y5|Vb$3#k3y*{PquU;Jml8}d_I_FpsjJwgcCoTm+~lj~&W*_S=P zon{RqjN%DG^}ky@%+su8bSBLASFGK7>k*-#+O+t1jkYICIs0pZMaqxjGap3RjfH1x zdQETctNjZ0aZsX*obfJ9|c&3YH*D zv5LgSF(807m1Kp2iwFb^lbJ~=DFTNy681^Z0X~9a8QZi$wE4^vW--g@{7P~@UNfkJ z?5{9(L(TcdL6uX~ni)(1Fi%8s3R+mflGso8?}fl8MClyXA_(%ySa@<>=3f^R^y zPbPy=EO~WxOgR&=E2K&*q&M^03NlrwL?RiA*Ijk)ZCjNX5#~a44m%N4#47 zI|nUe8YbpLPG)kaSl|?iDE(tMYBsAGWy!W;S*c zY_r+@kTJCflUR9@8P=;R&+;H&Uey*7jmFv%EBUqOLMrXUTYptRAV_r*gx8+$TOY23 z+GC1$nA}O!bV(wa;%IC#NYH*=rzc9p{54mwbt1&n{;EhC18r~IlZff2oo7;yXxU8L zX|(sRSLTP&U-BL)i+st4mn2b8^WVvRXr&+y*_o;mTjYAeS@+d_k5h29df%cYVb6f{ z>Kjd6Evwv1!DBKOzHzJ7qRO`WJD&WyNXue>hHlMVKsv>;v28l|M&s$S1OF6rq4F!T zZ+uINQ%W`JoAtcAK2N0Fdi-_zp~ShT@ShSQ##w9*F&w4JSTr5W^NzL; z%C{E*R~Nc@gs$q%1i3)KNNNKF<2ds=5m)k!I6x+EEhpXlo(+I7Kcx$OMkTks=+%nE zJNnBB`Sdcl!puE}U4Cf{c2&zF!@xG*rK+bIkB@vA#l7H2!W@uyCKD$_b?A6l-TmKg z_0El>ZXRA|&!1_07|i%gdkk?D@jf@QnuI%#vJG#(n+CBFN`1!;r(Z+G71NUyf{WW{ z{T}z7GULlTyKf$uIsb^21R3?ME*BY4nqfz12Op|HC`-Wf1f0)3Gm~jmEtI zcu;V%F#fqA;;lpPLDAiIdzLY!hl_cQmxLe}{mK56&G=mApA{>($s(iJgT*YU&T>pM zTDa?11IE(1n6eH8{{v@!za6rH$=mW5SnTbjsuh@pk@&R7w~D%UQ_J?qDeU>SHc{Wh zXM8`N$iKSO{ver|z~KxQj@IzMlTSp;QOP%@5lLzXtfmOhea3DXC`l0{12JY)W1|pPiGzYh) z3{!`3wP=wXXS%4`{CHE`!!zjZOFSa)e{Emr`RMMQCMx=IyjmlE#9VkwHbZsa!b=zV zoODaLxOZ{!(nulS&!+FkcKi{gD{t+Bp9*J0W@q}i>D%MA!6v^(9$a0{y4k(n{wy>B za+2ZwDQauww(tT)RA>JtIKX%@GQ6hL#2l%6CWp2pz+FLL zVPghdDWUfwItY?dya+#rgw%*v=%*77hJ&8ertm+Y@O@F1&o%a`v$#sj<3;9Gnj1S$ zp9splWqLThk>#9OS}Z5=9VybnvY2ht8>qzA5xlStsIaoqsmpfMN*D3Gf=FeN@g$;y#+Vp*fG3e_H55@X1S`Vpv6Rp9gZb${L+?or=8D#BfGXcPH2ot2a@jX%U&pA0!6yv;!$_V08yPS6ku7A(l`87`P7BHHeQ&+D^m zM<;&2dvNp<%)!uV@%9QV=O7oY--8pNzF>-l(fN1-tSq} zMhk%!4(us0j1WTc{)S`hW}BGBxP|VC-JD>dszWmEXFOmjE=JMmu4Rz>C+!fCwWA8D zu~~@I-yJVsbh@d?=AfkA7)Z!pm|w_g2s%yR4vC~v*gZ*B=>&?LO;7PU5e*2_pZydD zhHp?8A21p@aM&4;;D2;;W3JPP%4Yh0gP^&FFvCl`Ohnr@SgN++l;-a?pM4!&?(MK~$5nK*d;4a)(Q3Z+r^jzMVnBsg zW$AFS%Y=Ls8A#1*;*ned^V7)8_Ot0ALI~tMUpEBe90Wgxf#4fz?Jt_i)Is6jH}2%f&|&BPGK9<6AQ2y#b5T`2{fZtCHRMMg`N$l&I<|us=v`!A79y+MqrLm) z=3;s4h7af=CnDaX8$w%K@n@&Cx-zayn!!GZJEcSElvdE+4f&V?*B~+VY2ht9l#v#M$ zK6!3aS*ND(x#Rlg!rD$|+EmI@JG&u$J6wm5h%p1X1ANcp`(Bh9RmKsV88Vgv1ie5f z6<~LrZl7#~p<7;u;M2t-Di{x8NJ0FE>v0>O?}LoBd?F-hck$xFy3A2N!_P zNnfCr#L_sZ2J*EYr$uFQ`-sGgSSz06lo7VXP4-Y@3b^^yC*5XkOs?V=bzj0WB{ z{Uha5(@oaE5(LXYQ(0-KhDB4@0GbZAzGyDUIr(=tuP<9FZM=|=L&n%iW`Y+7;KY}U z!x`X3JuGoJxop_W86se$E7E?lZHxPDIo z=Z_hb=<*MG?tiN4#M<}SHG9`z;VD?^sY6Tz@nGV_#@PZqepzPoglx-uu3%P2e>v7- znL@2-Hb}kjcPut!8rA*rA`Nsi@>LIwEV$5v3#t(Q%Yh`^;?6=Z7cHu67nmFdJ+1?3 zx|s>ip%9=R-G)S6au3RlTR!I?+&DbXy#=4YO=EpXMF!%wAezJsj=Wr7XQ4$^5<=(W z5z|T?)oeWvxon?Ghj+tzQI)UaKOU8Jhn#TMB0!b-dK6fJqt>2dw{P|plF>yAZ0<~ zP9HhoZYMr5!Zoe5XN@o@fy0OFIk*N10ni~fSmk-)@oQI4+N!QIwbyMRT%$4L0rC#P z2JDN^Tq;>oAZ9v22AyOJBmxBYl`SLZ%TI^%?thb6w_eR)N{8TY5=-xRh|4~XU)}8V zqh%;O#kla@scwlKIdp1v z>(%{6z7W)J*ap>X;2;tJR00QK%z?&(9AIGlLXXrWhsxpFtr_E4H8w?SDM|MPs}&jJ zWc&o4FQg~K>7(9>O1_Cstd*X1#a@_yL}3Lm2!rZZl||Sk;?p=c zh@ZxHhgim7!2KY%&zSN)J}a+A;EKMLtt9Mbb>Y;wu-_MzqT9h1Zc@d`^E3YM)|_Cr zi$$})gl`mO2>Nl_LQhULb8rg`OfE-;i;k8A@&8pS4*C2LvHmcC^+@`F$H)! z&{=V&>;C61bdLkX7Ec5F(kUC(3^9+MW#O0NkQ9GqT$)Hq+NBpl3o^eX!MbM=pU$UX z<+m~%R)f_kAyqGavjt@)YByii>N8dioD((IYiAUsit4Ic+$*j(k$oC~yFs!QoYL=!- zdj3UWAEeB8`rg&B58)_O7Uz+W`Saxa{aJv~Y$%S-ftb(j?l3W2FyoTy%)^;Rznd4= zR1YoHm5s7iNj5V}MHWOXJjQC)zl57m0n=T2oRS=eZ#mFDpAZ4a@t?OQA|%AFKa>_6 ze-jbfsTR5$@fC6|dGvF4sUiVp_65)C=d0NT^^Wz@o!O3ZkAVIHFGFH^h8bd^(NTGAfBskLue31u!?g4ZC1b8 z)&SDy+b9z%TWEOH4EQ0q0~lyr05C#L)P2qgx=Mx<&rU;5K-1EM={+@R{ZtO_)elHA zot$#mXG}k+=|1drRN?C!+5)3-E+3*2R5{?aF#o0o*TYn8J3QQIDhTb%XPH*-DUL|- zlEV88ZrJP5&Bn@yKIU>jG%{K~6n;LvpK6EO>GgY)N22y6a)SWmX5Vz!-74YLH|eX} zLgMGBH#&taLgsJm7TqSLp~y`a$fL7Y-oXg~xN7@zhPmm!HKeis%SgnRV7@QRGZGeH zVRMGi*7mM(l>A$bovt%@q^5{YV+^-Cc)C~f7r;*y{GXntw*SHPvjZ}xB*EYP28HZa zb8tsI^qOHse@AOtXNp8On|ruob8=C7{I2<6@GBn4fv*PoV`f`_e62hdIwX$QOCkFM%Y(JmF1`nd=quHJ~(5ShzptIQP@CYtWZ7-QY{E{R1C4I4xVbW z_e{jtaX_^iVvk-D->Fs=S4UMnoK~@#qz{{6(+VhQ$1AdCf4q8ao#P%@QCHQ!q0PyV z04ydRtV;uL+w#0_>TX+V7jJE!=!^Av9((;~yf4%s*Q%#_BlhLq_YG%?k|(8w&sEt| z@H_cy0I0!5^&wW0u_hd@c407Zt31##?zHj9?tAUv%0Xcw)HCi!^r@|bv>$RFC57<1 zr9IoZ%hrho0bmk%l>3P`rUqOB9=4Z(TnG2pdBc`)N}=BwcN)0G+L7fkzl z8|#lYwVJB5HJU#Tve)IhnGw!fo)j|*&a69;0^+@Mr%zk3p?BCAShG47mH!_wu5nI`9+; zz4uyILh1|#gX}B=G=J;|{R%vHF{nT=b1>@A?A|F++fJk{ce#3m=zn&VCz^`Lj{=fNVB(zgXOwIrp>gn!1j)eD5TmPS07X%oMzyA%8F~m3Z54dyCn1gmC z|BFceRzg!f%5?LA;2-vxws*fdn);rkIWk0^T;9qL^i-PX1~yieMX{Pw@>MBuMy>m_Fk@F=Rko;t~WTP!ero6?BLHJ#hv#3 zLwAJac@OA(1;Zv|-wV5g%DMxpw1wvURiXVt@@BahF;kc|CCP>$>(4dnnaf+;a0AC` zxejJ|R>ia}t4TZxm$Sa1a}j&WreKctu-`KKjZVptd(OPGfk|mtRT_J71WLVr*Cald zTb%$ys%=n`WRi>u1dhfaF(d}h4MkO(LJWg${`vtg?Hg;UU5N>USk}^V0T_m8?p4N- zZRAPe6Ynj7uO;xQgd_vip8u?ifSY% z^3R)wy{bMEQY>UX$SXz)8JdhTN;#Xbf1=%{=zdWn?aAf;B6N-28)$>seZ4}{fe zO)f<)1Ea5E+^apFocR94O~tjwA=xmtE-mk*dZ0t!nwkYfNHxBr8e9cSm!@3YS>H`W z2$_>rb%jLqfmB70x%8wu<&g0seCovjR}tj42!boJfWAqOeMYS`3xIGGx8s*n6@kU2 zR3(7s-#aXC)|hmtp~H;eAHTFJpvGGC@8i`k1kBLXKo&Q9$fbid`h)H~#D>i* zmR9Eg+^E)Kbt=v-eklo!Hp^o0UgAJ_rP+nf+W)qRibrJh|7WK?YknlVEnlW@Q3vI2 z%ip4SvH-mHCcMx7Xu{RUUIyz4Y8suZ5r_!`mKXT9KJETv4I@vQj0G1N>kQxg`qT(s zWSg+lgH)*?1=z`5CHRAHyAVjeB9{9MQ8kan1Ai;&+5tM`h7titk){F-Fg}%a_1+ov zzj|h9$8Aho060iyp!HJZ`Dcok7AZ)gLva}M1@ZA^IFQbTzfL4~CYtkP;#-2ye7T$? zoV;r>f9=URz>6o*b^>X5Pwu$twk4Bd&o*%0w1*BPv zoh_V}ii$6srXGD2leT?f{oBh#L*aHJL%>FP#{Jc~vd6j6_K6z284z^g!aG@M+*B9J*^&%`<&xo+QzGq#W_K0$*HQR0 zy;qt*lzt%#xNL``GuBB{2t$V39{@~T2phuPDHD!40e*8S$--@GutRg)U!W@kQA|X( z++skab;QQ<0E8*VQj=Teo=vg~k{ctBxCDa*Zik;zH!C?qKO#}`&XTutsy8CS61~Xx zN#F2D&I^|%%RAq<44Q#~NtYh<##UbFG^13_M@H8T6o^(q_8^@mhb?c=VKOAAM;I3F zze0fLF~CG$CGrJy@asY!zXhA?h)Pd_pgi%IVZ}so)D>H>m@;LvAw+)c3r5+P2JVbO zam7y6KF5f!1?g?&!|W_gdyJ*-{qxyp+$z0v7JJ;BEDgO&IWSRW0O7zXh46u(M=-s( zq@6Aaii{0myL_SR-QrtgZMsn>3@SjgqJ@!6R@lC|6Ji463TaNGntqc3F zr|Q_tos-(2j*8R2WW|Pek~Nv-^UqT}~dlQxP9W=IGoIBmV z9paW>lGBWM!D~k#2^QZAxviC6Je|S!91x}HgY!yt2q?ZaNQU*>mD08WF-p3H(_%8l zgJUxd)Rg+H$o42>Ec*n)vos4z#6uri-ujnt6~`((;sR7*fT2gl2GeHfPL zmL90)dknjCk02x^(G)$5pQdyGq*INTmXjuWr@yqr09fvVjb2K zYQFGi7DmM=_}^K~%r6HZ-=Of?u63Twix7O!qjx7oqUl#Ny|up*v(LoKNqt-#P`OMl zXQ1k+;xsV*VJH(46awNQfmi4}a5XMjlMrr;i}|T=p)~#CiCdpo(?Q+bWTAuLgwh@M zq7r$@`s%YvQ?3W$*v8?_OF1otH3BD8wkh}oKH*oj9t}@3N;fO&15B~`tfva`->x)c z?=Uy4@{1nghksv2+5I`{dJEKp&{Jk|O;mRZDrQ90LHF(D;<<*PakA>Zqu{|MS-am^ znFarBFgM9MqMfy%aXC5#D0DFrLXu%BU7IKXVDo!%V5YT%gh$F?6E$mOHxYr?;q>?D zStDKzZ!U;JBt~tRYA6 z{!(_VTO|*Ecrx&5ap{}Y)26OH{}W8&K>6pe5gjp8=nyhK?v{nQ{3c~Z(aPYb3l%&* z2+^$S;XmhNMsSaPAS%oODUM!1JUwflLMj9g`!5Ly-Q34b9wZ0Xxd?xRX^@QG8q^}K z%Izbjv2c^vXJWse{JRKyy> zmsYI*%RY-}cOgdMB?#ZtoOA7RpM=M-qKKOBC1&;7bg^`?ufdZ!r~16?-1flekn2|N zfe(L7F)Sw5kVC^YrTR9}@WXNMKJ(kS948Yk1^C?#_FL$Ssv^o~P<{qrlEXTPygu;h z4tIVoTo>ijL-wrw?=Xyy4*9mxgPs_dNjL;5I9Rq$ByogeUfqM8iO78-WKknx7mFM6c=hNS!2Ih@5hj^6fd zYEFDb&c~-gt5pE{KC3T*$kVju91z)$8C|opb9D;;L`1tw-ye{cT~85M58GM~u|s8T z4@PcnX!pQB_1kIti`V9CHECRHarm4OUIT!UTz=h*oLC#BnhKFP4*v320lcSa0dO2E z$Us}T?e{J3j z`B9uG!g!DqaZaSt%pmt;w$2&6W%(<>GL%kv?V<*=A;R;g)YQpyF^ugfNWoPO1VPqa zt})HE?%v$G8BdSWgMH9mJik{NC%?iaecFa_jx3y0tqlOjG2tRf=e8}g?w?=GKch)f zQBG2^Kvbg~=pdGILzgvp<#TF)3Up7+tVMQtf0ZYx&S$@=PWqln0=f8p5L9y-y4s7n zqbjE<02@06Gn=;6$vHR!+(TD^)}&X%&^E3OsR~C16dbd7JhONd!V+=-0!}C|JeWpR? zt_2#|z_XwQkCWYb%B;umq(%kJ0dyF3F>&Rqn&o04tUpd9^?1`#0VdBhBME|sqG!+9 zFzFvIm5creYb0@*9Ym!5u6P4O6!E_W>h(v|-EZA#W^Rs}HXn6q&B9aE8Oh8Ujj^LP zOQ&7zLXkS3cw9oI9{*2y(q$(ER8OUwx&(q}r+&w)K@C4f zE$3-(BcM7-2>IRUBzhxK8;Yi9qCsYO7LRhI&+p3fNV`#56;Bg_@?cF?G=_o-*C4gX z8SThXoJ>&-^5a1$`ZmiWs{i3VGvU!oarAPJEOTo&C42lvm^$*}^g7To2xxBv6ryT4 zmB;TZTaC)a`HS~XZkF!JH$+~0(|0WwELMXZi2%?UWjo}w&ysNQ64r$mURofYD}w_#WS2!$Ou3a?e-{Y^=B)o-)6$h5ejEZU zDK5gkZP8<@tP+CE1#14EP)Y;cs0m1X_^q8!2lKy=Mg9ERZ@eAw4#3B8&b5Qji>%^EeR2N4;N3P~pVQ`aCMcL&`(0{OI~ zJ-JBru(LIkB=&t`^rfjA)Wzg~FMW1}ts1lQ15d{{8}3kBm^D9HfufWmeM@22Yfupf zUH8lOFd7jT*Mqu>1KTX3Y^XrRl>Q&VW;ew*E|qF=>2L(u%wZ9gR_=Ae+-JwJc7^%B zVt2S|P-2$7(&#d3n=YDP?GZZ?SF7UP9&+sU0ZzBpRE0Dmkjw&Wa$u9~AcCgt`kMXr zH-UVv5?F>%#^ISZOUMKg#$G3kKZ1^UY5+s13C}Ad7fdNGb_+KXItQB3g#3iWZusJPHqzABx^9|~WrNBvcnHzN_TG81SfsH?*|c6&@hE)u@www%9; zQ*SU?7rnCDd!_Ez72W})F&kN7Q#$tg>a)(PJ)H@-nQPHa<)bD3u{r-o(Y^mO{rGJF z-;o_0W}EYAbDZ;8L^bC*Hs?7u#~h22RH|)GbE-Lp#GHu=Q3%a3Bq4-S4iQD^B>I%^ zz1ttKUu=)Z_Ih8}^HPHDZ<_AG@I`3#3bYt#i_ll5I9FyB!uWbX%e#;MQ;b%{Z)u7p z9WjzWTI6(;fv^OMI~69=-A0&Eq{w)Q?g^eV%PG+!Vr1Za5=#oRb#4v!DcyW7B6VDe&|o{+Pol z>n~Vj91wZmP19wWr>i*eK`3nE@s&hkMc2pSj6+-p2HT1G$%{q6u1Q{hl9rfe()sim z?wr%d39pa6b(W7Mm6a2QKOF?Fb)wzk7(fZS^nMxmPq}PD&w?Z)JPyG(#5Rr!{#cE$ ztPm^B@{EU8UV*BHsMb+CDjU(f=6LZeps+bU$+?1*7jQih?=LrUNE1UjOEg+jLHAd4 zJJ#cOtIpSqo|>qw2YgU}?B=skqqdAaQ(I!dgk(m+n+GX+vtssT5_erUf|nafe|hXT zg&Liez>m7JA}sU+zKQT(YU2GZ^5XI=t#*%@`q0Q-ME?1`v%|vYD)yt6F|Bl|VSM$s zY5Tiz^YK8~gkIjsDNufwOXppbrsqlxIOV8WdD+@hF?3zeD~_l}soV;39>r z);XhlHjY>|?GhUu{&w}O>ET^v`G$2j(tU5^b+HywP~#OqXlRgd5|wY5D*TL=t$sm7 z@E|5=MZ}wq-77LGEfvWeaCsAk^?f%M`9n;d{yK^yI*jk>{X758$@I+^pUKiJccpH2 zzmYxBSHJ%JEZM7L1rx|T|796v%W2P8M|2;tNnF9`u53r!7ipgHjXM3yA<1I<-m`#k zNWzxD&p2pQqMtBZ|Gp=n@ZjmAi?4i=;aYzZl^KcAS7(I#=Y%~f3OVQuc31eF>spF) zX(rcG-h2y}#`BpcIxV@0#4Fk6yCrH?$*czOcJ-uO?Fk<*`=3{!EX55}QvT*PV`pKr z2)DTQ2D&a;fw}BNJj9DhN!7IZT|ZazCcuuAq?nr7CYG^WS+~qG58x$pc+BZbEoPGS zeSJxI|F$fsLn4qBZ}na9QT}RF84(KMfZF(AVVmcAAol#0QJ|L^Fse^R2gHdQD0OXk zM+B$oJHj$!_a%WeC~Qs)C7YI$1=Fs)l zWI0QOa&HE=OJ$jyf+k-337AgeLE*%(iGRad@AqfpzG+fu#34;}m3__4slj{NXFS|U zIo3_Khv`BJGMVI+V9%D|{^Nc*fr=QdfUYwA*g&f`3|?KtMS+>~VK5D&u8y0k*4I)a zhf%47T@F1BktoX!7(x`9bj5qc<~9^*Ng8F4%AKU)Inov^%Ih1!BLdr&seE364W#|A z^HYtYTF1LDZd))Z|9j`({cHcr`o-5LlSgv?2RN)tM%(NkVWVvo%-A%rZXZMuhp~5vwH0atr2qGiyNkU zn^iaGAu1nOW-dV=^hr*64!R@yTOag?Z~@>rD~A?KemonMVK{|z4K^J4i(|l44y$pJ zAS6@UT+nHkSc_1ka2Y+3$z2PCV#hiJWy<2_Hcxa9fsk_C+gXVsLG~+qeuY!|=@l4FM6+}{YXN9}o@}vk8vv99%;|qK= zPr-JHd^#txv_XO=e`;?CKGCl*$IimmLQCYcY?Cx!`RcvU^rQ$4VLXFdxuMZu#*pR{ z;*=%EbkNGoTFkv|%TmmtnFAwkGN40_O>E96mJ&k*Lim=y0}f$7X(_QITY**>mwmYm zeq++(K{|;b)Hb>2RrmhNzEEGXqFpJk%=G53*YB_Hq$rnPG@4-zyfO|JeK+}Cnr=x) z%IL6Z4uAx95>lxM$CNY=t4NN0-V=}(>PmQ=tl%^UQI(SATth=FB@?>0i z)TN5cxi;dl`Y5xyRrlj0g`5YdMtvCGlHE2J@xMb%r`?dNtC_8pN(rdRO}mi-(-(08 z?PJTivR(@-o>UHhs5cxV9Ejg zY|3P@tnZ3MZ&4Za?XNl|2YKnY1Z$=0n3Q7;isbPvkpa6`U<`o@+WPTs7*`3{GB6YM z+yqu>$I##e8ot+f0SF^nuTDCMQWRcj9uEM^?r#%g6ujfJYVZ6WsXJjGxp*Odx%^lL zugT)n%d7X}!P35UJY2wW?i?#ga%P!_yNY#TP)Yw`L$sOQ{_O>9H3U|pk(c;}{PvRv z+`*S%DWWR0Y})y__kcg9fZC7cd3*?wCjqU~eM&X;R*Zs(XC%x@-ma&n8^4IYVOw_I zMVCfTme|K(N3?mg9Rf1ki9r|Uskxsmb$Tq;(=X{S1CjHC-RmuE2#nC@?Z#rrnBp-W zlYxSl*^Jol|G%_uCOFwiOT4ztYZ`+Ew$^3tm^o*Zns3%B1UCFGxM|STCuX^Dx_3`U zLE$?F&abprcq2Y8&~f>_<2myTXBL)lLVWqbwf_7Hmbn){*6u;=DyQ0#%y`De2{O!X z#41=IlI2`&%2DPx79d5*;gc;<0CF5t(L-e>q#D?k71qDdCmDXTM^+#gX`yFohhZ6$MJ|$ zkSeiWhfv+@zNNr~-%pk`thI+Fvrhe8#a0?YFI(mKyCnz0Cfua;%gjy%)4)=W(@F*% z4c-?E*kws~mA_w`Og6Fl%6k%}EJ(xkpTA4vQ9+qiFV8>%^eayNv82HOpX%f6R0GAy zG;?Fc*O`ZbZr3&zNtSSP5GJ1n(N+Tfr^&OH7uS@0y@rmt`*G@~lS8$pLAXP>1gG%R zhZ=aWh`o466Pws9(#nDAX-)|vCE()4^RGSaT(#f+rDZm`MED(FZ82_G*5J1`gOisv zvHP@JbJ3oUEQwE8jzN;6lq#*`X3$9Y2Qi)~Yh5Ou@6jPA_*6C1S8cTgPC_8r3s!N=!O@GUc^B`UGsNP_A+>G%(JAPm~!=) zXeV`B%^9ua@MFW02HouNd+6GC488_Y(KjkpW!*_OA@Qj(Up2u2UV_#r8r{xLlniUB zvr~V4ux{0$qe+K55r2pdP~^Ew=3bs`Mn_vlqQrXG%`4lXiUWJdB2p)`qNT-{MSs>)kk|-JFtS0C_0l>v3U6Ls3l7Q zT{P)n1$x7JXV(8HjYE=R-dDphk0W9x4Jx4jdnEYhO-%G{_BN8Vi28IKbM(WgOPOxR zc~?UwdGrXn81`7eE~jAZ>X*w8Zd<4L{O>g6X>FK)8UfAWgct4t&z= zzn_F09Tk-{^MYsRv@W6*I6@a&k|e)5{G`NB9r1OE&}XK0&pL_;2X zbjQShoAKPpbK9vxEwqX@o7QTbK$^2W(0%rRD#mf+DGK5Hl zxUt~9EdHHkfgK{>WrItiKaM?!)l~*FPb+bW9{P3k<+Mtr^ci6|3xxj#A;(A!>uI>` zUak?jl8cROwo0`6A=~-!%0Hp#@E=za^KFBwAQm(bjs`l)2AgOn(t_ZH%Q#U6q((R@ z<3psec2wUjUqu(6R_kk%BG=wMx>gx{E#CFWo5NI}`Z)ZOhupG={c7mZTVeK_g=hX< zynZ0c>WVw-44?rCv{$iUf;Qyt55BiUe*O%8K@!vu2ZFPl)Nynqo6h$bQ0gH9R%BFu z6PZmqdEf?mIB-#sL-;cTnGd9^HUoe-K#n>1B01?vj*7gJ=Z7RwF+h;nfsl?hh<%#z zyA7leBU*<>i?NbZc%W)1N<+VD3qEBQLpY`+T^xpSY9W$OBYiu9)fHX+2&sYM7Xpj1 z!S!*+a**LLvsNqmGsLAC zV#NXbO zUKivTllFuIhR~EGTSPC^c_)W~2^_BCR{H*&9x(L=(=z+{kt_-2vB zm%~}>^RgXEQ49FU|K(g&4vZX6lU`#My}Fgyl5Z=IF2lI^yb_7-zJUMAgx4lt`$~Z$ zTCZ)^9oc8%RyJ^J@vN)`x&Mt^=jpt@S9|^NBAX|jg;1d#nWWUyPC3fuupTPd>++XR zI!Y2i6G;FG*s+HLpv37M6`H4um5wP6T2OCg%Fey63%Y_=(PvwsQ*(gibBch=7pYK% z7_!3LxqGX}j}Z>@Ey960LMmD!s_S~9Hw*I=J3ywQq5*ScRqYg?^mOax0@sQ-=el6K z;kclNlrXQ_sLn$5lvIjx>cbem=&+0O07l3;X51sf@BEXYH6Z@=_#F$;I36bR5_rP{ zvg!;;Spy3VGo^n*oMOZl01l@v2yDg(tZ55uObV2X3I0F{ejtY(TSh&JMMxc@PN3>kn^#VyKr|Z*pjN~x*R>>bz@RRpQeAO01$a;4L`r2 zKQI^Rl?%4O_>7H2&57zS;`RBAnS-IE_jsR&iqRhvAYZp`d=I}?V_E)N#SX4}G=pzN4()B@cchXj}on8|*lR%{eIGvt{u2X`gG&Q-bYkzW8A} zsRvY8uDBWiy(lITQK9`UKNmH!)J zAO$waE*+>$erVE_Zy_FZ9v>U^9bz7nIa>v40a|IE26~PKS)`@=r3;?1#j3X#1{z)9 z$uA7}Qxg?l8!IZN{=$Gt@h4q z8%S0@7jiV1(#;+2zN;CF51zALjg_HJebJ^0hb z{&SfP8-4KasI=F4uswjUY|TE$LZKH$mS9$k5G@MKt@o}xRb1wNGPSiAz@9POViicR zHaEEcVnnWKP#s8W&ork6bdd&Cq?zB$Oj0rnGAUBF8wG23g6OTF-0xs){YkY+9W$@< zmYvjgU{{*E?vL|9AM)DVHk~~vg#!$i5zvJb&e*{8T6fXZ==65OUx#g;|FvI8FS7Q1 zglB1lo19>brX??syNf|qWT-V29M21YFNgA8$_b_4>euEE!i&ta_>=xiPkqu*gt$bR z1ixMD*aUaVJQQui^YIOUd+9(`97H$&q&lbb=1=fI1BHw-@3`Aoy?1S-F!Ao^>?K>= zj5zL2TlCW-kiaVNg*t=mMBG!}`|azpHXNCEI*?*6z4z(KwAfWHR>2C^50$^SS%hCZ zf}d6^-x%wbL-%~L>)9RknUlQM>=5zeS<#D->n^BXUhnbY#IrwtUqA5Zon8S$_8oGP zD#Y(9i2ZbZjRC+|)e7D^I6Ld}-(Q5~hyaA0!Ye@NVX(?!b4%jK7_4iRIyn>S0Wy{l zS48%)r}K~op%%7a^xDfO9OWT=GQFP*bA!z%mCt=U#Z4PXTYz?3k<*jNej?vAnZKK= zWo0iKt}CwN5a+vyvDd-+Z`L?x4k+x@1Q-q4{dVb@Mx0X#i4Px?Co)wyp|(*X*C}mL zZytRCGie!MwN-HZEr7AN*}8}u2?6n-#*gg!c{3KlO29LR-VZI=*&K8sZc znNs7kE7AtPfm>HJ|J9JH*1!;vc&A+_5nD)F*45hDk+x?e^Gt{c8uwmh^vk#GhA7e_ z5q;pQEZhb6zbToYI=F|==GS01ZpMNA2BZ%Y`R@yuUz>RoE$mW`{eELj`Q}~`{*#3L zs`530dBiOvwx;>`yG-_E!T7-^_CLf6l^8IaRx$b$$*TelR0h424^Uu%WV&zfNltVY zo2av@|4nO6$b(R|FJeKtf1fLBQK+h1XAmWBbct7)rE792=>)H8J#QX&+&sF%)SyH} zpIoJdu(It+BHisnJM#5Vr^J>Z%A8kXI1ol#>2z(Ij_H8BA6N2@v-cPvot`FEObdLN z_T6vq*ySsvUhG}ab4MZE$q=sMz0)mvvaK%h(<5Rw;KD~RTN`Q^b1~sGI7^&-^%=Nx z6WNdRzgZ>!Mtio_tAkDHcnvs|XgsjGig-pVazf5|DDDdBHxKgyj-9V$dg&fD)PDFU zI5)+jvYN;1lZx$Ltl`Wb>6wfx1?2fh2D0yUyt%)#nhXrT1=v14jk{Aizuj`LE!1$< zPTypb#I*FbsCoieHEfo`BXpw|M4o>m`1ZL+HzY66%D6DX_4b+Z)UlqW{}%u8zF9Tt z75oG~!oFBRY%a~KD5iqtsZ0%VvD|)B#byv{??vZmjOG2@z09Rx1;7tv)wSsuqEY;* zDM*kqVDT#N#b%ID%ef=_D$3_UNFH+NdhYKw)77?j$3DK>y@yg@y<>Syi7uI2ezv;u zL{ui)QZ_opF;@NgYJ2FR@$|6E7lF3rjR~w5sTMtxDo?v;yo+2U=CGH+e-10dS|1UW z(T-7QQ&Omf)2h5S4M`#23TSV(pzy$KSINgT^2r@BdJ%2Z|B+B6NGim>K z5m%OMvemtha!8cvrmJmUDbe!}_eNu#MlXLD-cv>8U`Ubg`L#kSjl5KLiRR8RaNti?T2h;gqcaDhHUwE0Ro%9Z1WDaR0bHSME5yF&(SY8a#$R zRCLTSmDWv$<;dGGaroAtXYO|kRK40&g-2NuMi(~g>6O+34VI~4ckE_W-4)L_THon% zx6SR0eQ?~p-_rI*D~|Yq?`_+eSq0lYUE6-hxge!{g4O*v<8vVoiuKMvidtygn5@9t z*K$2oR|R`td8-J^46JJV>G#8Te2#n}Jg)l7BlGt$X4N`N_J|9F$-i+ti}okKA%puW zVOiYEV;@6E-7PkzJnbK^3n7R;H48Glh?p{AIT=tl>bI9g`vm1X+_S zdX!42eadJ%_KJXC7`IE0l=}cnf|-orEcn!yS*glp``mD8>3#)Vtprecn66Aq5M=%{ z93&3hB(Z@^>>X|}Rr>a31qA8Ae2^wX>3~CJGwLgNWD{X6LyDOk-f|BY+Ymb!Z?0de z9<-wST&1OnivYA^^X%25xldCxT=6w_8jllGM1pF?@N`rnpjIkwY}6VnlL+9(GwkLk z7&!t~SZ4vzkT{TqC{2HQ2Tj5^$en@VUrA(9`e8Vi5jRLBCCpsZKiT+#L_}-A1*r=% zCCX)S)l=)1!dNnufiwYafsh2Ta3E9!*Vl`3=P()K)T2}`{>X*{AEtKTp~&QvnL5a} zn|<9}euC=T$JGe62hxj2pS2nNXff)| zy^vceNySFRIX|#7)%o4!8$#GlQaOGzPD531>A84jZ|ll*MQ2UZh6bTtNgh zbG01_m(2%|ZM*$5K(1+80TJ;OqtT#V9Z8}RaZ4i%KEuE5{n>%zGzi8C7r=dbPZH|g zPvB;QWx}ZR)B`sP{j9*Z!j@#=O_PdbG->n70Qz!sps2s<;=+i1WV6|*qJ=M-NvLbe zb<}$-l{Zf5S9jgh4Km9cKXNbbha+Y6u=Jr5ws4Y1I+|=8g7{bhE185EIq3hf5Kp-V zs*oSL1~fUJpmA4G+baStOGX)dY&Cug%1~~av?>a3T}Zu?@Toz$EH)V^^b{^2mTvBU z;t%X6mPQw@2Y`_r8fZTaC_}8^MTik0uaq>fn`{`jk$)|$xx6e|4S<{X;GV@GF)7bA zymQ8UzUP83*JPp-=mB1;9Z6D~RG5vJrw4hyuz{6Hb%ooz*RE>eRzGWzV0;)Rt5Bla z-T)XWHpHzN4?9F@6^&*M6X-3O;p%z~ee=O?KeT6Q_Het{?9nJUNPa_9$k}Sf^ADSn zMOR6oPDV~=i$odGzorgvzS$DT!P#8YhQ{k z)*1_>9>b9N%ce2x zq>wH&Bm<@F>xMA_2-Ktgg97S(du4 zP5o#R0fZ)6VTC5MO-<#bP^G)+bl)oJFJU$Y1mg}gwfefKozfDdi&iy7Q)=UCq}Ry$ zFgq6Da2j19@a|QmY&{JSKjuc}cLghyO!}*IdvfvCLU{@U9&-s9ple_u-8|`kYzBdN zZFTEiy`&Gsm<{~4R4^T;kX(6(q^X!+C7L{y7@=|Sb-7gh0_eh#`9 zoI#5Hza`eCQnH`s$Uom8-Fa_{N60d^rN4%iCLZnM!FRwzhm=6123pb)MF8+;5|HN( z0H`V^2(kE&w3p9@U*vdSOV~|T`e1WHbyE;-WT~o72_OSn0r3!G6{eYpLrK_X5oqJx z3$14{xW2c+5P6#%&O?=E-*~OMHy<^AaNis2alri!DCH~5b03(~bg5b~Tn}1%xK{gV zH%Ccz#i=y(vfobBMU%Oj^LMtAhrTjwROHeSj@}`pxrSWAUEUp zT&CKb#gJDCL0E|~1eNUag&-`5I?gxa`f{>bFhf*j#p>R&MG}Uf?b*ffNy$Cvi0nz` z)fW=6cEWK}$eiJ+E;#JNdeUG>E(C-EJns6XiDhZ~Qn(vf1()wjMrvfSkxNu>of6Kk11-W6>x#F&KI+ImfMdAG; zo=-uKbnnyKMyUhVtDN!an<-JXdKzBE(j%`7b(^C0ifDXi(|DXV4}oxECIHI?KjByo zKy-N|Ec$eq2kj4QmJ@FC zx9r;i2^zbRng8x?^>)7TD?I#9NfVxcNYqE83WJkfQI?`G^kTDu>58=m73zqaX>S#E z!_MP)cdUR2b1Q806_40&Iyu7kC*KJ5ora1LX|@`c75O}@Gg@0f)~U;&fgDctf07=s z{3ftuz7ZH~brP1Bob<3->fRY~kVo6m`}hw4UiIzPL)R6#*@o7X)E~JBx7UdenmR5` z{OStu>sK9=lcDR`)dL@=lHp2_@vqkl#omAP*69jW&&=K8tNQ)PQ?t^VDryqM1#P$F zmUCW}4JU3GJh((olDN=t9)v>iH0J28;nx8>M$!!XP0&mh|w4R7+&MNZnf*%b}v2Quj7^>yuX*x*z{Ci%X z6}Rdt;a^nf%L{HV@kjgxE_l}J+c?9w2SMqtef*tAj~w4S_T<{v6U(5N=(kg7pZOE1 z80BO&jA}q#>Xj7Zl`5fTdQ=8cp3QLR0Bsx|2$B{$HpBC6#3lHiH#yRhe)CLuO0T_aH~sh(3Qi{gz#;gcu0B3&*+9$`Le^ zZ$K$_*F)wluAq9}_Q{nBe50LOYmsP@ik=kdiAJOO72AjR!u05U75Dm&>Xn*wA;q$s zLDJ+d)I6{bUHKH;{X)#Nv%m4l_eu*%{>*P`!$GFKuqF)zA1Tjttg3q=XwW*&(k3cb zc2h~oSwI*@ub|Rp0PVuNH_=li4wVwGF(`Nj+s1`$Q_>EtH~cqQf@a^|HZok1lpI)K z4|vf&BN1fe&3u?kA#0VttZl;0$Dz7@16 zk^42FD-^<}w3^wp0DgBcgv@fVC`ri}kdh=rPE1b=(!#wbUj3Z=XYq*7{!_Xn=O2=I z9fJXFqxH=&&_OMpIO3TWpa_$wOcbgDvUUg$_}#FG$eUNsjcf}UlT>=Lf;-EuV38D* zxrEd`@qEbT+R!yrcD-aUf^B7-k6s35-_-!?YVpBn06;&GbW~Bzd|qv+9z!z$wmcLA zwULcWC*jIJ>0BwOF&804q(fMu;$3w8Ox0$SyG<@*#$dTl3ybb$J6gj4%~nqFF1-TL zB>UZ@GvZL~M4nhLuA8{lIPk`T$fvIn*rt2gv^O&2X-2nYsi_rYoC6u3$Q(qq2%U6( zAXIeEk3Fm;287lNiL3y(x4bSV{34NyY>Sm3=ahxGlNs?R3Mk z_C+5%{4(ggn}aep;SOq1812FX7zr~^b+%4uwmhQ^JUjpC+1nN^pN-}X~LgA%zN^#SF4z%1P|M2{42ejompp&c+P3}EE@2H093 z0@)n_Pi>}N1`4G{;&`m&fR*wd+qXE#DdLbQR0Vz++1WZ^lHd3oRiwHo z_b8&f^KD~Ccb0ZW*3hI_U)XeQOtuQy)l&b4er55IlvitP(>0WXq4y0`QO}&E1b2we z^Z6j39Q`AAsSK6@*R}E8-=O#v{ z>y_$JsF*IG9iUdv6G&0GIio4es^vcLRKp43b;if9fbeV}l>dR4^V7rc-ax`~&aY93 z1gcl2zS_YU9lhw3kZ+^cfc~HS%d7d)KR!IW-ehBN=_Nf2dH_+a+?Dv-2AJrVIV_d= z)F~n|-@7w;l3)L|IKFnHzjUKo^7`WVt=3~jT`hTk3$7ZxhL6a|ET=@)r}_gr|8QvL z1vruppVTfLMY(qiaj)DIJ@$j%Mlc(oO-qt1nl`7Tjf%KK5E9df2S^v$nz0L=c*8-_ zhI!=iyi7&mB6{qtibY_%gS~bo-{Zk)b>X(!$;Pg~7FJ0`FHx=|^%=Ss_**t=@Z(I-@Mg&W)EAe@DO5@Wp7J!aC9c*&F`huw~QQZB4#S&+NlXW|wZyOn4`xsnn)h~?U z^EN=%$*r!X=a;tOCVTBk>|;hsf^!YM~ZMj1T5s2{LQp4;W8*7W;o>GM#dYK)bg(b&H+C4hWzlnSw?LXWFGd4t5c*mLYppTAE<3RLx1*-z~v zhOWqU{HZMK(Hj{!Jxi>lO`N_O`lZE3iuY8 zC4l;QZ}W!7Z5v_4(sVKN-Y9^=+E(OIi5DzPH58@@so{76ak(Y$Qj7%dmu>(XRn{CR zLIXgM$~^ScA!XN9NKgg^lBRVswK$@xp)Uav?$P>Kv8Tt9s9l))u&#}LP!+j=R9J95VVtHLw z@&5j~_nHbvWScqYk9{H}1DB{$a|0UDz|-pz1W;MCt;^4J!tKWm+IYR|8RJfw`FeKI zXc@U8MxwE*&fE9WmFJsG@6JxV2W)KI61H!y&ypu@Nbh>R>9v5*?P#=9Ggb60o>vZn zvolAZYhP_N`7i70VbQvwE58R{&62%^8=u}q#GY`A|8aN#2_66dO_;Z!aU#ySf=C$R zo6Oh4u+ARK_04(vuTck*6)J`V`E&rx=Aba}yEeNwR*(gz_)~#=^;>F8vkW;@vUR~p zv1e=Qf~N>f>Mk_JiSC-j2uNmS6|HT!jq>X#__4y&_UY4^9aUkJdN*V}g+UmNm|uTj z~4RniJ^ ziIea(8-;obCzySgvtsI`rSb z;E(8O!WYExGvudo6Pr&I51%xmxAwq#4ZS%gg!l1JhzVjyP z!W-`+iE|P|#RMgoH{k28#10BLSaEE;VM~d6+!rHpD~j>M1c)O^h;)DgV?eTffcI9f zUAMN>SRhIGr;`L8cQc*<0#9H9S8t`titJNq3RKnQn8Ab z`itg!xwMzRf4gxu-~Ud2b%*x;PSg_Gs|f0=ohnD%Pd@cJ<$Xu$OT(pz+H(bl*sSkc zc)0IY0_$~x5PyHL*(h9i8+f-wnAY=?*5FeH_XRQ`II{YbkQ|K$JVb~hP%NB{kemhg z(kYh{ID|Jz+~?5cfFmq8nXjcS_?e}g30WX~;hfD4DHDbJP2rgJv3!x6l(zcVXF2i` zNj&c6KR1P{Fa-p&$~E~6X@^`X$G~5oX60=OiUAP?@ESYUCui=trMpnro}=7?LcXJ> z(K;RG9ixy34-Kalz&+BJAt%inXi+D$_LaklrTjcQJ?Jdv zZcW)yOBXBgl>9(~Vu%|*jz$ItA6f>~s5xKr`*n-bTd3%%7y(b6^Ey`}Eb8mapSkT7 zuVONPiY8ql8jvbI>>2n#btN*$RPjPVust@w0tR92ChJdgrBKA<8t&k|dVS22;EAR= z3E<<|y|dAezcu>66{*d{D7!$Sd8C>taPVx88Sgv6C)*4tmN@f-fsj5zPtg?pfs*0* zP<(nwv&**+X)uHRF{(VuvY0S@6KwIqU@J>Lt5+4!C(VD_30KE}6;495@JH~V6cLet zWFadyCxD;ihFxVUmW`}ssC5ylvUTIJUj4DIQKjT+-Agnq&6&lo3ZA0xST@ zf%jPeWHqZFW~{yFYA^dI-@?ZArW<&tZH+y^ZcZ!g0-sR{wdjWd0fF$g7vAnszR&vGJ=#ZH(3F7r zKSS7#jBv#$PfN48Su{6qCW*rV z?Cf6k`>*NANR3iYgbDDt5%0+4Q-Ly&7a&VL36TscXkA>8zl@cxw%IJUfJ-wTn-aL# zmL$EwUN|(IpSlrArJp-7KOsokHG*nzQsoGA%uF5}=MV-V>;hBEJQ^WxFCr+POBSPk z8Z;NzM;$Q_nx7bMZ_Ww4p?MjqVGcY3A#54KI&QB>r5#b(+OZdd<>8>b9X);xu^01h zuYtU759xdXT+BoliO}djU=|WR97gY7eE>uX286?a2$nV!i&Hqlr*kJnq(C*l=&x^G*#NB zT1ZQn@BR5`+D3-z{#~=*pRN@7ZqSG>;#ju6Q!Z5Kz+4TbpZ=3SBod|r^*OAD93Jku z2nRS?`Ep4O?xI5{ND~m=PdX_`T+>sg0FkB;5Yhl9$#-a9)d|1x_JiRVcOvR!wIvOp z&Ia+7HpAI1M0gk(lv}6_St}0jvx^z{PGOq~x0_?inlr;$LCkN=G?Uy#Zv1pgdW)4R zoG=L{Q0OTZnXXfI)*u-Q9^u3Wg3On#w2!}l*>?a*V=y_Ps~W<-=bh6R>xbKxHmzcs z8hw^Vs@l2C*#j60mmGATO3^ZXm7`v4L~DT? zl^l~=y%7u)(M}URMgcNJSdx&^(fr3H=0et!V+Jk5`EZt^zUyIbuC(cp$9pMrfjCx@ zgzrYa!6a7WsyU{3GhVJ*E>y4lz+tXsOy?C}#$LwnyS=;BU0CBxGo$LHQ`srLtLB-` za<4>V`0W#i5vNSv!$s}utu!5Cluf^izx?wlPb1Xp)IZQ);l9nY*Mn3D@z+z}LF`o{ z5D~y{2ToRM2IyB`=*QfTnaWzNHIRZ9Me>N0s;Q_LDlD-9=5+uJVgR02nm1~<%t<^( zhRRVm{1|bfSOs3JoT zzTyH>aNDByw<*NEE!h2Fk0AB81kWd{?ok02K3wSYFym>Z0npw~J&^ylf~g?7G3P>4 zzDPI;QUf=1_=A%@##Df`sl;{_SFcKmla!9}A_sB9n6Q}U_b8n0J!3@yh%jhwI~~st zFB$qkMrJzE8d=aTW;2JMJzKh z^GX2@6npQtM)XbwfnghSs@Z@V(sJ|8*SrS@6Q@u8Eg3@Zs~39jKi_#W`A~aGZk&dx z+6KEq-}vaH7-m&eaEY5*)0lqgR;d#9ap1s7+IN`1>;u7zZKtGVh);dOY?D_KjPL>g z5FbynzY#rHt2Z_&j*y`$^_SPsKR3bp3iQy=G|PCKbl2sD&W++%iKDM^JR)UplVwOb z_HIX6NOz)yvi^#E{a-UNm52EDz-&vcjGW?yZ8_l<<7oHa&i+$|Rg#_k!i_MiJKwTC z>ZQ1CUzJ&8@HHr^!@r!RAzjy0ed0e5yI-ZcVg`;$lAvL>(38tU7U%stk4bZq1dMAw zDp`Gct5A)H!-yabEVMl8E=*#IE=X;>Xn!3W6QVpl zwBeG!BX+O)mfHC6hR~PB-_8D4Pr5FHxxNVX@8{lxPPrB``W>%7kl(5jK z)QcBy4SoL~W0N+bqa1AD?4_4Ag~(B{9yXDZ4e+vZoJj_7%kDzH{5}hexu2w&S>a6b zlBDy}>?lm|6I0^XtXh$|01YCmL9Fq$#^m0aR}pzvO}&Mip4`nYAdrf>hk<{E0@G={ z6>jEnCHVm!++DlVQ}5qNk8%Ul(g0zU+XMi@0ZNURv!_vFK!pBa(oVT-t2H2*CMeeT z;nzgUt_y##jOx0Ceca(Y`$QQ;{ltfMiEpG^`uAi$x0f00K!`eT0>}OT7bkl%#?!@( zf$meP_RG$HqnRe z8R9?W_1Er-vHkvjiiYa@M4eMVgpf*E4DV_sZFAX{nA`ugojgh(qW*P#kdYLK5ghSI+yuvn03v}F5dMDQ~I4ZwYN%iOTFw+|o~CbT`E(oZgRaE)hBSJ;n+aAr`^($$@1RSOWg~aqr~-Z8xK^>O z%s}JSrxzK4@s&YNDe=F?ws)Y%+|5;F#E2?p0)N+q^q&XNHm<&Sb-4zkQF&;k*)OVT zcj`yFPe+a9&mPyvvYTD)dEvUxTlTk3N%8zPLYjN^clxHT6}yyaaCcH~SmTvt(}~Ge zbYUsqh`#{Aw#om^)<<-@0CI_fyXMbSToC$w$|O4fE=P-+M0i`}ImLmom-_MnsPPD^ z9fp3B1rK6@)dDtFFIL{^m(F4%c9+Q3b9An!5k3xMx+7S_g7a&=D;#tiBq{epk~N_O z9+1*I<=f{~+yZ0m3oAK{-NVy$!YtAm41P8M`8ER68YWyT{r=|*tZ^Y|yk7R(9T>IY z65Cp^o)*I*_LTxgzw?CB#S|~iix%1n6kA&zGlO@)^W_Fr4wpE+1~P*IPB;Xuuyhwk=BSN%aLg{qBUvHCzo8?X%-Q-j=^r2m4UsF_OSDI0PyUut{hhwiLKaNh2K?9k z#*?*idQmBH#p(dEmN<&Jw(eP78QSTkdTmPdYRgX7LUfe1{qJDO_Hh|w;3U=|99A^F zl0wV6!1t-TO13k%KN`y;t}NR^!vurIx5=ZU`K#}SA(qNpSpsMG>&}Nw&>d(mNoG?A zL>)Kar+FgAtxwRh&e9vubv)P#K48!^@CfZ?(qwT|y;_^=0KYX8{Gc4nzt}&H4SP$5 zxxrA_Ksm-Bit>)JL7nXr?zOA$?G=Cehnbq2eTX__au@`G(*E)A0i#lLCp-5Pjr4V* z`jo!N5E|_)5i+c5TaXGsb~GBKZjTg3CE#45wZE(y-B)Y~IB|RdW7;Q31*imYfR-wF zwCK}IVt_B|_`tEvcJEVXNFO1B_WJ_%I6BoAg6=Lk;uvVmZSKCgSSa~sEc(ryhBxhYXZr2Mx#;f!<=5L? z-^E9C-@Elb1L~^Y*Ik(zokHXPNfVrTexaTi_a&Xj;0*L%rmSL!VAC^A!m?(tIhH7X z|M2~W|HP)k;Kz4no4tNd?0_f^RhWF(!2g%?;gFNvzT^QFcHaT%44#I03>d_Kk7Oks ziTZBvvtQcR48!&ikvLnx0`%0E=e}j}(@tY8`4Ipj%$F(XaCXj3D|2u*rGMntpU;_p z*^*{UNq>ar=@32%@X;`^F4tOqOXJ_C-Fqjt*sfJxLG>{6DDu_w@R}Iv;#mTm-h%l> z>=LgNL!1t@B+k^7kaPP(Y9v$vc@b-fQeqU2Ibr@zw1UV^ng{AZ1@qm??q#X>)Oo*FOlVf;de+ggrA z0y?e8bmFaj-UuRaBq7Zr5V*U?(DyxOKzb(FL^H`f$3x)d!P?-pXvb4&h?v1Ap)md% zH1)3)O!DT_PsR3EQGOr6D^z!iRFrdJAb>Ron;D< z2Jp&D@?wi$%lFg3E;nwN*Nkk;{BPpLQ*|ft{$Xn&;&nr1ca8rGFBj10FyGZ-L)fzq zd<04{T9Pm|mbT@E+@Ssm+X@O>B0(pUK?_DjyybSM^{UP3uQ}~fIL2Zk<}wxqMsuxQ zV8TZK@KuCGH*rV~blX84s;Caw-a2Zd2C+Ors`TM>qr$Wi&-7cM4`JA}F}4M@P1}LJ zif#Hu0F^4G6ag>*12%AjFG#`f(m-!`p#VR1z(lnZ>_Sy112SB7Ru5?A@}p{j^@A0R zSUU%~=80}o*s_4a;S$+iWnooxug;@oWnMV z0u|iqYWL0DSpL>TQY0517Tye&YdmbCAa;`5s=cT{Z~Hc3UrA=A37IS!uNsPRdsqoP zIS+UT7MuW+q-E%k5f5C38uyEI0wCl`9uZ*Pq;SX$V8l=C4tP7JcvD1EcmxOAC9wrx zchQng#GXp*mg>nV>s?#-6`xq-o?hh2eH}rq+zKVAh5p<(f#pi~)t(jOQ~czGEwVIR z_(g(CG2U{UgKwry-^FI?G`9fXy!{2K-hc=Ya}{7iIk-cguN*JD?gZq90Lxz+yRjc( zkuMZOGE9SuTXirvfn_8LAF&n*)HvxNK!1pJ$5~dMi~x{p3=l-YdqhDdWC9PskYg!` zDLY^j{y;$-?i_Z|zz+xk6g+_tJh{PK`GGv;!Z_IoAOMw($4&Bp4>U8ClF^0GE1G-n z_d+O}V<^9F-CR+&_o~fE)Ly06=4vO9b6&ovF0B^bf} z4VWrH7y~(ILlsEDCyf2Ak^rX-aI2>H!8?I3Aj33J1H&hSi%UTYa5cs6D#ZD5OjTeD z8AzU3K)bSEP*E;GWV&+2iHZdaCL~0dFrkVbLLfqcA_NGKA3uOJX=0Jb2@Do6V8{@X zWJ!`FcmOeoU_l5GC~Ty-;p3%<010jwDKccp5`6et#fsG`RneeAW7!hgiYQZ}Mx8#5 zx-@9ir$tGnN@`2itXHp0-AIrC1&$jwkVwgdESQrXDnwW~Xn`(61q>(DWk{E=g}iVKSX3r!(P6#|YFh~mu2n_xZuv)-? z0g4G6us{RIlOHTjOgJzgK<}|3e6UE6+V$(iix(si5Fo^lA}BzFs8It&jU7m^BO$V5 z2NCT&hj1T4L_oy7IWnLAb|UxXAGtr@zJBib>C;a>`Ityzz9f{G zkCI9ZOoE9AlW35M1$UsX1|~3JFu)^}fbc;2B1|v|N=PJ;iApjoa76P;T+otA7Q{rx z7b|%XMHp|yv63BkBoW35EwO|}9*L~v#Rpq755Wn|doaNx9E=diB5nvlITU=D0+maA zFv6ElnqWZ%gI+7nHQ3OM?SM7cWX!P=nh0hZZ0MwBnrV{B#DH4DU~ z;SMND$m^qnMxf1!CY+GslU*_iQ-lTdvMVqQR(ME=CnSRwe0# z9yX$(1QA?9VFVU@2*FAK67ZpjoqGD|sH=_=N~ximVk(uatV)(Cq?Ss>siLsb3Mp7z zd1aLxezU=>B8)JC3Mqa_L4-?LvP(3<{_<Z#)6-{u2*C0r@-n2&d&!BEkF) zG!V)JJFF4P2UV1E!6Pgr;>9QF>yQ$w0}PQv2vwxyNC#Vl(M29D!Gy*dPt4H8yNe`} z#30S7k~g3n&81uD^vjKBe@=<0i%sHx@`mzMi}Auct@MS1R8p{ z=m#O70D^`UuwUT>6Gm#)`|ekGLKtB(0V0J2MnK^PAVe4ofDnG&sY_v_H40i=lu~wD zW}~uyC}@WwE&UNCD^zg>D_pUP9sC9+HmD2`q~HYp9$e})2zZ!i*0mSC_%2@ZT9*P2 zq7cJ~CQU5q*O*km0u`!YGa~>ENlfFrlO$|;stO5c6rdW`yygI)D*^PH@PiWUMm4?( zfek`7GU5;bF^Xy5Z>Ynb9C$}NGsDi!j2508>{zw6Y0W_piV%Vpqy!xVtp-QX+J+{CwmL=#M1PDBB~k>p z63NX-M7mp(ZWN;o32sL;a#7&;HaH};h;1hF(AE&tp~l_di48;|6~2@SD_E}tf#Tem zJ_owc9l%X289@`0fjVJk0~^x_1`tLN0@(f`pkK7Rr3!Lqs4MhN1TbV&3RO6;g1r!i zfEkSiGGKxeoB(>KGC{%+hR*JJuOu`i!4JmwiGrXHiAAtU%24pD@XQY=RPmp&jy08K zX{CSCTFO@P2NeMh6e+D>g(_CDiXkLGXvj;R17$Ur)074<_2LU@EKmX#Xb*#$0^vdk zQy3EJYXKxk0X9*{8F;yi0v4bEUgX8jcBTpeI;&-~<+oAW)twPA+xXrZzd|m($or zHn#CwVot*sJ~&>V7Spc%km;k8^g#{H%pqewafJHm=QWUa~hTY7dXx5|vkdQ!} z+dEhWMuJ|1p;xCggyt1EQi2emASLV@O8{1jgMAu-e$jdsQt(HWR^aa`$Qs~5@fTof zDU?~rdgv-pF;Stpz+pNLo1?u0yE3!3a>6 zfE2JmWJlyd4{UHQR!Gq)_r!~sG?P5+H-QHoKx_q&y zgW2Y0{%4={xR z5QB=T{+H_q7*g~92U{2sR=Sh zdQeo0rRJ$EOR{oEh9)Q5fI7c%56*U`#o-_`PSeDa7~(9oo;5Bm03Lh zNf6Ym3?=x8J!<#S^MTA`M@Obo&r$B_QF`V-dh=SrC(#Uie&z>0GNn_v&6A1O>CMwXL(k zx^meH0jvI5KrPFp7zCV}1rBGyb*rgk40I-i0UId`Wx7!XtjPl8e8>}o5JXc|hT6-P zsYtTD&fncoV?EFv3uaY<9ylRayt)bGs$PV6`!~P|?l)6xaf^Hp*cMg+AONXVP=hiB z76?Rz4-U=}ejV>zaas5TfIt}TfCu!iUJj#PE(HUAzzU#%2E=P#7$e0%uJuah#aO`i zSZH||V&yoH<#4PoGzI3mOX6H(HHzZ^CSV1Apa)2RHCkge62Rwd0O-s_F~)+TsP3L> zAUkp<>EvkXV&>`0>^hi0jH;vQ*ec6}a0s_9%g`u4#>2`0jyS%A1NMg215!1!${>PLX4g&SL;{#@by#^NCQufi}) zE`sb1o`40I2zdO$f(ioxZY9_{?Ab^IU;>C^Ib!gvM24xBhyZR7OT>sQhz2~2rf70z>z2?5c0j+BK#XcYzt(Dt zdhpD)XzRRc2;-_~ln#!l5K8KZX{@G0vQP`vCXS>|2l!*q>S{v-hBfAF#+zS97SLPEQ~UOhy^@R;S!@UNbw0Yq6uigT~Mq!Nu#G+!va)b+AgC6G!Un- zlOSKtVeZ0rK;xr6?*O##EffF)PCx-(@FuWikd%mXY%m6~1Uhh{2+~Te-pC`DFza$) ztajie%R;T1Fw5R*%VIJnn-0tVLkgpi&EVtg>}syorYC*!j=GRKbO4U@Xld$$JP1t; zCqyX`XF}pI58<%U=x`6OGAlDp(awe^{YVV)46hvINtB=n25~IZ@~H|C65G-dcL~LH3P^SWz#C+Z=q zvNKhyG*xBjqlm~%W^n~{L)t8W=a32obilaUlc_TD-9msl@)RUNGG{{4>9Wo=abO8{ z;0An9s}Qv52vi6S)LGj~K^OEv&u&BKu0u*|Lf`BN{%SgymCYh_D5IvXz>+>bl<`7T zL{HR2`s%VO2`k(6k<^eXn45;@e;M+ zE-}X-knN=~U@-_F00@8wX30>dv`RB`6}PE^Ea*|K;0czqH&|oi9?)SZb1hv11r0_` z*$XACfCfUM!f54Id>{peAPS72u*wQG`wLmF@qeIYDx@Mdv9TMaWx+^66TIseKPZod$tWRLL%fk2)0W7soRKN@He zx)jn(GUhxFAYsDmVv#IYM&<@@aO#pM0Yo5JX_Bj2vOv8`Jt!&%rT_`RBWYMt2}M%t z^fQfoa6i&YL(NW+_Jc<8&Rd^`JSYzZj&MU9{={4ZWIq%MT+O4e+EqkZWDgVMMAUFz znWT5`Z1FOUw48+TIIV2hPA3xtJ3Os@R>NSO*DVoN|K>|bEpZzj_AWm`I38m&Q~(2v zqcvCn-BKs#rV!)xc)$suK!`53H)@WTP;56)_VY4eoU9Xrc8^s$^HeY} zXhI+be4uBSzzCKgSEi~u;;kFC5gUa9Dw@SLYx63W1!|+>P;(Py5{{W(iv=*{c#;P$ zFa>t{1SR~@qEgWUz;EdAka&B?#1G6*7hOanQ0|gUeO>{^BQbhoy zBgsObc?oyEQYgv_H%}>dJg_5+unM>Fr)v@oNl0rx0Ix*(Sn(jMkEBLQ9I23DG?nC7 z44LLhlsC1M!y=#8)ynkMPD&D|0d7plmOeHSYIJ%JlOXqhvB*+D^bPyzL_z69}we7k1$adLWs|0tl7>wTfVwS%xfr zVk?w|fBws#j^Y}XmV%XqPzeZwuL4m?6XD2l*UXZ(`bC8K^16=Ei9jt=m&#vQxWf>` zP1bfdFd&6cB?NC6hutNIci8?_eRu%O6FpyJIRrof!~_SE$aa2%iNyp^Ebk_AK%bPZ z>yXd~V1_zWS66m$@^r>>V+M1x4(pImj6Ii(7gSrVkX$(=LSVN+Kx8QAqe3`(is&fp za1wSK3y)j+kVoq(OAFD;lI+1ghlzE^e3Ul{dh=x9_F`HnPO2A`ws0MfImOsxmI5RR+rB!f8 zN`C+@bOi;v>{pPWnV(}UP=J26nVO4bo4J`b0aZ|$1uCAFP*VY%3720)Km~ZHmZ8JF4+m@WG<^$8@T!xe!xnw);a;R^(f}W z%DNX)xtnY#VU%Z4{-g&O@T@%sWtj`Zp_gRB^Hmjq27-VX|KcQg01BiOGDm=-tXn6l z=vP8!zJ9{8{ux`qs>OmAOrZdk6tZH$TA{KXdlja(YC`~bAY~)g5t_{93Z&s8xFHy3 z?-UWh3W$MV#>WDHfE$2706u{mXn+7v8xnIHgQU$PfIGMiT|0prpeJSkI^zJ+lVT>| zA~^cX5SIjCVxuFub|?ld=<^44Cd)7y2TYSah%P0;Y9%>WB)w`tSG}ZRX3KhTK=4C6 z@;g4vb?lm2rek_TSh}$0%Z#wQ3SGLrH?+U8hLO6`cSAH@B};b$`3~1Ekz-VC0M;nO zQn3J5OHKiqDtxLjJi}kDFna5B#(^9h_Hx)_#H-fDLU9xqPN4n8tRwDxVO%ak>IZfK z3uvG%^(vFO7^8dTx$5Z+l-M4^sfuPp2hfS_ zHc=x6VvK)wx*J>&lJ}2}hDCy2eZ1rO!oz;d&%S!{bf((Jg8J2~3}*Na4Fho)#;$uP zWr;+XXSLAvaWY>1rhenuKo4ONO6#n}D$Q;Dv@I&!rQPe&H*{~k6I-F;-9@v@gWtX$ zy@kdJ8IkqR4?`%aCL)i|qN(t&8UptF-YnT;D`zz2`S8F%zQx6>hYJf}7eaf?tS`tU zsecO%=s7uy`ywLIQ5fv{eEg6kK7}_y!LUC zz;Sneb2lZ7yj1Qu%Zc=-J3{N9`;#W0k)waitn#*IL^ZWjPE$q?`dZTvhMBZO{zy8# zk0Y>g+bkdU{OO0|i?l;&%@jh=n-OCV>B0$?QkGISMv5&K+Fx1_+Gq4%c@RM)^cSYE zFhWJ;4@&U1f18B6?0h`tM@da7gGJjXnnU!t-+5jKF9c3G!g2xa^1Vn$OI4mr*2hm1uDVLHh<1PmY9v@bRtPh>{MfE zAKXU83#5#=(zWkz$l>5fNq39-P(o}JUX9=l8p~bE+l4ixDy{k`()?akfeSDTz+ejq z!xB@XV`OYIV#UP9j$o9j)!jCj`jXYF$*A5xFfis?HeYO!>{c>_^{-7yfwE=9Go5pi zk8Zk<1r=tK5e1nL3Fa$SaezGocjdfmBH`G2oxl_F+Q0ltFI)c=?+=KCZDPR?DOC-P z^AW%1_Htv-&Ym7N%W;D4D02UiZNBNPNeVu`glcQ<(?$2f-8s&$yA#ZB1O=~(LI}0u zm2lv88F?@`Aa{L~ zcH+^P*VvrZn`$Q)vS?>MRYbV4iCf`)Yvpz@)1hJDGw`lPRw7C^Mqv5Cp{~l zgeRsWFM|ofz(A~KJLgA6&rtY1Jp9wn=&Rm}&bNcLpJw*^*OtXH&W_tXMGMBZ=TeCR z`_wD=>l*bR;CSKPI~8cuL-?o7kYLT>C{198$fRPTAt65ZzH7_RzG1HWT*h1}N;uyZ z7Wnl)A2x}9<_=i;_KW+&1V?*E$IblmQJ(v8x+^lFRE)#>)b}Lv9CAX353y6lIx*=> zG2g#Cppg%6O(@{AS^ZlPrYmwJ2=edCF{+s_(`3v$3EB}A7a#pCBLRW})4o6Wr^GEL zHa2V|&yXwSAtIRB!F-T%gf*S{F7X-1+i59;;N%|Zup%yd30j}IUsQZ8J+QEB;IFf7 zvhe5zA|-LusRg*%nrjI*Os8VPNpQpZbmH1c$VA|H-ZQPJQH%nV6b_ zUYb$mY!ctKbGqMCi;4O6uY$Y`5lSiF4<>9$Gxe@-I)dZ`iq8ee&;>rbtsXVlul6%A z7qjEA z@%ltV)Y}@zO(6xWgP{$mxBQ5HNU(At04>ZS7VV55@7_J!FWXmIjMabx#6ZSs?A^&c zR`ivXv51RQ*-)cRk<`?B2N+h;+_%n$`N*^PdF^U~8A=i*N7D7iI1~h|2`rL>ruV4{ z^p$aVAUO|{{CQW%Qkiw~P~DcFIlKkQlXE!}6O0IY#3qukbk}Xvgwq&w>w}Stk@Jpd zKbB`$JMW2oY&W;~X%&Za+kZmX>>MA?h>`jZ=ng<*&K%jGRNkCXzWfsvb#R~lyop>wR$Wi)6G;$BW2h$Q_CQo6IN%i;czz3yV1 zH7A!ggH1?$+9xM3kJYpvwxF<335PI4!FVF~(8P3e#o;|6TMl^|Z#DYN=!GM`+=P_m zcWNGmxs095Ay>0h89~+e z(=x76>kN0bB0^`4);PB7JZUcTc635RrE71utO?l7=nM_rbeS|-87Se-cp!i6mxn;9nVha2zxOynWJ*Xe z?6`A2ADSTKDiU&ZWF>&lyU6$8*ROXS_@!2;iYg)zZ@OTIKEi*F%){eZtIHJhnz*%k zTE6ShE#vnCCXATU%T4YV>p1wg+*$QR9yVT#rvPlo$TaU3TCC5?ia9&$_v*06M<(m& zp>$`WX?CKDD|5D$&yJ2BMNRXzN2+T!;$S*qwDwmXYtnDFwjpT<2}HFD-rpSBIaq>YU!~ zPum+F&dHGoB+E3o6%z{i!m8wko1d&SC(7o+i*VJKV3U$c>FT0Ve5*&45Dom+hzHJI zWfd2hTQ?GO{Soneugu$b?}W0;6j@|Vxl+DIMp{)k9XX4IsCR{ZJLlQy zt6SkGs(`%08YuibIy4ne_(McVY_0TH5=CJ6uUm?NT2ec_cX#pl2Hs)R5|J@Kf41(7 ziPSyaS%wp^Vw6rxxVYeOFS>A=RVED%mTFaYGb^K!4I2H+r>wGDj7j6Wt}q*-jen=p zRc?F>(vIDAPc#UfytgwX+hV$e)2=3OEbR-Y9J-o#UqyJ&dNC#4gHdl-&$>XKA%Wa^ zOniL19JijFYxQ|JKiktDyHVt_k(|XyPD8$)FN4$?; z%u$e#UPoL`HfUK|_rw`CMSlY!!G4Jg1BV*-2(+1C9HU~7$SA$VB{~mnyx;`2%P}|QhDWRpOkNNQRj>YYOC09O8 z^rcgH`MggnarP;c>kcvMT0b2xH`XEZdr*+8uq(6LP5(>=;pP^)k}D zb|mkm5fErXCg5m{0wg6hP1$eLv99VW|0q&_V9T(sgbiR92Q_m3VFC#qecin!Ma<1j z{?kRPg^^K~W5Xj1rKwgo3u>zT8&lIw6O$^_jouF?rlh9VN8}*N#(goH0KD+Rl5%o9 z%!ZPObEJ&R=50YTXBSvmkiKu>UhP&)cr>rH>SGBX-GAt@_fAspTCcNL++pa3=+!>k zNJ}ynF9@5fFwW+}mgS^b$AZtZ)&3}o3|oC-AE{pW%{=3gxS#-5G>oCj(JEP9ON*z$ z{kb>jwT@Pm1&@~2ENCqN+2c2br?vd(c1~7po=&3@w5qKHI)YyRvYw6v85%TXhUAGW zf5oILX^|}M>x-G}*2viS2hq#qVc!~16H`;pp2$rly~BJZfwaD1ggYL zE*-*kEG?6i{5K~AWG+807%XUCT8=mgK5i%#TsPJY9UYz7^V3(#(+ayqJa55wqtjg( z30UOh1EZNRwHq-%s;sAzUlad5+06@^t=yJ4J44n2P!rFn<)1|dI1r14B6+?0v226e zF8H9*VR%x3!NHmPJx#AKX(rRd{no0GVx{u_43s`b+*_4e62Jo*rR%#s$mSi!JO9PO@5JZ2r%94*kqs09FYVkb@TK@n8-P^aOw`Y@?N(cZw$H%otGexYG zRa7M9bbPK`WWsPX`w-t=20 zrp#ZzcB;OGw`fGYmWlzr+oNfTw<`rAl^OawwnMtD!qsoQszvwyVcnYmZq|k1Cd0+K z{!Lp~#7krKXJn`?AoV^Ux*klsCsb3LR8>_~QdSNc*c4S#jfur3!-=ki0++ z3Tj8I4Hq_B!MeGo|Jk7$fw2pnl1b@6m>usDc74WR$SJ0fvp8F7i3iX=KeKR>6ciZPlTJJt4|Ci-`ONFQClV<{Yg|4|DYwFDY156=&g z;7AaLw(po<71gSThh|w&am36XZZI$Qr&08#_oeW-qD^^DnsIvHjuBZ_KGy*{ljh_j z6;r1DuD2q5df)VNPmWQyHatAsP1pApw%&CgM|-=oEUM3>-KrX6P^N!`xt+!3!0ld2 zrSg%z094KG!+ELa^`gS}h=?}38dl5k#MX0GA_1R=1aV}@6wANuD6! z|FNRJ)Sd_bOWFg%b9JZ_8yy{J_)oyBm05G6b%1=$v0>3dd+{$6C8e6WdfU}ugI3um zOi=k~q`d7HyBaK?gqhnr4l_l~ptJ;=)3bVQ=KqEj>$nHg*8`M{CZ<+WRqcPcH8|ZX zJa#_$gtxnEd9YBIAj3rHs*NF$e$wKCOYF+`_JTU(0>j_r;3Q)9m# zp~Jzr(lQgGS~@KwE6ag|RPL&NB8df%ke;4iq{(P}LIox(_o_VT6)r9=z<||L4~M#i zFWAA^t4GysF(&}xxKcPFW(5TWe~OAeD&g30F<@e@(uKCC3*39|{YiN51PRuv%ynLm z(3nh8z|!s6by3pNk~1<|u;l0Uo4xz>!RNjA`;^Q5nzP$&gG$?ZyQ7w8G{E$Pb#-Ow zbhpThe;E?^VuByt{vy-fz5BM>fj}|fdk-q>b}j7p!#X*$ing|Rq52k3g@MS#51>z%L$g>N zAiU%IJv_F_Nwgw$h8290kPye59LQANDpAk}fJ(nQ?AL7-agspz$$f7K2jYDcJZjT` z!cGR!alEi#8b~P3)N63@x;j9;X%bD);|l5sj#5-r9h@p|@O-*`9&h#`URyV|D%y9c zzg_tX_i+3yDDm+l$@A^V{KF=*yJ5K|5pFvxDooA12sqUB(r4nu2KTk{o^-$jzce;N zr^@5g#`b55XnCKsnKzyf_q1oT2gG8G>Kl*q z(I3E4^iQa8B38{$2D0$;#_~y$f*CaBznAiO)0oU4vowee7>DU zBBU_kVm6p4t*=j6+I&aG=YF)AWUjw;+VdQkB}owUocR4a%iZOEACOL3HFoSMc#K9M z5m8Bbp~AWrcD!*qSP)Sv*?k`*wW}n}o*qhTFQ@hauC28^KYjtu(QJde(B$OsXgy#M zUAr#j#yw<9KG(*0jGB_Brnt$utll@mzR&lwF8fpEV_AFNZBRH;nN2jfS}`eKMc_%( zrnXEfHcy;wLxo2npc-l|92)d~{=`w%J1n|PpS8)Vt>xG-9CeMz%EH1CE1|9urO4~T z?8UTPxKvZ45o(E6E1X()`waYB&{|`nF^G||Z~3sN`4uKO*4B&?c`vadNqAnuu$5x3 z4q6cdsLA9q7Cze8P{^N)l7<*kzAI%dt*(}*OM|&8sXW_x^$1`2wozTp#58}H z`_T$h6fXOvwpfMc&U)7rz-4~B?sL$}5p@8hQ_|Iy1_X$EfhB^uS+oEC*iTwa40miy zX)uvBgzqR(3P6GX69<0ecO)vxO#RmLIQecURh$i!#cBu!tc75Py#ESLZ^iQ03deP~ zqsDV)S1zwuQ^LH7z8};*(WXfi6;7Zc$z8qJ0?3jM4vc|P_PXy%m1J4otQ^4uc8;Ws zOr}8#nW$(WcWv9fw;4?kmg{vGK1K@F;vIC zDV~tML~%zBInG*g#yf=q3;YIUV9IoGCM8P!M_~r&S>90fa(Q zN(%a+{oo@O2QrqUG{mln2xdLNF5EnJ2>5b1KmUJPTU_1f3`zo}Wq!8d=)qszgaxRf zw)QYkJhRo-Py5X+jR>UKESv(`1Bq?VMQ zPsh|?!-yBg$w64(jk`DVX3-8ENOoPHOA~Lwh~8cY;0V9?W74zruK2+30$%O}aOFx` ze2JbPwhIrI{k{V?VXVRwd9mKL`_OG#QzPC~mNqUll>X(XRbshC4>#9 z@v(gpwg)n`ccsk-;K2I1=SRw#U&U>{954u1l>GdtK=4+W7WM-F_=O%*UR*Zex=maQ7#DhVO%nD0u$x$9T@jiLfeKyXpv`pH7m{cpa~_8iIo(w_sQ zE8gANN|fhwA?GjkUQLNSUKbR=b7A9iJD}m^jS+e<;0a1yA2mGvrau4=|Zs*uQ$*$XKlDl zw7QL09WN(UG#fp5=4+14#NRQg`8u_`0L%d-rlMaqoFkiHA@qqjj*jh#DAtaQ@HodsEvN zpP*3&dJGAgMzJwS+iVGEKtEs~vQe)N??Zsgrrp=}fl@_LQMK^xV&8ug3S60l5+gw2 zK}L=?xVr;)9tH#BBUUIC^}tiY{Gr)$GmHoS5`6GmtK`mHIli}yjFh~*p))fU!mo=# z+9|&HRse>tjs#kV(>}jgXfY6A{R0;e+}N|@fu>++R&+GRNUdW>5~nrqV!i1vORr+@ z_)mnMfSgh6Y6|^vS&S;#@2wboIeKsOQ{(-Sx8SokEBF>TczEY)VfJTd1IKJ8l73r* zb!p+@S9z%lu7Vi91D)kA2qHaDW)6A7#p)db!&}HBrJu`&QBn5&b7o!~DdHD-`1wIi zocATi3QOkP`qy3b&;zYaLc?Tk7LT8_?R)g2da#j?|JCFI<(6zstD>s<(a!D_aNFb} za&(?Cq}V~X0;a`^Q=L|W8@v1RBa~i)83rzH$dFl=5o_?UbKOGbtfcjnO;%Z1;$K^x zv3ggT_l99Hkfap*ba_w@04_}~cG@xt6=NVj($l;D{fSK!@}qpbzh3Jm_7f}B*JDi? z;-pn+xfs{k*v7{sCYBQwB>)0sD8)VKAmlq@kBRc1!pmYqVJ9c(>qU3s*f&w8Z2?{0 zPq)(~0$!+vYHKs)-dWk%;M=@L?r@e$v@!j-0i~{H|E5Ppvij&_9aN`m8J}YXQBVGf z;Kg5e*uKth8i>po|6bgKFzAjifEnK2e!G+nxUzqEa2 zFz5?kJ63aS=P$J5M1oecjeEE*b|=zs8K$!Kc>#Yk>$vY`54FiF1NZ8^(5(y-fl|SZh zJlwFzAO&}HjsIq#d-tx#s`}SHJX$dIr>|epNVTzXakb2w70W7Q)ZV%5{Y&RNZcLJj z6|c0JMfbixyasK8hm}=nyZFtP=-MQKZw5;mR&h*}bOPp|K;W|xiL$#;;qYmQqi@e8 zItIQBRAPijb6#Y|6fm=aGzHc22~^*L>vX9|U%3#uA31PbX&ALCQY$J#g=rKQ1As9& zXvS_kSA}ddC0^VXM-~-)w8+Q)`6{BbQ}lE_Dj2w9YrQ{=IRiud1X^|~GzS40AFFdl z@VeL;{P}j>a*`f5qtr|u=wm8DL8ej881Ao~$Emm)|Zv4?()iI8Jg-M;oh9H2uYVq}_gaghhrm z|31C{+kTnsVHLgdEdxWIOhTltQ=g{G;|6`sL=OvvfItLL49ZGMePxC)V^t~%h6vm@ zpV*sX$2ih@druTgU*HBP%dCnD+=EYNAIu$U!Yov9YB-JW%N}xm)y_czZK~xk#Q^SdQY7+~del>;5IB6xN?V zQowsyJN1Bxt=bp3OvV2B$4txAX+zPesf&@$z+dM&Jz;aki$acPF{blhJ|-&qJ}w3HM;wS8!+xnk=R^-yq9SX<`=+4zhXuUt{jUkAa2j7G=ewb z7z!EK6kM+cHn0sBoi7JD}$rfmh=M-|#s z;uIG0AkQ&$p*SS$C7gdH6vLp?j+`#L(bO@n}F&jB2^e_*)`Vs6@k8vV{LzaCi>`fz0VB z-+m684M#neOEeK<_no~RZ?mQW)4=ew&SFOu3rp#Tt`cU9R2fkVDGIPofeFF88H;xI86wf*;5h+Z@Y6q>3Bv;q? zRBC_XNwY4dKq>)EfGB6K)R6>5ts=0Nz-LKDBn;tDp|Z>s)lOU^#9I1FI-KFrvSH1C z(|eA$&iY__f#s_Z$=H{N!wnB#Z}=-SI1(wrRSSJcV-o40m-fO0XGR?H|WY4A~&vil03n1*p|4#uBA%*+`? zRDrx$&a^R1pB+P61#TT3tbaQO7tOE%=dWJ|)&ZZifA{L+bI<^>D`a!w{A~9-8LO7^ z3ah_bT06h|r==QQOt^@9Wlvm}cn?_tv|eUJtEB%**ZNOMuUX#Fo^=0_QK%49YZ>w2 z>ko0VdCeBo7;ujPTOYrEq-`Hnq9GhU=b;M-OOQ5UB(UgJ%{V6`3)l)ot%b_SRPql` zn!tx@D~F=x3D`GcejSoc-|?x^nfx6Cw^F7W96U1YxA5m_hzeuJfdB!~6e~xuHq1hh z|HP=@)$)}CqU+npng%z%sD}xAdvD1Qttgf zDaqcLzyR-lS}*vg2ZG`dzi1Aa{VyV|Nw==B0hPfy`T6zTLUNZ1N%o4pKUbf3KNKGS zqTL$&dutFMWK+EV$X+L;H?0J0&WV&2(ZE1VM53shYY~U$Y$)DQMLf-~nZH+d>Q|1< z1IRqm_Fzm9mb{Jyri%54I{RYl4#s6pQ`;_0l9`YsdziNZ{-*eMNJLe*j2Wq_t=?%> zTHWZ&3SX5Q+sxMfNSx7xM<<5&zfNG%4x}2mI9krQnVHe1Qp&o@C3%=z@I1e{c|@;0 zjn}C)nv2WiRM;}Y;l-bSME2!LPLbroxHSnlCy(pqQ6gH{{_3}SeYC7#=qp%XLXMu7 z3SXc5BR*YT+KL{pvTiEZ7}|}Lzc)rK?0s_D&~FW0f84UvZq8I;2mW8M8@M=SW|QciT};Zl08%pkC6AzdjuILQ`^h5r^2ufZ+|Ri9DJ=LH2qqckYR1C8 zOO|Cd=g*GSm8*thv$>1inZYe>dKq}O?Gf{ixoV5#?$7p(Z3UYH`?`fp{AB3tc8x!d z1U?%ntC_5vw0-42+ZHmencwFJqX-EO7;_n%t~5r&F22v zM@^c1ghpmyn60O-Xi-=l1Ci?z?M>6UMX(mBR z{{g>ChCwsuv~9Z3fb9C@Z%Ii%<9|BbR#`$IG?R%OHU#hm1>m){oy%s-%tCwawep_qDqF8Q4hBxp9mylM3>5qaq#iZ z+9woZ6H@`_dQ9|Pbft9MzP#0}w8Cw0JNR@Eh%|HHW}+oN;mTI;(xwE)%WAj<1wl)( zG)Sc*5@Mb_N$1<_CNnJJ;be;kx@!Q*DKJtcvjf8s2)NBqv3tV*I2*e_5l{%gBGEE(-qOT+gsVXGih46qyWeOM|7|^Nk{AY>1d|P z8yqpD&0sK!jC}sQ7Q(Bb1btOBUlnq!QS?iqRR3XfOu{lYQPW&@nAk%~IAo&ldLY4> z@l9|u41AgW2$_ps&*3SH;Wz|bvFY#MAUA=N>&TmigNp-878p3a-^GFoTm(fqS`svg zVsjcPtgpdnn{7ShJ%?_UNB75sJih#_N=ikys|b;s8>7YM3cRK!4(}Pl-Vt-Nxvhzw zIXl1PTMQ%B%FEF$o;Y~%r%+N0pyqd7j0oV9M}h}-JKbq`(Gj+?GEZ*I3y z30VUeO6!7-n!Hw>>WHktP64K6Wu_84rAAK%w+cRenfYu zn(HQQZblu|_6dxJkI9^O;U4Wv#NIl0&OtHb&A$tvwta+qjbxJi>WNp-x^rVFe%HMIw;ZdA9!g}%sl zI$-gEmX|gtO6caxWuXSU(I5ei*lB_}_uVcMEI6mb>Np*xiUzLSBEpp4zYsM6wv{uF zmy}@^JVz}*JzZe?%(ZE1%74ERn}>=p*_;zNWVcH|n7+!6Y?{h4pRBvcN$^H|pen%@y zcyw$B_aqM8R%h?(x#>~227f* zF{RCGA=~NH^t{Ox^A>B)G!@XI5!fnnD{kR`#zi>&4?yIC2ozSz6 zF8JM$;3dTfSxgW|gdRD_8y~qaF<)P&aGZn%cjiERIgQ~l-9CQ}DP~(4ndnOAWWPsGw?p22)FEros7*(9zN%K3a^ZtPNw+{be=*_K5H4e>=C!Af$p^{&VAE;csBor|GoPW=P@7L)ay zEzc7fi0J5fRXKq%SycVfGP?`K#4UCEOg3(O08YSkBt-gu4#~kmX9D>I)C7s32S(Jq zp83(Y>GjNltboP^-FTlz&d=`0R|#m-j=Fgzdb@Mk2%&wV-XEcz$-EFH)Nnh*BiiW) z7Ce`I7VhSkxf%%(3OvGF8ThHzerA?pA9tZLYL4 zGTv+~VYcMOK&n%-wXveExUk@Rz1VoS7WQx+@`fd4$fHr8ii&Dytbu#1yu9)DY=H6p z&Uz3J1uRI~>ij)9jG@VN)s=rEi8WtiCpN*Mos)Z3I<0@M!II{%tez6F$me#_oADGl zYa>4GiX)a;l>?hGJ#du9S3Y~_v0#TI1`Lj5iRa_z;o1e=7nBY_`3C3Wm4=!++xg%5orP+KpuWIKGvm%g)`Pjf5yiyZ zcLTt-uEPu|XtTH=Hlb|wa8^*y!-!GUTIhxul!jS+OXY^nIE0?-Hv zO@=c@Rz-qu4h61RY+9jG-xOOzaTUg;n2+R|X9#-p+LIAa7q3U43w4r_$(xu!u7z$W zY8GAbY1J-KggOwvg|ueeLqCoFD6veGrJZx%`9iWYL)^LPGF~l#zI3-k4Lakl%h>_8 zLYdXAQvp4_#p2(0pnObjcE)bJ1;d3)W@A_ zr$7w@k5?HZJ^u#de0Ad4RAo1frb6LDy^aqjRHWONH7Hv^&mfZG?E`z? zl9Q9mX4#K3_t!dDHwr-ltx+(MJ1QaFC%&wV#cbJ!yXo!%Db6Eh$jt8X(7t@m#`~%7 z&BNmliNwc-Wyjgy32W;fzvD0^XXL`Zx9-d*kgFH9+EtF#sJwwZ;OdV3_;G^@Q#!3v zY7SP`an!n~1~_AWe%+~;2U(e>UG-_eAExoTn;1e%!)o^65gHcyJu<}b9;CN`Qy{T@ zQ=I%e?XvaJ%x;a({{vV46Zyl(PX&M923e}^=_z{$k_B8G>%y%p{0W&>KBmwW3I z(%`|`B}%ZrHUxWjrj9*1 z7pXKHARL%Q5G_jNme?vm^8-qmQ8vD&(rBsP}y#C`|6JJNJ~ zraYpgiuIMjJA>i9I0l8?Hl+Sc;B+z$4sIN3=u^_u2F)KgrywQhi4#O0UryELVoOGZ zt#|9#cbV0kPZgG)9mFaCZJ)^RN^iKjs`;f_=%LkJCSFlZO>AnlW_xN?T`V=f9Jf-q4R9^vOJXB3CaxYL5yE9T1jOpv2UqB&ER~ zg0i}Hf28faq`cJXU5b6KK>`zCgTPY|k%-Ju$dj+KoQ@n3ybUzw;c+>di~ASn96u57wjlb{2Rxx-1aU^SFP? zbuY`!n6PE0MXMG$@xyMM-o8IskMcfS4Icb_tvBbo)0&DvZgO(6nn<(zbJ_*E+|W>$ z&zbgzk`n**KNbGlUh3YM&JD{6#G2VwmQiT3*iTy3Bm2de(lL+_JAuzcljJBXw`m?Zhnk6&C1Gl z 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

~SltLUfWi#F|)DMEF-JpFXH|riN6Rpn>2E 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%0DjT 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⟑G)4g(HIqsa)=Cvt2(`3fDX;RN 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
%aS&2 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