From 0349d52e9f59e5627d34b83c96a79a647fe70bf1 Mon Sep 17 00:00:00 2001 From: ybbh Date: Wed, 8 Apr 2026 10:40:24 +0800 Subject: [PATCH] 1. mudu_sys 2.sql query/command 3. indexing 4. time_series_file --- Cargo.lock | 64 +- Cargo.toml | 2 + doc/cfg/mududb_cfg.toml | 1 - .../game-backend/src/generated/game_object.rs | 4 +- .../game-backend/src/generated/instance.rs | 18 +- example/game-backend/src/generated/mod.rs | 4 +- .../game-backend/src/generated/procedure.rs | 228 +- example/game-backend/src/rust/game_object.rs | 4 +- example/game-backend/src/rust/instance.rs | 18 +- example/game-backend/src/rust/mod.rs | 2 +- example/game-backend/src/rust/procedure.rs | 6 +- example/vote/Cargo.toml | 1 + example/vote/src/generated/procedure.rs | 85 +- example/vote/src/rust/procedure.rs | 25 +- example/wallet/Cargo.toml | 1 + example/wallet/src/generated/mod.rs | 2 +- example/wallet/src/generated/orders.rs | 931 ++++---- example/wallet/src/generated/procedures.rs | 1866 +++++++---------- example/wallet/src/generated/transactions.rs | 1076 +++++----- example/wallet/src/generated/users.rs | 1221 +++++------ example/wallet/src/generated/wallets.rs | 639 +++--- example/wallet/src/generated/warehouse.rs | 2 +- example/wallet/src/rust/orders.rs | 929 ++++---- example/wallet/src/rust/procedures.rs | 69 +- example/wallet/src/rust/transactions.rs | 1074 +++++----- example/wallet/src/rust/users.rs | 1219 +++++------ example/wallet/src/rust/wallets.rs | 637 +++--- example/wallet/src/testing/procedure.rs | 52 +- example/ycsb/Cargo.toml | 1 + example/ycsb/src/bin/ycsb_benchmark.rs | 43 +- example/ycsb/src/rust/procedure_async.rs | 2 +- mudu/src/common/crc.rs | 17 +- mudu/src/common/id.rs | 4 + mudu_adapter/Cargo.toml | 3 - mudu_adapter/src/backend.rs | 6 +- mudu_adapter/src/syscall.rs | 6 +- mudu_binding/src/codec/handle_sys_session.rs | 26 +- mudu_cli/src/client/json_client.rs | 32 +- mudu_contract/Cargo.toml | 1 + mudu_contract/src/procedure/proc.rs | 2 - mudu_contract/src/protocol.rs | 234 ++- mudu_contract/src/tuple/comparator.rs | 45 + mudu_gen/Cargo.toml | 3 - mudu_kernel/Cargo.toml | 21 +- mudu_kernel/cfg/test.toml | 9 - mudu_kernel/src/collection/mod.rs | 2 + mudu_kernel/src/command/create_table.rs | 57 +- mudu_kernel/src/command/delete_key_value.rs | 94 + mudu_kernel/src/command/drop_table.rs | 30 +- mudu_kernel/src/command/insert_key_value.rs | 54 +- mudu_kernel/src/command/load_from_file.rs | 153 +- mudu_kernel/src/command/mod.rs | 6 +- mudu_kernel/src/command/save_to_file.rs | 201 +- mudu_kernel/src/command/update_key_value.rs | 98 + mudu_kernel/src/common/mod.rs | 2 - mudu_kernel/src/common/mudu_cfg.rs | 107 - mudu_kernel/src/contract/data_row.rs | 238 ++- mudu_kernel/src/contract/mod.rs | 3 +- mudu_kernel/src/contract/table_desc.rs | 34 +- mudu_kernel/src/contract/version_delta.rs | 13 +- mudu_kernel/src/contract/version_tuple.rs | 19 +- mudu_kernel/src/contract/x_log.rs | 29 - mudu_kernel/src/executor/index_access_key.rs | 49 +- .../src/executor/index_access_range.rs | 67 +- mudu_kernel/src/executor/mod.rs | 19 + mudu_kernel/src/index/btree/btree_index.rs | 246 +++ mudu_kernel/src/index/btree/mod.rs | 1 + mudu_kernel/src/index/hash/linear_hash.rs | 182 +- .../src/index/index_key/compare_context.rs | 55 + mudu_kernel/src/index/index_key/key_tuple.rs | 94 + mudu_kernel/src/index/index_key/mod.rs | 2 + mudu_kernel/src/index/mod.rs | 3 + mudu_kernel/src/index/ordered_index.rs | 125 ++ mudu_kernel/src/io/file.rs | 1048 ++++++++- mudu_kernel/src/io/mod.rs | 8 + mudu_kernel/src/io/socket.rs | 1107 ++++++++++ mudu_kernel/src/io/user_io.rs | 52 + mudu_kernel/src/io/worker_ring.rs | 152 ++ mudu_kernel/src/io/worker_ring_stub.rs | 38 + mudu_kernel/src/lib.rs | 17 +- mudu_kernel/src/meta/meta_mgr.rs | 50 +- mudu_kernel/src/meta/mod.rs | 2 + mudu_kernel/src/server/accept_handle_task.rs | 70 - .../async_func_runtime.rs} | 8 +- mudu_kernel/src/server/async_func_task.rs | 111 + .../async_func_task_waker.rs} | 12 +- mudu_kernel/src/server/callback_registry.rs | 232 ++ .../src/server/connection_worker_task.rs | 144 ++ mudu_kernel/src/server/frame_dispatch.rs | 73 + mudu_kernel/src/{server_ur => server}/fsm.rs | 0 mudu_kernel/src/server/handlers/get.rs | 21 + mudu_kernel/src/server/handlers/mod.rs | 13 + .../src/server/handlers/procedure_invoke.rs | 21 + mudu_kernel/src/server/handlers/put.rs | 23 + mudu_kernel/src/server/handlers/range_scan.rs | 22 + .../src/server/handlers/session_close.rs | 21 + .../src/server/handlers/session_create.rs | 21 + mudu_kernel/src/server/incoming_session.rs | 34 - mudu_kernel/src/server/inflight_op.rs | 25 + mudu_kernel/src/server/loop_mailbox.rs | 67 + mudu_kernel/src/server/loop_user_io.rs | 43 + mudu_kernel/src/server/message_dispatcher.rs | 59 + mudu_kernel/src/server/mod.rs | 65 +- mudu_kernel/src/server/mudu_server.rs | 284 --- mudu_kernel/src/server/parser.rs | 48 - mudu_kernel/src/server/perf_test.rs | 871 ++++++++ mudu_kernel/src/server/protocol_codec.rs | 55 + mudu_kernel/src/server/request_ctx.rs | 158 ++ .../src/server/request_response_worker.rs | 40 + .../src/{server_ur => server}/routing.rs | 4 +- .../src/{server_ur => server}/server.rs | 444 ++-- mudu_kernel/src/server/server_iouring.rs | 343 +++ mudu_kernel/src/server/session.rs | 208 -- .../server/session_bound_worker_runtime.rs | 124 ++ mudu_kernel/src/server/session_handle_task.rs | 71 - mudu_kernel/src/server/session_mgr.rs | 44 - mudu_kernel/src/server/task.rs | 21 + mudu_kernel/src/server/task_registry.rs | 117 ++ mudu_kernel/src/server/test_pg_cli.rs | 93 - mudu_kernel/src/server/test_sql.rs | 88 - .../transferred_connection.rs | 14 +- mudu_kernel/src/server/worker.rs | 1067 ++++++++++ mudu_kernel/src/server/worker_local.rs | 47 + mudu_kernel/src/server/worker_loop_stats.rs | 31 + mudu_kernel/src/server/worker_mailbox.rs | 7 + .../{server_ur => server}/worker_registry.rs | 0 mudu_kernel/src/server/worker_ring_loop.rs | 836 ++++++++ .../src/server/worker_ring_loop/recovery.rs | 122 ++ .../src/server/worker_ring_loop/runtime.rs | 183 ++ .../src/server/worker_session_manager.rs | 283 +++ mudu_kernel/src/server/worker_snapshot.rs | 91 + mudu_kernel/src/server/worker_storage.rs | 871 ++++++++ mudu_kernel/src/server/worker_task.rs | 66 + mudu_kernel/src/server/worker_tx_manager.rs | 173 ++ mudu_kernel/src/server/x_contract.rs | 1067 ++++++++++ mudu_kernel/src/server/x_lock_mgr.rs | 40 + mudu_kernel/src/server_ur/inflight_op.rs | 79 - mudu_kernel/src/server_ur/mod.rs | 33 - .../server_ur/pending_procedure_invocation.rs | 39 - mudu_kernel/src/server_ur/perf_test.rs | 526 ----- mudu_kernel/src/server_ur/procedure_task.rs | 119 -- mudu_kernel/src/server_ur/server_iouring.rs | 571 ----- mudu_kernel/src/server_ur/worker.rs | 1043 --------- .../src/server_ur/worker_connection.rs | 120 -- mudu_kernel/src/server_ur/worker_local.rs | 38 - mudu_kernel/src/server_ur/worker_local_log.rs | 444 ---- mudu_kernel/src/server_ur/worker_mailbox.rs | 7 - mudu_kernel/src/server_ur/worker_ring_loop.rs | 1054 ---------- mudu_kernel/src/sql/binder.rs | 436 ++++ mudu_kernel/src/sql/bound_stmt.rs | 92 + mudu_kernel/src/sql/build_where_predicate.rs | 25 +- mudu_kernel/src/sql/copy_layout.rs | 77 + mudu_kernel/src/sql/current_tx.rs | 4 +- mudu_kernel/src/sql/describer.rs | 63 + mudu_kernel/src/sql/mod.rs | 9 + mudu_kernel/src/sql/plan_ctx.rs | 11 + mudu_kernel/src/sql/planner.rs | 189 ++ mudu_kernel/src/sql/stmt_copy_from.rs | 4 +- mudu_kernel/src/sql/stmt_query_run.rs | 3 +- mudu_kernel/src/sql/value_codec.rs | 42 + mudu_kernel/src/storage/bufer_manager.rs | 136 -- mudu_kernel/src/storage/constant.rs | 101 - mudu_kernel/src/storage/data_file.rs | 161 -- mudu_kernel/src/storage/data_header.rs | 4 - mudu_kernel/src/storage/disk_io.rs | 94 - mudu_kernel/src/storage/extent.rs | 755 ------- mudu_kernel/src/storage/frame.rs | 120 -- mudu_kernel/src/storage/frame_ctrl.rs | 146 -- mudu_kernel/src/storage/log_entry.rs | 50 - mudu_kernel/src/storage/mem_table.rs | 5 +- mudu_kernel/src/storage/mod.rs | 20 +- mudu_kernel/src/storage/page/mod.rs | 9 + .../src/storage/page/page_block_ref.rs | 293 +++ .../src/storage/page/page_block_ref_mut.rs | 477 +++++ mudu_kernel/src/storage/page/page_header.rs | 249 +++ mudu_kernel/src/storage/page/page_id.rs | 1 + mudu_kernel/src/storage/page/page_tailer.rs | 140 ++ mudu_kernel/src/storage/page/record_slot.rs | 250 +++ .../src/storage/page/record_slot_ref.rs | 96 + mudu_kernel/src/storage/page_block.rs | 147 -- mudu_kernel/src/storage/page_id.rs | 4 - mudu_kernel/src/storage/page_index.rs | 37 - mudu_kernel/src/storage/pst_store_impl.rs | 3 +- mudu_kernel/src/storage/relation/mod.rs | 1 + mudu_kernel/src/storage/relation/relation.rs | 428 ++++ mudu_kernel/src/storage/space_manager.rs | 35 - mudu_kernel/src/storage/storage_cfg.rs | 7 - mudu_kernel/src/storage/storage_context.rs | 97 - mudu_kernel/src/storage/table_space.rs | 136 -- mudu_kernel/src/storage/test_pst_store.rs | 6 +- mudu_kernel/src/storage/time_series/mod.rs | 1 + .../storage/time_series/time_series_file.rs | 1701 +++++++++++++++ mudu_kernel/src/storage/worker_kv_store.rs | 431 ---- mudu_kernel/src/tx/mod.rs | 2 + mudu_kernel/src/tx/test_x_snap_mgr.rs | 7 +- mudu_kernel/src/wal/log_frame.rs | 277 +++ mudu_kernel/src/wal/lsn.rs | 1 + mudu_kernel/src/wal/mod.rs | 16 + mudu_kernel/src/wal/pl_batch.rs | 17 + mudu_kernel/src/wal/pl_batch_worker_log.rs | 175 ++ mudu_kernel/src/wal/pl_entry.rs | 57 + .../src/{x_log => wal}/test_xl_batch.rs | 0 mudu_kernel/src/wal/typed_worker_log.rs | 265 +++ mudu_kernel/src/wal/worker_log.rs | 40 + mudu_kernel/src/wal/worker_wal_backend.rs | 1289 ++++++++++++ mudu_kernel/src/wal/xid.rs | 1 + mudu_kernel/src/wal/xl_batch.rs | 18 + mudu_kernel/src/wal/xl_batch_worker_log.rs | 217 ++ mudu_kernel/src/wal/xl_c_abort.rs | 1 + mudu_kernel/src/wal/xl_data_op.rs | 56 + mudu_kernel/src/wal/xl_entry.rs | 45 + mudu_kernel/src/x_engine/api.rs | 107 +- mudu_kernel/src/x_engine/mod.rs | 2 + mudu_kernel/src/x_engine/thd_ctx.rs | 87 +- mudu_kernel/src/x_engine/x_param.rs | 7 + mudu_kernel/src/x_log/fsync_task.rs | 122 -- mudu_kernel/src/x_log/iou.rs | 513 ----- mudu_kernel/src/x_log/lsn_allocator.rs | 31 - mudu_kernel/src/x_log/lsn_syncer.rs | 346 --- mudu_kernel/src/x_log/mod.rs | 26 - mudu_kernel/src/x_log/recovery_task.rs | 84 - mudu_kernel/src/x_log/test_x_log.rs | 367 ---- mudu_kernel/src/x_log/worker_kv_log.rs | 423 ---- mudu_kernel/src/x_log/x_log_file.rs | 389 ---- mudu_kernel/src/x_log/x_log_file_iou.rs | 31 - mudu_kernel/src/x_log/x_log_impl.rs | 137 -- mudu_kernel/src/x_log/x_log_service.rs | 115 - mudu_kernel/src/x_log/xid.rs | 1 - mudu_kernel/src/x_log/xl_c_abort.rs | 22 - mudu_kernel/src/x_log/xl_c_begin.rs | 22 - mudu_kernel/src/x_log/xl_c_commit.rs | 29 - mudu_kernel/src/x_log/xl_cfg.rs | 44 - mudu_kernel/src/x_log/xl_file_info.rs | 9 - mudu_kernel/src/x_log/xl_path.rs | 10 - mudu_macro/src/lib.rs | 23 +- mudu_runtime/Cargo.toml | 6 +- mudu_runtime/src/async_utils/blocking.rs | 11 +- mudu_runtime/src/backend/app_mgr.rs | 6 +- mudu_runtime/src/backend/backend.rs | 5 +- .../src/backend/http_api/io_uring_http_api.rs | 6 +- .../src/backend/http_api/legacy_http_api.rs | 4 +- mudu_runtime/src/backend/http_api/mod.rs | 12 +- mudu_runtime/src/backend/iouring_admin.rs | 2 +- mudu_runtime/src/backend/mod.rs | 4 + mudu_runtime/src/backend/mudu_app_mgr.rs | 20 +- mudu_runtime/src/backend/mudu_conn_async.rs | 94 + mudu_runtime/src/backend/mudu_conn_core.rs | 201 ++ .../src/backend/mudu_prepared_stmt.rs | 40 + .../src/backend/mudu_result_set_async.rs | 47 + mudu_runtime/src/backend/mududb_cfg.rs | 26 +- mudu_runtime/src/backend/server_ur/server.rs | 9 +- .../src/backend/server_ur/test_mpk.rs | 27 +- mudu_runtime/src/backend/test_backend.rs | 8 +- mudu_runtime/src/backend/web_serve.rs | 14 +- .../libsql_async_conn_inner.rs | 15 +- .../src/db_postgres/pg_interactive_conn.rs | 2 +- mudu_runtime/src/db_turso/turso_conn_inner.rs | 7 +- mudu_runtime/src/interface/kernel.rs | 172 +- mudu_runtime/src/procedure/mod.rs | 1 - mudu_runtime/src/procedure/wasi_context.rs | 78 - mudu_runtime/src/service/app_inst.rs | 2 +- mudu_runtime/src/service/app_inst_impl.rs | 64 +- .../src/service/kernel_function_p1.rs | 392 ---- .../src/service/kernel_function_p2.rs | 7 +- .../src/service/kernel_function_p2_async.rs | 9 +- mudu_runtime/src/service/mod.rs | 3 - mudu_runtime/src/service/mudu_package.rs | 9 +- .../src/service/procedure_invoke_component.rs | 11 +- .../src/service/procedure_invoke_p1.rs | 300 --- mudu_runtime/src/service/runtime_opt.rs | 46 +- mudu_runtime/src/service/runtime_simple.rs | 8 +- .../src/service/test_runtime_simple.rs | 9 +- .../src/service/wasi_context_component.rs | 19 +- mudu_runtime/src/service/wt_instance_pre.rs | 29 +- mudu_runtime/src/service/wt_runtime.rs | 40 +- .../src/service/wt_runtime_component.rs | 9 +- mudu_runtime/src/service/wt_runtime_p1.rs | 340 --- mudu_runtime/wit/api.wit | 2 + mudu_runtime/wit/async-api.wit | 2 + mudu_sys/Cargo.toml | 16 + mudu_sys/src/api/env.rs | 15 + mudu_sys/src/api/fs.rs | 17 + mudu_sys/src/api/mod.rs | 7 + mudu_sys/src/api/net.rs | 9 + mudu_sys/src/api/random.rs | 14 + mudu_sys/src/api/sync.rs | 10 + mudu_sys/src/api/task.rs | 9 + mudu_sys/src/api/time.rs | 21 + mudu_sys/src/env.rs | 27 + mudu_sys/src/fd.rs | 5 + mudu_sys/src/fs.rs | 44 + mudu_sys/src/lib.rs | 18 + mudu_sys/src/linux/env.rs | 61 + mudu_sys/src/linux/fs.rs | 114 + mudu_sys/src/linux/mod.rs | 7 + mudu_sys/src/linux/net.rs | 96 + mudu_sys/src/linux/random.rs | 10 + mudu_sys/src/linux/sync.rs | 107 + mudu_sys/src/linux/task.rs | 18 + mudu_sys/src/linux/time.rs | 19 + mudu_sys/src/net.rs | 102 + mudu_sys/src/sync.rs | 19 + mudu_sys/src/task.rs | 42 + mudu_sys/src/uring.rs | 248 +++ mudu_transpiler/Cargo.toml | 1 - mudu_type/src/dat_value.rs | 2 +- mudu_wasm/Cargo.toml | 2 +- mudu_wasm/readme.md | 5 +- mudud/src/main.rs | 5 +- script/build/build.py | 4 +- sys_interface/Cargo.toml | 1 - sys_interface/src/api_impl/async_.rs | 9 +- sys_interface/src/api_impl/mod.rs | 6 +- sys_interface/src/api_impl/sync.rs | 119 +- sys_interface/src/api_impl/sync_wasm.rs | 53 - sys_interface/src/extern_c.rs | 126 -- sys_interface/src/inner_component_async.rs | 11 +- sys_interface/src/inner_p1.rs | 356 ---- sys_interface/src/lib.rs | 12 - 319 files changed, 25825 insertions(+), 18748 deletions(-) delete mode 100644 mudu_kernel/cfg/test.toml create mode 100644 mudu_kernel/src/command/delete_key_value.rs create mode 100644 mudu_kernel/src/command/update_key_value.rs delete mode 100644 mudu_kernel/src/common/mudu_cfg.rs delete mode 100644 mudu_kernel/src/contract/x_log.rs create mode 100644 mudu_kernel/src/index/btree/btree_index.rs create mode 100644 mudu_kernel/src/index/btree/mod.rs create mode 100644 mudu_kernel/src/index/index_key/compare_context.rs create mode 100644 mudu_kernel/src/index/index_key/key_tuple.rs create mode 100644 mudu_kernel/src/index/index_key/mod.rs create mode 100644 mudu_kernel/src/index/ordered_index.rs create mode 100644 mudu_kernel/src/io/socket.rs create mode 100644 mudu_kernel/src/io/user_io.rs create mode 100644 mudu_kernel/src/io/worker_ring.rs create mode 100644 mudu_kernel/src/io/worker_ring_stub.rs delete mode 100644 mudu_kernel/src/server/accept_handle_task.rs rename mudu_kernel/src/{server_ur/procedure_runtime.rs => server/async_func_runtime.rs} (52%) create mode 100644 mudu_kernel/src/server/async_func_task.rs rename mudu_kernel/src/{server_ur/procedure_task_waker.rs => server/async_func_task_waker.rs} (63%) create mode 100644 mudu_kernel/src/server/callback_registry.rs create mode 100644 mudu_kernel/src/server/connection_worker_task.rs create mode 100644 mudu_kernel/src/server/frame_dispatch.rs rename mudu_kernel/src/{server_ur => server}/fsm.rs (100%) create mode 100644 mudu_kernel/src/server/handlers/get.rs create mode 100644 mudu_kernel/src/server/handlers/mod.rs create mode 100644 mudu_kernel/src/server/handlers/procedure_invoke.rs create mode 100644 mudu_kernel/src/server/handlers/put.rs create mode 100644 mudu_kernel/src/server/handlers/range_scan.rs create mode 100644 mudu_kernel/src/server/handlers/session_close.rs create mode 100644 mudu_kernel/src/server/handlers/session_create.rs delete mode 100644 mudu_kernel/src/server/incoming_session.rs create mode 100644 mudu_kernel/src/server/inflight_op.rs create mode 100644 mudu_kernel/src/server/loop_mailbox.rs create mode 100644 mudu_kernel/src/server/loop_user_io.rs create mode 100644 mudu_kernel/src/server/message_dispatcher.rs delete mode 100644 mudu_kernel/src/server/mudu_server.rs delete mode 100644 mudu_kernel/src/server/parser.rs create mode 100644 mudu_kernel/src/server/perf_test.rs create mode 100644 mudu_kernel/src/server/protocol_codec.rs create mode 100644 mudu_kernel/src/server/request_ctx.rs create mode 100644 mudu_kernel/src/server/request_response_worker.rs rename mudu_kernel/src/{server_ur => server}/routing.rs (98%) rename mudu_kernel/src/{server_ur => server}/server.rs (66%) create mode 100644 mudu_kernel/src/server/server_iouring.rs delete mode 100644 mudu_kernel/src/server/session.rs create mode 100644 mudu_kernel/src/server/session_bound_worker_runtime.rs delete mode 100644 mudu_kernel/src/server/session_handle_task.rs delete mode 100644 mudu_kernel/src/server/session_mgr.rs create mode 100644 mudu_kernel/src/server/task.rs create mode 100644 mudu_kernel/src/server/task_registry.rs delete mode 100644 mudu_kernel/src/server/test_pg_cli.rs delete mode 100644 mudu_kernel/src/server/test_sql.rs rename mudu_kernel/src/{server_ur => server}/transferred_connection.rs (58%) create mode 100644 mudu_kernel/src/server/worker.rs create mode 100644 mudu_kernel/src/server/worker_local.rs create mode 100644 mudu_kernel/src/server/worker_loop_stats.rs create mode 100644 mudu_kernel/src/server/worker_mailbox.rs rename mudu_kernel/src/{server_ur => server}/worker_registry.rs (100%) create mode 100644 mudu_kernel/src/server/worker_ring_loop.rs create mode 100644 mudu_kernel/src/server/worker_ring_loop/recovery.rs create mode 100644 mudu_kernel/src/server/worker_ring_loop/runtime.rs create mode 100644 mudu_kernel/src/server/worker_session_manager.rs create mode 100644 mudu_kernel/src/server/worker_snapshot.rs create mode 100644 mudu_kernel/src/server/worker_storage.rs create mode 100644 mudu_kernel/src/server/worker_task.rs create mode 100644 mudu_kernel/src/server/worker_tx_manager.rs create mode 100644 mudu_kernel/src/server/x_contract.rs create mode 100644 mudu_kernel/src/server/x_lock_mgr.rs delete mode 100644 mudu_kernel/src/server_ur/inflight_op.rs delete mode 100644 mudu_kernel/src/server_ur/mod.rs delete mode 100644 mudu_kernel/src/server_ur/pending_procedure_invocation.rs delete mode 100644 mudu_kernel/src/server_ur/perf_test.rs delete mode 100644 mudu_kernel/src/server_ur/procedure_task.rs delete mode 100644 mudu_kernel/src/server_ur/server_iouring.rs delete mode 100644 mudu_kernel/src/server_ur/worker.rs delete mode 100644 mudu_kernel/src/server_ur/worker_connection.rs delete mode 100644 mudu_kernel/src/server_ur/worker_local.rs delete mode 100644 mudu_kernel/src/server_ur/worker_local_log.rs delete mode 100644 mudu_kernel/src/server_ur/worker_mailbox.rs delete mode 100644 mudu_kernel/src/server_ur/worker_ring_loop.rs create mode 100644 mudu_kernel/src/sql/binder.rs create mode 100644 mudu_kernel/src/sql/bound_stmt.rs create mode 100644 mudu_kernel/src/sql/copy_layout.rs create mode 100644 mudu_kernel/src/sql/describer.rs create mode 100644 mudu_kernel/src/sql/plan_ctx.rs create mode 100644 mudu_kernel/src/sql/planner.rs create mode 100644 mudu_kernel/src/sql/value_codec.rs delete mode 100644 mudu_kernel/src/storage/bufer_manager.rs delete mode 100644 mudu_kernel/src/storage/constant.rs delete mode 100644 mudu_kernel/src/storage/data_file.rs delete mode 100644 mudu_kernel/src/storage/data_header.rs delete mode 100644 mudu_kernel/src/storage/disk_io.rs delete mode 100644 mudu_kernel/src/storage/extent.rs delete mode 100644 mudu_kernel/src/storage/frame.rs delete mode 100644 mudu_kernel/src/storage/frame_ctrl.rs delete mode 100644 mudu_kernel/src/storage/log_entry.rs create mode 100644 mudu_kernel/src/storage/page/mod.rs create mode 100644 mudu_kernel/src/storage/page/page_block_ref.rs create mode 100644 mudu_kernel/src/storage/page/page_block_ref_mut.rs create mode 100644 mudu_kernel/src/storage/page/page_header.rs create mode 100644 mudu_kernel/src/storage/page/page_id.rs create mode 100644 mudu_kernel/src/storage/page/page_tailer.rs create mode 100644 mudu_kernel/src/storage/page/record_slot.rs create mode 100644 mudu_kernel/src/storage/page/record_slot_ref.rs delete mode 100644 mudu_kernel/src/storage/page_block.rs delete mode 100644 mudu_kernel/src/storage/page_id.rs delete mode 100644 mudu_kernel/src/storage/page_index.rs create mode 100644 mudu_kernel/src/storage/relation/mod.rs create mode 100644 mudu_kernel/src/storage/relation/relation.rs delete mode 100644 mudu_kernel/src/storage/space_manager.rs delete mode 100644 mudu_kernel/src/storage/storage_cfg.rs delete mode 100644 mudu_kernel/src/storage/storage_context.rs delete mode 100644 mudu_kernel/src/storage/table_space.rs create mode 100644 mudu_kernel/src/storage/time_series/mod.rs create mode 100644 mudu_kernel/src/storage/time_series/time_series_file.rs delete mode 100644 mudu_kernel/src/storage/worker_kv_store.rs create mode 100644 mudu_kernel/src/wal/log_frame.rs create mode 100644 mudu_kernel/src/wal/lsn.rs create mode 100644 mudu_kernel/src/wal/mod.rs create mode 100644 mudu_kernel/src/wal/pl_batch.rs create mode 100644 mudu_kernel/src/wal/pl_batch_worker_log.rs create mode 100644 mudu_kernel/src/wal/pl_entry.rs rename mudu_kernel/src/{x_log => wal}/test_xl_batch.rs (100%) create mode 100644 mudu_kernel/src/wal/typed_worker_log.rs create mode 100644 mudu_kernel/src/wal/worker_log.rs create mode 100644 mudu_kernel/src/wal/worker_wal_backend.rs create mode 100644 mudu_kernel/src/wal/xid.rs create mode 100644 mudu_kernel/src/wal/xl_batch.rs create mode 100644 mudu_kernel/src/wal/xl_batch_worker_log.rs create mode 100644 mudu_kernel/src/wal/xl_c_abort.rs create mode 100644 mudu_kernel/src/wal/xl_data_op.rs create mode 100644 mudu_kernel/src/wal/xl_entry.rs delete mode 100644 mudu_kernel/src/x_log/fsync_task.rs delete mode 100644 mudu_kernel/src/x_log/iou.rs delete mode 100644 mudu_kernel/src/x_log/lsn_allocator.rs delete mode 100644 mudu_kernel/src/x_log/lsn_syncer.rs delete mode 100644 mudu_kernel/src/x_log/mod.rs delete mode 100644 mudu_kernel/src/x_log/recovery_task.rs delete mode 100644 mudu_kernel/src/x_log/test_x_log.rs delete mode 100644 mudu_kernel/src/x_log/worker_kv_log.rs delete mode 100644 mudu_kernel/src/x_log/x_log_file.rs delete mode 100644 mudu_kernel/src/x_log/x_log_file_iou.rs delete mode 100644 mudu_kernel/src/x_log/x_log_impl.rs delete mode 100644 mudu_kernel/src/x_log/x_log_service.rs delete mode 100644 mudu_kernel/src/x_log/xid.rs delete mode 100644 mudu_kernel/src/x_log/xl_c_abort.rs delete mode 100644 mudu_kernel/src/x_log/xl_c_begin.rs delete mode 100644 mudu_kernel/src/x_log/xl_c_commit.rs delete mode 100644 mudu_kernel/src/x_log/xl_cfg.rs delete mode 100644 mudu_kernel/src/x_log/xl_file_info.rs delete mode 100644 mudu_kernel/src/x_log/xl_path.rs create mode 100644 mudu_runtime/src/backend/mudu_conn_async.rs create mode 100644 mudu_runtime/src/backend/mudu_conn_core.rs create mode 100644 mudu_runtime/src/backend/mudu_prepared_stmt.rs create mode 100644 mudu_runtime/src/backend/mudu_result_set_async.rs delete mode 100644 mudu_runtime/src/procedure/wasi_context.rs delete mode 100644 mudu_runtime/src/service/kernel_function_p1.rs delete mode 100644 mudu_runtime/src/service/procedure_invoke_p1.rs delete mode 100644 mudu_runtime/src/service/wt_runtime_p1.rs create mode 100644 mudu_sys/Cargo.toml create mode 100644 mudu_sys/src/api/env.rs create mode 100644 mudu_sys/src/api/fs.rs create mode 100644 mudu_sys/src/api/mod.rs create mode 100644 mudu_sys/src/api/net.rs create mode 100644 mudu_sys/src/api/random.rs create mode 100644 mudu_sys/src/api/sync.rs create mode 100644 mudu_sys/src/api/task.rs create mode 100644 mudu_sys/src/api/time.rs create mode 100644 mudu_sys/src/env.rs create mode 100644 mudu_sys/src/fd.rs create mode 100644 mudu_sys/src/fs.rs create mode 100644 mudu_sys/src/lib.rs create mode 100644 mudu_sys/src/linux/env.rs create mode 100644 mudu_sys/src/linux/fs.rs create mode 100644 mudu_sys/src/linux/mod.rs create mode 100644 mudu_sys/src/linux/net.rs create mode 100644 mudu_sys/src/linux/random.rs create mode 100644 mudu_sys/src/linux/sync.rs create mode 100644 mudu_sys/src/linux/task.rs create mode 100644 mudu_sys/src/linux/time.rs create mode 100644 mudu_sys/src/net.rs create mode 100644 mudu_sys/src/sync.rs create mode 100644 mudu_sys/src/task.rs create mode 100644 mudu_sys/src/uring.rs delete mode 100644 sys_interface/src/extern_c.rs delete mode 100644 sys_interface/src/inner_p1.rs diff --git a/Cargo.lock b/Cargo.lock index a6f17e4..602efe4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -424,12 +424,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -[[package]] -name = "as-any" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f477b951e452a0b6b4a10b53ccd569042d1d01729b519e02074a9c0958a063" - [[package]] name = "as-slice" version = "0.2.1" @@ -3734,12 +3728,6 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" -[[package]] -name = "mcslock" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897fcda81d16fb4a190eae8b95ef041cf707262f15a49ab7b5a9455e2bc763cd" - [[package]] name = "md-5" version = "0.10.6" @@ -3905,11 +3893,8 @@ dependencies = [ "mysql", "mysql_async", "postgres", - "rmp-serde", - "rmpv", "rusqlite", "scc", - "serde", "serde_json", "tokio", "tokio-postgres", @@ -3964,6 +3949,7 @@ dependencies = [ "mudu", "mudu_type", "paste", + "rmp-serde", "scc", "serde", "serde_json", @@ -3982,12 +3968,9 @@ dependencies = [ "askama 0.15.0", "base16ct", "clap", - "lazy_static", "md-5 0.11.0-rc.3", "mudu", "mudu_binding", - "mudu_contract", - "mudu_type", "paste", "rust-format", "serde", @@ -4002,29 +3985,22 @@ name = "mudu_kernel" version = "0.0.1" dependencies = [ "arbitrary", - "async-backtrace", "async-std", "async-trait", - "chrono", + "byteorder", "crossbeam-queue", "csv-async", "futures", - "home", "lazy_static", "libc", - "log", - "mcslock", "mudu", "mudu_contract", + "mudu_sys", "mudu_type", "mudu_utils", "pgwire", - "postgres", "project-root", - "rand 0.9.2", - "regex", - "rliburing", - "roaring", + "rmp-serde", "scc", "serde", "serde_json", @@ -4033,8 +4009,6 @@ dependencies = [ "sql_parser", "test_utils", "tokio", - "tokio-condvar", - "toml 1.1.0+spec-1.1.0", "tracing", "uuid", ] @@ -4085,7 +4059,6 @@ dependencies = [ "actix-cors", "actix-web", "anyhow", - "as-any", "as-slice", "async-trait", "base64 0.22.1", @@ -4098,11 +4071,11 @@ dependencies = [ "mudu_contract", "mudu_gen", "mudu_kernel", + "mudu_sys", "mudu_type", "mudu_utils", "pgwire", "postgres", - "reqwest", "scc", "scopeguard", "serde", @@ -4121,6 +4094,20 @@ dependencies = [ "zip", ] +[[package]] +name = "mudu_sys" +version = "0.1.0" +dependencies = [ + "async-trait", + "chrono", + "libc", + "mudu", + "rliburing", + "socket2 0.5.10", + "tokio", + "uuid", +] + [[package]] name = "mudu_transpiler" version = "0.1.0" @@ -4134,7 +4121,6 @@ dependencies = [ "mudu_contract", "mudu_type", "mudu_utils", - "sql_parser", "tree-sitter", "tree-sitter-rust", ] @@ -6407,15 +6393,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "tokio-condvar" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8530e402d24f6a65019baa57593f1769557c670302f493cdf8fa3dfbe4d85ac" -dependencies = [ - "tokio", -] - [[package]] name = "tokio-io-timeout" version = "1.2.1" @@ -7271,6 +7248,7 @@ dependencies = [ "mudu", "mudu_binding", "mudu_contract", + "mudu_sys", "mudu_type", "sys_interface", "uuid", @@ -7295,6 +7273,7 @@ dependencies = [ "mudu", "mudu_binding", "mudu_contract", + "mudu_sys", "mudu_type", "sys_interface", "uuid", @@ -8637,6 +8616,7 @@ dependencies = [ "mudu_binding", "mudu_cli", "mudu_contract", + "mudu_sys", "mudu_type", "mudu_utils", "sys_interface", diff --git a/Cargo.toml b/Cargo.toml index 7619a50..120c0a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "mudu", + "mudu_sys", "mudu_adapter", "mudu_type", "mudu_contract", @@ -36,6 +37,7 @@ test_utils = { path = "test_utils" } sys_interface = { path = "sys_interface" } mudu_adapter = { path = "mudu_adapter" } mudu = { path = "mudu" } +mudu_sys = { path = "mudu_sys" } mudu_binding = { path = "mudu_binding" } md-5 = { version = "0.11.0-rc.0" } sql_parser = { path = "sql_parser" } diff --git a/doc/cfg/mududb_cfg.toml b/doc/cfg/mududb_cfg.toml index ec1d1ee..40dc67c 100644 --- a/doc/cfg/mududb_cfg.toml +++ b/doc/cfg/mududb_cfg.toml @@ -14,7 +14,6 @@ http_worker_threads = 1 pg_listen_port = 5432 # Enable the WASI component runtime used by the new backend. -enable_p2 = true enable_async = true # 0 = Legacy diff --git a/example/game-backend/src/generated/game_object.rs b/example/game-backend/src/generated/game_object.rs index 81b8d2f..4bf1f11 100644 --- a/example/game-backend/src/generated/game_object.rs +++ b/example/game-backend/src/generated/game_object.rs @@ -1,4 +1,4 @@ #[derive(Clone)] pub struct GameObject { - pub id : u64, -} \ No newline at end of file + pub id: u64, +} diff --git a/example/game-backend/src/generated/instance.rs b/example/game-backend/src/generated/instance.rs index 5a1f6f4..664cb2a 100644 --- a/example/game-backend/src/generated/instance.rs +++ b/example/game-backend/src/generated/instance.rs @@ -1,27 +1,21 @@ +use crate::generated::game_object::GameObject; use std::cell::RefCell; use std::collections::HashMap; -use crate::generated::game_object::GameObject; thread_local! { static MAP:RefCell> = RefCell::new(HashMap::new()); } - -struct Instance { - -} - +struct Instance {} impl Instance { - pub fn put(id:u64, object:GameObject) { + pub fn put(id: u64, object: GameObject) { MAP.with(|m| { m.borrow_mut().insert(id, object); }) } - pub fn get(id:u64) -> Option { - MAP.with(|m| { - m.borrow().get(&id).cloned() - }) + pub fn get(id: u64) -> Option { + MAP.with(|m| m.borrow().get(&id).cloned()) } -} \ No newline at end of file +} diff --git a/example/game-backend/src/generated/mod.rs b/example/game-backend/src/generated/mod.rs index c5b6e0c..0e8fefe 100644 --- a/example/game-backend/src/generated/mod.rs +++ b/example/game-backend/src/generated/mod.rs @@ -1,3 +1,3 @@ -pub mod procedure; mod game_object; -mod instance; \ No newline at end of file +mod instance; +pub mod procedure; diff --git a/example/game-backend/src/generated/procedure.rs b/example/game-backend/src/generated/procedure.rs index a3215f5..dc5151c 100644 --- a/example/game-backend/src/generated/procedure.rs +++ b/example/game-backend/src/generated/procedure.rs @@ -2,93 +2,68 @@ use mudu::common::id::OID; use mudu::common::result::RS; /**mudu-proc**/ -pub fn command(oid:OID, message: Vec) -> RS> { +pub fn command(oid: OID, message: Vec) -> RS> { Ok(message) } /**mudu-proc**/ -pub fn event(oid:OID) -> RS> { +pub fn event(oid: OID) -> RS> { Ok(Vec::new()) } - fn mp2_event(param:Vec) -> Vec { - ::mudu_binding::procedure::procedure_invoke::invoke_procedure( - param, - mudu_inner_p2_event, - ) +fn mp2_event(param: Vec) -> Vec { + ::mudu_binding::procedure::procedure_invoke::invoke_procedure(param, mudu_inner_p2_event) } -pub fn mudu_inner_p2_event( +pub fn mudu_inner_p2_event( param: ::mudu_contract::procedure::procedure_param::ProcedureParam, -) -> ::mudu::common::result::RS< - ::mudu_contract::procedure::procedure_result::ProcedureResult, -> { +) -> ::mudu::common::result::RS<::mudu_contract::procedure::procedure_result::ProcedureResult> { let return_desc = mudu_result_desc_event().clone(); - let res = event( - param.session_id(), - - ); + let res = event(param.session_id()); match res { Ok(tuple) => { - let return_list = { - - vec![ - - ::mudu_type::dat_value::DatValue::from_binary(tuple) - - ] - - }; + let return_list = { vec![::mudu_type::dat_value::DatValue::from_binary(tuple)] }; Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) } Err(e) => Err(e), } } -pub fn mudu_argv_desc_event() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static ARGV_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - ARGV_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ]) - } - ) +pub fn mudu_argv_desc_event() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static ARGV_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + ARGV_DESC.get_or_init(|| ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![])) } -pub fn mudu_result_desc_event() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static RESULT_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - RESULT_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "0".to_string(), - - ::mudu_type::dat_type::DatType::new_no_param(::mudu_type::dat_type_id::DatTypeID::Binary) - +pub fn mudu_result_desc_event() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc +{ + static RESULT_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + RESULT_DESC.get_or_init(|| { + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "0".to_string(), + ::mudu_type::dat_type::DatType::new_no_param( + ::mudu_type::dat_type_id::DatTypeID::Binary, ), - - ]) - } - ) + ), + ]) + }) } -pub fn mudu_proc_desc_event() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { - static _PROC_DESC: std::sync::OnceLock< - ::mudu_contract::procedure::proc_desc::ProcDesc, - > = std::sync::OnceLock::new(); - _PROC_DESC - .get_or_init(|| { - ::mudu_contract::procedure::proc_desc::ProcDesc::new( - "game_backend".to_string(), - "event".to_string(), - mudu_argv_desc_event().clone(), - mudu_result_desc_event().clone(), - false - ) - }) +pub fn mudu_proc_desc_event() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { + static _PROC_DESC: std::sync::OnceLock<::mudu_contract::procedure::proc_desc::ProcDesc> = + std::sync::OnceLock::new(); + _PROC_DESC.get_or_init(|| { + ::mudu_contract::procedure::proc_desc::ProcDesc::new( + "game_backend".to_string(), + "event".to_string(), + mudu_argv_desc_event().clone(), + mudu_result_desc_event().clone(), + false, + ) + }) } mod mod_event { @@ -99,7 +74,7 @@ mod mod_event { export mp2-event: func(param:list) -> list; } "##, - + }); #[allow(non_camel_case_types)] @@ -107,103 +82,80 @@ mod mod_event { struct GuestEvent {} impl Guest for GuestEvent { - fn mp2_event(param:Vec) -> Vec { + fn mp2_event(param: Vec) -> Vec { super::mp2_event(param) } } export!(GuestEvent); } - fn mp2_command(param:Vec) -> Vec { - ::mudu_binding::procedure::procedure_invoke::invoke_procedure( - param, - mudu_inner_p2_command, - ) +fn mp2_command(param: Vec) -> Vec { + ::mudu_binding::procedure::procedure_invoke::invoke_procedure(param, mudu_inner_p2_command) } -pub fn mudu_inner_p2_command( +pub fn mudu_inner_p2_command( param: ::mudu_contract::procedure::procedure_param::ProcedureParam, -) -> ::mudu::common::result::RS< - ::mudu_contract::procedure::procedure_result::ProcedureResult, -> { +) -> ::mudu::common::result::RS<::mudu_contract::procedure::procedure_result::ProcedureResult> { let return_desc = mudu_result_desc_command().clone(); let res = command( param.session_id(), - - - param.param_list()[0].expect_binary().clone(), - - + param.param_list()[0].expect_binary().clone(), ); match res { Ok(tuple) => { - let return_list = { - - vec![ - - ::mudu_type::dat_value::DatValue::from_binary(tuple) - - ] - - }; + let return_list = { vec![::mudu_type::dat_value::DatValue::from_binary(tuple)] }; Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) } Err(e) => Err(e), } } -pub fn mudu_argv_desc_command() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static ARGV_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - ARGV_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "message".to_string(), - - ::mudu_type::dat_type::DatType::new_no_param(::mudu_type::dat_type_id::DatTypeID::Binary) - +pub fn mudu_argv_desc_command() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc +{ + static ARGV_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + ARGV_DESC.get_or_init(|| { + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "message".to_string(), + ::mudu_type::dat_type::DatType::new_no_param( + ::mudu_type::dat_type_id::DatTypeID::Binary, ), - - ]) - } - ) + ), + ]) + }) } -pub fn mudu_result_desc_command() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static RESULT_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - RESULT_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "0".to_string(), - - ::mudu_type::dat_type::DatType::new_no_param(::mudu_type::dat_type_id::DatTypeID::Binary) - +pub fn mudu_result_desc_command() +-> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static RESULT_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + RESULT_DESC.get_or_init(|| { + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "0".to_string(), + ::mudu_type::dat_type::DatType::new_no_param( + ::mudu_type::dat_type_id::DatTypeID::Binary, ), - - ]) - } - ) + ), + ]) + }) } -pub fn mudu_proc_desc_command() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { - static _PROC_DESC: std::sync::OnceLock< - ::mudu_contract::procedure::proc_desc::ProcDesc, - > = std::sync::OnceLock::new(); - _PROC_DESC - .get_or_init(|| { - ::mudu_contract::procedure::proc_desc::ProcDesc::new( - "game_backend".to_string(), - "command".to_string(), - mudu_argv_desc_command().clone(), - mudu_result_desc_command().clone(), - false - ) - }) +pub fn mudu_proc_desc_command() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { + static _PROC_DESC: std::sync::OnceLock<::mudu_contract::procedure::proc_desc::ProcDesc> = + std::sync::OnceLock::new(); + _PROC_DESC.get_or_init(|| { + ::mudu_contract::procedure::proc_desc::ProcDesc::new( + "game_backend".to_string(), + "command".to_string(), + mudu_argv_desc_command().clone(), + mudu_result_desc_command().clone(), + false, + ) + }) } mod mod_command { @@ -214,7 +166,7 @@ mod mod_command { export mp2-command: func(param:list) -> list; } "##, - + }); #[allow(non_camel_case_types)] @@ -222,10 +174,10 @@ mod mod_command { struct GuestCommand {} impl Guest for GuestCommand { - fn mp2_command(param:Vec) -> Vec { + fn mp2_command(param: Vec) -> Vec { super::mp2_command(param) } } export!(GuestCommand); -} \ No newline at end of file +} diff --git a/example/game-backend/src/rust/game_object.rs b/example/game-backend/src/rust/game_object.rs index 81b8d2f..4bf1f11 100644 --- a/example/game-backend/src/rust/game_object.rs +++ b/example/game-backend/src/rust/game_object.rs @@ -1,4 +1,4 @@ #[derive(Clone)] pub struct GameObject { - pub id : u64, -} \ No newline at end of file + pub id: u64, +} diff --git a/example/game-backend/src/rust/instance.rs b/example/game-backend/src/rust/instance.rs index 799b2ae..9ca0e1a 100644 --- a/example/game-backend/src/rust/instance.rs +++ b/example/game-backend/src/rust/instance.rs @@ -1,27 +1,21 @@ +use crate::rust::game_object::GameObject; use std::cell::RefCell; use std::collections::HashMap; -use crate::rust::game_object::GameObject; thread_local! { static MAP:RefCell> = RefCell::new(HashMap::new()); } - -struct Instance { - -} - +struct Instance {} impl Instance { - pub fn put(id:u64, object:GameObject) { + pub fn put(id: u64, object: GameObject) { MAP.with(|m| { m.borrow_mut().insert(id, object); }) } - pub fn get(id:u64) -> Option { - MAP.with(|m| { - m.borrow().get(&id).cloned() - }) + pub fn get(id: u64) -> Option { + MAP.with(|m| m.borrow().get(&id).cloned()) } -} \ No newline at end of file +} diff --git a/example/game-backend/src/rust/mod.rs b/example/game-backend/src/rust/mod.rs index e4e9006..0e8fefe 100644 --- a/example/game-backend/src/rust/mod.rs +++ b/example/game-backend/src/rust/mod.rs @@ -1,3 +1,3 @@ -pub mod procedure; mod game_object; mod instance; +pub mod procedure; diff --git a/example/game-backend/src/rust/procedure.rs b/example/game-backend/src/rust/procedure.rs index 0b46068..9df10b8 100644 --- a/example/game-backend/src/rust/procedure.rs +++ b/example/game-backend/src/rust/procedure.rs @@ -2,11 +2,11 @@ use mudu::common::id::OID; use mudu::common::result::RS; /**mudu-proc**/ -pub fn command(oid:OID, message: Vec) -> RS> { +pub fn command(oid: OID, message: Vec) -> RS> { Ok(message) } /**mudu-proc**/ -pub fn event(oid:OID) -> RS> { +pub fn event(oid: OID) -> RS> { Ok(Vec::new()) -} \ No newline at end of file +} diff --git a/example/vote/Cargo.toml b/example/vote/Cargo.toml index d6af0e1..7e5ad03 100644 --- a/example/vote/Cargo.toml +++ b/example/vote/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["cdylib"] [dependencies] mudu = { workspace = true } +mudu_sys = { workspace = true } mudu_type = { workspace = true } mudu_contract = { workspace = true } mudu_binding = { workspace = true} diff --git a/example/vote/src/generated/procedure.rs b/example/vote/src/generated/procedure.rs index b4eda6e..6bd3586 100644 --- a/example/vote/src/generated/procedure.rs +++ b/example/vote/src/generated/procedure.rs @@ -3,7 +3,6 @@ use crate::generated::vote_actions::object::VoteActions; use crate::generated::vote_history_item::object::VoteHistoryItem; use crate::generated::vote_result::object::VoteResult; use crate::generated::votes::object::Votes; -use chrono::Utc; use fallible_iterator::FallibleIterator; use mudu::common::result::RS; use mudu::common::xid::XID; @@ -12,12 +11,11 @@ use mudu::m_error; use mudu_contract::database::entity_set::RecordSet; use mudu_contract::{sql_params, sql_stmt}; use sys_interface::async_api::{mudu_command, mudu_query}; -use uuid::Uuid; // User management /**mudu-proc**/ pub async fn create_user(xid: XID, phone: String) -> RS { - let user_id = Uuid::new_v4().to_string(); + let user_id = mudu_sys::random::next_uuid_v4_string(); mudu_command( xid, sql_stmt!(&"INSERT INTO users (user_id, phone) VALUES (?, ?)"), @@ -39,7 +37,7 @@ pub async fn create_vote( visibility_rule: String, ) -> RS { // Validate input - if end_time <= Utc::now().timestamp() { + if end_time <= mudu_sys::time::utc_now().timestamp() { return Err(m_error!( MuduError, "End time must be in future".to_string() @@ -64,7 +62,7 @@ pub async fn create_vote( )); } - let vote_id = Uuid::new_v4().to_string(); + let vote_id = mudu_sys::random::next_uuid_v4_string(); mudu_command( xid, sql_stmt!( @@ -79,7 +77,7 @@ pub async fn create_vote( // Add option to vote /**mudu-proc**/ pub async fn add_option(xid: XID, vote_id: String, option_text: String) -> RS { - let option_id = Uuid::new_v4().to_string(); + let option_id = mudu_sys::random::next_uuid_v4_string(); mudu_command( xid, sql_stmt!(&"INSERT INTO options (option_id, vote_id, option_text) VALUES (?, ?, ?)"), @@ -107,7 +105,7 @@ pub async fn cast_vote( .next()? .ok_or_else(|| m_error!(MuduError, "Vote not found".to_string()))?; - if Utc::now().timestamp() > vote.get_end_time().unwrap() as i64 { + if mudu_sys::time::utc_now().timestamp() > vote.get_end_time().unwrap() as i64 { return Err(m_error!(MuduError, "Voting has ended".to_string())); } @@ -141,8 +139,8 @@ pub async fn cast_vote( } // Create vote action - let action_id = Uuid::new_v4().to_string(); - let action_time = Utc::now().timestamp(); + let action_id = mudu_sys::random::next_uuid_v4_string(); + let action_time = mudu_sys::time::utc_now().timestamp(); mudu_command( xid, sql_stmt!( @@ -155,7 +153,7 @@ pub async fn cast_vote( // Create vote choices for option_id in option_ids { - let choice_id = Uuid::new_v4().to_string(); + let choice_id = mudu_sys::random::next_uuid_v4_string(); mudu_command( xid, sql_stmt!( @@ -182,7 +180,7 @@ pub async fn withdraw_vote(xid: XID, user_id: String, vote_id: String) -> RS<()> .next()? .ok_or_else(|| m_error!(MuduError, "Vote not found".to_string()))?; - if Utc::now().timestamp() > vote.get_end_time().unwrap() as i64 { + if mudu_sys::time::utc_now().timestamp() > vote.get_end_time().unwrap() as i64 { return Err(m_error!( MuduError, "Voting has ended, cannot withdraw".to_string() @@ -226,7 +224,7 @@ pub async fn get_vote_result(xid: XID, vote_id: String) -> RS { .next()? .ok_or_else(|| m_error!(MuduError, "Vote not found".to_string()))?; - let now = Utc::now().timestamp(); + let now = mudu_sys::time::utc_now().timestamp(); let vote_ended = now > vote.get_end_time().unwrap() as i64; // Check visibility rules @@ -305,7 +303,8 @@ pub async fn get_voting_history(xid: XID, user_id: String) -> RS action.get_action_time().unwrap() as i64) as i32; + let vote_ended = (mudu_sys::time::utc_now().timestamp() + > action.get_action_time().unwrap() as i64) as i32; history.push(VoteHistoryItem::new( Some(action.get_vote_id().as_ref().unwrap().to_string()), Some("topic todo".to_string()), @@ -338,8 +337,8 @@ pub async fn mudu_inner_p2_create_user( Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::from(tuple, &return_desc)?) } -pub fn mudu_argv_desc_create_user() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_argv_desc_create_user( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static ARGV_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -354,8 +353,8 @@ pub fn mudu_argv_desc_create_user() }) } -pub fn mudu_result_desc_create_user() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_result_desc_create_user( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static RESULT_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -427,8 +426,8 @@ pub async fn mudu_inner_p2_cast_vote( Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::from(tuple, &return_desc)?) } -pub fn mudu_argv_desc_cast_vote() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_argv_desc_cast_vote( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static ARGV_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -465,8 +464,8 @@ pub fn mudu_argv_desc_cast_vote() }) } -pub fn mudu_result_desc_cast_vote() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_result_desc_cast_vote( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static RESULT_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -533,8 +532,8 @@ pub async fn mudu_inner_p2_get_vote_result( Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::from(tuple, &return_desc)?) } -pub fn mudu_argv_desc_get_vote_result() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_argv_desc_get_vote_result( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static ARGV_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -549,8 +548,8 @@ pub fn mudu_argv_desc_get_vote_result() }) } -pub fn mudu_result_desc_get_vote_result() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_result_desc_get_vote_result( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static RESULT_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -618,8 +617,8 @@ pub async fn mudu_inner_p2_get_voting_history( Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::from(tuple, &return_desc)?) } -pub fn mudu_argv_desc_get_voting_history() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_argv_desc_get_voting_history( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static ARGV_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -634,8 +633,8 @@ pub fn mudu_argv_desc_get_voting_history() }) } -pub fn mudu_result_desc_get_voting_history() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_result_desc_get_voting_history( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static RESULT_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -650,8 +649,8 @@ pub fn mudu_result_desc_get_voting_history() }) } -pub fn mudu_proc_desc_get_voting_history() --> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { +pub fn mudu_proc_desc_get_voting_history( +) -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { static _PROC_DESC: std::sync::OnceLock<::mudu_contract::procedure::proc_desc::ProcDesc> = std::sync::OnceLock::new(); _PROC_DESC.get_or_init(|| { @@ -710,8 +709,8 @@ pub async fn mudu_inner_p2_add_option( Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::from(tuple, &return_desc)?) } -pub fn mudu_argv_desc_add_option() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_argv_desc_add_option( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static ARGV_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -727,8 +726,8 @@ pub fn mudu_argv_desc_add_option() }) } -pub fn mudu_result_desc_add_option() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_result_desc_add_option( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static RESULT_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -800,8 +799,8 @@ pub async fn mudu_inner_p2_create_vote( Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::from(tuple, &return_desc)?) } -pub fn mudu_argv_desc_create_vote() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_argv_desc_create_vote( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static ARGV_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -850,8 +849,8 @@ pub fn mudu_argv_desc_create_vote() }) } -pub fn mudu_result_desc_create_vote() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_result_desc_create_vote( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static RESULT_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -919,8 +918,8 @@ pub async fn mudu_inner_p2_withdraw_vote( Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::from(tuple, &return_desc)?) } -pub fn mudu_argv_desc_withdraw_vote() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_argv_desc_withdraw_vote( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static ARGV_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); @@ -935,8 +934,8 @@ pub fn mudu_argv_desc_withdraw_vote() }) } -pub fn mudu_result_desc_withdraw_vote() --> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { +pub fn mudu_result_desc_withdraw_vote( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { static RESULT_DESC: std::sync::OnceLock< ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, > = std::sync::OnceLock::new(); diff --git a/example/vote/src/rust/procedure.rs b/example/vote/src/rust/procedure.rs index e832f12..3c14378 100644 --- a/example/vote/src/rust/procedure.rs +++ b/example/vote/src/rust/procedure.rs @@ -3,7 +3,6 @@ use crate::rust::vote_actions::object::VoteActions; use crate::rust::vote_history_item::object::VoteHistoryItem; use crate::rust::vote_result::object::VoteResult; use crate::rust::votes::object::Votes; -use chrono::Utc; use fallible_iterator::FallibleIterator; use mudu::common::result::RS; use mudu::common::xid::XID; @@ -12,12 +11,11 @@ use mudu::m_error; use mudu_contract::database::entity_set::RecordSet; use mudu_contract::{sql_params, sql_stmt}; use sys_interface::sync_api::{mudu_command, mudu_query}; -use uuid::Uuid; // User management /**mudu-proc**/ pub fn create_user(xid: XID, phone: String) -> RS { - let user_id = Uuid::new_v4().to_string(); + let user_id = mudu_sys::random::next_uuid_v4_string(); mudu_command( xid, sql_stmt!(&"INSERT INTO users (user_id, phone) VALUES (?, ?)"), @@ -38,7 +36,7 @@ pub fn create_vote( visibility_rule: String, ) -> RS { // Validate input - if end_time <= Utc::now().timestamp() { + if end_time <= mudu_sys::time::utc_now().timestamp() { return Err(m_error!( MuduError, "End time must be in future".to_string() @@ -63,7 +61,7 @@ pub fn create_vote( )); } - let vote_id = Uuid::new_v4().to_string(); + let vote_id = mudu_sys::random::next_uuid_v4_string(); mudu_command( xid, sql_stmt!( @@ -78,7 +76,7 @@ pub fn create_vote( // Add option to vote /**mudu-proc**/ pub fn add_option(xid: XID, vote_id: String, option_text: String) -> RS { - let option_id = Uuid::new_v4().to_string(); + let option_id = mudu_sys::random::next_uuid_v4_string(); mudu_command( xid, sql_stmt!(&"INSERT INTO options (option_id, vote_id, option_text) VALUES (?, ?, ?)"), @@ -99,7 +97,7 @@ pub fn cast_vote(xid: XID, user_id: String, vote_id: String, option_ids: Vec vote.get_end_time().unwrap() as i64 { + if mudu_sys::time::utc_now().timestamp() > vote.get_end_time().unwrap() as i64 { return Err(m_error!(MuduError, "Voting has ended".to_string())); } @@ -132,8 +130,8 @@ pub fn cast_vote(xid: XID, user_id: String, vote_id: String, option_ids: Vec RS<()> { .next()? .ok_or_else(|| m_error!(MuduError, "Vote not found".to_string()))?; - if Utc::now().timestamp() > vote.get_end_time().unwrap() as i64 { + if mudu_sys::time::utc_now().timestamp() > vote.get_end_time().unwrap() as i64 { return Err(m_error!( MuduError, "Voting has ended, cannot withdraw".to_string() @@ -211,7 +209,7 @@ pub fn get_vote_result(xid: XID, vote_id: String) -> RS { .next()? .ok_or_else(|| m_error!(MuduError, "Vote not found".to_string()))?; - let now = Utc::now().timestamp(); + let now = mudu_sys::time::utc_now().timestamp(); let vote_ended = now > vote.get_end_time().unwrap() as i64; // Check visibility rules @@ -286,7 +284,8 @@ pub fn get_voting_history(xid: XID, user_id: String) -> RS> let mut history = Vec::new(); for action in actions { - let vote_ended = (Utc::now().timestamp() > action.get_action_time().unwrap() as i64) as i32; + let vote_ended = (mudu_sys::time::utc_now().timestamp() + > action.get_action_time().unwrap() as i64) as i32; history.push(VoteHistoryItem::new( Some(action.get_vote_id().as_ref().unwrap().to_string()), Some("topic todo".to_string()), diff --git a/example/wallet/Cargo.toml b/example/wallet/Cargo.toml index e15ad14..31e899b 100644 --- a/example/wallet/Cargo.toml +++ b/example/wallet/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["cdylib"] [dependencies] mudu = { workspace = true } +mudu_sys = { workspace = true } mudu_type = { workspace = true } mudu_contract = { workspace = true } mudu_binding = { workspace = true } diff --git a/example/wallet/src/generated/mod.rs b/example/wallet/src/generated/mod.rs index 74c007e..dfb9a98 100644 --- a/example/wallet/src/generated/mod.rs +++ b/example/wallet/src/generated/mod.rs @@ -3,4 +3,4 @@ pub(crate) mod procedures; pub mod transactions; pub mod users; pub mod wallets; -pub mod warehouse; \ No newline at end of file +pub mod warehouse; diff --git a/example/wallet/src/generated/orders.rs b/example/wallet/src/generated/orders.rs index bc43169..26c49e9 100644 --- a/example/wallet/src/generated/orders.rs +++ b/example/wallet/src/generated/orders.rs @@ -1,599 +1,556 @@ pub mod object { -use lazy_static::lazy_static; -use mudu::common::result::RS; -use mudu_type::dat_binary::DatBinary; -use mudu_type::dat_textual::DatTextual; -use mudu_type::dat_type::DatType; -use mudu_type::dat_type_id::DatTypeID; -use mudu_type::dat_value::DatValue; -use mudu_type::datum::{Datum, DatumDyn}; -use mudu_contract::database::attr_field_access; -use mudu_contract::database::attr_value::AttrValue; -use mudu_contract::database::entity::Entity; -use mudu_contract::database::entity_utils; -use mudu_contract::tuple::datum_desc::DatumDesc; -use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; -use mudu_contract::tuple::tuple_datum::TupleDatumMarker; -use mudu_contract::database::sql_params::SQLParamMarker; - -// constant definition -const ORDERS:&str = "orders"; - -const ORDER_ID:&str = "order_id"; - -const USER_ID:&str = "user_id"; - -const MERCH_ID:&str = "merch_id"; - -const AMOUNT:&str = "amount"; - -const CREATED_AT:&str = "created_at"; - - -// entity struct definition -#[derive(Debug, Clone, Default)] -pub struct Orders { - - order_id: AttrOrderId, - - user_id: AttrUserId, - - merch_id: AttrMerchId, - - amount: AttrAmount, - - created_at: AttrCreatedAt, - -} + use lazy_static::lazy_static; + use mudu::common::result::RS; + use mudu_contract::database::attr_field_access; + use mudu_contract::database::attr_value::AttrValue; + use mudu_contract::database::entity::Entity; + use mudu_contract::database::entity_utils; + use mudu_contract::database::sql_params::SQLParamMarker; + use mudu_contract::tuple::datum_desc::DatumDesc; + use mudu_contract::tuple::tuple_datum::TupleDatumMarker; + use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; + use mudu_type::dat_binary::DatBinary; + use mudu_type::dat_textual::DatTextual; + use mudu_type::dat_type::DatType; + use mudu_type::dat_type_id::DatTypeID; + use mudu_type::dat_value::DatValue; + use mudu_type::datum::{Datum, DatumDyn}; -impl TupleDatumMarker for Orders {} - -impl SQLParamMarker for Orders {} - -impl Orders { - pub fn new( - order_id: Option, - user_id: Option, - merch_id: Option, - amount: Option, - created_at: Option, - - ) -> Self { - let s = Self { - - order_id : AttrOrderId::from(order_id), - - user_id : AttrUserId::from(user_id), - - merch_id : AttrMerchId::from(merch_id), - - amount : AttrAmount::from(amount), - - created_at : AttrCreatedAt::from(created_at), - - }; - s - } + // constant definition + const ORDERS: &str = "orders"; - pub fn new_empty() -> Self { - Self::default() - } + const ORDER_ID: &str = "order_id"; - - pub fn set_order_id( - &mut self, - order_id: i32, - ) { - self.order_id.update(order_id) - } + const USER_ID: &str = "user_id"; - pub fn get_order_id( - &self, - ) -> &Option { - self.order_id.get() - } - - pub fn set_user_id( - &mut self, - user_id: i32, - ) { - self.user_id.update(user_id) - } + const MERCH_ID: &str = "merch_id"; - pub fn get_user_id( - &self, - ) -> &Option { - self.user_id.get() - } - - pub fn set_merch_id( - &mut self, - merch_id: i32, - ) { - self.merch_id.update(merch_id) - } + const AMOUNT: &str = "amount"; - pub fn get_merch_id( - &self, - ) -> &Option { - self.merch_id.get() - } - - pub fn set_amount( - &mut self, - amount: i32, - ) { - self.amount.update(amount) - } + const CREATED_AT: &str = "created_at"; - pub fn get_amount( - &self, - ) -> &Option { - self.amount.get() - } - - pub fn set_created_at( - &mut self, - created_at: i32, - ) { - self.created_at.update(created_at) - } + // entity struct definition + #[derive(Debug, Clone, Default)] + pub struct Orders { + order_id: AttrOrderId, + + user_id: AttrUserId, - pub fn get_created_at( - &self, - ) -> &Option { - self.created_at.get() + merch_id: AttrMerchId, + + amount: AttrAmount, + + created_at: AttrCreatedAt, } - -} -impl Datum for Orders { - fn dat_type() -> &'static DatType { - lazy_static! { - static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); + impl TupleDatumMarker for Orders {} + + impl SQLParamMarker for Orders {} + + impl Orders { + pub fn new( + order_id: Option, + user_id: Option, + merch_id: Option, + amount: Option, + created_at: Option, + ) -> Self { + let s = Self { + order_id: AttrOrderId::from(order_id), + + user_id: AttrUserId::from(user_id), + + merch_id: AttrMerchId::from(merch_id), + + amount: AttrAmount::from(amount), + + created_at: AttrCreatedAt::from(created_at), + }; + s } - &DAT_TYPE - } - fn from_binary(binary: &[u8]) -> RS { - entity_utils::entity_from_binary(binary) - } + pub fn new_empty() -> Self { + Self::default() + } - fn from_value(value: &DatValue) -> RS { - entity_utils::entity_from_value(value) - } + pub fn set_order_id(&mut self, order_id: i32) { + self.order_id.update(order_id) + } - fn from_textual(textual: &str) -> RS { - entity_utils::entity_from_textual(textual) - } -} + pub fn get_order_id(&self) -> &Option { + self.order_id.get() + } -impl DatumDyn for Orders { - fn dat_type_id(&self) -> RS { - entity_utils::entity_dat_type_id() - } + pub fn set_user_id(&mut self, user_id: i32) { + self.user_id.update(user_id) + } - fn to_binary(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_binary(self, dat_type) - } + pub fn get_user_id(&self) -> &Option { + self.user_id.get() + } - fn to_textual(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_textual(self, dat_type) - } + pub fn set_merch_id(&mut self, merch_id: i32) { + self.merch_id.update(merch_id) + } - fn to_value(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_value(self, dat_type) - } + pub fn get_merch_id(&self) -> &Option { + self.merch_id.get() + } - fn clone_boxed(&self) -> Box { - entity_utils::entity_clone_boxed(self) - } -} + pub fn set_amount(&mut self, amount: i32) { + self.amount.update(amount) + } -impl Entity for Orders { - fn new_empty() -> Self { - Self::new_empty() - } + pub fn get_amount(&self) -> &Option { + self.amount.get() + } - fn tuple_desc() -> &'static TupleFieldDesc { - lazy_static! { - static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ - - AttrOrderId::datum_desc().clone(), - - AttrUserId::datum_desc().clone(), - - AttrMerchId::datum_desc().clone(), - - AttrAmount::datum_desc().clone(), - - AttrCreatedAt::datum_desc().clone(), - - ]); - } - &TUPLE_DESC - } + pub fn set_created_at(&mut self, created_at: i32) { + self.created_at.update(created_at) + } - fn object_name() -> &'static str { - ORDERS + pub fn get_created_at(&self) -> &Option { + self.created_at.get() + } } - fn get_field_binary(&self, field: &str) -> RS>> { - match field { - - ORDER_ID => { - attr_field_access::attr_get_binary::<_>(self.order_id.get()) - } - - USER_ID => { - attr_field_access::attr_get_binary::<_>(self.user_id.get()) - } - - MERCH_ID => { - attr_field_access::attr_get_binary::<_>(self.merch_id.get()) - } - - AMOUNT => { - attr_field_access::attr_get_binary::<_>(self.amount.get()) + impl Datum for Orders { + fn dat_type() -> &'static DatType { + lazy_static! { + static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); } - - CREATED_AT => { - attr_field_access::attr_get_binary::<_>(self.created_at.get()) - } - - _ => { panic!("unknown name"); } + &DAT_TYPE } - } - fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { - match field { - - ORDER_ID => { - attr_field_access::attr_set_binary::<_, _>(self.order_id.get_mut(), binary.as_ref())?; - } - - USER_ID => { - attr_field_access::attr_set_binary::<_, _>(self.user_id.get_mut(), binary.as_ref())?; - } - - MERCH_ID => { - attr_field_access::attr_set_binary::<_, _>(self.merch_id.get_mut(), binary.as_ref())?; - } - - AMOUNT => { - attr_field_access::attr_set_binary::<_, _>(self.amount.get_mut(), binary.as_ref())?; - } - - CREATED_AT => { - attr_field_access::attr_set_binary::<_, _>(self.created_at.get_mut(), binary.as_ref())?; - } - - _ => { panic!("unknown name"); } + fn from_binary(binary: &[u8]) -> RS { + entity_utils::entity_from_binary(binary) + } + + fn from_value(value: &DatValue) -> RS { + entity_utils::entity_from_value(value) + } + + fn from_textual(textual: &str) -> RS { + entity_utils::entity_from_textual(textual) } - Ok(()) } - fn get_field_value(&self, field: &str) -> RS> { - match field { - - ORDER_ID => { - attr_field_access::attr_get_value::<_>(self.order_id.get()) - } - - USER_ID => { - attr_field_access::attr_get_value::<_>(self.user_id.get()) - } - - MERCH_ID => { - attr_field_access::attr_get_value::<_>(self.merch_id.get()) - } - - AMOUNT => { - attr_field_access::attr_get_value::<_>(self.amount.get()) - } - - CREATED_AT => { - attr_field_access::attr_get_value::<_>(self.created_at.get()) - } - - _ => { panic!("unknown name"); } + impl DatumDyn for Orders { + fn dat_type_id(&self) -> RS { + entity_utils::entity_dat_type_id() + } + + fn to_binary(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_binary(self, dat_type) + } + + fn to_textual(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_textual(self, dat_type) + } + + fn to_value(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_value(self, dat_type) + } + + fn clone_boxed(&self) -> Box { + entity_utils::entity_clone_boxed(self) } } - fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { - match field { - - ORDER_ID => { - attr_field_access::attr_set_value::<_, _>(self.order_id.get_mut(), value)?; - } - - USER_ID => { - attr_field_access::attr_set_value::<_, _>(self.user_id.get_mut(), value)?; + impl Entity for Orders { + fn new_empty() -> Self { + Self::new_empty() + } + + fn tuple_desc() -> &'static TupleFieldDesc { + lazy_static! { + static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ + AttrOrderId::datum_desc().clone(), + AttrUserId::datum_desc().clone(), + AttrMerchId::datum_desc().clone(), + AttrAmount::datum_desc().clone(), + AttrCreatedAt::datum_desc().clone(), + ]); } - - MERCH_ID => { - attr_field_access::attr_set_value::<_, _>(self.merch_id.get_mut(), value)?; + &TUPLE_DESC + } + + fn object_name() -> &'static str { + ORDERS + } + + fn get_field_binary(&self, field: &str) -> RS>> { + match field { + ORDER_ID => attr_field_access::attr_get_binary::<_>(self.order_id.get()), + + USER_ID => attr_field_access::attr_get_binary::<_>(self.user_id.get()), + + MERCH_ID => attr_field_access::attr_get_binary::<_>(self.merch_id.get()), + + AMOUNT => attr_field_access::attr_get_binary::<_>(self.amount.get()), + + CREATED_AT => attr_field_access::attr_get_binary::<_>(self.created_at.get()), + + _ => { + panic!("unknown name"); + } } - - AMOUNT => { - attr_field_access::attr_set_value::<_, _>(self.amount.get_mut(), value)?; + } + + fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { + match field { + ORDER_ID => { + attr_field_access::attr_set_binary::<_, _>( + self.order_id.get_mut(), + binary.as_ref(), + )?; + } + + USER_ID => { + attr_field_access::attr_set_binary::<_, _>( + self.user_id.get_mut(), + binary.as_ref(), + )?; + } + + MERCH_ID => { + attr_field_access::attr_set_binary::<_, _>( + self.merch_id.get_mut(), + binary.as_ref(), + )?; + } + + AMOUNT => { + attr_field_access::attr_set_binary::<_, _>( + self.amount.get_mut(), + binary.as_ref(), + )?; + } + + CREATED_AT => { + attr_field_access::attr_set_binary::<_, _>( + self.created_at.get_mut(), + binary.as_ref(), + )?; + } + + _ => { + panic!("unknown name"); + } } - - CREATED_AT => { - attr_field_access::attr_set_value::<_, _>(self.created_at.get_mut(), value)?; + Ok(()) + } + + fn get_field_value(&self, field: &str) -> RS> { + match field { + ORDER_ID => attr_field_access::attr_get_value::<_>(self.order_id.get()), + + USER_ID => attr_field_access::attr_get_value::<_>(self.user_id.get()), + + MERCH_ID => attr_field_access::attr_get_value::<_>(self.merch_id.get()), + + AMOUNT => attr_field_access::attr_get_value::<_>(self.amount.get()), + + CREATED_AT => attr_field_access::attr_get_value::<_>(self.created_at.get()), + + _ => { + panic!("unknown name"); + } } - - _ => { panic!("unknown name"); } } - Ok(()) - } -} + fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { + match field { + ORDER_ID => { + attr_field_access::attr_set_value::<_, _>(self.order_id.get_mut(), value)?; + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrOrderId { - is_dirty:bool, - value: Option -} + USER_ID => { + attr_field_access::attr_set_value::<_, _>(self.user_id.get_mut(), value)?; + } -impl AttrOrderId { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + MERCH_ID => { + attr_field_access::attr_set_value::<_, _>(self.merch_id.get_mut(), value)?; + } + + AMOUNT => { + attr_field_access::attr_set_value::<_, _>(self.amount.get_mut(), value)?; + } + + CREATED_AT => { + attr_field_access::attr_set_value::<_, _>(self.created_at.get_mut(), value)?; + } + + _ => { + panic!("unknown name"); + } + } + Ok(()) } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrOrderId { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrOrderId { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrOrderId { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - ORDERS - } + impl AttrValue for AttrOrderId { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - ORDER_ID - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrUserId { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + ORDERS + } -impl AttrUserId { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + ORDER_ID } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrUserId { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrUserId { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrUserId { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - ORDERS - } + impl AttrValue for AttrUserId { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - USER_ID - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrMerchId { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + ORDERS + } -impl AttrMerchId { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + USER_ID } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrMerchId { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrMerchId { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrMerchId { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - ORDERS - } + impl AttrValue for AttrMerchId { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - MERCH_ID - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrAmount { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + ORDERS + } -impl AttrAmount { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + MERCH_ID } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrAmount { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrAmount { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrAmount { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - ORDERS - } + impl AttrValue for AttrAmount { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - AMOUNT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrCreatedAt { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + ORDERS + } -impl AttrCreatedAt { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + AMOUNT } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrCreatedAt { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrCreatedAt { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrCreatedAt { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - ORDERS - } + impl AttrValue for AttrCreatedAt { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - CREATED_AT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } + fn object_name() -> &'static str { + ORDERS + } -} \ No newline at end of file + fn attr_name() -> &'static str { + CREATED_AT + } + } +} diff --git a/example/wallet/src/generated/procedures.rs b/example/wallet/src/generated/procedures.rs index 9a985ff..efcc739 100644 --- a/example/wallet/src/generated/procedures.rs +++ b/example/wallet/src/generated/procedures.rs @@ -11,7 +11,7 @@ use sys_interface::async_api::{mudu_command, mudu_query}; use uuid::Uuid; fn current_timestamp() -> i64 { - let now = SystemTime::now(); + let now = mudu_sys::time::system_time_now(); let duration_since_epoch = now .duration_since(UNIX_EPOCH) .expect("SystemTime before UNIX EPOCH!"); @@ -40,8 +40,8 @@ pub async fn transfer_funds(xid: XID, from_user_id: i32, to_user_id: i32, amount xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?;"), sql_params!(&(from_user_id,)), - ).await - ?; + ) + .await?; let from_wallet = if let Some(row) = wallet_rs.next_record()? { row @@ -58,8 +58,8 @@ pub async fn transfer_funds(xid: XID, from_user_id: i32, to_user_id: i32, amount xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?;"), sql_params!(&(to_user_id)), - ).await - ?; + ) + .await?; let _to_wallet = if let Some(row) = to_wallet.next_record()? { row } else { @@ -72,8 +72,8 @@ pub async fn transfer_funds(xid: XID, from_user_id: i32, to_user_id: i32, amount xid, sql_stmt!(&"UPDATE wallets SET balance = balance - ? WHERE user_id = ?;"), sql_params!(&(amount, from_user_id)), - ).await - ?; + ) + .await?; if deduct_updated_rows != 1 { return Err(m_error!(MuduError, "transfer fund failed")); } @@ -82,14 +82,14 @@ pub async fn transfer_funds(xid: XID, from_user_id: i32, to_user_id: i32, amount xid, sql_stmt!(&"UPDATE wallets SET balance = balance + ? WHERE user_id = ?;"), sql_params!(&(amount, to_user_id)), - ).await - ?; + ) + .await?; if increase_updated_rows != 1 { return Err(m_error!(MuduError, "transfer fund failed")); } // 3. Entity the transaction - let id = Uuid::new_v4().to_string(); + let id = mudu_sys::random::next_uuid_v4_string(); let insert_rows = mudu_command( xid, sql_stmt!( @@ -100,8 +100,8 @@ pub async fn transfer_funds(xid: XID, from_user_id: i32, to_user_id: i32, amount "# ), sql_params!(&(id, from_user_id, to_user_id, amount)), - ).await - ?; + ) + .await?; if insert_rows != 1 { return Err(m_error!(MuduError, "transfer fund failed")); } @@ -130,8 +130,8 @@ pub async fn create_user(xid: XID, user_id: i32, name: String, email: String) -> xid, sql_stmt!(&"INSERT INTO wallets (user_id, balance, updated_at) VALUES (?, ?, ?)"), sql_params!(&(user_id, 0, now)), - ).await - ?; + ) + .await?; if wallet_created != 1 { return Err(m_error!(MuduError, "Failed to create wallet")); @@ -147,8 +147,8 @@ pub async fn delete_user(xid: XID, user_id: i32) -> RS<()> { xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?"), sql_params!(&(user_id,)), - ).await - ?; + ) + .await?; let wallet = wallet_rs .next_record()? @@ -166,16 +166,16 @@ pub async fn delete_user(xid: XID, user_id: i32) -> RS<()> { xid, sql_stmt!(&"DELETE FROM wallets WHERE user_id = ?"), sql_params!(&(user_id,)), - ).await - ?; + ) + .await?; // Delete user mudu_command( xid, sql_stmt!(&"DELETE FROM users WHERE user_id = ?"), sql_params!(&(user_id,)), - ).await - ?; + ) + .await?; Ok(()) } @@ -217,15 +217,15 @@ pub async fn deposit(xid: XID, user_id: i32, amount: i32) -> RS<()> { } let now = current_timestamp(); - let tx_id = Uuid::new_v4().to_string(); + let tx_id = mudu_sys::random::next_uuid_v4_string(); // Update wallet balance let updated = mudu_command( xid, sql_stmt!(&"UPDATE wallets SET balance = balance + ?, updated_at = ? WHERE user_id = ?"), sql_params!(&(amount, now, user_id)), - ).await - ?; + ) + .await?; if updated != 1 { return Err(m_error!(MuduError, "User wallet not found")); @@ -254,8 +254,8 @@ pub async fn withdraw(xid: XID, user_id: i32, amount: i32) -> RS<()> { xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?"), sql_params!(&(user_id,)), - ).await - ?; + ) + .await?; let wallet = wallet_rs .next_record()? @@ -266,15 +266,15 @@ pub async fn withdraw(xid: XID, user_id: i32, amount: i32) -> RS<()> { } let now = current_timestamp(); - let tx_id = Uuid::new_v4().to_string(); + let tx_id = mudu_sys::random::next_uuid_v4_string(); // Update wallet balance mudu_command( xid, sql_stmt!(&"UPDATE wallets SET balance = balance - ?, updated_at = ? WHERE user_id = ?"), sql_params!(&(amount, now, user_id)), - ).await - ?; + ) + .await?; // Entity transaction mudu_command( @@ -303,8 +303,8 @@ pub async fn transfer(xid: XID, from_user_id: i32, to_user_id: i32, amount: i32) xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?"), sql_params!(&(from_user_id,)), - ).await - ? + ) + .await? .next_record()? .ok_or_else(|| m_error!(MuduError, "Sender wallet not found"))?; @@ -317,8 +317,8 @@ pub async fn transfer(xid: XID, from_user_id: i32, to_user_id: i32, amount: i32) xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?"), sql_params!(&(to_user_id.clone(),)), - ).await - ? + ) + .await? .next_record()? .is_some(); @@ -327,23 +327,23 @@ pub async fn transfer(xid: XID, from_user_id: i32, to_user_id: i32, amount: i32) } let now = current_timestamp(); - let tx_id = Uuid::new_v4().to_string(); + let tx_id = mudu_sys::random::next_uuid_v4_string(); // Debit sender mudu_command( xid, sql_stmt!(&"UPDATE wallets SET balance = balance - ?, updated_at = ? WHERE user_id = ?"), sql_params!(&(amount, now, from_user_id)), - ).await - ?; + ) + .await?; // Credit receiver mudu_command( xid, sql_stmt!(&"UPDATE wallets SET balance = balance + ?, updated_at = ? WHERE user_id = ?"), sql_params!(&(amount, now, to_user_id)), - ).await - ?; + ) + .await?; // Entity transaction mudu_command( @@ -376,8 +376,8 @@ pub async fn purchase(xid: XID, user_id: i32, amount: i32, description: String) xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?"), sql_params!(&(user_id,)), - ).await - ? + ) + .await? .next_record()? .ok_or_else(|| m_error!(MuduError, "Wallet not found"))?; @@ -386,15 +386,15 @@ pub async fn purchase(xid: XID, user_id: i32, amount: i32, description: String) } let now = current_timestamp(); - let tx_id = Uuid::new_v4().to_string(); + let tx_id = mudu_sys::random::next_uuid_v4_string(); // Deduct amount mudu_command( xid, sql_stmt!(&"UPDATE wallets SET balance = balance - ?, updated_at = ? WHERE user_id = ?"), sql_params!(&(amount, now, user_id)), - ).await - ?; + ) + .await?; // Entity transaction mudu_command( @@ -406,1028 +406,760 @@ pub async fn purchase(xid: XID, user_id: i32, amount: i32, description: String) ).await?; Ok(()) -} -async fn mp2_create_user(param:Vec) -> Vec { - ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( - param, - mudu_inner_p2_create_user, - ).await -} - -pub async fn mudu_inner_p2_create_user( - param: ::mudu_contract::procedure::procedure_param::ProcedureParam, -) -> ::mudu::common::result::RS< - ::mudu_contract::procedure::procedure_result::ProcedureResult, -> { - let return_desc = mudu_result_desc_create_user().clone(); - let res = create_user( - param.session_id(), - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[0], "i32")?, - - - - ::mudu_type::datum::value_to_typed::< - String, - _, - >(¶m.param_list()[1], "String")?, - - - - ::mudu_type::datum::value_to_typed::< - String, - _, - >(¶m.param_list()[2], "String")?, - - - ).await; - match res { - Ok(tuple) => { - let return_list = { - - vec![] - - }; - Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) - } - Err(e) => Err(e), - } -} - -pub fn mudu_argv_desc_create_user() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static ARGV_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - ARGV_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "user_id".to_string(), - - ::dat_type().clone() - - ), - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "name".to_string(), - - ::dat_type().clone() - - ), - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "email".to_string(), - - ::dat_type().clone() - - ), - - ]) - } - ) -} - -pub fn mudu_result_desc_create_user() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static RESULT_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - RESULT_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ]) - } - ) -} - -pub fn mudu_proc_desc_create_user() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { - static _PROC_DESC: std::sync::OnceLock< - ::mudu_contract::procedure::proc_desc::ProcDesc, - > = std::sync::OnceLock::new(); - _PROC_DESC - .get_or_init(|| { - ::mudu_contract::procedure::proc_desc::ProcDesc::new( - "wallet".to_string(), - "create_user".to_string(), - mudu_argv_desc_create_user().clone(), - mudu_result_desc_create_user().clone(), - false - ) - }) -} - -mod mod_create_user { - wit_bindgen::generate!({ - inline: - r##"package mudu:mp2-create-user; - world mudu-app-mp2-create-user { - export mp2-create-user: func(param:list) -> list; - } - "##, - async: true - }); - - #[allow(non_camel_case_types)] - #[allow(unused)] - struct GuestCreateUser {} - - impl Guest for GuestCreateUser { - async fn mp2_create_user(param:Vec) -> Vec { - super::mp2_create_user(param).await - } - } - - export!(GuestCreateUser); -} -async fn mp2_purchase(param:Vec) -> Vec { - ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( - param, - mudu_inner_p2_purchase, - ).await -} - -pub async fn mudu_inner_p2_purchase( - param: ::mudu_contract::procedure::procedure_param::ProcedureParam, -) -> ::mudu::common::result::RS< - ::mudu_contract::procedure::procedure_result::ProcedureResult, -> { - let return_desc = mudu_result_desc_purchase().clone(); - let res = purchase( - param.session_id(), - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[0], "i32")?, - - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[1], "i32")?, - - - - ::mudu_type::datum::value_to_typed::< - String, - _, - >(¶m.param_list()[2], "String")?, - - - ).await; - match res { - Ok(tuple) => { - let return_list = { - - vec![] - - }; - Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) - } - Err(e) => Err(e), - } -} - -pub fn mudu_argv_desc_purchase() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static ARGV_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - ARGV_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "user_id".to_string(), - - ::dat_type().clone() - - ), - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "amount".to_string(), - - ::dat_type().clone() - - ), - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "description".to_string(), - - ::dat_type().clone() - - ), - - ]) - } - ) -} - -pub fn mudu_result_desc_purchase() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static RESULT_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - RESULT_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ]) - } - ) -} - -pub fn mudu_proc_desc_purchase() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { - static _PROC_DESC: std::sync::OnceLock< - ::mudu_contract::procedure::proc_desc::ProcDesc, - > = std::sync::OnceLock::new(); - _PROC_DESC - .get_or_init(|| { - ::mudu_contract::procedure::proc_desc::ProcDesc::new( - "wallet".to_string(), - "purchase".to_string(), - mudu_argv_desc_purchase().clone(), - mudu_result_desc_purchase().clone(), - false - ) - }) -} - -mod mod_purchase { - wit_bindgen::generate!({ - inline: - r##"package mudu:mp2-purchase; - world mudu-app-mp2-purchase { - export mp2-purchase: func(param:list) -> list; - } - "##, - async: true - }); - - #[allow(non_camel_case_types)] - #[allow(unused)] - struct GuestPurchase {} - - impl Guest for GuestPurchase { - async fn mp2_purchase(param:Vec) -> Vec { - super::mp2_purchase(param).await - } - } - - export!(GuestPurchase); -} -async fn mp2_delete_user(param:Vec) -> Vec { - ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( - param, - mudu_inner_p2_delete_user, - ).await -} - -pub async fn mudu_inner_p2_delete_user( - param: ::mudu_contract::procedure::procedure_param::ProcedureParam, -) -> ::mudu::common::result::RS< - ::mudu_contract::procedure::procedure_result::ProcedureResult, -> { - let return_desc = mudu_result_desc_delete_user().clone(); - let res = delete_user( - param.session_id(), - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[0], "i32")?, - - - ).await; - match res { - Ok(tuple) => { - let return_list = { - - vec![] - - }; - Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) - } - Err(e) => Err(e), - } -} - -pub fn mudu_argv_desc_delete_user() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static ARGV_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - ARGV_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "user_id".to_string(), - - ::dat_type().clone() - - ), - - ]) - } - ) -} - -pub fn mudu_result_desc_delete_user() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static RESULT_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - RESULT_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ]) - } - ) -} - -pub fn mudu_proc_desc_delete_user() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { - static _PROC_DESC: std::sync::OnceLock< - ::mudu_contract::procedure::proc_desc::ProcDesc, - > = std::sync::OnceLock::new(); - _PROC_DESC - .get_or_init(|| { - ::mudu_contract::procedure::proc_desc::ProcDesc::new( - "wallet".to_string(), - "delete_user".to_string(), - mudu_argv_desc_delete_user().clone(), - mudu_result_desc_delete_user().clone(), - false - ) - }) -} - -mod mod_delete_user { - wit_bindgen::generate!({ - inline: - r##"package mudu:mp2-delete-user; - world mudu-app-mp2-delete-user { - export mp2-delete-user: func(param:list) -> list; - } - "##, - async: true - }); - - #[allow(non_camel_case_types)] - #[allow(unused)] - struct GuestDeleteUser {} - - impl Guest for GuestDeleteUser { - async fn mp2_delete_user(param:Vec) -> Vec { - super::mp2_delete_user(param).await - } - } - - export!(GuestDeleteUser); -} -async fn mp2_deposit(param:Vec) -> Vec { - ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( - param, - mudu_inner_p2_deposit, - ).await -} - -pub async fn mudu_inner_p2_deposit( - param: ::mudu_contract::procedure::procedure_param::ProcedureParam, -) -> ::mudu::common::result::RS< - ::mudu_contract::procedure::procedure_result::ProcedureResult, -> { - let return_desc = mudu_result_desc_deposit().clone(); - let res = deposit( - param.session_id(), - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[0], "i32")?, - - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[1], "i32")?, - - - ).await; - match res { - Ok(tuple) => { - let return_list = { - - vec![] - - }; - Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) - } - Err(e) => Err(e), - } -} - -pub fn mudu_argv_desc_deposit() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static ARGV_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - ARGV_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "user_id".to_string(), - - ::dat_type().clone() - - ), - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "amount".to_string(), - - ::dat_type().clone() - - ), - - ]) - } - ) -} - -pub fn mudu_result_desc_deposit() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static RESULT_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - RESULT_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ]) - } - ) -} - -pub fn mudu_proc_desc_deposit() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { - static _PROC_DESC: std::sync::OnceLock< - ::mudu_contract::procedure::proc_desc::ProcDesc, - > = std::sync::OnceLock::new(); - _PROC_DESC - .get_or_init(|| { - ::mudu_contract::procedure::proc_desc::ProcDesc::new( - "wallet".to_string(), - "deposit".to_string(), - mudu_argv_desc_deposit().clone(), - mudu_result_desc_deposit().clone(), - false - ) - }) -} - -mod mod_deposit { - wit_bindgen::generate!({ - inline: - r##"package mudu:mp2-deposit; - world mudu-app-mp2-deposit { - export mp2-deposit: func(param:list) -> list; - } - "##, - async: true - }); - - #[allow(non_camel_case_types)] - #[allow(unused)] - struct GuestDeposit {} - - impl Guest for GuestDeposit { - async fn mp2_deposit(param:Vec) -> Vec { - super::mp2_deposit(param).await - } - } - - export!(GuestDeposit); -} -async fn mp2_transfer_funds(param:Vec) -> Vec { - ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( - param, - mudu_inner_p2_transfer_funds, - ).await -} - -pub async fn mudu_inner_p2_transfer_funds( - param: ::mudu_contract::procedure::procedure_param::ProcedureParam, -) -> ::mudu::common::result::RS< - ::mudu_contract::procedure::procedure_result::ProcedureResult, -> { - let return_desc = mudu_result_desc_transfer_funds().clone(); - let res = transfer_funds( - param.session_id(), - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[0], "i32")?, - - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[1], "i32")?, - - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[2], "i32")?, - - - ).await; - match res { - Ok(tuple) => { - let return_list = { - - vec![] - - }; - Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) - } - Err(e) => Err(e), - } -} - -pub fn mudu_argv_desc_transfer_funds() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static ARGV_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - ARGV_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "from_user_id".to_string(), - - ::dat_type().clone() - - ), - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "to_user_id".to_string(), - - ::dat_type().clone() - - ), - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "amount".to_string(), - - ::dat_type().clone() - - ), - - ]) - } - ) -} - -pub fn mudu_result_desc_transfer_funds() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static RESULT_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - RESULT_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ]) - } - ) -} - -pub fn mudu_proc_desc_transfer_funds() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { - static _PROC_DESC: std::sync::OnceLock< - ::mudu_contract::procedure::proc_desc::ProcDesc, - > = std::sync::OnceLock::new(); - _PROC_DESC - .get_or_init(|| { - ::mudu_contract::procedure::proc_desc::ProcDesc::new( - "wallet".to_string(), - "transfer_funds".to_string(), - mudu_argv_desc_transfer_funds().clone(), - mudu_result_desc_transfer_funds().clone(), - false - ) - }) -} - -mod mod_transfer_funds { - wit_bindgen::generate!({ - inline: - r##"package mudu:mp2-transfer-funds; - world mudu-app-mp2-transfer-funds { - export mp2-transfer-funds: func(param:list) -> list; - } - "##, - async: true - }); - - #[allow(non_camel_case_types)] - #[allow(unused)] - struct GuestTransferFunds {} - - impl Guest for GuestTransferFunds { - async fn mp2_transfer_funds(param:Vec) -> Vec { - super::mp2_transfer_funds(param).await - } - } - - export!(GuestTransferFunds); -} -async fn mp2_update_user(param:Vec) -> Vec { - ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( - param, - mudu_inner_p2_update_user, - ).await -} - -pub async fn mudu_inner_p2_update_user( - param: ::mudu_contract::procedure::procedure_param::ProcedureParam, -) -> ::mudu::common::result::RS< - ::mudu_contract::procedure::procedure_result::ProcedureResult, -> { - let return_desc = mudu_result_desc_update_user().clone(); - let res = update_user( - param.session_id(), - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[0], "i32")?, - - - - ::mudu_type::datum::value_to_typed::< - String, - _, - >(¶m.param_list()[1], "String")?, - - - - ::mudu_type::datum::value_to_typed::< - String, - _, - >(¶m.param_list()[2], "String")?, - - - ).await; - match res { - Ok(tuple) => { - let return_list = { - - vec![] - - }; - Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) - } - Err(e) => Err(e), - } -} - -pub fn mudu_argv_desc_update_user() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static ARGV_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - ARGV_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "user_id".to_string(), - - ::dat_type().clone() - - ), - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "name".to_string(), - - ::dat_type().clone() - - ), - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "email".to_string(), - - ::dat_type().clone() - - ), - - ]) - } - ) -} - -pub fn mudu_result_desc_update_user() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static RESULT_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - RESULT_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ]) - } - ) -} - -pub fn mudu_proc_desc_update_user() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { - static _PROC_DESC: std::sync::OnceLock< - ::mudu_contract::procedure::proc_desc::ProcDesc, - > = std::sync::OnceLock::new(); - _PROC_DESC - .get_or_init(|| { - ::mudu_contract::procedure::proc_desc::ProcDesc::new( - "wallet".to_string(), - "update_user".to_string(), - mudu_argv_desc_update_user().clone(), - mudu_result_desc_update_user().clone(), - false - ) - }) -} - -mod mod_update_user { - wit_bindgen::generate!({ - inline: - r##"package mudu:mp2-update-user; - world mudu-app-mp2-update-user { - export mp2-update-user: func(param:list) -> list; - } - "##, - async: true - }); - - #[allow(non_camel_case_types)] - #[allow(unused)] - struct GuestUpdateUser {} - - impl Guest for GuestUpdateUser { - async fn mp2_update_user(param:Vec) -> Vec { - super::mp2_update_user(param).await - } - } - - export!(GuestUpdateUser); -} -async fn mp2_withdraw(param:Vec) -> Vec { - ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( - param, - mudu_inner_p2_withdraw, - ).await -} - -pub async fn mudu_inner_p2_withdraw( - param: ::mudu_contract::procedure::procedure_param::ProcedureParam, -) -> ::mudu::common::result::RS< - ::mudu_contract::procedure::procedure_result::ProcedureResult, -> { - let return_desc = mudu_result_desc_withdraw().clone(); - let res = withdraw( - param.session_id(), - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[0], "i32")?, - - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[1], "i32")?, - - - ).await; - match res { - Ok(tuple) => { - let return_list = { - - vec![] - - }; - Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) - } - Err(e) => Err(e), - } -} - -pub fn mudu_argv_desc_withdraw() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static ARGV_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - ARGV_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "user_id".to_string(), - - ::dat_type().clone() - - ), - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "amount".to_string(), - - ::dat_type().clone() - - ), - - ]) - } - ) -} - -pub fn mudu_result_desc_withdraw() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static RESULT_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - RESULT_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ]) - } - ) -} - -pub fn mudu_proc_desc_withdraw() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { - static _PROC_DESC: std::sync::OnceLock< - ::mudu_contract::procedure::proc_desc::ProcDesc, - > = std::sync::OnceLock::new(); - _PROC_DESC - .get_or_init(|| { - ::mudu_contract::procedure::proc_desc::ProcDesc::new( - "wallet".to_string(), - "withdraw".to_string(), - mudu_argv_desc_withdraw().clone(), - mudu_result_desc_withdraw().clone(), - false - ) - }) -} - -mod mod_withdraw { - wit_bindgen::generate!({ - inline: - r##"package mudu:mp2-withdraw; - world mudu-app-mp2-withdraw { - export mp2-withdraw: func(param:list) -> list; - } - "##, - async: true - }); - - #[allow(non_camel_case_types)] - #[allow(unused)] - struct GuestWithdraw {} - - impl Guest for GuestWithdraw { - async fn mp2_withdraw(param:Vec) -> Vec { - super::mp2_withdraw(param).await - } - } - - export!(GuestWithdraw); -} -async fn mp2_transfer(param:Vec) -> Vec { - ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( - param, - mudu_inner_p2_transfer, - ).await -} - -pub async fn mudu_inner_p2_transfer( - param: ::mudu_contract::procedure::procedure_param::ProcedureParam, -) -> ::mudu::common::result::RS< - ::mudu_contract::procedure::procedure_result::ProcedureResult, -> { - let return_desc = mudu_result_desc_transfer().clone(); - let res = transfer( - param.session_id(), - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[0], "i32")?, - - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[1], "i32")?, - - - - ::mudu_type::datum::value_to_typed::< - i32, - _, - >(¶m.param_list()[2], "i32")?, - - - ).await; - match res { - Ok(tuple) => { - let return_list = { - - vec![] - - }; - Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) - } - Err(e) => Err(e), - } -} - -pub fn mudu_argv_desc_transfer() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static ARGV_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - ARGV_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "from_user_id".to_string(), - - ::dat_type().clone() - - ), - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "to_user_id".to_string(), - - ::dat_type().clone() - - ), - - ::mudu_contract::tuple::datum_desc::DatumDesc::new( - "amount".to_string(), - - ::dat_type().clone() - - ), - - ]) - } - ) -} - -pub fn mudu_result_desc_transfer() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { - static RESULT_DESC: std::sync::OnceLock<::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc> = - std::sync::OnceLock::new(); - RESULT_DESC.get_or_init(|| - { - ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ - - ]) - } - ) -} - -pub fn mudu_proc_desc_transfer() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { - static _PROC_DESC: std::sync::OnceLock< - ::mudu_contract::procedure::proc_desc::ProcDesc, - > = std::sync::OnceLock::new(); - _PROC_DESC - .get_or_init(|| { - ::mudu_contract::procedure::proc_desc::ProcDesc::new( - "wallet".to_string(), - "transfer".to_string(), - mudu_argv_desc_transfer().clone(), - mudu_result_desc_transfer().clone(), - false - ) - }) -} - -mod mod_transfer { - wit_bindgen::generate!({ - inline: - r##"package mudu:mp2-transfer; - world mudu-app-mp2-transfer { - export mp2-transfer: func(param:list) -> list; - } - "##, - async: true - }); - - #[allow(non_camel_case_types)] - #[allow(unused)] - struct GuestTransfer {} - - impl Guest for GuestTransfer { - async fn mp2_transfer(param:Vec) -> Vec { - super::mp2_transfer(param).await - } - } - - export!(GuestTransfer); -} \ No newline at end of file +} +async fn mp2_create_user(param: Vec) -> Vec { + ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( + param, + mudu_inner_p2_create_user, + ) + .await +} + +pub async fn mudu_inner_p2_create_user( + param: ::mudu_contract::procedure::procedure_param::ProcedureParam, +) -> ::mudu::common::result::RS<::mudu_contract::procedure::procedure_result::ProcedureResult> { + let return_desc = mudu_result_desc_create_user().clone(); + let res = create_user( + param.session_id(), + ::mudu_type::datum::value_to_typed::(¶m.param_list()[0], "i32")?, + ::mudu_type::datum::value_to_typed::(¶m.param_list()[1], "String")?, + ::mudu_type::datum::value_to_typed::(¶m.param_list()[2], "String")?, + ) + .await; + match res { + Ok(tuple) => { + let return_list = { vec![] }; + Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) + } + Err(e) => Err(e), + } +} + +pub fn mudu_argv_desc_create_user( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static ARGV_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + ARGV_DESC.get_or_init(|| { + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "user_id".to_string(), + ::dat_type().clone(), + ), + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "name".to_string(), + ::dat_type().clone(), + ), + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "email".to_string(), + ::dat_type().clone(), + ), + ]) + }) +} + +pub fn mudu_result_desc_create_user( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static RESULT_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + RESULT_DESC + .get_or_init(|| ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![])) +} + +pub fn mudu_proc_desc_create_user() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { + static _PROC_DESC: std::sync::OnceLock<::mudu_contract::procedure::proc_desc::ProcDesc> = + std::sync::OnceLock::new(); + _PROC_DESC.get_or_init(|| { + ::mudu_contract::procedure::proc_desc::ProcDesc::new( + "wallet".to_string(), + "create_user".to_string(), + mudu_argv_desc_create_user().clone(), + mudu_result_desc_create_user().clone(), + false, + ) + }) +} + +mod mod_create_user { + wit_bindgen::generate!({ + inline: + r##"package mudu:mp2-create-user; + world mudu-app-mp2-create-user { + export mp2-create-user: func(param:list) -> list; + } + "##, + async: true + }); + + #[allow(non_camel_case_types)] + #[allow(unused)] + struct GuestCreateUser {} + + impl Guest for GuestCreateUser { + async fn mp2_create_user(param: Vec) -> Vec { + super::mp2_create_user(param).await + } + } + + export!(GuestCreateUser); +} +async fn mp2_purchase(param: Vec) -> Vec { + ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( + param, + mudu_inner_p2_purchase, + ) + .await +} + +pub async fn mudu_inner_p2_purchase( + param: ::mudu_contract::procedure::procedure_param::ProcedureParam, +) -> ::mudu::common::result::RS<::mudu_contract::procedure::procedure_result::ProcedureResult> { + let return_desc = mudu_result_desc_purchase().clone(); + let res = purchase( + param.session_id(), + ::mudu_type::datum::value_to_typed::(¶m.param_list()[0], "i32")?, + ::mudu_type::datum::value_to_typed::(¶m.param_list()[1], "i32")?, + ::mudu_type::datum::value_to_typed::(¶m.param_list()[2], "String")?, + ) + .await; + match res { + Ok(tuple) => { + let return_list = { vec![] }; + Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) + } + Err(e) => Err(e), + } +} + +pub fn mudu_argv_desc_purchase() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc +{ + static ARGV_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + ARGV_DESC.get_or_init(|| { + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "user_id".to_string(), + ::dat_type().clone(), + ), + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "amount".to_string(), + ::dat_type().clone(), + ), + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "description".to_string(), + ::dat_type().clone(), + ), + ]) + }) +} + +pub fn mudu_result_desc_purchase( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static RESULT_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + RESULT_DESC + .get_or_init(|| ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![])) +} + +pub fn mudu_proc_desc_purchase() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { + static _PROC_DESC: std::sync::OnceLock<::mudu_contract::procedure::proc_desc::ProcDesc> = + std::sync::OnceLock::new(); + _PROC_DESC.get_or_init(|| { + ::mudu_contract::procedure::proc_desc::ProcDesc::new( + "wallet".to_string(), + "purchase".to_string(), + mudu_argv_desc_purchase().clone(), + mudu_result_desc_purchase().clone(), + false, + ) + }) +} + +mod mod_purchase { + wit_bindgen::generate!({ + inline: + r##"package mudu:mp2-purchase; + world mudu-app-mp2-purchase { + export mp2-purchase: func(param:list) -> list; + } + "##, + async: true + }); + + #[allow(non_camel_case_types)] + #[allow(unused)] + struct GuestPurchase {} + + impl Guest for GuestPurchase { + async fn mp2_purchase(param: Vec) -> Vec { + super::mp2_purchase(param).await + } + } + + export!(GuestPurchase); +} +async fn mp2_delete_user(param: Vec) -> Vec { + ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( + param, + mudu_inner_p2_delete_user, + ) + .await +} + +pub async fn mudu_inner_p2_delete_user( + param: ::mudu_contract::procedure::procedure_param::ProcedureParam, +) -> ::mudu::common::result::RS<::mudu_contract::procedure::procedure_result::ProcedureResult> { + let return_desc = mudu_result_desc_delete_user().clone(); + let res = delete_user( + param.session_id(), + ::mudu_type::datum::value_to_typed::(¶m.param_list()[0], "i32")?, + ) + .await; + match res { + Ok(tuple) => { + let return_list = { vec![] }; + Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) + } + Err(e) => Err(e), + } +} + +pub fn mudu_argv_desc_delete_user( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static ARGV_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + ARGV_DESC.get_or_init(|| { + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "user_id".to_string(), + ::dat_type().clone(), + ), + ]) + }) +} + +pub fn mudu_result_desc_delete_user( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static RESULT_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + RESULT_DESC + .get_or_init(|| ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![])) +} + +pub fn mudu_proc_desc_delete_user() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { + static _PROC_DESC: std::sync::OnceLock<::mudu_contract::procedure::proc_desc::ProcDesc> = + std::sync::OnceLock::new(); + _PROC_DESC.get_or_init(|| { + ::mudu_contract::procedure::proc_desc::ProcDesc::new( + "wallet".to_string(), + "delete_user".to_string(), + mudu_argv_desc_delete_user().clone(), + mudu_result_desc_delete_user().clone(), + false, + ) + }) +} + +mod mod_delete_user { + wit_bindgen::generate!({ + inline: + r##"package mudu:mp2-delete-user; + world mudu-app-mp2-delete-user { + export mp2-delete-user: func(param:list) -> list; + } + "##, + async: true + }); + + #[allow(non_camel_case_types)] + #[allow(unused)] + struct GuestDeleteUser {} + + impl Guest for GuestDeleteUser { + async fn mp2_delete_user(param: Vec) -> Vec { + super::mp2_delete_user(param).await + } + } + + export!(GuestDeleteUser); +} +async fn mp2_deposit(param: Vec) -> Vec { + ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( + param, + mudu_inner_p2_deposit, + ) + .await +} + +pub async fn mudu_inner_p2_deposit( + param: ::mudu_contract::procedure::procedure_param::ProcedureParam, +) -> ::mudu::common::result::RS<::mudu_contract::procedure::procedure_result::ProcedureResult> { + let return_desc = mudu_result_desc_deposit().clone(); + let res = deposit( + param.session_id(), + ::mudu_type::datum::value_to_typed::(¶m.param_list()[0], "i32")?, + ::mudu_type::datum::value_to_typed::(¶m.param_list()[1], "i32")?, + ) + .await; + match res { + Ok(tuple) => { + let return_list = { vec![] }; + Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) + } + Err(e) => Err(e), + } +} + +pub fn mudu_argv_desc_deposit() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc +{ + static ARGV_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + ARGV_DESC.get_or_init(|| { + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "user_id".to_string(), + ::dat_type().clone(), + ), + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "amount".to_string(), + ::dat_type().clone(), + ), + ]) + }) +} + +pub fn mudu_result_desc_deposit( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static RESULT_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + RESULT_DESC + .get_or_init(|| ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![])) +} + +pub fn mudu_proc_desc_deposit() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { + static _PROC_DESC: std::sync::OnceLock<::mudu_contract::procedure::proc_desc::ProcDesc> = + std::sync::OnceLock::new(); + _PROC_DESC.get_or_init(|| { + ::mudu_contract::procedure::proc_desc::ProcDesc::new( + "wallet".to_string(), + "deposit".to_string(), + mudu_argv_desc_deposit().clone(), + mudu_result_desc_deposit().clone(), + false, + ) + }) +} + +mod mod_deposit { + wit_bindgen::generate!({ + inline: + r##"package mudu:mp2-deposit; + world mudu-app-mp2-deposit { + export mp2-deposit: func(param:list) -> list; + } + "##, + async: true + }); + + #[allow(non_camel_case_types)] + #[allow(unused)] + struct GuestDeposit {} + + impl Guest for GuestDeposit { + async fn mp2_deposit(param: Vec) -> Vec { + super::mp2_deposit(param).await + } + } + + export!(GuestDeposit); +} +async fn mp2_transfer_funds(param: Vec) -> Vec { + ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( + param, + mudu_inner_p2_transfer_funds, + ) + .await +} + +pub async fn mudu_inner_p2_transfer_funds( + param: ::mudu_contract::procedure::procedure_param::ProcedureParam, +) -> ::mudu::common::result::RS<::mudu_contract::procedure::procedure_result::ProcedureResult> { + let return_desc = mudu_result_desc_transfer_funds().clone(); + let res = transfer_funds( + param.session_id(), + ::mudu_type::datum::value_to_typed::(¶m.param_list()[0], "i32")?, + ::mudu_type::datum::value_to_typed::(¶m.param_list()[1], "i32")?, + ::mudu_type::datum::value_to_typed::(¶m.param_list()[2], "i32")?, + ) + .await; + match res { + Ok(tuple) => { + let return_list = { vec![] }; + Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) + } + Err(e) => Err(e), + } +} + +pub fn mudu_argv_desc_transfer_funds( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static ARGV_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + ARGV_DESC.get_or_init(|| { + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "from_user_id".to_string(), + ::dat_type().clone(), + ), + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "to_user_id".to_string(), + ::dat_type().clone(), + ), + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "amount".to_string(), + ::dat_type().clone(), + ), + ]) + }) +} + +pub fn mudu_result_desc_transfer_funds( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static RESULT_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + RESULT_DESC + .get_or_init(|| ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![])) +} + +pub fn mudu_proc_desc_transfer_funds() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { + static _PROC_DESC: std::sync::OnceLock<::mudu_contract::procedure::proc_desc::ProcDesc> = + std::sync::OnceLock::new(); + _PROC_DESC.get_or_init(|| { + ::mudu_contract::procedure::proc_desc::ProcDesc::new( + "wallet".to_string(), + "transfer_funds".to_string(), + mudu_argv_desc_transfer_funds().clone(), + mudu_result_desc_transfer_funds().clone(), + false, + ) + }) +} + +mod mod_transfer_funds { + wit_bindgen::generate!({ + inline: + r##"package mudu:mp2-transfer-funds; + world mudu-app-mp2-transfer-funds { + export mp2-transfer-funds: func(param:list) -> list; + } + "##, + async: true + }); + + #[allow(non_camel_case_types)] + #[allow(unused)] + struct GuestTransferFunds {} + + impl Guest for GuestTransferFunds { + async fn mp2_transfer_funds(param: Vec) -> Vec { + super::mp2_transfer_funds(param).await + } + } + + export!(GuestTransferFunds); +} +async fn mp2_update_user(param: Vec) -> Vec { + ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( + param, + mudu_inner_p2_update_user, + ) + .await +} + +pub async fn mudu_inner_p2_update_user( + param: ::mudu_contract::procedure::procedure_param::ProcedureParam, +) -> ::mudu::common::result::RS<::mudu_contract::procedure::procedure_result::ProcedureResult> { + let return_desc = mudu_result_desc_update_user().clone(); + let res = update_user( + param.session_id(), + ::mudu_type::datum::value_to_typed::(¶m.param_list()[0], "i32")?, + ::mudu_type::datum::value_to_typed::(¶m.param_list()[1], "String")?, + ::mudu_type::datum::value_to_typed::(¶m.param_list()[2], "String")?, + ) + .await; + match res { + Ok(tuple) => { + let return_list = { vec![] }; + Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) + } + Err(e) => Err(e), + } +} + +pub fn mudu_argv_desc_update_user( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static ARGV_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + ARGV_DESC.get_or_init(|| { + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "user_id".to_string(), + ::dat_type().clone(), + ), + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "name".to_string(), + ::dat_type().clone(), + ), + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "email".to_string(), + ::dat_type().clone(), + ), + ]) + }) +} + +pub fn mudu_result_desc_update_user( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static RESULT_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + RESULT_DESC + .get_or_init(|| ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![])) +} + +pub fn mudu_proc_desc_update_user() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { + static _PROC_DESC: std::sync::OnceLock<::mudu_contract::procedure::proc_desc::ProcDesc> = + std::sync::OnceLock::new(); + _PROC_DESC.get_or_init(|| { + ::mudu_contract::procedure::proc_desc::ProcDesc::new( + "wallet".to_string(), + "update_user".to_string(), + mudu_argv_desc_update_user().clone(), + mudu_result_desc_update_user().clone(), + false, + ) + }) +} + +mod mod_update_user { + wit_bindgen::generate!({ + inline: + r##"package mudu:mp2-update-user; + world mudu-app-mp2-update-user { + export mp2-update-user: func(param:list) -> list; + } + "##, + async: true + }); + + #[allow(non_camel_case_types)] + #[allow(unused)] + struct GuestUpdateUser {} + + impl Guest for GuestUpdateUser { + async fn mp2_update_user(param: Vec) -> Vec { + super::mp2_update_user(param).await + } + } + + export!(GuestUpdateUser); +} +async fn mp2_withdraw(param: Vec) -> Vec { + ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( + param, + mudu_inner_p2_withdraw, + ) + .await +} + +pub async fn mudu_inner_p2_withdraw( + param: ::mudu_contract::procedure::procedure_param::ProcedureParam, +) -> ::mudu::common::result::RS<::mudu_contract::procedure::procedure_result::ProcedureResult> { + let return_desc = mudu_result_desc_withdraw().clone(); + let res = withdraw( + param.session_id(), + ::mudu_type::datum::value_to_typed::(¶m.param_list()[0], "i32")?, + ::mudu_type::datum::value_to_typed::(¶m.param_list()[1], "i32")?, + ) + .await; + match res { + Ok(tuple) => { + let return_list = { vec![] }; + Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) + } + Err(e) => Err(e), + } +} + +pub fn mudu_argv_desc_withdraw() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc +{ + static ARGV_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + ARGV_DESC.get_or_init(|| { + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "user_id".to_string(), + ::dat_type().clone(), + ), + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "amount".to_string(), + ::dat_type().clone(), + ), + ]) + }) +} + +pub fn mudu_result_desc_withdraw( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static RESULT_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + RESULT_DESC + .get_or_init(|| ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![])) +} + +pub fn mudu_proc_desc_withdraw() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { + static _PROC_DESC: std::sync::OnceLock<::mudu_contract::procedure::proc_desc::ProcDesc> = + std::sync::OnceLock::new(); + _PROC_DESC.get_or_init(|| { + ::mudu_contract::procedure::proc_desc::ProcDesc::new( + "wallet".to_string(), + "withdraw".to_string(), + mudu_argv_desc_withdraw().clone(), + mudu_result_desc_withdraw().clone(), + false, + ) + }) +} + +mod mod_withdraw { + wit_bindgen::generate!({ + inline: + r##"package mudu:mp2-withdraw; + world mudu-app-mp2-withdraw { + export mp2-withdraw: func(param:list) -> list; + } + "##, + async: true + }); + + #[allow(non_camel_case_types)] + #[allow(unused)] + struct GuestWithdraw {} + + impl Guest for GuestWithdraw { + async fn mp2_withdraw(param: Vec) -> Vec { + super::mp2_withdraw(param).await + } + } + + export!(GuestWithdraw); +} +async fn mp2_transfer(param: Vec) -> Vec { + ::mudu_binding::procedure::procedure_invoke::invoke_procedure_async( + param, + mudu_inner_p2_transfer, + ) + .await +} + +pub async fn mudu_inner_p2_transfer( + param: ::mudu_contract::procedure::procedure_param::ProcedureParam, +) -> ::mudu::common::result::RS<::mudu_contract::procedure::procedure_result::ProcedureResult> { + let return_desc = mudu_result_desc_transfer().clone(); + let res = transfer( + param.session_id(), + ::mudu_type::datum::value_to_typed::(¶m.param_list()[0], "i32")?, + ::mudu_type::datum::value_to_typed::(¶m.param_list()[1], "i32")?, + ::mudu_type::datum::value_to_typed::(¶m.param_list()[2], "i32")?, + ) + .await; + match res { + Ok(tuple) => { + let return_list = { vec![] }; + Ok(::mudu_contract::procedure::procedure_result::ProcedureResult::new(return_list)) + } + Err(e) => Err(e), + } +} + +pub fn mudu_argv_desc_transfer() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc +{ + static ARGV_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + ARGV_DESC.get_or_init(|| { + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![ + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "from_user_id".to_string(), + ::dat_type().clone(), + ), + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "to_user_id".to_string(), + ::dat_type().clone(), + ), + ::mudu_contract::tuple::datum_desc::DatumDesc::new( + "amount".to_string(), + ::dat_type().clone(), + ), + ]) + }) +} + +pub fn mudu_result_desc_transfer( +) -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { + static RESULT_DESC: std::sync::OnceLock< + ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc, + > = std::sync::OnceLock::new(); + RESULT_DESC + .get_or_init(|| ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc::new(vec![])) +} + +pub fn mudu_proc_desc_transfer() -> &'static ::mudu_contract::procedure::proc_desc::ProcDesc { + static _PROC_DESC: std::sync::OnceLock<::mudu_contract::procedure::proc_desc::ProcDesc> = + std::sync::OnceLock::new(); + _PROC_DESC.get_or_init(|| { + ::mudu_contract::procedure::proc_desc::ProcDesc::new( + "wallet".to_string(), + "transfer".to_string(), + mudu_argv_desc_transfer().clone(), + mudu_result_desc_transfer().clone(), + false, + ) + }) +} + +mod mod_transfer { + wit_bindgen::generate!({ + inline: + r##"package mudu:mp2-transfer; + world mudu-app-mp2-transfer { + export mp2-transfer: func(param:list) -> list; + } + "##, + async: true + }); + + #[allow(non_camel_case_types)] + #[allow(unused)] + struct GuestTransfer {} + + impl Guest for GuestTransfer { + async fn mp2_transfer(param: Vec) -> Vec { + super::mp2_transfer(param).await + } + } + + export!(GuestTransfer); +} diff --git a/example/wallet/src/generated/transactions.rs b/example/wallet/src/generated/transactions.rs index 947cc0f..62c52ff 100644 --- a/example/wallet/src/generated/transactions.rs +++ b/example/wallet/src/generated/transactions.rs @@ -1,690 +1,640 @@ pub mod object { -use lazy_static::lazy_static; -use mudu::common::result::RS; -use mudu_type::dat_binary::DatBinary; -use mudu_type::dat_textual::DatTextual; -use mudu_type::dat_type::DatType; -use mudu_type::dat_type_id::DatTypeID; -use mudu_type::dat_value::DatValue; -use mudu_type::datum::{Datum, DatumDyn}; -use mudu_contract::database::attr_field_access; -use mudu_contract::database::attr_value::AttrValue; -use mudu_contract::database::entity::Entity; -use mudu_contract::database::entity_utils; -use mudu_contract::tuple::datum_desc::DatumDesc; -use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; -use mudu_contract::tuple::tuple_datum::TupleDatumMarker; -use mudu_contract::database::sql_params::SQLParamMarker; - -// constant definition -const TRANSACTIONS:&str = "transactions"; - -const TRANS_ID:&str = "trans_id"; - -const TRANS_TYPE:&str = "trans_type"; - -const FROM_USER:&str = "from_user"; - -const TO_USER:&str = "to_user"; - -const AMOUNT:&str = "amount"; - -const CREATED_AT:&str = "created_at"; - - -// entity struct definition -#[derive(Debug, Clone, Default)] -pub struct Transactions { - - trans_id: AttrTransId, - - trans_type: AttrTransType, - - from_user: AttrFromUser, - - to_user: AttrToUser, - - amount: AttrAmount, - - created_at: AttrCreatedAt, - -} + use lazy_static::lazy_static; + use mudu::common::result::RS; + use mudu_contract::database::attr_field_access; + use mudu_contract::database::attr_value::AttrValue; + use mudu_contract::database::entity::Entity; + use mudu_contract::database::entity_utils; + use mudu_contract::database::sql_params::SQLParamMarker; + use mudu_contract::tuple::datum_desc::DatumDesc; + use mudu_contract::tuple::tuple_datum::TupleDatumMarker; + use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; + use mudu_type::dat_binary::DatBinary; + use mudu_type::dat_textual::DatTextual; + use mudu_type::dat_type::DatType; + use mudu_type::dat_type_id::DatTypeID; + use mudu_type::dat_value::DatValue; + use mudu_type::datum::{Datum, DatumDyn}; -impl TupleDatumMarker for Transactions {} - -impl SQLParamMarker for Transactions {} - -impl Transactions { - pub fn new( - trans_id: Option, - trans_type: Option, - from_user: Option, - to_user: Option, - amount: Option, - created_at: Option, - - ) -> Self { - let s = Self { - - trans_id : AttrTransId::from(trans_id), - - trans_type : AttrTransType::from(trans_type), - - from_user : AttrFromUser::from(from_user), - - to_user : AttrToUser::from(to_user), - - amount : AttrAmount::from(amount), - - created_at : AttrCreatedAt::from(created_at), - - }; - s - } + // constant definition + const TRANSACTIONS: &str = "transactions"; - pub fn new_empty() -> Self { - Self::default() - } + const TRANS_ID: &str = "trans_id"; - - pub fn set_trans_id( - &mut self, - trans_id: String, - ) { - self.trans_id.update(trans_id) - } + const TRANS_TYPE: &str = "trans_type"; - pub fn get_trans_id( - &self, - ) -> &Option { - self.trans_id.get() - } - - pub fn set_trans_type( - &mut self, - trans_type: String, - ) { - self.trans_type.update(trans_type) - } + const FROM_USER: &str = "from_user"; - pub fn get_trans_type( - &self, - ) -> &Option { - self.trans_type.get() - } - - pub fn set_from_user( - &mut self, - from_user: i32, - ) { - self.from_user.update(from_user) - } + const TO_USER: &str = "to_user"; - pub fn get_from_user( - &self, - ) -> &Option { - self.from_user.get() - } - - pub fn set_to_user( - &mut self, - to_user: i32, - ) { - self.to_user.update(to_user) - } + const AMOUNT: &str = "amount"; - pub fn get_to_user( - &self, - ) -> &Option { - self.to_user.get() - } - - pub fn set_amount( - &mut self, - amount: i32, - ) { - self.amount.update(amount) - } + const CREATED_AT: &str = "created_at"; - pub fn get_amount( - &self, - ) -> &Option { - self.amount.get() - } - - pub fn set_created_at( - &mut self, - created_at: i32, - ) { - self.created_at.update(created_at) - } + // entity struct definition + #[derive(Debug, Clone, Default)] + pub struct Transactions { + trans_id: AttrTransId, + + trans_type: AttrTransType, - pub fn get_created_at( - &self, - ) -> &Option { - self.created_at.get() + from_user: AttrFromUser, + + to_user: AttrToUser, + + amount: AttrAmount, + + created_at: AttrCreatedAt, } - -} -impl Datum for Transactions { - fn dat_type() -> &'static DatType { - lazy_static! { - static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); + impl TupleDatumMarker for Transactions {} + + impl SQLParamMarker for Transactions {} + + impl Transactions { + pub fn new( + trans_id: Option, + trans_type: Option, + from_user: Option, + to_user: Option, + amount: Option, + created_at: Option, + ) -> Self { + let s = Self { + trans_id: AttrTransId::from(trans_id), + + trans_type: AttrTransType::from(trans_type), + + from_user: AttrFromUser::from(from_user), + + to_user: AttrToUser::from(to_user), + + amount: AttrAmount::from(amount), + + created_at: AttrCreatedAt::from(created_at), + }; + s } - &DAT_TYPE - } - fn from_binary(binary: &[u8]) -> RS { - entity_utils::entity_from_binary(binary) - } + pub fn new_empty() -> Self { + Self::default() + } - fn from_value(value: &DatValue) -> RS { - entity_utils::entity_from_value(value) - } + pub fn set_trans_id(&mut self, trans_id: String) { + self.trans_id.update(trans_id) + } - fn from_textual(textual: &str) -> RS { - entity_utils::entity_from_textual(textual) - } -} + pub fn get_trans_id(&self) -> &Option { + self.trans_id.get() + } -impl DatumDyn for Transactions { - fn dat_type_id(&self) -> RS { - entity_utils::entity_dat_type_id() - } + pub fn set_trans_type(&mut self, trans_type: String) { + self.trans_type.update(trans_type) + } - fn to_binary(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_binary(self, dat_type) - } + pub fn get_trans_type(&self) -> &Option { + self.trans_type.get() + } - fn to_textual(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_textual(self, dat_type) - } + pub fn set_from_user(&mut self, from_user: i32) { + self.from_user.update(from_user) + } - fn to_value(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_value(self, dat_type) - } + pub fn get_from_user(&self) -> &Option { + self.from_user.get() + } - fn clone_boxed(&self) -> Box { - entity_utils::entity_clone_boxed(self) - } -} + pub fn set_to_user(&mut self, to_user: i32) { + self.to_user.update(to_user) + } -impl Entity for Transactions { - fn new_empty() -> Self { - Self::new_empty() - } + pub fn get_to_user(&self) -> &Option { + self.to_user.get() + } - fn tuple_desc() -> &'static TupleFieldDesc { - lazy_static! { - static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ - - AttrTransId::datum_desc().clone(), - - AttrTransType::datum_desc().clone(), - - AttrFromUser::datum_desc().clone(), - - AttrToUser::datum_desc().clone(), - - AttrAmount::datum_desc().clone(), - - AttrCreatedAt::datum_desc().clone(), - - ]); - } - &TUPLE_DESC - } + pub fn set_amount(&mut self, amount: i32) { + self.amount.update(amount) + } - fn object_name() -> &'static str { - TRANSACTIONS - } + pub fn get_amount(&self) -> &Option { + self.amount.get() + } - fn get_field_binary(&self, field: &str) -> RS>> { - match field { - - TRANS_ID => { - attr_field_access::attr_get_binary::<_>(self.trans_id.get()) - } - - TRANS_TYPE => { - attr_field_access::attr_get_binary::<_>(self.trans_type.get()) - } - - FROM_USER => { - attr_field_access::attr_get_binary::<_>(self.from_user.get()) - } - - TO_USER => { - attr_field_access::attr_get_binary::<_>(self.to_user.get()) - } - - AMOUNT => { - attr_field_access::attr_get_binary::<_>(self.amount.get()) - } - - CREATED_AT => { - attr_field_access::attr_get_binary::<_>(self.created_at.get()) - } - - _ => { panic!("unknown name"); } + pub fn set_created_at(&mut self, created_at: i32) { + self.created_at.update(created_at) + } + + pub fn get_created_at(&self) -> &Option { + self.created_at.get() } } - fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { - match field { - - TRANS_ID => { - attr_field_access::attr_set_binary::<_, _>(self.trans_id.get_mut(), binary.as_ref())?; - } - - TRANS_TYPE => { - attr_field_access::attr_set_binary::<_, _>(self.trans_type.get_mut(), binary.as_ref())?; - } - - FROM_USER => { - attr_field_access::attr_set_binary::<_, _>(self.from_user.get_mut(), binary.as_ref())?; - } - - TO_USER => { - attr_field_access::attr_set_binary::<_, _>(self.to_user.get_mut(), binary.as_ref())?; - } - - AMOUNT => { - attr_field_access::attr_set_binary::<_, _>(self.amount.get_mut(), binary.as_ref())?; - } - - CREATED_AT => { - attr_field_access::attr_set_binary::<_, _>(self.created_at.get_mut(), binary.as_ref())?; + impl Datum for Transactions { + fn dat_type() -> &'static DatType { + lazy_static! { + static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); } - - _ => { panic!("unknown name"); } + &DAT_TYPE + } + + fn from_binary(binary: &[u8]) -> RS { + entity_utils::entity_from_binary(binary) + } + + fn from_value(value: &DatValue) -> RS { + entity_utils::entity_from_value(value) + } + + fn from_textual(textual: &str) -> RS { + entity_utils::entity_from_textual(textual) } - Ok(()) } - fn get_field_value(&self, field: &str) -> RS> { - match field { - - TRANS_ID => { - attr_field_access::attr_get_value::<_>(self.trans_id.get()) - } - - TRANS_TYPE => { - attr_field_access::attr_get_value::<_>(self.trans_type.get()) - } - - FROM_USER => { - attr_field_access::attr_get_value::<_>(self.from_user.get()) - } - - TO_USER => { - attr_field_access::attr_get_value::<_>(self.to_user.get()) - } - - AMOUNT => { - attr_field_access::attr_get_value::<_>(self.amount.get()) - } - - CREATED_AT => { - attr_field_access::attr_get_value::<_>(self.created_at.get()) - } - - _ => { panic!("unknown name"); } + impl DatumDyn for Transactions { + fn dat_type_id(&self) -> RS { + entity_utils::entity_dat_type_id() + } + + fn to_binary(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_binary(self, dat_type) + } + + fn to_textual(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_textual(self, dat_type) + } + + fn to_value(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_value(self, dat_type) + } + + fn clone_boxed(&self) -> Box { + entity_utils::entity_clone_boxed(self) } } - fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { - match field { - - TRANS_ID => { - attr_field_access::attr_set_value::<_, _>(self.trans_id.get_mut(), value)?; - } - - TRANS_TYPE => { - attr_field_access::attr_set_value::<_, _>(self.trans_type.get_mut(), value)?; - } - - FROM_USER => { - attr_field_access::attr_set_value::<_, _>(self.from_user.get_mut(), value)?; + impl Entity for Transactions { + fn new_empty() -> Self { + Self::new_empty() + } + + fn tuple_desc() -> &'static TupleFieldDesc { + lazy_static! { + static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ + AttrTransId::datum_desc().clone(), + AttrTransType::datum_desc().clone(), + AttrFromUser::datum_desc().clone(), + AttrToUser::datum_desc().clone(), + AttrAmount::datum_desc().clone(), + AttrCreatedAt::datum_desc().clone(), + ]); } - - TO_USER => { - attr_field_access::attr_set_value::<_, _>(self.to_user.get_mut(), value)?; + &TUPLE_DESC + } + + fn object_name() -> &'static str { + TRANSACTIONS + } + + fn get_field_binary(&self, field: &str) -> RS>> { + match field { + TRANS_ID => attr_field_access::attr_get_binary::<_>(self.trans_id.get()), + + TRANS_TYPE => attr_field_access::attr_get_binary::<_>(self.trans_type.get()), + + FROM_USER => attr_field_access::attr_get_binary::<_>(self.from_user.get()), + + TO_USER => attr_field_access::attr_get_binary::<_>(self.to_user.get()), + + AMOUNT => attr_field_access::attr_get_binary::<_>(self.amount.get()), + + CREATED_AT => attr_field_access::attr_get_binary::<_>(self.created_at.get()), + + _ => { + panic!("unknown name"); + } } - - AMOUNT => { - attr_field_access::attr_set_value::<_, _>(self.amount.get_mut(), value)?; + } + + fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { + match field { + TRANS_ID => { + attr_field_access::attr_set_binary::<_, _>( + self.trans_id.get_mut(), + binary.as_ref(), + )?; + } + + TRANS_TYPE => { + attr_field_access::attr_set_binary::<_, _>( + self.trans_type.get_mut(), + binary.as_ref(), + )?; + } + + FROM_USER => { + attr_field_access::attr_set_binary::<_, _>( + self.from_user.get_mut(), + binary.as_ref(), + )?; + } + + TO_USER => { + attr_field_access::attr_set_binary::<_, _>( + self.to_user.get_mut(), + binary.as_ref(), + )?; + } + + AMOUNT => { + attr_field_access::attr_set_binary::<_, _>( + self.amount.get_mut(), + binary.as_ref(), + )?; + } + + CREATED_AT => { + attr_field_access::attr_set_binary::<_, _>( + self.created_at.get_mut(), + binary.as_ref(), + )?; + } + + _ => { + panic!("unknown name"); + } } - - CREATED_AT => { - attr_field_access::attr_set_value::<_, _>(self.created_at.get_mut(), value)?; + Ok(()) + } + + fn get_field_value(&self, field: &str) -> RS> { + match field { + TRANS_ID => attr_field_access::attr_get_value::<_>(self.trans_id.get()), + + TRANS_TYPE => attr_field_access::attr_get_value::<_>(self.trans_type.get()), + + FROM_USER => attr_field_access::attr_get_value::<_>(self.from_user.get()), + + TO_USER => attr_field_access::attr_get_value::<_>(self.to_user.get()), + + AMOUNT => attr_field_access::attr_get_value::<_>(self.amount.get()), + + CREATED_AT => attr_field_access::attr_get_value::<_>(self.created_at.get()), + + _ => { + panic!("unknown name"); + } } - - _ => { panic!("unknown name"); } } - Ok(()) - } -} + fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { + match field { + TRANS_ID => { + attr_field_access::attr_set_value::<_, _>(self.trans_id.get_mut(), value)?; + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrTransId { - is_dirty:bool, - value: Option -} + TRANS_TYPE => { + attr_field_access::attr_set_value::<_, _>(self.trans_type.get_mut(), value)?; + } + + FROM_USER => { + attr_field_access::attr_set_value::<_, _>(self.from_user.get_mut(), value)?; + } + + TO_USER => { + attr_field_access::attr_set_value::<_, _>(self.to_user.get_mut(), value)?; + } -impl AttrTransId { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + AMOUNT => { + attr_field_access::attr_set_value::<_, _>(self.amount.get_mut(), value)?; + } + + CREATED_AT => { + attr_field_access::attr_set_value::<_, _>(self.created_at.get_mut(), value)?; + } + + _ => { + panic!("unknown name"); + } + } + Ok(()) } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrTransId { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrTransId { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: String) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrTransId { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: String) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - TRANSACTIONS - } + impl AttrValue for AttrTransId { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - TRANS_ID - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrTransType { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + TRANSACTIONS + } -impl AttrTransType { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + TRANS_ID } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrTransType { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrTransType { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: String) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrTransType { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: String) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - TRANSACTIONS - } + impl AttrValue for AttrTransType { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - TRANS_TYPE - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrFromUser { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + TRANSACTIONS + } -impl AttrFromUser { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + TRANS_TYPE } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrFromUser { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrFromUser { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrFromUser { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - TRANSACTIONS - } + impl AttrValue for AttrFromUser { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - FROM_USER - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrToUser { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + TRANSACTIONS + } -impl AttrToUser { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + FROM_USER } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrToUser { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrToUser { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrToUser { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - TRANSACTIONS - } + impl AttrValue for AttrToUser { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - TO_USER - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrAmount { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + TRANSACTIONS + } -impl AttrAmount { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + TO_USER } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrAmount { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrAmount { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrAmount { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - TRANSACTIONS - } + impl AttrValue for AttrAmount { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - AMOUNT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrCreatedAt { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + TRANSACTIONS + } -impl AttrCreatedAt { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + AMOUNT } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrCreatedAt { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrCreatedAt { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrCreatedAt { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - TRANSACTIONS - } + impl AttrValue for AttrCreatedAt { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - CREATED_AT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } + fn object_name() -> &'static str { + TRANSACTIONS + } -} \ No newline at end of file + fn attr_name() -> &'static str { + CREATED_AT + } + } +} diff --git a/example/wallet/src/generated/users.rs b/example/wallet/src/generated/users.rs index 0f02ddc..a007e74 100644 --- a/example/wallet/src/generated/users.rs +++ b/example/wallet/src/generated/users.rs @@ -1,781 +1,724 @@ pub mod object { -use lazy_static::lazy_static; -use mudu::common::result::RS; -use mudu_type::dat_binary::DatBinary; -use mudu_type::dat_textual::DatTextual; -use mudu_type::dat_type::DatType; -use mudu_type::dat_type_id::DatTypeID; -use mudu_type::dat_value::DatValue; -use mudu_type::datum::{Datum, DatumDyn}; -use mudu_contract::database::attr_field_access; -use mudu_contract::database::attr_value::AttrValue; -use mudu_contract::database::entity::Entity; -use mudu_contract::database::entity_utils; -use mudu_contract::tuple::datum_desc::DatumDesc; -use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; -use mudu_contract::tuple::tuple_datum::TupleDatumMarker; -use mudu_contract::database::sql_params::SQLParamMarker; - -// constant definition -const USERS:&str = "users"; - -const USER_ID:&str = "user_id"; - -const NAME:&str = "name"; - -const PHONE:&str = "phone"; - -const EMAIL:&str = "email"; - -const PASSWORD:&str = "password"; - -const CREATED_AT:&str = "created_at"; - -const UPDATED_AT:&str = "updated_at"; - - -// entity struct definition -#[derive(Debug, Clone, Default)] -pub struct Users { - - user_id: AttrUserId, - - name: AttrName, - - phone: AttrPhone, - - email: AttrEmail, - - password: AttrPassword, - - created_at: AttrCreatedAt, - - updated_at: AttrUpdatedAt, - -} + use lazy_static::lazy_static; + use mudu::common::result::RS; + use mudu_contract::database::attr_field_access; + use mudu_contract::database::attr_value::AttrValue; + use mudu_contract::database::entity::Entity; + use mudu_contract::database::entity_utils; + use mudu_contract::database::sql_params::SQLParamMarker; + use mudu_contract::tuple::datum_desc::DatumDesc; + use mudu_contract::tuple::tuple_datum::TupleDatumMarker; + use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; + use mudu_type::dat_binary::DatBinary; + use mudu_type::dat_textual::DatTextual; + use mudu_type::dat_type::DatType; + use mudu_type::dat_type_id::DatTypeID; + use mudu_type::dat_value::DatValue; + use mudu_type::datum::{Datum, DatumDyn}; -impl TupleDatumMarker for Users {} - -impl SQLParamMarker for Users {} - -impl Users { - pub fn new( - user_id: Option, - name: Option, - phone: Option, - email: Option, - password: Option, - created_at: Option, - updated_at: Option, - - ) -> Self { - let s = Self { - - user_id : AttrUserId::from(user_id), - - name : AttrName::from(name), - - phone : AttrPhone::from(phone), - - email : AttrEmail::from(email), - - password : AttrPassword::from(password), - - created_at : AttrCreatedAt::from(created_at), - - updated_at : AttrUpdatedAt::from(updated_at), - - }; - s - } + // constant definition + const USERS: &str = "users"; - pub fn new_empty() -> Self { - Self::default() - } + const USER_ID: &str = "user_id"; - - pub fn set_user_id( - &mut self, - user_id: i32, - ) { - self.user_id.update(user_id) - } + const NAME: &str = "name"; - pub fn get_user_id( - &self, - ) -> &Option { - self.user_id.get() - } - - pub fn set_name( - &mut self, - name: String, - ) { - self.name.update(name) - } + const PHONE: &str = "phone"; - pub fn get_name( - &self, - ) -> &Option { - self.name.get() - } - - pub fn set_phone( - &mut self, - phone: String, - ) { - self.phone.update(phone) - } + const EMAIL: &str = "email"; - pub fn get_phone( - &self, - ) -> &Option { - self.phone.get() - } - - pub fn set_email( - &mut self, - email: String, - ) { - self.email.update(email) - } + const PASSWORD: &str = "password"; - pub fn get_email( - &self, - ) -> &Option { - self.email.get() - } - - pub fn set_password( - &mut self, - password: String, - ) { - self.password.update(password) - } + const CREATED_AT: &str = "created_at"; - pub fn get_password( - &self, - ) -> &Option { - self.password.get() - } - - pub fn set_created_at( - &mut self, - created_at: i32, - ) { - self.created_at.update(created_at) - } + const UPDATED_AT: &str = "updated_at"; - pub fn get_created_at( - &self, - ) -> &Option { - self.created_at.get() - } - - pub fn set_updated_at( - &mut self, - updated_at: i32, - ) { - self.updated_at.update(updated_at) - } + // entity struct definition + #[derive(Debug, Clone, Default)] + pub struct Users { + user_id: AttrUserId, + + name: AttrName, - pub fn get_updated_at( - &self, - ) -> &Option { - self.updated_at.get() + phone: AttrPhone, + + email: AttrEmail, + + password: AttrPassword, + + created_at: AttrCreatedAt, + + updated_at: AttrUpdatedAt, } - -} -impl Datum for Users { - fn dat_type() -> &'static DatType { - lazy_static! { - static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); + impl TupleDatumMarker for Users {} + + impl SQLParamMarker for Users {} + + impl Users { + pub fn new( + user_id: Option, + name: Option, + phone: Option, + email: Option, + password: Option, + created_at: Option, + updated_at: Option, + ) -> Self { + let s = Self { + user_id: AttrUserId::from(user_id), + + name: AttrName::from(name), + + phone: AttrPhone::from(phone), + + email: AttrEmail::from(email), + + password: AttrPassword::from(password), + + created_at: AttrCreatedAt::from(created_at), + + updated_at: AttrUpdatedAt::from(updated_at), + }; + s } - &DAT_TYPE - } - fn from_binary(binary: &[u8]) -> RS { - entity_utils::entity_from_binary(binary) - } + pub fn new_empty() -> Self { + Self::default() + } - fn from_value(value: &DatValue) -> RS { - entity_utils::entity_from_value(value) - } + pub fn set_user_id(&mut self, user_id: i32) { + self.user_id.update(user_id) + } - fn from_textual(textual: &str) -> RS { - entity_utils::entity_from_textual(textual) - } -} + pub fn get_user_id(&self) -> &Option { + self.user_id.get() + } -impl DatumDyn for Users { - fn dat_type_id(&self) -> RS { - entity_utils::entity_dat_type_id() - } + pub fn set_name(&mut self, name: String) { + self.name.update(name) + } - fn to_binary(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_binary(self, dat_type) - } + pub fn get_name(&self) -> &Option { + self.name.get() + } - fn to_textual(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_textual(self, dat_type) - } + pub fn set_phone(&mut self, phone: String) { + self.phone.update(phone) + } - fn to_value(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_value(self, dat_type) - } + pub fn get_phone(&self) -> &Option { + self.phone.get() + } - fn clone_boxed(&self) -> Box { - entity_utils::entity_clone_boxed(self) - } -} + pub fn set_email(&mut self, email: String) { + self.email.update(email) + } -impl Entity for Users { - fn new_empty() -> Self { - Self::new_empty() - } + pub fn get_email(&self) -> &Option { + self.email.get() + } - fn tuple_desc() -> &'static TupleFieldDesc { - lazy_static! { - static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ - - AttrUserId::datum_desc().clone(), - - AttrName::datum_desc().clone(), - - AttrPhone::datum_desc().clone(), - - AttrEmail::datum_desc().clone(), - - AttrPassword::datum_desc().clone(), - - AttrCreatedAt::datum_desc().clone(), - - AttrUpdatedAt::datum_desc().clone(), - - ]); - } - &TUPLE_DESC - } + pub fn set_password(&mut self, password: String) { + self.password.update(password) + } - fn object_name() -> &'static str { - USERS - } + pub fn get_password(&self) -> &Option { + self.password.get() + } - fn get_field_binary(&self, field: &str) -> RS>> { - match field { - - USER_ID => { - attr_field_access::attr_get_binary::<_>(self.user_id.get()) - } - - NAME => { - attr_field_access::attr_get_binary::<_>(self.name.get()) - } - - PHONE => { - attr_field_access::attr_get_binary::<_>(self.phone.get()) - } - - EMAIL => { - attr_field_access::attr_get_binary::<_>(self.email.get()) - } - - PASSWORD => { - attr_field_access::attr_get_binary::<_>(self.password.get()) - } - - CREATED_AT => { - attr_field_access::attr_get_binary::<_>(self.created_at.get()) - } - - UPDATED_AT => { - attr_field_access::attr_get_binary::<_>(self.updated_at.get()) - } - - _ => { panic!("unknown name"); } + pub fn set_created_at(&mut self, created_at: i32) { + self.created_at.update(created_at) + } + + pub fn get_created_at(&self) -> &Option { + self.created_at.get() + } + + pub fn set_updated_at(&mut self, updated_at: i32) { + self.updated_at.update(updated_at) + } + + pub fn get_updated_at(&self) -> &Option { + self.updated_at.get() } } - fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { - match field { - - USER_ID => { - attr_field_access::attr_set_binary::<_, _>(self.user_id.get_mut(), binary.as_ref())?; - } - - NAME => { - attr_field_access::attr_set_binary::<_, _>(self.name.get_mut(), binary.as_ref())?; - } - - PHONE => { - attr_field_access::attr_set_binary::<_, _>(self.phone.get_mut(), binary.as_ref())?; - } - - EMAIL => { - attr_field_access::attr_set_binary::<_, _>(self.email.get_mut(), binary.as_ref())?; - } - - PASSWORD => { - attr_field_access::attr_set_binary::<_, _>(self.password.get_mut(), binary.as_ref())?; - } - - CREATED_AT => { - attr_field_access::attr_set_binary::<_, _>(self.created_at.get_mut(), binary.as_ref())?; - } - - UPDATED_AT => { - attr_field_access::attr_set_binary::<_, _>(self.updated_at.get_mut(), binary.as_ref())?; + impl Datum for Users { + fn dat_type() -> &'static DatType { + lazy_static! { + static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); } - - _ => { panic!("unknown name"); } + &DAT_TYPE + } + + fn from_binary(binary: &[u8]) -> RS { + entity_utils::entity_from_binary(binary) + } + + fn from_value(value: &DatValue) -> RS { + entity_utils::entity_from_value(value) + } + + fn from_textual(textual: &str) -> RS { + entity_utils::entity_from_textual(textual) } - Ok(()) } - fn get_field_value(&self, field: &str) -> RS> { - match field { - - USER_ID => { - attr_field_access::attr_get_value::<_>(self.user_id.get()) - } - - NAME => { - attr_field_access::attr_get_value::<_>(self.name.get()) - } - - PHONE => { - attr_field_access::attr_get_value::<_>(self.phone.get()) - } - - EMAIL => { - attr_field_access::attr_get_value::<_>(self.email.get()) - } - - PASSWORD => { - attr_field_access::attr_get_value::<_>(self.password.get()) - } - - CREATED_AT => { - attr_field_access::attr_get_value::<_>(self.created_at.get()) - } - - UPDATED_AT => { - attr_field_access::attr_get_value::<_>(self.updated_at.get()) - } - - _ => { panic!("unknown name"); } + impl DatumDyn for Users { + fn dat_type_id(&self) -> RS { + entity_utils::entity_dat_type_id() + } + + fn to_binary(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_binary(self, dat_type) + } + + fn to_textual(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_textual(self, dat_type) + } + + fn to_value(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_value(self, dat_type) + } + + fn clone_boxed(&self) -> Box { + entity_utils::entity_clone_boxed(self) } } - fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { - match field { - - USER_ID => { - attr_field_access::attr_set_value::<_, _>(self.user_id.get_mut(), value)?; - } - - NAME => { - attr_field_access::attr_set_value::<_, _>(self.name.get_mut(), value)?; - } - - PHONE => { - attr_field_access::attr_set_value::<_, _>(self.phone.get_mut(), value)?; - } - - EMAIL => { - attr_field_access::attr_set_value::<_, _>(self.email.get_mut(), value)?; + impl Entity for Users { + fn new_empty() -> Self { + Self::new_empty() + } + + fn tuple_desc() -> &'static TupleFieldDesc { + lazy_static! { + static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ + AttrUserId::datum_desc().clone(), + AttrName::datum_desc().clone(), + AttrPhone::datum_desc().clone(), + AttrEmail::datum_desc().clone(), + AttrPassword::datum_desc().clone(), + AttrCreatedAt::datum_desc().clone(), + AttrUpdatedAt::datum_desc().clone(), + ]); } - - PASSWORD => { - attr_field_access::attr_set_value::<_, _>(self.password.get_mut(), value)?; + &TUPLE_DESC + } + + fn object_name() -> &'static str { + USERS + } + + fn get_field_binary(&self, field: &str) -> RS>> { + match field { + USER_ID => attr_field_access::attr_get_binary::<_>(self.user_id.get()), + + NAME => attr_field_access::attr_get_binary::<_>(self.name.get()), + + PHONE => attr_field_access::attr_get_binary::<_>(self.phone.get()), + + EMAIL => attr_field_access::attr_get_binary::<_>(self.email.get()), + + PASSWORD => attr_field_access::attr_get_binary::<_>(self.password.get()), + + CREATED_AT => attr_field_access::attr_get_binary::<_>(self.created_at.get()), + + UPDATED_AT => attr_field_access::attr_get_binary::<_>(self.updated_at.get()), + + _ => { + panic!("unknown name"); + } } - - CREATED_AT => { - attr_field_access::attr_set_value::<_, _>(self.created_at.get_mut(), value)?; + } + + fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { + match field { + USER_ID => { + attr_field_access::attr_set_binary::<_, _>( + self.user_id.get_mut(), + binary.as_ref(), + )?; + } + + NAME => { + attr_field_access::attr_set_binary::<_, _>( + self.name.get_mut(), + binary.as_ref(), + )?; + } + + PHONE => { + attr_field_access::attr_set_binary::<_, _>( + self.phone.get_mut(), + binary.as_ref(), + )?; + } + + EMAIL => { + attr_field_access::attr_set_binary::<_, _>( + self.email.get_mut(), + binary.as_ref(), + )?; + } + + PASSWORD => { + attr_field_access::attr_set_binary::<_, _>( + self.password.get_mut(), + binary.as_ref(), + )?; + } + + CREATED_AT => { + attr_field_access::attr_set_binary::<_, _>( + self.created_at.get_mut(), + binary.as_ref(), + )?; + } + + UPDATED_AT => { + attr_field_access::attr_set_binary::<_, _>( + self.updated_at.get_mut(), + binary.as_ref(), + )?; + } + + _ => { + panic!("unknown name"); + } } - - UPDATED_AT => { - attr_field_access::attr_set_value::<_, _>(self.updated_at.get_mut(), value)?; + Ok(()) + } + + fn get_field_value(&self, field: &str) -> RS> { + match field { + USER_ID => attr_field_access::attr_get_value::<_>(self.user_id.get()), + + NAME => attr_field_access::attr_get_value::<_>(self.name.get()), + + PHONE => attr_field_access::attr_get_value::<_>(self.phone.get()), + + EMAIL => attr_field_access::attr_get_value::<_>(self.email.get()), + + PASSWORD => attr_field_access::attr_get_value::<_>(self.password.get()), + + CREATED_AT => attr_field_access::attr_get_value::<_>(self.created_at.get()), + + UPDATED_AT => attr_field_access::attr_get_value::<_>(self.updated_at.get()), + + _ => { + panic!("unknown name"); + } } - - _ => { panic!("unknown name"); } } - Ok(()) - } -} + fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { + match field { + USER_ID => { + attr_field_access::attr_set_value::<_, _>(self.user_id.get_mut(), value)?; + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrUserId { - is_dirty:bool, - value: Option -} + NAME => { + attr_field_access::attr_set_value::<_, _>(self.name.get_mut(), value)?; + } + + PHONE => { + attr_field_access::attr_set_value::<_, _>(self.phone.get_mut(), value)?; + } + + EMAIL => { + attr_field_access::attr_set_value::<_, _>(self.email.get_mut(), value)?; + } + + PASSWORD => { + attr_field_access::attr_set_value::<_, _>(self.password.get_mut(), value)?; + } + + CREATED_AT => { + attr_field_access::attr_set_value::<_, _>(self.created_at.get_mut(), value)?; + } + + UPDATED_AT => { + attr_field_access::attr_set_value::<_, _>(self.updated_at.get_mut(), value)?; + } -impl AttrUserId { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + _ => { + panic!("unknown name"); + } + } + Ok(()) } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrUserId { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrUserId { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrUserId { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrUserId { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - USER_ID - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrName { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + USERS + } -impl AttrName { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + USER_ID } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrName { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrName { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: String) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrName { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: String) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrName { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - NAME - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrPhone { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + USERS + } -impl AttrPhone { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + NAME } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrPhone { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrPhone { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: String) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrPhone { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: String) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrPhone { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - PHONE - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrEmail { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + USERS + } -impl AttrEmail { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + PHONE } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrEmail { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrEmail { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: String) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrEmail { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: String) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrEmail { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - EMAIL - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrPassword { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + USERS + } -impl AttrPassword { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + EMAIL } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrPassword { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrPassword { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: String) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrPassword { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: String) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrPassword { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - PASSWORD - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrCreatedAt { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + USERS + } -impl AttrCreatedAt { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + PASSWORD } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrCreatedAt { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrCreatedAt { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrCreatedAt { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrCreatedAt { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - CREATED_AT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrUpdatedAt { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + USERS + } -impl AttrUpdatedAt { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + CREATED_AT } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrUpdatedAt { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrUpdatedAt { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrUpdatedAt { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrUpdatedAt { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - UPDATED_AT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } + fn object_name() -> &'static str { + USERS + } -} \ No newline at end of file + fn attr_name() -> &'static str { + UPDATED_AT + } + } +} diff --git a/example/wallet/src/generated/wallets.rs b/example/wallet/src/generated/wallets.rs index b0aa482..420d8d6 100644 --- a/example/wallet/src/generated/wallets.rs +++ b/example/wallet/src/generated/wallets.rs @@ -1,417 +1,384 @@ pub mod object { -use lazy_static::lazy_static; -use mudu::common::result::RS; -use mudu_type::dat_binary::DatBinary; -use mudu_type::dat_textual::DatTextual; -use mudu_type::dat_type::DatType; -use mudu_type::dat_type_id::DatTypeID; -use mudu_type::dat_value::DatValue; -use mudu_type::datum::{Datum, DatumDyn}; -use mudu_contract::database::attr_field_access; -use mudu_contract::database::attr_value::AttrValue; -use mudu_contract::database::entity::Entity; -use mudu_contract::database::entity_utils; -use mudu_contract::tuple::datum_desc::DatumDesc; -use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; -use mudu_contract::tuple::tuple_datum::TupleDatumMarker; -use mudu_contract::database::sql_params::SQLParamMarker; - -// constant definition -const WALLETS:&str = "wallets"; - -const USER_ID:&str = "user_id"; - -const BALANCE:&str = "balance"; - -const UPDATED_AT:&str = "updated_at"; - - -// entity struct definition -#[derive(Debug, Clone, Default)] -pub struct Wallets { - - user_id: AttrUserId, - - balance: AttrBalance, - - updated_at: AttrUpdatedAt, - -} + use lazy_static::lazy_static; + use mudu::common::result::RS; + use mudu_contract::database::attr_field_access; + use mudu_contract::database::attr_value::AttrValue; + use mudu_contract::database::entity::Entity; + use mudu_contract::database::entity_utils; + use mudu_contract::database::sql_params::SQLParamMarker; + use mudu_contract::tuple::datum_desc::DatumDesc; + use mudu_contract::tuple::tuple_datum::TupleDatumMarker; + use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; + use mudu_type::dat_binary::DatBinary; + use mudu_type::dat_textual::DatTextual; + use mudu_type::dat_type::DatType; + use mudu_type::dat_type_id::DatTypeID; + use mudu_type::dat_value::DatValue; + use mudu_type::datum::{Datum, DatumDyn}; -impl TupleDatumMarker for Wallets {} - -impl SQLParamMarker for Wallets {} - -impl Wallets { - pub fn new( - user_id: Option, - balance: Option, - updated_at: Option, - - ) -> Self { - let s = Self { - - user_id : AttrUserId::from(user_id), - - balance : AttrBalance::from(balance), - - updated_at : AttrUpdatedAt::from(updated_at), - - }; - s - } + // constant definition + const WALLETS: &str = "wallets"; - pub fn new_empty() -> Self { - Self::default() - } + const USER_ID: &str = "user_id"; - - pub fn set_user_id( - &mut self, - user_id: i32, - ) { - self.user_id.update(user_id) - } + const BALANCE: &str = "balance"; - pub fn get_user_id( - &self, - ) -> &Option { - self.user_id.get() - } - - pub fn set_balance( - &mut self, - balance: i32, - ) { - self.balance.update(balance) - } + const UPDATED_AT: &str = "updated_at"; - pub fn get_balance( - &self, - ) -> &Option { - self.balance.get() - } - - pub fn set_updated_at( - &mut self, - updated_at: i32, - ) { - self.updated_at.update(updated_at) - } + // entity struct definition + #[derive(Debug, Clone, Default)] + pub struct Wallets { + user_id: AttrUserId, - pub fn get_updated_at( - &self, - ) -> &Option { - self.updated_at.get() - } - -} + balance: AttrBalance, -impl Datum for Wallets { - fn dat_type() -> &'static DatType { - lazy_static! { - static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); - } - &DAT_TYPE + updated_at: AttrUpdatedAt, } - fn from_binary(binary: &[u8]) -> RS { - entity_utils::entity_from_binary(binary) - } + impl TupleDatumMarker for Wallets {} - fn from_value(value: &DatValue) -> RS { - entity_utils::entity_from_value(value) - } + impl SQLParamMarker for Wallets {} - fn from_textual(textual: &str) -> RS { - entity_utils::entity_from_textual(textual) - } -} + impl Wallets { + pub fn new(user_id: Option, balance: Option, updated_at: Option) -> Self { + let s = Self { + user_id: AttrUserId::from(user_id), -impl DatumDyn for Wallets { - fn dat_type_id(&self) -> RS { - entity_utils::entity_dat_type_id() - } + balance: AttrBalance::from(balance), - fn to_binary(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_binary(self, dat_type) - } + updated_at: AttrUpdatedAt::from(updated_at), + }; + s + } - fn to_textual(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_textual(self, dat_type) - } + pub fn new_empty() -> Self { + Self::default() + } - fn to_value(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_value(self, dat_type) - } + pub fn set_user_id(&mut self, user_id: i32) { + self.user_id.update(user_id) + } - fn clone_boxed(&self) -> Box { - entity_utils::entity_clone_boxed(self) - } -} + pub fn get_user_id(&self) -> &Option { + self.user_id.get() + } -impl Entity for Wallets { - fn new_empty() -> Self { - Self::new_empty() - } + pub fn set_balance(&mut self, balance: i32) { + self.balance.update(balance) + } - fn tuple_desc() -> &'static TupleFieldDesc { - lazy_static! { - static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ - - AttrUserId::datum_desc().clone(), - - AttrBalance::datum_desc().clone(), - - AttrUpdatedAt::datum_desc().clone(), - - ]); - } - &TUPLE_DESC - } + pub fn get_balance(&self) -> &Option { + self.balance.get() + } - fn object_name() -> &'static str { - WALLETS + pub fn set_updated_at(&mut self, updated_at: i32) { + self.updated_at.update(updated_at) + } + + pub fn get_updated_at(&self) -> &Option { + self.updated_at.get() + } } - fn get_field_binary(&self, field: &str) -> RS>> { - match field { - - USER_ID => { - attr_field_access::attr_get_binary::<_>(self.user_id.get()) - } - - BALANCE => { - attr_field_access::attr_get_binary::<_>(self.balance.get()) + impl Datum for Wallets { + fn dat_type() -> &'static DatType { + lazy_static! { + static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); } - - UPDATED_AT => { - attr_field_access::attr_get_binary::<_>(self.updated_at.get()) - } - - _ => { panic!("unknown name"); } + &DAT_TYPE + } + + fn from_binary(binary: &[u8]) -> RS { + entity_utils::entity_from_binary(binary) + } + + fn from_value(value: &DatValue) -> RS { + entity_utils::entity_from_value(value) + } + + fn from_textual(textual: &str) -> RS { + entity_utils::entity_from_textual(textual) } } - fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { - match field { - - USER_ID => { - attr_field_access::attr_set_binary::<_, _>(self.user_id.get_mut(), binary.as_ref())?; - } - - BALANCE => { - attr_field_access::attr_set_binary::<_, _>(self.balance.get_mut(), binary.as_ref())?; - } - - UPDATED_AT => { - attr_field_access::attr_set_binary::<_, _>(self.updated_at.get_mut(), binary.as_ref())?; - } - - _ => { panic!("unknown name"); } + impl DatumDyn for Wallets { + fn dat_type_id(&self) -> RS { + entity_utils::entity_dat_type_id() + } + + fn to_binary(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_binary(self, dat_type) + } + + fn to_textual(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_textual(self, dat_type) + } + + fn to_value(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_value(self, dat_type) + } + + fn clone_boxed(&self) -> Box { + entity_utils::entity_clone_boxed(self) } - Ok(()) } - fn get_field_value(&self, field: &str) -> RS> { - match field { - - USER_ID => { - attr_field_access::attr_get_value::<_>(self.user_id.get()) - } - - BALANCE => { - attr_field_access::attr_get_value::<_>(self.balance.get()) - } - - UPDATED_AT => { - attr_field_access::attr_get_value::<_>(self.updated_at.get()) + impl Entity for Wallets { + fn new_empty() -> Self { + Self::new_empty() + } + + fn tuple_desc() -> &'static TupleFieldDesc { + lazy_static! { + static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ + AttrUserId::datum_desc().clone(), + AttrBalance::datum_desc().clone(), + AttrUpdatedAt::datum_desc().clone(), + ]); } - - _ => { panic!("unknown name"); } + &TUPLE_DESC } - } - fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { - match field { - - USER_ID => { - attr_field_access::attr_set_value::<_, _>(self.user_id.get_mut(), value)?; + fn object_name() -> &'static str { + WALLETS + } + + fn get_field_binary(&self, field: &str) -> RS>> { + match field { + USER_ID => attr_field_access::attr_get_binary::<_>(self.user_id.get()), + + BALANCE => attr_field_access::attr_get_binary::<_>(self.balance.get()), + + UPDATED_AT => attr_field_access::attr_get_binary::<_>(self.updated_at.get()), + + _ => { + panic!("unknown name"); + } } - - BALANCE => { - attr_field_access::attr_set_value::<_, _>(self.balance.get_mut(), value)?; + } + + fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { + match field { + USER_ID => { + attr_field_access::attr_set_binary::<_, _>( + self.user_id.get_mut(), + binary.as_ref(), + )?; + } + + BALANCE => { + attr_field_access::attr_set_binary::<_, _>( + self.balance.get_mut(), + binary.as_ref(), + )?; + } + + UPDATED_AT => { + attr_field_access::attr_set_binary::<_, _>( + self.updated_at.get_mut(), + binary.as_ref(), + )?; + } + + _ => { + panic!("unknown name"); + } } - - UPDATED_AT => { - attr_field_access::attr_set_value::<_, _>(self.updated_at.get_mut(), value)?; + Ok(()) + } + + fn get_field_value(&self, field: &str) -> RS> { + match field { + USER_ID => attr_field_access::attr_get_value::<_>(self.user_id.get()), + + BALANCE => attr_field_access::attr_get_value::<_>(self.balance.get()), + + UPDATED_AT => attr_field_access::attr_get_value::<_>(self.updated_at.get()), + + _ => { + panic!("unknown name"); + } } - - _ => { panic!("unknown name"); } } - Ok(()) - } -} + fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { + match field { + USER_ID => { + attr_field_access::attr_set_value::<_, _>(self.user_id.get_mut(), value)?; + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrUserId { - is_dirty:bool, - value: Option -} + BALANCE => { + attr_field_access::attr_set_value::<_, _>(self.balance.get_mut(), value)?; + } -impl AttrUserId { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + UPDATED_AT => { + attr_field_access::attr_set_value::<_, _>(self.updated_at.get_mut(), value)?; + } + + _ => { + panic!("unknown name"); + } + } + Ok(()) } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrUserId { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrUserId { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrUserId { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - WALLETS - } + impl AttrValue for AttrUserId { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - USER_ID - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrBalance { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + WALLETS + } -impl AttrBalance { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + USER_ID } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrBalance { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrBalance { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrBalance { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - WALLETS - } + impl AttrValue for AttrBalance { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - BALANCE - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrUpdatedAt { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + WALLETS + } -impl AttrUpdatedAt { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + BALANCE } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrUpdatedAt { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrUpdatedAt { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrUpdatedAt { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - WALLETS - } + impl AttrValue for AttrUpdatedAt { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - UPDATED_AT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } + fn object_name() -> &'static str { + WALLETS + } -} \ No newline at end of file + fn attr_name() -> &'static str { + UPDATED_AT + } + } +} diff --git a/example/wallet/src/generated/warehouse.rs b/example/wallet/src/generated/warehouse.rs index 6916b66..9544dd2 100644 --- a/example/wallet/src/generated/warehouse.rs +++ b/example/wallet/src/generated/warehouse.rs @@ -525,4 +525,4 @@ pub mod object { COLUMN_W_ZIP } } -} // end mod object \ No newline at end of file +} // end mod object diff --git a/example/wallet/src/rust/orders.rs b/example/wallet/src/rust/orders.rs index 51c38e4..26c49e9 100644 --- a/example/wallet/src/rust/orders.rs +++ b/example/wallet/src/rust/orders.rs @@ -1,599 +1,556 @@ pub mod object { -use lazy_static::lazy_static; -use mudu::common::result::RS; -use mudu_type::dat_binary::DatBinary; -use mudu_type::dat_textual::DatTextual; -use mudu_type::dat_type::DatType; -use mudu_type::dat_type_id::DatTypeID; -use mudu_type::dat_value::DatValue; -use mudu_type::datum::{Datum, DatumDyn}; -use mudu_contract::database::attr_field_access; -use mudu_contract::database::attr_value::AttrValue; -use mudu_contract::database::entity::Entity; -use mudu_contract::database::entity_utils; -use mudu_contract::tuple::datum_desc::DatumDesc; -use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; -use mudu_contract::tuple::tuple_datum::TupleDatumMarker; -use mudu_contract::database::sql_params::SQLParamMarker; - -// constant definition -const ORDERS:&str = "orders"; - -const ORDER_ID:&str = "order_id"; - -const USER_ID:&str = "user_id"; - -const MERCH_ID:&str = "merch_id"; - -const AMOUNT:&str = "amount"; - -const CREATED_AT:&str = "created_at"; - - -// entity struct definition -#[derive(Debug, Clone, Default)] -pub struct Orders { - - order_id: AttrOrderId, - - user_id: AttrUserId, - - merch_id: AttrMerchId, - - amount: AttrAmount, - - created_at: AttrCreatedAt, - -} + use lazy_static::lazy_static; + use mudu::common::result::RS; + use mudu_contract::database::attr_field_access; + use mudu_contract::database::attr_value::AttrValue; + use mudu_contract::database::entity::Entity; + use mudu_contract::database::entity_utils; + use mudu_contract::database::sql_params::SQLParamMarker; + use mudu_contract::tuple::datum_desc::DatumDesc; + use mudu_contract::tuple::tuple_datum::TupleDatumMarker; + use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; + use mudu_type::dat_binary::DatBinary; + use mudu_type::dat_textual::DatTextual; + use mudu_type::dat_type::DatType; + use mudu_type::dat_type_id::DatTypeID; + use mudu_type::dat_value::DatValue; + use mudu_type::datum::{Datum, DatumDyn}; -impl TupleDatumMarker for Orders {} - -impl SQLParamMarker for Orders {} - -impl Orders { - pub fn new( - order_id: Option, - user_id: Option, - merch_id: Option, - amount: Option, - created_at: Option, - - ) -> Self { - let s = Self { - - order_id : AttrOrderId::from(order_id), - - user_id : AttrUserId::from(user_id), - - merch_id : AttrMerchId::from(merch_id), - - amount : AttrAmount::from(amount), - - created_at : AttrCreatedAt::from(created_at), - - }; - s - } + // constant definition + const ORDERS: &str = "orders"; - pub fn new_empty() -> Self { - Self::default() - } + const ORDER_ID: &str = "order_id"; - - pub fn set_order_id( - &mut self, - order_id: i32, - ) { - self.order_id.update(order_id) - } + const USER_ID: &str = "user_id"; - pub fn get_order_id( - &self, - ) -> &Option { - self.order_id.get() - } - - pub fn set_user_id( - &mut self, - user_id: i32, - ) { - self.user_id.update(user_id) - } + const MERCH_ID: &str = "merch_id"; - pub fn get_user_id( - &self, - ) -> &Option { - self.user_id.get() - } - - pub fn set_merch_id( - &mut self, - merch_id: i32, - ) { - self.merch_id.update(merch_id) - } + const AMOUNT: &str = "amount"; - pub fn get_merch_id( - &self, - ) -> &Option { - self.merch_id.get() - } - - pub fn set_amount( - &mut self, - amount: i32, - ) { - self.amount.update(amount) - } + const CREATED_AT: &str = "created_at"; - pub fn get_amount( - &self, - ) -> &Option { - self.amount.get() - } - - pub fn set_created_at( - &mut self, - created_at: i32, - ) { - self.created_at.update(created_at) - } + // entity struct definition + #[derive(Debug, Clone, Default)] + pub struct Orders { + order_id: AttrOrderId, - pub fn get_created_at( - &self, - ) -> &Option { - self.created_at.get() + user_id: AttrUserId, + + merch_id: AttrMerchId, + + amount: AttrAmount, + + created_at: AttrCreatedAt, } - -} -impl Datum for Orders { - fn dat_type() -> &'static DatType { - lazy_static! { - static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); + impl TupleDatumMarker for Orders {} + + impl SQLParamMarker for Orders {} + + impl Orders { + pub fn new( + order_id: Option, + user_id: Option, + merch_id: Option, + amount: Option, + created_at: Option, + ) -> Self { + let s = Self { + order_id: AttrOrderId::from(order_id), + + user_id: AttrUserId::from(user_id), + + merch_id: AttrMerchId::from(merch_id), + + amount: AttrAmount::from(amount), + + created_at: AttrCreatedAt::from(created_at), + }; + s } - &DAT_TYPE - } - fn from_binary(binary: &[u8]) -> RS { - entity_utils::entity_from_binary(binary) - } + pub fn new_empty() -> Self { + Self::default() + } - fn from_value(value: &DatValue) -> RS { - entity_utils::entity_from_value(value) - } + pub fn set_order_id(&mut self, order_id: i32) { + self.order_id.update(order_id) + } - fn from_textual(textual: &str) -> RS { - entity_utils::entity_from_textual(textual) - } -} + pub fn get_order_id(&self) -> &Option { + self.order_id.get() + } -impl DatumDyn for Orders { - fn dat_type_id(&self) -> RS { - entity_utils::entity_dat_type_id() - } + pub fn set_user_id(&mut self, user_id: i32) { + self.user_id.update(user_id) + } - fn to_binary(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_binary(self, dat_type) - } + pub fn get_user_id(&self) -> &Option { + self.user_id.get() + } - fn to_textual(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_textual(self, dat_type) - } + pub fn set_merch_id(&mut self, merch_id: i32) { + self.merch_id.update(merch_id) + } - fn to_value(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_value(self, dat_type) - } + pub fn get_merch_id(&self) -> &Option { + self.merch_id.get() + } - fn clone_boxed(&self) -> Box { - entity_utils::entity_clone_boxed(self) - } -} + pub fn set_amount(&mut self, amount: i32) { + self.amount.update(amount) + } -impl Entity for Orders { - fn new_empty() -> Self { - Self::new_empty() - } + pub fn get_amount(&self) -> &Option { + self.amount.get() + } - fn tuple_desc() -> &'static TupleFieldDesc { - lazy_static! { - static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ - - AttrOrderId::datum_desc().clone(), - - AttrUserId::datum_desc().clone(), - - AttrMerchId::datum_desc().clone(), - - AttrAmount::datum_desc().clone(), - - AttrCreatedAt::datum_desc().clone(), - - ]); - } - &TUPLE_DESC - } + pub fn set_created_at(&mut self, created_at: i32) { + self.created_at.update(created_at) + } - fn object_name() -> &'static str { - ORDERS + pub fn get_created_at(&self) -> &Option { + self.created_at.get() + } } - fn get_field_binary(&self, field: &str) -> RS>> { - match field { - - ORDER_ID => { - attr_field_access::attr_get_binary::<_>(self.order_id.get()) - } - - USER_ID => { - attr_field_access::attr_get_binary::<_>(self.user_id.get()) - } - - MERCH_ID => { - attr_field_access::attr_get_binary::<_>(self.merch_id.get()) + impl Datum for Orders { + fn dat_type() -> &'static DatType { + lazy_static! { + static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); } - - AMOUNT => { - attr_field_access::attr_get_binary::<_>(self.amount.get()) - } - - CREATED_AT => { - attr_field_access::attr_get_binary::<_>(self.created_at.get()) - } - - _ => { panic!("unknown name"); } + &DAT_TYPE } - } - fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { - match field { - - ORDER_ID => { - attr_field_access::attr_set_binary::<_, _>(self.order_id.get_mut(), binary.as_ref())?; - } - - USER_ID => { - attr_field_access::attr_set_binary::<_, _>(self.user_id.get_mut(), binary.as_ref())?; - } - - MERCH_ID => { - attr_field_access::attr_set_binary::<_, _>(self.merch_id.get_mut(), binary.as_ref())?; - } - - AMOUNT => { - attr_field_access::attr_set_binary::<_, _>(self.amount.get_mut(), binary.as_ref())?; - } - - CREATED_AT => { - attr_field_access::attr_set_binary::<_, _>(self.created_at.get_mut(), binary.as_ref())?; - } - - _ => { panic!("unknown name"); } + fn from_binary(binary: &[u8]) -> RS { + entity_utils::entity_from_binary(binary) + } + + fn from_value(value: &DatValue) -> RS { + entity_utils::entity_from_value(value) + } + + fn from_textual(textual: &str) -> RS { + entity_utils::entity_from_textual(textual) } - Ok(()) } - fn get_field_value(&self, field: &str) -> RS> { - match field { - - ORDER_ID => { - attr_field_access::attr_get_value::<_>(self.order_id.get()) - } - - USER_ID => { - attr_field_access::attr_get_value::<_>(self.user_id.get()) - } - - MERCH_ID => { - attr_field_access::attr_get_value::<_>(self.merch_id.get()) - } - - AMOUNT => { - attr_field_access::attr_get_value::<_>(self.amount.get()) - } - - CREATED_AT => { - attr_field_access::attr_get_value::<_>(self.created_at.get()) - } - - _ => { panic!("unknown name"); } + impl DatumDyn for Orders { + fn dat_type_id(&self) -> RS { + entity_utils::entity_dat_type_id() + } + + fn to_binary(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_binary(self, dat_type) + } + + fn to_textual(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_textual(self, dat_type) + } + + fn to_value(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_value(self, dat_type) + } + + fn clone_boxed(&self) -> Box { + entity_utils::entity_clone_boxed(self) } } - fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { - match field { - - ORDER_ID => { - attr_field_access::attr_set_value::<_, _>(self.order_id.get_mut(), value)?; - } - - USER_ID => { - attr_field_access::attr_set_value::<_, _>(self.user_id.get_mut(), value)?; + impl Entity for Orders { + fn new_empty() -> Self { + Self::new_empty() + } + + fn tuple_desc() -> &'static TupleFieldDesc { + lazy_static! { + static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ + AttrOrderId::datum_desc().clone(), + AttrUserId::datum_desc().clone(), + AttrMerchId::datum_desc().clone(), + AttrAmount::datum_desc().clone(), + AttrCreatedAt::datum_desc().clone(), + ]); } - - MERCH_ID => { - attr_field_access::attr_set_value::<_, _>(self.merch_id.get_mut(), value)?; + &TUPLE_DESC + } + + fn object_name() -> &'static str { + ORDERS + } + + fn get_field_binary(&self, field: &str) -> RS>> { + match field { + ORDER_ID => attr_field_access::attr_get_binary::<_>(self.order_id.get()), + + USER_ID => attr_field_access::attr_get_binary::<_>(self.user_id.get()), + + MERCH_ID => attr_field_access::attr_get_binary::<_>(self.merch_id.get()), + + AMOUNT => attr_field_access::attr_get_binary::<_>(self.amount.get()), + + CREATED_AT => attr_field_access::attr_get_binary::<_>(self.created_at.get()), + + _ => { + panic!("unknown name"); + } } - - AMOUNT => { - attr_field_access::attr_set_value::<_, _>(self.amount.get_mut(), value)?; + } + + fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { + match field { + ORDER_ID => { + attr_field_access::attr_set_binary::<_, _>( + self.order_id.get_mut(), + binary.as_ref(), + )?; + } + + USER_ID => { + attr_field_access::attr_set_binary::<_, _>( + self.user_id.get_mut(), + binary.as_ref(), + )?; + } + + MERCH_ID => { + attr_field_access::attr_set_binary::<_, _>( + self.merch_id.get_mut(), + binary.as_ref(), + )?; + } + + AMOUNT => { + attr_field_access::attr_set_binary::<_, _>( + self.amount.get_mut(), + binary.as_ref(), + )?; + } + + CREATED_AT => { + attr_field_access::attr_set_binary::<_, _>( + self.created_at.get_mut(), + binary.as_ref(), + )?; + } + + _ => { + panic!("unknown name"); + } } - - CREATED_AT => { - attr_field_access::attr_set_value::<_, _>(self.created_at.get_mut(), value)?; + Ok(()) + } + + fn get_field_value(&self, field: &str) -> RS> { + match field { + ORDER_ID => attr_field_access::attr_get_value::<_>(self.order_id.get()), + + USER_ID => attr_field_access::attr_get_value::<_>(self.user_id.get()), + + MERCH_ID => attr_field_access::attr_get_value::<_>(self.merch_id.get()), + + AMOUNT => attr_field_access::attr_get_value::<_>(self.amount.get()), + + CREATED_AT => attr_field_access::attr_get_value::<_>(self.created_at.get()), + + _ => { + panic!("unknown name"); + } } - - _ => { panic!("unknown name"); } } - Ok(()) - } -} + fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { + match field { + ORDER_ID => { + attr_field_access::attr_set_value::<_, _>(self.order_id.get_mut(), value)?; + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrOrderId { - is_dirty:bool, - value: Option -} + USER_ID => { + attr_field_access::attr_set_value::<_, _>(self.user_id.get_mut(), value)?; + } + + MERCH_ID => { + attr_field_access::attr_set_value::<_, _>(self.merch_id.get_mut(), value)?; + } + + AMOUNT => { + attr_field_access::attr_set_value::<_, _>(self.amount.get_mut(), value)?; + } -impl AttrOrderId { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + CREATED_AT => { + attr_field_access::attr_set_value::<_, _>(self.created_at.get_mut(), value)?; + } + + _ => { + panic!("unknown name"); + } + } + Ok(()) } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrOrderId { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrOrderId { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrOrderId { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - ORDERS - } + impl AttrValue for AttrOrderId { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - ORDER_ID - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrUserId { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + ORDERS + } -impl AttrUserId { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + ORDER_ID } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrUserId { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrUserId { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrUserId { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - ORDERS - } + impl AttrValue for AttrUserId { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - USER_ID - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrMerchId { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + ORDERS + } -impl AttrMerchId { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + USER_ID } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrMerchId { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrMerchId { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrMerchId { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - ORDERS - } + impl AttrValue for AttrMerchId { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - MERCH_ID - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrAmount { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + ORDERS + } -impl AttrAmount { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + MERCH_ID } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrAmount { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrAmount { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrAmount { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - ORDERS - } + impl AttrValue for AttrAmount { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - AMOUNT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrCreatedAt { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + ORDERS + } -impl AttrCreatedAt { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + AMOUNT } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrCreatedAt { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrCreatedAt { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrCreatedAt { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - ORDERS - } + impl AttrValue for AttrCreatedAt { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - CREATED_AT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } + fn object_name() -> &'static str { + ORDERS + } + fn attr_name() -> &'static str { + CREATED_AT + } + } } diff --git a/example/wallet/src/rust/procedures.rs b/example/wallet/src/rust/procedures.rs index 2cf597e..d52c65e 100644 --- a/example/wallet/src/rust/procedures.rs +++ b/example/wallet/src/rust/procedures.rs @@ -6,12 +6,11 @@ use mudu::m_error; use mudu_contract::database::attr_value::AttrValue; use mudu_contract::{sql_params, sql_stmt}; use mudu_type::datum::DatumDyn; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::time::UNIX_EPOCH; use sys_interface::sync_api::{mudu_command, mudu_query}; -use uuid::Uuid; fn current_timestamp() -> i64 { - let now = SystemTime::now(); + let now = mudu_sys::time::system_time_now(); let duration_since_epoch = now .duration_since(UNIX_EPOCH) .expect("SystemTime before UNIX EPOCH!"); @@ -40,8 +39,7 @@ pub fn transfer_funds(xid: XID, from_user_id: i32, to_user_id: i32, amount: i32) xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?;"), sql_params!(&(from_user_id,)), - ) - ?; + )?; let from_wallet = if let Some(row) = wallet_rs.next_record()? { row @@ -58,8 +56,7 @@ pub fn transfer_funds(xid: XID, from_user_id: i32, to_user_id: i32, amount: i32) xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?;"), sql_params!(&(to_user_id)), - ) - ?; + )?; let _to_wallet = if let Some(row) = to_wallet.next_record()? { row } else { @@ -72,8 +69,7 @@ pub fn transfer_funds(xid: XID, from_user_id: i32, to_user_id: i32, amount: i32) xid, sql_stmt!(&"UPDATE wallets SET balance = balance - ? WHERE user_id = ?;"), sql_params!(&(amount, from_user_id)), - ) - ?; + )?; if deduct_updated_rows != 1 { return Err(m_error!(MuduError, "transfer fund failed")); } @@ -82,14 +78,13 @@ pub fn transfer_funds(xid: XID, from_user_id: i32, to_user_id: i32, amount: i32) xid, sql_stmt!(&"UPDATE wallets SET balance = balance + ? WHERE user_id = ?;"), sql_params!(&(amount, to_user_id)), - ) - ?; + )?; if increase_updated_rows != 1 { return Err(m_error!(MuduError, "transfer fund failed")); } // 3. Entity the transaction - let id = Uuid::new_v4().to_string(); + let id = mudu_sys::random::next_uuid_v4_string(); let insert_rows = mudu_command( xid, sql_stmt!( @@ -100,8 +95,7 @@ pub fn transfer_funds(xid: XID, from_user_id: i32, to_user_id: i32, amount: i32) "# ), sql_params!(&(id, from_user_id, to_user_id, amount)), - ) - ?; + )?; if insert_rows != 1 { return Err(m_error!(MuduError, "transfer fund failed")); } @@ -130,8 +124,7 @@ pub fn create_user(xid: XID, user_id: i32, name: String, email: String) -> RS<() xid, sql_stmt!(&"INSERT INTO wallets (user_id, balance, updated_at) VALUES (?, ?, ?)"), sql_params!(&(user_id, 0, now)), - ) - ?; + )?; if wallet_created != 1 { return Err(m_error!(MuduError, "Failed to create wallet")); @@ -147,8 +140,7 @@ pub fn delete_user(xid: XID, user_id: i32) -> RS<()> { xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?"), sql_params!(&(user_id,)), - ) - ?; + )?; let wallet = wallet_rs .next_record()? @@ -166,16 +158,14 @@ pub fn delete_user(xid: XID, user_id: i32) -> RS<()> { xid, sql_stmt!(&"DELETE FROM wallets WHERE user_id = ?"), sql_params!(&(user_id,)), - ) - ?; + )?; // Delete user mudu_command( xid, sql_stmt!(&"DELETE FROM users WHERE user_id = ?"), sql_params!(&(user_id,)), - ) - ?; + )?; Ok(()) } @@ -217,15 +207,14 @@ pub fn deposit(xid: XID, user_id: i32, amount: i32) -> RS<()> { } let now = current_timestamp(); - let tx_id = Uuid::new_v4().to_string(); + let tx_id = mudu_sys::random::next_uuid_v4_string(); // Update wallet balance let updated = mudu_command( xid, sql_stmt!(&"UPDATE wallets SET balance = balance + ?, updated_at = ? WHERE user_id = ?"), sql_params!(&(amount, now, user_id)), - ) - ?; + )?; if updated != 1 { return Err(m_error!(MuduError, "User wallet not found")); @@ -254,8 +243,7 @@ pub fn withdraw(xid: XID, user_id: i32, amount: i32) -> RS<()> { xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?"), sql_params!(&(user_id,)), - ) - ?; + )?; let wallet = wallet_rs .next_record()? @@ -266,15 +254,14 @@ pub fn withdraw(xid: XID, user_id: i32, amount: i32) -> RS<()> { } let now = current_timestamp(); - let tx_id = Uuid::new_v4().to_string(); + let tx_id = mudu_sys::random::next_uuid_v4_string(); // Update wallet balance mudu_command( xid, sql_stmt!(&"UPDATE wallets SET balance = balance - ?, updated_at = ? WHERE user_id = ?"), sql_params!(&(amount, now, user_id)), - ) - ?; + )?; // Entity transaction mudu_command( @@ -303,8 +290,7 @@ pub fn transfer(xid: XID, from_user_id: i32, to_user_id: i32, amount: i32) -> RS xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?"), sql_params!(&(from_user_id,)), - ) - ? + )? .next_record()? .ok_or_else(|| m_error!(MuduError, "Sender wallet not found"))?; @@ -317,8 +303,7 @@ pub fn transfer(xid: XID, from_user_id: i32, to_user_id: i32, amount: i32) -> RS xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?"), sql_params!(&(to_user_id.clone(),)), - ) - ? + )? .next_record()? .is_some(); @@ -327,23 +312,21 @@ pub fn transfer(xid: XID, from_user_id: i32, to_user_id: i32, amount: i32) -> RS } let now = current_timestamp(); - let tx_id = Uuid::new_v4().to_string(); + let tx_id = mudu_sys::random::next_uuid_v4_string(); // Debit sender mudu_command( xid, sql_stmt!(&"UPDATE wallets SET balance = balance - ?, updated_at = ? WHERE user_id = ?"), sql_params!(&(amount, now, from_user_id)), - ) - ?; + )?; // Credit receiver mudu_command( xid, sql_stmt!(&"UPDATE wallets SET balance = balance + ?, updated_at = ? WHERE user_id = ?"), sql_params!(&(amount, now, to_user_id)), - ) - ?; + )?; // Entity transaction mudu_command( @@ -376,8 +359,7 @@ pub fn purchase(xid: XID, user_id: i32, amount: i32, description: String) -> RS< xid, sql_stmt!(&"SELECT user_id, balance, updated_at FROM wallets WHERE user_id = ?"), sql_params!(&(user_id,)), - ) - ? + )? .next_record()? .ok_or_else(|| m_error!(MuduError, "Wallet not found"))?; @@ -386,15 +368,14 @@ pub fn purchase(xid: XID, user_id: i32, amount: i32, description: String) -> RS< } let now = current_timestamp(); - let tx_id = Uuid::new_v4().to_string(); + let tx_id = mudu_sys::random::next_uuid_v4_string(); // Deduct amount mudu_command( xid, sql_stmt!(&"UPDATE wallets SET balance = balance - ?, updated_at = ? WHERE user_id = ?"), sql_params!(&(amount, now, user_id)), - ) - ?; + )?; // Entity transaction mudu_command( diff --git a/example/wallet/src/rust/transactions.rs b/example/wallet/src/rust/transactions.rs index 448414c..62c52ff 100644 --- a/example/wallet/src/rust/transactions.rs +++ b/example/wallet/src/rust/transactions.rs @@ -1,690 +1,640 @@ pub mod object { -use lazy_static::lazy_static; -use mudu::common::result::RS; -use mudu_type::dat_binary::DatBinary; -use mudu_type::dat_textual::DatTextual; -use mudu_type::dat_type::DatType; -use mudu_type::dat_type_id::DatTypeID; -use mudu_type::dat_value::DatValue; -use mudu_type::datum::{Datum, DatumDyn}; -use mudu_contract::database::attr_field_access; -use mudu_contract::database::attr_value::AttrValue; -use mudu_contract::database::entity::Entity; -use mudu_contract::database::entity_utils; -use mudu_contract::tuple::datum_desc::DatumDesc; -use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; -use mudu_contract::tuple::tuple_datum::TupleDatumMarker; -use mudu_contract::database::sql_params::SQLParamMarker; - -// constant definition -const TRANSACTIONS:&str = "transactions"; - -const TRANS_ID:&str = "trans_id"; - -const TRANS_TYPE:&str = "trans_type"; - -const FROM_USER:&str = "from_user"; - -const TO_USER:&str = "to_user"; - -const AMOUNT:&str = "amount"; - -const CREATED_AT:&str = "created_at"; - - -// entity struct definition -#[derive(Debug, Clone, Default)] -pub struct Transactions { - - trans_id: AttrTransId, - - trans_type: AttrTransType, - - from_user: AttrFromUser, - - to_user: AttrToUser, - - amount: AttrAmount, - - created_at: AttrCreatedAt, - -} + use lazy_static::lazy_static; + use mudu::common::result::RS; + use mudu_contract::database::attr_field_access; + use mudu_contract::database::attr_value::AttrValue; + use mudu_contract::database::entity::Entity; + use mudu_contract::database::entity_utils; + use mudu_contract::database::sql_params::SQLParamMarker; + use mudu_contract::tuple::datum_desc::DatumDesc; + use mudu_contract::tuple::tuple_datum::TupleDatumMarker; + use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; + use mudu_type::dat_binary::DatBinary; + use mudu_type::dat_textual::DatTextual; + use mudu_type::dat_type::DatType; + use mudu_type::dat_type_id::DatTypeID; + use mudu_type::dat_value::DatValue; + use mudu_type::datum::{Datum, DatumDyn}; -impl TupleDatumMarker for Transactions {} - -impl SQLParamMarker for Transactions {} - -impl Transactions { - pub fn new( - trans_id: Option, - trans_type: Option, - from_user: Option, - to_user: Option, - amount: Option, - created_at: Option, - - ) -> Self { - let s = Self { - - trans_id : AttrTransId::from(trans_id), - - trans_type : AttrTransType::from(trans_type), - - from_user : AttrFromUser::from(from_user), - - to_user : AttrToUser::from(to_user), - - amount : AttrAmount::from(amount), - - created_at : AttrCreatedAt::from(created_at), - - }; - s - } + // constant definition + const TRANSACTIONS: &str = "transactions"; - pub fn new_empty() -> Self { - Self::default() - } + const TRANS_ID: &str = "trans_id"; - - pub fn set_trans_id( - &mut self, - trans_id: String, - ) { - self.trans_id.update(trans_id) - } + const TRANS_TYPE: &str = "trans_type"; - pub fn get_trans_id( - &self, - ) -> &Option { - self.trans_id.get() - } - - pub fn set_trans_type( - &mut self, - trans_type: String, - ) { - self.trans_type.update(trans_type) - } + const FROM_USER: &str = "from_user"; - pub fn get_trans_type( - &self, - ) -> &Option { - self.trans_type.get() - } - - pub fn set_from_user( - &mut self, - from_user: i32, - ) { - self.from_user.update(from_user) - } + const TO_USER: &str = "to_user"; - pub fn get_from_user( - &self, - ) -> &Option { - self.from_user.get() - } - - pub fn set_to_user( - &mut self, - to_user: i32, - ) { - self.to_user.update(to_user) - } + const AMOUNT: &str = "amount"; - pub fn get_to_user( - &self, - ) -> &Option { - self.to_user.get() - } - - pub fn set_amount( - &mut self, - amount: i32, - ) { - self.amount.update(amount) - } + const CREATED_AT: &str = "created_at"; - pub fn get_amount( - &self, - ) -> &Option { - self.amount.get() - } - - pub fn set_created_at( - &mut self, - created_at: i32, - ) { - self.created_at.update(created_at) - } + // entity struct definition + #[derive(Debug, Clone, Default)] + pub struct Transactions { + trans_id: AttrTransId, + + trans_type: AttrTransType, - pub fn get_created_at( - &self, - ) -> &Option { - self.created_at.get() + from_user: AttrFromUser, + + to_user: AttrToUser, + + amount: AttrAmount, + + created_at: AttrCreatedAt, } - -} -impl Datum for Transactions { - fn dat_type() -> &'static DatType { - lazy_static! { - static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); + impl TupleDatumMarker for Transactions {} + + impl SQLParamMarker for Transactions {} + + impl Transactions { + pub fn new( + trans_id: Option, + trans_type: Option, + from_user: Option, + to_user: Option, + amount: Option, + created_at: Option, + ) -> Self { + let s = Self { + trans_id: AttrTransId::from(trans_id), + + trans_type: AttrTransType::from(trans_type), + + from_user: AttrFromUser::from(from_user), + + to_user: AttrToUser::from(to_user), + + amount: AttrAmount::from(amount), + + created_at: AttrCreatedAt::from(created_at), + }; + s } - &DAT_TYPE - } - fn from_binary(binary: &[u8]) -> RS { - entity_utils::entity_from_binary(binary) - } + pub fn new_empty() -> Self { + Self::default() + } - fn from_value(value: &DatValue) -> RS { - entity_utils::entity_from_value(value) - } + pub fn set_trans_id(&mut self, trans_id: String) { + self.trans_id.update(trans_id) + } - fn from_textual(textual: &str) -> RS { - entity_utils::entity_from_textual(textual) - } -} + pub fn get_trans_id(&self) -> &Option { + self.trans_id.get() + } -impl DatumDyn for Transactions { - fn dat_type_id(&self) -> RS { - entity_utils::entity_dat_type_id() - } + pub fn set_trans_type(&mut self, trans_type: String) { + self.trans_type.update(trans_type) + } - fn to_binary(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_binary(self, dat_type) - } + pub fn get_trans_type(&self) -> &Option { + self.trans_type.get() + } - fn to_textual(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_textual(self, dat_type) - } + pub fn set_from_user(&mut self, from_user: i32) { + self.from_user.update(from_user) + } - fn to_value(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_value(self, dat_type) - } + pub fn get_from_user(&self) -> &Option { + self.from_user.get() + } - fn clone_boxed(&self) -> Box { - entity_utils::entity_clone_boxed(self) - } -} + pub fn set_to_user(&mut self, to_user: i32) { + self.to_user.update(to_user) + } -impl Entity for Transactions { - fn new_empty() -> Self { - Self::new_empty() - } + pub fn get_to_user(&self) -> &Option { + self.to_user.get() + } - fn tuple_desc() -> &'static TupleFieldDesc { - lazy_static! { - static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ - - AttrTransId::datum_desc().clone(), - - AttrTransType::datum_desc().clone(), - - AttrFromUser::datum_desc().clone(), - - AttrToUser::datum_desc().clone(), - - AttrAmount::datum_desc().clone(), - - AttrCreatedAt::datum_desc().clone(), - - ]); - } - &TUPLE_DESC - } + pub fn set_amount(&mut self, amount: i32) { + self.amount.update(amount) + } - fn object_name() -> &'static str { - TRANSACTIONS - } + pub fn get_amount(&self) -> &Option { + self.amount.get() + } - fn get_field_binary(&self, field: &str) -> RS>> { - match field { - - TRANS_ID => { - attr_field_access::attr_get_binary::<_>(self.trans_id.get()) - } - - TRANS_TYPE => { - attr_field_access::attr_get_binary::<_>(self.trans_type.get()) - } - - FROM_USER => { - attr_field_access::attr_get_binary::<_>(self.from_user.get()) - } - - TO_USER => { - attr_field_access::attr_get_binary::<_>(self.to_user.get()) - } - - AMOUNT => { - attr_field_access::attr_get_binary::<_>(self.amount.get()) - } - - CREATED_AT => { - attr_field_access::attr_get_binary::<_>(self.created_at.get()) - } - - _ => { panic!("unknown name"); } + pub fn set_created_at(&mut self, created_at: i32) { + self.created_at.update(created_at) + } + + pub fn get_created_at(&self) -> &Option { + self.created_at.get() } } - fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { - match field { - - TRANS_ID => { - attr_field_access::attr_set_binary::<_, _>(self.trans_id.get_mut(), binary.as_ref())?; - } - - TRANS_TYPE => { - attr_field_access::attr_set_binary::<_, _>(self.trans_type.get_mut(), binary.as_ref())?; - } - - FROM_USER => { - attr_field_access::attr_set_binary::<_, _>(self.from_user.get_mut(), binary.as_ref())?; - } - - TO_USER => { - attr_field_access::attr_set_binary::<_, _>(self.to_user.get_mut(), binary.as_ref())?; - } - - AMOUNT => { - attr_field_access::attr_set_binary::<_, _>(self.amount.get_mut(), binary.as_ref())?; - } - - CREATED_AT => { - attr_field_access::attr_set_binary::<_, _>(self.created_at.get_mut(), binary.as_ref())?; + impl Datum for Transactions { + fn dat_type() -> &'static DatType { + lazy_static! { + static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); } - - _ => { panic!("unknown name"); } + &DAT_TYPE + } + + fn from_binary(binary: &[u8]) -> RS { + entity_utils::entity_from_binary(binary) + } + + fn from_value(value: &DatValue) -> RS { + entity_utils::entity_from_value(value) + } + + fn from_textual(textual: &str) -> RS { + entity_utils::entity_from_textual(textual) } - Ok(()) } - fn get_field_value(&self, field: &str) -> RS> { - match field { - - TRANS_ID => { - attr_field_access::attr_get_value::<_>(self.trans_id.get()) - } - - TRANS_TYPE => { - attr_field_access::attr_get_value::<_>(self.trans_type.get()) - } - - FROM_USER => { - attr_field_access::attr_get_value::<_>(self.from_user.get()) - } - - TO_USER => { - attr_field_access::attr_get_value::<_>(self.to_user.get()) - } - - AMOUNT => { - attr_field_access::attr_get_value::<_>(self.amount.get()) - } - - CREATED_AT => { - attr_field_access::attr_get_value::<_>(self.created_at.get()) - } - - _ => { panic!("unknown name"); } + impl DatumDyn for Transactions { + fn dat_type_id(&self) -> RS { + entity_utils::entity_dat_type_id() + } + + fn to_binary(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_binary(self, dat_type) + } + + fn to_textual(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_textual(self, dat_type) + } + + fn to_value(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_value(self, dat_type) + } + + fn clone_boxed(&self) -> Box { + entity_utils::entity_clone_boxed(self) } } - fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { - match field { - - TRANS_ID => { - attr_field_access::attr_set_value::<_, _>(self.trans_id.get_mut(), value)?; - } - - TRANS_TYPE => { - attr_field_access::attr_set_value::<_, _>(self.trans_type.get_mut(), value)?; - } - - FROM_USER => { - attr_field_access::attr_set_value::<_, _>(self.from_user.get_mut(), value)?; + impl Entity for Transactions { + fn new_empty() -> Self { + Self::new_empty() + } + + fn tuple_desc() -> &'static TupleFieldDesc { + lazy_static! { + static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ + AttrTransId::datum_desc().clone(), + AttrTransType::datum_desc().clone(), + AttrFromUser::datum_desc().clone(), + AttrToUser::datum_desc().clone(), + AttrAmount::datum_desc().clone(), + AttrCreatedAt::datum_desc().clone(), + ]); } - - TO_USER => { - attr_field_access::attr_set_value::<_, _>(self.to_user.get_mut(), value)?; + &TUPLE_DESC + } + + fn object_name() -> &'static str { + TRANSACTIONS + } + + fn get_field_binary(&self, field: &str) -> RS>> { + match field { + TRANS_ID => attr_field_access::attr_get_binary::<_>(self.trans_id.get()), + + TRANS_TYPE => attr_field_access::attr_get_binary::<_>(self.trans_type.get()), + + FROM_USER => attr_field_access::attr_get_binary::<_>(self.from_user.get()), + + TO_USER => attr_field_access::attr_get_binary::<_>(self.to_user.get()), + + AMOUNT => attr_field_access::attr_get_binary::<_>(self.amount.get()), + + CREATED_AT => attr_field_access::attr_get_binary::<_>(self.created_at.get()), + + _ => { + panic!("unknown name"); + } } - - AMOUNT => { - attr_field_access::attr_set_value::<_, _>(self.amount.get_mut(), value)?; + } + + fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { + match field { + TRANS_ID => { + attr_field_access::attr_set_binary::<_, _>( + self.trans_id.get_mut(), + binary.as_ref(), + )?; + } + + TRANS_TYPE => { + attr_field_access::attr_set_binary::<_, _>( + self.trans_type.get_mut(), + binary.as_ref(), + )?; + } + + FROM_USER => { + attr_field_access::attr_set_binary::<_, _>( + self.from_user.get_mut(), + binary.as_ref(), + )?; + } + + TO_USER => { + attr_field_access::attr_set_binary::<_, _>( + self.to_user.get_mut(), + binary.as_ref(), + )?; + } + + AMOUNT => { + attr_field_access::attr_set_binary::<_, _>( + self.amount.get_mut(), + binary.as_ref(), + )?; + } + + CREATED_AT => { + attr_field_access::attr_set_binary::<_, _>( + self.created_at.get_mut(), + binary.as_ref(), + )?; + } + + _ => { + panic!("unknown name"); + } } - - CREATED_AT => { - attr_field_access::attr_set_value::<_, _>(self.created_at.get_mut(), value)?; + Ok(()) + } + + fn get_field_value(&self, field: &str) -> RS> { + match field { + TRANS_ID => attr_field_access::attr_get_value::<_>(self.trans_id.get()), + + TRANS_TYPE => attr_field_access::attr_get_value::<_>(self.trans_type.get()), + + FROM_USER => attr_field_access::attr_get_value::<_>(self.from_user.get()), + + TO_USER => attr_field_access::attr_get_value::<_>(self.to_user.get()), + + AMOUNT => attr_field_access::attr_get_value::<_>(self.amount.get()), + + CREATED_AT => attr_field_access::attr_get_value::<_>(self.created_at.get()), + + _ => { + panic!("unknown name"); + } } - - _ => { panic!("unknown name"); } } - Ok(()) - } -} + fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { + match field { + TRANS_ID => { + attr_field_access::attr_set_value::<_, _>(self.trans_id.get_mut(), value)?; + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrTransId { - is_dirty:bool, - value: Option -} + TRANS_TYPE => { + attr_field_access::attr_set_value::<_, _>(self.trans_type.get_mut(), value)?; + } + + FROM_USER => { + attr_field_access::attr_set_value::<_, _>(self.from_user.get_mut(), value)?; + } + + TO_USER => { + attr_field_access::attr_set_value::<_, _>(self.to_user.get_mut(), value)?; + } -impl AttrTransId { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + AMOUNT => { + attr_field_access::attr_set_value::<_, _>(self.amount.get_mut(), value)?; + } + + CREATED_AT => { + attr_field_access::attr_set_value::<_, _>(self.created_at.get_mut(), value)?; + } + + _ => { + panic!("unknown name"); + } + } + Ok(()) } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrTransId { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrTransId { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: String) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrTransId { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: String) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - TRANSACTIONS - } + impl AttrValue for AttrTransId { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - TRANS_ID - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrTransType { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + TRANSACTIONS + } -impl AttrTransType { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + TRANS_ID } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrTransType { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrTransType { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: String) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrTransType { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: String) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - TRANSACTIONS - } + impl AttrValue for AttrTransType { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - TRANS_TYPE - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrFromUser { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + TRANSACTIONS + } -impl AttrFromUser { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + TRANS_TYPE } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrFromUser { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrFromUser { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrFromUser { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - TRANSACTIONS - } + impl AttrValue for AttrFromUser { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - FROM_USER - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrToUser { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + TRANSACTIONS + } -impl AttrToUser { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + FROM_USER } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrToUser { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrToUser { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrToUser { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - TRANSACTIONS - } + impl AttrValue for AttrToUser { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - TO_USER - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrAmount { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + TRANSACTIONS + } -impl AttrAmount { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + TO_USER } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrAmount { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrAmount { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrAmount { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - TRANSACTIONS - } + impl AttrValue for AttrAmount { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - AMOUNT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrCreatedAt { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + TRANSACTIONS + } -impl AttrCreatedAt { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + AMOUNT } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrCreatedAt { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrCreatedAt { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrCreatedAt { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - TRANSACTIONS - } + impl AttrValue for AttrCreatedAt { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - CREATED_AT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } + fn object_name() -> &'static str { + TRANSACTIONS + } + fn attr_name() -> &'static str { + CREATED_AT + } + } } diff --git a/example/wallet/src/rust/users.rs b/example/wallet/src/rust/users.rs index e84a9cf..a007e74 100644 --- a/example/wallet/src/rust/users.rs +++ b/example/wallet/src/rust/users.rs @@ -1,781 +1,724 @@ pub mod object { -use lazy_static::lazy_static; -use mudu::common::result::RS; -use mudu_type::dat_binary::DatBinary; -use mudu_type::dat_textual::DatTextual; -use mudu_type::dat_type::DatType; -use mudu_type::dat_type_id::DatTypeID; -use mudu_type::dat_value::DatValue; -use mudu_type::datum::{Datum, DatumDyn}; -use mudu_contract::database::attr_field_access; -use mudu_contract::database::attr_value::AttrValue; -use mudu_contract::database::entity::Entity; -use mudu_contract::database::entity_utils; -use mudu_contract::tuple::datum_desc::DatumDesc; -use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; -use mudu_contract::tuple::tuple_datum::TupleDatumMarker; -use mudu_contract::database::sql_params::SQLParamMarker; - -// constant definition -const USERS:&str = "users"; - -const USER_ID:&str = "user_id"; - -const NAME:&str = "name"; - -const PHONE:&str = "phone"; - -const EMAIL:&str = "email"; - -const PASSWORD:&str = "password"; - -const CREATED_AT:&str = "created_at"; - -const UPDATED_AT:&str = "updated_at"; - - -// entity struct definition -#[derive(Debug, Clone, Default)] -pub struct Users { - - user_id: AttrUserId, - - name: AttrName, - - phone: AttrPhone, - - email: AttrEmail, - - password: AttrPassword, - - created_at: AttrCreatedAt, - - updated_at: AttrUpdatedAt, - -} + use lazy_static::lazy_static; + use mudu::common::result::RS; + use mudu_contract::database::attr_field_access; + use mudu_contract::database::attr_value::AttrValue; + use mudu_contract::database::entity::Entity; + use mudu_contract::database::entity_utils; + use mudu_contract::database::sql_params::SQLParamMarker; + use mudu_contract::tuple::datum_desc::DatumDesc; + use mudu_contract::tuple::tuple_datum::TupleDatumMarker; + use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; + use mudu_type::dat_binary::DatBinary; + use mudu_type::dat_textual::DatTextual; + use mudu_type::dat_type::DatType; + use mudu_type::dat_type_id::DatTypeID; + use mudu_type::dat_value::DatValue; + use mudu_type::datum::{Datum, DatumDyn}; -impl TupleDatumMarker for Users {} - -impl SQLParamMarker for Users {} - -impl Users { - pub fn new( - user_id: Option, - name: Option, - phone: Option, - email: Option, - password: Option, - created_at: Option, - updated_at: Option, - - ) -> Self { - let s = Self { - - user_id : AttrUserId::from(user_id), - - name : AttrName::from(name), - - phone : AttrPhone::from(phone), - - email : AttrEmail::from(email), - - password : AttrPassword::from(password), - - created_at : AttrCreatedAt::from(created_at), - - updated_at : AttrUpdatedAt::from(updated_at), - - }; - s - } + // constant definition + const USERS: &str = "users"; - pub fn new_empty() -> Self { - Self::default() - } + const USER_ID: &str = "user_id"; - - pub fn set_user_id( - &mut self, - user_id: i32, - ) { - self.user_id.update(user_id) - } + const NAME: &str = "name"; - pub fn get_user_id( - &self, - ) -> &Option { - self.user_id.get() - } - - pub fn set_name( - &mut self, - name: String, - ) { - self.name.update(name) - } + const PHONE: &str = "phone"; - pub fn get_name( - &self, - ) -> &Option { - self.name.get() - } - - pub fn set_phone( - &mut self, - phone: String, - ) { - self.phone.update(phone) - } + const EMAIL: &str = "email"; - pub fn get_phone( - &self, - ) -> &Option { - self.phone.get() - } - - pub fn set_email( - &mut self, - email: String, - ) { - self.email.update(email) - } + const PASSWORD: &str = "password"; - pub fn get_email( - &self, - ) -> &Option { - self.email.get() - } - - pub fn set_password( - &mut self, - password: String, - ) { - self.password.update(password) - } + const CREATED_AT: &str = "created_at"; - pub fn get_password( - &self, - ) -> &Option { - self.password.get() - } - - pub fn set_created_at( - &mut self, - created_at: i32, - ) { - self.created_at.update(created_at) - } + const UPDATED_AT: &str = "updated_at"; - pub fn get_created_at( - &self, - ) -> &Option { - self.created_at.get() - } - - pub fn set_updated_at( - &mut self, - updated_at: i32, - ) { - self.updated_at.update(updated_at) - } + // entity struct definition + #[derive(Debug, Clone, Default)] + pub struct Users { + user_id: AttrUserId, + + name: AttrName, - pub fn get_updated_at( - &self, - ) -> &Option { - self.updated_at.get() + phone: AttrPhone, + + email: AttrEmail, + + password: AttrPassword, + + created_at: AttrCreatedAt, + + updated_at: AttrUpdatedAt, } - -} -impl Datum for Users { - fn dat_type() -> &'static DatType { - lazy_static! { - static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); + impl TupleDatumMarker for Users {} + + impl SQLParamMarker for Users {} + + impl Users { + pub fn new( + user_id: Option, + name: Option, + phone: Option, + email: Option, + password: Option, + created_at: Option, + updated_at: Option, + ) -> Self { + let s = Self { + user_id: AttrUserId::from(user_id), + + name: AttrName::from(name), + + phone: AttrPhone::from(phone), + + email: AttrEmail::from(email), + + password: AttrPassword::from(password), + + created_at: AttrCreatedAt::from(created_at), + + updated_at: AttrUpdatedAt::from(updated_at), + }; + s } - &DAT_TYPE - } - fn from_binary(binary: &[u8]) -> RS { - entity_utils::entity_from_binary(binary) - } + pub fn new_empty() -> Self { + Self::default() + } - fn from_value(value: &DatValue) -> RS { - entity_utils::entity_from_value(value) - } + pub fn set_user_id(&mut self, user_id: i32) { + self.user_id.update(user_id) + } - fn from_textual(textual: &str) -> RS { - entity_utils::entity_from_textual(textual) - } -} + pub fn get_user_id(&self) -> &Option { + self.user_id.get() + } -impl DatumDyn for Users { - fn dat_type_id(&self) -> RS { - entity_utils::entity_dat_type_id() - } + pub fn set_name(&mut self, name: String) { + self.name.update(name) + } - fn to_binary(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_binary(self, dat_type) - } + pub fn get_name(&self) -> &Option { + self.name.get() + } - fn to_textual(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_textual(self, dat_type) - } + pub fn set_phone(&mut self, phone: String) { + self.phone.update(phone) + } - fn to_value(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_value(self, dat_type) - } + pub fn get_phone(&self) -> &Option { + self.phone.get() + } - fn clone_boxed(&self) -> Box { - entity_utils::entity_clone_boxed(self) - } -} + pub fn set_email(&mut self, email: String) { + self.email.update(email) + } -impl Entity for Users { - fn new_empty() -> Self { - Self::new_empty() - } + pub fn get_email(&self) -> &Option { + self.email.get() + } - fn tuple_desc() -> &'static TupleFieldDesc { - lazy_static! { - static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ - - AttrUserId::datum_desc().clone(), - - AttrName::datum_desc().clone(), - - AttrPhone::datum_desc().clone(), - - AttrEmail::datum_desc().clone(), - - AttrPassword::datum_desc().clone(), - - AttrCreatedAt::datum_desc().clone(), - - AttrUpdatedAt::datum_desc().clone(), - - ]); - } - &TUPLE_DESC - } + pub fn set_password(&mut self, password: String) { + self.password.update(password) + } - fn object_name() -> &'static str { - USERS - } + pub fn get_password(&self) -> &Option { + self.password.get() + } - fn get_field_binary(&self, field: &str) -> RS>> { - match field { - - USER_ID => { - attr_field_access::attr_get_binary::<_>(self.user_id.get()) - } - - NAME => { - attr_field_access::attr_get_binary::<_>(self.name.get()) - } - - PHONE => { - attr_field_access::attr_get_binary::<_>(self.phone.get()) - } - - EMAIL => { - attr_field_access::attr_get_binary::<_>(self.email.get()) - } - - PASSWORD => { - attr_field_access::attr_get_binary::<_>(self.password.get()) - } - - CREATED_AT => { - attr_field_access::attr_get_binary::<_>(self.created_at.get()) - } - - UPDATED_AT => { - attr_field_access::attr_get_binary::<_>(self.updated_at.get()) - } - - _ => { panic!("unknown name"); } + pub fn set_created_at(&mut self, created_at: i32) { + self.created_at.update(created_at) + } + + pub fn get_created_at(&self) -> &Option { + self.created_at.get() + } + + pub fn set_updated_at(&mut self, updated_at: i32) { + self.updated_at.update(updated_at) + } + + pub fn get_updated_at(&self) -> &Option { + self.updated_at.get() } } - fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { - match field { - - USER_ID => { - attr_field_access::attr_set_binary::<_, _>(self.user_id.get_mut(), binary.as_ref())?; - } - - NAME => { - attr_field_access::attr_set_binary::<_, _>(self.name.get_mut(), binary.as_ref())?; - } - - PHONE => { - attr_field_access::attr_set_binary::<_, _>(self.phone.get_mut(), binary.as_ref())?; - } - - EMAIL => { - attr_field_access::attr_set_binary::<_, _>(self.email.get_mut(), binary.as_ref())?; - } - - PASSWORD => { - attr_field_access::attr_set_binary::<_, _>(self.password.get_mut(), binary.as_ref())?; - } - - CREATED_AT => { - attr_field_access::attr_set_binary::<_, _>(self.created_at.get_mut(), binary.as_ref())?; + impl Datum for Users { + fn dat_type() -> &'static DatType { + lazy_static! { + static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); } - - UPDATED_AT => { - attr_field_access::attr_set_binary::<_, _>(self.updated_at.get_mut(), binary.as_ref())?; - } - - _ => { panic!("unknown name"); } + &DAT_TYPE + } + + fn from_binary(binary: &[u8]) -> RS { + entity_utils::entity_from_binary(binary) + } + + fn from_value(value: &DatValue) -> RS { + entity_utils::entity_from_value(value) + } + + fn from_textual(textual: &str) -> RS { + entity_utils::entity_from_textual(textual) } - Ok(()) } - fn get_field_value(&self, field: &str) -> RS> { - match field { - - USER_ID => { - attr_field_access::attr_get_value::<_>(self.user_id.get()) - } - - NAME => { - attr_field_access::attr_get_value::<_>(self.name.get()) - } - - PHONE => { - attr_field_access::attr_get_value::<_>(self.phone.get()) - } - - EMAIL => { - attr_field_access::attr_get_value::<_>(self.email.get()) - } - - PASSWORD => { - attr_field_access::attr_get_value::<_>(self.password.get()) - } - - CREATED_AT => { - attr_field_access::attr_get_value::<_>(self.created_at.get()) - } - - UPDATED_AT => { - attr_field_access::attr_get_value::<_>(self.updated_at.get()) - } - - _ => { panic!("unknown name"); } + impl DatumDyn for Users { + fn dat_type_id(&self) -> RS { + entity_utils::entity_dat_type_id() + } + + fn to_binary(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_binary(self, dat_type) + } + + fn to_textual(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_textual(self, dat_type) + } + + fn to_value(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_value(self, dat_type) + } + + fn clone_boxed(&self) -> Box { + entity_utils::entity_clone_boxed(self) } } - fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { - match field { - - USER_ID => { - attr_field_access::attr_set_value::<_, _>(self.user_id.get_mut(), value)?; - } - - NAME => { - attr_field_access::attr_set_value::<_, _>(self.name.get_mut(), value)?; - } - - PHONE => { - attr_field_access::attr_set_value::<_, _>(self.phone.get_mut(), value)?; - } - - EMAIL => { - attr_field_access::attr_set_value::<_, _>(self.email.get_mut(), value)?; + impl Entity for Users { + fn new_empty() -> Self { + Self::new_empty() + } + + fn tuple_desc() -> &'static TupleFieldDesc { + lazy_static! { + static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ + AttrUserId::datum_desc().clone(), + AttrName::datum_desc().clone(), + AttrPhone::datum_desc().clone(), + AttrEmail::datum_desc().clone(), + AttrPassword::datum_desc().clone(), + AttrCreatedAt::datum_desc().clone(), + AttrUpdatedAt::datum_desc().clone(), + ]); } - - PASSWORD => { - attr_field_access::attr_set_value::<_, _>(self.password.get_mut(), value)?; + &TUPLE_DESC + } + + fn object_name() -> &'static str { + USERS + } + + fn get_field_binary(&self, field: &str) -> RS>> { + match field { + USER_ID => attr_field_access::attr_get_binary::<_>(self.user_id.get()), + + NAME => attr_field_access::attr_get_binary::<_>(self.name.get()), + + PHONE => attr_field_access::attr_get_binary::<_>(self.phone.get()), + + EMAIL => attr_field_access::attr_get_binary::<_>(self.email.get()), + + PASSWORD => attr_field_access::attr_get_binary::<_>(self.password.get()), + + CREATED_AT => attr_field_access::attr_get_binary::<_>(self.created_at.get()), + + UPDATED_AT => attr_field_access::attr_get_binary::<_>(self.updated_at.get()), + + _ => { + panic!("unknown name"); + } } - - CREATED_AT => { - attr_field_access::attr_set_value::<_, _>(self.created_at.get_mut(), value)?; + } + + fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { + match field { + USER_ID => { + attr_field_access::attr_set_binary::<_, _>( + self.user_id.get_mut(), + binary.as_ref(), + )?; + } + + NAME => { + attr_field_access::attr_set_binary::<_, _>( + self.name.get_mut(), + binary.as_ref(), + )?; + } + + PHONE => { + attr_field_access::attr_set_binary::<_, _>( + self.phone.get_mut(), + binary.as_ref(), + )?; + } + + EMAIL => { + attr_field_access::attr_set_binary::<_, _>( + self.email.get_mut(), + binary.as_ref(), + )?; + } + + PASSWORD => { + attr_field_access::attr_set_binary::<_, _>( + self.password.get_mut(), + binary.as_ref(), + )?; + } + + CREATED_AT => { + attr_field_access::attr_set_binary::<_, _>( + self.created_at.get_mut(), + binary.as_ref(), + )?; + } + + UPDATED_AT => { + attr_field_access::attr_set_binary::<_, _>( + self.updated_at.get_mut(), + binary.as_ref(), + )?; + } + + _ => { + panic!("unknown name"); + } } - - UPDATED_AT => { - attr_field_access::attr_set_value::<_, _>(self.updated_at.get_mut(), value)?; + Ok(()) + } + + fn get_field_value(&self, field: &str) -> RS> { + match field { + USER_ID => attr_field_access::attr_get_value::<_>(self.user_id.get()), + + NAME => attr_field_access::attr_get_value::<_>(self.name.get()), + + PHONE => attr_field_access::attr_get_value::<_>(self.phone.get()), + + EMAIL => attr_field_access::attr_get_value::<_>(self.email.get()), + + PASSWORD => attr_field_access::attr_get_value::<_>(self.password.get()), + + CREATED_AT => attr_field_access::attr_get_value::<_>(self.created_at.get()), + + UPDATED_AT => attr_field_access::attr_get_value::<_>(self.updated_at.get()), + + _ => { + panic!("unknown name"); + } } - - _ => { panic!("unknown name"); } } - Ok(()) - } -} + fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { + match field { + USER_ID => { + attr_field_access::attr_set_value::<_, _>(self.user_id.get_mut(), value)?; + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrUserId { - is_dirty:bool, - value: Option -} + NAME => { + attr_field_access::attr_set_value::<_, _>(self.name.get_mut(), value)?; + } + + PHONE => { + attr_field_access::attr_set_value::<_, _>(self.phone.get_mut(), value)?; + } + + EMAIL => { + attr_field_access::attr_set_value::<_, _>(self.email.get_mut(), value)?; + } + + PASSWORD => { + attr_field_access::attr_set_value::<_, _>(self.password.get_mut(), value)?; + } + + CREATED_AT => { + attr_field_access::attr_set_value::<_, _>(self.created_at.get_mut(), value)?; + } -impl AttrUserId { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + UPDATED_AT => { + attr_field_access::attr_set_value::<_, _>(self.updated_at.get_mut(), value)?; + } + + _ => { + panic!("unknown name"); + } + } + Ok(()) } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrUserId { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrUserId { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrUserId { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrUserId { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - USER_ID - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrName { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + USERS + } -impl AttrName { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + USER_ID } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrName { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrName { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: String) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrName { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: String) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrName { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - NAME - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrPhone { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + USERS + } -impl AttrPhone { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + NAME } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrPhone { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrPhone { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: String) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrPhone { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: String) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrPhone { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - PHONE - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrEmail { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + USERS + } -impl AttrEmail { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + PHONE } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrEmail { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrEmail { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: String) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrEmail { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: String) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrEmail { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - EMAIL - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrPassword { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + USERS + } -impl AttrPassword { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + EMAIL } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrPassword { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrPassword { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: String) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrPassword { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: String) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrPassword { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - PASSWORD - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrCreatedAt { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + USERS + } -impl AttrCreatedAt { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + PASSWORD } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrCreatedAt { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrCreatedAt { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrCreatedAt { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrCreatedAt { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - CREATED_AT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrUpdatedAt { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + USERS + } -impl AttrUpdatedAt { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + CREATED_AT } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrUpdatedAt { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrUpdatedAt { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrUpdatedAt { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - USERS - } + impl AttrValue for AttrUpdatedAt { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - UPDATED_AT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } + fn object_name() -> &'static str { + USERS + } + fn attr_name() -> &'static str { + UPDATED_AT + } + } } diff --git a/example/wallet/src/rust/wallets.rs b/example/wallet/src/rust/wallets.rs index dd775bc..420d8d6 100644 --- a/example/wallet/src/rust/wallets.rs +++ b/example/wallet/src/rust/wallets.rs @@ -1,417 +1,384 @@ pub mod object { -use lazy_static::lazy_static; -use mudu::common::result::RS; -use mudu_type::dat_binary::DatBinary; -use mudu_type::dat_textual::DatTextual; -use mudu_type::dat_type::DatType; -use mudu_type::dat_type_id::DatTypeID; -use mudu_type::dat_value::DatValue; -use mudu_type::datum::{Datum, DatumDyn}; -use mudu_contract::database::attr_field_access; -use mudu_contract::database::attr_value::AttrValue; -use mudu_contract::database::entity::Entity; -use mudu_contract::database::entity_utils; -use mudu_contract::tuple::datum_desc::DatumDesc; -use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; -use mudu_contract::tuple::tuple_datum::TupleDatumMarker; -use mudu_contract::database::sql_params::SQLParamMarker; - -// constant definition -const WALLETS:&str = "wallets"; - -const USER_ID:&str = "user_id"; - -const BALANCE:&str = "balance"; - -const UPDATED_AT:&str = "updated_at"; - - -// entity struct definition -#[derive(Debug, Clone, Default)] -pub struct Wallets { - - user_id: AttrUserId, - - balance: AttrBalance, - - updated_at: AttrUpdatedAt, - -} + use lazy_static::lazy_static; + use mudu::common::result::RS; + use mudu_contract::database::attr_field_access; + use mudu_contract::database::attr_value::AttrValue; + use mudu_contract::database::entity::Entity; + use mudu_contract::database::entity_utils; + use mudu_contract::database::sql_params::SQLParamMarker; + use mudu_contract::tuple::datum_desc::DatumDesc; + use mudu_contract::tuple::tuple_datum::TupleDatumMarker; + use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; + use mudu_type::dat_binary::DatBinary; + use mudu_type::dat_textual::DatTextual; + use mudu_type::dat_type::DatType; + use mudu_type::dat_type_id::DatTypeID; + use mudu_type::dat_value::DatValue; + use mudu_type::datum::{Datum, DatumDyn}; -impl TupleDatumMarker for Wallets {} - -impl SQLParamMarker for Wallets {} - -impl Wallets { - pub fn new( - user_id: Option, - balance: Option, - updated_at: Option, - - ) -> Self { - let s = Self { - - user_id : AttrUserId::from(user_id), - - balance : AttrBalance::from(balance), - - updated_at : AttrUpdatedAt::from(updated_at), - - }; - s - } + // constant definition + const WALLETS: &str = "wallets"; - pub fn new_empty() -> Self { - Self::default() - } + const USER_ID: &str = "user_id"; - - pub fn set_user_id( - &mut self, - user_id: i32, - ) { - self.user_id.update(user_id) - } + const BALANCE: &str = "balance"; - pub fn get_user_id( - &self, - ) -> &Option { - self.user_id.get() - } - - pub fn set_balance( - &mut self, - balance: i32, - ) { - self.balance.update(balance) - } + const UPDATED_AT: &str = "updated_at"; - pub fn get_balance( - &self, - ) -> &Option { - self.balance.get() - } - - pub fn set_updated_at( - &mut self, - updated_at: i32, - ) { - self.updated_at.update(updated_at) - } + // entity struct definition + #[derive(Debug, Clone, Default)] + pub struct Wallets { + user_id: AttrUserId, - pub fn get_updated_at( - &self, - ) -> &Option { - self.updated_at.get() - } - -} + balance: AttrBalance, -impl Datum for Wallets { - fn dat_type() -> &'static DatType { - lazy_static! { - static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); - } - &DAT_TYPE + updated_at: AttrUpdatedAt, } - fn from_binary(binary: &[u8]) -> RS { - entity_utils::entity_from_binary(binary) - } + impl TupleDatumMarker for Wallets {} - fn from_value(value: &DatValue) -> RS { - entity_utils::entity_from_value(value) - } + impl SQLParamMarker for Wallets {} - fn from_textual(textual: &str) -> RS { - entity_utils::entity_from_textual(textual) - } -} + impl Wallets { + pub fn new(user_id: Option, balance: Option, updated_at: Option) -> Self { + let s = Self { + user_id: AttrUserId::from(user_id), -impl DatumDyn for Wallets { - fn dat_type_id(&self) -> RS { - entity_utils::entity_dat_type_id() - } + balance: AttrBalance::from(balance), - fn to_binary(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_binary(self, dat_type) - } + updated_at: AttrUpdatedAt::from(updated_at), + }; + s + } - fn to_textual(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_textual(self, dat_type) - } + pub fn new_empty() -> Self { + Self::default() + } - fn to_value(&self, dat_type: &DatType) -> RS { - entity_utils::entity_to_value(self, dat_type) - } + pub fn set_user_id(&mut self, user_id: i32) { + self.user_id.update(user_id) + } - fn clone_boxed(&self) -> Box { - entity_utils::entity_clone_boxed(self) - } -} + pub fn get_user_id(&self) -> &Option { + self.user_id.get() + } -impl Entity for Wallets { - fn new_empty() -> Self { - Self::new_empty() - } + pub fn set_balance(&mut self, balance: i32) { + self.balance.update(balance) + } - fn tuple_desc() -> &'static TupleFieldDesc { - lazy_static! { - static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ - - AttrUserId::datum_desc().clone(), - - AttrBalance::datum_desc().clone(), - - AttrUpdatedAt::datum_desc().clone(), - - ]); - } - &TUPLE_DESC - } + pub fn get_balance(&self) -> &Option { + self.balance.get() + } + + pub fn set_updated_at(&mut self, updated_at: i32) { + self.updated_at.update(updated_at) + } - fn object_name() -> &'static str { - WALLETS + pub fn get_updated_at(&self) -> &Option { + self.updated_at.get() + } } - fn get_field_binary(&self, field: &str) -> RS>> { - match field { - - USER_ID => { - attr_field_access::attr_get_binary::<_>(self.user_id.get()) + impl Datum for Wallets { + fn dat_type() -> &'static DatType { + lazy_static! { + static ref DAT_TYPE: DatType = entity_utils::entity_dat_type::(); } - - BALANCE => { - attr_field_access::attr_get_binary::<_>(self.balance.get()) - } - - UPDATED_AT => { - attr_field_access::attr_get_binary::<_>(self.updated_at.get()) - } - - _ => { panic!("unknown name"); } + &DAT_TYPE + } + + fn from_binary(binary: &[u8]) -> RS { + entity_utils::entity_from_binary(binary) + } + + fn from_value(value: &DatValue) -> RS { + entity_utils::entity_from_value(value) + } + + fn from_textual(textual: &str) -> RS { + entity_utils::entity_from_textual(textual) } } - fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { - match field { - - USER_ID => { - attr_field_access::attr_set_binary::<_, _>(self.user_id.get_mut(), binary.as_ref())?; - } - - BALANCE => { - attr_field_access::attr_set_binary::<_, _>(self.balance.get_mut(), binary.as_ref())?; - } - - UPDATED_AT => { - attr_field_access::attr_set_binary::<_, _>(self.updated_at.get_mut(), binary.as_ref())?; - } - - _ => { panic!("unknown name"); } + impl DatumDyn for Wallets { + fn dat_type_id(&self) -> RS { + entity_utils::entity_dat_type_id() + } + + fn to_binary(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_binary(self, dat_type) + } + + fn to_textual(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_textual(self, dat_type) + } + + fn to_value(&self, dat_type: &DatType) -> RS { + entity_utils::entity_to_value(self, dat_type) + } + + fn clone_boxed(&self) -> Box { + entity_utils::entity_clone_boxed(self) } - Ok(()) } - fn get_field_value(&self, field: &str) -> RS> { - match field { - - USER_ID => { - attr_field_access::attr_get_value::<_>(self.user_id.get()) - } - - BALANCE => { - attr_field_access::attr_get_value::<_>(self.balance.get()) - } - - UPDATED_AT => { - attr_field_access::attr_get_value::<_>(self.updated_at.get()) + impl Entity for Wallets { + fn new_empty() -> Self { + Self::new_empty() + } + + fn tuple_desc() -> &'static TupleFieldDesc { + lazy_static! { + static ref TUPLE_DESC: TupleFieldDesc = TupleFieldDesc::new(vec![ + AttrUserId::datum_desc().clone(), + AttrBalance::datum_desc().clone(), + AttrUpdatedAt::datum_desc().clone(), + ]); } - - _ => { panic!("unknown name"); } + &TUPLE_DESC } - } - fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { - match field { - - USER_ID => { - attr_field_access::attr_set_value::<_, _>(self.user_id.get_mut(), value)?; + fn object_name() -> &'static str { + WALLETS + } + + fn get_field_binary(&self, field: &str) -> RS>> { + match field { + USER_ID => attr_field_access::attr_get_binary::<_>(self.user_id.get()), + + BALANCE => attr_field_access::attr_get_binary::<_>(self.balance.get()), + + UPDATED_AT => attr_field_access::attr_get_binary::<_>(self.updated_at.get()), + + _ => { + panic!("unknown name"); + } } - - BALANCE => { - attr_field_access::attr_set_value::<_, _>(self.balance.get_mut(), value)?; + } + + fn set_field_binary>(&mut self, field: &str, binary: B) -> RS<()> { + match field { + USER_ID => { + attr_field_access::attr_set_binary::<_, _>( + self.user_id.get_mut(), + binary.as_ref(), + )?; + } + + BALANCE => { + attr_field_access::attr_set_binary::<_, _>( + self.balance.get_mut(), + binary.as_ref(), + )?; + } + + UPDATED_AT => { + attr_field_access::attr_set_binary::<_, _>( + self.updated_at.get_mut(), + binary.as_ref(), + )?; + } + + _ => { + panic!("unknown name"); + } } - - UPDATED_AT => { - attr_field_access::attr_set_value::<_, _>(self.updated_at.get_mut(), value)?; + Ok(()) + } + + fn get_field_value(&self, field: &str) -> RS> { + match field { + USER_ID => attr_field_access::attr_get_value::<_>(self.user_id.get()), + + BALANCE => attr_field_access::attr_get_value::<_>(self.balance.get()), + + UPDATED_AT => attr_field_access::attr_get_value::<_>(self.updated_at.get()), + + _ => { + panic!("unknown name"); + } } - - _ => { panic!("unknown name"); } } - Ok(()) - } -} + fn set_field_value>(&mut self, field: &str, value: B) -> RS<()> { + match field { + USER_ID => { + attr_field_access::attr_set_value::<_, _>(self.user_id.get_mut(), value)?; + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrUserId { - is_dirty:bool, - value: Option -} + BALANCE => { + attr_field_access::attr_set_value::<_, _>(self.balance.get_mut(), value)?; + } + + UPDATED_AT => { + attr_field_access::attr_set_value::<_, _>(self.updated_at.get_mut(), value)?; + } -impl AttrUserId { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + _ => { + panic!("unknown name"); + } + } + Ok(()) } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrUserId { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrUserId { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrUserId { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - WALLETS - } + impl AttrValue for AttrUserId { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - USER_ID - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrBalance { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + WALLETS + } -impl AttrBalance { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + USER_ID } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrBalance { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrBalance { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrBalance { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - WALLETS - } + impl AttrValue for AttrBalance { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - BALANCE - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } -// attribute struct definition -#[derive(Default, Clone, Debug)] -pub struct AttrUpdatedAt { - is_dirty:bool, - value: Option -} + fn object_name() -> &'static str { + WALLETS + } -impl AttrUpdatedAt { - fn from(value:Option) -> Self { - Self { - is_dirty: false, - value + fn attr_name() -> &'static str { + BALANCE } } - fn get(&self) -> &Option { - &self.value + // attribute struct definition + #[derive(Default, Clone, Debug)] + pub struct AttrUpdatedAt { + is_dirty: bool, + value: Option, } - fn get_mut(&mut self) -> &mut Option { - &mut self.value - } + impl AttrUpdatedAt { + fn from(value: Option) -> Self { + Self { + is_dirty: false, + value, + } + } - fn set(&mut self, value:Option) { - self.value = value - } + fn get(&self) -> &Option { + &self.value + } - fn update(&mut self, value: i32) { - self.is_dirty = true; - self.value = Some(value) - } -} + fn get_mut(&mut self) -> &mut Option { + &mut self.value + } -impl AttrValue for AttrUpdatedAt { - fn dat_type() -> &'static DatType { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) - } + fn set(&mut self, value: Option) { + self.value = value + } - fn datum_desc() -> &'static DatumDesc { - static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + fn update(&mut self, value: i32) { + self.is_dirty = true; + self.value = Some(value) + } } - fn object_name() -> &'static str { - WALLETS - } + impl AttrValue for AttrUpdatedAt { + fn dat_type() -> &'static DatType { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_dat_type()) + } - fn attr_name() -> &'static str { - UPDATED_AT - } -} + fn datum_desc() -> &'static DatumDesc { + static ONCE_LOCK: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE_LOCK.get_or_init(|| Self::attr_datum_desc()) + } + fn object_name() -> &'static str { + WALLETS + } + fn attr_name() -> &'static str { + UPDATED_AT + } + } } diff --git a/example/wallet/src/testing/procedure.rs b/example/wallet/src/testing/procedure.rs index 27d8f70..28a2c08 100644 --- a/example/wallet/src/testing/procedure.rs +++ b/example/wallet/src/testing/procedure.rs @@ -6,20 +6,25 @@ use mudu_contract::database::entity_set::RecordSet; use mudu_contract::{sql_params, sql_stmt}; use std::path::PathBuf; use std::sync::{Mutex, OnceLock}; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::time::UNIX_EPOCH; use sys_interface::sync_api::{mudu_batch, mudu_close, mudu_open, mudu_query}; static TEST_MUTEX: OnceLock> = OnceLock::new(); #[test] fn create_update_and_delete_user() { - let _guard = test_mutex().lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + let _guard = test_mutex() + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); let mut db = TestDb::new(); let xid = db.open_session(); procedures::create_user(xid, 3, "Carol".to_string(), "carol@example.com".to_string()).unwrap(); - assert_eq!(db.query_count("SELECT COUNT(*) FROM users WHERE user_id = ?", &(3,)), 1); + assert_eq!( + db.query_count("SELECT COUNT(*) FROM users WHERE user_id = ?", &(3,)), + 1 + ); assert_eq!( db.query_string("SELECT name FROM users WHERE user_id = ?", &(3,)), Some("Carol".to_string()) @@ -41,13 +46,18 @@ fn create_update_and_delete_user() { ); procedures::delete_user(xid, 3).unwrap(); - assert_eq!(db.query_count("SELECT COUNT(*) FROM users WHERE user_id = ?", &(3,)), 0); + assert_eq!( + db.query_count("SELECT COUNT(*) FROM users WHERE user_id = ?", &(3,)), + 0 + ); assert!(db.query_wallet(3).is_none()); } #[test] fn delete_user_rejects_non_zero_balance() { - let _guard = test_mutex().lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + let _guard = test_mutex() + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); let mut db = TestDb::new(); let xid = db.open_session(); @@ -60,7 +70,9 @@ fn delete_user_rejects_non_zero_balance() { #[test] fn transfer_funds_moves_balance_and_writes_transaction() { - let _guard = test_mutex().lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + let _guard = test_mutex() + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); let mut db = TestDb::new(); let xid = db.open_session(); @@ -79,7 +91,9 @@ fn transfer_funds_moves_balance_and_writes_transaction() { #[test] fn transfer_rejects_self_transfer() { - let _guard = test_mutex().lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + let _guard = test_mutex() + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); let mut db = TestDb::new(); let xid = db.open_session(); @@ -89,7 +103,9 @@ fn transfer_rejects_self_transfer() { #[test] fn deposit_withdraw_and_purchase_update_balance_and_transactions() { - let _guard = test_mutex().lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + let _guard = test_mutex() + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); let mut db = TestDb::new(); let xid = db.open_session(); @@ -98,7 +114,7 @@ fn deposit_withdraw_and_purchase_update_balance_and_transactions() { procedures::purchase(xid, 1, 50, "book".to_string()).unwrap(); assert_eq!(db.query_wallet(1).unwrap().get_balance(), &Some(10100)); - assert_eq!( + assert_eq!( db.query_count( "SELECT COUNT(*) FROM transactions WHERE trans_type = ?", &(String::from("DEPOSIT"),), @@ -123,7 +139,9 @@ fn deposit_withdraw_and_purchase_update_balance_and_transactions() { #[test] fn withdraw_rejects_insufficient_funds() { - let _guard = test_mutex().lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + let _guard = test_mutex() + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); let mut db = TestDb::new(); let xid = db.open_session(); @@ -187,7 +205,11 @@ impl TestDb { rs.next_record().unwrap() } - fn query_count(&self, sql: &str, params: &P) -> i64 { + fn query_count( + &self, + sql: &str, + params: &P, + ) -> i64 { let rs = mudu_query::(self.current_session(), sql_stmt!(&sql), params).unwrap(); rs.next_record().unwrap().unwrap() } @@ -201,7 +223,10 @@ impl TestDb { rs.next_record().unwrap() } - fn query_records( + fn query_records< + R: mudu_contract::database::entity::Entity, + P: mudu_contract::database::sql_params::SQLParams, + >( &self, sql: &str, params: &P, @@ -227,10 +252,9 @@ impl Drop for TestDb { } fn unique_db_path() -> PathBuf { - let nanos = SystemTime::now() + let nanos = mudu_sys::time::system_time_now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); std::env::temp_dir().join(format!("wallet-procedure-test-{nanos}.db")) } - diff --git a/example/ycsb/Cargo.toml b/example/ycsb/Cargo.toml index 9aa3118..9907fd6 100644 --- a/example/ycsb/Cargo.toml +++ b/example/ycsb/Cargo.toml @@ -23,6 +23,7 @@ benchmark-runner = [ [dependencies] mudu = { workspace = true } +mudu_sys = { workspace = true } mudu_type = { workspace = true } mudu_contract = { workspace = true } mudu_binding = { workspace = true } diff --git a/example/ycsb/src/bin/ycsb_benchmark.rs b/example/ycsb/src/bin/ycsb_benchmark.rs index 05b695b..31adb8b 100644 --- a/example/ycsb/src/bin/ycsb_benchmark.rs +++ b/example/ycsb/src/bin/ycsb_benchmark.rs @@ -2,15 +2,15 @@ use clap::Parser; use mudu::common::result::RS; use mudu::common::xid::XID; use mudu_binding::universal::uni_session_open_argv::UniSessionOpenArgv; -use mudu_cli::management::{ServerTopology, fetch_server_topology}; +use mudu_cli::management::{fetch_server_topology, ServerTopology}; use mudu_contract::database::sql_stmt_text::SQLStmtText; use mudu_utils::debug::debug_serve; use mudu_utils::notifier::NotifyWait; use mudu_utils::task::spawn_task; use mudu_utils::task_trace; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::sync::Barrier as StdBarrier; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; use std::time::{Duration, Instant}; use sys_interface::async_api::{ @@ -161,14 +161,14 @@ impl ProgressReporter { let handle = thread::Builder::new() .name(format!("ycsb-progress-{}", phase.as_str())) .spawn(move || { - let start = Instant::now(); + let start = mudu_sys::time::instant_now(); let mut last_completed = 0; let mut last_report = start; loop { - thread::sleep(PROGRESS_REPORT_INTERVAL); + mudu_sys::task::sleep_blocking(PROGRESS_REPORT_INTERVAL); let started = thread_tracker.started.load(Ordering::Relaxed).min(total); let completed = thread_tracker.completed.load(Ordering::Relaxed).min(total); - let now = Instant::now(); + let now = mudu_sys::time::instant_now(); let delta = completed.saturating_sub(last_completed); let delta_secs = now.duration_since(last_report).as_secs_f64().max(0.000_001); let total_secs = now.duration_since(start).as_secs_f64().max(0.000_001); @@ -319,7 +319,7 @@ fn run_sync_mode(args: Args) -> RS<()> { let value_template = Arc::new(build_value(args.field_length)); let routing = Arc::new(load_partition_routing_sync(partition_count)?); - let load_start = Instant::now(); + let load_start = mudu_sys::time::instant_now(); run_workers_sync( &args, connection_count, @@ -329,7 +329,7 @@ fn run_sync_mode(args: Args) -> RS<()> { )?; let load_elapsed = load_start.elapsed(); - let run_start = Instant::now(); + let run_start = mudu_sys::time::instant_now(); let counters = run_workers_sync( &args, connection_count, @@ -356,7 +356,7 @@ async fn run_async(args: Args) -> RS<()> { let value_template = Arc::new(build_value(args.field_length)); let routing = Arc::new(load_partition_routing_async(partition_count).await?); - let load_start = Instant::now(); + let load_start = mudu_sys::time::instant_now(); run_workers_async( &args, connection_count, @@ -367,7 +367,7 @@ async fn run_async(args: Args) -> RS<()> { .await?; let load_elapsed = load_start.elapsed(); - let run_start = Instant::now(); + let run_start = mudu_sys::time::instant_now(); let counters = run_workers_async( &args, connection_count, @@ -901,18 +901,21 @@ async fn run_run_phase_async( mark_progress_started(progress, 1); let op = choose_op(&args.workload, rng); let result = if transaction_enabled(args, WorkerPhase::Run) { - run_in_transaction_async(xid, execute_run_op_async( + run_in_transaction_async( xid, - counters, - rng, - connection_count, - partition_id, - args, - value_template, - next_insert, - &mut owned_keys, - op, - )) + execute_run_op_async( + xid, + counters, + rng, + connection_count, + partition_id, + args, + value_template, + next_insert, + &mut owned_keys, + op, + ), + ) .await } else { execute_run_op_async( diff --git a/example/ycsb/src/rust/procedure_async.rs b/example/ycsb/src/rust/procedure_async.rs index 0fc823c..54d2654 100644 --- a/example/ycsb/src/rust/procedure_async.rs +++ b/example/ycsb/src/rust/procedure_async.rs @@ -3,9 +3,9 @@ use mudu::common::result::RS; use mudu::common::xid::XID; use mudu::error::ec::EC; use mudu::m_error; -use sys_interface::async_api::{mudu_get, mudu_put, mudu_range}; #[cfg(feature = "benchmark-runner")] use mudu_utils::task_trace; +use sys_interface::async_api::{mudu_get, mudu_put, mudu_range}; #[cfg(not(feature = "benchmark-runner"))] macro_rules! task_trace { diff --git a/mudu/src/common/crc.rs b/mudu/src/common/crc.rs index 671b74b..cddfecb 100644 --- a/mudu/src/common/crc.rs +++ b/mudu/src/common/crc.rs @@ -1,6 +1,19 @@ -use crc::{CRC_64_XZ, Crc}; -const CRC64XZ: Crc = Crc::::new(&CRC_64_XZ); +const CRC64XZ: crc::Crc = crc::Crc::::new(&crc::CRC_64_XZ); +const CRC32: crc::Crc = crc::Crc::::new(&crc::CRC_32_ISCSI); +const CRC16: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_SDLC); pub fn calc_crc(bytes: &[u8]) -> u64 { CRC64XZ.checksum(bytes) } + +pub fn crc64(bytes: &[u8]) -> u64 { + CRC64XZ.checksum(bytes) +} + +pub fn crc32(bytes: &[u8]) -> u32 { + CRC32.checksum(bytes) +} + +pub fn crc16(bytes: &[u8]) -> u16 { + CRC16.checksum(bytes) +} diff --git a/mudu/src/common/id.rs b/mudu/src/common/id.rs index e47e5e2..517365c 100644 --- a/mudu/src/common/id.rs +++ b/mudu/src/common/id.rs @@ -3,6 +3,10 @@ use uuid::Uuid; /// unique object id pub type OID = u128; +// Nth attribute index of data tuple +pub type AttrIndex = usize; + +pub type TupleID = u64; pub type ThdID = u64; pub const INVALID_OID: OID = 0; diff --git a/mudu_adapter/Cargo.toml b/mudu_adapter/Cargo.toml index 44cdc33..08d08d3 100644 --- a/mudu_adapter/Cargo.toml +++ b/mudu_adapter/Cargo.toml @@ -18,10 +18,7 @@ tokio-postgres = { workspace = true } mysql = { workspace = true } mysql_async = { workspace = true } scc = { workspace = true } -serde = { workspace = true } serde_json = { workspace = true } -rmpv = { workspace = true } -rmp-serde = { workspace = true } tokio = { workspace = true } lazy_static = { workspace = true } async-backtrace = { workspace = true, optional = true } diff --git a/mudu_adapter/src/backend.rs b/mudu_adapter/src/backend.rs index 07cd112..7747817 100644 --- a/mudu_adapter/src/backend.rs +++ b/mudu_adapter/src/backend.rs @@ -180,11 +180,7 @@ pub async fn mudu_command_async( } } -pub async fn mudu_batch_async( - oid: OID, - sql_stmt: &dyn SQLStmt, - params: &dyn SQLParams, -) -> RS { +pub async fn mudu_batch_async(oid: OID, sql_stmt: &dyn SQLStmt, params: &dyn SQLParams) -> RS { match config::driver() { Driver::Sqlite => sqlite::mudu_batch_async(oid, sql_stmt, params).await, Driver::Postgres | Driver::MySql | Driver::Mudud => Err(mudu::m_error!( diff --git a/mudu_adapter/src/syscall.rs b/mudu_adapter/src/syscall.rs index f283ec4..f1b6cd1 100644 --- a/mudu_adapter/src/syscall.rs +++ b/mudu_adapter/src/syscall.rs @@ -117,11 +117,7 @@ pub async fn mudu_command_async( backend::mudu_command_async(oid, sql_stmt, params).await } -pub async fn mudu_batch_async( - oid: OID, - sql_stmt: &dyn SQLStmt, - params: &dyn SQLParams, -) -> RS { +pub async fn mudu_batch_async(oid: OID, sql_stmt: &dyn SQLStmt, params: &dyn SQLParams) -> RS { let _trace = mudu_utils::task_trace!(); backend::mudu_batch_async(oid, sql_stmt, params).await } diff --git a/mudu_binding/src/codec/handle_sys_session.rs b/mudu_binding/src/codec/handle_sys_session.rs index 6bd295d..926158a 100644 --- a/mudu_binding/src/codec/handle_sys_session.rs +++ b/mudu_binding/src/codec/handle_sys_session.rs @@ -1,7 +1,7 @@ +use crate::universal::uni_session_open_argv::UniSessionOpenArgv; use mudu::common::endian::{read_u128, write_u128}; use mudu::common::id::OID; use mudu::common::result::RS; -use crate::universal::uni_session_open_argv::UniSessionOpenArgv; use std::mem::size_of; fn write_u32_be(output: &mut Vec, value: u32) { @@ -155,6 +155,30 @@ pub fn deserialize_put_result(input: &[u8]) -> RS<()> { } } +pub fn serialize_delete_param(key: &[u8]) -> Vec { + serialize_session_get_param(0, key) +} + +pub fn serialize_session_delete_param(session_id: OID, key: &[u8]) -> Vec { + serialize_session_get_param(session_id, key) +} + +pub fn deserialize_delete_param(input: &[u8]) -> RS> { + deserialize_get_param(input) +} + +pub fn deserialize_session_delete_param(input: &[u8]) -> RS<(OID, Vec)> { + deserialize_session_get_param(input) +} + +pub fn serialize_delete_result() -> Vec { + serialize_put_result() +} + +pub fn deserialize_delete_result(input: &[u8]) -> RS<()> { + deserialize_put_result(input) +} + pub fn serialize_range_param(start_key: &[u8], end_key: &[u8]) -> Vec { serialize_session_range_param(0, start_key, end_key) } diff --git a/mudu_cli/src/client/json_client.rs b/mudu_cli/src/client/json_client.rs index 5a753e8..2abf810 100644 --- a/mudu_cli/src/client/json_client.rs +++ b/mudu_cli/src/client/json_client.rs @@ -1,11 +1,11 @@ use crate::client::async_client::{AsyncClient, AsyncClientImpl}; use base64::Engine; -use mudu_binding::universal::uni_dat_value::UniDatValue; -use mudu_binding::universal::uni_oid::UniOid; -use mudu_binding::universal::uni_primitive_value::UniPrimitiveValue; use mudu::common::result::RS; use mudu::error::ec::EC; use mudu::m_error; +use mudu_binding::universal::uni_dat_value::UniDatValue; +use mudu_binding::universal::uni_oid::UniOid; +use mudu_binding::universal::uni_primitive_value::UniPrimitiveValue; use mudu_contract::protocol::{ ClientRequest, GetRequest, KeyValue, ProcedureInvokeRequest, PutRequest, RangeScanRequest, }; @@ -181,21 +181,29 @@ fn json_value_to_uni_dat_value(value: Value) -> RS { serde_json::to_vec(&Value::Null) .map_err(|e| m_error!(EC::EncodeErr, "encode null payload error", e))?, )), - Value::Bool(inner) => Ok(UniDatValue::from_primitive(UniPrimitiveValue::from_bool(inner))), + Value::Bool(inner) => Ok(UniDatValue::from_primitive(UniPrimitiveValue::from_bool( + inner, + ))), Value::Number(inner) => { if let Some(value) = inner.as_i64() { - Ok(UniDatValue::from_primitive(UniPrimitiveValue::from_i64(value))) + Ok(UniDatValue::from_primitive(UniPrimitiveValue::from_i64( + value, + ))) } else if let Some(value) = inner.as_u64() { - Ok(UniDatValue::from_primitive(UniPrimitiveValue::from_u64(value))) + Ok(UniDatValue::from_primitive(UniPrimitiveValue::from_u64( + value, + ))) } else if let Some(value) = inner.as_f64() { - Ok(UniDatValue::from_primitive(UniPrimitiveValue::from_f64(value))) + Ok(UniDatValue::from_primitive(UniPrimitiveValue::from_f64( + value, + ))) } else { Err(m_error!(EC::DecodeErr, "unsupported numeric json payload")) } } - Value::String(inner) => Ok(UniDatValue::from_primitive( - UniPrimitiveValue::from_string(inner), - )), + Value::String(inner) => Ok(UniDatValue::from_primitive(UniPrimitiveValue::from_string( + inner, + ))), Value::Array(inner) => inner .into_iter() .map(json_value_to_uni_dat_value) @@ -299,8 +307,8 @@ mod tests { use async_trait::async_trait; use mudu_contract::protocol::{ GetResponse, KeyValue, ProcedureInvokeResponse, PutResponse, RangeScanResponse, - ServerResponse, - SessionCloseRequest, SessionCloseResponse, SessionCreateRequest, SessionCreateResponse, + ServerResponse, SessionCloseRequest, SessionCloseResponse, SessionCreateRequest, + SessionCreateResponse, }; struct MockAsyncIoUringTcpClient { diff --git a/mudu_contract/Cargo.toml b/mudu_contract/Cargo.toml index b02c96b..5305f82 100644 --- a/mudu_contract/Cargo.toml +++ b/mudu_contract/Cargo.toml @@ -8,6 +8,7 @@ mudu = { workspace = true } mudu_type = { workspace = true } serde_json = { workspace = true } serde = { workspace = true } +rmp-serde = { workspace = true } scc = { workspace = true } byteorder = { workspace = true } lazy_static = { workspace = true } diff --git a/mudu_contract/src/procedure/proc.rs b/mudu_contract/src/procedure/proc.rs index c0308e7..115e2d4 100644 --- a/mudu_contract/src/procedure/proc.rs +++ b/mudu_contract/src/procedure/proc.rs @@ -1,5 +1,3 @@ -pub const MUDU_PROC_PREFIX: &'static str = "mp1_"; - pub const MUDU_PROC_P2_PREFIX: &'static str = "mp2_"; pub const MUDU_PROC_P2_EXPORTED_PREFIX: &'static str = "mp2e_"; diff --git a/mudu_contract/src/protocol.rs b/mudu_contract/src/protocol.rs index b89c91a..98428a8 100644 --- a/mudu_contract/src/protocol.rs +++ b/mudu_contract/src/protocol.rs @@ -1,6 +1,7 @@ use mudu::common::result::RS; use mudu::error::ec::EC; use mudu::m_error; +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; pub const HEADER_LEN: usize = 20; @@ -22,6 +23,37 @@ pub enum MessageType { SessionClose = 12, } +impl From for u16 { + fn from(value: MessageType) -> Self { + value as u16 + } +} + +impl TryFrom for MessageType { + type Error = mudu::error::err::MError; + + fn try_from(value: u16) -> RS { + match value { + 1 => Ok(MessageType::Handshake), + 2 => Ok(MessageType::Auth), + 3 => Ok(MessageType::Query), + 4 => Ok(MessageType::Execute), + 5 => Ok(MessageType::Response), + 6 => Ok(MessageType::Error), + 7 => Ok(MessageType::Get), + 8 => Ok(MessageType::Put), + 9 => Ok(MessageType::RangeScan), + 10 => Ok(MessageType::ProcedureInvoke), + 11 => Ok(MessageType::SessionCreate), + 12 => Ok(MessageType::SessionClose), + _ => Err(m_error!( + EC::ParseErr, + format!("unknown message type {}", value) + )), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FrameHeader { magic: u16, @@ -133,23 +165,30 @@ pub struct Frame { impl Frame { pub fn new(message_type: MessageType, request_id: u64, payload: Vec) -> Self { Self { - header: FrameHeader { - magic: MAGIC, - version: 1, - message_type, - flags: 0, - request_id, - payload_len: payload.len() as u32, - }, + header: FrameHeader::new(message_type, request_id, payload.len() as u32), payload, } } + pub fn from_parts(header: FrameHeader, payload: Vec) -> RS { + if header.payload_len() as usize != payload.len() { + return Err(m_error!( + EC::ParseErr, + format!( + "frame payload length mismatch: header {}, actual {}", + header.payload_len(), + payload.len() + ) + )); + } + Ok(Self { header, payload }) + } + pub fn encode(&self) -> Vec { let mut out = Vec::with_capacity(HEADER_LEN + self.payload.len()); out.extend_from_slice(&self.header.magic.to_be_bytes()); out.extend_from_slice(&self.header.version.to_be_bytes()); - out.extend_from_slice(&(self.header.message_type as u16).to_be_bytes()); + out.extend_from_slice(&u16::from(self.header.message_type).to_be_bytes()); out.extend_from_slice(&self.header.flags.to_be_bytes()); out.extend_from_slice(&self.header.request_id.to_be_bytes()); out.extend_from_slice(&self.header.payload_len.to_be_bytes()); @@ -161,46 +200,13 @@ impl Frame { if buf.len() < HEADER_LEN { return Err(m_error!(EC::ParseErr, "frame header is incomplete")); } - let magic = u16::from_be_bytes([buf[0], buf[1]]); - if magic != MAGIC { - return Err(m_error!(EC::ParseErr, "invalid frame magic")); - } - let version = u16::from_be_bytes([buf[2], buf[3]]); - let message_type = match u16::from_be_bytes([buf[4], buf[5]]) { - 1 => MessageType::Handshake, - 2 => MessageType::Auth, - 3 => MessageType::Query, - 4 => MessageType::Execute, - 5 => MessageType::Response, - 6 => MessageType::Error, - 7 => MessageType::Get, - 8 => MessageType::Put, - 9 => MessageType::RangeScan, - 10 => MessageType::ProcedureInvoke, - 11 => MessageType::SessionCreate, - 12 => MessageType::SessionClose, - _ => return Err(m_error!(EC::ParseErr, "unknown message type")), - }; - let flags = u16::from_be_bytes([buf[6], buf[7]]); - let request_id = u64::from_be_bytes([ - buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15], - ]); - let payload_len = u32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]); + let header = FrameHeader::decode_header_bytes(&buf[..HEADER_LEN])?; + let payload_len = header.payload_len(); let total_len = HEADER_LEN + payload_len as usize; if buf.len() < total_len { return Err(m_error!(EC::ParseErr, "frame payload is incomplete")); } - Ok(Self { - header: FrameHeader { - magic, - version, - message_type, - flags, - request_id, - payload_len, - }, - payload: buf[HEADER_LEN..total_len].to_vec(), - }) + Self::from_parts(header, buf[HEADER_LEN..total_len].to_vec()) } pub fn header(&self) -> &FrameHeader { @@ -210,9 +216,49 @@ impl Frame { pub fn payload(&self) -> &[u8] { &self.payload } + + pub fn into_payload(self) -> Vec { + self.payload + } } impl FrameHeader { + pub fn new(message_type: MessageType, request_id: u64, payload_len: u32) -> Self { + Self { + magic: MAGIC, + version: 1, + message_type, + flags: 0, + request_id, + payload_len, + } + } + + pub fn decode_header_bytes(buf: &[u8]) -> RS { + if buf.len() < HEADER_LEN { + return Err(m_error!(EC::ParseErr, "frame header is incomplete")); + } + let magic = u16::from_be_bytes([buf[0], buf[1]]); + if magic != MAGIC { + return Err(m_error!(EC::ParseErr, "invalid frame magic")); + } + let version = u16::from_be_bytes([buf[2], buf[3]]); + let message_type = MessageType::try_from(u16::from_be_bytes([buf[4], buf[5]]))?; + let flags = u16::from_be_bytes([buf[6], buf[7]]); + let request_id = u64::from_be_bytes([ + buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15], + ]); + let payload_len = u32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]); + Ok(Self { + magic, + version, + message_type, + flags, + request_id, + payload_len, + }) + } + pub fn magic(&self) -> u16 { self.magic } @@ -502,8 +548,7 @@ pub fn encode_client_request_with_message_type( request_id: u64, request: &ClientRequest, ) -> RS> { - let payload = serde_json::to_vec(request) - .map_err(|e| m_error!(EC::EncodeErr, "encode client request error", e))?; + let payload = encode_payload(request, "encode client request error")?; Ok(Frame::new(message_type, request_id, payload).encode()) } @@ -512,70 +557,58 @@ pub fn encode_client_request(request_id: u64, request: &ClientRequest) -> RS RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode client request error", e)) + decode_payload(frame.payload(), "decode client request error") } pub fn encode_server_response(request_id: u64, response: &ServerResponse) -> RS> { - let payload = serde_json::to_vec(response) - .map_err(|e| m_error!(EC::EncodeErr, "encode server response error", e))?; + let payload = encode_payload(response, "encode server response error")?; Ok(Frame::new(MessageType::Response, request_id, payload).encode()) } pub fn decode_server_response(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode server response error", e)) + decode_payload(frame.payload(), "decode server response error") } pub fn encode_get_request(request_id: u64, request: &GetRequest) -> RS> { - let payload = serde_json::to_vec(request) - .map_err(|e| m_error!(EC::EncodeErr, "encode get request error", e))?; + let payload = encode_payload(request, "encode get request error")?; Ok(Frame::new(MessageType::Get, request_id, payload).encode()) } pub fn decode_get_request(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode get request error", e)) + decode_payload(frame.payload(), "decode get request error") } pub fn decode_get_response(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode get response error", e)) + decode_payload(frame.payload(), "decode get response error") } pub fn encode_put_request(request_id: u64, request: &PutRequest) -> RS> { - let payload = serde_json::to_vec(request) - .map_err(|e| m_error!(EC::EncodeErr, "encode put request error", e))?; + let payload = encode_payload(request, "encode put request error")?; Ok(Frame::new(MessageType::Put, request_id, payload).encode()) } pub fn decode_put_request(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode put request error", e)) + decode_payload(frame.payload(), "decode put request error") } pub fn decode_put_response(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode put response error", e)) + decode_payload(frame.payload(), "decode put response error") } pub fn encode_range_scan_request(request_id: u64, request: &RangeScanRequest) -> RS> { - let payload = serde_json::to_vec(request) - .map_err(|e| m_error!(EC::EncodeErr, "encode range scan request error", e))?; + let payload = encode_payload(request, "encode range scan request error")?; Ok(Frame::new(MessageType::RangeScan, request_id, payload).encode()) } pub fn decode_range_scan_request(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode range scan request error", e)) + decode_payload(frame.payload(), "decode range scan request error") } pub fn encode_procedure_invoke_request( request_id: u64, request: &ProcedureInvokeRequest, ) -> RS> { - let payload = serde_json::to_vec(request) - .map_err(|e| m_error!(EC::EncodeErr, "encode procedure invoke request error", e))?; + let payload = encode_payload(request, "encode procedure invoke request error")?; Ok(Frame::new(MessageType::ProcedureInvoke, request_id, payload).encode()) } @@ -583,60 +616,50 @@ pub fn encode_session_create_request( request_id: u64, request: &SessionCreateRequest, ) -> RS> { - let payload = serde_json::to_vec(request) - .map_err(|e| m_error!(EC::EncodeErr, "encode session create request error", e))?; + let payload = encode_payload(request, "encode session create request error")?; Ok(Frame::new(MessageType::SessionCreate, request_id, payload).encode()) } pub fn encode_session_close_request(request_id: u64, request: &SessionCloseRequest) -> RS> { - let payload = serde_json::to_vec(request) - .map_err(|e| m_error!(EC::EncodeErr, "encode session close request error", e))?; + let payload = encode_payload(request, "encode session close request error")?; Ok(Frame::new(MessageType::SessionClose, request_id, payload).encode()) } pub fn decode_range_scan_response(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode range scan response error", e)) + decode_payload(frame.payload(), "decode range scan response error") } pub fn decode_procedure_invoke_request(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode procedure invoke request error", e)) + decode_payload(frame.payload(), "decode procedure invoke request error") } pub fn decode_session_create_response(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode session create response error", e)) + decode_payload(frame.payload(), "decode session create response error") } pub fn decode_session_create_request(frame: &Frame) -> RS { if frame.payload().is_empty() { return Ok(SessionCreateRequest::default()); } - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode session create request error", e)) + decode_payload(frame.payload(), "decode session create request error") } pub fn decode_session_close_request(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode session close request error", e)) + decode_payload(frame.payload(), "decode session close request error") } pub fn encode_get_response(request_id: u64, response: &GetResponse) -> RS> { - let payload = serde_json::to_vec(response) - .map_err(|e| m_error!(EC::EncodeErr, "encode get response error", e))?; + let payload = encode_payload(response, "encode get response error")?; Ok(Frame::new(MessageType::Response, request_id, payload).encode()) } pub fn encode_put_response(request_id: u64, response: &PutResponse) -> RS> { - let payload = serde_json::to_vec(response) - .map_err(|e| m_error!(EC::EncodeErr, "encode put response error", e))?; + let payload = encode_payload(response, "encode put response error")?; Ok(Frame::new(MessageType::Response, request_id, payload).encode()) } pub fn encode_range_scan_response(request_id: u64, response: &RangeScanResponse) -> RS> { - let payload = serde_json::to_vec(response) - .map_err(|e| m_error!(EC::EncodeErr, "encode range scan response error", e))?; + let payload = encode_payload(response, "encode range scan response error")?; Ok(Frame::new(MessageType::Response, request_id, payload).encode()) } @@ -644,8 +667,7 @@ pub fn encode_procedure_invoke_response( request_id: u64, response: &ProcedureInvokeResponse, ) -> RS> { - let payload = serde_json::to_vec(response) - .map_err(|e| m_error!(EC::EncodeErr, "encode procedure invoke response error", e))?; + let payload = encode_payload(response, "encode procedure invoke response error")?; Ok(Frame::new(MessageType::Response, request_id, payload).encode()) } @@ -653,8 +675,7 @@ pub fn encode_session_create_response( request_id: u64, response: &SessionCreateResponse, ) -> RS> { - let payload = serde_json::to_vec(response) - .map_err(|e| m_error!(EC::EncodeErr, "encode session create response error", e))?; + let payload = encode_payload(response, "encode session create response error")?; Ok(Frame::new(MessageType::Response, request_id, payload).encode()) } @@ -662,30 +683,33 @@ pub fn encode_session_close_response( request_id: u64, response: &SessionCloseResponse, ) -> RS> { - let payload = serde_json::to_vec(response) - .map_err(|e| m_error!(EC::EncodeErr, "encode session close response error", e))?; + let payload = encode_payload(response, "encode session close response error")?; Ok(Frame::new(MessageType::Response, request_id, payload).encode()) } pub fn decode_procedure_invoke_response(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode procedure invoke response error", e)) + decode_payload(frame.payload(), "decode procedure invoke response error") } pub fn decode_session_close_response(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode session close response error", e)) + decode_payload(frame.payload(), "decode session close response error") } pub fn encode_error_response(request_id: u64, message: impl Into) -> RS> { - let payload = serde_json::to_vec(&ErrorResponse::new(message)) - .map_err(|e| m_error!(EC::EncodeErr, "encode error response error", e))?; + let payload = encode_payload(&ErrorResponse::new(message), "encode error response error")?; Ok(Frame::new(MessageType::Error, request_id, payload).encode()) } pub fn decode_error_response(frame: &Frame) -> RS { - serde_json::from_slice(frame.payload()) - .map_err(|e| m_error!(EC::DecodeErr, "decode error response error", e)) + decode_payload(frame.payload(), "decode error response error") +} + +fn encode_payload(value: &T, err_msg: &'static str) -> RS> { + rmp_serde::to_vec(value).map_err(|e| m_error!(EC::EncodeErr, err_msg, e)) +} + +fn decode_payload(payload: &[u8], err_msg: &'static str) -> RS { + rmp_serde::from_slice(payload).map_err(|e| m_error!(EC::DecodeErr, err_msg, e)) } #[cfg(test)] diff --git a/mudu_contract/src/tuple/comparator.rs b/mudu_contract/src/tuple/comparator.rs index 2beb0db..20b3cbb 100644 --- a/mudu_contract/src/tuple/comparator.rs +++ b/mudu_contract/src/tuple/comparator.rs @@ -11,6 +11,51 @@ use mudu_type::dat_type::DatType; use mudu_type::dat_type_id::DatTypeID; use mudu_type::dat_value::DatValue; +#[derive(Clone, Copy)] +pub struct TupleComparator { + pub compare: fn(&[u8], &[u8], &TupleBinaryDesc) -> RS, + pub equal: fn(&[u8], &[u8], &TupleBinaryDesc) -> RS, + pub hash_cal_one: fn(&[u8], &TupleBinaryDesc, &mut dyn Hasher) -> RS<()>, + pub hash_cal_finish: fn(&[u8], &TupleBinaryDesc, &mut dyn Hasher) -> RS, +} + +impl TupleComparator { + pub fn new() -> Self { + Self { + compare: tuple_compare_adapter, + equal: tuple_equal_adapter, + hash_cal_one: tuple_hash_adapter, + hash_cal_finish: tuple_hash_finish_adapter, + } + } +} + +impl Default for TupleComparator { + fn default() -> Self { + Self::new() + } +} + +fn tuple_compare_adapter(tuple1: &[u8], tuple2: &[u8], desc: &TupleBinaryDesc) -> RS { + tuple_compare(desc, tuple1, tuple2) +} + +fn tuple_equal_adapter(tuple1: &[u8], tuple2: &[u8], desc: &TupleBinaryDesc) -> RS { + tuple_equal(desc, tuple1, tuple2) +} + +fn tuple_hash_adapter(tuple: &[u8], desc: &TupleBinaryDesc, hasher: &mut dyn Hasher) -> RS<()> { + tuple_hash(desc, tuple, hasher) +} + +fn tuple_hash_finish_adapter( + tuple: &[u8], + desc: &TupleBinaryDesc, + hasher: &mut dyn Hasher, +) -> RS { + tuple_hash_finish(desc, tuple, hasher) +} + pub fn tuple_compare(desc: &TupleBinaryDesc, tuple1: &[u8], tuple2: &[u8]) -> RS { _iter_value( desc, diff --git a/mudu_gen/Cargo.toml b/mudu_gen/Cargo.toml index b46aea7..45edfe3 100644 --- a/mudu_gen/Cargo.toml +++ b/mudu_gen/Cargo.toml @@ -12,13 +12,10 @@ sql_parser = { workspace = true } tree-sitter = { workspace = true } tree-sitter-wit = { workspace = true } clap = { workspace = true, features = ["derive"] } -mudu_type = { workspace = true } -mudu_contract = { workspace = true } askama = {workspace = true} serde = { workspace = true } rust-format = "0.3.4" paste = { workspace = true } -lazy_static = { workspace = true } mudu_binding = {workspace = true} [build-dependencies] md-5 = { workspace = true } diff --git a/mudu_kernel/Cargo.toml b/mudu_kernel/Cargo.toml index 402a3b1..79b61b9 100644 --- a/mudu_kernel/Cargo.toml +++ b/mudu_kernel/Cargo.toml @@ -10,6 +10,7 @@ debug_trace = ["mudu_utils/debug_trace"] [dependencies] mudu = { workspace = true, features = ["test"] } +mudu_sys = { workspace = true } mudu_type = { workspace = true, features = ["test"] } mudu_contract = { workspace = true } @@ -17,41 +18,33 @@ uuid = { workspace = true } short-uuid = "0.2.0" serde = { workspace = true } +rmp-serde = { workspace = true } tokio = { workspace = true } lazy_static = { workspace = true } -regex = { version = "1.11.0" } + serde_json = { workspace = true } async-trait = { workspace = true } arbitrary = { version = "1.4.2", features = ["derive"] } tracing = { workspace = true } -chrono = { workspace = true } -async-backtrace = { workspace = true } + pgwire = { workspace = true } futures = { workspace = true } scc = {workspace = true} project-root = { workspace = true } -toml = { workspace = true } -home = { version = "0.5.9" } + csv-async = { version = "1.3.0" } async-std = { version = "1.13.0" } libc = { workspace = true } sql_parser = { workspace = true } test_utils = { workspace = true } mudu_utils = { workspace = true } -roaring = "0.11.2" -mcslock = {version = "0.4.1"} -rand = "0.9.2" -tokio-condvar = {version = "0.3.0"} socket2 = "0.5.8" crossbeam-queue = "0.3.11" -log = "0.4.29" -[target.'cfg(target_os = "linux")'.dependencies] -rliburing = { git = "https://github.com/scuptio/rliburing.git" } +byteorder = "1.5.0" + -[dev-dependencies] -postgres = { workspace = true } diff --git a/mudu_kernel/cfg/test.toml b/mudu_kernel/cfg/test.toml deleted file mode 100644 index 44c45c8..0000000 --- a/mudu_kernel/cfg/test.toml +++ /dev/null @@ -1,9 +0,0 @@ -server_listen_port = 5432 -server_bind_address = "0.0.0.0" -db_path = "/tmp/mudu/" -session_threads = 4 -x_log_folder = "x_log" -x_log_ext_name = "xl" -x_log_channels = 4 -x_log_file_size_limit = 10485760 -x_log_use_io_uring = false diff --git a/mudu_kernel/src/collection/mod.rs b/mudu_kernel/src/collection/mod.rs index 6962b70..9a16d54 100644 --- a/mudu_kernel/src/collection/mod.rs +++ b/mudu_kernel/src/collection/mod.rs @@ -1,2 +1,4 @@ +#![allow(dead_code)] + pub mod hash_map; pub mod tree_map; diff --git a/mudu_kernel/src/command/create_table.rs b/mudu_kernel/src/command/create_table.rs index 145f7a6..bf3fc5f 100644 --- a/mudu_kernel/src/command/create_table.rs +++ b/mudu_kernel/src/command/create_table.rs @@ -1,11 +1,14 @@ use crate::contract::cmd_exec::CmdExec; +use crate::contract::meta_mgr::MetaMgr; use crate::x_engine::api::XContract; -use crate::x_engine::thd_ctx::ThdCtx; use crate::x_engine::x_param::PCreateTable; use async_trait::async_trait; use mudu::common::result::RS; +use mudu::error::ec::EC as ER; +use mudu::m_error; use mudu_utils::sync::a_mutex::AMutex; use mudu_utils::task_trace; +use std::sync::Arc; pub struct CreateTable { inner: AMutex<_InnerCreateTable>, @@ -13,13 +16,18 @@ pub struct CreateTable { struct _InnerCreateTable { param: PCreateTable, - thd_ctx: ThdCtx, + x_contract: Arc, + meta_mgr: Arc, } impl CreateTable { - pub fn new(param: PCreateTable, thd_ctx: ThdCtx) -> Self { + pub fn new( + param: PCreateTable, + x_contract: Arc, + meta_mgr: Arc, + ) -> Self { Self { - inner: AMutex::new(_InnerCreateTable::new(param, thd_ctx)), + inner: AMutex::new(_InnerCreateTable::new(param, x_contract, meta_mgr)), } } } @@ -28,14 +36,14 @@ impl CreateTable { impl CmdExec for CreateTable { async fn prepare(&self) -> RS<()> { task_trace!(); - Ok(()) + let inner = self.inner.lock().await; + inner.prepare().await } async fn run(&self) -> RS<()> { task_trace!(); - let mut g = self.inner.lock().await; - g.run().await?; - Ok(()) + let mut inner = self.inner.lock().await; + inner.run().await } async fn affected_rows(&self) -> RS { @@ -45,15 +53,38 @@ impl CmdExec for CreateTable { } impl _InnerCreateTable { - fn new(param: PCreateTable, thd_ctx: ThdCtx) -> Self { - Self { param, thd_ctx } + fn new( + param: PCreateTable, + x_contract: Arc, + meta_mgr: Arc, + ) -> Self { + Self { + param, + x_contract, + meta_mgr, + } + } + + async fn prepare(&self) -> RS<()> { + let table_name = self.param.schema.table_name().clone(); + if self + .meta_mgr + .get_table_by_name(&table_name) + .await? + .is_some() + { + return Err(m_error!( + ER::ExistingSuchElement, + format!("table {} already exists", table_name) + )); + } + Ok(()) } async fn run(&mut self) -> RS<()> { task_trace!(); - self.thd_ctx + self.x_contract .create_table(self.param.xid, &self.param.schema) - .await?; - Ok(()) + .await } } diff --git a/mudu_kernel/src/command/delete_key_value.rs b/mudu_kernel/src/command/delete_key_value.rs new file mode 100644 index 0000000..9454280 --- /dev/null +++ b/mudu_kernel/src/command/delete_key_value.rs @@ -0,0 +1,94 @@ +use crate::contract::cmd_exec::CmdExec; +use crate::contract::meta_mgr::MetaMgr; +use crate::x_engine::api::{OptDelete, Predicate, XContract}; +use crate::x_engine::x_param::PDeleteKeyValue; +use async_trait::async_trait; +use mudu::common::result::RS; +use mudu::error::ec::EC as ER; +use mudu::m_error; +use mudu_utils::sync::a_mutex::AMutex; +use std::sync::Arc; + +pub struct DeleteKeyValue { + inner: AMutex<_DeleteKeyValue>, +} + +struct _DeleteKeyValue { + param: PDeleteKeyValue, + x_contract: Arc, + meta_mgr: Arc, + affected_rows: u64, +} + +impl DeleteKeyValue { + pub fn new( + param: PDeleteKeyValue, + x_contract: Arc, + meta_mgr: Arc, + ) -> Self { + Self { + inner: AMutex::new(_DeleteKeyValue::new(param, x_contract, meta_mgr)), + } + } +} + +impl _DeleteKeyValue { + fn new( + param: PDeleteKeyValue, + x_contract: Arc, + meta_mgr: Arc, + ) -> Self { + Self { + param, + x_contract, + meta_mgr, + affected_rows: 0, + } + } + + async fn prepare(&self) -> RS<()> { + let _ = self.meta_mgr.get_table_by_id(self.param.table_id).await?; + if self.param.key.data().is_empty() { + return Err(m_error!(ER::NoSuchElement, "delete key is empty")); + } + Ok(()) + } + + async fn run(&mut self) -> RS<()> { + // Delete currently stays on the exact-key path to keep semantics explicit. + let deleted = self + .x_contract + .delete( + self.param.xid, + self.param.table_id, + &self.param.key, + &Predicate::CNF(Vec::new()), + &OptDelete::default(), + ) + .await?; + self.affected_rows = deleted as u64; + Ok(()) + } + + fn affected_rows(&self) -> u64 { + self.affected_rows + } +} + +#[async_trait] +impl CmdExec for DeleteKeyValue { + async fn prepare(&self) -> RS<()> { + let inner = self.inner.lock().await; + inner.prepare().await + } + + async fn run(&self) -> RS<()> { + let mut inner = self.inner.lock().await; + inner.run().await + } + + async fn affected_rows(&self) -> RS { + let inner = self.inner.lock().await; + Ok(inner.affected_rows()) + } +} diff --git a/mudu_kernel/src/command/drop_table.rs b/mudu_kernel/src/command/drop_table.rs index 22b4f4c..dae7aaa 100644 --- a/mudu_kernel/src/command/drop_table.rs +++ b/mudu_kernel/src/command/drop_table.rs @@ -1,31 +1,47 @@ use crate::contract::cmd_exec::CmdExec; +use crate::contract::meta_mgr::MetaMgr; use crate::x_engine::api::XContract; -use crate::x_engine::thd_ctx::ThdCtx; use crate::x_engine::x_param::PDropTable; use async_trait::async_trait; use mudu::common::result::RS; +use mudu_utils::task_trace; +use std::sync::Arc; pub struct DropTable { drop_param: PDropTable, - ctx: ThdCtx, + x_contract: Arc, + meta_mgr: Arc, } impl DropTable { - pub fn new(drop_param: PDropTable, ctx: ThdCtx) -> Self { - Self { drop_param, ctx } + pub fn new( + drop_param: PDropTable, + x_contract: Arc, + meta_mgr: Arc, + ) -> Self { + Self { + drop_param, + x_contract, + meta_mgr, + } } } #[async_trait] impl CmdExec for DropTable { async fn prepare(&self) -> RS<()> { + if let Some(table_id) = self.drop_param.oid { + let _ = self.meta_mgr.get_table_by_id(table_id).await?; + } Ok(()) } async fn run(&self) -> RS<()> { - let xid = self.drop_param.xid; - if let Some(t) = &self.drop_param.oid { - self.ctx.drop_table(xid, *t).await?; + task_trace!(); + if let Some(table_id) = self.drop_param.oid { + self.x_contract + .drop_table(self.drop_param.xid, table_id) + .await?; } Ok(()) } diff --git a/mudu_kernel/src/command/insert_key_value.rs b/mudu_kernel/src/command/insert_key_value.rs index fc7d719..f326c51 100644 --- a/mudu_kernel/src/command/insert_key_value.rs +++ b/mudu_kernel/src/command/insert_key_value.rs @@ -1,6 +1,6 @@ use crate::contract::cmd_exec::CmdExec; -use crate::x_engine::api::{OptInsert, VecDatum, XContract}; -use crate::x_engine::thd_ctx::ThdCtx; +use crate::contract::meta_mgr::MetaMgr; +use crate::x_engine::api::{OptInsert, XContract}; use crate::x_engine::x_param::PInsertKeyValue; use async_trait::async_trait; use mudu::common::result::RS; @@ -8,6 +8,7 @@ use mudu::error::ec::EC as ER; use mudu::m_error; use mudu_utils::sync::a_mutex::AMutex; use mudu_utils::task_trace; +use std::sync::Arc; pub struct InsertKeyValue { inner: AMutex<_InsertKeyValue>, @@ -15,23 +16,33 @@ pub struct InsertKeyValue { struct _InsertKeyValue { param: PInsertKeyValue, - thd_ctx: ThdCtx, + x_contract: Arc, + meta_mgr: Arc, affected_rows: u64, } impl InsertKeyValue { - pub fn new(param: PInsertKeyValue, thd_ctx: ThdCtx) -> Self { + pub fn new( + param: PInsertKeyValue, + x_contract: Arc, + meta_mgr: Arc, + ) -> Self { Self { - inner: AMutex::new(_InsertKeyValue::new(param, thd_ctx)), + inner: AMutex::new(_InsertKeyValue::new(param, x_contract, meta_mgr)), } } } impl _InsertKeyValue { - fn new(param: PInsertKeyValue, thd_ctx: ThdCtx) -> Self { + fn new( + param: PInsertKeyValue, + x_contract: Arc, + meta_mgr: Arc, + ) -> Self { Self { param, - thd_ctx, + x_contract, + meta_mgr, affected_rows: 0, } } @@ -40,34 +51,33 @@ impl _InsertKeyValue { #[async_trait] impl CmdExec for InsertKeyValue { async fn prepare(&self) -> RS<()> { - Ok(()) + let inner = self.inner.lock().await; + inner.prepare().await } async fn run(&self) -> RS<()> { - let mut g = self.inner.lock().await; - g.insert().await?; - Ok(()) + let mut inner = self.inner.lock().await; + inner.insert().await } async fn affected_rows(&self) -> RS { - let g = self.inner.lock().await; - Ok(g.affected_rows()) + let inner = self.inner.lock().await; + Ok(inner.affected_rows()) } } impl _InsertKeyValue { - async fn insert(&mut self) -> RS<()> { - task_trace!(); - let mut key = VecDatum::default(); - let mut value = VecDatum::default(); - - key.swap(&mut self.param.key); - value.swap(&mut self.param.value); - if key.data().is_empty() { + async fn prepare(&self) -> RS<()> { + let _ = self.meta_mgr.get_table_by_id(self.param.table_id).await?; + if self.param.key.data().is_empty() { return Err(m_error!(ER::NoSuchElement, "key is empty")); } + Ok(()) + } - self.thd_ctx + async fn insert(&mut self) -> RS<()> { + task_trace!(); + self.x_contract .insert( self.param.xid, self.param.table_id, diff --git a/mudu_kernel/src/command/load_from_file.rs b/mudu_kernel/src/command/load_from_file.rs index 9a74a0c..0707e70 100644 --- a/mudu_kernel/src/command/load_from_file.rs +++ b/mudu_kernel/src/command/load_from_file.rs @@ -1,22 +1,17 @@ use crate::contract::cmd_exec::CmdExec; -use crate::contract::data_row::DataRow; -use crate::contract::pst_op_list::PstOpList; -use crate::contract::timestamp::Timestamp; -use crate::contract::version_tuple::VersionTuple; -use crate::x_engine::thd_ctx::ThdCtx; +use crate::contract::meta_mgr::MetaMgr; +use crate::x_engine::api::{OptInsert, VecDatum, XContract}; use async_std::fs::File; use async_trait::async_trait; use csv_async::StringRecord; use futures::StreamExt; use mudu::common::buf::Buf; -use mudu::common::id::{gen_oid, OID}; +use mudu::common::id::OID; use mudu::common::result::RS; +use mudu::common::xid::XID; use mudu::error::ec::EC as ER; use mudu::m_error; -use mudu_contract::tuple::build_tuple::build_tuple; -use mudu_contract::tuple::tuple_binary_desc::TupleBinaryDesc as TupleDesc; use std::sync::Arc; -use tokio::sync::oneshot::channel; use tokio::sync::Mutex; pub struct LoadFromFile { @@ -25,34 +20,34 @@ pub struct LoadFromFile { struct _LoadFromFile { csv_file: String, + xid: XID, table_id: OID, key_index: Vec, value_index: Vec, - key_desc: TupleDesc, - value_desc: TupleDesc, - thd_ctx: ThdCtx, + x_contract: Arc, + meta_mgr: Arc, affected_rows: u64, } impl LoadFromFile { pub fn new( csv_file: String, + xid: XID, table_id: OID, key_index: Vec, value_index: Vec, - key_desc: TupleDesc, - value_desc: TupleDesc, - thd_ctx: ThdCtx, + x_contract: Arc, + meta_mgr: Arc, ) -> Self { Self { inner: Arc::new(Mutex::new(_LoadFromFile::new( csv_file, + xid, table_id, key_index, value_index, - key_desc, - value_desc, - thd_ctx, + x_contract, + meta_mgr, ))), } } @@ -61,42 +56,48 @@ impl LoadFromFile { impl _LoadFromFile { fn new( csv_file: String, + xid: XID, table_id: OID, key_index: Vec, value_index: Vec, - key_desc: TupleDesc, - value_desc: TupleDesc, - thd_ctx: ThdCtx, + x_contract: Arc, + meta_mgr: Arc, ) -> Self { - if key_index.len() != key_desc.field_count() - || value_index.len() != value_desc.field_count() - { - panic!("column size error!"); - } Self { csv_file, + xid, table_id, key_index, value_index, - key_desc, - value_desc, - thd_ctx, + x_contract, + meta_mgr, affected_rows: 0, } } + async fn prepare(&self) -> RS<()> { + let table_desc = self.meta_mgr.get_table_by_id(self.table_id).await?; + if self.key_index.len() != table_desc.key_info().len() + || self.value_index.len() != table_desc.value_info().len() + { + return Err(m_error!(ER::IOErr, "column size error")); + } + Ok(()) + } + async fn load_table(&self) -> RS { + let table_desc = self.meta_mgr.get_table_by_id(self.table_id).await?; let file = File::open(self.csv_file.clone()).await.map_err(|e| { m_error!( ER::IOErr, format!("load failed, open csv file {} error, {}", self.csv_file, e) ) })?; - let mut rdr = csv_async::AsyncReader::from_reader(file); - let mut records = rdr.records(); + let mut reader = csv_async::AsyncReader::from_reader(file); + let mut records = reader.records(); let mut rows = 0; - while let Some(t) = records.next().await { - let record = t.map_err(|e| { + while let Some(record) = records.next().await { + let record = record.map_err(|e| { m_error!( ER::IOErr, format!("load failed, csv file {} error, {}", self.csv_file, e) @@ -113,32 +114,19 @@ impl _LoadFromFile { ) )); } - let key = Self::build_tuple_from_line(&record, &self.key_index, &self.key_desc)?; - let value = Self::build_tuple_from_line(&record, &self.value_index, &self.value_desc)?; - let data_row = DataRow::new(); - let opt = self - .thd_ctx - .tree_store() - .insert_key(self.table_id, key.clone(), data_row.clone()) + + let key = Self::build_datum_from_line(&record, &self.key_index, &table_desc, 0)?; + let value = Self::build_datum_from_line( + &record, + &self.value_index, + &table_desc, + table_desc.key_info().len(), + )?; + self.x_contract + .insert(self.xid, self.table_id, &key, &value, &OptInsert::default()) .await?; - let data_row = match opt { - Some((_k, row)) => row, - None => data_row, - }; - let timestamp = Timestamp::default(); - let tuple = VersionTuple::new(timestamp.clone(), value.clone()); - data_row.write(tuple, None).await?; - let mut list = PstOpList::new(); - list.push_insert(self.table_id, gen_oid(), timestamp, key, value); - self.thd_ctx.pst_op_ch().async_run(list)?; rows += 1; } - let (s, r) = channel(); - let mut list = PstOpList::new(); - list.push_flush(s); - self.thd_ctx.pst_op_ch().async_run(list)?; - r.await - .map_err(|e| m_error!(ER::IOErr, "flush failed", e))?; Ok(rows) } @@ -150,48 +138,47 @@ impl _LoadFromFile { self.affected_rows } - fn build_tuple_from_line( + fn build_datum_from_line( record: &StringRecord, - index: &[usize], - tuple_desc: &TupleDesc, - ) -> RS { - let mut tuple = vec![]; - for i in index.iter() { - let opt = record.get(*i); - let s = match opt { - Some(s) => s.to_string(), - None => return Err(m_error!(ER::IndexOutOfRange)), - }; - let field_desc = &tuple_desc.field_desc()[*i]; - let dat_id = field_desc.data_type(); - let type_param = field_desc.type_obj(); - let internal = dat_id.fn_input()(&s, type_param) + csv_index: &[usize], + table_desc: &crate::contract::table_desc::TableDesc, + attr_base: usize, + ) -> RS { + let mut datum = Vec::with_capacity(csv_index.len()); + for (position, csv_col) in csv_index.iter().enumerate() { + let textual = record + .get(*csv_col) + .ok_or_else(|| m_error!(ER::IndexOutOfRange))?; + let field = table_desc.get_attr(attr_base + position); + let dat_type = field.type_desc(); + let dat_id = dat_type.dat_type_id(); + let internal = dat_id.fn_input()(textual, dat_type) .map_err(|e| m_error!(ER::TypeBaseErr, "convert printable to internal error", e))?; - let binary = dat_id.fn_send()(&internal, type_param) - .map_err(|e| m_error!(ER::TypeBaseErr, "converting internal to binary error", e))?; - tuple.push(binary.into()); + let binary: Buf = dat_id.fn_send()(&internal, dat_type) + .map_err(|e| m_error!(ER::TypeBaseErr, "converting internal to binary error", e))? + .into(); + datum.push((attr_base + position, binary)); } - let buf = build_tuple(&tuple, tuple_desc)?; - Ok(buf) + Ok(VecDatum::new(datum)) } } #[async_trait] impl CmdExec for LoadFromFile { async fn prepare(&self) -> RS<()> { - Ok(()) + let inner = self.inner.lock().await; + inner.prepare().await } async fn run(&self) -> RS<()> { - let mut g = self.inner.lock().await; - let rows = g.load_table().await?; - g.set_affected_rows(rows); + let mut inner = self.inner.lock().await; + let rows = inner.load_table().await?; + inner.set_affected_rows(rows); Ok(()) } async fn affected_rows(&self) -> RS { - let g = self.inner.lock().await; - let rows = g.get_affected_rows(); - Ok(rows) + let inner = self.inner.lock().await; + Ok(inner.get_affected_rows()) } } diff --git a/mudu_kernel/src/command/mod.rs b/mudu_kernel/src/command/mod.rs index 30d7501..9e7c0b1 100644 --- a/mudu_kernel/src/command/mod.rs +++ b/mudu_kernel/src/command/mod.rs @@ -1,5 +1,9 @@ +#![allow(dead_code)] + pub mod create_table; +pub mod delete_key_value; pub mod drop_table; pub mod insert_key_value; pub mod load_from_file; -mod save_to_file; +pub mod save_to_file; +pub mod update_key_value; diff --git a/mudu_kernel/src/command/save_to_file.rs b/mudu_kernel/src/command/save_to_file.rs index fc9cae9..d4d84c6 100644 --- a/mudu_kernel/src/command/save_to_file.rs +++ b/mudu_kernel/src/command/save_to_file.rs @@ -1,8 +1,19 @@ use crate::contract::cmd_exec::CmdExec; +use crate::contract::meta_mgr::MetaMgr; +use crate::contract::table_desc::TableDesc; +use crate::x_engine::api::{OptRead, Predicate, RangeData, VecSelTerm, XContract}; +use async_std::fs::File; use async_trait::async_trait; +use csv_async::AsyncWriter; use mudu::common::id::OID; use mudu::common::result::RS; +use mudu::common::xid::XID; +use mudu::error::ec::EC as ER; +use mudu::m_error; +use mudu_contract::tuple::datum_desc::DatumDesc; use mudu_utils::sync::a_mutex::AMutex; +use std::ops::Bound; +use std::sync::Arc; pub struct SaveToFile { inner: AMutex<_SaveToFile>, @@ -10,24 +21,206 @@ pub struct SaveToFile { struct _SaveToFile { file_path: String, - table_name: OID, + xid: XID, + table_id: OID, key_indexing: Vec, value_indexing: Vec, + x_contract: Arc, + meta_mgr: Arc, + affected_rows: u64, +} + +impl SaveToFile { + pub fn new( + file_path: String, + xid: XID, + table_id: OID, + key_indexing: Vec, + value_indexing: Vec, + x_contract: Arc, + meta_mgr: Arc, + ) -> Self { + Self { + inner: AMutex::new(_SaveToFile::new( + file_path, + xid, + table_id, + key_indexing, + value_indexing, + x_contract, + meta_mgr, + )), + } + } } #[async_trait] impl CmdExec for SaveToFile { async fn prepare(&self) -> RS<()> { - Ok(()) + let inner = self.inner.lock().await; + inner.prepare().await } async fn run(&self) -> RS<()> { + let mut inner = self.inner.lock().await; + let rows = inner.save_table().await?; + inner.affected_rows = rows; Ok(()) } async fn affected_rows(&self) -> RS { - todo!() + let inner = self.inner.lock().await; + Ok(inner.affected_rows) } } -impl _SaveToFile {} +impl _SaveToFile { + fn new( + file_path: String, + xid: XID, + table_id: OID, + key_indexing: Vec, + value_indexing: Vec, + x_contract: Arc, + meta_mgr: Arc, + ) -> Self { + Self { + file_path, + xid, + table_id, + key_indexing, + value_indexing, + x_contract, + meta_mgr, + affected_rows: 0, + } + } + + async fn prepare(&self) -> RS<()> { + let table_desc = self.meta_mgr.get_table_by_id(self.table_id).await?; + if self.key_indexing.len() != table_desc.key_info().len() + || self.value_indexing.len() != table_desc.value_info().len() + { + return Err(m_error!(ER::IOErr, "column size error")); + } + let total = self.key_indexing.len() + self.value_indexing.len(); + Self::validate_indexing(&self.key_indexing, &self.value_indexing, total) + } + + async fn save_table(&self) -> RS { + let table_desc = self.meta_mgr.get_table_by_id(self.table_id).await?; + let select = Self::build_select(&table_desc); + let output_desc = Self::build_output_desc(&table_desc); + let cursor = self + .x_contract + .read_range( + self.xid, + self.table_id, + &RangeData::new(Bound::Unbounded, Bound::Unbounded), + &Predicate::CNF(Vec::new()), + &select, + &OptRead::default(), + ) + .await?; + + let file = File::create(self.file_path.clone()).await.map_err(|e| { + m_error!( + ER::IOErr, + format!( + "save failed, create csv file {} error, {}", + self.file_path, e + ) + ) + })?; + let mut writer = AsyncWriter::from_writer(file); + let header = self.reorder_row(&Self::build_header(&table_desc))?; + writer.write_record(header).await.map_err(|e| { + m_error!( + ER::IOErr, + format!( + "save failed, write csv header {} error, {}", + self.file_path, e + ) + ) + })?; + + let mut rows = 0; + while let Some(row) = cursor.next().await? { + let textual = row.to_textual(&output_desc)?; + let ordered = self.reorder_row(&textual)?; + writer.write_record(ordered).await.map_err(|e| { + m_error!( + ER::IOErr, + format!("save failed, write csv row {} error, {}", self.file_path, e) + ) + })?; + rows += 1; + } + writer.flush().await.map_err(|e| { + m_error!( + ER::IOErr, + format!( + "save failed, flush csv file {} error, {}", + self.file_path, e + ) + ) + })?; + Ok(rows) + } + + fn validate_indexing(key_indexing: &[usize], value_indexing: &[usize], total: usize) -> RS<()> { + let mut seen = vec![false; total]; + for idx in key_indexing.iter().chain(value_indexing.iter()) { + if *idx >= total { + return Err(m_error!(ER::IndexOutOfRange)); + } + if seen[*idx] { + return Err(m_error!(ER::IOErr, "duplicate column index")); + } + seen[*idx] = true; + } + if seen.iter().any(|item| !item) { + return Err(m_error!(ER::IOErr, "column index is not continuous")); + } + Ok(()) + } + + fn build_select(table_desc: &TableDesc) -> VecSelTerm { + let total = table_desc.key_info().len() + table_desc.value_info().len(); + VecSelTerm::new((0..total).collect()) + } + + fn build_output_desc(table_desc: &TableDesc) -> Vec { + let total = table_desc.key_info().len() + table_desc.value_info().len(); + (0..total) + .map(|attr| { + let field = table_desc.get_attr(attr); + DatumDesc::new(field.name().clone(), field.type_desc().clone()) + }) + .collect() + } + + fn build_header(table_desc: &TableDesc) -> Vec { + let total = table_desc.key_info().len() + table_desc.value_info().len(); + (0..total) + .map(|attr| table_desc.get_attr(attr).name().clone()) + .collect() + } + + fn reorder_row(&self, textual: &[String]) -> RS> { + let total = self.key_indexing.len() + self.value_indexing.len(); + if textual.len() != total { + return Err(m_error!(ER::IOErr, "row column size error")); + } + let mut ordered = vec![String::new(); total]; + for (src, dest) in self.key_indexing.iter().enumerate().chain( + self.value_indexing + .iter() + .enumerate() + .map(|(i, idx)| (self.key_indexing.len() + i, idx)), + ) { + ordered[*dest] = textual[src].clone(); + } + Ok(ordered) + } +} diff --git a/mudu_kernel/src/command/update_key_value.rs b/mudu_kernel/src/command/update_key_value.rs new file mode 100644 index 0000000..3df2f03 --- /dev/null +++ b/mudu_kernel/src/command/update_key_value.rs @@ -0,0 +1,98 @@ +use crate::contract::cmd_exec::CmdExec; +use crate::contract::meta_mgr::MetaMgr; +use crate::x_engine::api::{OptUpdate, Predicate, XContract}; +use crate::x_engine::x_param::PUpdateKeyValue; +use async_trait::async_trait; +use mudu::common::result::RS; +use mudu::error::ec::EC as ER; +use mudu::m_error; +use mudu_utils::sync::a_mutex::AMutex; +use std::sync::Arc; + +pub struct UpdateKeyValue { + inner: AMutex<_UpdateKeyValue>, +} + +struct _UpdateKeyValue { + param: PUpdateKeyValue, + x_contract: Arc, + meta_mgr: Arc, + affected_rows: u64, +} + +impl UpdateKeyValue { + pub fn new( + param: PUpdateKeyValue, + x_contract: Arc, + meta_mgr: Arc, + ) -> Self { + Self { + inner: AMutex::new(_UpdateKeyValue::new(param, x_contract, meta_mgr)), + } + } +} + +impl _UpdateKeyValue { + fn new( + param: PUpdateKeyValue, + x_contract: Arc, + meta_mgr: Arc, + ) -> Self { + Self { + param, + x_contract, + meta_mgr, + affected_rows: 0, + } + } + + async fn prepare(&self) -> RS<()> { + let _ = self.meta_mgr.get_table_by_id(self.param.table_id).await?; + if self.param.key.data().is_empty() { + return Err(m_error!(ER::NoSuchElement, "update key is empty")); + } + if self.param.value.data().is_empty() { + return Err(m_error!(ER::NoSuchElement, "update value is empty")); + } + Ok(()) + } + + async fn run(&mut self) -> RS<()> { + // The SQL binder only emits key-equality updates for now. + let updated = self + .x_contract + .update( + self.param.xid, + self.param.table_id, + &self.param.key, + &Predicate::CNF(Vec::new()), + &self.param.value, + &OptUpdate {}, + ) + .await?; + self.affected_rows = updated as u64; + Ok(()) + } + + fn affected_rows(&self) -> u64 { + self.affected_rows + } +} + +#[async_trait] +impl CmdExec for UpdateKeyValue { + async fn prepare(&self) -> RS<()> { + let inner = self.inner.lock().await; + inner.prepare().await + } + + async fn run(&self) -> RS<()> { + let mut inner = self.inner.lock().await; + inner.run().await + } + + async fn affected_rows(&self) -> RS { + let inner = self.inner.lock().await; + Ok(inner.affected_rows()) + } +} diff --git a/mudu_kernel/src/common/mod.rs b/mudu_kernel/src/common/mod.rs index c54f6bc..25b4b77 100644 --- a/mudu_kernel/src/common/mod.rs +++ b/mudu_kernel/src/common/mod.rs @@ -1,3 +1 @@ -pub mod mudu_cfg; - pub mod test_delta_apply; diff --git a/mudu_kernel/src/common/mudu_cfg.rs b/mudu_kernel/src/common/mudu_cfg.rs deleted file mode 100644 index 1a34668..0000000 --- a/mudu_kernel/src/common/mudu_cfg.rs +++ /dev/null @@ -1,107 +0,0 @@ -use home::home_dir; -use mudu::common::result::RS; -use mudu::error::ec::EC as ER; -use mudu::m_error; -use serde::{Deserialize, Serialize}; -use std::fs; -use std::path::{Path, PathBuf}; -use toml; - -const CFG_TOML_PATH: &str = ".mudu/cfg.toml"; - -const LOG_FILE_EXT: &str = "xl"; - -/// log file size limit 10MB -const LOG_FILE_LIMIT: u64 = 1024 * 1024 * 10; - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MuduCfg { - pub server_listen_port: u16, - pub server_bind_address: String, - pub db_path: String, - pub session_threads: u32, - pub x_log_folder: String, - pub x_log_ext_name: String, - pub x_log_channels: u32, - pub x_log_file_size_limit: u64, - pub x_log_use_io_uring: bool, -} - -impl Default for MuduCfg { - fn default() -> Self { - MuduCfg { - server_listen_port: 5432, - server_bind_address: "0.0.0.0".to_string(), - db_path: "/tmp/mudu/".to_string(), - session_threads: 4, - x_log_folder: "x_log".to_string(), - x_log_ext_name: LOG_FILE_EXT.to_string(), - x_log_channels: 4, - x_log_file_size_limit: LOG_FILE_LIMIT, - x_log_use_io_uring: false, - } - } -} - -pub fn load_mudu_conf(opt_cfg_path: Option) -> RS { - let cfg_path = match opt_cfg_path { - Some(cfg_path) => PathBuf::from(cfg_path), - None => { - let opt_home = home_dir(); - let home_path = match opt_home { - Some(p) => p, - None => return Err(m_error!(ER::IOErr, "no home path env setting")), - }; - home_path.join(CFG_TOML_PATH) - } - }; - - if cfg_path.exists() { - let cfg = read_mudu_conf(cfg_path)?; - Ok(cfg) - } else { - let cfg = MuduCfg::default(); - write_mudu_conf(cfg_path, &cfg)?; - Ok(cfg) - } -} - -fn read_mudu_conf>(path: P) -> RS { - let r = fs::read_to_string(path); - let s = r.map_err(|e| m_error!(ER::IOErr, "read Mudu configuration error", e))?; - let r = toml::from_str::(s.as_str()); - let cfg = r.map_err(|e| m_error!(ER::IOErr, "deserialization configuration file error", e))?; - Ok(cfg) -} - -fn write_mudu_conf>(path: P, cfg: &MuduCfg) -> RS<()> { - let path = path.as_ref(); - if let Some(parent) = path.parent() { - if !parent.exists() { - fs::create_dir_all(parent) - .map_err(|e| m_error!(ER::IOErr, "create directory error", e))?; - } - } - let r = toml::to_string(cfg); - let s = r.map_err(|e| m_error!(ER::EncodeErr, "serialize configuration error", e))?; - - let r = fs::write(path, s); - r.map_err(|e| m_error!(ER::IOErr, "write configuration file error", e))?; - Ok(()) -} - -#[cfg(test)] -mod _test { - use crate::common::mudu_cfg::{read_mudu_conf, write_mudu_conf, MuduCfg}; - #[test] - fn test_conf() { - let cfg = MuduCfg::default(); - let path = "/tmp/mudu/cfg.toml".to_string(); - let r = write_mudu_conf(path.clone(), &cfg); - assert!(r.is_ok()); - let r = read_mudu_conf(path.clone()); - assert!(r.is_ok()); - let conf1 = r.unwrap(); - assert_eq!(conf1, cfg); - } -} diff --git a/mudu_kernel/src/contract/data_row.rs b/mudu_kernel/src/contract/data_row.rs index 91d141e..fc5032f 100644 --- a/mudu_kernel/src/contract/data_row.rs +++ b/mudu_kernel/src/contract/data_row.rs @@ -1,13 +1,12 @@ use crate::contract::snapshot::Snapshot; use crate::contract::version_delta::VersionDelta; use crate::contract::version_tuple::VersionTuple; -use mudu::common::id::OID; +use mudu::common::id::{TupleID, OID}; use mudu::common::result::RS; use mudu::common::update_delta::UpdateDelta; -use std::sync::Arc; -use tokio::sync::Mutex; +use std::sync::{Arc, Mutex}; -const MAX_TUPLE_VERSION_SIZE: usize = 4; +const UNCOMPRESSED_VERSION_COUNT: usize = 4; #[derive(Clone)] pub struct DataRow { @@ -15,13 +14,33 @@ pub struct DataRow { } struct DataRowInner { - tuple: Vec<(VersionTuple, Vec)>, + tid: TupleID, + // Full versions are stored from oldest to newest inside the retained + // in-memory window. New commits therefore append to the tail, and the + // latest version is always `tuple.last()`. + // + // Only the newest `UNCOMPRESSED_VERSION_COUNT` full versions stay in this + // window. When the window overflows, the oldest retained full version is + // evicted from the head. + tuple: Vec, + // Delta entries are append-only and ordered from oldest transition to + // newest transition. + // + // Each `delta[i]` converts a newer version into the immediately previous + // older version. For example, the logical chain `v1 <- v2 <- v3` is stored + // as `[v2->v1, v3->v2]`. + // + // The delta chain covers the entire history except for the oldest version. + // It also keeps transitions for versions that are still present in `tuple`, + // so the chain remains contiguous after older full versions are evicted + // from the retained window. delta: Vec, } impl DataRowInner { - fn new() -> Self { + fn new(tid: TupleID) -> Self { Self { + tid, tuple: vec![], delta: vec![], } @@ -34,122 +53,165 @@ impl DataRowInner { version: VersionTuple, prev_version: Option, ) -> RS<()> { - if self.tuple.is_empty() { - self.tuple.push((version, vec![])); - } else { - { - let prev_version = prev_version.unwrap(); - let last = self.tuple.last_mut().unwrap(); - if !last.0.timestamp().eq(prev_version.timestamp()) { - panic!("error, timestamp") - } - last.1 = prev_version.update_delta_into(); - } - if self.tuple.len() < MAX_TUPLE_VERSION_SIZE { - self.tuple.push((version, vec![])); - } else { - let mut tuple: Vec<_> = self.tuple.drain(1..).collect(); - std::mem::swap(&mut self.tuple, &mut tuple); - let (v_t, v_d) = tuple.pop().unwrap(); - let v_delta = VersionDelta::new(v_t.timestamp_into(), v_d); - self.delta.push(v_delta); - } + if let Some(latest) = self.tuple.last() { + let delta = prev_version.unwrap_or_else(|| build_version_delta(&version, latest)); + self.delta.push(delta); + } + self.tuple.push(version); + if self.tuple.len() > UNCOMPRESSED_VERSION_COUNT { + self.tuple.remove(0); } - Ok(()) } fn read_latest(&self) -> RS> { - Ok(self.tuple.last().map(|e| e.0.clone())) + Ok(self.tuple.last().cloned()) } fn read_version(&self, snapshot: &Snapshot) -> RS> { - let r = self.read_version_in_tuple_array(snapshot)?; - match r { - Ok(v) => Ok(Some(v)), - Err(opt) => match opt { - Some(v) => { - let opt_v = self.read_version_in_delta_list(snapshot, v)?; - Ok(opt_v) - } - None => Ok(None), - }, + if let Some(version) = self + .tuple + .iter() + .rev() + .find(|v| snapshot.is_tuple_visible(v.timestamp())) + .cloned() + { + return Ok(Some(version)); } - } - fn read_version_in_tuple_array( - &self, - snapshot: &Snapshot, - ) -> RS>> { - let mut opt_last: Option<&VersionTuple> = None; - for (v, _d) in self.tuple.iter().rev() { - let ts = v.timestamp(); - if snapshot.is_tuple_visible(ts) { - return Ok(Ok(v.clone())); - } else { - opt_last = Some(v); - } - } - let ret = opt_last.cloned(); - Ok(Err(ret)) - } - - fn read_version_in_delta_list( - &self, - snapshot: &Snapshot, - prev_version: VersionTuple, - ) -> RS> { - let mut version = prev_version; - let mut vec_delta = vec![]; - let mut opt_ts = None; - for version_delta in self.delta.iter().rev() { - let ts = version_delta.timestamp(); - for delta in version_delta.update_delta().iter() { - vec_delta.push(delta); - } - if snapshot.is_tuple_visible(ts) { - opt_ts = Some(ts.clone()); - break; - } + let Some(mut version) = self.tuple.first().cloned() else { + return Ok(None); + }; + + let older_version_count = self + .delta + .len() + .saturating_add(1) + .saturating_sub(self.tuple.len()); + if older_version_count == 0 { + return Ok(None); } - if let Some(ts) = opt_ts { - assert!(!vec_delta.is_empty()); - for delta in vec_delta { - let tuple = version.mut_tuple(); - let _ = delta.apply_to(tuple); + let start = older_version_count - 1; + for index in (0..=start).rev() { + apply_version_delta(&mut version, &self.delta[index]); + if snapshot.is_tuple_visible(version.timestamp()) { + return Ok(Some(version)); } - version.update_timestamp(ts); - Ok(Some(version)) - } else { - Ok(None) } + + Ok(None) } } impl DataRow { - pub fn new() -> Self { + pub fn new(tid: TupleID) -> Self { Self { - inner: Arc::new(Mutex::new(DataRowInner::new())), + inner: Arc::new(Mutex::new(DataRowInner::new(tid))), } } pub async fn tuple_id(&self) -> RS> { - todo!() + self.tuple_id_sync() + } + + pub fn tuple_id_sync(&self) -> RS> { + let guard = self.inner.lock().unwrap(); + Ok(Some(guard.tid as OID)) } pub async fn read(&self, snapshot: &Snapshot) -> RS> { - let guard = self.inner.lock().await; + self.read_sync(snapshot) + } + + pub fn read_sync(&self, snapshot: &Snapshot) -> RS> { + let guard = self.inner.lock().unwrap(); guard.read_version(snapshot) } pub async fn read_latest(&self) -> RS> { - let guard = self.inner.lock().await; + self.read_latest_sync() + } + + pub fn read_latest_sync(&self) -> RS> { + let guard = self.inner.lock().unwrap(); guard.read_latest() } pub async fn write(&self, version: VersionTuple, prev_version: Option) -> RS<()> { - let mut guard = self.inner.lock().await; + self.write_sync(version, prev_version) + } + + pub fn write_sync(&self, version: VersionTuple, prev_version: Option) -> RS<()> { + let mut guard = self.inner.lock().unwrap(); guard.write_version(version, prev_version) } } + +unsafe impl Send for DataRow {} +unsafe impl Sync for DataRow {} + +fn build_version_delta(newer: &VersionTuple, older: &VersionTuple) -> VersionDelta { + VersionDelta::new( + older.timestamp().clone(), + older.is_deleted(), + vec![UpdateDelta::new( + 0, + newer.tuple().len() as u32, + older.tuple().clone(), + )], + ) +} + +fn apply_version_delta(version: &mut VersionTuple, delta: &VersionDelta) { + let mut tuple = version.tuple().clone(); + for item in delta.update_delta() { + let _ = item.apply_to(&mut tuple); + } + *version = if delta.is_deleted() { + VersionTuple::new_delete(delta.timestamp().clone()) + } else { + VersionTuple::new(delta.timestamp().clone(), tuple) + }; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::contract::snapshot::{RunningXList, Snapshot}; + use crate::contract::timestamp::Timestamp; + + fn version(xid: u64, value: &[u8]) -> VersionTuple { + VersionTuple::new(Timestamp::new(xid, u64::MAX), value.to_vec()) + } + + fn snapshot(xid: u64) -> Snapshot { + Snapshot::from(RunningXList::new(xid, vec![])) + } + + #[test] + fn keeps_latest_versions_uncompressed() { + let row = DataRow::new(1); + for xid in 1..=6 { + row.write_sync(version(xid, &[xid as u8]), None).unwrap(); + } + + let guard = row.inner.lock().unwrap(); + assert_eq!(guard.tuple.len(), UNCOMPRESSED_VERSION_COUNT); + assert_eq!(guard.delta.len(), 5); + assert_eq!(guard.tuple[0].tuple(), &vec![3]); + assert_eq!(guard.tuple[3].tuple(), &vec![6]); + } + + #[test] + fn reads_compressed_old_versions_via_delta_chain() { + let row = DataRow::new(1); + for xid in 1..=6 { + row.write_sync(version(xid, &[xid as u8]), None).unwrap(); + } + + let visible = row.read_sync(&snapshot(2)).unwrap().unwrap(); + assert_eq!(visible.tuple(), &vec![2]); + assert_eq!(visible.timestamp().c_min(), 2); + } +} diff --git a/mudu_kernel/src/contract/mod.rs b/mudu_kernel/src/contract/mod.rs index 3586d8e..43767c0 100644 --- a/mudu_kernel/src/contract/mod.rs +++ b/mudu_kernel/src/contract/mod.rs @@ -1,7 +1,8 @@ +#![allow(dead_code)] + pub mod lsn; pub mod mem_store; pub mod x_lock_mgr; -pub mod x_log; pub mod meta_mgr; diff --git a/mudu_kernel/src/contract/table_desc.rs b/mudu_kernel/src/contract/table_desc.rs index 55fd563..e20ce0e 100644 --- a/mudu_kernel/src/contract/table_desc.rs +++ b/mudu_kernel/src/contract/table_desc.rs @@ -1,4 +1,4 @@ -use mudu::common::id::OID; +use mudu::common::id::{AttrIndex, OID}; use crate::contract::field_info::FieldInfo; use mudu_contract::tuple::tuple_binary_desc::TupleBinaryDesc as TupleDesc; @@ -9,8 +9,14 @@ pub struct TableDesc { oid: OID, key_oid: Vec, value_oid: Vec, + + // use AttrIndex index to access key/value + // [0 -- N ] , key datum, if 0 <= AttrIndex < N, this index would be key + // [N + 1 -- M ] , value datum, if N <= AttrIndex < M, this index would be value key_desc: TupleDesc, value_desc: TupleDesc, + key_info: Vec, + value_info: Vec, name2oid: HashMap, oid2col: HashMap, column_oid: Vec, @@ -30,6 +36,17 @@ impl TableDesc { let mut vec: Vec<(&OID, &FieldInfo)> = oid2col.iter().collect(); vec.sort_by(|a, b| a.1.column_index().cmp(&b.1.column_index())); let column_oid: Vec = vec.iter().map(|(id, _)| *(*id)).collect(); + let mut key_info: Vec<_> = Vec::new(); + let mut value_info: Vec<_> = Vec::new(); + key_info.resize(key_desc.field_count(), FieldInfo::default()); + value_info.resize(value_desc.field_count(), FieldInfo::default()); + for (_oid, field) in oid2col.iter() { + if field.is_primary() { + key_info[field.column_index()] = field.clone(); + } else { + value_info[field.column_index()] = field.clone(); + } + } Self { name, oid, @@ -37,6 +54,8 @@ impl TableDesc { value_oid, key_desc, value_desc, + key_info, + value_info, oid2col, name2oid, column_oid, @@ -51,10 +70,23 @@ impl TableDesc { &self.value_oid } + pub fn get_attr(&self, index: AttrIndex) -> &FieldInfo { + if index < self.key_info.len() { + &self.key_info[index] + } else { + &self.value_info[index - self.key_info.len()] + } + } + pub fn key_info(&self) -> &Vec { + &self.key_info + } pub fn key_desc(&self) -> &TupleDesc { &self.key_desc } + pub fn value_info(&self) -> &Vec { + &self.value_info + } pub fn value_desc(&self) -> &TupleDesc { &self.value_desc } diff --git a/mudu_kernel/src/contract/version_delta.rs b/mudu_kernel/src/contract/version_delta.rs index afb47dd..1a84b4e 100644 --- a/mudu_kernel/src/contract/version_delta.rs +++ b/mudu_kernel/src/contract/version_delta.rs @@ -2,14 +2,22 @@ use crate::contract::timestamp::Timestamp; use mudu::common::update_delta::UpdateDelta; impl VersionDelta { - pub fn new(timestamp: Timestamp, update: Vec) -> Self { - Self { timestamp, update } + pub fn new(timestamp: Timestamp, deleted: bool, update: Vec) -> Self { + Self { + timestamp, + deleted, + update, + } } pub fn timestamp(&self) -> &Timestamp { &self.timestamp } + pub fn is_deleted(&self) -> bool { + self.deleted + } + pub fn update_delta(&self) -> &Vec { &self.update } @@ -21,5 +29,6 @@ impl VersionDelta { pub struct VersionDelta { timestamp: Timestamp, + deleted: bool, update: Vec, } diff --git a/mudu_kernel/src/contract/version_tuple.rs b/mudu_kernel/src/contract/version_tuple.rs index 6c2c1c4..ccdc9a9 100644 --- a/mudu_kernel/src/contract/version_tuple.rs +++ b/mudu_kernel/src/contract/version_tuple.rs @@ -5,11 +5,24 @@ use mudu::common::buf::Buf; pub struct VersionTuple { timestamp: Timestamp, buf: Buf, + deleted: bool, } impl VersionTuple { pub fn new(timestamp: Timestamp, buf: Buf) -> VersionTuple { - Self { timestamp, buf } + Self { + timestamp, + buf, + deleted: false, + } + } + + pub fn new_delete(timestamp: Timestamp) -> VersionTuple { + Self { + timestamp, + buf: Vec::new(), + deleted: true, + } } pub fn timestamp(&self) -> &Timestamp { &self.timestamp @@ -30,4 +43,8 @@ impl VersionTuple { pub fn mut_tuple(&mut self) -> &mut Buf { &mut self.buf } + + pub fn is_deleted(&self) -> bool { + self.deleted + } } diff --git a/mudu_kernel/src/contract/x_log.rs b/mudu_kernel/src/contract/x_log.rs deleted file mode 100644 index 1450e6b..0000000 --- a/mudu_kernel/src/contract/x_log.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::contract::lsn::LSN; -use crate::contract::waiter::Waiter; -use crate::contract::xl_rec::XLRec; -use async_trait::async_trait; -use mudu::common::result::RS; -use std::sync::Arc; - -pub struct OptAppend { - pub wait: bool, -} - -#[async_trait] -pub trait XLog: Send + Sync { - async fn append( - &self, - log_rec: Vec, - opt: OptAppend, - ) -> RS<(LSN, Option>>)>; - - async fn flush(&self, lsn: LSN) -> RS>>; - - async fn flush_all(&self) -> RS>>; -} - -impl Default for OptAppend { - fn default() -> Self { - Self { wait: false } - } -} diff --git a/mudu_kernel/src/executor/index_access_key.rs b/mudu_kernel/src/executor/index_access_key.rs index 0ad96de..173719b 100644 --- a/mudu_kernel/src/executor/index_access_key.rs +++ b/mudu_kernel/src/executor/index_access_key.rs @@ -1,11 +1,13 @@ +use crate::contract::meta_mgr::MetaMgr; use crate::contract::query_exec::QueryExec; +use crate::executor::project_tuple_desc; use crate::x_engine::api::{TupleRow, XContract}; -use crate::x_engine::thd_ctx::ThdCtx; use crate::x_engine::x_param::PAccessKey; use async_trait::async_trait; use mudu::common::result::RS; use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc as TupleDesc; use mudu_utils::sync::a_mutex::AMutex; +use std::sync::Arc; pub struct IndexAccessKey { tuple_desc: TupleDesc, @@ -14,28 +16,35 @@ pub struct IndexAccessKey { struct _IndexAccessKey { param: PAccessKey, - thd_ctx: ThdCtx, + x_contract: Arc, + fetched: bool, } impl IndexAccessKey { - pub fn new(param: PAccessKey, tuple_desc: TupleDesc, ctx: ThdCtx) -> Self { - Self { + pub async fn new( + param: PAccessKey, + x_contract: Arc, + meta_mgr: Arc, + ) -> RS { + let table_desc = meta_mgr.get_table_by_id(param.table_id).await?; + let tuple_desc = project_tuple_desc(&table_desc, ¶m.select); + Ok(Self { tuple_desc, - inner: AMutex::new(_IndexAccessKey::new(param, ctx)), - } + inner: AMutex::new(_IndexAccessKey::new(param, x_contract)), + }) } } #[async_trait] impl QueryExec for IndexAccessKey { async fn open(&self) -> RS<()> { - let inner = self.inner.lock().await; - (*inner).open().await + let mut inner = self.inner.lock().await; + inner.open().await } async fn next(&self) -> RS> { let mut inner = self.inner.lock().await; - (*inner).next().await + inner.next().await } fn tuple_desc(&self) -> RS { @@ -44,21 +53,31 @@ impl QueryExec for IndexAccessKey { } impl _IndexAccessKey { - fn new(param: PAccessKey, thd_ctx: ThdCtx) -> Self { - Self { param, thd_ctx } + fn new(param: PAccessKey, x_contract: Arc) -> Self { + Self { + param, + x_contract, + fetched: false, + } } - async fn open(&self) -> RS<()> { + async fn open(&mut self) -> RS<()> { + self.fetched = false; Ok(()) } async fn next(&mut self) -> RS> { + if self.fetched { + return Ok(None); + } + self.fetched = true; + let p = &self.param; - let t = self - .thd_ctx + let row = self + .x_contract .read_key(p.xid, p.table_id, &p.pred_key, &p.select, &p.opt_read) .await?; - Ok(t.map(|e| TupleRow::new(e))) + Ok(row.map(TupleRow::new)) } } diff --git a/mudu_kernel/src/executor/index_access_range.rs b/mudu_kernel/src/executor/index_access_range.rs index 7f3eecc..b4808af 100644 --- a/mudu_kernel/src/executor/index_access_range.rs +++ b/mudu_kernel/src/executor/index_access_range.rs @@ -1,6 +1,7 @@ +use crate::contract::meta_mgr::MetaMgr; use crate::contract::query_exec::QueryExec; +use crate::executor::project_tuple_desc; use crate::x_engine::api::{RSCursor, TupleRow, XContract}; -use crate::x_engine::thd_ctx::ThdCtx; use crate::x_engine::x_param::PAccessRange; use async_trait::async_trait; use mudu::common::result::RS; @@ -9,20 +10,28 @@ use mudu_utils::sync::a_mutex::AMutex; use std::sync::Arc; pub struct IndexAccessRange { + tuple_desc: TupleDesc, inner: AMutex<_IndexAccessRange>, } struct _IndexAccessRange { - param: Option, + param: PAccessRange, cursor: Option>, - thd_ctx: ThdCtx, + x_contract: Arc, } impl IndexAccessRange { - fn new(param: PAccessRange, thd_ctx: ThdCtx) -> Self { - Self { - inner: AMutex::new(_IndexAccessRange::new(param, thd_ctx)), - } + pub async fn new( + param: PAccessRange, + x_contract: Arc, + meta_mgr: Arc, + ) -> RS { + let table_desc = meta_mgr.get_table_by_id(param.table_id).await?; + let tuple_desc = project_tuple_desc(&table_desc, ¶m.select); + Ok(Self { + tuple_desc, + inner: AMutex::new(_IndexAccessRange::new(param, x_contract)), + }) } } @@ -30,7 +39,7 @@ impl IndexAccessRange { impl QueryExec for IndexAccessRange { async fn open(&self) -> RS<()> { let mut inner = self.inner.lock().await; - (*inner).open().await + inner.open().await } async fn next(&self) -> RS> { @@ -39,50 +48,44 @@ impl QueryExec for IndexAccessRange { } fn tuple_desc(&self) -> RS { - todo!() + Ok(self.tuple_desc.clone()) } } impl _IndexAccessRange { - fn new(param: PAccessRange, thd_ctx: ThdCtx) -> Self { + fn new(param: PAccessRange, x_contract: Arc) -> Self { Self { - param: Some(param), + param, cursor: None, - thd_ctx, + x_contract, } } async fn open(&mut self) -> RS<()> { - if self.param.is_some() { - return Ok(()); - } - let mut param = None; - std::mem::swap(&mut self.param, &mut param); - let p = param.unwrap(); - let t = self - .thd_ctx + let param = &self.param; + let cursor = self + .x_contract .read_range( - p.xid, - p.table_id, - &p.pred_key, - &p.pred_non_key, - &p.select, - &p.opt_read, + param.xid, + param.table_id, + ¶m.pred_key, + ¶m.pred_non_key, + ¶m.select, + ¶m.opt_read, ) .await?; - self.cursor = Some(t); - + self.cursor = Some(cursor); Ok(()) } async fn next(&mut self) -> RS> { match &self.cursor { - Some(c) => { - let opt = c.next().await?; - if opt.is_none() { + Some(cursor) => { + let row = cursor.next().await?; + if row.is_none() { self.cursor = None; } - Ok(opt) + Ok(row) } None => Ok(None), } diff --git a/mudu_kernel/src/executor/mod.rs b/mudu_kernel/src/executor/mod.rs index ef03637..fb9f654 100644 --- a/mudu_kernel/src/executor/mod.rs +++ b/mudu_kernel/src/executor/mod.rs @@ -1,2 +1,21 @@ +#![allow(dead_code)] + +use crate::contract::table_desc::TableDesc; +use crate::x_engine::api::VecSelTerm; +use mudu_contract::tuple::datum_desc::DatumDesc; +use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; + pub mod index_access_key; pub mod index_access_range; + +pub(crate) fn project_tuple_desc(table_desc: &TableDesc, select: &VecSelTerm) -> TupleFieldDesc { + let fields = select + .vec() + .iter() + .map(|attr| { + let field = table_desc.get_attr(*attr); + DatumDesc::new(field.name().clone(), field.type_desc().clone()) + }) + .collect(); + TupleFieldDesc::new(fields) +} diff --git a/mudu_kernel/src/index/btree/btree_index.rs b/mudu_kernel/src/index/btree/btree_index.rs new file mode 100644 index 0000000..f43965c --- /dev/null +++ b/mudu_kernel/src/index/btree/btree_index.rs @@ -0,0 +1,246 @@ +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::ops::Bound; + +use mudu::common::result::RS; + +use crate::index::index_key::compare_context::CompareContext; +use crate::index::index_key::key_tuple::KeyTuple; + +pub struct BTreeIndex { + context: RefCell, + inner_map: BTreeMap, +} + +impl BTreeIndex { + pub fn new(context: CompareContext) -> Self { + Self { + context: RefCell::new(context), + inner_map: BTreeMap::new(), + } + } + + pub fn len(&self) -> RS { + self.with_read_context(|map| map.len()) + } + + pub fn is_empty(&self) -> RS { + self.with_read_context(|map| map.is_empty()) + } + + pub fn clear(&mut self) -> RS<()> + where + V: Clone, + { + self.clear_impl() + } + + fn clear_impl(&mut self) -> RS<()> + where + V: Clone, + { + self.with_write_context(|map| { + map.clear(); + }) + } + + pub fn contains_key(&self, key: &KeyTuple) -> RS { + self.with_read_context(|map| map.contains_key(key)) + } + + pub fn get(&self, key: &KeyTuple) -> RS> { + self.with_read_context(|map| map.get(key)) + } + + pub fn get_key_value(&self, key: &KeyTuple) -> RS> { + self.with_read_context(|map| map.get_key_value(key)) + } + + pub fn first_key_value(&self) -> RS> { + self.with_read_context(|map| map.first_key_value()) + } + + pub fn last_key_value(&self) -> RS> { + self.with_read_context(|map| map.last_key_value()) + } + + pub fn insert(&mut self, key: KeyTuple, value: V) -> RS> + where + V: Clone, + { + self.with_write_context(move |map| map.insert(key, value)) + } + + pub fn remove(&mut self, key: &KeyTuple) -> RS> + where + V: Clone, + { + self.with_write_context(|map| map.remove(key)) + } + + pub fn pop_first(&mut self) -> RS> + where + V: Clone, + { + self.with_write_context(|map| map.pop_first()) + } + + pub fn pop_last(&mut self) -> RS> + where + V: Clone, + { + self.with_write_context(|map| map.pop_last()) + } + + pub fn range(&self, bounds: (Bound<&KeyTuple>, Bound<&KeyTuple>)) -> RS> { + self.with_read_context(|map| map.range(bounds).collect()) + } + + fn with_read_context<'a, R, F>(&'a self, f: F) -> RS + where + F: FnOnce(&'a BTreeMap) -> R, + { + let ctx = self.fresh_context(); + CompareContext::set(RefCell::new(ctx)); + let result = f(&self.inner_map); + let status = Self::take_context_result(); + CompareContext::unset(); + status.map(|()| result) + } + + fn with_write_context(&mut self, f: F) -> RS + where + V: Clone, + F: FnOnce(&mut BTreeMap) -> R, + { + let ctx = self.fresh_context(); + CompareContext::set(RefCell::new(ctx)); + let mut staging = self.inner_map.clone(); + let result = f(&mut staging); + let status = Self::take_context_result(); + CompareContext::unset(); + status.map(|()| { + self.inner_map = staging; + result + }) + } + + fn fresh_context(&self) -> CompareContext { + let mut ctx = self.context.borrow().clone(); + ctx.result = Ok(()); + ctx + } + + fn take_context_result() -> RS<()> { + CompareContext::with_context(|c| Some(c.result.clone())).unwrap_or(Ok(())) + } +} + +#[cfg(test)] +mod tests { + use std::cmp::Ordering; + use std::hash::Hasher; + + use mudu::error::ec::EC; + use mudu::m_error; + use mudu_contract::tuple::comparator::TupleComparator; + use mudu_contract::tuple::tuple_binary_desc::TupleBinaryDesc; + use mudu_type::dat_type::DatType; + use mudu_type::dat_type_id::DatTypeID; + + use super::*; + + fn test_desc() -> TupleBinaryDesc { + TupleBinaryDesc::from(vec![DatType::new_no_param(DatTypeID::I32)]).unwrap() + } + + fn ok_compare(left: &[u8], right: &[u8], _desc: &TupleBinaryDesc) -> RS { + Ok(left.cmp(right)) + } + + fn ok_equal(left: &[u8], right: &[u8], _desc: &TupleBinaryDesc) -> RS { + Ok(left == right) + } + + fn ok_hash(tuple: &[u8], _desc: &TupleBinaryDesc, hasher: &mut dyn Hasher) -> RS<()> { + hasher.write(tuple); + Ok(()) + } + + fn err_compare(_left: &[u8], _right: &[u8], _desc: &TupleBinaryDesc) -> RS { + Err(m_error!(EC::CompareErr, "compare failed")) + } + + fn err_equal(_left: &[u8], _right: &[u8], _desc: &TupleBinaryDesc) -> RS { + Err(m_error!(EC::CompareErr, "compare failed")) + } + + fn err_hash(_tuple: &[u8], _desc: &TupleBinaryDesc, _hasher: &mut dyn Hasher) -> RS<()> { + Err(m_error!(EC::CompareErr, "hash failed")) + } + + fn finish_hash(tuple: &[u8], desc: &TupleBinaryDesc, hasher: &mut dyn Hasher) -> RS { + ok_hash(tuple, desc, hasher)?; + Ok(hasher.finish()) + } + + fn comparator_ok() -> TupleComparator { + TupleComparator { + compare: ok_compare, + equal: ok_equal, + hash_cal_one: ok_hash, + hash_cal_finish: finish_hash, + } + } + + fn comparator_err() -> TupleComparator { + TupleComparator { + compare: err_compare, + equal: err_equal, + hash_cal_one: err_hash, + hash_cal_finish: finish_hash, + } + } + + #[test] + fn insert_and_read_like_btreemap() { + let mut index = BTreeIndex::new(CompareContext { + result: Ok(()), + comparator: comparator_ok(), + desc: test_desc(), + }); + + assert!(index.is_empty().unwrap()); + assert_eq!(index.insert(KeyTuple::from(vec![1]), 10).unwrap(), None); + assert_eq!(index.insert(KeyTuple::from(vec![2]), 20).unwrap(), None); + assert_eq!(index.len().unwrap(), 2); + assert_eq!(index.get(&KeyTuple::from(vec![1])).unwrap(), Some(&10)); + assert!(index.contains_key(&KeyTuple::from(vec![2])).unwrap()); + assert_eq!( + index + .range((Bound::Included(&KeyTuple::from(vec![1])), Bound::Unbounded)) + .unwrap() + .len(), + 2 + ); + } + + #[test] + fn failed_compare_does_not_commit_insert() { + let mut index = BTreeIndex::new(CompareContext { + result: Ok(()), + comparator: comparator_ok(), + desc: test_desc(), + }); + index.insert(KeyTuple::from(vec![1]), 10).unwrap(); + + index.context.borrow_mut().comparator = comparator_err(); + let err = index.insert(KeyTuple::from(vec![2]), 20).unwrap_err(); + assert_eq!(err.ec(), EC::CompareErr); + + index.context.borrow_mut().comparator = comparator_ok(); + assert_eq!(index.len().unwrap(), 1); + assert_eq!(index.get(&KeyTuple::from(vec![1])).unwrap(), Some(&10)); + assert_eq!(index.get(&KeyTuple::from(vec![2])).unwrap(), None); + } +} diff --git a/mudu_kernel/src/index/btree/mod.rs b/mudu_kernel/src/index/btree/mod.rs new file mode 100644 index 0000000..ef291e2 --- /dev/null +++ b/mudu_kernel/src/index/btree/mod.rs @@ -0,0 +1 @@ +pub mod btree_index; diff --git a/mudu_kernel/src/index/hash/linear_hash.rs b/mudu_kernel/src/index/hash/linear_hash.rs index ea6c68e..619fc5e 100644 --- a/mudu_kernel/src/index/hash/linear_hash.rs +++ b/mudu_kernel/src/index/hash/linear_hash.rs @@ -4,8 +4,9 @@ use mudu::m_error; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use std::sync::Arc; -use tokio::fs::{self, OpenOptions}; -use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom}; + +use crate::io::file::{self, IoFile}; +use crate::io::worker_ring; use tokio::sync::{Mutex, MutexGuard}; pub const META_FILE_NAME: &str = "linear_hash.meta.json"; @@ -14,6 +15,7 @@ pub const PAGE_FILE_NAME: &str = "linear_hash.pages"; const META_VERSION: u32 = 1; const PAGE_HEADER_SIZE: usize = 16; const NONE_PAGE_ID: u64 = u64::MAX; +const FILE_MODE_644: u32 = 0o644; #[derive(Debug, Clone)] pub struct LinearHashConfig { @@ -110,7 +112,7 @@ impl LinearHash { "linear hash files not found and create_if_missing=false" )); } - fs::create_dir_all(&dir) + tokio::fs::create_dir_all(&dir) .await .map_err(|e| m_error!(ER::IOErr, "create index dir error", e))?; return Self::create_new(dir, config).await; @@ -440,14 +442,8 @@ impl LinearHash { async fn create_new(dir: PathBuf, config: LinearHashConfig) -> RS { let page_path = dir.join(PAGE_FILE_NAME); let meta_path = dir.join(META_FILE_NAME); - let _file = OpenOptions::new() - .create(true) - .truncate(true) - .read(true) - .write(true) - .open(&page_path) - .await - .map_err(|e| m_error!(ER::IOErr, "create page file error", e))?; + let page_file = open_rw_create_truncate(&page_path).await?; + close_file(page_file).await?; let initial_buckets = config.initial_buckets.max(1); let level = initial_buckets.trailing_zeros(); @@ -485,11 +481,13 @@ impl LinearHash { total_entries: 0, }; - let content = serde_json::to_vec_pretty(&meta) - .map_err(|e| m_error!(ER::EncodeErr, "encode meta error", e))?; - fs::write(&meta_path, content) - .await - .map_err(|e| m_error!(ER::IOErr, "write meta file error", e))?; + write_all_file( + &meta_path, + &serde_json::to_vec_pretty(&meta) + .map_err(|e| m_error!(ER::EncodeErr, "encode meta error", e))?, + true, + ) + .await?; let bucket_locks = (0..meta.bucket_page_ids.len()) .map(|_| Arc::new(Mutex::new(()))) @@ -529,18 +527,18 @@ impl LinearHash { } async fn write_meta_file(&self, path: &Path, meta: &LinearHashMeta) -> RS<()> { - let content = serde_json::to_vec_pretty(meta) - .map_err(|e| m_error!(ER::EncodeErr, "encode meta error", e))?; - fs::write(path, content) - .await - .map_err(|e| m_error!(ER::IOErr, "write meta file error", e))?; + write_all_file( + path, + &serde_json::to_vec_pretty(meta) + .map_err(|e| m_error!(ER::EncodeErr, "encode meta error", e))?, + true, + ) + .await?; Ok(()) } async fn read_meta(path: &Path) -> RS { - let buf = fs::read(path) - .await - .map_err(|e| m_error!(ER::IOErr, "read meta file error", e))?; + let buf = read_all_file(path).await?; serde_json::from_slice(&buf) .map_err(|e| m_error!(ER::DecodeErr, "decode meta file error", e)) } @@ -590,18 +588,16 @@ async fn read_page_data( value_size: usize, bucket_capacity: usize, ) -> RS { - let mut file = OpenOptions::new() - .read(true) - .write(true) - .open(page_path) - .await - .map_err(|e| m_error!(ER::IOErr, "open page file error", e))?; - - let mut buf = vec![0; page_size]; - seek_page(&mut file, page_id, page_size).await?; - file.read_exact(&mut buf) - .await - .map_err(|e| m_error!(ER::IOErr, "read page error", e))?; + let file = open_rw(page_path).await?; + let offset = page_offset(page_id, page_size)?; + let read_result = if worker_ring::has_current_worker_ring() { + file::read(&file, page_size, offset).await + } else { + file::read_sync(&file, page_size, offset) + }; + let close_result = close_file(file).await; + let buf = read_result.map_err(|e| m_error!(ER::IOErr, "read page error", e))?; + close_result?; let next = read_u64(&buf[0..8]); let count = read_u32(&buf[8..12]) as usize; @@ -653,13 +649,6 @@ async fn write_page_data( )); } - let mut file = OpenOptions::new() - .read(true) - .write(true) - .open(page_path) - .await - .map_err(|e| m_error!(ER::IOErr, "open page file error", e))?; - let mut buf = vec![0u8; page_size]; write_u64(&mut buf[0..8], page.next_page_id.unwrap_or(NONE_PAGE_ID)); write_u32(&mut buf[8..12], page.entries.len() as u32); @@ -678,26 +667,103 @@ async fn write_page_data( offset += value_size; } - seek_page(&mut file, page_id, page_size).await?; - file.write_all(&buf) - .await - .map_err(|e| m_error!(ER::IOErr, "write page error", e))?; - file.flush() - .await - .map_err(|e| m_error!(ER::IOErr, "flush page file error", e))?; + let file = open_rw(page_path).await?; + let offset = page_offset(page_id, page_size)?; + let write_result = if worker_ring::has_current_worker_ring() { + file::write(&file, buf, offset).await.map(|_| ()) + } else { + file::write_sync(&file, &buf, offset) + }; + let flush_result = if worker_ring::has_current_worker_ring() { + file::flush(&file).await + } else { + file::flush_sync(&file) + }; + let close_result = close_file(file).await; + write_result.map_err(|e| m_error!(ER::IOErr, "write page error", e))?; + flush_result.map_err(|e| m_error!(ER::IOErr, "flush page file error", e))?; + close_result?; Ok(()) } -async fn seek_page(file: &mut tokio::fs::File, page_id: u64, page_size: usize) -> RS<()> { - let offset = page_id - .checked_mul(page_size as u64) - .ok_or_else(|| m_error!(ER::IndexOutOfRange, "page seek overflow"))?; - file.seek(SeekFrom::Start(offset)) +async fn open_rw(path: &Path) -> RS { + if worker_ring::has_current_worker_ring() { + file::open(path, libc::O_RDWR | file::cloexec_flag(), FILE_MODE_644).await + } else { + file::open_sync(path, libc::O_RDWR | file::cloexec_flag(), FILE_MODE_644) + } +} + +async fn open_rw_create_truncate(path: &Path) -> RS { + if worker_ring::has_current_worker_ring() { + file::open( + path, + libc::O_CREAT | libc::O_TRUNC | libc::O_RDWR | file::cloexec_flag(), + FILE_MODE_644, + ) .await - .map_err(|e| m_error!(ER::IOErr, "seek page error", e))?; + } else { + file::open_sync( + path, + libc::O_CREAT | libc::O_TRUNC | libc::O_RDWR | file::cloexec_flag(), + FILE_MODE_644, + ) + } +} + +async fn read_all_file(path: &Path) -> RS> { + let file = open_rw(path).await?; + let len = std::fs::metadata(path) + .map_err(|e| m_error!(ER::IOErr, "read meta file metadata error", e))? + .len() as usize; + let read_result = if worker_ring::has_current_worker_ring() { + file::read(&file, len, 0).await + } else { + file::read_sync(&file, len, 0) + }; + let close_result = close_file(file).await; + let buf = read_result.map_err(|e| m_error!(ER::IOErr, "read meta file error", e))?; + close_result?; + Ok(buf) +} + +async fn write_all_file(path: &Path, buf: &[u8], truncate: bool) -> RS<()> { + let file = if truncate { + open_rw_create_truncate(path).await? + } else { + open_rw(path).await? + }; + let write_result = if worker_ring::has_current_worker_ring() { + file::write(&file, buf.to_vec(), 0).await.map(|_| ()) + } else { + file::write_sync(&file, buf, 0) + }; + let flush_result = if worker_ring::has_current_worker_ring() { + file::flush(&file).await + } else { + file::flush_sync(&file) + }; + let close_result = close_file(file).await; + write_result.map_err(|e| m_error!(ER::IOErr, "write meta file error", e))?; + flush_result.map_err(|e| m_error!(ER::IOErr, "flush meta file error", e))?; + close_result?; Ok(()) } +fn page_offset(page_id: u64, page_size: usize) -> RS { + page_id + .checked_mul(page_size as u64) + .ok_or_else(|| m_error!(ER::IndexOutOfRange, "page seek overflow")) +} + +async fn close_file(file: IoFile) -> RS<()> { + if worker_ring::has_current_worker_ring() { + file::close(file).await + } else { + file::close_sync(file) + } +} + fn validate_key_len(meta: &LinearHashMeta, key: &[u8]) -> RS<()> { if key.len() != meta.key_size { return Err(m_error!( @@ -793,10 +859,10 @@ fn write_u32(buf: &mut [u8], v: u32) { #[cfg(test)] mod tests { use super::*; - use std::time::{SystemTime, UNIX_EPOCH}; + use std::time::UNIX_EPOCH; fn test_dir() -> PathBuf { - let ts = SystemTime::now() + let ts = mudu_sys::time::system_time_now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); diff --git a/mudu_kernel/src/index/index_key/compare_context.rs b/mudu_kernel/src/index/index_key/compare_context.rs new file mode 100644 index 0000000..2bf1e8a --- /dev/null +++ b/mudu_kernel/src/index/index_key/compare_context.rs @@ -0,0 +1,55 @@ +use mudu::common::result::RS; +use mudu_contract::tuple::comparator::TupleComparator; +use mudu_contract::tuple::tuple_binary_desc::TupleBinaryDesc; +use std::cell::RefCell; +use std::ops::Deref; + +thread_local! { + static COMPARE_CONTEXT: RefCell>> = RefCell::new(None); +} + +#[derive(Clone)] +pub struct CompareContext { + pub result: RS<()>, + pub comparator: TupleComparator, + pub desc: TupleBinaryDesc, +} + +impl CompareContext { + pub fn with_context(f: F) -> Option + where + F: FnOnce(&CompareContext) -> Option, + { + Self::with_inner(|c| f(&c.borrow())) + } + + pub fn with_context_mut(f: F) -> Option + where + F: FnOnce(&mut CompareContext) -> Option, + { + Self::with_inner(|c| f(&mut c.borrow_mut())) + } + + pub fn set(ref_cell: RefCell) { + COMPARE_CONTEXT.set(Some(ref_cell)) + } + + pub fn unset() { + COMPARE_CONTEXT.set(None) + } + + fn with_inner(f: F) -> Option + where + F: FnOnce(&RefCell) -> Option, + { + COMPARE_CONTEXT.with::<_, Option>(|context| { + let v = context.borrow(); + match v.deref() { + None => { + todo!() + } + Some(c) => Some(f(c)?), + } + }) + } +} diff --git a/mudu_kernel/src/index/index_key/key_tuple.rs b/mudu_kernel/src/index/index_key/key_tuple.rs new file mode 100644 index 0000000..656c4bb --- /dev/null +++ b/mudu_kernel/src/index/index_key/key_tuple.rs @@ -0,0 +1,94 @@ +use crate::index::index_key::compare_context::CompareContext; +use mudu::common::result::RS; +use std::cmp::Ordering; +use std::hash::{Hash, Hasher}; + +#[derive(Clone, Debug)] +pub struct KeyTuple { + tuple: Vec, +} + +impl KeyTuple { + pub fn new(tuple: Vec) -> Self { + Self { tuple } + } + + pub fn as_slice(&self) -> &[u8] { + &self.tuple + } + + pub fn into_inner(self) -> Vec { + self.tuple + } +} + +impl From> for KeyTuple { + fn from(value: Vec) -> Self { + Self::new(value) + } +} + +impl Eq for KeyTuple {} + +impl PartialEq for KeyTuple { + fn eq(&self, other: &Self) -> bool { + let r = CompareContext::with_context_mut(|c: &mut CompareContext| { + if c.result.is_err() { + return None; + } + let r: RS = (c.comparator.equal)(&self.tuple, &other.tuple, &c.desc); + match r { + Ok(e) => Some(e), + Err(e) => { + c.result = Err(e); + None + } + } + }); + r.unwrap_or(true) + } +} + +impl PartialOrd for KeyTuple { + fn partial_cmp(&self, other: &Self) -> Option { + let r = CompareContext::with_context_mut(|c: &mut CompareContext| { + if c.result.is_err() { + return None; + } + let r: RS = (c.comparator.compare)(&self.tuple, &other.tuple, &c.desc); + match r { + Ok(ord) => Some(ord), + Err(e) => { + c.result = Err(e); + None + } + } + }); + r.or(Some(Ordering::Equal)) + } +} + +impl Ord for KeyTuple { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +impl Hash for KeyTuple { + fn hash(&self, state: &mut H) { + let _ = CompareContext::with_context_mut(|c: &mut CompareContext| { + if c.result.is_err() { + return None; + } + let r: RS<()> = + (c.comparator.hash_cal_one)(&self.tuple, &c.desc, state as &mut dyn Hasher); + match r { + Ok(()) => Some(()), + Err(e) => { + c.result = Err(e); + None + } + } + }); + } +} diff --git a/mudu_kernel/src/index/index_key/mod.rs b/mudu_kernel/src/index/index_key/mod.rs new file mode 100644 index 0000000..12daa3a --- /dev/null +++ b/mudu_kernel/src/index/index_key/mod.rs @@ -0,0 +1,2 @@ +pub mod compare_context; +pub mod key_tuple; diff --git a/mudu_kernel/src/index/mod.rs b/mudu_kernel/src/index/mod.rs index ec5d33c..3d15a4f 100644 --- a/mudu_kernel/src/index/mod.rs +++ b/mudu_kernel/src/index/mod.rs @@ -1 +1,4 @@ +pub mod btree; pub mod hash; +pub mod index_key; +pub mod ordered_index; diff --git a/mudu_kernel/src/index/ordered_index.rs b/mudu_kernel/src/index/ordered_index.rs new file mode 100644 index 0000000..c84e682 --- /dev/null +++ b/mudu_kernel/src/index/ordered_index.rs @@ -0,0 +1,125 @@ +use std::ops::Bound; + +use mudu::common::result::RS; + +use crate::index::btree::btree_index::BTreeIndex; +use crate::index::index_key::compare_context::CompareContext; +use crate::index::index_key::key_tuple::KeyTuple; + +pub trait OrderedIndex { + fn new(context: CompareContext) -> Self + where + Self: Sized; + + fn len(&self) -> RS; + + fn is_empty(&self) -> RS; + + fn clear(&mut self) -> RS<()> + where + V: Clone; + + fn contains_key(&self, key: &KeyTuple) -> RS; + + fn get(&self, key: &KeyTuple) -> RS>; + + fn get_key_value(&self, key: &KeyTuple) -> RS>; + + fn first_key_value(&self) -> RS>; + + fn last_key_value(&self) -> RS>; + + fn insert(&mut self, key: KeyTuple, value: V) -> RS> + where + V: Clone; + + fn remove(&mut self, key: &KeyTuple) -> RS> + where + V: Clone; + + fn pop_first(&mut self) -> RS> + where + V: Clone; + + fn pop_last(&mut self) -> RS> + where + V: Clone; + + fn range(&self, bounds: (Bound<&KeyTuple>, Bound<&KeyTuple>)) -> RS>; +} + +impl OrderedIndex for BTreeIndex { + fn new(context: CompareContext) -> Self + where + Self: Sized, + { + BTreeIndex::new(context) + } + + fn len(&self) -> RS { + BTreeIndex::len(self) + } + + fn is_empty(&self) -> RS { + BTreeIndex::is_empty(self) + } + + fn clear(&mut self) -> RS<()> + where + V: Clone, + { + BTreeIndex::clear(self) + } + + fn contains_key(&self, key: &KeyTuple) -> RS { + BTreeIndex::contains_key(self, key) + } + + fn get(&self, key: &KeyTuple) -> RS> { + BTreeIndex::get(self, key) + } + + fn get_key_value(&self, key: &KeyTuple) -> RS> { + BTreeIndex::get_key_value(self, key) + } + + fn first_key_value(&self) -> RS> { + BTreeIndex::first_key_value(self) + } + + fn last_key_value(&self) -> RS> { + BTreeIndex::last_key_value(self) + } + + fn insert(&mut self, key: KeyTuple, value: V) -> RS> + where + V: Clone, + { + BTreeIndex::insert(self, key, value) + } + + fn remove(&mut self, key: &KeyTuple) -> RS> + where + V: Clone, + { + BTreeIndex::remove(self, key) + } + + fn pop_first(&mut self) -> RS> + where + V: Clone, + { + BTreeIndex::pop_first(self) + } + + fn pop_last(&mut self) -> RS> + where + V: Clone, + { + BTreeIndex::pop_last(self) + } + + fn range(&self, bounds: (Bound<&KeyTuple>, Bound<&KeyTuple>)) -> RS> { + BTreeIndex::range(self, bounds) + } +} diff --git a/mudu_kernel/src/io/file.rs b/mudu_kernel/src/io/file.rs index e09b9d0..1dc7ab4 100644 --- a/mudu_kernel/src/io/file.rs +++ b/mudu_kernel/src/io/file.rs @@ -1,3 +1,1049 @@ +use std::any::Any; +use std::ffi::CString; +use std::future::{poll_fn, Future}; +use std::mem::ManuallyDrop; +use std::path::Path; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +#[cfg(target_os = "linux")] +use crate::io::user_io::completion_error; +use crate::io::user_io::{complete_op, op_state, poll_op, try_take_op, OpState}; +#[cfg(target_os = "linux")] +use crate::io::worker_ring::{with_current_ring, WorkerLocalRing, WorkerRingOp}; +#[cfg(not(target_os = "linux"))] +use crate::io::worker_ring::{with_current_ring, WorkerRingOp}; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; + +#[cfg(unix)] +use std::os::fd::{FromRawFd, IntoRawFd, RawFd}; +#[cfg(windows)] +use std::os::windows::io::{FromRawHandle, IntoRawHandle, RawHandle}; + +#[cfg(windows)] +type RawFd = usize; + +pub type File = tokio::fs::File; pub type TFile = tokio::fs::File; -pub type File = TFile; +#[derive(Debug)] +pub struct IoFile { + fd: RawFd, +} + +#[derive(Clone)] +pub struct WriteHandle { + state: Arc>, +} + +#[derive(Clone)] +pub struct FlushHandle

{ + state: Arc>>, + _marker: std::marker::PhantomData

, +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct OptionWrite { + pub blind_write: bool, +} + +pub(crate) enum FileIoRequest { + Open(FileOpenRequest), + Close(FileCloseRequest), + Read(FileReadRequest), + Write(FileWriteRequest), + Flush(FileFlushRequest), +} + +pub(crate) enum FileInflightOp { + Open(Box), + Close(Box), + Read { + request: Box, + buf: Vec, + }, + Write(Box), + Flush(Box), +} + +pub(crate) struct FileOpenRequest { + path: CString, + flags: i32, + mode: u32, + state: Arc>, +} + +pub(crate) struct FileCloseRequest { + fd: RawFd, + state: Arc>, +} + +pub(crate) struct FileReadRequest { + fd: RawFd, + len: usize, + offset: u64, + state: Arc>>, +} + +pub(crate) struct FileWriteRequest { + fd: RawFd, + offset: u64, + data: Vec, + written: usize, + blind_write: bool, + state: Arc>, +} + +pub(crate) struct FileFlushRequest { + fd: RawFd, + payload: Option>, + state: Arc>>, +} + +impl Default for IoFile { + fn default() -> Self { + Self { + #[cfg(unix)] + fd: 0, + #[cfg(windows)] + fd: 0, + } + } +} + +impl IoFile { + pub fn is_invalid(&self) -> bool { + #[cfg(unix)] + { + self.fd == 0 + } + #[cfg(windows)] + { + self.fd == 0 + } + } + + pub fn new(fd: RawFd) -> Self { + Self { fd } + } +} +pub async fn open>(path: P, flags: i32, mode: u32) -> RS { + let path = CString::new(path.as_ref().as_os_str().as_encoded_bytes()) + .map_err(|_| m_error!(EC::ParseErr, "path contains NUL byte"))?; + let fd = FileOpenFuture::new(path, flags, mode).await?; + Ok(IoFile { fd }) +} + +pub async fn close(file: IoFile) -> RS<()> { + FileCloseFuture::new(file.fd).await +} + +pub async fn read(file: &IoFile, len: usize, offset: u64) -> RS> { + FileReadFuture::new(file.fd, len, offset).await +} + +pub async fn write(file: &IoFile, data: Vec, offset: u64) -> RS { + write_submit_option(file, data, offset, OptionWrite::default())? + .wait() + .await +} + +pub fn open_sync(path: &Path, flags: i32, mode: u32) -> RS { + mudu_sys::fs::open(path, flags, mode).map(std_file_to_io_file) +} + +pub fn read_sync(file: &IoFile, len: usize, offset: u64) -> RS> { + with_std_file(file, |std_file| { + mudu_sys::fs::read_exact_at(std_file, len, offset) + }) +} + +pub fn write_sync(file: &IoFile, payload: &[u8], offset: u64) -> RS<()> { + with_std_file(file, |std_file| { + mudu_sys::fs::write_all_at(std_file, payload, offset) + }) +} + +pub fn flush_sync(file: &IoFile) -> RS<()> { + with_std_file(file, mudu_sys::fs::fsync) +} + +pub fn close_sync(file: IoFile) -> RS<()> { + mudu_sys::fs::close(io_file_into_std(file)) +} + +pub const fn cloexec_flag() -> i32 { + #[cfg(unix)] + { + libc::O_CLOEXEC + } + #[cfg(not(unix))] + { + 0 + } +} + +pub async fn write_option( + file: &IoFile, + data: Vec, + offset: u64, + option: OptionWrite, +) -> RS { + write_submit_option(file, data, offset, option)? + .wait() + .await +} + +pub fn write_submit(file: &IoFile, data: Vec, offset: u64) -> RS { + write_submit_option(file, data, offset, OptionWrite::default()) +} + +pub fn write_submit_option( + file: &IoFile, + data: Vec, + offset: u64, + option: OptionWrite, +) -> RS { + let total_len = data.len(); + let state = op_state(); + with_current_ring(|ring| { + ring.register(WorkerRingOp::File(FileIoRequest::Write( + FileWriteRequest::new(file.fd, offset, data, option.blind_write, state.clone()), + ))) + .map(|_| ()) + })?; + if option.blind_write { + complete_op(state.clone(), Ok(total_len)); + } + Ok(WriteHandle { state }) +} + +pub async fn flush(file: &IoFile) -> RS<()> { + flush_submit(file)?.wait().await +} + +pub async fn flush_lsn(file: &IoFile, ready_lsn: Vec) -> RS> { + flush_submit_lsn(file, ready_lsn)?.wait().await +} + +pub fn flush_submit(file: &IoFile) -> RS> { + flush_submit_payload(file, ()) +} + +pub fn flush_submit_lsn(file: &IoFile, ready_lsn: Vec) -> RS>> { + flush_submit_payload(file, ready_lsn) +} + +fn flush_submit_payload

(file: &IoFile, payload: P) -> RS> +where + P: Send + 'static, +{ + let state = op_state(); + with_current_ring(|ring| { + ring.register(WorkerRingOp::File(FileIoRequest::Flush( + FileFlushRequest::new(file.fd, payload, state.clone()), + ))) + .map(|_| ()) + })?; + Ok(FlushHandle { + state, + _marker: std::marker::PhantomData, + }) +} + +impl IoFile { + pub fn fd(&self) -> RawFd { + self.fd + } + + pub(crate) fn from_raw_fd(fd: RawFd) -> Self { + Self { fd } + } +} + +impl WriteHandle { + pub async fn wait(self) -> RS { + poll_fn(|cx| poll_op(&self.state, cx)).await + } + + pub fn try_take_result(&self) -> Option> { + try_take_op(&self.state) + } +} + +impl

FlushHandle

+where + P: Send + 'static, +{ + pub async fn wait(self) -> RS

{ + poll_fn(|cx| poll_op(&self.state, cx)) + .await + .and_then(|payload| { + payload.downcast::

().map(|boxed| *boxed).map_err(|_| { + mudu::m_error!(EC::InternalErr, "file flush payload type mismatch") + }) + }) + } + + pub fn try_take_result(&self) -> Option> { + try_take_op(&self.state).map(|result| { + result.and_then(|payload| { + payload.downcast::

().map(|boxed| *boxed).map_err(|_| { + mudu::m_error!(EC::InternalErr, "file flush payload type mismatch") + }) + }) + }) + } +} + +impl FileOpenRequest { + fn new(path: CString, flags: i32, mode: u32, state: Arc>) -> Self { + Self { + path, + flags, + mode, + state, + } + } + + pub(crate) fn path(&self) -> &CString { + &self.path + } + + pub(crate) fn flags(&self) -> i32 { + self.flags + } + + pub(crate) fn mode(&self) -> u32 { + self.mode + } + + pub(crate) fn finish(self, result: RS) { + complete_op(self.state, result); + } +} + +impl FileCloseRequest { + fn new(fd: RawFd, state: Arc>) -> Self { + Self { fd, state } + } + + pub(crate) fn fd(&self) -> RawFd { + self.fd + } + + pub(crate) fn finish(self, result: RS<()>) { + complete_op(self.state, result); + } +} + +impl FileReadRequest { + fn new(fd: RawFd, len: usize, offset: u64, state: Arc>>) -> Self { + Self { + fd, + len, + offset, + state, + } + } + + pub(crate) fn fd(&self) -> RawFd { + self.fd + } + + pub(crate) fn len(&self) -> usize { + self.len + } + + pub(crate) fn offset(&self) -> u64 { + self.offset + } + + pub(crate) fn finish(self, result: RS>) { + complete_op(self.state, result); + } +} + +impl FileWriteRequest { + fn new( + fd: RawFd, + offset: u64, + data: Vec, + blind_write: bool, + state: Arc>, + ) -> Self { + Self { + fd, + offset, + data, + written: 0, + blind_write, + state, + } + } + + pub(crate) fn fd(&self) -> RawFd { + self.fd + } + + pub(crate) fn offset(&self) -> u64 { + self.offset + self.written as u64 + } + + pub(crate) fn data_ptr(&self) -> *const libc::c_void { + unsafe { self.data.as_ptr().add(self.written) as *const libc::c_void } + } + + pub(crate) fn remaining_len(&self) -> usize { + self.data.len().saturating_sub(self.written) + } + + pub(crate) fn advance(&mut self, written: usize) { + self.written += written; + } + + pub(crate) fn is_complete(&self) -> bool { + self.written >= self.data.len() + } + + pub(crate) fn total_len(&self) -> usize { + self.data.len() + } + + pub(crate) fn blind_write(&self) -> bool { + self.blind_write + } + + pub(crate) fn finish(self, result: RS) { + complete_op(self.state, result); + } +} + +impl FileFlushRequest { + fn new

(fd: RawFd, payload: P, state: Arc>>) -> Self + where + P: Send + 'static, + { + Self { + fd, + payload: Some(Box::new(payload)), + state, + } + } + + pub(crate) fn fd(&self) -> RawFd { + self.fd + } + + fn finish_boxed(self, result: RS>) { + complete_op(self.state, result); + } + + pub(crate) fn finish_success(mut self) { + let payload = self + .payload + .take() + .expect("flush payload must be present when completing"); + self.finish_boxed(Ok(payload)); + } + + pub(crate) fn finish_error(self, err: mudu::error::err::MError) { + self.finish_boxed(Err(err)); + } +} + +enum FileFutureState { + Init, + Pending(Arc>), + Done, +} + +struct FileOpenFuture { + path: Option, + flags: i32, + mode: u32, + state: FileFutureState, +} + +struct FileCloseFuture { + fd: RawFd, + state: FileFutureState<()>, +} + +struct FileReadFuture { + fd: RawFd, + len: usize, + offset: u64, + state: FileFutureState>, +} + +#[allow(dead_code)] +struct FileWriteFuture { + fd: RawFd, + offset: u64, + data: Option>, + option: OptionWrite, + state: FileFutureState, +} + +#[allow(dead_code)] +struct FileFlushFuture

{ + fd: RawFd, + payload: Option

, + state: FileFutureState>, +} + +impl FileOpenFuture { + fn new(path: CString, flags: i32, mode: u32) -> Self { + Self { + path: Some(path), + flags, + mode, + state: FileFutureState::Init, + } + } +} + +impl FileCloseFuture { + fn new(fd: RawFd) -> Self { + Self { + fd, + state: FileFutureState::Init, + } + } +} + +impl FileReadFuture { + fn new(fd: RawFd, len: usize, offset: u64) -> Self { + Self { + fd, + len, + offset, + state: FileFutureState::Init, + } + } +} + +#[allow(dead_code)] +impl FileWriteFuture { + fn new(fd: RawFd, offset: u64, data: Vec, option: OptionWrite) -> Self { + Self { + fd, + offset, + data: Some(data), + option, + state: FileFutureState::Init, + } + } +} + +#[allow(dead_code)] +impl

FileFlushFuture

{ + fn new(fd: RawFd, payload: P) -> Self { + Self { + fd, + payload: Some(payload), + state: FileFutureState::Init, + } + } +} + +impl Future for FileOpenFuture { + type Output = RS; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &self.state { + FileFutureState::Init => { + let state = op_state(); + let path = self.path.take().unwrap(); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::File(FileIoRequest::Open( + FileOpenRequest::new(path, self.flags, self.mode, state.clone()), + ))) + .map(|_| ()) + }) { + self.state = FileFutureState::Done; + return Poll::Ready(Err(err)); + } + self.state = FileFutureState::Pending(state); + self.poll(cx) + } + FileFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + self.state = FileFutureState::Done; + Poll::Ready(result) + } + Poll::Pending => Poll::Pending, + }, + FileFutureState::Done => Poll::Pending, + } + } +} + +impl Future for FileCloseFuture { + type Output = RS<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &self.state { + FileFutureState::Init => { + let state = op_state(); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::File(FileIoRequest::Close( + FileCloseRequest::new(self.fd, state.clone()), + ))) + .map(|_| ()) + }) { + self.state = FileFutureState::Done; + return Poll::Ready(Err(err)); + } + self.state = FileFutureState::Pending(state); + self.poll(cx) + } + FileFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + self.state = FileFutureState::Done; + Poll::Ready(result) + } + Poll::Pending => Poll::Pending, + }, + FileFutureState::Done => Poll::Pending, + } + } +} + +impl Future for FileReadFuture { + type Output = RS>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &self.state { + FileFutureState::Init => { + let state = op_state(); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::File(FileIoRequest::Read( + FileReadRequest::new(self.fd, self.len, self.offset, state.clone()), + ))) + .map(|_| ()) + }) { + self.state = FileFutureState::Done; + return Poll::Ready(Err(err)); + } + self.state = FileFutureState::Pending(state); + self.poll(cx) + } + FileFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + self.state = FileFutureState::Done; + Poll::Ready(result) + } + Poll::Pending => Poll::Pending, + }, + FileFutureState::Done => Poll::Pending, + } + } +} + +impl Future for FileWriteFuture { + type Output = RS; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &self.state { + FileFutureState::Init => { + let state = op_state(); + let data = self.data.take().unwrap(); + let total_len = data.len(); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::File(FileIoRequest::Write( + FileWriteRequest::new( + self.fd, + self.offset, + data, + self.option.blind_write, + state.clone(), + ), + ))) + .map(|_| ()) + }) { + self.state = FileFutureState::Done; + return Poll::Ready(Err(err)); + } + if self.option.blind_write { + self.state = FileFutureState::Done; + return Poll::Ready(Ok(total_len)); + } + self.state = FileFutureState::Pending(state); + self.poll(cx) + } + FileFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + self.state = FileFutureState::Done; + Poll::Ready(result) + } + Poll::Pending => Poll::Pending, + }, + FileFutureState::Done => Poll::Pending, + } + } +} + +impl

Future for FileFlushFuture

+where + P: Send + Unpin + 'static, +{ + type Output = RS

; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.as_mut().get_mut(); + match &this.state { + FileFutureState::Init => { + let state = op_state(); + let payload = this + .payload + .take() + .expect("flush future payload must be present before registration"); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::File(FileIoRequest::Flush( + FileFlushRequest::new(this.fd, payload, state.clone()), + ))) + .map(|_| ()) + }) { + this.state = FileFutureState::Done; + return Poll::Ready(Err(err)); + } + this.state = FileFutureState::Pending(state); + self.poll(cx) + } + FileFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + this.state = FileFutureState::Done; + Poll::Ready(result.and_then(|payload| { + payload.downcast::

().map(|boxed| *boxed).map_err(|_| { + m_error!(EC::InternalErr, "file flush payload type mismatch") + }) + })) + } + Poll::Pending => Poll::Pending, + }, + FileFutureState::Done => Poll::Pending, + } + } +} + +#[cfg(target_os = "linux")] +pub(crate) fn submit_file_io( + request: FileIoRequest, + sqe: &mut mudu_sys::uring::SubmissionQueueEntry<'_>, +) -> FileInflightOp { + match request { + FileIoRequest::Open(request) => { + sqe.prep_openat(libc::AT_FDCWD, request.path().as_c_str(), request.flags(), request.mode()); + FileInflightOp::Open(Box::new(request)) + } + FileIoRequest::Close(request) => { + sqe.prep_close(request.fd()); + FileInflightOp::Close(Box::new(request)) + } + FileIoRequest::Read(request) => { + let mut buf = vec![0u8; request.len()]; + sqe.prep_read_raw(request.fd(), buf.as_mut_ptr(), request.len(), request.offset()); + FileInflightOp::Read { + request: Box::new(request), + buf, + } + } + FileIoRequest::Write(request) => { + sqe.prep_write_raw( + request.fd(), + request.data_ptr().cast(), + request.remaining_len(), + request.offset(), + ); + FileInflightOp::Write(Box::new(request)) + } + FileIoRequest::Flush(request) => { + sqe.prep_fsync(request.fd()); + FileInflightOp::Flush(Box::new(request)) + } + } +} + +#[cfg(target_os = "linux")] +pub(crate) fn complete_file_io( + op_id: u64, + op: FileInflightOp, + result: i32, + ring: &WorkerLocalRing, +) -> RS<()> { + match op { + FileInflightOp::Open(request) => { + if result < 0 { + request.finish(Err(completion_error("file open", result))); + } else { + request.finish(Ok(result as RawFd)); + } + } + FileInflightOp::Close(request) => { + if result < 0 { + request.finish(Err(completion_error("file close", result))); + } else { + request.finish(Ok(())); + } + } + FileInflightOp::Read { request, mut buf } => { + if result < 0 { + request.finish(Err(completion_error("file read", result))); + } else { + buf.truncate(result as usize); + request.finish(Ok(buf)); + } + } + FileInflightOp::Write(mut request) => { + if result < 0 { + if !request.blind_write() { + request.finish(Err(completion_error("file write", result))); + } + } else { + request.advance(result as usize); + if request.is_complete() { + let total = request.total_len(); + if !request.blind_write() { + request.finish(Ok(total)); + } + } else { + ring.requeue_front(op_id, WorkerRingOp::File(FileIoRequest::Write(*request)))?; + } + } + } + FileInflightOp::Flush(request) => { + if result < 0 { + request.finish_error(completion_error("file flush", result)); + } else { + request.finish_success(); + } + } + } + Ok(()) +} + +fn std_file_to_io_file(file: std::fs::File) -> IoFile { + #[cfg(unix)] + { + IoFile::from_raw_fd(file.into_raw_fd()) + } + #[cfg(windows)] + { + IoFile::from_raw_fd(file.into_raw_handle() as usize) + } +} + +fn with_std_file(file: &IoFile, f: impl FnOnce(&std::fs::File) -> RS) -> RS { + #[cfg(unix)] + let file = unsafe { ManuallyDrop::new(std::fs::File::from_raw_fd(file.fd())) }; + #[cfg(windows)] + let file = unsafe { ManuallyDrop::new(std::fs::File::from_raw_handle(file.fd() as RawHandle)) }; + f(&file) +} + +fn io_file_into_std(file: IoFile) -> std::fs::File { + #[cfg(unix)] + unsafe { + std::fs::File::from_raw_fd(file.fd()) + } + #[cfg(windows)] + unsafe { + std::fs::File::from_raw_handle(file.fd() as RawHandle) + } +} + +#[cfg(all(test, target_os = "linux"))] +mod tests { + use super::*; + use crate::io::worker_ring::{set_current_worker_ring, unset_current_worker_ring}; + use tokio::task::yield_now; + + fn install_test_ring() -> Arc { + let ring = Arc::new(WorkerLocalRing::new()); + set_current_worker_ring(ring.clone()); + ring + } + + #[tokio::test(flavor = "current_thread")] + async fn open_enqueues_request_and_returns_file() { + let ring = install_test_ring(); + let task = tokio::spawn(async { open("/tmp/test-open", libc::O_RDONLY, 0).await }); + yield_now().await; + + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::File(FileIoRequest::Open(request)) => { + assert_eq!(request.flags(), libc::O_RDONLY); + request.finish(Ok(17)); + } + _ => panic!("expected open request"), + } + + let file = task.await.unwrap().unwrap(); + assert_eq!(file.fd(), 17); + unset_current_worker_ring(); + } + + #[tokio::test(flavor = "current_thread")] + async fn read_enqueues_request_and_receives_payload() { + let ring = install_test_ring(); + let file = IoFile { fd: 21 }; + let task = tokio::spawn(async move { read(&file, 8, 12).await }); + yield_now().await; + + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::File(FileIoRequest::Read(request)) => { + assert_eq!(request.fd(), 21); + assert_eq!(request.len(), 8); + assert_eq!(request.offset(), 12); + request.finish(Ok(vec![1, 2, 3])); + } + _ => panic!("expected read request"), + } + + let buf = task.await.unwrap().unwrap(); + assert_eq!(buf, vec![1, 2, 3]); + unset_current_worker_ring(); + } + + #[tokio::test(flavor = "current_thread")] + async fn write_flush_and_close_enqueue_requests() { + let ring = install_test_ring(); + let file = IoFile { fd: 33 }; + + let write_task = tokio::spawn(async move { write(&file, vec![9, 8, 7], 4).await }); + yield_now().await; + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::File(FileIoRequest::Write(request)) => { + assert_eq!(request.fd(), 33); + assert_eq!(request.offset(), 4); + assert_eq!(request.remaining_len(), 3); + request.finish(Ok(3)); + } + _ => panic!("expected write request"), + } + assert_eq!(write_task.await.unwrap().unwrap(), 3); + + let file = IoFile { fd: 33 }; + let flush_task = tokio::spawn(async move { flush(&file).await }); + yield_now().await; + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::File(FileIoRequest::Flush(request)) => { + assert_eq!(request.fd(), 33); + request.finish_success(); + } + _ => panic!("expected flush request"), + } + flush_task.await.unwrap().unwrap(); + + let close_task = tokio::spawn(async move { close(IoFile { fd: 33 }).await }); + yield_now().await; + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::File(FileIoRequest::Close(request)) => { + assert_eq!(request.fd(), 33); + request.finish(Ok(())); + } + _ => panic!("expected close request"), + } + close_task.await.unwrap().unwrap(); + unset_current_worker_ring(); + } + + #[tokio::test(flavor = "current_thread")] + async fn write_submit_and_wait_split_registration_from_completion() { + let ring = install_test_ring(); + let file = IoFile { fd: 44 }; + + let handle = write_submit(&file, vec![5, 6, 7], 16).unwrap(); + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::File(FileIoRequest::Write(request)) => { + assert_eq!(request.fd(), 44); + assert_eq!(request.offset(), 16); + assert_eq!(request.remaining_len(), 3); + request.finish(Ok(3)); + } + _ => panic!("expected write request"), + } + assert_eq!(handle.wait().await.unwrap(), 3); + unset_current_worker_ring(); + } + + #[tokio::test(flavor = "current_thread")] + async fn blind_write_returns_after_registration() { + let ring = install_test_ring(); + let file = IoFile { fd: 55 }; + + let write_task = tokio::spawn(async move { + write_option( + &file, + vec![1, 2, 3, 4], + 8, + OptionWrite { blind_write: true }, + ) + .await + }); + yield_now().await; + + assert!(write_task.is_finished()); + assert_eq!(write_task.await.unwrap().unwrap(), 4); + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::File(FileIoRequest::Write(request)) => { + assert_eq!(request.fd(), 55); + assert_eq!(request.offset(), 8); + assert!(request.blind_write()); + assert_eq!(request.remaining_len(), 4); + } + _ => panic!("expected blind write request"), + } + unset_current_worker_ring(); + } + + #[tokio::test(flavor = "current_thread")] + async fn flush_submit_lsn_and_wait_split_registration_from_completion() { + let ring = install_test_ring(); + let file = IoFile { fd: 61 }; + + let handle = flush_submit_lsn(&file, vec![10, 11]).unwrap(); + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::File(FileIoRequest::Flush(request)) => { + assert_eq!(request.fd(), 61); + request.finish_success(); + } + _ => panic!("expected flush request"), + } + + assert_eq!(handle.wait().await.unwrap(), vec![10, 11]); + unset_current_worker_ring(); + } + + #[tokio::test(flavor = "current_thread")] + async fn flush_lsn_enqueues_request_and_returns_payload() { + let ring = install_test_ring(); + let file = IoFile { fd: 41 }; + let task = tokio::spawn(async move { flush_lsn(&file, vec![7, 8, 9]).await }); + yield_now().await; + + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::File(FileIoRequest::Flush(request)) => { + assert_eq!(request.fd(), 41); + request.finish_success(); + } + _ => panic!("expected flush request"), + } + + let ready_lsns = task.await.unwrap().unwrap(); + assert_eq!(ready_lsns, vec![7, 8, 9]); + unset_current_worker_ring(); + } + + #[tokio::test(flavor = "current_thread")] + async fn open_without_current_ring_returns_error() { + unset_current_worker_ring(); + let err = open("/tmp/test-open", libc::O_RDONLY, 0).await.unwrap_err(); + assert_eq!(err.ec(), EC::NoSuchElement); + } +} diff --git a/mudu_kernel/src/io/mod.rs b/mudu_kernel/src/io/mod.rs index 2e172cd..d1f6bac 100644 --- a/mudu_kernel/src/io/mod.rs +++ b/mudu_kernel/src/io/mod.rs @@ -1 +1,9 @@ pub mod file; +#[cfg(target_os = "linux")] +pub mod socket; +pub(crate) mod user_io; +#[cfg(target_os = "linux")] +pub mod worker_ring; +#[cfg(not(target_os = "linux"))] +#[path = "worker_ring_stub.rs"] +pub mod worker_ring; diff --git a/mudu_kernel/src/io/socket.rs b/mudu_kernel/src/io/socket.rs new file mode 100644 index 0000000..0282d0a --- /dev/null +++ b/mudu_kernel/src/io/socket.rs @@ -0,0 +1,1107 @@ +use std::future::Future; +use std::marker::PhantomData; +use std::net::SocketAddr; +use std::os::fd::RawFd; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; + +use crate::io::user_io::{complete_op, completion_error, op_state, poll_op, OpState}; +use crate::io::worker_ring::{with_current_ring, WorkerLocalRing, WorkerRingOp}; + +#[derive(Debug)] +pub struct IoSocket { + fd: RawFd, +} + +pub(crate) enum SocketIoRequest { + Socket(SocketOpenRequest), + Connect(SocketConnectRequest), + Accept(SocketAcceptRequest), + Recv(SocketRecvRequest), + Send(SocketSendRequest), + SendRef(SocketSendRefRequest), + Shutdown(SocketShutdownRequest), + Close(SocketCloseRequest), +} + +pub(crate) enum SocketInflightOp { + Open(Box), + Connect(Box), + Accept(Box), + Recv(Box), + Send(Box), + SendRef(Box), + Shutdown(Box), + Close(Box), +} + +pub(crate) struct SocketOpenRequest { + domain: i32, + socket_type: i32, + protocol: i32, + state: Arc>, +} + +pub(crate) struct SocketConnectRequest { + fd: RawFd, + addr: mudu_sys::uring::SockAddrBuf, + state: Arc>, +} + +pub(crate) struct SocketAcceptRequest { + fd: RawFd, + addr: mudu_sys::uring::SockAddrBuf, + state: Arc>, +} + +pub(crate) struct SocketRecvRequest { + fd: RawFd, + buf_ptr: *mut u8, + len: usize, + flags: i32, + state: Arc>, +} + +pub(crate) struct SocketSendRequest { + fd: RawFd, + flags: i32, + data: Vec, + sent: usize, + state: Arc>, +} + +pub(crate) struct SocketSendRefRequest { + fd: RawFd, + flags: i32, + data_ptr: *const u8, + len: usize, + sent: usize, + state: Arc>, +} + +pub(crate) struct SocketShutdownRequest { + fd: RawFd, + how: i32, + state: Arc>, +} + +pub(crate) struct SocketCloseRequest { + fd: RawFd, + state: Arc>, +} + +pub async fn socket(domain: i32, socket_type: i32, protocol: i32) -> RS { + let fd = SocketOpenFuture::new(domain, socket_type, protocol).await?; + Ok(IoSocket { fd }) +} + +pub async fn connect(sock: &IoSocket, addr: SocketAddr) -> RS<()> { + SocketConnectFuture::new(sock.fd, addr).await +} + +pub async fn accept(sock: &IoSocket) -> RS<(IoSocket, SocketAddr)> { + let (fd, addr) = SocketAcceptFuture::new(sock.fd).await?; + Ok((IoSocket { fd }, addr)) +} + +pub async fn recv(sock: &IoSocket, len: usize, flags: i32) -> RS> { + let mut buf = vec![0u8; len]; + let read = recv_into(sock, buf.as_mut_slice(), flags).await?; + buf.truncate(read); + Ok(buf) +} + +pub async fn send(sock: &IoSocket, data: Vec, flags: i32) -> RS { + SocketSendFuture::new(sock.fd, data, flags).await +} + +pub async fn shutdown(sock: &IoSocket, how: i32) -> RS<()> { + SocketShutdownFuture::new(sock.fd, how).await +} + +pub async fn close(sock: IoSocket) -> RS<()> { + SocketCloseFuture::new(sock.fd).await +} + +pub async fn recv_some(sock: &IoSocket, len: usize) -> RS> { + recv(sock, len, 0).await +} + +pub async fn recv_into(sock: &IoSocket, buf: &mut [u8], flags: i32) -> RS { + SocketRecvIntoFuture::new(sock.fd, buf, flags).await +} + +pub async fn send_all(sock: &IoSocket, data: &[u8]) -> RS<()> { + let sent = SocketSendRefFuture::new(sock.fd, data, 0).await?; + if sent != data.len() { + return Err(m_error!( + EC::NetErr, + format!( + "socket send incomplete: sent {}, expected {}", + sent, + data.len() + ) + )); + } + Ok(()) +} + +impl IoSocket { + pub fn fd(&self) -> RawFd { + self.fd + } + + pub(crate) fn from_raw_fd(fd: RawFd) -> Self { + Self { fd } + } + + pub(crate) fn into_raw_fd(self) -> RawFd { + self.fd + } +} + +impl SocketOpenRequest { + fn new(domain: i32, socket_type: i32, protocol: i32, state: Arc>) -> Self { + Self { + domain, + socket_type, + protocol, + state, + } + } + + pub(crate) fn domain(&self) -> i32 { + self.domain + } + + pub(crate) fn socket_type(&self) -> i32 { + self.socket_type + } + + pub(crate) fn protocol(&self) -> i32 { + self.protocol + } + + pub(crate) fn finish(self, result: RS) { + complete_op(self.state, result); + } +} + +impl SocketConnectRequest { + fn new( + fd: RawFd, + addr: mudu_sys::uring::SockAddrBuf, + state: Arc>, + ) -> Self { + Self { fd, addr, state } + } + + pub(crate) fn fd(&self) -> RawFd { + self.fd + } + + pub(crate) fn addr(&self) -> &mudu_sys::uring::SockAddrBuf { + &self.addr + } + + pub(crate) fn finish(self, result: RS<()>) { + complete_op(self.state, result); + } +} + +impl SocketAcceptRequest { + fn new(fd: RawFd, state: Arc>) -> Self { + Self { + fd, + addr: mudu_sys::uring::SockAddrBuf::new_empty(), + state, + } + } + + pub(crate) fn fd(&self) -> RawFd { + self.fd + } + + pub(crate) fn addr_mut(&mut self) -> &mut mudu_sys::uring::SockAddrBuf { + &mut self.addr + } + + pub(crate) fn addr(&self) -> &mudu_sys::uring::SockAddrBuf { + &self.addr + } + + pub(crate) fn finish(self, result: RS<(RawFd, SocketAddr)>) { + complete_op(self.state, result); + } +} + +impl SocketRecvRequest { + fn new( + fd: RawFd, + buf_ptr: *mut u8, + len: usize, + flags: i32, + state: Arc>, + ) -> Self { + Self { + fd, + buf_ptr, + len, + flags, + state, + } + } + + pub(crate) fn fd(&self) -> RawFd { + self.fd + } + + pub(crate) fn len(&self) -> usize { + self.len + } + + pub(crate) fn buf_ptr(&self) -> *mut libc::c_void { + self.buf_ptr as *mut libc::c_void + } + + pub(crate) fn flags(&self) -> i32 { + self.flags + } + + pub(crate) fn finish(self, result: RS) { + complete_op(self.state, result); + } +} + +impl SocketSendRequest { + fn new(fd: RawFd, data: Vec, flags: i32, state: Arc>) -> Self { + Self { + fd, + flags, + data, + sent: 0, + state, + } + } + + pub(crate) fn fd(&self) -> RawFd { + self.fd + } + + pub(crate) fn flags(&self) -> i32 { + self.flags + } + + pub(crate) fn data_ptr(&self) -> *const libc::c_void { + unsafe { self.data.as_ptr().add(self.sent) as *const libc::c_void } + } + + pub(crate) fn remaining_len(&self) -> usize { + self.data.len().saturating_sub(self.sent) + } + + pub(crate) fn advance(&mut self, sent: usize) { + self.sent += sent; + } + + pub(crate) fn is_complete(&self) -> bool { + self.sent >= self.data.len() + } + + pub(crate) fn total_len(&self) -> usize { + self.data.len() + } + + pub(crate) fn finish(self, result: RS) { + complete_op(self.state, result); + } +} + +impl SocketSendRefRequest { + fn new( + fd: RawFd, + data_ptr: *const u8, + len: usize, + flags: i32, + state: Arc>, + ) -> Self { + Self { + fd, + flags, + data_ptr, + len, + sent: 0, + state, + } + } + + pub(crate) fn fd(&self) -> RawFd { + self.fd + } + + pub(crate) fn flags(&self) -> i32 { + self.flags + } + + pub(crate) fn data_ptr(&self) -> *const libc::c_void { + unsafe { self.data_ptr.add(self.sent) as *const libc::c_void } + } + + pub(crate) fn remaining_len(&self) -> usize { + self.len.saturating_sub(self.sent) + } + + pub(crate) fn advance(&mut self, sent: usize) { + self.sent += sent; + } + + pub(crate) fn is_complete(&self) -> bool { + self.sent >= self.len + } + + pub(crate) fn total_len(&self) -> usize { + self.len + } + + pub(crate) fn finish(self, result: RS) { + complete_op(self.state, result); + } +} + +impl SocketShutdownRequest { + fn new(fd: RawFd, how: i32, state: Arc>) -> Self { + Self { fd, how, state } + } + + pub(crate) fn fd(&self) -> RawFd { + self.fd + } + + pub(crate) fn how(&self) -> i32 { + self.how + } + + pub(crate) fn finish(self, result: RS<()>) { + complete_op(self.state, result); + } +} + +impl SocketCloseRequest { + fn new(fd: RawFd, state: Arc>) -> Self { + Self { fd, state } + } + + pub(crate) fn fd(&self) -> RawFd { + self.fd + } + + pub(crate) fn finish(self, result: RS<()>) { + complete_op(self.state, result); + } +} + +enum SocketFutureState { + Init, + Pending(Arc>), + Done, +} + +struct SocketOpenFuture { + domain: i32, + socket_type: i32, + protocol: i32, + state: SocketFutureState, +} + +struct SocketConnectFuture { + fd: RawFd, + addr: Option, + state: SocketFutureState<()>, +} + +struct SocketAcceptFuture { + fd: RawFd, + state: SocketFutureState<(RawFd, SocketAddr)>, +} + +struct SocketRecvIntoFuture<'a> { + fd: RawFd, + buf_ptr: *mut u8, + len: usize, + flags: i32, + state: SocketFutureState, + _marker: PhantomData<&'a mut [u8]>, +} + +unsafe impl<'a> Send for SocketRecvIntoFuture<'a> {} + +struct SocketSendFuture { + fd: RawFd, + data: Option>, + flags: i32, + state: SocketFutureState, +} + +struct SocketSendRefFuture<'a> { + fd: RawFd, + data_ptr: *const u8, + len: usize, + flags: i32, + state: SocketFutureState, + _marker: PhantomData<&'a [u8]>, +} + +struct SocketShutdownFuture { + fd: RawFd, + how: i32, + state: SocketFutureState<()>, +} + +struct SocketCloseFuture { + fd: RawFd, + state: SocketFutureState<()>, +} + +impl SocketOpenFuture { + fn new(domain: i32, socket_type: i32, protocol: i32) -> Self { + Self { + domain, + socket_type, + protocol, + state: SocketFutureState::Init, + } + } +} + +impl SocketConnectFuture { + fn new(fd: RawFd, addr: SocketAddr) -> Self { + Self { + fd, + addr: Some(socket_addr_to_raw(addr)), + state: SocketFutureState::Init, + } + } +} + +impl SocketAcceptFuture { + fn new(fd: RawFd) -> Self { + Self { + fd, + state: SocketFutureState::Init, + } + } +} + +impl<'a> SocketRecvIntoFuture<'a> { + fn new(fd: RawFd, buf: &'a mut [u8], flags: i32) -> Self { + Self { + fd, + buf_ptr: buf.as_mut_ptr(), + len: buf.len(), + flags, + state: SocketFutureState::Init, + _marker: PhantomData, + } + } +} + +impl SocketSendFuture { + fn new(fd: RawFd, data: Vec, flags: i32) -> Self { + Self { + fd, + data: Some(data), + flags, + state: SocketFutureState::Init, + } + } +} + +impl<'a> SocketSendRefFuture<'a> { + fn new(fd: RawFd, data: &'a [u8], flags: i32) -> Self { + Self { + fd, + data_ptr: data.as_ptr(), + len: data.len(), + flags, + state: SocketFutureState::Init, + _marker: PhantomData, + } + } +} + +impl SocketShutdownFuture { + fn new(fd: RawFd, how: i32) -> Self { + Self { + fd, + how, + state: SocketFutureState::Init, + } + } +} + +impl SocketCloseFuture { + fn new(fd: RawFd) -> Self { + Self { + fd, + state: SocketFutureState::Init, + } + } +} + +impl Future for SocketOpenFuture { + type Output = RS; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &self.state { + SocketFutureState::Init => { + let state = op_state(); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::Socket(SocketIoRequest::Socket( + SocketOpenRequest::new( + self.domain, + self.socket_type, + self.protocol, + state.clone(), + ), + ))) + .map(|_| ()) + }) { + self.state = SocketFutureState::Done; + return Poll::Ready(Err(err)); + } + self.state = SocketFutureState::Pending(state); + self.poll(cx) + } + SocketFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + self.state = SocketFutureState::Done; + Poll::Ready(result) + } + Poll::Pending => Poll::Pending, + }, + SocketFutureState::Done => Poll::Pending, + } + } +} + +impl Future for SocketConnectFuture { + type Output = RS<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &self.state { + SocketFutureState::Init => { + let state = op_state(); + let addr = self.addr.take().unwrap(); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::Socket(SocketIoRequest::Connect( + SocketConnectRequest::new(self.fd, addr, state.clone()), + ))) + .map(|_| ()) + }) { + self.state = SocketFutureState::Done; + return Poll::Ready(Err(err)); + } + self.state = SocketFutureState::Pending(state); + self.poll(cx) + } + SocketFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + self.state = SocketFutureState::Done; + Poll::Ready(result) + } + Poll::Pending => Poll::Pending, + }, + SocketFutureState::Done => Poll::Pending, + } + } +} + +impl Future for SocketAcceptFuture { + type Output = RS<(RawFd, SocketAddr)>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &self.state { + SocketFutureState::Init => { + let state = op_state(); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::Socket(SocketIoRequest::Accept( + SocketAcceptRequest::new(self.fd, state.clone()), + ))) + .map(|_| ()) + }) { + self.state = SocketFutureState::Done; + return Poll::Ready(Err(err)); + } + self.state = SocketFutureState::Pending(state); + self.poll(cx) + } + SocketFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + self.state = SocketFutureState::Done; + Poll::Ready(result) + } + Poll::Pending => Poll::Pending, + }, + SocketFutureState::Done => Poll::Pending, + } + } +} + +impl<'a> Future for SocketRecvIntoFuture<'a> { + type Output = RS; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &self.state { + SocketFutureState::Init => { + let state = op_state(); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::Socket(SocketIoRequest::Recv( + SocketRecvRequest::new( + self.fd, + self.buf_ptr, + self.len, + self.flags, + state.clone(), + ), + ))) + .map(|_| ()) + }) { + self.state = SocketFutureState::Done; + return Poll::Ready(Err(err)); + } + self.state = SocketFutureState::Pending(state); + self.poll(cx) + } + SocketFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + self.state = SocketFutureState::Done; + Poll::Ready(result) + } + Poll::Pending => Poll::Pending, + }, + SocketFutureState::Done => Poll::Pending, + } + } +} + +impl Future for SocketSendFuture { + type Output = RS; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &self.state { + SocketFutureState::Init => { + let state = op_state(); + let data = self.data.take().unwrap(); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::Socket(SocketIoRequest::Send( + SocketSendRequest::new(self.fd, data, self.flags, state.clone()), + ))) + .map(|_| ()) + }) { + self.state = SocketFutureState::Done; + return Poll::Ready(Err(err)); + } + self.state = SocketFutureState::Pending(state); + self.poll(cx) + } + SocketFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + self.state = SocketFutureState::Done; + Poll::Ready(result) + } + Poll::Pending => Poll::Pending, + }, + SocketFutureState::Done => Poll::Pending, + } + } +} + +unsafe impl<'a> Send for SocketSendRefFuture<'a> {} + +impl<'a> Future for SocketSendRefFuture<'a> { + type Output = RS; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &self.state { + SocketFutureState::Init => { + let state = op_state(); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::Socket(SocketIoRequest::SendRef( + SocketSendRefRequest::new( + self.fd, + self.data_ptr, + self.len, + self.flags, + state.clone(), + ), + ))) + .map(|_| ()) + }) { + self.state = SocketFutureState::Done; + return Poll::Ready(Err(err)); + } + self.state = SocketFutureState::Pending(state); + self.poll(cx) + } + SocketFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + self.state = SocketFutureState::Done; + Poll::Ready(result) + } + Poll::Pending => Poll::Pending, + }, + SocketFutureState::Done => Poll::Pending, + } + } +} + +impl Future for SocketShutdownFuture { + type Output = RS<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &self.state { + SocketFutureState::Init => { + let state = op_state(); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::Socket(SocketIoRequest::Shutdown( + SocketShutdownRequest::new(self.fd, self.how, state.clone()), + ))) + .map(|_| ()) + }) { + self.state = SocketFutureState::Done; + return Poll::Ready(Err(err)); + } + self.state = SocketFutureState::Pending(state); + self.poll(cx) + } + SocketFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + self.state = SocketFutureState::Done; + Poll::Ready(result) + } + Poll::Pending => Poll::Pending, + }, + SocketFutureState::Done => Poll::Pending, + } + } +} + +impl Future for SocketCloseFuture { + type Output = RS<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &self.state { + SocketFutureState::Init => { + let state = op_state(); + if let Err(err) = with_current_ring(|ring| { + ring.register(WorkerRingOp::Socket(SocketIoRequest::Close( + SocketCloseRequest::new(self.fd, state.clone()), + ))) + .map(|_| ()) + }) { + self.state = SocketFutureState::Done; + return Poll::Ready(Err(err)); + } + self.state = SocketFutureState::Pending(state); + self.poll(cx) + } + SocketFutureState::Pending(state) => match poll_op(state, cx) { + Poll::Ready(result) => { + self.state = SocketFutureState::Done; + Poll::Ready(result) + } + Poll::Pending => Poll::Pending, + }, + SocketFutureState::Done => Poll::Pending, + } + } +} + +fn socket_addr_to_raw(addr: SocketAddr) -> mudu_sys::uring::SockAddrBuf { + mudu_sys::net::socket_addr_to_storage(addr).expect("socket addr to storage conversion failed") +} + +pub(crate) fn raw_to_socket_addr(addr: &mudu_sys::uring::SockAddrBuf) -> RS { + mudu_sys::net::sockaddr_to_socket_addr(addr) +} + +pub(crate) fn submit_socket_io( + request: SocketIoRequest, + sqe: &mut mudu_sys::uring::SubmissionQueueEntry<'_>, +) -> SocketInflightOp { + match request { + SocketIoRequest::Socket(request) => { + sqe.prep_socket(request.domain(), request.socket_type(), request.protocol(), 0); + SocketInflightOp::Open(Box::new(request)) + } + SocketIoRequest::Connect(request) => { + sqe.prep_connect(request.fd(), request.addr()); + SocketInflightOp::Connect(Box::new(request)) + } + SocketIoRequest::Accept(request) => { + let mut request = Box::new(request); + sqe.prep_accept(request.fd(), request.addr_mut(), 0); + SocketInflightOp::Accept(request) + } + SocketIoRequest::Recv(request) => { + sqe.prep_recv_raw(request.fd(), request.buf_ptr().cast(), request.len(), request.flags()); + SocketInflightOp::Recv(Box::new(request)) + } + SocketIoRequest::Send(request) => { + sqe.prep_send_raw( + request.fd(), + request.data_ptr().cast(), + request.remaining_len(), + request.flags(), + ); + SocketInflightOp::Send(Box::new(request)) + } + SocketIoRequest::SendRef(request) => { + sqe.prep_send_raw( + request.fd(), + request.data_ptr().cast(), + request.remaining_len(), + request.flags(), + ); + SocketInflightOp::SendRef(Box::new(request)) + } + SocketIoRequest::Shutdown(request) => { + sqe.prep_shutdown(request.fd(), request.how()); + SocketInflightOp::Shutdown(Box::new(request)) + } + SocketIoRequest::Close(request) => { + sqe.prep_close(request.fd()); + SocketInflightOp::Close(Box::new(request)) + } + } +} + +pub(crate) fn complete_socket_io( + op_id: u64, + op: SocketInflightOp, + result: i32, + ring: &WorkerLocalRing, +) -> RS<()> { + match op { + SocketInflightOp::Open(request) => { + if result < 0 { + request.finish(Err(completion_error("socket open", result))); + } else { + request.finish(Ok(result as RawFd)); + } + } + SocketInflightOp::Connect(request) => { + if result < 0 { + request.finish(Err(completion_error("socket connect", result))); + } else { + request.finish(Ok(())); + } + } + SocketInflightOp::Accept(request) => { + if result < 0 { + request.finish(Err(completion_error("socket accept", result))); + } else { + let remote_addr = raw_to_socket_addr(request.addr())?; + request.finish(Ok((result as RawFd, remote_addr))); + } + } + SocketInflightOp::Recv(request) => { + if result < 0 { + request.finish(Err(completion_error("socket recv", result))); + } else { + request.finish(Ok(result as usize)); + } + } + SocketInflightOp::Send(mut request) => { + if result < 0 { + request.finish(Err(completion_error("socket send", result))); + } else { + request.advance(result as usize); + if request.is_complete() { + let total = request.total_len(); + request.finish(Ok(total)); + } else { + ring.requeue_front( + op_id, + WorkerRingOp::Socket(SocketIoRequest::Send(*request)), + )?; + } + } + } + SocketInflightOp::SendRef(mut request) => { + if result < 0 { + request.finish(Err(completion_error("socket send", result))); + } else { + request.advance(result as usize); + if request.is_complete() { + let total = request.total_len(); + request.finish(Ok(total)); + } else { + ring.requeue_front( + op_id, + WorkerRingOp::Socket(SocketIoRequest::SendRef(*request)), + )?; + } + } + } + SocketInflightOp::Shutdown(request) => { + if result < 0 { + request.finish(Err(completion_error("socket shutdown", result))); + } else { + request.finish(Ok(())); + } + } + SocketInflightOp::Close(request) => { + if result < 0 { + request.finish(Err(completion_error("socket close", result))); + } else { + request.finish(Ok(())); + } + } + } + Ok(()) +} + +#[cfg(all(test, target_os = "linux"))] +mod tests { + use super::*; + use crate::io::worker_ring::{set_current_worker_ring, unset_current_worker_ring}; + use tokio::task::yield_now; + + fn install_test_ring() -> Arc { + let ring = Arc::new(WorkerLocalRing::new()); + set_current_worker_ring(ring.clone()); + ring + } + + #[tokio::test(flavor = "current_thread")] + async fn socket_and_connect_enqueue_requests() { + let ring = install_test_ring(); + let create_task = tokio::spawn(async { socket(libc::AF_INET, libc::SOCK_STREAM, 0).await }); + yield_now().await; + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::Socket(SocketIoRequest::Socket(request)) => { + assert_eq!(request.domain(), libc::AF_INET); + assert_eq!(request.socket_type(), libc::SOCK_STREAM); + assert_eq!(request.protocol(), 0); + request.finish(Ok(41)); + } + _ => panic!("expected socket request"), + } + let sock = create_task.await.unwrap().unwrap(); + assert_eq!(sock.fd(), 41); + + let connect_task = + tokio::spawn(async move { connect(&sock, "127.0.0.1:9000".parse().unwrap()).await }); + yield_now().await; + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::Socket(SocketIoRequest::Connect(request)) => { + assert_eq!(request.fd(), 41); + request.finish(Ok(())); + } + _ => panic!("expected connect request"), + } + connect_task.await.unwrap().unwrap(); + unset_current_worker_ring(); + } + + #[tokio::test(flavor = "current_thread")] + async fn accept_recv_send_shutdown_and_close_enqueue_requests() { + let ring = install_test_ring(); + let sock = IoSocket { fd: 51 }; + + let accept_task = tokio::spawn(async move { accept(&sock).await }); + yield_now().await; + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::Socket(SocketIoRequest::Accept(request)) => { + assert_eq!(request.fd(), 51); + request.finish(Ok((61, "127.0.0.1:9010".parse().unwrap()))); + } + _ => panic!("expected accept request"), + } + let (accepted, addr) = accept_task.await.unwrap().unwrap(); + assert_eq!(accepted.fd(), 61); + assert_eq!(addr, "127.0.0.1:9010".parse::().unwrap()); + + let recv_task = tokio::spawn(async move { + let mut buf = [0u8; 8]; + let read = recv_into(&accepted, &mut buf, libc::MSG_DONTWAIT).await?; + Ok::<_, mudu::error::err::MError>((read, buf)) + }); + yield_now().await; + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::Socket(SocketIoRequest::Recv(request)) => { + assert_eq!(request.fd(), 61); + assert_eq!(request.len(), 8); + assert_eq!(request.flags(), libc::MSG_DONTWAIT); + unsafe { + std::ptr::copy_nonoverlapping( + [7u8, 8, 9].as_ptr(), + request.buf_ptr() as *mut u8, + 3, + ); + } + request.finish(Ok(3)); + } + _ => panic!("expected recv request"), + } + let (read, recv_buf) = recv_task.await.unwrap().unwrap(); + assert_eq!(read, 3); + assert_eq!(&recv_buf[..3], &[7, 8, 9]); + + let send_sock = IoSocket { fd: 71 }; + let send_task = + tokio::spawn(async move { send(&send_sock, vec![1, 2, 3], libc::MSG_NOSIGNAL).await }); + yield_now().await; + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::Socket(SocketIoRequest::Send(request)) => { + assert_eq!(request.fd(), 71); + assert_eq!(request.flags(), libc::MSG_NOSIGNAL); + assert_eq!(request.remaining_len(), 3); + request.finish(Ok(3)); + } + _ => panic!("expected send request"), + } + assert_eq!(send_task.await.unwrap().unwrap(), 3); + + let shutdown_sock = IoSocket { fd: 71 }; + let shutdown_task = + tokio::spawn(async move { shutdown(&shutdown_sock, libc::SHUT_WR).await }); + yield_now().await; + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::Socket(SocketIoRequest::Shutdown(request)) => { + assert_eq!(request.fd(), 71); + assert_eq!(request.how(), libc::SHUT_WR); + request.finish(Ok(())); + } + _ => panic!("expected shutdown request"), + } + shutdown_task.await.unwrap().unwrap(); + + let close_task = tokio::spawn(async move { close(IoSocket { fd: 71 }).await }); + yield_now().await; + match ring.take_pending().unwrap().unwrap().1 { + WorkerRingOp::Socket(SocketIoRequest::Close(request)) => { + assert_eq!(request.fd(), 71); + request.finish(Ok(())); + } + _ => panic!("expected close request"), + } + close_task.await.unwrap().unwrap(); + unset_current_worker_ring(); + } + + #[tokio::test(flavor = "current_thread")] + async fn socket_without_current_ring_returns_error() { + unset_current_worker_ring(); + let err = socket(libc::AF_INET, libc::SOCK_STREAM, 0) + .await + .unwrap_err(); + assert_eq!(err.ec(), EC::NoSuchElement); + } +} diff --git a/mudu_kernel/src/io/user_io.rs b/mudu_kernel/src/io/user_io.rs new file mode 100644 index 0000000..303f13b --- /dev/null +++ b/mudu_kernel/src/io/user_io.rs @@ -0,0 +1,52 @@ +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::error::err::MError; +use mudu::m_error; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll, Waker}; + +pub(crate) fn completion_error(kind: &'static str, result: i32) -> MError { + m_error!( + EC::IOErr, + format!("worker user {} completion error {}", kind, result) + ) +} + +pub(crate) struct OpState { + result: Mutex>>, + waker: Mutex>, +} + +pub(crate) fn op_state() -> Arc> { + Arc::new(OpState { + result: Mutex::new(None), + waker: Mutex::new(None), + }) +} + +pub(crate) fn complete_op(state: Arc>, result: RS) { + if let Ok(mut slot) = state.result.lock() { + *slot = Some(result); + } + if let Ok(mut waker) = state.waker.lock() { + if let Some(waker) = waker.take() { + waker.wake(); + } + } +} + +pub(crate) fn poll_op(state: &Arc>, cx: &mut Context<'_>) -> Poll> { + if let Ok(mut slot) = state.result.lock() { + if let Some(result) = slot.take() { + return Poll::Ready(result); + } + } + if let Ok(mut waker) = state.waker.lock() { + *waker = Some(cx.waker().clone()); + } + Poll::Pending +} + +pub(crate) fn try_take_op(state: &Arc>) -> Option> { + state.result.lock().ok()?.take() +} diff --git a/mudu_kernel/src/io/worker_ring.rs b/mudu_kernel/src/io/worker_ring.rs new file mode 100644 index 0000000..4b369d6 --- /dev/null +++ b/mudu_kernel/src/io/worker_ring.rs @@ -0,0 +1,152 @@ +use std::cell::RefCell; +use std::collections::{HashMap, VecDeque}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; + +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; + +use crate::io::file::{complete_file_io, submit_file_io, FileInflightOp, FileIoRequest}; +use crate::io::socket::{complete_socket_io, submit_socket_io, SocketInflightOp, SocketIoRequest}; +use crate::server::task_registry::WorkerTaskRegistry; + +thread_local! { + static CURRENT_WORKER_RING: RefCell>> = const { RefCell::new(None) }; +} + +pub(crate) enum WorkerRingOp { + File(FileIoRequest), + Socket(SocketIoRequest), +} + +pub(crate) enum UserIoInflight { + File { op_id: u64, op: FileInflightOp }, + Socket { op_id: u64, op: SocketInflightOp }, +} + +pub(crate) struct WorkerLocalRing { + worker_tasks: WorkerTaskRegistry, + next_op_id: AtomicU64, + pending: Mutex>, + ops: Mutex>, +} + +impl WorkerLocalRing { + pub(crate) fn new() -> Self { + Self { + worker_tasks: WorkerTaskRegistry::new(), + next_op_id: AtomicU64::new(1), + pending: Mutex::new(VecDeque::new()), + ops: Mutex::new(HashMap::new()), + } + } + + pub fn worker_task_registry(&self) -> &WorkerTaskRegistry { + &self.worker_tasks + } + pub(crate) fn register(&self, op: WorkerRingOp) -> RS { + let op_id = self.next_op_id.fetch_add(1, Ordering::Relaxed); + self.ops + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker local ring lock poisoned"))? + .insert(op_id, op); + self.pending + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker local ring lock poisoned"))? + .push_back(op_id); + Ok(op_id) + } + + pub(crate) fn requeue_front(&self, op_id: u64, op: WorkerRingOp) -> RS<()> { + self.ops + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker local ring lock poisoned"))? + .insert(op_id, op); + self.pending + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker local ring lock poisoned"))? + .push_front(op_id); + Ok(()) + } + + pub(crate) fn take_pending(&self) -> RS> { + let Some(op_id) = self + .pending + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker local ring lock poisoned"))? + .pop_front() + else { + return Ok(None); + }; + let op = self + .ops + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker local ring lock poisoned"))? + .remove(&op_id) + .ok_or_else(|| { + m_error!( + EC::InternalErr, + format!("worker local ring op {} missing from registry", op_id) + ) + })?; + Ok(Some((op_id, op))) + } +} + +pub(crate) fn set_current_worker_ring(ring: Arc) { + CURRENT_WORKER_RING.with(|slot| { + *slot.borrow_mut() = Some(ring); + }); +} + +pub(crate) fn unset_current_worker_ring() { + CURRENT_WORKER_RING.with(|slot| { + *slot.borrow_mut() = None; + }); +} + +pub(crate) fn has_current_worker_ring() -> bool { + CURRENT_WORKER_RING.with(|slot| slot.borrow().is_some()) +} + +pub(crate) fn with_current_ring(f: F) -> RS +where + F: FnOnce(&Arc) -> RS, +{ + CURRENT_WORKER_RING.with(|slot| { + let ring = slot.borrow(); + let ring = ring + .as_ref() + .ok_or_else(|| m_error!(EC::NoSuchElement, "current worker ring is not set"))?; + f(ring) + }) +} + +pub(crate) fn submit_user_ring_op( + op_id: u64, + op: WorkerRingOp, + sqe: &mut mudu_sys::uring::SubmissionQueueEntry<'_>, +) -> UserIoInflight { + match op { + WorkerRingOp::File(request) => UserIoInflight::File { + op_id, + op: submit_file_io(request, sqe), + }, + WorkerRingOp::Socket(request) => UserIoInflight::Socket { + op_id, + op: submit_socket_io(request, sqe), + }, + } +} + +pub(crate) fn complete_user_ring_op( + op: UserIoInflight, + result: i32, + ring: &WorkerLocalRing, +) -> RS<()> { + match op { + UserIoInflight::File { op_id, op } => complete_file_io(op_id, op, result, ring), + UserIoInflight::Socket { op_id, op } => complete_socket_io(op_id, op, result, ring), + } +} diff --git a/mudu_kernel/src/io/worker_ring_stub.rs b/mudu_kernel/src/io/worker_ring_stub.rs new file mode 100644 index 0000000..66d7c6c --- /dev/null +++ b/mudu_kernel/src/io/worker_ring_stub.rs @@ -0,0 +1,38 @@ +use std::sync::Arc; + +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; + +pub(crate) struct WorkerLocalRing; + +pub(crate) enum WorkerRingOp { + File(crate::io::file::FileIoRequest), +} + +impl WorkerLocalRing { + pub(crate) fn register(&self, _op: WorkerRingOp) -> RS { + Err(m_error!( + EC::NotImplemented, + "worker ring is only available on linux" + )) + } +} + +pub(crate) fn set_current_worker_ring(_ring: Arc) {} + +pub(crate) fn unset_current_worker_ring() {} + +pub(crate) fn has_current_worker_ring() -> bool { + false +} + +pub(crate) fn with_current_ring(_f: F) -> RS +where + F: FnOnce(&Arc) -> RS, +{ + Err(m_error!( + EC::NotImplemented, + "worker ring is only available on linux" + )) +} diff --git a/mudu_kernel/src/lib.rs b/mudu_kernel/src/lib.rs index be21f50..b01d241 100644 --- a/mudu_kernel/src/lib.rs +++ b/mudu_kernel/src/lib.rs @@ -1,23 +1,18 @@ -#![feature(generic_atomic)] -#![allow(dead_code)] -#![allow(unused)] mod collection; mod common; -mod contract; +pub mod contract; pub mod fuzz; pub mod index; -mod io; +pub mod io; mod meta; -pub mod x_log; +pub mod sql; +pub mod wal; mod command; mod executor; - -pub mod server; -mod sql; mod test; -pub mod server_ur; +pub mod server; pub mod storage; mod tx; -mod x_engine; +pub mod x_engine; diff --git a/mudu_kernel/src/meta/meta_mgr.rs b/mudu_kernel/src/meta/meta_mgr.rs index 116bff7..7956f0d 100644 --- a/mudu_kernel/src/meta/meta_mgr.rs +++ b/mudu_kernel/src/meta/meta_mgr.rs @@ -41,20 +41,31 @@ impl MetaMgrImpl { } } - Ok(Self { + let this = Self { path: path.to_str().unwrap().to_string(), id2table: Default::default(), name2id: Default::default(), table: Default::default(), - }) + }; + + for (table_name, table_info) in hash_table { + let table_id = table_info.schema().id(); + let _ = this + .table + .insert_sync(table_name.clone(), table_info.clone()); + let _ = this.id2table.insert_sync(table_id, table_info); + let _ = this.name2id.insert_sync(table_name, table_id); + } + + Ok(this) } - pub fn get_table_by_id(&self, oid: OID) -> Option { + pub fn lookup_table_info_by_id(&self, oid: OID) -> Option { let opt = self.id2table.get_sync(&oid); opt.map(|e| e.get().clone()) } - pub fn get_table_by_name(&self, name: &String) -> RS>> { + pub fn lookup_table_by_name(&self, name: &String) -> RS>> { let opt = self.table.get_sync(name); let table_desc = match opt { None => return Ok(None), @@ -63,7 +74,7 @@ impl MetaMgrImpl { Ok(Some(table_desc)) } - pub fn _create_table(&self, schema: &SchemaTable) -> RS<()> { + pub fn create_table_inner(&self, schema: &SchemaTable) -> RS<()> { if !self.table.contains_sync(schema.table_name()) { let table_name = schema.table_name().clone(); let mut pb = PathBuf::from(self.path.clone()); @@ -86,6 +97,25 @@ impl MetaMgrImpl { Ok(()) } + pub fn drop_table_inner(&self, oid: OID) -> RS<()> { + let table = self + .lookup_table_info_by_id(oid) + .ok_or_else(|| m_error!(ER::NoSuchElement, format!("no such table {}", oid)))?; + let schema = table.schema(); + let table_name = schema.table_name().clone(); + + let mut pb = PathBuf::from(self.path.clone()); + pb.push(format!("{}.json", table_name)); + if pb.exists() { + fs::remove_file(&pb).map_err(|e| m_error!(ER::IOErr, "remove schema file error", e))?; + } + + let _ = self.id2table.remove_sync(&oid); + let _ = self.name2id.remove_sync(&table_name); + let _ = self.table.remove_sync(&table_name); + Ok(()) + } + fn read_schema_from_file(path: &String) -> RS { let r_open = File::open(path); let file = rs_io(r_open)?; @@ -117,7 +147,7 @@ impl MetaMgrImpl { #[async_trait] impl MetaMgr for MetaMgrImpl { async fn get_table_by_id(&self, oid: OID) -> RS> { - let opt = self.get_table_by_id(oid); + let opt = self.lookup_table_info_by_id(oid); match opt { Some(t) => t.table_desc(), None => Err(m_error!( @@ -128,15 +158,15 @@ impl MetaMgr for MetaMgrImpl { } async fn get_table_by_name(&self, name: &String) -> RS>> { - self.get_table_by_name(name) + self.lookup_table_by_name(name) } async fn create_table(&self, schema: &SchemaTable) -> RS<()> { - self._create_table(schema) + self.create_table_inner(schema) } - async fn drop_table(&self, _oid: OID) -> RS<()> { - todo!() + async fn drop_table(&self, table_id: OID) -> RS<()> { + self.drop_table_inner(table_id) } } diff --git a/mudu_kernel/src/meta/mod.rs b/mudu_kernel/src/meta/mod.rs index 8a5ce58..ccb4610 100644 --- a/mudu_kernel/src/meta/mod.rs +++ b/mudu_kernel/src/meta/mod.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + pub mod _fuzz; pub mod meta_mgr; diff --git a/mudu_kernel/src/server/accept_handle_task.rs b/mudu_kernel/src/server/accept_handle_task.rs deleted file mode 100644 index bd7e896..0000000 --- a/mudu_kernel/src/server/accept_handle_task.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::server::incoming_session::{IncomingSession, SSPSender}; -use async_trait::async_trait; -use mudu::common::result::RS; -use mudu::error::ec::EC as ER; -use mudu::m_error; -use mudu_utils::notifier::NotifyWait; -use mudu_utils::sync::a_task::ATask; -use std::net::SocketAddr; -use tokio::net::TcpListener; -use tracing::info; - -impl AcceptHandleTask { - pub fn new( - canceller: NotifyWait, - bind_addr: SocketAddr, - ssp_sender_channel: Vec, - wait_recovery: NotifyWait, - ) -> Self { - Self { - canceller, - name: "accept_session".to_string(), - bind_addr, - wait_recovery, - ssp_sender_channel, - } - } - - async fn server_accept(self) -> RS<()> { - self.wait_recovery.notified().await; - let listener = TcpListener::bind(self.bind_addr) - .await - .map_err(|_e| m_error!(ER::NetErr, "bind address error"))?; - info!("server listen on address {}", self.bind_addr); - let mut session_id: u64 = 0; - - loop { - let r = listener.accept().await; - let incoming = r.map_err(|_e| m_error!(ER::NetErr, "client accept error", _e))?; - info!("accept connection {}", incoming.1); - let param = IncomingSession::new(incoming.1, incoming.0); - session_id += 1; - let index = (session_id as usize) % self.ssp_sender_channel.len(); - let r = self.ssp_sender_channel[index].send(param).await; - r.map_err(|_e| m_error!(ER::SyncErr, "channel send error", _e))?; - } - } -} - -pub struct AcceptHandleTask { - canceller: NotifyWait, - name: String, - bind_addr: SocketAddr, - ssp_sender_channel: Vec, - wait_recovery: NotifyWait, -} - -#[async_trait] -impl ATask for AcceptHandleTask { - fn notifier(&self) -> NotifyWait { - self.canceller.clone() - } - - fn name(&self) -> String { - self.name.clone() - } - - async fn run(self) -> RS<()> { - self.server_accept().await - } -} diff --git a/mudu_kernel/src/server_ur/procedure_runtime.rs b/mudu_kernel/src/server/async_func_runtime.rs similarity index 52% rename from mudu_kernel/src/server_ur/procedure_runtime.rs rename to mudu_kernel/src/server/async_func_runtime.rs index 129533a..4688900 100644 --- a/mudu_kernel/src/server_ur/procedure_runtime.rs +++ b/mudu_kernel/src/server/async_func_runtime.rs @@ -1,13 +1,11 @@ -use crate::server_ur::worker_local::WorkerLocalRef; +use crate::server::worker_local::WorkerLocalRef; use async_trait::async_trait; use mudu::common::id::OID; use mudu::common::result::RS; use std::sync::Arc; #[async_trait] -pub trait ProcInvoker: Send + Sync { - // The kernel uses a binary procedure ABI so the TCP worker can forward a - // decoded invoke request without depending on mudu_runtime internals. +pub trait AsyncFuncInvoker: Send + Sync { async fn invoke( &self, session_id: OID, @@ -17,4 +15,4 @@ pub trait ProcInvoker: Send + Sync { ) -> RS>; } -pub type ProcInvokerPtr = Arc; +pub type AsyncFuncInvokerPtr = Arc; diff --git a/mudu_kernel/src/server/async_func_task.rs b/mudu_kernel/src/server/async_func_task.rs new file mode 100644 index 0000000..5eeb07a --- /dev/null +++ b/mudu_kernel/src/server/async_func_task.rs @@ -0,0 +1,111 @@ +#![allow(dead_code)] + +use crate::server::routing::SessionOpenTransferAction; +use mudu::common::id::OID; +use mudu::common::result::RS; +use std::future::Future; +use std::pin::Pin; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +pub struct AsyncFuncTask { + conn_id: u64, + request_id: u64, + future: AsyncFuncFuture, + queued: Arc, + completed: Arc, + waiting_on: Option, +} + +pub(in crate::server) enum HandleResult { + Response(Vec), + Transfer(SessionTransferDispatch), +} + +#[derive(Clone)] +pub(in crate::server) struct SessionTransferDispatch { + target_worker: usize, + session_ids: Vec, + action: SessionOpenTransferAction, +} + +impl SessionTransferDispatch { + pub(in crate::server) fn new( + target_worker: usize, + session_ids: Vec, + action: SessionOpenTransferAction, + ) -> Self { + Self { + target_worker, + session_ids, + action, + } + } + + pub(in crate::server) fn target_worker(&self) -> usize { + self.target_worker + } + + pub(in crate::server) fn session_ids(&self) -> &[OID] { + &self.session_ids + } + + pub(in crate::server) fn action(&self) -> SessionOpenTransferAction { + self.action + } +} + +impl AsyncFuncTask { + pub(in crate::server) fn new( + conn_id: u64, + request_id: u64, + future: AsyncFuncFuture, + completed: Arc, + ) -> Self { + Self { + conn_id, + request_id, + future, + queued: Arc::new(AtomicBool::new(false)), + completed, + waiting_on: None, + } + } + + pub(in crate::server) fn conn_id(&self) -> u64 { + self.conn_id + } + + pub(in crate::server) fn request_id(&self) -> u64 { + self.request_id + } + + pub(in crate::server) fn future_mut(&mut self) -> AsyncFuncFutureRef<'_> { + self.future.as_mut() + } + + pub(in crate::server) fn queued(&self) -> &Arc { + &self.queued + } + + pub(in crate::server) fn completed(&self) -> &Arc { + &self.completed + } + + pub(in crate::server) fn clear_queued(&self) { + self.queued.store(false, Ordering::Release); + } + + pub(in crate::server) fn take_waiting_on(&mut self) -> Option { + self.waiting_on.take() + } + + pub(in crate::server) fn set_waiting_on(&mut self, op_id: u64) { + self.waiting_on = Some(op_id); + } +} + +pub(in crate::server) type AsyncFuncFuture = + Pin> + 'static>>; + +type AsyncFuncFutureRef<'a> = Pin<&'a mut (dyn Future> + 'static)>; diff --git a/mudu_kernel/src/server_ur/procedure_task_waker.rs b/mudu_kernel/src/server/async_func_task_waker.rs similarity index 63% rename from mudu_kernel/src/server_ur/procedure_task_waker.rs rename to mudu_kernel/src/server/async_func_task_waker.rs index 9cce6ae..19a63b8 100644 --- a/mudu_kernel/src/server_ur/procedure_task_waker.rs +++ b/mudu_kernel/src/server/async_func_task_waker.rs @@ -3,14 +3,14 @@ use futures::task::ArcWake; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -pub struct ProcedureTaskWaker { +pub struct AsyncFuncTaskWaker { op_id: u64, completion_queue: Arc>, completed: Arc, notified: Arc, } -impl ProcedureTaskWaker { +impl AsyncFuncTaskWaker { pub fn new( op_id: u64, completion_queue: Arc>, @@ -25,14 +25,8 @@ impl ProcedureTaskWaker { } } -impl ArcWake for ProcedureTaskWaker { +impl ArcWake for AsyncFuncTaskWaker { fn wake_by_ref(arc_self: &Arc) { - // Procedure futures are resumed through the worker's completion path. - // This mirrors io_uring: wake -> completion queue -> task resume. - - // A wake means the future may now make progress after previously - // returning `Poll::Pending`. Once the future is already completed, - // re-queuing it would only create a spurious extra poll. if arc_self.completed.load(Ordering::Acquire) { return; } diff --git a/mudu_kernel/src/server/callback_registry.rs b/mudu_kernel/src/server/callback_registry.rs new file mode 100644 index 0000000..c1d0ff0 --- /dev/null +++ b/mudu_kernel/src/server/callback_registry.rs @@ -0,0 +1,232 @@ +#![allow(dead_code)] + +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::future::Future; +use std::pin::Pin; + +use mudu::common::result::RS; + +pub type CallbackId = u64; +pub type CallbackFuture = Pin> + Send>>; +pub type AsyncCallback = Box CallbackFuture + Send>; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct CallbackEventKey { + pub kind: u16, + pub id: u64, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum CallbackDomain { + Generic(u16), +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum CallbackTrigger { + Event(CallbackEventKey), + Sequence { domain: CallbackDomain, target: u64 }, +} + +pub struct PendingCallback { + pub id: CallbackId, + pub trigger: CallbackTrigger, + pub callback: AsyncCallback, +} + +pub(in crate::server) struct CallbackRegistry { + next_id: CallbackId, + event_waiters: HashMap>, + sequence_waiters: HashMap>>, + cancelled: HashSet, +} + +impl CallbackRegistry { + pub(in crate::server) fn new() -> Self { + Self { + next_id: 1, + event_waiters: HashMap::new(), + sequence_waiters: HashMap::new(), + cancelled: HashSet::new(), + } + } + + pub(in crate::server) fn register( + &mut self, + trigger: CallbackTrigger, + callback: AsyncCallback, + ) -> CallbackId { + let id = self.next_id; + self.next_id += 1; + let pending = PendingCallback { + id, + trigger, + callback, + }; + match trigger { + CallbackTrigger::Event(key) => { + self.event_waiters.entry(key).or_default().push(pending); + } + CallbackTrigger::Sequence { domain, target } => { + self.sequence_waiters + .entry(domain) + .or_default() + .entry(target) + .or_default() + .push(pending); + } + } + id + } + + pub(in crate::server) fn register_event( + &mut self, + key: CallbackEventKey, + callback: AsyncCallback, + ) -> CallbackId { + self.register(CallbackTrigger::Event(key), callback) + } + + pub(in crate::server) fn register_after( + &mut self, + domain: CallbackDomain, + target: u64, + callback: AsyncCallback, + ) -> CallbackId { + self.register(CallbackTrigger::Sequence { domain, target }, callback) + } + + pub(in crate::server) fn cancel(&mut self, id: CallbackId) -> bool { + self.cancelled.insert(id) + } + + pub(in crate::server) fn fire_event(&mut self, key: CallbackEventKey) -> Vec { + let Some(callbacks) = self.event_waiters.remove(&key) else { + return Vec::new(); + }; + self.filter_cancelled(callbacks) + } + + pub(in crate::server) fn advance_sequence( + &mut self, + domain: CallbackDomain, + value: u64, + ) -> Vec { + let Some(waiters) = self.sequence_waiters.get_mut(&domain) else { + return Vec::new(); + }; + + let targets: Vec = waiters.range(..=value).map(|(&target, _)| target).collect(); + let mut ready = Vec::new(); + for target in targets { + if let Some(callbacks) = waiters.remove(&target) { + ready.extend(callbacks); + } + } + if waiters.is_empty() { + self.sequence_waiters.remove(&domain); + } + self.filter_cancelled(ready) + } + + pub(in crate::server) fn is_empty(&self) -> bool { + self.event_waiters.is_empty() && self.sequence_waiters.is_empty() + } + + fn filter_cancelled(&mut self, callbacks: Vec) -> Vec { + callbacks + .into_iter() + .filter(|pending| !self.cancelled.remove(&pending.id)) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::Arc; + + #[tokio::test] + async fn fires_event_callbacks_once() { + let mut registry = CallbackRegistry::new(); + let hit = Arc::new(AtomicUsize::new(0)); + let hit_clone = hit.clone(); + registry.register_event( + CallbackEventKey { kind: 1, id: 7 }, + Box::new(move || { + Box::pin(async move { + hit_clone.fetch_add(1, Ordering::SeqCst); + Ok(()) + }) + }), + ); + + let ready = registry.fire_event(CallbackEventKey { kind: 1, id: 7 }); + assert_eq!(ready.len(), 1); + assert_eq!( + ready[0].trigger, + CallbackTrigger::Event(CallbackEventKey { kind: 1, id: 7 }) + ); + for callback in ready { + (callback.callback)().await.unwrap(); + } + assert_eq!(hit.load(Ordering::SeqCst), 1); + assert!(registry + .fire_event(CallbackEventKey { kind: 1, id: 7 }) + .is_empty()); + assert!(registry.is_empty()); + } + + #[tokio::test] + async fn advances_sequence_callbacks_up_to_frontier() { + let mut registry = CallbackRegistry::new(); + let hit = Arc::new(AtomicUsize::new(0)); + for target in [3u64, 5u64] { + let hit_clone = hit.clone(); + registry.register_after( + CallbackDomain::Generic(9), + target, + Box::new(move || { + Box::pin(async move { + hit_clone.fetch_add(1, Ordering::SeqCst); + Ok(()) + }) + }), + ); + } + + let ready = registry.advance_sequence(CallbackDomain::Generic(9), 3); + assert_eq!(ready.len(), 1); + assert_eq!( + ready[0].trigger, + CallbackTrigger::Sequence { + domain: CallbackDomain::Generic(9), + target: 3, + } + ); + for callback in ready { + (callback.callback)().await.unwrap(); + } + assert_eq!(hit.load(Ordering::SeqCst), 1); + + let ready = registry.advance_sequence(CallbackDomain::Generic(9), 5); + assert_eq!(ready.len(), 1); + for callback in ready { + (callback.callback)().await.unwrap(); + } + assert_eq!(hit.load(Ordering::SeqCst), 2); + } + + #[test] + fn cancelled_callback_is_filtered_before_execution() { + let mut registry = CallbackRegistry::new(); + let id = registry.register_event( + CallbackEventKey { kind: 2, id: 11 }, + Box::new(|| Box::pin(async { Ok(()) })), + ); + assert!(registry.cancel(id)); + let ready = registry.fire_event(CallbackEventKey { kind: 2, id: 11 }); + assert!(ready.is_empty()); + assert!(registry.is_empty()); + } +} diff --git a/mudu_kernel/src/server/connection_worker_task.rs b/mudu_kernel/src/server/connection_worker_task.rs new file mode 100644 index 0000000..f047ce2 --- /dev/null +++ b/mudu_kernel/src/server/connection_worker_task.rs @@ -0,0 +1,144 @@ +use crossbeam_queue::SegQueue; +use mudu::common::result::RS; +use mudu_contract::protocol::encode_error_response; +use std::net::SocketAddr; +use std::os::fd::RawFd; +use std::sync::Arc; + +use crate::io::socket::{close, IoSocket}; +use crate::server::async_func_task::{HandleResult, SessionTransferDispatch}; +use crate::server::frame_dispatch::dispatch_frame_async; +use crate::server::protocol_codec::{read_next_frame, write_response}; +use crate::server::routing::ConnectionTransfer; +use crate::server::transferred_connection::TransferredConnection; +use crate::server::worker::IoUringWorker; +use crate::server::worker_mailbox::WorkerMailboxMsg; +use crate::server::worker_ring_loop::WorkerRingLoop; +use crate::server::worker_task::WorkerTaskFuture; + +pub(in crate::server) fn spawn_connection_worker_task( + worker: IoUringWorker, + mailbox_fds: Vec, + mailboxes: Vec>>, + connections: Arc>, + conn_id: u64, + socket: IoSocket, + remote_addr: SocketAddr, + initial_response: Option>, +) -> WorkerTaskFuture { + Box::pin(async move { + run_connection_worker_task( + worker, + mailbox_fds, + mailboxes, + connections, + conn_id, + socket, + remote_addr, + initial_response, + ) + .await + }) +} + +async fn run_connection_worker_task( + worker: IoUringWorker, + mailbox_fds: Vec, + mailboxes: Vec>>, + connections: Arc>, + conn_id: u64, + socket: IoSocket, + remote_addr: SocketAddr, + initial_response: Option>, +) -> RS<()> { + let r = _run_connection_worker_task( + worker, + mailbox_fds, + mailboxes, + conn_id, + socket, + remote_addr, + initial_response, + ) + .await; + let _ = connections.remove_sync(&conn_id); + r +} +async fn _run_connection_worker_task( + worker: IoUringWorker, + mailbox_fds: Vec, + mailboxes: Vec>>, + conn_id: u64, + socket: IoSocket, + remote_addr: SocketAddr, + initial_response: Option>, +) -> RS<()> { + let mut socket = Some(socket); + let mut read_buf = Vec::with_capacity(8192); + + if let Some(response) = initial_response { + write_response(socket.as_ref().unwrap(), &response).await?; + } + + loop { + let frame = match read_next_frame(socket.as_ref().unwrap(), &mut read_buf).await { + Ok(Some(frame)) => frame, + Ok(None) => { + close(socket.take().unwrap()).await?; + worker.close_connection_sessions(conn_id)?; + break; + } + Err(err) => { + let _ = close(socket.take().unwrap()).await; + return Err(err); + } + }; + + let request_id = frame.header().request_id(); + match dispatch_frame_async(&worker, conn_id, &frame).await { + Ok(HandleResult::Response(response)) => { + write_response(socket.as_ref().unwrap(), &response).await?; + } + Ok(HandleResult::Transfer(transfer)) => { + let connection = build_transfer( + conn_id, + remote_addr, + socket.take().unwrap(), + transfer.clone(), + ); + WorkerRingLoop::dispatch_mailbox_message( + &mailbox_fds, + &mailboxes, + connection.transfer().target_worker(), + WorkerMailboxMsg::AdoptConnection(connection), + )?; + break; + } + Err(err) => { + let response = encode_error_response(request_id, err.to_string())?; + write_response(socket.as_ref().unwrap(), &response).await?; + } + } + read_buf = frame.into_payload(); + } + Ok(()) +} + +fn build_transfer( + conn_id: u64, + remote_addr: SocketAddr, + socket: IoSocket, + transfer: SessionTransferDispatch, +) -> TransferredConnection { + TransferredConnection::new( + ConnectionTransfer::new( + conn_id, + transfer.target_worker(), + crate::server::fsm::ConnectionState::Active, + remote_addr, + ), + socket.into_raw_fd(), + transfer.session_ids().to_vec(), + Some(transfer.action()), + ) +} diff --git a/mudu_kernel/src/server/frame_dispatch.rs b/mudu_kernel/src/server/frame_dispatch.rs new file mode 100644 index 0000000..7e79aae --- /dev/null +++ b/mudu_kernel/src/server/frame_dispatch.rs @@ -0,0 +1,73 @@ +#![allow(dead_code)] + +use crate::server::async_func_task::HandleResult; +use crate::server::message_dispatcher::MessageDispatcher; +use crate::server::request_ctx::RequestCtx; +use crate::server::session_bound_worker_runtime::new_session_bound_worker_runtime; +use crate::server::worker::IoUringWorker; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use mudu_contract::protocol::{ + decode_client_request, encode_server_response, Frame, MessageType, ServerResponse, HEADER_LEN, +}; + +pub fn try_decode_next_frame(buf: &[u8]) -> RS> { + if buf.len() < HEADER_LEN { + return Ok(None); + } + let payload_len = u32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]) as usize; + let frame_len = HEADER_LEN + payload_len; + if buf.len() < frame_len { + return Ok(None); + } + let frame = Frame::decode(&buf[..frame_len])?; + Ok(Some((frame, frame_len))) +} + +pub async fn dispatch_frame_async( + worker: &IoUringWorker, + conn_id: u64, + frame: &Frame, +) -> RS { + let ctx = RequestCtx::new( + new_session_bound_worker_runtime(worker.clone(), 0), + conn_id, + frame.header().request_id(), + ); + if let Some(result) = MessageDispatcher::global().dispatch(&ctx, frame).await { + return result; + } + match frame.header().message_type() { + MessageType::Query | MessageType::Execute => { + let request = decode_client_request(frame)?; + Ok(HandleResult::Response(encode_server_response( + frame.header().request_id(), + &ServerResponse::new( + vec![], + vec![], + 0, + Some(format!( + "SQL interface is disabled in the client backend for app '{}'", + request.app_name() + )), + ), + )?)) + } + MessageType::Get + | MessageType::Put + | MessageType::RangeScan + | MessageType::ProcedureInvoke + | MessageType::SessionCreate + | MessageType::SessionClose => unreachable!(), + MessageType::Handshake | MessageType::Auth | MessageType::Response | MessageType::Error => { + Err(m_error!( + EC::ParseErr, + format!( + "unsupported client message type {:?}", + frame.header().message_type() + ) + )) + } + } +} diff --git a/mudu_kernel/src/server_ur/fsm.rs b/mudu_kernel/src/server/fsm.rs similarity index 100% rename from mudu_kernel/src/server_ur/fsm.rs rename to mudu_kernel/src/server/fsm.rs diff --git a/mudu_kernel/src/server/handlers/get.rs b/mudu_kernel/src/server/handlers/get.rs new file mode 100644 index 0000000..5fdbf1e --- /dev/null +++ b/mudu_kernel/src/server/handlers/get.rs @@ -0,0 +1,21 @@ +use async_trait::async_trait; +use mudu::common::result::RS; +use mudu_contract::protocol::{decode_get_request, Frame, MessageType}; + +use crate::server::async_func_task::HandleResult; +use crate::server::message_dispatcher::MessageHandler; +use crate::server::request_ctx::RequestCtx; + +pub(in crate::server) struct GetHandler; + +#[async_trait] +impl MessageHandler for GetHandler { + fn message_type(&self) -> MessageType { + MessageType::Get + } + + async fn handle(&self, ctx: &RequestCtx, frame: &Frame) -> RS { + let request = decode_get_request(frame)?; + ctx.get(request.session_id(), request.key()).await + } +} diff --git a/mudu_kernel/src/server/handlers/mod.rs b/mudu_kernel/src/server/handlers/mod.rs new file mode 100644 index 0000000..4bd792d --- /dev/null +++ b/mudu_kernel/src/server/handlers/mod.rs @@ -0,0 +1,13 @@ +mod get; +mod procedure_invoke; +mod put; +mod range_scan; +mod session_close; +mod session_create; + +pub(in crate::server) use get::GetHandler; +pub(in crate::server) use procedure_invoke::ProcedureInvokeHandler; +pub(in crate::server) use put::PutHandler; +pub(in crate::server) use range_scan::RangeScanHandler; +pub(in crate::server) use session_close::SessionCloseHandler; +pub(in crate::server) use session_create::SessionCreateHandler; diff --git a/mudu_kernel/src/server/handlers/procedure_invoke.rs b/mudu_kernel/src/server/handlers/procedure_invoke.rs new file mode 100644 index 0000000..647ed26 --- /dev/null +++ b/mudu_kernel/src/server/handlers/procedure_invoke.rs @@ -0,0 +1,21 @@ +use async_trait::async_trait; +use mudu::common::result::RS; +use mudu_contract::protocol::{decode_procedure_invoke_request, Frame, MessageType}; + +use crate::server::async_func_task::HandleResult; +use crate::server::message_dispatcher::MessageHandler; +use crate::server::request_ctx::RequestCtx; + +pub(in crate::server) struct ProcedureInvokeHandler; + +#[async_trait] +impl MessageHandler for ProcedureInvokeHandler { + fn message_type(&self) -> MessageType { + MessageType::ProcedureInvoke + } + + async fn handle(&self, ctx: &RequestCtx, frame: &Frame) -> RS { + let request = decode_procedure_invoke_request(frame)?; + ctx.invoke_procedure(request).await + } +} diff --git a/mudu_kernel/src/server/handlers/put.rs b/mudu_kernel/src/server/handlers/put.rs new file mode 100644 index 0000000..65d4551 --- /dev/null +++ b/mudu_kernel/src/server/handlers/put.rs @@ -0,0 +1,23 @@ +use async_trait::async_trait; +use mudu::common::result::RS; +use mudu_contract::protocol::{decode_put_request, Frame, MessageType}; + +use crate::server::async_func_task::HandleResult; +use crate::server::message_dispatcher::MessageHandler; +use crate::server::request_ctx::RequestCtx; + +pub(in crate::server) struct PutHandler; + +#[async_trait] +impl MessageHandler for PutHandler { + fn message_type(&self) -> MessageType { + MessageType::Put + } + + async fn handle(&self, ctx: &RequestCtx, frame: &Frame) -> RS { + let request = decode_put_request(frame)?; + let session_id = request.session_id(); + let (key, value) = request.into_parts(); + ctx.put(session_id, key, value).await + } +} diff --git a/mudu_kernel/src/server/handlers/range_scan.rs b/mudu_kernel/src/server/handlers/range_scan.rs new file mode 100644 index 0000000..13d1760 --- /dev/null +++ b/mudu_kernel/src/server/handlers/range_scan.rs @@ -0,0 +1,22 @@ +use async_trait::async_trait; +use mudu::common::result::RS; +use mudu_contract::protocol::{decode_range_scan_request, Frame, MessageType}; + +use crate::server::async_func_task::HandleResult; +use crate::server::message_dispatcher::MessageHandler; +use crate::server::request_ctx::RequestCtx; + +pub(in crate::server) struct RangeScanHandler; + +#[async_trait] +impl MessageHandler for RangeScanHandler { + fn message_type(&self) -> MessageType { + MessageType::RangeScan + } + + async fn handle(&self, ctx: &RequestCtx, frame: &Frame) -> RS { + let request = decode_range_scan_request(frame)?; + ctx.range_scan(request.session_id(), request.start_key(), request.end_key()) + .await + } +} diff --git a/mudu_kernel/src/server/handlers/session_close.rs b/mudu_kernel/src/server/handlers/session_close.rs new file mode 100644 index 0000000..f2d54ae --- /dev/null +++ b/mudu_kernel/src/server/handlers/session_close.rs @@ -0,0 +1,21 @@ +use async_trait::async_trait; +use mudu::common::result::RS; +use mudu_contract::protocol::{decode_session_close_request, Frame, MessageType}; + +use crate::server::async_func_task::HandleResult; +use crate::server::message_dispatcher::MessageHandler; +use crate::server::request_ctx::RequestCtx; + +pub(in crate::server) struct SessionCloseHandler; + +#[async_trait] +impl MessageHandler for SessionCloseHandler { + fn message_type(&self) -> MessageType { + MessageType::SessionClose + } + + async fn handle(&self, ctx: &RequestCtx, frame: &Frame) -> RS { + let request = decode_session_close_request(frame)?; + ctx.session_close(request.session_id()).await + } +} diff --git a/mudu_kernel/src/server/handlers/session_create.rs b/mudu_kernel/src/server/handlers/session_create.rs new file mode 100644 index 0000000..cc07729 --- /dev/null +++ b/mudu_kernel/src/server/handlers/session_create.rs @@ -0,0 +1,21 @@ +use async_trait::async_trait; +use mudu::common::result::RS; +use mudu_contract::protocol::{decode_session_create_request, Frame, MessageType}; + +use crate::server::async_func_task::HandleResult; +use crate::server::message_dispatcher::MessageHandler; +use crate::server::request_ctx::RequestCtx; +pub(in crate::server) struct SessionCreateHandler; + +#[async_trait] +impl MessageHandler for SessionCreateHandler { + fn message_type(&self) -> MessageType { + MessageType::SessionCreate + } + + async fn handle(&self, ctx: &RequestCtx, frame: &Frame) -> RS { + let request = decode_session_create_request(frame)?; + let config = ctx.parse_session_open_config(request.config_json())?; + ctx.session_create(config).await + } +} diff --git a/mudu_kernel/src/server/incoming_session.rs b/mudu_kernel/src/server/incoming_session.rs deleted file mode 100644 index 7b8a362..0000000 --- a/mudu_kernel/src/server/incoming_session.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::server::session_mgr::SessionMgr; -use crate::x_engine::thd_ctx::ThdCtx; -use mudu::common::result::RS; -use mudu::error::ec::EC as ER; -use mudu::m_error; -use pgwire::tokio::process_socket; -use std::net::SocketAddr; -use tokio::net::TcpStream; -use tokio::sync::mpsc::{Receiver, Sender}; - -pub type SSPSender = Sender; -pub type SSPReceiver = Receiver; - -pub struct IncomingSession { - //wait_recovery_notified:Notifier, - incoming_addr: SocketAddr, - tcp_socket: TcpStream, -} - -impl IncomingSession { - pub fn new(incoming_addr: SocketAddr, tcp_socket: TcpStream) -> Self { - Self { - incoming_addr, - tcp_socket, - } - } - - pub async fn session_handler_task(self, ctx: ThdCtx) -> RS<()> { - let session_mgr = SessionMgr::new(ctx); - let r = process_socket(self.tcp_socket, None, session_mgr).await; - r.map_err(|e| m_error!(ER::NetErr, "PG Wire handle error", e))?; - Ok(()) - } -} diff --git a/mudu_kernel/src/server/inflight_op.rs b/mudu_kernel/src/server/inflight_op.rs new file mode 100644 index 0000000..820e878 --- /dev/null +++ b/mudu_kernel/src/server/inflight_op.rs @@ -0,0 +1,25 @@ +use crate::io::worker_ring::UserIoInflight; + +pub(in crate::server) struct AcceptOp { + addr: mudu_sys::uring::SockAddrBuf, +} + +impl AcceptOp { + pub(in crate::server) fn new(addr: mudu_sys::uring::SockAddrBuf) -> Self { + Self { addr } + } + + pub(in crate::server) fn addr(&self) -> &mudu_sys::uring::SockAddrBuf { + &self.addr + } + + pub(in crate::server) fn addr_mut(&mut self) -> &mut mudu_sys::uring::SockAddrBuf { + &mut self.addr + } +} + +pub(in crate::server) enum InflightOp { + Accept(Box), + MailboxRead { _value: Box }, + UserIo(UserIoInflight), +} diff --git a/mudu_kernel/src/server/loop_mailbox.rs b/mudu_kernel/src/server/loop_mailbox.rs new file mode 100644 index 0000000..9c76152 --- /dev/null +++ b/mudu_kernel/src/server/loop_mailbox.rs @@ -0,0 +1,67 @@ +use std::collections::HashMap; + +use crossbeam_queue::SegQueue; +use mudu::common::result::RS; + +use crate::server::inflight_op::InflightOp; +use crate::server::worker_loop_stats::WorkerLoopStats; +use crate::server::worker_mailbox::WorkerMailboxMsg; + +pub(in crate::server) struct LoopMailboxSubmitCtx<'a> { + pub ring: &'a mut mudu_sys::uring::IoUring, + pub mailbox_fd: i32, + pub mailbox_read_submitted: &'a mut bool, + pub inflight: &'a mut HashMap, + pub next_token: &'a mut u64, + pub stats: &'a mut WorkerLoopStats, + pub shutting_down: bool, +} + +pub(in crate::server) fn drain_messages( + mailbox: &SegQueue, + stats: &mut WorkerLoopStats, +) -> Vec { + let mut drained = Vec::new(); + while let Some(msg) = mailbox.pop() { + stats.mailbox_drained += 1; + drained.push(msg); + } + drained +} + +pub(in crate::server) fn submit_read_if_needed(ctx: &mut LoopMailboxSubmitCtx<'_>) -> RS<()> { + if *ctx.mailbox_read_submitted || ctx.shutting_down { + return Ok(()); + } + let Some(mut sqe) = ctx.ring.next_sqe() else { + return Ok(()); + }; + let mut value = Box::new(0u64); + let token = alloc_token(ctx.next_token); + sqe.set_user_data(token); + sqe.prep_read_raw( + ctx.mailbox_fd, + (&mut *value as *mut u64).cast(), + std::mem::size_of::(), + 0, + ); + ctx.inflight + .insert(token, InflightOp::MailboxRead { _value: value }); + *ctx.mailbox_read_submitted = true; + ctx.stats.mailbox_submit += 1; + Ok(()) +} + +pub(in crate::server) fn handle_read_completion( + mailbox_read_submitted: &mut bool, + stats: &mut WorkerLoopStats, +) { + stats.cqe_mailbox += 1; + *mailbox_read_submitted = false; +} + +fn alloc_token(next_token: &mut u64) -> u64 { + let token = *next_token; + *next_token += 1; + token +} diff --git a/mudu_kernel/src/server/loop_user_io.rs b/mudu_kernel/src/server/loop_user_io.rs new file mode 100644 index 0000000..207ac92 --- /dev/null +++ b/mudu_kernel/src/server/loop_user_io.rs @@ -0,0 +1,43 @@ +use std::collections::HashMap; + +use mudu::common::result::RS; + +use crate::io::worker_ring::{complete_user_ring_op, submit_user_ring_op, WorkerLocalRing}; +use crate::server::inflight_op::InflightOp; + +pub(in crate::server) struct LoopUserIoCtx<'a> { + pub ring: &'a mut mudu_sys::uring::IoUring, + pub user_ring: &'a WorkerLocalRing, + pub inflight: &'a mut HashMap, + pub next_token: &'a mut u64, +} + +pub(in crate::server) fn submit(ctx: &mut LoopUserIoCtx<'_>) -> RS<()> { + loop { + let Some((op_id, op)) = ctx.user_ring.take_pending()? else { + return Ok(()); + }; + let Some(mut sqe) = ctx.ring.next_sqe() else { + ctx.user_ring.requeue_front(op_id, op)?; + return Ok(()); + }; + let token = alloc_token(ctx.next_token); + sqe.set_user_data(token); + let inflight = submit_user_ring_op(op_id, op, &mut sqe); + ctx.inflight.insert(token, InflightOp::UserIo(inflight)); + } +} + +pub(in crate::server) fn handle_completion( + user_ring: &WorkerLocalRing, + op: crate::io::worker_ring::UserIoInflight, + result: i32, +) -> RS<()> { + complete_user_ring_op(op, result, user_ring) +} + +fn alloc_token(next_token: &mut u64) -> u64 { + let token = *next_token; + *next_token += 1; + token +} diff --git a/mudu_kernel/src/server/message_dispatcher.rs b/mudu_kernel/src/server/message_dispatcher.rs new file mode 100644 index 0000000..de6bce6 --- /dev/null +++ b/mudu_kernel/src/server/message_dispatcher.rs @@ -0,0 +1,59 @@ +use std::sync::OnceLock; + +use async_trait::async_trait; +use mudu::common::result::RS; +use mudu_contract::protocol::{Frame, MessageType}; + +use crate::server::async_func_task::HandleResult; +use crate::server::handlers::{ + GetHandler, ProcedureInvokeHandler, PutHandler, RangeScanHandler, SessionCloseHandler, + SessionCreateHandler, +}; +use crate::server::request_ctx::RequestCtx; + +#[async_trait] +pub(in crate::server) trait MessageHandler: Send + Sync { + fn message_type(&self) -> MessageType; + async fn handle(&self, ctx: &RequestCtx, frame: &Frame) -> RS; +} + +pub(in crate::server) struct MessageDispatcher { + handlers: Vec<(MessageType, Box)>, +} + +impl MessageDispatcher { + pub(in crate::server) fn global() -> &'static Self { + static INSTANCE: OnceLock = OnceLock::new(); + INSTANCE.get_or_init(Self::new) + } + + fn new() -> Self { + let mut handlers: Vec<(MessageType, Box)> = Vec::new(); + register(&mut handlers, Box::new(GetHandler)); + register(&mut handlers, Box::new(PutHandler)); + register(&mut handlers, Box::new(RangeScanHandler)); + register(&mut handlers, Box::new(ProcedureInvokeHandler)); + register(&mut handlers, Box::new(SessionCreateHandler)); + register(&mut handlers, Box::new(SessionCloseHandler)); + Self { handlers } + } + + pub(in crate::server) async fn dispatch( + &self, + ctx: &RequestCtx, + frame: &Frame, + ) -> Option> { + let (_, handler) = self + .handlers + .iter() + .find(|(message_type, _)| *message_type == frame.header().message_type())?; + Some(handler.handle(ctx, frame).await) + } +} + +fn register( + handlers: &mut Vec<(MessageType, Box)>, + handler: Box, +) { + handlers.push((handler.message_type(), handler)); +} diff --git a/mudu_kernel/src/server/mod.rs b/mudu_kernel/src/server/mod.rs index 2ad10e1..811fc3e 100644 --- a/mudu_kernel/src/server/mod.rs +++ b/mudu_kernel/src/server/mod.rs @@ -1,9 +1,56 @@ -mod accept_handle_task; -mod incoming_session; -pub mod mudu_server; -mod parser; -mod session; -mod session_handle_task; -mod session_mgr; -mod test_pg_cli; -mod test_sql; +//! TCP server backend with a Linux-first `io_uring` implementation. +//! +//! The public `client` module name is kept for compatibility. On Linux the +//! backend uses the native `io_uring` worker loop; on other platforms the same +//! public API falls back to a portable thread-per-worker implementation. +//! Modules that depend on `rliburing` are therefore compiled only on Linux. + +pub mod async_func_runtime; +mod async_func_task; +mod async_func_task_waker; +#[cfg(target_os = "linux")] +mod callback_registry; +#[cfg(target_os = "linux")] +mod connection_worker_task; +mod frame_dispatch; +pub mod fsm; +mod handlers; +#[cfg(target_os = "linux")] +mod inflight_op; +#[cfg(target_os = "linux")] +mod loop_mailbox; +#[cfg(target_os = "linux")] +mod loop_user_io; +mod message_dispatcher; +#[cfg(all(test, target_os = "linux"))] +mod perf_test; +#[cfg(target_os = "linux")] +mod protocol_codec; +mod request_ctx; +mod request_response_worker; +pub mod routing; +pub mod server; +#[cfg(target_os = "linux")] +mod server_iouring; +mod session_bound_worker_runtime; +mod task; +#[cfg(target_os = "linux")] +pub(crate) mod task_registry; +#[cfg(target_os = "linux")] +mod transferred_connection; +pub mod worker; +pub mod worker_local; +mod worker_loop_stats; +#[cfg(target_os = "linux")] +mod worker_mailbox; +pub mod worker_registry; +#[cfg(target_os = "linux")] +mod worker_ring_loop; +mod worker_session_manager; +pub mod worker_snapshot; +mod worker_storage; +#[cfg(target_os = "linux")] +mod worker_task; +mod worker_tx_manager; +pub mod x_contract; +mod x_lock_mgr; diff --git a/mudu_kernel/src/server/mudu_server.rs b/mudu_kernel/src/server/mudu_server.rs deleted file mode 100644 index 25ecd3a..0000000 --- a/mudu_kernel/src/server/mudu_server.rs +++ /dev/null @@ -1,284 +0,0 @@ -use mudu_utils::notifier::NotifyWait; - -use crate::common::mudu_cfg::{load_mudu_conf, MuduCfg}; -use crate::meta::meta_mgr_factory::MetaMgrFactory; -use crate::server::accept_handle_task::AcceptHandleTask; -use crate::server::incoming_session::IncomingSession; -use crate::server::session_handle_task::SessionHandleTask; -use crate::storage::mem_store_factory::MemStoreFactory; -use crate::storage::pst_store_factory::PstStoreFactory; -use crate::tx::tx_mgr_factory::TxMgrFactory; -use crate::x_engine::thd_ctx::ThdCtx; -use crate::x_log::x_log_service::XLogService; -use crate::x_log::xl_cfg::XLCfg; -use mudu::common::result::RS; -use mudu::error::ec::EC as ER; -use mudu::m_error; -use mudu_utils::debug; -use mudu_utils::sync::a_task::{ATaskRef, AsyncTask}; -use mudu_utils::sync::notify_wait::{create_notify_wait, Notify, Wait}; -use mudu_utils::sync::s_task::{STask, SyncTask}; -use mudu_utils::sync::unique_inner::UniqueInner; -use mudu_utils::task::spawn_local_task; -use std::net::{IpAddr, SocketAddr}; -use std::path::PathBuf; -use std::str::FromStr; -use std::sync::Arc; -use std::thread; -use std::thread::JoinHandle; -use tokio::runtime; -use tokio::sync::mpsc::channel; -use tokio::task::LocalSet; -use tracing::{error, info}; - -pub struct MuduServer { - cfg: MuduCfg, - thread_handle: Vec>>, - stop_notify: Notify<()>, -} - -pub struct MuduStop { - canceller: NotifyWait, - wait: Wait<()>, -} - -struct DebugServer { - canceler: NotifyWait, - port: u16, -} - -impl MuduStop { - fn new(canceller: NotifyWait, wait: Wait<()>) -> MuduStop { - Self { canceller, wait } - } - pub fn stop(&self) { - let _ = self.canceller.notify_all(); - let wait = self.wait.clone(); - let runtime = runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - runtime.block_on(async move { - let _ = wait.wait().await; - }); - } -} - -impl MuduServer { - pub fn start(cfg_path: Option) -> RS<(Self, MuduStop)> { - let mut thread_handle = vec![]; - let canceller = NotifyWait::new(); - - let cfg = load_mudu_conf(cfg_path)?; - - let wait_recovery = NotifyWait::new(); - let x_log_path = PathBuf::from(&cfg.db_path); - let x_log_path = x_log_path.join(&cfg.x_log_folder); - let x_log_path = x_log_path.to_str().unwrap().to_string(); - let xl_conf = XLCfg { - x_log_use_io_uring: cfg.x_log_use_io_uring, - x_log_path, - x_log_ext_name: cfg.x_log_ext_name.clone(), - x_log_channels: cfg.x_log_channels, - x_log_file_size_limit: cfg.x_log_file_size_limit, - }; - let x_log_service = XLogService::new(xl_conf, canceller.clone(), wait_recovery.clone())?; - let x_log_and_fsync_task = x_log_service.x_log_channel(); - let tree_store = MemStoreFactory::create(cfg.db_path.clone())?; - let meta_mgr = MetaMgrFactory::create(cfg.db_path.clone())?; - let x_lock_mgr = TxMgrFactory::create_lock_mgr(); - let x_snap_mgr = TxMgrFactory::create_snap_mgr(canceller.clone(), 0, 100); - let (kv_sync_task, ch) = PstStoreFactory::create(cfg.db_path.clone())?; - if cfg.session_threads as usize > x_log_and_fsync_task.len() { - panic!("log channel size must larger than threads") - } - - { - let debug_server = DebugServer::new(canceller.clone(), 1800); - let debug_server_task = Arc::new(UniqueInner::new(debug_server)); - let j = Self::create_sync_thread("debug_serve".to_string(), debug_server_task)?; - thread_handle.push(j); - } - - let mut session_receiver = vec![]; - let mut session_sender = vec![]; - - for _i in 0..cfg.session_threads { - let (s, r) = channel::(10); - session_receiver.push(r); - session_sender.push(s); - } - - { - // async recovery task - let task = x_log_service.recovery_task(); - let j = Self::create_thread(canceller.clone(), "recovery".to_string(), vec![task])?; - thread_handle.push(j); - } - - { - // snapshot assignment task - let task = x_snap_mgr.snap_assign_task(); - let j = Self::create_thread(canceller.clone(), "snap_assign".to_string(), vec![task])?; - thread_handle.push(j); - } - - { - // main accept connected session task - let ip_addr = IpAddr::from_str(cfg.server_bind_address.as_str()) - .map_err(|e| m_error!(ER::ParseErr, "ip address parse error", e))?; - let bind_addr = SocketAddr::new(ip_addr, cfg.server_listen_port); - let accept_handle_task = - AcceptHandleTask::new(canceller.clone(), bind_addr, session_sender, wait_recovery); - let task: Arc = Arc::new(UniqueInner::new(accept_handle_task)); - let j = Self::create_thread(canceller.clone(), "accept".to_string(), vec![task])?; - thread_handle.push(j); - } - { - let j = Self::create_sync_thread("kv_flush".to_string(), kv_sync_task)?; - thread_handle.push(j); - } - { - // session handle tasks - let mut logs: Vec> = vec![]; - let mut tasks: Vec> = vec![]; - logs.resize(cfg.session_threads as usize, vec![]); - tasks.resize(cfg.session_threads as usize, vec![]); - for (i, (log, task)) in x_log_and_fsync_task.into_iter().enumerate() { - let n = i % cfg.session_threads as usize; - logs[n].push(log); - tasks[n].push(task); - } - - let mut session_receiver: Vec<_> = session_receiver.into_iter().rev().collect(); - let receiver_size = session_receiver.len(); - for i in 0..cfg.session_threads { - let n = i as usize; - let logs = logs[n].clone(); - let mut tasks = tasks[n].clone(); - let opt = session_receiver.pop(); - let receiver = match opt { - Some(h) => h, - None => { - panic!( - "session_receiver size {}, is not equal to the thread count {}, \ - which should be expected equal", - receiver_size, cfg.session_threads - ) - } - }; - let thd_ctx = ThdCtx::new( - n as u64, - meta_mgr.clone(), - Arc::new(x_snap_mgr.snapshot_requester()), - x_lock_mgr.clone(), - logs, - tree_store.clone(), - ch.clone(), - ); - let session_handler = SessionHandleTask::new( - thd_ctx, - format!("session_handler_{}", n), - receiver, - canceller.clone(), - ); - tasks.push(Arc::new(UniqueInner::new(session_handler))); - - let j = - Self::create_thread(canceller.clone(), format!("session_{}", i + 1), tasks)?; - thread_handle.push(j); - } - } - let (notify, wait) = create_notify_wait(); - let server = Self { - cfg, - thread_handle, - stop_notify: notify, - }; - let stop = MuduStop::new(canceller, wait); - Ok((server, stop)) - } - - pub fn join(self) -> RS<()> { - for t in self.thread_handle { - let r = t.join(); - match r { - Err(e) => { - error!("join error {:?}", e) - } - Ok(rr) => match rr { - Err(e) => { - error!("thread error {:?}", e) - } - Ok(_) => {} - }, - } - } - Ok(()) - } - - fn create_thread( - canceller: NotifyWait, - name: String, - tasks: Vec, - ) -> RS>> { - let r_thd = thread::Builder::new() - .name(name.clone()) - .spawn(move || Self::_thread_task(canceller, name, tasks)); - let thd = r_thd.map_err(|e| m_error!(ER::ThreadErr, "create thread error", e))?; - Ok(thd) - } - - fn create_sync_thread(name: String, task: SyncTask) -> RS>> { - let r_thd = thread::Builder::new() - .name(name.clone()) - .spawn(move || task.run_once()); - let thd = r_thd.map_err(|e| m_error!(ER::ThreadErr, "spawn new thread error", e))?; - Ok(thd) - } - - fn _thread_task(canceller: NotifyWait, name: String, tasks: Vec) -> RS<()> { - let r_tokio_runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build(); - let runtime = r_tokio_runtime.map_err(|e| m_error!(ER::TokioErr, "tokio error", e))?; - let ls = LocalSet::new(); - - for task in tasks { - let c = canceller.clone(); - let n = name.clone(); - ls.spawn_local(async move { - spawn_local_task(c, "", async move { - let r = task.run_once().await; - match r { - Ok(()) => {} - Err(e) => { - error!("session thread {} run task error {:?}", n, e); - } - } - }) - }); - } - - runtime.block_on(ls); - info!("task {} done", name); - Ok(()) - } -} - -impl DebugServer { - fn new(canceler: NotifyWait, port: u16) -> Self { - Self { canceler, port } - } -} - -impl STask for DebugServer { - fn name(&self) -> String { - "debug server".to_owned() - } - - fn run(self) -> RS<()> { - debug::debug_serve(self.canceler, self.port); - Ok(()) - } -} diff --git a/mudu_kernel/src/server/parser.rs b/mudu_kernel/src/server/parser.rs deleted file mode 100644 index de72b42..0000000 --- a/mudu_kernel/src/server/parser.rs +++ /dev/null @@ -1,48 +0,0 @@ -use async_trait::async_trait; -use pgwire::api::portal::Format; -use pgwire::api::results::FieldInfo; -use pgwire::api::stmt::QueryParser; -use pgwire::api::Type; -use pgwire::error::{PgWireError, PgWireResult}; -use sql_parser::ast::parser::SQLParser; -use sql_parser::ast::stmt_list::StmtList; -use std::sync::Arc; -use tracing::info; - -pub struct Parser {} - -impl Parser { - pub fn new() -> Self { - Self {} - } -} - -#[async_trait] -impl QueryParser for Parser { - type Statement = Arc; - - async fn parse_sql( - &self, - client: &C, - sql: &str, - types: &[Option], - ) -> PgWireResult { - info!("parsing statement: {}", sql); - let parser = SQLParser::new(); - let r_tree = parser.parse(sql); - let tree = r_tree.map_err(|e| PgWireError::ApiError(Box::new(e)))?; - Ok(Arc::new(tree)) - } - - fn get_parameter_types(&self, _stmt: &Self::Statement) -> PgWireResult> { - todo!() - } - - fn get_result_schema( - &self, - _stmt: &Self::Statement, - column_format: Option<&Format>, - ) -> PgWireResult> { - todo!() - } -} diff --git a/mudu_kernel/src/server/perf_test.rs b/mudu_kernel/src/server/perf_test.rs new file mode 100644 index 0000000..0cd3d41 --- /dev/null +++ b/mudu_kernel/src/server/perf_test.rs @@ -0,0 +1,871 @@ +use crate::server::routing::{route_worker, RoutingContext, RoutingMode}; +use crate::server::server::{IoUringTcpBackend, IoUringTcpServerConfig}; +use crate::server::worker_registry::{load_or_create_worker_registry, WorkerRegistry}; +use tracing::log::info; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::error::err::MError; +use mudu::m_error; +use mudu_contract::protocol::{ + decode_error_response, decode_get_response, decode_put_response, + decode_session_create_response, encode_get_request, encode_put_request, + encode_session_create_request, Frame, GetRequest, MessageType, PutRequest, + SessionCreateRequest, HEADER_LEN, +}; +use mudu_utils::log::log_setup; +use mudu_utils::notifier::{notify_wait, NotifyWait}; +use mudu_utils::task::spawn_task; +use mudu_utils::{debug, task_trace}; +use short_uuid::ShortUuid; +use std::env::temp_dir; +use std::net::{Ipv4Addr, SocketAddr, TcpListener}; +use std::ops::RangeInclusive; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::mpsc::{self, Receiver, TryRecvError}; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{TcpSocket as TokioTcpSocket, TcpStream as TokioTcpStream}; +use tokio::sync::Notify; +use tokio::task::JoinSet; +use tracing::debug; +use uuid::Uuid; + +struct AsyncPerfClient { + stream: TokioTcpStream, + next_request_id: u64, + session_id: u128, +} + +impl AsyncPerfClient { + async fn connect(port: u16) -> RS { + Self::connect_with_loopback_shard(port, 0).await + } + + async fn connect_with_loopback_shard(port: u16, shard: usize) -> RS { + let source_ip = loopback_shard_ip(shard); + let socket = TokioTcpSocket::new_v4() + .map_err(|e| m_error!(EC::NetErr, "create io_uring perf client socket error", e))?; + socket + .bind(SocketAddr::from((source_ip, 0))) + .map_err(|e| m_error!(EC::NetErr, "bind io_uring perf client socket error", e))?; + let stream = socket + .connect(SocketAddr::from((Ipv4Addr::LOCALHOST, port))) + .await + .map_err(|e| m_error!(EC::NetErr, "connect io_uring tcp server error", e))?; + stream + .set_nodelay(true) + .map_err(|e| m_error!(EC::NetErr, "set tcp nodelay error", e))?; + let mut client = Self { + stream, + next_request_id: 1, + session_id: 0, + }; + client.session_id = client.create_session(None).await?; + Ok(client) + } + + async fn put(&mut self, key: Vec, value: Vec) -> RS<()> { + let _ = task_trace!(); + + let request_id = self.take_request_id(); + let payload = + encode_put_request(request_id, &PutRequest::new(self.session_id, key, value))?; + let frame = self.send_and_receive(&payload).await?; + self.ensure_success_frame(&frame)?; + if decode_put_response(&frame)?.ok() { + Ok(()) + } else { + Err(m_error!( + EC::NetErr, + "remote put operation returned failure" + )) + } + } + + async fn get(&mut self, key: Vec) -> RS>> { + let _t = task_trace!(); + let request_id = self.take_request_id(); + let payload = encode_get_request(request_id, &GetRequest::new(self.session_id, key))?; + let frame = self.send_and_receive(&payload).await?; + self.ensure_success_frame(&frame)?; + Ok(decode_get_response(&frame)?.into_value()) + } + + async fn create_session(&mut self, config_json: Option) -> RS { + let request_id = self.take_request_id(); + let payload = + encode_session_create_request(request_id, &SessionCreateRequest::new(config_json))?; + let frame = self.send_and_receive(&payload).await?; + self.ensure_success_frame(&frame)?; + Ok(decode_session_create_response(&frame)?.session_id()) + } + + async fn close(mut self) -> RS<()> { + self.stream + .shutdown() + .await + .map_err(|e| m_error!(EC::NetErr, "shutdown io_uring perf client stream error", e)) + } + + fn take_request_id(&mut self) -> u64 { + let request_id = self.next_request_id; + self.next_request_id += 1; + request_id + } + + async fn send_and_receive(&mut self, payload: &[u8]) -> RS { + let _trace = task_trace!(); + self._send(payload).await?; + self._receive().await + } + + async fn _send(&mut self, payload: &[u8]) -> RS<()> { + let _trace = task_trace!(); + self.stream + .write_all(payload) + .await + .map_err(|e| m_error!(EC::NetErr, "write request frame error", e))?; + self.stream + .flush() + .await + .map_err(|e| m_error!(EC::NetErr, "flush request frame error", e))?; + Ok(()) + } + async fn _receive(&mut self) -> RS { + let _ = task_trace!(); + let mut header = [0u8; HEADER_LEN]; + self.stream + .read_exact(&mut header) + .await + .map_err(|e| m_error!(EC::NetErr, "read response header error", e))?; + let payload_len = + u32::from_be_bytes([header[16], header[17], header[18], header[19]]) as usize; + let mut frame_bytes = Vec::with_capacity(HEADER_LEN + payload_len); + frame_bytes.extend_from_slice(&header); + if payload_len > 0 { + let mut body = vec![0u8; payload_len]; + self.stream + .read_exact(&mut body) + .await + .map_err(|e| m_error!(EC::NetErr, "read response payload error", e))?; + frame_bytes.extend_from_slice(&body); + } + Frame::decode(&frame_bytes) + } + + fn ensure_success_frame(&self, frame: &Frame) -> RS<()> { + let _trace = task_trace!(); + if frame.header().message_type() == MessageType::Error { + let error = decode_error_response(frame)?; + return Err(m_error!(EC::NetErr, error.message())); + } + Ok(()) + } +} + +fn loopback_shard_ip(shard: usize) -> Ipv4Addr { + // Linux routes the entire 127.0.0.0/8 block to loopback. Spreading + // clients across multiple source IPs expands the available 4-tuple space + // for single-host load tests without changing the client count. + let host = (shard % 250) as u8 + 2; + Ipv4Addr::new(127, 0, 0, host) +} + +fn reserve_listener() -> Option { + let ephemeral = linux_ephemeral_port_range().unwrap_or(32768..=60999); + for range in candidate_port_ranges(&ephemeral) { + for port in range { + match bind_reserved_listener(port) { + Ok(listener) => return Some(listener), + Err(_) => continue, + } + } + } + eprintln!("skip io_uring perf test: unable to reserve a port outside ephemeral range"); + None +} + +fn bind_reserved_listener(port: u16) -> std::io::Result { + let listener = TcpListener::bind(("127.0.0.1", port))?; + listener.set_nonblocking(true)?; + Ok(listener) +} + +fn linux_ephemeral_port_range() -> Option> { + let raw = std::fs::read_to_string("/proc/sys/net/ipv4/ip_local_port_range").ok()?; + let mut parts = raw.split_whitespace(); + let start = parts.next()?.parse::().ok()?; + let end = parts.next()?.parse::().ok()?; + Some(start..=end) +} + +fn candidate_port_ranges(ephemeral: &RangeInclusive) -> Vec> { + let mut ranges = Vec::new(); + let low = 10000u16; + let high = 65000u16; + let eph_start = *ephemeral.start(); + let eph_end = *ephemeral.end(); + if low < eph_start { + ranges.push(low..=eph_start.saturating_sub(1)); + } + if eph_end < high { + ranges.push(eph_end.saturating_add(1)..=high); + } + ranges +} + +async fn wait_for_clients_ready( + clients: usize, + ready_clients: &AtomicU64, + setup_error: &Mutex>, +) -> RS<()> { + let deadline = mudu_sys::time::instant_now() + perf_client_setup_timeout(clients); + while mudu_sys::time::instant_now() < deadline { + if let Some(err) = setup_error.lock().unwrap().clone() { + return Err(m_error!( + EC::NetErr, + format!("io_uring perf client setup failed: {err}") + )); + } + let ready = ready_clients.load(Ordering::Acquire) as usize; + if ready == clients { + return Ok(()); + } + mudu_sys::task::sleep(Duration::from_millis(25)) + .await + .expect("linux sleep wrapper should not fail"); + } + let ready = ready_clients.load(Ordering::Acquire); + Err(m_error!( + EC::NetErr, + format!( + "io_uring perf clients did not all become ready before timeout: ready={}, expected={}", + ready, clients + ) + )) +} + +fn perf_client_setup_timeout(clients: usize) -> Duration { + let default_secs = std::cmp::max(30, ((clients + 39) / 40) as u64); + let secs = std::env::var("MUDU_PERF_SETUP_TIMEOUT_SECS") + .ok() + .and_then(|v| v.parse::().ok()) + .filter(|v| *v > 0) + .unwrap_or(default_secs); + Duration::from_secs(secs) +} + +async fn wait_until_server_ready_async(port: u16) { + let deadline = mudu_sys::time::instant_now() + Duration::from_secs(10); + while mudu_sys::time::instant_now() < deadline { + match TokioTcpStream::connect(("127.0.0.1", port)).await { + Ok(stream) => { + let _ = stream.set_nodelay(true); + let _ = stream + .into_std() + .and_then(|s| s.shutdown(std::net::Shutdown::Both)); + return; + } + Err(_) => {} + } + mudu_sys::task::sleep(Duration::from_millis(25)) + .await + .expect("linux sleep wrapper should not fail"); + } + panic!("io_uring backend did not become ready on port {}", port); +} + +struct TestServerHandle { + join_handle: thread::JoinHandle>, + exit_rx: Receiver>, +} + +impl TestServerHandle { + fn join(self) -> thread::Result> { + self.join_handle.join() + } +} + +async fn wait_until_server_ready_or_exit_async(port: u16, server: &TestServerHandle) -> RS<()> { + let deadline = mudu_sys::time::instant_now() + Duration::from_secs(10); + while mudu_sys::time::instant_now() < deadline { + match server.exit_rx.try_recv() { + Ok(Ok(())) => { + return Err(m_error!( + EC::NetErr, + format!( + "io_uring backend exited before becoming ready on port {}", + port + ) + )); + } + Ok(Err(err)) => { + return Err(m_error!( + EC::NetErr, + format!( + "io_uring backend exited before becoming ready on port {}: {}", + port, err + ) + )); + } + Err(TryRecvError::Disconnected) => { + return Err(m_error!( + EC::NetErr, + format!( + "io_uring backend exit channel disconnected before ready on port {}", + port + ) + )); + } + Err(TryRecvError::Empty) => {} + } + match TokioTcpStream::connect(("127.0.0.1", port)).await { + Ok(stream) => { + let _ = stream.set_nodelay(true); + let _ = stream + .into_std() + .and_then(|s| s.shutdown(std::net::Shutdown::Both)); + return Ok(()); + } + Err(_) => {} + } + mudu_sys::task::sleep(Duration::from_millis(25)).await?; + } + Err(m_error!( + EC::NetErr, + format!("io_uring backend did not become ready on port {}", port) + )) +} + +fn spawn_iouring_server( + listener: TcpListener, + worker_count: usize, + data_dir: &std::path::Path, + log_chunk_size: u64, + worker_registry: Option>, +) -> (mudu_utils::notifier::Notifier, TestServerHandle) { + let (stop_notifier, server_stop) = notify_wait(); + let (exit_tx, exit_rx) = mpsc::channel(); + let port = listener.local_addr().unwrap().port(); + let mut server_cfg = IoUringTcpServerConfig::new( + worker_count, + "127.0.0.1".to_string(), + port, + data_dir.to_string_lossy().into_owned(), + data_dir.to_string_lossy().into_owned(), + RoutingMode::ConnectionId, + None, + ) + .unwrap() + .with_prebound_listener(listener) + .with_log_chunk_size(log_chunk_size); + if let Some(worker_registry) = worker_registry { + server_cfg = server_cfg.with_worker_registry(worker_registry).unwrap(); + } + let join_handle = thread::spawn(move || { + let result = IoUringTcpBackend::sync_serve_with_stop(server_cfg, server_stop); + let exit_msg = match &result { + Ok(()) => Ok(()), + Err(err) => Err(err.to_string()), + }; + let _ = exit_tx.send(exit_msg); + result + }); + ( + stop_notifier, + TestServerHandle { + join_handle, + exit_rx, + }, + ) +} + +fn percentile_us(samples: &mut [u64], percentile: f64) -> Option { + if samples.is_empty() { + return None; + } + samples.sort_unstable(); + let rank = ((samples.len() - 1) as f64 * percentile).ceil() as usize; + samples.get(rank).copied() +} + +fn avg_us(samples: &[u64]) -> Option { + if samples.is_empty() { + return None; + } + Some(samples.iter().copied().sum::() as f64 / samples.len() as f64) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn iouring_backend_perf_put_get() -> RS<()> { + log_setup("info"); + let notifier = NotifyWait::new(); + { + let _n = notifier.clone(); + let _ = thread::spawn(move || { + debug::debug_serve(_n, 1800); + }); + }; + let Some(listener) = reserve_listener() else { + return Ok(()); + }; + let port = listener.local_addr().unwrap().port(); + let worker_count = 6usize; + let clients = 6usize; + let bench_duration = Duration::from_secs(10); + let data_dir = temp_dir().join(format!( + "mududb_iouring_perf_{}", + mudu_sys::random::uuid_v4() + )); + std::fs::create_dir_all(&data_dir).unwrap(); + + let (stop_notifier, server_stop) = notifier.notify_wait(); + let server_cfg = IoUringTcpServerConfig::new( + worker_count, + "127.0.0.1".to_string(), + port, + data_dir.to_string_lossy().into_owned(), + data_dir.to_string_lossy().into_owned(), + RoutingMode::ConnectionId, + None, + ) + .unwrap() + .with_prebound_listener(listener); + let server_thread = + thread::spawn(move || IoUringTcpBackend::sync_serve_with_stop(server_cfg, server_stop)); + + wait_until_server_ready_async(port).await; + + let start_clients = Arc::new(AtomicBool::new(false)); + let start_notify = Arc::new(Notify::new()); + let ready_clients = Arc::new(AtomicU64::new(0)); + let setup_error = Arc::new(Mutex::new(None::)); + let stop_clients = Arc::new(AtomicBool::new(false)); + let put_ops = Arc::new(AtomicU64::new(0)); + let get_ops = Arc::new(AtomicU64::new(0)); + let put_latencies_us = Arc::new(Mutex::new(Vec::::new())); + let get_latencies_us = Arc::new(Mutex::new(Vec::::new())); + let mut join_set: JoinSet> = tokio::task::JoinSet::new(); + for client_id in 0..clients { + let start_clients = start_clients.clone(); + let start_notify = start_notify.clone(); + let ready_clients = ready_clients.clone(); + let setup_error = setup_error.clone(); + let stop_clients = stop_clients.clone(); + let put_ops = put_ops.clone(); + let get_ops = get_ops.clone(); + let put_latencies_us = put_latencies_us.clone(); + let get_latencies_us = get_latencies_us.clone(); + let join_handle = spawn_task( + notifier.clone(), + format!("task_cli_{}", client_id).as_str(), + async move { + let mut client = + match AsyncPerfClient::connect_with_loopback_shard(port, client_id).await { + Ok(client) => client, + Err(err) => { + let mut setup_error = setup_error.lock().unwrap(); + if setup_error.is_none() { + *setup_error = + Some(format!("client {client_id} connect/setup error: {err}")); + } + return Err(err); + } + }; + ready_clients.fetch_add(1, Ordering::AcqRel); + while !start_clients.load(Ordering::Acquire) { + start_notify.notified().await; + } + let mut op_id = 0usize; + let mut local_put_latencies_us = Vec::new(); + let mut local_get_latencies_us = Vec::new(); + while !stop_clients.load(Ordering::Relaxed) { + let key = format!("client-{client_id:02}-key-{op_id:06}").into_bytes(); + let value = format!("value-{client_id:02}-{op_id:06}").into_bytes(); + debug!("client {} put key ", client_id); + let put_started_at = mudu_sys::time::instant_now(); + client.put(key.clone(), value.clone()).await?; + local_put_latencies_us.push(put_started_at.elapsed().as_micros() as u64); + debug!("client {} put key done", client_id); + put_ops.fetch_add(1, Ordering::Relaxed); + debug!("client {} get key", client_id); + let get_started_at = mudu_sys::time::instant_now(); + let returned = client.get(key).await?; + local_get_latencies_us.push(get_started_at.elapsed().as_micros() as u64); + debug!("client {} get key done", client_id); + assert_eq!(returned, Some(value)); + get_ops.fetch_add(1, Ordering::Relaxed); + op_id += 1; + } + put_latencies_us + .lock() + .unwrap() + .extend(local_put_latencies_us); + get_latencies_us + .lock() + .unwrap() + .extend(local_get_latencies_us); + Ok::<(), MError>(()) + }, + )?; + join_set.spawn(async move { + match join_handle.await { + Ok(Some(Ok(()))) => Ok(()), + Ok(Some(Err(err))) => Err(err), + Ok(None) => Err(m_error!( + EC::TokioErr, + format!("io_uring perf client task {} cancelled", client_id) + )), + Err(err) => Err(m_error!( + EC::TokioErr, + format!("join io_uring perf client task {} error", client_id), + err + )), + } + }); + } + + wait_for_clients_ready(clients, ready_clients.as_ref(), setup_error.as_ref()).await?; + start_clients.store(true, Ordering::Release); + start_notify.notify_waiters(); + info!("begin testing"); + let started_at = mudu_sys::time::instant_now(); + mudu_sys::task::sleep(bench_duration).await?; + let elapsed = started_at.elapsed(); + stop_clients.store(true, Ordering::Relaxed); + while let Some(result) = join_set.join_next().await { + result.map_err(|e| { + m_error!( + EC::TokioErr, + "join io_uring perf client aggregation task error", + e + ) + })??; + } + let total_put_ops = put_ops.load(Ordering::Relaxed) as usize; + let total_get_ops = get_ops.load(Ordering::Relaxed) as usize; + let total_ops = total_put_ops + total_get_ops; + let throughput = total_ops as f64 / elapsed.as_secs_f64(); + let mut put_samples = put_latencies_us.lock().unwrap().clone(); + let mut get_samples = get_latencies_us.lock().unwrap().clone(); + let mut total_samples = Vec::with_capacity(put_samples.len() + get_samples.len()); + total_samples.extend_from_slice(&put_samples); + total_samples.extend_from_slice(&get_samples); + let put_p1_us = percentile_us(&mut put_samples, 0.01); + let put_p50_us = percentile_us(&mut put_samples, 0.50); + let put_p99_us = percentile_us(&mut put_samples, 0.99); + let put_p999_us = percentile_us(&mut put_samples, 0.999); + let get_p1_us = percentile_us(&mut get_samples, 0.01); + let get_p50_us = percentile_us(&mut get_samples, 0.50); + let get_p99_us = percentile_us(&mut get_samples, 0.99); + let get_p999_us = percentile_us(&mut get_samples, 0.999); + let total_p1_us = percentile_us(&mut total_samples, 0.01); + let total_p50_us = percentile_us(&mut total_samples, 0.50); + let total_p99_us = percentile_us(&mut total_samples, 0.99); + let total_p999_us = percentile_us(&mut total_samples, 0.999); + let put_avg_us = avg_us(&put_samples); + let get_avg_us = avg_us(&get_samples); + let total_avg_us = avg_us(&total_samples); + info!( + "io_uring kv perf: clients={}, puts={}, gets={}, total_ops={}, elapsed_ms={}, throughput_ops_per_sec={:.2}, put_avg_us={:.2}, put_p1_us={}, put_p50_us={}, put_tail_p99_us={}, put_tail_p999_us={}, get_avg_us={:.2}, get_p1_us={}, get_p50_us={}, get_tail_p99_us={}, get_tail_p999_us={}, total_avg_us={:.2}, total_p1_us={}, total_p50_us={}, total_tail_p99_us={}, total_tail_p999_us={}", + clients, + total_put_ops, + total_get_ops, + total_ops, + elapsed.as_millis(), + throughput, + put_avg_us.unwrap_or_default(), + put_p1_us.unwrap_or_default(), + put_p50_us.unwrap_or_default(), + put_p99_us.unwrap_or_default(), + put_p999_us.unwrap_or_default(), + get_avg_us.unwrap_or_default(), + get_p1_us.unwrap_or_default(), + get_p50_us.unwrap_or_default(), + get_p99_us.unwrap_or_default(), + get_p999_us.unwrap_or_default(), + total_avg_us.unwrap_or_default(), + total_p1_us.unwrap_or_default(), + total_p50_us.unwrap_or_default(), + total_p99_us.unwrap_or_default(), + total_p999_us.unwrap_or_default() + ); + stop_notifier.notify_all(); + server_thread.join().unwrap()?; + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn iouring_backend_recovery_replays_worker_logs() -> RS<()> { + let Some(listener) = reserve_listener() else { + return Ok(()); + }; + let port = listener.local_addr().unwrap().port(); + let worker_count = 2usize; + let data_dir = temp_dir().join(format!( + "mududb_iouring_recovery_{}", + mudu_sys::random::uuid_v4() + )); + std::fs::create_dir_all(&data_dir).unwrap(); + let registry = load_or_create_worker_registry(&data_dir, worker_count)?; + let target_worker = registry.worker(0).unwrap(); + + let (stop_notifier, server_thread) = spawn_iouring_server( + listener, + worker_count, + &data_dir, + 64 * 1024 * 1024, + Some(registry.clone()), + ); + wait_until_server_ready_or_exit_async(port, &server_thread).await?; + + { + let mut client = AsyncPerfClient::connect(port).await?; + client.session_id = client + .create_session(Some( + serde_json::json!({ + "session_id": "0", + "worker_id": target_worker.worker_id.to_string(), + }) + .to_string(), + )) + .await?; + client.put(b"alpha".to_vec(), b"one".to_vec()).await?; + client.put(b"beta".to_vec(), b"two".to_vec()).await?; + assert_eq!(client.get(b"alpha".to_vec()).await?, Some(b"one".to_vec())); + assert_eq!(client.get(b"beta".to_vec()).await?, Some(b"two".to_vec())); + client.close().await?; + } + + stop_notifier.notify_all(); + server_thread.join().unwrap()?; + + let (stop_notifier, server_thread) = spawn_iouring_server( + bind_reserved_listener(port).unwrap(), + worker_count, + &data_dir, + 64 * 1024 * 1024, + Some(registry.clone()), + ); + wait_until_server_ready_or_exit_async(port, &server_thread).await?; + + { + let mut client = AsyncPerfClient::connect(port).await?; + client.session_id = client + .create_session(Some( + serde_json::json!({ + "session_id": "0", + "worker_id": target_worker.worker_id.to_string(), + }) + .to_string(), + )) + .await?; + assert_eq!(client.get(b"alpha".to_vec()).await?, Some(b"one".to_vec())); + assert_eq!(client.get(b"beta".to_vec()).await?, Some(b"two".to_vec())); + client.close().await?; + } + + stop_notifier.notify_all(); + server_thread.join().unwrap()?; + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn iouring_backend_recovery_replays_across_multiple_chunks() -> RS<()> { + let Some(listener) = reserve_listener() else { + return Ok(()); + }; + let port = listener.local_addr().unwrap().port(); + let worker_count = 1usize; + let log_chunk_size = 64u64; + let data_dir = temp_dir().join(format!( + "mududb_iouring_recovery_multichunk_{}", + mudu_sys::random::uuid_v4() + )); + std::fs::create_dir_all(&data_dir).unwrap(); + let entries = vec![ + (b"alpha".to_vec(), b"one".to_vec()), + (b"beta".to_vec(), b"two".to_vec()), + (b"gamma".to_vec(), b"three".to_vec()), + (b"delta".to_vec(), b"four".to_vec()), + ]; + + let (stop_notifier, server_thread) = + spawn_iouring_server(listener, worker_count, &data_dir, log_chunk_size, None); + wait_until_server_ready_or_exit_async(port, &server_thread).await?; + { + let mut client = AsyncPerfClient::connect(port).await?; + for (key, value) in &entries { + client.put(key.clone(), value.clone()).await?; + } + client.close().await?; + } + stop_notifier.notify_all(); + server_thread.join().unwrap()?; + + let chunk_count = std::fs::read_dir(&data_dir) + .unwrap() + .filter_map(|entry| entry.ok()) + .filter(|entry| entry.path().extension().and_then(|ext| ext.to_str()) == Some("xl")) + .count(); + assert!( + chunk_count >= 2, + "expected multiple log chunks, got {}", + chunk_count + ); + + let (stop_notifier, server_thread) = spawn_iouring_server( + bind_reserved_listener(port).unwrap(), + worker_count, + &data_dir, + log_chunk_size, + None, + ); + wait_until_server_ready_or_exit_async(port, &server_thread).await?; + { + let mut client = AsyncPerfClient::connect(port).await?; + for (key, value) in &entries { + assert_eq!(client.get(key.clone()).await?, Some(value.clone())); + } + client.close().await?; + } + stop_notifier.notify_all(); + server_thread.join().unwrap()?; + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn iouring_backend_open_session_routes_connection_to_requested_partition() -> RS<()> { + let Some(listener) = reserve_listener() else { + return Ok(()); + }; + let port = listener.local_addr().unwrap().port(); + let worker_count = 2usize; + let data_dir = temp_dir().join(format!( + "mududb_iouring_route_{}", + mudu_sys::random::uuid_v4() + )); + std::fs::create_dir_all(&data_dir).unwrap(); + + let initial_partition = route_worker( + &RoutingContext::new(1, "127.0.0.1:10000".parse().unwrap(), None), + RoutingMode::ConnectionId, + worker_count, + ); + let target_partition = (initial_partition + 1) % worker_count; + let registry = load_or_create_worker_registry(&data_dir, worker_count)?; + let target_worker = registry.worker(target_partition).unwrap(); + + let (stop_notifier, server_thread) = spawn_iouring_server( + listener, + worker_count, + &data_dir, + 64 * 1024 * 1024, + Some(registry.clone()), + ); + wait_until_server_ready_or_exit_async(port, &server_thread).await?; + + { + let mut client = AsyncPerfClient::connect(port).await?; + let session_id = client + .create_session(Some( + serde_json::json!({ + "session_id": "0", + "worker_id": target_worker.worker_id.to_string(), + }) + .to_string(), + )) + .await?; + client.session_id = session_id; + client + .put(b"route-key".to_vec(), b"route-val".to_vec()) + .await?; + assert_eq!( + client.get(b"route-key".to_vec()).await?, + Some(b"route-val".to_vec()) + ); + } + + stop_notifier.notify_all(); + server_thread.join().unwrap()?; + + let expected_prefix = + ShortUuid::from_uuid(&Uuid::from_u128(target_worker.worker_id)).to_string(); + let routed_chunk_count = std::fs::read_dir(&data_dir) + .unwrap() + .filter_map(|entry| entry.ok()) + .filter(|entry| { + entry + .file_name() + .to_str() + .map(|name| name.starts_with(&expected_prefix) && name.ends_with(".xl")) + .unwrap_or(false) + }) + .count(); + assert!( + routed_chunk_count > 0, + "expected log chunks for target partition {}", + target_partition + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn iouring_backend_open_session_rebind_keeps_same_session_id() -> RS<()> { + let Some(listener) = reserve_listener() else { + return Ok(()); + }; + let port = listener.local_addr().unwrap().port(); + let worker_count = 2usize; + let data_dir = temp_dir().join(format!( + "mududb_iouring_rebind_{}", + mudu_sys::random::uuid_v4() + )); + std::fs::create_dir_all(&data_dir).unwrap(); + + let initial_partition = route_worker( + &RoutingContext::new(1, "127.0.0.1:10001".parse().unwrap(), None), + RoutingMode::ConnectionId, + worker_count, + ); + let target_partition = (initial_partition + 1) % worker_count; + let registry = load_or_create_worker_registry(&data_dir, worker_count)?; + let target_worker = registry.worker(target_partition).unwrap(); + + let (stop_notifier, server_thread) = spawn_iouring_server( + listener, + worker_count, + &data_dir, + 64 * 1024 * 1024, + Some(registry.clone()), + ); + wait_until_server_ready_or_exit_async(port, &server_thread).await?; + + { + let mut client = AsyncPerfClient::connect(port).await?; + let original_session_id = client.session_id; + let rebound_session_id = client + .create_session(Some( + serde_json::json!({ + "session_id": original_session_id.to_string(), + "worker_id": target_worker.worker_id.to_string(), + }) + .to_string(), + )) + .await?; + assert_eq!(rebound_session_id, original_session_id); + client + .put(b"rebind-key".to_vec(), b"rebind-val".to_vec()) + .await?; + assert_eq!( + client.get(b"rebind-key".to_vec()).await?, + Some(b"rebind-val".to_vec()) + ); + } + + stop_notifier.notify_all(); + server_thread.join().unwrap()?; + Ok(()) +} diff --git a/mudu_kernel/src/server/protocol_codec.rs b/mudu_kernel/src/server/protocol_codec.rs new file mode 100644 index 0000000..a5cfbdc --- /dev/null +++ b/mudu_kernel/src/server/protocol_codec.rs @@ -0,0 +1,55 @@ +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use mudu_contract::protocol::{Frame, FrameHeader, HEADER_LEN}; + +use crate::io::socket::{recv_into, send_all, IoSocket}; + +pub(in crate::server) async fn read_next_frame( + socket: &IoSocket, + read_buf: &mut Vec, +) -> RS> { + let mut header_buf = [0u8; HEADER_LEN]; + match read_exact(socket, &mut header_buf).await? { + Some(()) => {} + None => return Ok(None), + } + let header = FrameHeader::decode_header_bytes(&header_buf)?; + read_buf.clear(); + read_buf.resize(header.payload_len() as usize, 0); + if !read_buf.is_empty() { + read_exact(socket, read_buf.as_mut_slice()) + .await? + .ok_or_else(|| { + m_error!( + EC::ParseErr, + "connection closed with an incomplete protocol frame" + ) + })?; + } + let payload = std::mem::take(read_buf); + Ok(Some(Frame::from_parts(header, payload)?)) +} + +pub(in crate::server) async fn write_response(socket: &IoSocket, payload: &[u8]) -> RS<()> { + send_all(socket, payload).await +} + +async fn read_exact(socket: &IoSocket, mut dst: &mut [u8]) -> RS> { + let mut read_any = false; + while !dst.is_empty() { + let read = recv_into(socket, dst, 0).await?; + if read == 0 { + if read_any { + return Err(m_error!( + EC::ParseErr, + "connection closed with an incomplete protocol frame" + )); + } + return Ok(None); + } + read_any = true; + dst = &mut dst[read..]; + } + Ok(Some(())) +} diff --git a/mudu_kernel/src/server/request_ctx.rs b/mudu_kernel/src/server/request_ctx.rs new file mode 100644 index 0000000..c17141b --- /dev/null +++ b/mudu_kernel/src/server/request_ctx.rs @@ -0,0 +1,158 @@ +use mudu::common::id::OID; +use mudu::common::result::RS; +use mudu_contract::protocol::{ + encode_get_response, encode_procedure_invoke_response, encode_put_response, + encode_range_scan_response, encode_session_close_response, encode_session_create_response, + GetResponse, KeyValue, ProcedureInvokeResponse, PutResponse, RangeScanResponse, + SessionCloseResponse, SessionCreateResponse, +}; +use std::sync::Arc; + +use crate::server::async_func_task::HandleResult; +use crate::server::request_response_worker::WorkerRuntimeRef; +use crate::server::routing::parse_session_open_config; +use crate::server::routing::{SessionOpenConfig, SessionOpenTransferAction}; +use crate::server::worker_registry::WorkerRegistry; + +#[derive(Clone)] +pub(in crate::server) struct RequestCtx { + worker: WorkerRuntimeRef, + conn_id: u64, + request_id: u64, +} + +impl RequestCtx { + pub(in crate::server) fn new(worker: WorkerRuntimeRef, conn_id: u64, request_id: u64) -> Self { + Self { + worker, + conn_id, + request_id, + } + } + + #[allow(dead_code)] + pub(in crate::server) fn conn_id(&self) -> u64 { + self.conn_id + } + + #[allow(dead_code)] + pub(in crate::server) fn request_id(&self) -> u64 { + self.request_id + } + + pub(in crate::server) fn worker_index(&self) -> usize { + self.worker.worker_index() + } + + pub(in crate::server) fn worker_id(&self) -> OID { + self.worker.worker_id() + } + + pub(in crate::server) fn registry(&self) -> Arc { + self.worker.registry() + } + + pub(in crate::server) fn parse_session_open_config( + &self, + config_json: Option<&str>, + ) -> RS { + parse_session_open_config( + config_json, + self.worker_index(), + self.worker_id(), + self.registry().as_ref(), + ) + } + + pub(in crate::server) async fn get(&self, session_id: OID, key: &[u8]) -> RS { + let value = self.worker.get_async(session_id, key).await?; + Ok(HandleResult::Response(encode_get_response( + self.request_id, + &GetResponse::new(value), + )?)) + } + + pub(in crate::server) async fn put( + &self, + session_id: OID, + key: Vec, + value: Vec, + ) -> RS { + self.worker.put_async(session_id, key, value).await?; + Ok(HandleResult::Response(encode_put_response( + self.request_id, + &PutResponse::new(true), + )?)) + } + + pub(in crate::server) async fn invoke_procedure( + &self, + request: mudu_contract::protocol::ProcedureInvokeRequest, + ) -> RS { + let response = self + .worker + .handle_procedure_request(self.conn_id, &request) + .await?; + Ok(HandleResult::Response(encode_procedure_invoke_response( + self.request_id, + &ProcedureInvokeResponse::new(response.into_result()), + )?)) + } + + pub(in crate::server) async fn range_scan( + &self, + session_id: OID, + start_key: &[u8], + end_key: &[u8], + ) -> RS { + let items = self + .worker + .range_async(session_id, start_key, end_key) + .await?; + Ok(HandleResult::Response(encode_range_scan_response( + self.request_id, + &RangeScanResponse::new( + items + .into_iter() + .map(|item| KeyValue::new(item.key, item.value)) + .collect(), + ), + )?)) + } + + pub(in crate::server) async fn session_create( + &self, + config: SessionOpenConfig, + ) -> RS { + if config.target_worker_index() == self.worker.worker_index() { + Ok(HandleResult::Response(encode_session_create_response( + self.request_id, + &SessionCreateResponse::new( + self.worker.open_session_with_config(self.conn_id, config)?, + ), + )?)) + } else { + let action = SessionOpenTransferAction::new(self.request_id, config); + let session_ids = self + .worker + .prepare_connection_transfer(self.conn_id, Some(action))?; + Ok(HandleResult::Transfer( + crate::server::async_func_task::SessionTransferDispatch::new( + config.target_worker_index(), + session_ids, + action, + ), + )) + } + } + + pub(in crate::server) async fn session_close(&self, session_id: OID) -> RS { + Ok(HandleResult::Response(encode_session_close_response( + self.request_id, + &SessionCloseResponse::new( + self.worker + .close_session_for_connection(self.conn_id, session_id)?, + ), + )?)) + } +} diff --git a/mudu_kernel/src/server/request_response_worker.rs b/mudu_kernel/src/server/request_response_worker.rs new file mode 100644 index 0000000..4f47c6e --- /dev/null +++ b/mudu_kernel/src/server/request_response_worker.rs @@ -0,0 +1,40 @@ +use crate::server::worker_local::WorkerLocal; +use crate::server::worker_registry::WorkerRegistry; +use async_trait::async_trait; +use mudu::common::id::OID; +use mudu::common::result::RS; +use mudu_contract::protocol::{ProcedureInvokeRequest, ProcedureInvokeResponse}; +use std::sync::Arc; + +use crate::server::routing::{SessionOpenConfig, SessionOpenTransferAction}; + +#[async_trait] +pub trait RequestResponseWorker: Send + Sync { + fn worker_index(&self) -> usize; + + fn worker_id(&self) -> OID; + + fn registry(&self) -> Arc; + + fn open_session_with_config(&self, conn_id: u64, config: SessionOpenConfig) -> RS; + + fn prepare_connection_transfer( + &self, + conn_id: u64, + action: Option, + ) -> RS>; + + fn close_session_for_connection(&self, conn_id: u64, session_id: OID) -> RS; + + async fn handle_procedure_request( + &self, + conn_id: u64, + request: &ProcedureInvokeRequest, + ) -> RS; +} + +pub trait WorkerRuntime: RequestResponseWorker + WorkerLocal {} + +impl WorkerRuntime for T where T: RequestResponseWorker + WorkerLocal + ?Sized {} + +pub type WorkerRuntimeRef = Arc; diff --git a/mudu_kernel/src/server_ur/routing.rs b/mudu_kernel/src/server/routing.rs similarity index 98% rename from mudu_kernel/src/server_ur/routing.rs rename to mudu_kernel/src/server/routing.rs index 92d248e..7681a4b 100644 --- a/mudu_kernel/src/server_ur/routing.rs +++ b/mudu_kernel/src/server/routing.rs @@ -1,5 +1,5 @@ -use crate::server_ur::fsm::ConnectionState; -use crate::server_ur::worker_registry::WorkerRegistry; +use crate::server::fsm::ConnectionState; +use crate::server::worker_registry::WorkerRegistry; use mudu::common::id::OID; use mudu::common::result::RS; use mudu::error::ec::EC; diff --git a/mudu_kernel/src/server_ur/server.rs b/mudu_kernel/src/server/server.rs similarity index 66% rename from mudu_kernel/src/server_ur/server.rs rename to mudu_kernel/src/server/server.rs index 005935e..aacbab6 100644 --- a/mudu_kernel/src/server_ur/server.rs +++ b/mudu_kernel/src/server/server.rs @@ -1,23 +1,22 @@ -use crate::server_ur::procedure_runtime::ProcInvokerPtr; -use crate::server_ur::routing::{ - parse_session_open_config, ConnectionTransfer, RoutingMode, SessionOpenTransferAction, -}; -use crate::server_ur::worker::IoUringWorker; -use crate::server_ur::worker_local::WorkerLocal; -use crate::server_ur::worker_registry::{load_or_create_worker_registry, WorkerRegistry}; +#![allow(dead_code)] + +use crate::server::async_func_runtime::AsyncFuncInvokerPtr; +use crate::server::async_func_task::{AsyncFuncFuture, AsyncFuncTask, HandleResult}; +use crate::server::async_func_task_waker::AsyncFuncTaskWaker; +use crate::server::frame_dispatch::{dispatch_frame_async, try_decode_next_frame}; +use crate::server::routing::{ConnectionTransfer, RoutingMode, SessionOpenTransferAction}; +use crate::server::worker::IoUringWorker; +use crate::server::worker_registry::{load_or_create_worker_registry, WorkerRegistry}; +use crate::wal::worker_log::WorkerLogBatching; use crossbeam_queue::SegQueue; +use futures::task::{waker, Context}; +use futures::Future; use mudu::common::id::OID; use mudu::common::result::RS; use mudu::error::ec::EC; use mudu::m_error; use mudu_contract::protocol::{ - decode_client_request, decode_get_request, decode_procedure_invoke_request, decode_put_request, - decode_range_scan_request, decode_session_close_request, decode_session_create_request, - encode_error_response, encode_get_response, encode_procedure_invoke_response, - encode_put_response, encode_range_scan_response, encode_server_response, - encode_session_close_response, encode_session_create_response, Frame, GetResponse, KeyValue, - MessageType, ProcedureInvokeResponse, PutResponse, RangeScanResponse, ServerResponse, - SessionCloseResponse, SessionCreateResponse, HEADER_LEN, + encode_error_response, encode_session_create_response, Frame, SessionCreateResponse, }; use mudu_utils::notifier::{notify_wait, Waiter}; use socket2::{Domain, Protocol, Socket, Type}; @@ -26,6 +25,7 @@ use std::io::{ErrorKind, Read, Write}; use std::net::{SocketAddr, TcpListener, TcpStream}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{atomic::AtomicBool, Arc}; +use std::task::Poll; use std::thread; use std::time::Duration; @@ -40,11 +40,14 @@ pub struct IoUringTcpServerConfig { worker_count: usize, listen_ip: String, listen_port: u16, + prebound_listener: Option, + data_dir: String, log_dir: String, log_chunk_size: u64, + log_batching: WorkerLogBatching, routing_mode: RoutingMode, - procedure_runtime: Option, - worker_procedure_runtimes: Option>, + procedure_runtime: Option, + worker_procedure_runtimes: Option>, worker_registry: Arc, } @@ -58,17 +61,21 @@ impl IoUringTcpServerConfig { worker_count: usize, listen_ip: String, listen_port: u16, + data_dir: String, log_dir: String, routing_mode: RoutingMode, - procedure_runtime: Option, + procedure_runtime: Option, ) -> RS { let worker_registry = load_or_create_worker_registry(&log_dir, worker_count)?; Ok(Self { worker_count, listen_ip, listen_port, + prebound_listener: None, + data_dir, log_dir, log_chunk_size: 64 * 1024 * 1024, + log_batching: WorkerLogBatching::default(), routing_mode, procedure_runtime, worker_procedure_runtimes: None, @@ -81,13 +88,38 @@ impl IoUringTcpServerConfig { self } + pub fn with_log_batching(mut self, log_batching: WorkerLogBatching) -> Self { + self.log_batching = log_batching; + self + } + + pub fn with_prebound_listener(mut self, listener: TcpListener) -> Self { + self.prebound_listener = Some(listener); + self + } + + pub fn with_worker_registry(mut self, worker_registry: Arc) -> RS { + if worker_registry.workers().len() != self.worker_count { + return Err(m_error!( + EC::ParseErr, + format!( + "worker registry count {} does not match expected {}", + worker_registry.workers().len(), + self.worker_count + ) + )); + } + self.worker_registry = worker_registry; + Ok(self) + } + /// Installs per-worker procedure runtimes. /// /// When this is not set, every worker uses `procedure_runtime()`. This hook /// exists so upper layers can give each worker an isolated invoker instance /// while keeping the transport API unchanged across Linux and non-Linux /// implementations. - pub fn with_worker_procedure_runtimes(mut self, runtimes: Vec) -> Self { + pub fn with_worker_procedure_runtimes(mut self, runtimes: Vec) -> Self { self.worker_procedure_runtimes = Some(runtimes); self } @@ -104,14 +136,26 @@ impl IoUringTcpServerConfig { self.listen_port } + pub fn take_prebound_listener(&mut self) -> Option { + self.prebound_listener.take() + } + pub fn log_dir(&self) -> &str { &self.log_dir } + pub fn data_dir(&self) -> &str { + &self.data_dir + } + pub fn log_chunk_size(&self) -> u64 { self.log_chunk_size } + pub fn log_batching(&self) -> WorkerLogBatching { + self.log_batching + } + pub fn routing_mode(&self) -> RoutingMode { self.routing_mode } @@ -120,11 +164,11 @@ impl IoUringTcpServerConfig { self.worker_registry.clone() } - pub fn procedure_runtime(&self) -> Option { + pub fn procedure_runtime(&self) -> Option { self.procedure_runtime.clone() } - pub fn procedure_runtime_for_worker(&self, worker_id: usize) -> Option { + pub fn procedure_runtime_for_worker(&self, worker_id: usize) -> Option { self.worker_procedure_runtimes .as_ref() .and_then(|runtimes| runtimes.get(worker_id).cloned()) @@ -149,7 +193,7 @@ struct TransferredConnection { struct WorkerConnection { conn_id: u64, - state: crate::server_ur::fsm::ConnectionState, + state: crate::server::fsm::ConnectionState, stream: TcpStream, remote_addr: SocketAddr, transferred: bool, @@ -157,13 +201,145 @@ struct WorkerConnection { write_buf: Vec, } -enum DispatchFrameResult { - Immediate(Vec), - Transfer { - target_worker: usize, - session_ids: Vec, - action: SessionOpenTransferAction, - }, +fn apply_handle_result_to_connection( + connection: &mut WorkerConnection, + inboxes: &[Arc>], + result: HandleResult, +) -> RS<()> { + match result { + HandleResult::Response(payload) => { + connection.write_buf.extend_from_slice(&payload); + } + HandleResult::Transfer(transfer) => { + let stream = connection + .stream + .try_clone() + .map_err(|e| m_error!(EC::NetErr, "clone transferred stream error", e))?; + enqueue_transfer( + inboxes, + connection.conn_id, + transfer.target_worker(), + connection.remote_addr, + stream, + transfer.session_ids().to_vec(), + Some(transfer.action()), + )?; + connection.transferred = true; + connection.state = crate::server::fsm::ConnectionState::Closing; + connection.write_buf.clear(); + } + } + Ok(()) +} + +fn apply_handle_result( + connections: &mut HashMap, + inboxes: &[Arc>], + conn_id: u64, + result: HandleResult, +) -> RS<()> { + let Some(connection) = connections.get_mut(&conn_id) else { + return Ok(()); + }; + apply_handle_result_to_connection(connection, inboxes, result) +} + +struct FallbackAsyncFuncState { + next_task_id: u64, + next_op_id: u64, + tasks: HashMap, + ready_queue: Arc>, + completion_queue: Arc>, + op_registry: HashMap, +} + +impl FallbackAsyncFuncState { + fn new() -> Self { + Self { + next_task_id: 1, + next_op_id: 1, + tasks: HashMap::new(), + ready_queue: Arc::new(SegQueue::new()), + completion_queue: Arc::new(SegQueue::new()), + op_registry: HashMap::new(), + } + } + + fn enqueue_future(&mut self, conn_id: u64, request_id: u64, future: AsyncFuncFuture) { + let task_id = self.next_task_id; + self.next_task_id += 1; + self.tasks.insert( + task_id, + AsyncFuncTask::new( + conn_id, + request_id, + future, + Arc::new(AtomicBool::new(false)), + ), + ); + self.ready_queue.push(task_id); + } + + fn drain_completions(&mut self) -> bool { + let mut progressed = false; + while let Some(op_id) = self.completion_queue.pop() { + let Some(task_id) = self.op_registry.remove(&op_id) else { + continue; + }; + let Some(task) = self.tasks.get(&task_id) else { + continue; + }; + if !task.queued().swap(true, Ordering::AcqRel) { + self.ready_queue.push(task_id); + progressed = true; + } + } + progressed + } + + fn poll_ready( + &mut self, + connections: &mut HashMap, + inboxes: &[Arc>], + ) -> RS { + let mut progressed = false; + while let Some(task_id) = self.ready_queue.pop() { + let Some(mut task) = self.tasks.remove(&task_id) else { + continue; + }; + progressed = true; + task.clear_queued(); + if let Some(waiting_on) = task.take_waiting_on() { + self.op_registry.remove(&waiting_on); + } + + let op_id = self.next_op_id; + self.next_op_id += 1; + let waker = waker(Arc::new(AsyncFuncTaskWaker::new( + op_id, + self.completion_queue.clone(), + task.completed().clone(), + ))); + let mut cx = Context::from_waker(&waker); + match task.future_mut().poll(&mut cx) { + Poll::Ready(Ok(result)) => { + apply_handle_result(connections, inboxes, task.conn_id(), result)?; + } + Poll::Ready(Err(err)) => { + if let Some(connection) = connections.get_mut(&task.conn_id()) { + let response = encode_error_response(task.request_id(), err.to_string())?; + connection.write_buf.extend_from_slice(&response); + } + } + Poll::Pending => { + task.set_waiting_on(op_id); + self.op_registry.insert(op_id, task_id); + self.tasks.insert(task_id, task); + } + } + } + Ok(progressed) + } } impl IoUringTcpBackend { @@ -184,7 +360,7 @@ impl IoUringTcpBackend { pub fn sync_serve_with_stop(cfg: IoUringTcpServerConfig, stop: Waiter) -> RS<()> { #[cfg(target_os = "linux")] { - return crate::server_ur::server_iouring::sync_serve_iouring(cfg, stop); + return crate::server::server_iouring::sync_serve_iouring(cfg, stop); } #[cfg(not(target_os = "linux"))] @@ -220,7 +396,7 @@ impl IoUringTcpBackend { } // Non-Linux compatibility path for the historical `IoUringTcpBackend` API. -fn sync_serve_fallback(cfg: IoUringTcpServerConfig, stop: Arc) -> RS<()> { +fn sync_serve_fallback(mut cfg: IoUringTcpServerConfig, stop: Arc) -> RS<()> { if cfg.worker_count() == 0 { return Err(m_error!(EC::ParseErr, "invalid io_uring worker count")); } @@ -232,13 +408,17 @@ fn sync_serve_fallback(cfg: IoUringTcpServerConfig, stop: Arc) -> RS let inboxes: Vec<_> = (0..cfg.worker_count()) .map(|_| Arc::new(SegQueue::::new())) .collect(); - let listener = create_listener(listen_addr)?; + let listener = match cfg.take_prebound_listener() { + Some(listener) => listener, + None => create_listener(listen_addr)?, + }; let mut handles = Vec::with_capacity(cfg.worker_count()); for worker_id in 0..cfg.worker_count() { let worker_count = cfg.worker_count(); let log_dir = cfg.log_dir().to_string(); let log_chunk_size = cfg.log_chunk_size(); + let log_batching = cfg.log_batching(); let routing_mode = cfg.routing_mode(); let procedure_runtime = cfg.procedure_runtime_for_worker(worker_id); let worker_identity = cfg @@ -252,6 +432,7 @@ fn sync_serve_fallback(cfg: IoUringTcpServerConfig, stop: Arc) -> RS ) })?; let worker_registry = cfg.worker_registry(); + let data_dir = cfg.data_dir().to_string(); let inbox = inboxes[worker_id].clone(); let all_inboxes = inboxes.clone(); let conn_id_alloc = conn_id_alloc.clone(); @@ -262,12 +443,14 @@ fn sync_serve_fallback(cfg: IoUringTcpServerConfig, stop: Arc) -> RS let handle = thread::Builder::new() .name(format!("iouring-tcp-worker-{worker_id}")) .spawn(move || { - let worker = IoUringWorker::new( + let worker = IoUringWorker::new_with_log_batching( worker_identity, worker_count, routing_mode, log_dir, + data_dir, log_chunk_size, + log_batching, procedure_runtime, worker_registry, )?; @@ -317,11 +500,8 @@ fn run_worker_loop( conn_id_alloc: Arc, stop: Arc, ) -> RS<()> { - let mut runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .map_err(|e| m_error!(EC::TokioErr, "create worker runtime error", e))?; let mut connections = HashMap::::new(); + let mut async_funcs = FallbackAsyncFuncState::new(); let idle_sleep = Duration::from_millis(1); while !stop.load(Ordering::Relaxed) { @@ -334,7 +514,9 @@ fn run_worker_loop( &conn_id_alloc, )?; progressed |= drain_transferred_connections(&worker, inbox.as_ref(), &mut connections)?; - progressed |= drive_connections(&worker, &mut runtime, &mut connections, &inboxes)?; + progressed |= async_funcs.drain_completions(); + progressed |= async_funcs.poll_ready(&mut connections, &inboxes)?; + progressed |= drive_connections(&worker, &mut async_funcs, &mut connections, &inboxes)?; if !progressed { thread::sleep(idle_sleep); @@ -403,7 +585,7 @@ fn enqueue_transfer( transfer: ConnectionTransfer::new( conn_id, target_worker, - crate::server_ur::fsm::ConnectionState::Accepted, + crate::server::fsm::ConnectionState::Accepted, remote_addr, ), stream, @@ -462,7 +644,7 @@ fn register_connection( conn_id, WorkerConnection { conn_id, - state: crate::server_ur::fsm::ConnectionState::Active, + state: crate::server::fsm::ConnectionState::Active, stream, remote_addr, transferred: false, @@ -475,7 +657,7 @@ fn register_connection( fn drive_connections( worker: &IoUringWorker, - runtime: &mut tokio::runtime::Runtime, + async_funcs: &mut FallbackAsyncFuncState, connections: &mut HashMap, inboxes: &[Arc>], ) -> RS { @@ -488,9 +670,9 @@ fn drive_connections( continue; }; progressed |= flush_pending_writes(connection)?; - let connection_progress = read_and_dispatch(worker, runtime, connection, inboxes)?; + let connection_progress = read_and_dispatch(worker, async_funcs, connection, inboxes)?; progressed |= connection_progress; - if connection.state == crate::server_ur::fsm::ConnectionState::Closing + if connection.state == crate::server::fsm::ConnectionState::Closing && connection.write_buf.is_empty() { closed.push((conn_id, connection.transferred)); @@ -511,7 +693,7 @@ fn flush_pending_writes(connection: &mut WorkerConnection) -> RS { while !connection.write_buf.is_empty() { match connection.stream.write(&connection.write_buf) { Ok(0) => { - connection.state = crate::server_ur::fsm::ConnectionState::Closing; + connection.state = crate::server::fsm::ConnectionState::Closing; break; } Ok(written) => { @@ -527,7 +709,7 @@ fn flush_pending_writes(connection: &mut WorkerConnection) -> RS { fn read_and_dispatch( worker: &IoUringWorker, - runtime: &mut tokio::runtime::Runtime, + async_funcs: &mut FallbackAsyncFuncState, connection: &mut WorkerConnection, inboxes: &[Arc>], ) -> RS { @@ -536,7 +718,7 @@ fn read_and_dispatch( loop { match connection.stream.read(&mut buf) { Ok(0) => { - connection.state = crate::server_ur::fsm::ConnectionState::Closing; + connection.state = crate::server::fsm::ConnectionState::Closing; break; } Ok(read) => { @@ -550,35 +732,16 @@ fn read_and_dispatch( while let Some((frame, consumed)) = try_decode_next_frame(&connection.read_buf)? { progressed = true; - let response = dispatch_frame(worker, connection.conn_id, runtime, &frame); + let response = dispatch_frame(worker, connection.conn_id, async_funcs, &frame); connection.read_buf.drain(0..consumed); match response { - Ok(DispatchFrameResult::Immediate(payload)) => { - connection.write_buf.extend_from_slice(&payload); - } - Ok(DispatchFrameResult::Transfer { - target_worker, - session_ids, - action, - }) => { - let stream = connection - .stream - .try_clone() - .map_err(|e| m_error!(EC::NetErr, "clone transferred stream error", e))?; - enqueue_transfer( - inboxes, - connection.conn_id, - target_worker, - connection.remote_addr, - stream, - session_ids, - Some(action), - )?; - connection.transferred = true; - connection.state = crate::server_ur::fsm::ConnectionState::Closing; - connection.write_buf.clear(); - return Ok(true); + Ok(Some(result)) => { + apply_handle_result_to_connection(connection, inboxes, result)?; + if connection.transferred { + return Ok(true); + } } + Ok(None) => {} Err(err) => { let payload = encode_error_response(frame.header().request_id(), err.to_string())?; connection.write_buf.extend_from_slice(&payload); @@ -588,130 +751,28 @@ fn read_and_dispatch( Ok(progressed) } -fn try_decode_next_frame(buf: &[u8]) -> RS> { - if buf.len() < HEADER_LEN { - return Ok(None); - } - let payload_len = u32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]) as usize; - let frame_len = HEADER_LEN + payload_len; - if buf.len() < frame_len { - return Ok(None); - } - let frame = Frame::decode(&buf[..frame_len])?; - Ok(Some((frame, frame_len))) -} - fn dispatch_frame( worker: &IoUringWorker, conn_id: u64, - runtime: &mut tokio::runtime::Runtime, + async_funcs: &mut FallbackAsyncFuncState, frame: &Frame, -) -> RS { - match frame.header().message_type() { - MessageType::Query | MessageType::Execute => { - let request = decode_client_request(frame)?; - Ok(DispatchFrameResult::Immediate(encode_server_response( - frame.header().request_id(), - &ServerResponse::new( - vec![], - vec![], - 0, - Some(format!( - "SQL interface is disabled in the client backend for app '{}'", - request.app_name() - )), - ), - )?)) - } - MessageType::Get => { - let request = decode_get_request(frame)?; - let value = worker.get_for_connection(conn_id, request.session_id(), request.key())?; - Ok(DispatchFrameResult::Immediate(encode_get_response( - frame.header().request_id(), - &GetResponse::new(value), - )?)) - } - MessageType::Put => { - let request = decode_put_request(frame)?; - let session_id = request.session_id(); - let (key, value) = request.into_parts(); - worker.put_for_connection(conn_id, session_id, key, value)?; - Ok(DispatchFrameResult::Immediate(encode_put_response( - frame.header().request_id(), - &PutResponse::new(true), - )?)) - } - MessageType::RangeScan => { - let request = decode_range_scan_request(frame)?; - let items = worker.range_for_connection( - conn_id, - request.session_id(), - request.start_key(), - request.end_key(), - )?; - Ok(DispatchFrameResult::Immediate(encode_range_scan_response( - frame.header().request_id(), - &RangeScanResponse::new( - items - .into_iter() - .map(|item| KeyValue::new(item.key, item.value)) - .collect(), - ), - )?)) - } - MessageType::ProcedureInvoke => { - let request = decode_procedure_invoke_request(frame)?; - let response = runtime.block_on(worker.handle_procedure_request(conn_id, &request))?; - Ok(DispatchFrameResult::Immediate( - encode_procedure_invoke_response(frame.header().request_id(), &response)?, - )) - } - MessageType::SessionCreate => { - let request = decode_session_create_request(frame)?; - let config = parse_session_open_config( - request.config_json(), - worker.worker_index(), - worker.worker_id(), - worker.registry().as_ref(), - )?; - if config.target_worker_index() == worker.worker_index() { - Ok(DispatchFrameResult::Immediate( - encode_session_create_response( - frame.header().request_id(), - &SessionCreateResponse::new( - worker.open_session_with_config(conn_id, config)?, - ), - )?, - )) - } else { - let action = SessionOpenTransferAction::new(frame.header().request_id(), config); - let session_ids = worker.prepare_connection_transfer(conn_id, Some(action))?; - Ok(DispatchFrameResult::Transfer { - target_worker: config.target_worker_index(), - session_ids, - action, - }) - } - } - MessageType::SessionClose => { - let request = decode_session_close_request(frame)?; - Ok(DispatchFrameResult::Immediate( - encode_session_close_response( - frame.header().request_id(), - &SessionCloseResponse::new( - worker.close_session(conn_id, request.session_id())?, - ), - )?, - )) - } - MessageType::Handshake | MessageType::Auth | MessageType::Response | MessageType::Error => { - Err(m_error!( - EC::ParseErr, - format!( - "unsupported client message type {:?}", - frame.header().message_type() - ) - )) +) -> RS> { + let request_id = frame.header().request_id(); + let worker = worker.clone(); + let frame = frame.clone(); + let mut future = Box::pin(async move { dispatch_frame_async(&worker, conn_id, &frame).await }); + let waker = waker(Arc::new(AsyncFuncTaskWaker::new( + 0, + Arc::new(SegQueue::new()), + Arc::new(AtomicBool::new(false)), + ))); + let mut cx = Context::from_waker(&waker); + match future.as_mut().poll(&mut cx) { + Poll::Ready(Ok(result)) => Ok(Some(result)), + Poll::Ready(Err(err)) => Err(err), + Poll::Pending => { + async_funcs.enqueue_future(conn_id, request_id, future); + Ok(None) } } } @@ -721,6 +782,7 @@ mod tests { use super::*; use mudu_contract::protocol::encode_get_request; use mudu_contract::protocol::GetRequest; + use mudu_contract::protocol::HEADER_LEN; #[test] fn try_decode_next_frame_waits_for_full_payload() { diff --git a/mudu_kernel/src/server/server_iouring.rs b/mudu_kernel/src/server/server_iouring.rs new file mode 100644 index 0000000..7735b82 --- /dev/null +++ b/mudu_kernel/src/server/server_iouring.rs @@ -0,0 +1,343 @@ +use crate::server::frame_dispatch::{dispatch_frame_async, try_decode_next_frame}; +use crate::server::server::IoUringTcpServerConfig; +use crate::server::worker::IoUringWorker; +use crate::server::worker_loop_stats::WorkerLoopStats; +use crate::server::worker_mailbox::WorkerMailboxMsg; +use crate::server::worker_ring_loop::WorkerRingLoop; +use crossbeam_queue::SegQueue; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use mudu_contract::protocol::Frame; +use mudu_utils::notifier::Waiter; +use std::os::fd::{IntoRawFd, RawFd}; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::{Arc, Condvar, Mutex}; +use tracing::debug; + +pub(crate) struct RecoveryCoordinator { + total_workers: usize, + state: Mutex, + condvar: Condvar, +} + +#[derive(Default)] +struct RecoveryState { + recovered_workers: usize, + failed: bool, +} + +pub(crate) fn sync_serve_iouring(mut cfg: IoUringTcpServerConfig, stop: Waiter) -> RS<()> { + if cfg.worker_count() == 0 { + return Err(m_error!(EC::ParseErr, "invalid io_uring worker count")); + } + let listen_addr: std::net::SocketAddr = format!("{}:{}", cfg.listen_ip(), cfg.listen_port()) + .parse() + .map_err(|e| m_error!(EC::ParseErr, "parse io_uring tcp listen address error", e))?; + let prebound_listener = cfg.take_prebound_listener(); + let conn_id_alloc = Arc::new(AtomicU64::new(1)); + let mailboxes: Vec<_> = (0..cfg.worker_count()) + .map(|_| Arc::new(SegQueue::::new())) + .collect(); + let mailbox_fds: Vec<_> = (0..cfg.worker_count()) + .map(|_| create_mailbox_event_fd()) + .collect::>>()?; + let stop_flag = Arc::new(AtomicBool::new(false)); + let recovery_coordinator = Arc::new(RecoveryCoordinator::new(cfg.worker_count())); + + let stop_for_notifier = stop.clone(); + let shutdown_mailboxes = mailboxes.clone(); + let shutdown_mailbox_fds = mailbox_fds.clone(); + let notifier_stop_flag = stop_flag.clone(); + let notifier = mudu_sys::task::spawn_thread_named("iouring-shutdown-notifier", move || { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|e| { + m_error!( + EC::TokioErr, + "create runtime for io_uring shutdown notifier error", + e + ) + })?; + runtime.block_on(stop_for_notifier.wait()); + notifier_stop_flag.store(true, Ordering::Relaxed); + for (mailbox, fd) in shutdown_mailboxes + .into_iter() + .zip(shutdown_mailbox_fds.into_iter()) + { + mailbox.push(WorkerMailboxMsg::Shutdown); + notify_mailbox_fd(fd)?; + } + debug!("notify shutdown"); + Ok(()) + })?; + + let mut handles = Vec::with_capacity(cfg.worker_count()); + for worker_id in 0..cfg.worker_count() { + let listen_addr = listen_addr; + let conn_id_alloc = conn_id_alloc.clone(); + let mailbox = mailboxes[worker_id].clone(); + let all_mailboxes = mailboxes.clone(); + let all_mailbox_fds = mailbox_fds.clone(); + let procedure_runtime = cfg.procedure_runtime_for_worker(worker_id); + let worker_identity = cfg + .worker_registry() + .worker(worker_id) + .cloned() + .ok_or_else(|| { + m_error!( + EC::NoSuchElement, + format!("missing worker identity {}", worker_id) + ) + })?; + let worker_registry = cfg.worker_registry(); + let routing_mode = cfg.routing_mode(); + let data_dir = cfg.data_dir().to_string(); + let log_dir = cfg.log_dir().to_string(); + let log_chunk_size = cfg.log_chunk_size(); + let log_batching = cfg.log_batching(); + let worker_count = cfg.worker_count(); + let listener = match &prebound_listener { + Some(listener) => Some( + listener + .try_clone() + .map_err(|e| m_error!(EC::NetErr, "clone tcp listener error", e))?, + ), + None => None, + }; + let stop = stop_flag.clone(); + let recovery_coordinator = recovery_coordinator.clone(); + let mailbox_fd = mailbox_fds[worker_id]; + let handle = mudu_sys::task::spawn_thread_named( + format!("iouring-ring-worker-{worker_id}"), + move || { + let listener_fd = match listener { + Some(listener) => listener.into_raw_fd(), + None => create_listener_fd(listen_addr)?, + }; + let worker = IoUringWorker::new_with_log_batching( + worker_identity, + worker_count, + routing_mode, + log_dir.clone(), + data_dir.clone(), + log_chunk_size, + log_batching, + procedure_runtime, + worker_registry, + )?; + let mut loop_state = WorkerRingLoop::new( + worker, + listener_fd, + mailbox_fd, + mailbox, + all_mailboxes, + all_mailbox_fds, + conn_id_alloc, + recovery_coordinator, + stop, + )?; + let r = loop_state.run(); + r + }, + )?; + handles.push(handle); + } + + let mut worker_stats = Vec::::with_capacity(cfg.worker_count()); + for handle in handles { + let result = handle + .join() + .map_err(|_| m_error!(EC::ThreadErr, "join io_uring worker error"))?; + worker_stats.push(result?); + } + + let notify_result = notifier + .join() + .map_err(|_| m_error!(EC::ThreadErr, "join io_uring shutdown notifier error"))?; + notify_result?; + + for fd in mailbox_fds { + unsafe { + libc::close(fd); + } + } + log_worker_stats(&worker_stats); + Ok(()) +} + +impl RecoveryCoordinator { + pub(crate) fn new(total_workers: usize) -> Self { + Self { + total_workers, + state: Mutex::new(RecoveryState::default()), + condvar: Condvar::new(), + } + } + + pub(crate) fn worker_succeeded(&self) -> RS<()> { + let mut state = self + .state + .lock() + .map_err(|_| m_error!(EC::InternalErr, "recovery coordinator lock poisoned"))?; + if state.failed { + return Err(m_error!( + EC::ThreadErr, + "worker recovery aborted because another worker failed" + )); + } + state.recovered_workers += 1; + if state.recovered_workers == self.total_workers { + self.condvar.notify_all(); + return Ok(()); + } + // Recovery must be complete on every worker before the service loop + // starts. If one worker fails recovery, wake everybody and abort + // instead of leaving the successful workers stuck forever. + while !state.failed && state.recovered_workers < self.total_workers { + state = self.condvar.wait(state).map_err(|_| { + m_error!( + EC::InternalErr, + "recovery coordinator condvar wait poisoned" + ) + })?; + } + if state.failed { + return Err(m_error!( + EC::ThreadErr, + "worker recovery aborted because another worker failed" + )); + } + Ok(()) + } + + pub(crate) fn worker_failed(&self) { + if let Ok(mut state) = self.state.lock() { + state.failed = true; + self.condvar.notify_all(); + } + } +} + +#[allow(dead_code)] +pub async fn dispatch_frame_iouring( + worker: &IoUringWorker, + conn_id: u64, + frame: &Frame, +) -> RS { + dispatch_frame_async(worker, conn_id, frame).await +} + +#[allow(dead_code)] +pub fn try_decode_next_frame_iouring(buf: &[u8]) -> RS> { + try_decode_next_frame(buf) +} + +fn create_listener_fd(listen_addr: std::net::SocketAddr) -> RS { + mudu_sys::net::create_tcp_listener_fd(listen_addr, 1024) +} + +pub fn set_connection_options(fd: RawFd) -> RS<()> { + mudu_sys::net::set_tcp_nodelay(fd) +} + +fn create_mailbox_event_fd() -> RS { + create_event_fd("create io_uring worker mailbox eventfd error") +} + +fn create_event_fd(message: &str) -> RS { + mudu_sys::sync::eventfd().map_err(|e| m_error!(EC::NetErr, message, e)) +} + +pub(super) fn notify_mailbox_fd(fd: RawFd) -> RS<()> { + notify_event_fd(fd, "write io_uring worker mailbox eventfd error") +} + +fn notify_event_fd(fd: RawFd, message: &str) -> RS<()> { + mudu_sys::sync::notify_eventfd(fd).map_err(|e| m_error!(EC::NetErr, message, e)) +} + +fn log_worker_stats(stats: &[WorkerLoopStats]) { + for stat in stats { + debug!( + "iouring worker stats: \n\ + worker={}, submit_calls={}, wait_cqe_calls={}, \n\ + accept_submit={}, mailbox_submit={}, recv_submit={}, send_submit={}, \ + log_write_submit={}, cqe_accept={}, cqe_mailbox={}, cqe_recv={}, cqe_send={}, \ + cqe_log_write={}, cqe_close={}, recv_queue_push={}, recv_queue_pop={}, \ + send_queue_push={}, send_queue_pop={}, mailbox_drained={}, local_register={}", + stat.worker_id, + stat.submit_calls, + stat.wait_cqe_calls, + stat.accept_submit, + stat.mailbox_submit, + stat.recv_submit, + stat.send_submit, + stat.log_write_submit, + stat.cqe_accept, + stat.cqe_mailbox, + stat.cqe_recv, + stat.cqe_send, + stat.cqe_log_write, + stat.cqe_close, + stat.recv_queue_push, + stat.recv_queue_pop, + stat.send_queue_push, + stat.send_queue_pop, + stat.mailbox_drained, + stat.local_register, + ); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::server::routing::ConnectionTransfer; + use crate::server::transferred_connection::TransferredConnection; + + #[test] + fn mailbox_eventfd_accumulates_wakeups() { + let fd = create_mailbox_event_fd().unwrap(); + notify_mailbox_fd(fd).unwrap(); + notify_mailbox_fd(fd).unwrap(); + + let value = mudu_sys::sync::read_eventfd(fd).unwrap(); + assert_eq!(value, 2); + + mudu_sys::sync::close_fd(fd).unwrap(); + } + + #[test] + fn mailbox_can_store_shutdown_and_transfer_messages() { + let mailbox = SegQueue::new(); + mailbox.push(WorkerMailboxMsg::AdoptConnection( + TransferredConnection::new( + ConnectionTransfer::new( + 11, + 1, + crate::server::fsm::ConnectionState::Accepted, + "127.0.0.1:9527".parse().unwrap(), + ), + -1, + Vec::new(), + None, + ), + )); + mailbox.push(WorkerMailboxMsg::Shutdown); + match mailbox.pop() { + Some(WorkerMailboxMsg::AdoptConnection(connection)) => { + assert_eq!(connection.transfer().conn_id(), 11); + assert_eq!(connection.transfer().target_worker(), 1); + } + other => panic!("unexpected first mailbox message: {other:?}"), + } + assert!(matches!(mailbox.pop(), Some(WorkerMailboxMsg::Shutdown))); + assert!(mailbox.pop().is_none()); + } +} + +pub fn sockaddr_to_socket_addr(storage: &mudu_sys::uring::SockAddrBuf) -> RS { + mudu_sys::net::sockaddr_to_socket_addr(storage) +} diff --git a/mudu_kernel/src/server/session.rs b/mudu_kernel/src/server/session.rs deleted file mode 100644 index 27810bb..0000000 --- a/mudu_kernel/src/server/session.rs +++ /dev/null @@ -1,208 +0,0 @@ -use crate::server::parser::Parser; -use std::cell::RefCell; - -use async_trait::async_trait; -use futures::Sink; -use pgwire::api::auth::md5pass::hash_md5_password; -use pgwire::api::auth::{AuthSource, LoginInfo, Password}; -use pgwire::api::portal::Portal; -use pgwire::api::query::{ExtendedQueryHandler, SimpleQueryHandler}; -use pgwire::api::results::{ - DescribePortalResponse, DescribeStatementResponse, QueryResponse, Response, Tag, -}; -use pgwire::api::stmt::{QueryParser, StoredStatement}; -use pgwire::api::store::PortalStore; -use pgwire::api::{ClientInfo, ClientPortalStore}; -use sql_parser::ast::stmt_list::StmtList; - -use crate::contract::ssn_ctx::SsnCtx; -use crate::sql::stmt_cmd_run::run_cmd_stmt; -use crate::sql::stmt_query_run::run_query_stmt; -use crate::x_engine::thd_ctx::ThdCtx; -use mudu::common::result::RS; -use mudu::common::xid::XID; -use mudu::error::ec::EC as ER; -use mudu::m_error; -use pgwire::error::{PgWireError, PgWireResult}; -use pgwire::messages::PgWireBackendMessage; -use sql_parser::ast::stmt_type::StmtType; -use std::fmt::{Debug, Formatter}; -use std::sync::Arc; - -/// session would be accessed in local thread -pub struct Session { - xid: RefCell>, - ctx: ThdCtx, - parser: Arc, -} - -impl Session { - pub fn new(ctx: ThdCtx) -> Self { - Self { - xid: RefCell::new(None), - ctx, - parser: Arc::new(Parser::new()), - } - } - - fn current_tx(&self) -> Option { - todo!() - } - - fn thd_ctx(&self) -> &ThdCtx { - &self.ctx - } -} - -pub struct DummyAuthSource; - -impl Debug for DummyAuthSource { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Ok(()) - } -} - -#[async_trait] -impl AuthSource for DummyAuthSource { - async fn get_password(&self, info: &LoginInfo) -> PgWireResult { - println!("login info: {:?}", info); - - let salt = vec![0, 0, 0, 0]; - let password = "root"; - - let hash_password = - hash_md5_password(info.user().as_ref().unwrap(), password, salt.as_ref()); - Ok(Password::new(Some(salt), hash_password.as_bytes().to_vec())) - } -} - -async fn do_query<'a>(stmt: &StmtType, ctx: &dyn SsnCtx) -> PgWireResult { - let response = match stmt { - StmtType::Command(stmt) => { - let rows = run_cmd_stmt(todo!(), ctx) - .await - .map_err(|e| PgWireError::ApiError(Box::new(e)))?; - Response::Execution(Tag::new("OK").with_rows(rows as usize)) - } - StmtType::Select(stmt) => { - let (fields, stream) = run_query_stmt(todo!(), ctx) - .await - .map_err(|e| PgWireError::ApiError(Box::new(e)))?; - Response::Query(QueryResponse::new(fields, stream)) - } - }; - Ok(response) -} - -#[async_trait] -impl SimpleQueryHandler for Session { - async fn do_query(&self, client: &mut C, query: &str) -> PgWireResult> - where - C: ClientInfo + ClientPortalStore + Sink + Unpin + Send + Sync, - C::Error: Debug, - PgWireError: From<>::Error>, - { - let query = self.parser.parse_sql::(client, query, &[]).await?; - let query_types = query.stmts(); - let mut result = vec![]; - for query_type in query_types { - let r = do_query(&query_type, self).await?; - result.push(r); - } - Ok(result) - } -} - -#[async_trait] -impl ExtendedQueryHandler for Session { - type Statement = Arc; - type QueryParser = Parser; - - fn query_parser(&self) -> Arc { - self.parser.clone() - } - - async fn do_describe_statement( - &self, - _client: &mut C, - target: &StoredStatement, - ) -> PgWireResult - where - C: ClientInfo + ClientPortalStore + Sink + Unpin + Send + Sync, - C::PortalStore: PortalStore, - C::Error: Debug, - PgWireError: From<>::Error>, - { - let root_stmt = target.statement.clone(); - - let query_type = todo!(); - - match query_type { - StmtType::Command(_stmt) => Ok(DescribeStatementResponse::new(vec![], vec![])), - StmtType::Select(q) => Ok(DescribeStatementResponse::new(vec![], vec![])), - } - } - - async fn do_describe_portal( - &self, - _client: &mut C, - _target: &Portal, - ) -> PgWireResult - where - C: ClientInfo + ClientPortalStore + Sink + Unpin + Send + Sync, - C::PortalStore: PortalStore, - C::Error: Debug, - PgWireError: From<>::Error>, - { - unimplemented!() - } - - async fn do_query( - &self, - client: &mut C, - portal: &Portal, - max_rows: usize, - ) -> PgWireResult - where - C: ClientInfo + ClientPortalStore + Sink + Unpin + Send + Sync, - C::PortalStore: PortalStore, - C::Error: Debug, - PgWireError: From<>::Error>, - { - let root_stmt = portal.statement.statement.clone(); - let r = do_query(todo!(), self).await?; - Ok(r) - } -} - -impl SsnCtx for Session { - fn current_tx(&self) -> Option { - self.xid.take() - } - - fn begin_tx(&self, xid: XID) -> RS<()> { - let mut x = self.xid.borrow_mut(); - match *x { - Some(id) => Err(m_error!( - ER::ExistingSuchElement, - format!("existing transaction in current session {}", id) - )), - None => { - *x = Some(xid); - Ok(()) - } - } - } - - fn end_tx(&self) -> RS<()> { - let mut x = self.xid.borrow_mut(); - if let Some(_id) = *x { - *x = None; - } - Ok(()) - } -} - -unsafe impl Send for Session {} - -unsafe impl Sync for Session {} diff --git a/mudu_kernel/src/server/session_bound_worker_runtime.rs b/mudu_kernel/src/server/session_bound_worker_runtime.rs new file mode 100644 index 0000000..be18adb --- /dev/null +++ b/mudu_kernel/src/server/session_bound_worker_runtime.rs @@ -0,0 +1,124 @@ +use crate::server::request_response_worker::{RequestResponseWorker, WorkerRuntimeRef}; +use crate::server::routing::{SessionOpenConfig, SessionOpenTransferAction}; +use crate::server::worker::IoUringWorker; +use crate::server::worker_local::{WorkerExecute, WorkerLocal, WorkerLocalRef}; +use crate::server::worker_registry::WorkerRegistry; +use crate::server::worker_snapshot::KvItem; +use async_trait::async_trait; +use mudu::common::id::OID; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use mudu_contract::protocol::{ProcedureInvokeRequest, ProcedureInvokeResponse}; +use std::sync::Arc; + +struct SessionBoundWorkerRuntime { + worker: Arc, + current_session_id: OID, +} + +pub(crate) fn new_session_bound_worker_runtime( + worker: IoUringWorker, + current_session_id: OID, +) -> WorkerRuntimeRef { + Arc::new(SessionBoundWorkerRuntime { + worker: Arc::new(worker), + current_session_id, + }) +} + +pub(crate) fn as_worker_local_ref(worker: WorkerRuntimeRef) -> WorkerLocalRef { + worker +} + +#[async_trait] +impl WorkerLocal for SessionBoundWorkerRuntime { + async fn open_async(&self) -> RS { + self.worker.open_session(self.current_session_id) + } + + async fn open_argv_async(&self, worker_id: OID) -> RS { + if worker_id == 0 || worker_id == self.worker.worker_id() { + self.open_async().await + } else { + Err(m_error!( + EC::NotImplemented, + format!( + "worker-local open cannot move from worker {} to worker {}", + self.worker.worker_id(), + worker_id + ) + )) + } + } + + async fn close_async(&self, session_id: OID) -> RS<()> { + self.worker.close_session_by_id(session_id) + } + + async fn execute_async(&self, session_id: OID, instruction: WorkerExecute) -> RS<()> { + self.worker.execute_tx_async(session_id, instruction).await + } + + async fn put_async(&self, session_id: OID, key: Vec, value: Vec) -> RS<()> { + self.worker + .put_in_session_async(session_id, key, value) + .await + } + + async fn delete_async(&self, session_id: OID, key: &[u8]) -> RS<()> { + self.worker.delete_in_session_async(session_id, key).await + } + + async fn get_async(&self, session_id: OID, key: &[u8]) -> RS>> { + self.worker.get_in_session(session_id, key) + } + + async fn range_async( + &self, + session_id: OID, + start_key: &[u8], + end_key: &[u8], + ) -> RS> { + self.worker.range_in_session(session_id, start_key, end_key) + } +} + +#[async_trait] +impl RequestResponseWorker for SessionBoundWorkerRuntime { + fn worker_index(&self) -> usize { + self.worker.worker_index() + } + + fn worker_id(&self) -> OID { + self.worker.worker_id() + } + + fn registry(&self) -> Arc { + self.worker.registry().clone() + } + + fn open_session_with_config(&self, conn_id: u64, config: SessionOpenConfig) -> RS { + self.worker.open_session_with_config(conn_id, config) + } + + fn prepare_connection_transfer( + &self, + conn_id: u64, + action: Option, + ) -> RS> { + self.worker.prepare_connection_transfer(conn_id, action) + } + + fn close_session_for_connection(&self, conn_id: u64, session_id: OID) -> RS { + self.worker.close_session(conn_id, session_id) + } + + async fn handle_procedure_request( + &self, + conn_id: u64, + request: &ProcedureInvokeRequest, + ) -> RS { + self.worker.handle_procedure_request(conn_id, request).await + } +} diff --git a/mudu_kernel/src/server/session_handle_task.rs b/mudu_kernel/src/server/session_handle_task.rs deleted file mode 100644 index 24cd5a6..0000000 --- a/mudu_kernel/src/server/session_handle_task.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::server::incoming_session::SSPReceiver; -use crate::x_engine::thd_ctx::ThdCtx; -use async_trait::async_trait; -use mudu::common::result::RS; -use mudu_utils::notifier::NotifyWait; -use mudu_utils::sync::a_task::ATask; -use mudu_utils::task::spawn_local_task; -use tracing::error; - -pub struct SessionHandleTask { - thd_ctx: ThdCtx, - name: String, - canceller: NotifyWait, - receiver: SSPReceiver, -} - -impl SessionHandleTask { - pub fn new( - thd_ctx: ThdCtx, - name: String, - receiver: SSPReceiver, - canceller: NotifyWait, - ) -> Self { - Self { - thd_ctx, - name, - canceller, - receiver, - } - } - - async fn serve_handle_connect(self) -> RS<()> { - let mut receiver = self.receiver; - let canceller = self.canceller; - loop { - let r = receiver.recv().await; - match r { - Some(p) => { - let c = canceller.clone(); - let t = self.thd_ctx.clone(); - let _ = spawn_local_task(c, "", async move { - let r = p.session_handler_task(t).await; - match r { - Ok(_) => {} - Err(e) => { - error!("handle session task error {}", e); - } - } - }); - } - None => { - break; - } - }; - } - Ok(()) - } -} -#[async_trait] -impl ATask for SessionHandleTask { - fn notifier(&self) -> NotifyWait { - self.canceller.clone() - } - fn name(&self) -> String { - self.name.clone() - } - - async fn run(self) -> RS<()> { - self.serve_handle_connect().await - } -} diff --git a/mudu_kernel/src/server/session_mgr.rs b/mudu_kernel/src/server/session_mgr.rs deleted file mode 100644 index bf514b0..0000000 --- a/mudu_kernel/src/server/session_mgr.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::server::session::{DummyAuthSource, Session}; -use crate::x_engine::thd_ctx::ThdCtx; -use pgwire::api::auth::md5pass::Md5PasswordAuthStartupHandler; -use pgwire::api::auth::{DefaultServerParameterProvider, StartupHandler}; -use pgwire::api::copy::CopyHandler; -use pgwire::api::query::{ExtendedQueryHandler, SimpleQueryHandler}; -use pgwire::api::{ErrorHandler, NoopHandler, PgWireServerHandlers}; -use std::sync::Arc; - -#[derive(Clone)] -pub struct SessionMgr { - ctx: ThdCtx, -} - -impl SessionMgr { - pub fn new(ctx: ThdCtx) -> Self { - Self { ctx } - } -} - -impl PgWireServerHandlers for SessionMgr { - fn simple_query_handler(&self) -> Arc { - Arc::new(Session::new(self.ctx.clone())) - } - - fn extended_query_handler(&self) -> Arc { - Arc::new(Session::new(self.ctx.clone())) - } - - fn startup_handler(&self) -> Arc { - Arc::new(Md5PasswordAuthStartupHandler::new( - Arc::new(DummyAuthSource), - Arc::new(DefaultServerParameterProvider::default()), - )) - } - - fn copy_handler(&self) -> Arc { - Arc::new(NoopHandler) - } - - fn error_handler(&self) -> Arc { - Arc::new(NoopHandler) - } -} diff --git a/mudu_kernel/src/server/task.rs b/mudu_kernel/src/server/task.rs new file mode 100644 index 0000000..7674354 --- /dev/null +++ b/mudu_kernel/src/server/task.rs @@ -0,0 +1,21 @@ +#[cfg(target_os = "linux")] +use crate::io::worker_ring::with_current_ring; +#[cfg(target_os = "linux")] +use crate::server::worker_task::WorkerTaskFuture; + + +#[cfg(target_os = "linux")] +#[allow(dead_code)] +pub fn spawn(conn_id: Option, future: WorkerTaskFuture) { + let _ = with_current_ring(|ring| { + ring.worker_task_registry().spawn(conn_id, future); + Ok(()) + }); +} + +#[cfg(not(target_os = "linux"))] +pub fn spawn(_conn_id: Option, _future: T) +where + T: Send + 'static, +{ +} diff --git a/mudu_kernel/src/server/task_registry.rs b/mudu_kernel/src/server/task_registry.rs new file mode 100644 index 0000000..9326d7f --- /dev/null +++ b/mudu_kernel/src/server/task_registry.rs @@ -0,0 +1,117 @@ +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; +use std::task::{Context, Poll}; + +use crossbeam_queue::SegQueue; +use futures::task::waker; +use mudu::common::result::RS; + +use crate::server::async_func_task_waker::AsyncFuncTaskWaker; +use crate::server::worker_task::{WorkerTask, WorkerTaskFuture}; + +pub(in crate::server) struct CompletedWorkerTask { + conn_id: Option, + is_system: bool, + result: RS<()>, +} + +pub(crate) struct WorkerTaskRegistry { + tasks: scc::HashMap, + ready_queue: Arc>, + completion_queue: Arc>, + op_registry: scc::HashMap, + next_task_id: AtomicU64, + next_op_id: AtomicU64, +} + +impl WorkerTaskRegistry { + pub fn new() -> Self { + Self { + tasks: scc::HashMap::new(), + ready_queue: Arc::new(SegQueue::new()), + completion_queue: Arc::new(SegQueue::new()), + op_registry: scc::HashMap::new(), + next_task_id: AtomicU64::new(1), + next_op_id: AtomicU64::new(1), + } + } + + pub(in crate::server) fn spawn(&self, conn_id: Option, future: WorkerTaskFuture) { + let task_id = self.next_task_id.fetch_add(1, Ordering::Relaxed); + let _ = self + .tasks + .insert_sync(task_id, WorkerTask::new(conn_id, future)); + self.ready_queue.push(task_id); + } + + #[allow(dead_code)] + pub(crate) fn spawn_system(&self, future: WorkerTaskFuture) { + self.spawn(None, future); + } + + pub(in crate::server) fn drain_completions(&self) { + while let Some(op_id) = self.completion_queue.pop() { + let Some((_, task_id)) = self.op_registry.remove_sync(&op_id) else { + continue; + }; + let Some(task) = self.tasks.get_sync(&task_id) else { + continue; + }; + if !task.queued().swap(true, Ordering::AcqRel) { + self.ready_queue.push(task_id); + } + } + } + + pub(in crate::server) fn poll_ready(&self) -> Vec { + let mut completed = Vec::new(); + while let Some(task_id) = self.ready_queue.pop() { + let Some((_, mut task)) = self.tasks.remove_sync(&task_id) else { + continue; + }; + task.clear_queued(); + if let Some(waiting_on) = task.take_waiting_on() { + let _ = self.op_registry.remove_sync(&waiting_on); + } + let op_id = self.next_op_id.fetch_add(1, Ordering::Relaxed); + + let waker = waker(Arc::new(AsyncFuncTaskWaker::new( + op_id, + self.completion_queue.clone(), + task.completed().clone(), + ))); + let mut cx = Context::from_waker(&waker); + match task.future_mut().poll(&mut cx) { + Poll::Ready(result) => completed.push(CompletedWorkerTask { + conn_id: task.conn_id(), + is_system: task.conn_id().is_none(), + result, + }), + Poll::Pending => { + task.set_waiting_on(op_id); + let _ = self.op_registry.insert_sync(op_id, task_id); + let _ = self.tasks.insert_sync(task_id, task); + } + } + } + completed + } + + pub(in crate::server) fn is_empty(&self) -> bool { + self.tasks.is_empty() + } +} + +impl CompletedWorkerTask { + pub(in crate::server) fn conn_id(&self) -> Option { + self.conn_id + } + + pub(in crate::server) fn is_system(&self) -> bool { + self.is_system + } + + pub(in crate::server) fn into_result(self) -> RS<()> { + self.result + } +} diff --git a/mudu_kernel/src/server/test_pg_cli.rs b/mudu_kernel/src/server/test_pg_cli.rs deleted file mode 100644 index 547ec2a..0000000 --- a/mudu_kernel/src/server/test_pg_cli.rs +++ /dev/null @@ -1,93 +0,0 @@ -#[cfg(test)] -pub mod test { - use postgres::{Client, NoTls}; - - use std::thread::sleep; - use std::time::Duration; - use tracing::{error, info}; - - enum TestResult { - Query(Result>, String>), - Command(Result), - } - pub struct TestSQL { - sql: String, - result: TestResult, - } - - impl TestSQL { - pub fn from_query(sql: String, result: Result>, String>) -> Self { - Self { - sql, - result: TestResult::Query(result), - } - } - - pub fn from_command(sql: String, result: Result) -> Self { - Self { - sql, - result: TestResult::Command(result), - } - } - } - - pub fn run_pg_client(pg_host: String, user: String, password: String, vec_sql: Vec) { - let mut client = loop { - let connect_str = format!("host={} user={} password={}", pg_host, user, password); - let r = Client::connect(&connect_str, NoTls); - match r { - Ok(c) => break c, - Err(e) => { - info!("{:?}, {:?}, {:?}", e, e.code(), e.as_db_error()); - sleep(Duration::from_millis(1000)); - } - } - }; - for stmt in vec_sql { - let sql = stmt.sql; - match stmt.result { - TestResult::Command(r_expected) => { - let r_executed = client.execute(&sql, &[]); - match (&r_executed, &r_expected) { - (Ok(rows_executed), Ok(rows_expected)) => { - assert_eq!(rows_executed, rows_expected); - info!("{} rows affected", rows_executed); - } - (Err(e), Err(_e)) => { - error!("{:?}, {:?}, {:?}", e, e.code(), e.as_db_error()); - } - _ => { - error!("mismatch result {:?}, {:?}", r_executed, r_expected); - panic!("error mismatch result"); - } - }; - } - TestResult::Query(r_expected) => { - let r_executed = client.query(&sql, &[]); - match (r_executed, r_expected) { - (Ok(rows_executed), Ok(rows_expected)) => { - let rows: Vec<_> = rows_executed - .iter() - .map(|row| { - let mut vec = vec![]; - for i in 0..row.len() { - let s: String = row.get(i); - vec.push(s); - } - vec - }) - .collect(); - println!("{:?} result rows", rows); - } - (Err(e), Err(_e)) => { - error!("{:?}, {:?}, {:?}", e, e.code(), e.as_db_error()); - } - _ => { - panic!("error mismatch result"); - } - } - } - } - } - } -} diff --git a/mudu_kernel/src/server/test_sql.rs b/mudu_kernel/src/server/test_sql.rs deleted file mode 100644 index d46a29a..0000000 --- a/mudu_kernel/src/server/test_sql.rs +++ /dev/null @@ -1,88 +0,0 @@ -#[cfg(test)] -mod test { - use crate::server::mudu_server::{MuduServer, MuduStop}; - use project_root::get_project_root; - - use crate::server::test_pg_cli::test::{run_pg_client, TestSQL}; - use mudu_utils::log::log_setup; - use std::thread::JoinHandle; - use tracing::info; - - //#[test] - #[allow(dead_code)] - fn run_test_sql() { - log_setup("debug"); - let (server, stop) = mudu_serve().unwrap(); - let cli = pg_client(stop); - server.join().unwrap(); - cli.join().unwrap(); - info!("run_test_sql test success"); - } - - fn mudu_serve() -> Result<(MuduServer, MuduStop), Box> { - log_setup("info"); - let mut cfg_path = get_project_root().unwrap(); - cfg_path.push("cfg/test.toml"); - let cfg_path = cfg_path.to_str().unwrap().to_string(); - let r_server = MuduServer::start(Some(cfg_path)); - let server = r_server.map_err(|e| Box::new(e))?; - Ok(server) - } - - fn pg_client(stop: MuduStop) -> JoinHandle<()> { - let thd = std::thread::spawn(move || _run_pg_client(stop)); - thd - } - - fn _run_pg_client(stop: MuduStop) { - let test_sql = vec![ - TestSQL::from_command( - r#" - CREATE TABLE T1( - C1 INT, - C2 INT, - C3 CHAR (20), - C4 INT, - C5 VARCHAR (25), - C6 INT, - PRIMARY KEY (C1, C2) - ); - "# - .to_string(), - Ok(0), - ), - TestSQL::from_command( - r#" - INSERT INTO T1 (C1,C2,C3,C4,C5,C6) - VALUES (1,1,'aaabbbccc1', - 1,'1323456',1); - "# - .to_string(), - Ok(1), - ), - TestSQL::from_command( - r#" - INSERT INTO T1 (C3,C4,C5,C6, C2, C1) - VALUES ('aaabbbccc2', - 2,'13234562',2, 2, 2); - "# - .to_string(), - Ok(1), - ), - TestSQL::from_command( - r#" - SELECT C4, C1, C2, C3, C2, C5 FROM T1 WHERE C1 = 1 AND C2 = 1; - "# - .to_string(), - Ok(1), - ), - ]; - run_pg_client( - "localhost".to_string(), - "postgres".to_string(), - "root".to_string(), - test_sql, - ); - stop.stop(); - } -} diff --git a/mudu_kernel/src/server_ur/transferred_connection.rs b/mudu_kernel/src/server/transferred_connection.rs similarity index 58% rename from mudu_kernel/src/server_ur/transferred_connection.rs rename to mudu_kernel/src/server/transferred_connection.rs index 775e30e..7648983 100644 --- a/mudu_kernel/src/server_ur/transferred_connection.rs +++ b/mudu_kernel/src/server/transferred_connection.rs @@ -1,9 +1,9 @@ -use crate::server_ur::routing::{ConnectionTransfer, SessionOpenTransferAction}; +use crate::server::routing::{ConnectionTransfer, SessionOpenTransferAction}; use mudu::common::id::OID; use std::os::fd::RawFd; #[derive(Debug)] -pub(in crate::server_ur) struct TransferredConnection { +pub(in crate::server) struct TransferredConnection { transfer: ConnectionTransfer, fd: RawFd, session_ids: Vec, @@ -11,7 +11,7 @@ pub(in crate::server_ur) struct TransferredConnection { } impl TransferredConnection { - pub(in crate::server_ur) fn new( + pub(in crate::server) fn new( transfer: ConnectionTransfer, fd: RawFd, session_ids: Vec, @@ -25,19 +25,19 @@ impl TransferredConnection { } } - pub(in crate::server_ur) fn transfer(&self) -> &ConnectionTransfer { + pub(in crate::server) fn transfer(&self) -> &ConnectionTransfer { &self.transfer } - pub(in crate::server_ur) fn fd(&self) -> RawFd { + pub(in crate::server) fn fd(&self) -> RawFd { self.fd } - pub(in crate::server_ur) fn session_ids(&self) -> &[OID] { + pub(in crate::server) fn session_ids(&self) -> &[OID] { &self.session_ids } - pub(in crate::server_ur) fn session_open_action(&self) -> Option { + pub(in crate::server) fn session_open_action(&self) -> Option { self.session_open_action } } diff --git a/mudu_kernel/src/server/worker.rs b/mudu_kernel/src/server/worker.rs new file mode 100644 index 0000000..d9647b0 --- /dev/null +++ b/mudu_kernel/src/server/worker.rs @@ -0,0 +1,1067 @@ +use crate::server::async_func_runtime::AsyncFuncInvokerPtr; +use crate::server::routing::{ + route_worker, RoutingContext, RoutingMode, SessionOpenConfig, SessionOpenTransferAction, +}; +use crate::server::session_bound_worker_runtime::{ + as_worker_local_ref, new_session_bound_worker_runtime, +}; +use crate::server::worker_local::{WorkerExecute, WorkerLocalRef}; +use crate::server::worker_registry::{WorkerIdentity, WorkerRegistry}; +use crate::server::worker_session_manager::{SessionContext, WorkerSessionManager}; +use crate::server::worker_snapshot::KvItem; +use crate::server::worker_tx_manager::WorkerTxManager; +use crate::server::x_contract::IoUringXContract; +use crate::wal::worker_log::{ChunkedWorkerLogBackend, WorkerLogBatching, WorkerLogLayout}; +use crate::wal::xl_batch::XLBatch; +use mudu::common::id::OID; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use mudu_contract::protocol::{ProcedureInvokeRequest, ProcedureInvokeResponse}; +use std::collections::BTreeMap; +use std::net::SocketAddr; +use std::sync::atomic::AtomicUsize; +use std::sync::Arc; + +#[derive(Clone)] +/// Per-worker execution context used by the `client` backend. +/// +/// The `IoUringWorker` name is also historical. The type is shared by both the +/// Linux native `io_uring` loop and the non-Linux fallback loop so upper +/// layers do not need target-specific worker abstractions. +pub struct IoUringWorker { + worker_index: usize, + worker_id: OID, + partition_ids: Vec, + worker_count: usize, + routing_mode: RoutingMode, + contract: Arc, + log_layout: WorkerLogLayout, + procedure_runtime: Option, + session_manager: Arc, + registry: Arc, +} + +impl IoUringWorker { + pub fn new( + identity: WorkerIdentity, + worker_count: usize, + routing_mode: RoutingMode, + log_dir: String, + data_dir: String, + log_chunk_size: u64, + procedure_runtime: Option, + registry: Arc, + ) -> RS { + Self::new_with_log_batching( + identity, + worker_count, + routing_mode, + log_dir, + data_dir, + log_chunk_size, + WorkerLogBatching::default(), + procedure_runtime, + registry, + ) + } + + pub fn new_with_log_batching( + identity: WorkerIdentity, + worker_count: usize, + routing_mode: RoutingMode, + log_dir: String, + data_dir: String, + log_chunk_size: u64, + log_batching: WorkerLogBatching, + procedure_runtime: Option, + registry: Arc, + ) -> RS { + let active_sessions = Arc::new(AtomicUsize::new(0)); + let partition_id = identity.partition_ids.first().copied().ok_or_else(|| { + m_error!( + EC::ParseErr, + format!("worker {} has no partition ids", identity.worker_id) + ) + })?; + let worker_id = identity.worker_id; + let log_layout = + WorkerLogLayout::new(log_dir, worker_id, log_chunk_size)?.with_batching(log_batching); + let log = ChunkedWorkerLogBackend::new_with_active_sessions( + log_layout.clone(), + active_sessions.clone(), + )?; + Ok(Self { + worker_index: identity.worker_index, + worker_id, + partition_ids: identity.partition_ids, + worker_count, + routing_mode, + contract: Arc::new(IoUringXContract::with_worker_log_and_data_dir( + log, + partition_id, + data_dir, + )), + log_layout, + procedure_runtime, + session_manager: Arc::new(WorkerSessionManager::new(active_sessions)), + registry, + }) + } + + pub fn route_connection(&self, conn_id: u64, remote_addr: SocketAddr) -> usize { + let ctx = RoutingContext::new(conn_id, remote_addr, None); + route_worker(&ctx, self.routing_mode, self.worker_count) + } + + pub async fn delete_async(&self, key: &[u8]) -> RS<()> { + self.contract.worker_delete_async(key).await + } + + pub fn get(&self, key: &[u8]) -> RS>> { + self.contract.worker_get(key) + } + + pub async fn invoke_procedure( + &self, + session_id: OID, + procedure_name: &str, + procedure_parameters: Vec, + worker_local: WorkerLocalRef, + ) -> RS> { + let procedure_runtime = self + .procedure_runtime + .as_ref() + .ok_or_else(|| m_error!(EC::NotImplemented, "procedure runtime is not configured"))?; + procedure_runtime + .invoke( + session_id, + procedure_name, + procedure_parameters, + worker_local, + ) + .await + } + + pub fn create_session(&self, conn_id: u64) -> RS { + self.session_manager.create_session(conn_id) + } + + pub fn close_session(&self, conn_id: u64, session_id: OID) -> RS { + self.session_manager.close_session(conn_id, session_id) + } + + pub fn close_connection_sessions(&self, conn_id: u64) -> RS<()> { + self.session_manager.close_connection_sessions(conn_id) + } + + pub fn open_session(&self, session_id: OID) -> RS { + self.session_manager.open_session(session_id) + } + + pub fn close_session_by_id(&self, session_id: OID) -> RS<()> { + self.session_manager.close_session_by_id(session_id) + } + + fn session_context(&self, session_id: OID) -> RS> { + self.session_manager.session_context(session_id) + } + + pub fn get_for_connection( + &self, + conn_id: u64, + session_id: OID, + key: &[u8], + ) -> RS>> { + self.ensure_session_owned_by_connection(conn_id, session_id)?; + self.get_in_session(session_id, key) + } + + pub fn put_for_connection( + &self, + conn_id: u64, + session_id: OID, + key: Vec, + value: Vec, + ) -> RS<()> { + self.ensure_session_owned_by_connection(conn_id, session_id)?; + self.put_in_session(session_id, key, value) + } + + pub async fn put_for_connection_async( + &self, + conn_id: u64, + session_id: OID, + key: Vec, + value: Vec, + ) -> RS<()> { + self.ensure_session_owned_by_connection(conn_id, session_id)?; + self.put_in_session_async(session_id, key, value).await + } + + pub fn range_for_connection( + &self, + conn_id: u64, + session_id: OID, + start_key: &[u8], + end_key: &[u8], + ) -> RS> { + self.ensure_session_owned_by_connection(conn_id, session_id)?; + self.range_in_session(session_id, start_key, end_key) + } + + #[allow(dead_code)] + fn execute_tx(&self, session_id: OID, instruction: WorkerExecute) -> RS<()> { + let session = self.session_context(session_id)?; + match instruction { + WorkerExecute::BeginTx => { + if session.tx_manager_ref().is_some() { + return Err(m_error!( + EC::ExistingSuchElement, + format!("session {} already has an active transaction", session_id) + )); + } + session + .set_tx_manager(Some(WorkerTxManager::new(self.contract.worker_begin_tx()?))); + Ok(()) + } + WorkerExecute::CommitTx => { + let tx_manager = session.take_tx_manager().ok_or_else(|| { + m_error!( + EC::NoSuchElement, + format!("session {} has no active transaction", session_id) + ) + })?; + let snapshot = tx_manager.snapshot().clone(); + let xid = tx_manager.xid(); + let items = tx_manager.staged_put_items(); + let batch = tx_manager.into_xl_batch(); + self.contract + .worker_commit_put_batch(&snapshot, xid, items, batch)?; + Ok(()) + } + WorkerExecute::RollbackTx => { + let tx_manager = session.take_tx_manager().ok_or_else(|| { + m_error!( + EC::NoSuchElement, + format!("session {} has no active transaction", session_id) + ) + })?; + self.contract.worker_rollback_tx(tx_manager.xid())?; + Ok(()) + } + } + } + + pub(crate) async fn execute_tx_async( + &self, + session_id: OID, + instruction: WorkerExecute, + ) -> RS<()> { + let session = self.session_context(session_id)?; + match instruction { + WorkerExecute::BeginTx => { + if session.tx_manager_ref().is_some() { + return Err(m_error!( + EC::ExistingSuchElement, + format!("session {} already has an active transaction", session_id) + )); + } + session + .set_tx_manager(Some(WorkerTxManager::new(self.contract.worker_begin_tx()?))); + Ok(()) + } + WorkerExecute::CommitTx => { + let tx_manager = session.take_tx_manager().ok_or_else(|| { + m_error!( + EC::NoSuchElement, + format!("session {} has no active transaction", session_id) + ) + })?; + let snapshot = tx_manager.snapshot().clone(); + let xid = tx_manager.xid(); + let items = tx_manager.staged_put_items(); + let batch = tx_manager.into_xl_batch(); + self.contract + .worker_commit_put_batch_async(&snapshot, xid, items, batch) + .await + } + WorkerExecute::RollbackTx => { + let tx_manager = session.take_tx_manager().ok_or_else(|| { + m_error!( + EC::NoSuchElement, + format!("session {} has no active transaction", session_id) + ) + })?; + self.contract.worker_rollback_tx(tx_manager.xid())?; + Ok(()) + } + } + } + + fn put_in_session(&self, session_id: OID, key: Vec, value: Vec) -> RS<()> { + let session = self.session_context(session_id)?; + match session.tx_manager_mut().as_mut() { + Some(tx_manager) => { + tx_manager.put(key, value); + Ok(()) + } + None => self.contract.worker_put(key, value), + } + } + + pub(crate) async fn put_in_session_async( + &self, + session_id: OID, + key: Vec, + value: Vec, + ) -> RS<()> { + let session = self.session_context(session_id)?; + match session.tx_manager_mut().as_mut() { + Some(tx_manager) => { + tx_manager.put(key, value); + Ok(()) + } + None => self.contract.worker_put_async(key, value).await, + } + } + + pub(crate) async fn delete_in_session_async(&self, session_id: OID, key: &[u8]) -> RS<()> { + let session = self.session_context(session_id)?; + match session.tx_manager_mut().as_mut() { + Some(tx_manager) => { + tx_manager.delete(key.to_vec()); + Ok(()) + } + None => self.contract.worker_delete_async(key).await, + } + } + + pub(crate) fn get_in_session(&self, session_id: OID, key: &[u8]) -> RS>> { + let session = self.session_context(session_id)?; + let staged = session + .tx_manager_ref() + .as_ref() + .and_then(|tx_manager| tx_manager.get(key)); + match staged { + Some(value) => Ok(value), + None => match session.tx_manager_ref().as_ref() { + Some(tx_manager) => self + .contract + .worker_get_with_snapshot(tx_manager.snapshot(), key), + None => self.contract.worker_get(key), + }, + } + } + + pub(crate) fn range_in_session( + &self, + session_id: OID, + start_key: &[u8], + end_key: &[u8], + ) -> RS> { + let session = self.session_context(session_id)?; + let staged = session + .tx_manager_ref() + .as_ref() + .map(|tx_manager| tx_manager.staged_items_in_range(start_key, end_key)) + .unwrap_or_default(); + + let mut merged = BTreeMap::new(); + let base_items = match session.tx_manager_ref().as_ref() { + Some(tx_manager) => self.contract.worker_range_scan_with_snapshot( + tx_manager.snapshot(), + start_key, + end_key, + )?, + None => self.contract.worker_range_scan(start_key, end_key)?, + }; + for item in base_items { + merged.insert(item.key, Some(item.value)); + } + for (key, value) in staged { + merged.insert(key, value); + } + Ok(merged + .into_iter() + .filter_map(|(key, value)| value.map(|value| KvItem { key, value })) + .collect()) + } + + fn ensure_session_owned_by_connection(&self, conn_id: u64, session_id: OID) -> RS<()> { + self.session_manager + .ensure_session_owned_by_connection(conn_id, session_id) + } + + pub async fn handle_procedure_request( + &self, + conn_id: u64, + request: &ProcedureInvokeRequest, + ) -> RS { + let session_id = request.session_id() as OID; + self.ensure_session_owned_by_connection(conn_id, session_id)?; + let worker_local = + as_worker_local_ref(new_session_bound_worker_runtime(self.clone(), session_id)); + let result = self + .invoke_procedure( + session_id, + request.procedure_name(), + request.procedure_parameters_owned(), + worker_local, + ) + .await?; + Ok(ProcedureInvokeResponse::new(result)) + } + + pub fn worker_index(&self) -> usize { + self.worker_index + } + + pub fn worker_id(&self) -> OID { + self.worker_id + } + + pub fn partition_ids(&self) -> &[OID] { + &self.partition_ids + } + + pub fn worker_count(&self) -> usize { + self.worker_count + } + + pub fn registry(&self) -> &Arc { + &self.registry + } + + pub fn log_layout(&self) -> WorkerLogLayout { + self.log_layout.clone() + } + + pub fn worker_log(&self) -> Option { + self.contract.worker_log() + } + + pub fn replay_log_batch(&self, batch: XLBatch) -> RS<()> { + self.contract.replay_worker_log_batch(batch) + } + + pub fn open_session_with_config(&self, conn_id: u64, config: SessionOpenConfig) -> RS { + if config.target_worker_index() != self.worker_index() + || config.worker_id() != self.worker_id() + { + return Err(m_error!( + EC::InternalErr, + format!( + "session open landed on worker index {} worker id {}, expected worker index {} worker id {}", + self.worker_index(), + self.worker_id(), + config.target_worker_index(), + config.worker_id() + ) + )); + } + if config.session_id() == 0 { + self.create_session(conn_id) + } else { + self.ensure_session_owned_by_connection(conn_id, config.session_id())?; + Ok(config.session_id()) + } + } + + pub fn prepare_connection_transfer( + &self, + conn_id: u64, + action: Option, + ) -> RS> { + if self.connection_has_active_tx(conn_id)? { + return Err(m_error!( + EC::TxErr, + format!( + "connection {} cannot be transferred while a session transaction is active", + conn_id + ) + )); + } + if let Some(action) = action { + let config = action.config(); + if config.session_id() != 0 { + self.ensure_session_owned_by_connection(conn_id, config.session_id())?; + } + } + self.session_manager.detach_connection_sessions(conn_id) + } + + pub fn adopt_connection_sessions(&self, conn_id: u64, session_ids: &[OID]) -> RS<()> { + self.session_manager + .adopt_connection_sessions(conn_id, session_ids) + } + + fn connection_has_active_tx(&self, conn_id: u64) -> RS { + self.session_manager.connection_has_active_tx(conn_id) + } +} + +#[allow(dead_code)] +fn worker_log_oid(worker_id: usize) -> OID { + worker_id as u128 + 1 +} + +#[allow(dead_code)] +fn is_key_in_range(key: &[u8], start_key: &[u8], end_key: &[u8]) -> bool { + key >= start_key && (end_key.is_empty() || key < end_key) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::contract::meta_mgr::MetaMgr; + use crate::contract::schema_column::SchemaColumn; + use crate::contract::schema_table::SchemaTable; + use crate::contract::table_desc::TableDesc; + use crate::contract::table_info::TableInfo; + use crate::server::async_func_runtime::AsyncFuncInvoker; + use crate::server::session_bound_worker_runtime::new_session_bound_worker_runtime; + use crate::server::worker_local::{WorkerExecute, WorkerLocal}; + use crate::server::worker_registry::{load_or_create_worker_registry, WorkerRegistry}; + use crate::storage::time_series::time_series_file::TimeSeriesFile; + use crate::x_engine::api::XContract; + use async_trait::async_trait; + use futures::FutureExt; + use mudu::common::id::gen_oid; + use mudu_type::dat_type_id::DatTypeID; + use mudu_type::dt_info::DTInfo; + use std::collections::HashMap; + use std::env::temp_dir; + use std::sync::{Arc, Mutex}; + + #[derive(Default)] + struct RecordingProcedureRuntime { + calls: Mutex)>>, + } + + #[async_trait] + impl AsyncFuncInvoker for RecordingProcedureRuntime { + async fn invoke( + &self, + session_id: OID, + procedure_name: &str, + procedure_parameters: Vec, + _worker_local: WorkerLocalRef, + ) -> RS> { + self.calls.lock().unwrap().push(( + session_id, + procedure_name.to_string(), + procedure_parameters.clone(), + )); + Ok(procedure_parameters) + } + } + + struct TestMetaMgr { + tables: Mutex>>, + } + + impl TestMetaMgr { + fn new() -> Self { + Self { + tables: Mutex::new(HashMap::new()), + } + } + } + + #[async_trait] + impl MetaMgr for TestMetaMgr { + async fn get_table_by_id(&self, oid: OID) -> RS> { + self.tables + .lock() + .unwrap() + .get(&oid) + .cloned() + .ok_or_else(|| m_error!(EC::NoSuchElement, format!("no such table {}", oid))) + } + + async fn get_table_by_name(&self, name: &String) -> RS>> { + Ok(self + .tables + .lock() + .unwrap() + .values() + .find(|table| table.name() == name) + .cloned()) + } + + async fn create_table(&self, schema: &SchemaTable) -> RS<()> { + let table = TableInfo::new(schema.clone())?.table_desc()?; + self.tables.lock().unwrap().insert(schema.id(), table); + Ok(()) + } + + async fn drop_table(&self, table_id: OID) -> RS<()> { + self.tables.lock().unwrap().remove(&table_id); + Ok(()) + } + } + + fn test_registry(worker_count: usize) -> (String, Arc) { + let dir = temp_dir() + .join(format!("worker_test_{}", gen_oid())) + .to_string_lossy() + .into_owned(); + let registry = load_or_create_worker_registry(&dir, worker_count).unwrap(); + (dir, registry) + } + + fn test_worker( + worker_index: usize, + worker_count: usize, + log_dir: &str, + data_dir: &str, + registry: Arc, + procedure_runtime: Option, + ) -> IoUringWorker { + let identity = registry.worker(worker_index).cloned().unwrap(); + IoUringWorker::new( + identity, + worker_count, + RoutingMode::ConnectionId, + log_dir.to_string(), + data_dir.to_string(), + 4096, + procedure_runtime, + registry, + ) + .unwrap() + } + + fn test_schema() -> SchemaTable { + SchemaTable::new( + "t".to_string(), + vec![SchemaColumn::new( + "id".to_string(), + DatTypeID::I32, + DTInfo::from_text(DatTypeID::I32, String::new()), + )], + vec![SchemaColumn::new( + "v".to_string(), + DatTypeID::I32, + DTInfo::from_text(DatTypeID::I32, String::new()), + )], + ) + } + + #[tokio::test] + async fn worker_invokes_configured_procedure_runtime() { + let runtime = Arc::new(RecordingProcedureRuntime::default()); + let (log_dir, registry) = test_registry(1); + let worker = test_worker(0, 1, &log_dir, &log_dir, registry, Some(runtime.clone())); + + let response = worker + .handle_procedure_request( + 11, + &ProcedureInvokeRequest::new(9, "app/mod/proc", b"payload".to_vec()), + ) + .await + .unwrap_err(); + assert!(response.to_string().contains("does not exist")); + + let session_id = worker.create_session(11).unwrap(); + let response = worker + .handle_procedure_request( + 11, + &ProcedureInvokeRequest::new(session_id, "app/mod/proc", b"payload".to_vec()), + ) + .await + .unwrap(); + assert_eq!(response.into_result(), b"payload".to_vec()); + + let calls = runtime.calls.lock().unwrap(); + assert_eq!(calls.len(), 1); + assert_eq!(calls[0].0, session_id); + assert_eq!(calls[0].1, "app/mod/proc"); + assert_eq!(calls[0].2, b"payload".to_vec()); + } + + #[test] + fn worker_session_lifecycle_is_connection_scoped() { + let (log_dir, registry) = test_registry(1); + let worker = test_worker(0, 1, &log_dir, &log_dir, registry, None); + + let session_id = worker.create_session(7).unwrap(); + assert!(worker.close_session(7, session_id).unwrap()); + + let session_id = worker.create_session(7).unwrap(); + let err = worker.close_session(8, session_id).unwrap_err(); + assert!(err.to_string().contains("does not belong to connection 8")); + + worker.close_connection_sessions(7).unwrap(); + let err = worker + .handle_procedure_request( + 7, + &ProcedureInvokeRequest::new(session_id, "app/mod/proc", b"payload".to_vec()), + ) + .now_or_never() + .unwrap() + .unwrap_err(); + assert!(err.to_string().contains("does not exist")); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_implements_worker_local_interface() { + let (log_dir, registry) = test_registry(1); + let worker = test_worker(0, 1, &log_dir, &log_dir, registry, None); + + let session_id = worker.create_session(1).unwrap(); + let local = new_session_bound_worker_runtime(worker.clone(), session_id); + let local: &dyn WorkerLocal = local.as_ref(); + let opened = local.open_async().await.unwrap(); + local + .execute_async(opened, WorkerExecute::BeginTx) + .await + .unwrap(); + local + .put_async(opened, b"a".to_vec(), b"1".to_vec()) + .await + .unwrap(); + local + .put_async(opened, b"b".to_vec(), b"2".to_vec()) + .await + .unwrap(); + + assert_eq!( + local.get_async(opened, b"a").await.unwrap(), + Some(b"1".to_vec()) + ); + assert_eq!( + local.range_async(opened, b"a", b"z").await.unwrap().len(), + 2 + ); + local + .execute_async(opened, WorkerExecute::CommitTx) + .await + .unwrap(); + assert_eq!(worker.get(b"a").unwrap(), Some(b"1".to_vec())); + local.close_async(opened).await.unwrap(); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_rollback_discards_staged_writes() { + let (log_dir, registry) = test_registry(1); + let worker = test_worker(0, 1, &log_dir, &log_dir, registry, None); + + let session_id = worker.create_session(1).unwrap(); + let local = new_session_bound_worker_runtime(worker.clone(), session_id); + let local: &dyn WorkerLocal = local.as_ref(); + + local + .execute_async(session_id, WorkerExecute::BeginTx) + .await + .unwrap(); + local + .put_async(session_id, b"a".to_vec(), b"1".to_vec()) + .await + .unwrap(); + assert_eq!( + local.get_async(session_id, b"a").await.unwrap(), + Some(b"1".to_vec()) + ); + local + .execute_async(session_id, WorkerExecute::RollbackTx) + .await + .unwrap(); + + assert_eq!(local.get_async(session_id, b"a").await.unwrap(), None); + assert_eq!(worker.get(b"a").unwrap(), None); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_delete_removes_visible_value() { + let (log_dir, registry) = test_registry(1); + let worker = test_worker(0, 1, &log_dir, &log_dir, registry, None); + + let session_id = worker.create_session(1).unwrap(); + let local = new_session_bound_worker_runtime(worker.clone(), session_id); + let local: &dyn WorkerLocal = local.as_ref(); + + local + .put_async(session_id, b"a".to_vec(), b"1".to_vec()) + .await + .unwrap(); + assert_eq!( + local.get_async(session_id, b"a").await.unwrap(), + Some(b"1".to_vec()) + ); + local.delete_async(session_id, b"a").await.unwrap(); + + assert_eq!(local.get_async(session_id, b"a").await.unwrap(), None); + assert_eq!(worker.get(b"a").unwrap(), None); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_storage_uses_worker_partition_id_for_relation_files() { + let (log_dir, registry) = test_registry(1); + let identity = registry.worker(0).cloned().unwrap(); + let partition_id = identity.partition_ids[0]; + let _worker = IoUringWorker::new( + identity, + 1, + RoutingMode::ConnectionId, + log_dir.clone(), + log_dir.clone(), + 4096, + None, + registry, + ) + .unwrap(); + let contract = IoUringXContract::with_log_and_data_dir( + Arc::new(TestMetaMgr::new()), + None, + partition_id, + log_dir.clone(), + ); + let schema = test_schema(); + let table_id = schema.id(); + contract.create_table(0, &schema).await.unwrap(); + + let key_path = TimeSeriesFile::relation_file_path(&log_dir, partition_id, table_id, 0); + let value_path = TimeSeriesFile::relation_file_path(&log_dir, partition_id, table_id, 1); + assert!( + key_path.exists(), + "missing relation key file {:?}", + key_path + ); + assert!( + value_path.exists(), + "missing relation value file {:?}", + value_path + ); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_delete_inside_tx_is_visible_to_same_session_only_after_commit() { + let (log_dir, registry) = test_registry(1); + let worker = test_worker(0, 1, &log_dir, &log_dir, registry, None); + + let session_a = worker.create_session(1).unwrap(); + let session_b = worker.create_session(2).unwrap(); + let local_a = new_session_bound_worker_runtime(worker.clone(), session_a); + let local_b = new_session_bound_worker_runtime(worker.clone(), session_b); + local_b + .put_async(session_b, b"k".to_vec(), b"v".to_vec()) + .await + .unwrap(); + + worker + .execute_tx(session_a, WorkerExecute::BeginTx) + .unwrap(); + local_a.delete_async(session_a, b"k").await.unwrap(); + + assert_eq!(local_a.get_async(session_a, b"k").await.unwrap(), None); + assert_eq!( + local_b.get_async(session_b, b"k").await.unwrap(), + Some(b"v".to_vec()) + ); + + worker + .execute_tx(session_a, WorkerExecute::CommitTx) + .unwrap(); + + assert_eq!(worker.get(b"k").unwrap(), None); + assert_eq!(local_b.get_async(session_b, b"k").await.unwrap(), None); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_async_put_persists_value() { + let (log_dir, registry) = test_registry(1); + let worker = test_worker(0, 1, &log_dir, &log_dir, registry, None); + + let session_id = worker.create_session(1).unwrap(); + let local = new_session_bound_worker_runtime(worker.clone(), session_id); + let local: &dyn WorkerLocal = local.as_ref(); + + local + .put_async(session_id, b"a".to_vec(), b"1".to_vec()) + .await + .unwrap(); + assert_eq!( + local.get_async(session_id, b"a").await.unwrap(), + Some(b"1".to_vec()) + ); + assert_eq!(worker.get(b"a").unwrap(), Some(b"1".to_vec())); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_async_execute_commits_transaction() { + let (log_dir, registry) = test_registry(1); + let worker = test_worker(0, 1, &log_dir, &log_dir, registry, None); + + let session_id = worker.create_session(1).unwrap(); + let local = new_session_bound_worker_runtime(worker.clone(), session_id); + let local: &dyn WorkerLocal = local.as_ref(); + + local + .execute_async(session_id, WorkerExecute::BeginTx) + .await + .unwrap(); + local + .put_async(session_id, b"a".to_vec(), b"1".to_vec()) + .await + .unwrap(); + local + .execute_async(session_id, WorkerExecute::CommitTx) + .await + .unwrap(); + + assert_eq!(worker.get(b"a").unwrap(), Some(b"1".to_vec())); + } + + #[test] + fn worker_can_transfer_connection_sessions_between_partitions() { + let (log_dir, registry) = test_registry(2); + let source = test_worker(0, 2, &log_dir, &log_dir, registry.clone(), None); + let target = test_worker(1, 2, &log_dir, &log_dir, registry.clone(), None); + + let conn_id = 41; + let session_a = source.create_session(conn_id).unwrap(); + let session_b = source.create_session(conn_id).unwrap(); + let target_identity = registry.worker(1).unwrap(); + let action = SessionOpenTransferAction::new( + 7, + SessionOpenConfig::new(session_a, target_identity.worker_id, 1), + ); + + let transferred = source + .prepare_connection_transfer(conn_id, Some(action)) + .unwrap(); + assert_eq!(transferred.len(), 2); + assert!(source.get_for_connection(conn_id, session_a, b"k").is_err()); + + target + .adopt_connection_sessions(conn_id, &transferred) + .unwrap(); + assert_eq!( + target + .open_session_with_config(conn_id, action.config()) + .unwrap(), + session_a + ); + target + .put_for_connection(conn_id, session_b, b"k".to_vec(), b"v".to_vec()) + .unwrap(); + assert_eq!( + target.get_for_connection(conn_id, session_b, b"k").unwrap(), + Some(b"v".to_vec()) + ); + } + + #[test] + fn worker_rejects_transfer_with_active_transaction() { + let (log_dir, registry) = test_registry(2); + let worker = test_worker(0, 2, &log_dir, &log_dir, registry.clone(), None); + let conn_id = 51; + let session_id = worker.create_session(conn_id).unwrap(); + worker + .execute_tx(session_id, WorkerExecute::BeginTx) + .unwrap(); + + let err = worker + .prepare_connection_transfer( + conn_id, + Some(SessionOpenTransferAction::new( + 1, + SessionOpenConfig::new(session_id, registry.worker(1).unwrap().worker_id, 1), + )), + ) + .unwrap_err(); + assert!(err.to_string().contains("cannot be transferred")); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_snapshot_isolation_hides_later_commits_from_existing_tx() { + let (log_dir, registry) = test_registry(1); + let worker = test_worker(0, 1, &log_dir, &log_dir, registry, None); + + let session_a = worker.create_session(1).unwrap(); + let session_b = worker.create_session(2).unwrap(); + worker + .execute_tx(session_a, WorkerExecute::BeginTx) + .unwrap(); + let local_a = new_session_bound_worker_runtime(worker.clone(), session_a); + let local_b = new_session_bound_worker_runtime(worker.clone(), session_b); + local_b + .put_async(session_b, b"k".to_vec(), b"v1".to_vec()) + .await + .unwrap(); + + assert_eq!(local_a.get_async(session_a, b"k").await.unwrap(), None); + assert_eq!( + local_b.get_async(session_b, b"k").await.unwrap(), + Some(b"v1".to_vec()) + ); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_snapshot_isolation_range_stays_stable_for_existing_tx() { + let (log_dir, registry) = test_registry(1); + let worker = test_worker(0, 1, &log_dir, &log_dir, registry, None); + + let session_a = worker.create_session(1).unwrap(); + let session_b = worker.create_session(2).unwrap(); + let local_a = new_session_bound_worker_runtime(worker.clone(), session_a); + let local_b = new_session_bound_worker_runtime(worker.clone(), session_b); + local_b + .put_async(session_b, b"a".to_vec(), b"1".to_vec()) + .await + .unwrap(); + worker + .execute_tx(session_a, WorkerExecute::BeginTx) + .unwrap(); + local_b + .put_async(session_b, b"b".to_vec(), b"2".to_vec()) + .await + .unwrap(); + + let rows = local_a.range_async(session_a, b"a", b"z").await.unwrap(); + assert_eq!( + rows, + vec![KvItem { + key: b"a".to_vec(), + value: b"1".to_vec() + }] + ); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_first_committer_wins_without_locks() { + let (log_dir, registry) = test_registry(1); + let worker = test_worker(0, 1, &log_dir, &log_dir, registry, None); + + let session_a = worker.create_session(1).unwrap(); + let session_b = worker.create_session(2).unwrap(); + worker + .execute_tx(session_a, WorkerExecute::BeginTx) + .unwrap(); + worker + .execute_tx(session_b, WorkerExecute::BeginTx) + .unwrap(); + let local_a = new_session_bound_worker_runtime(worker.clone(), session_a); + let local_b = new_session_bound_worker_runtime(worker.clone(), session_b); + local_a + .put_async(session_a, b"k".to_vec(), b"v1".to_vec()) + .await + .unwrap(); + local_b + .put_async(session_b, b"k".to_vec(), b"v2".to_vec()) + .await + .unwrap(); + + worker + .execute_tx(session_a, WorkerExecute::CommitTx) + .unwrap(); + let err = worker + .execute_tx(session_b, WorkerExecute::CommitTx) + .unwrap_err(); + + assert!(err.to_string().contains("write-write conflict")); + assert_eq!(worker.get(b"k").unwrap(), Some(b"v1".to_vec())); + } +} diff --git a/mudu_kernel/src/server/worker_local.rs b/mudu_kernel/src/server/worker_local.rs new file mode 100644 index 0000000..afa2a03 --- /dev/null +++ b/mudu_kernel/src/server/worker_local.rs @@ -0,0 +1,47 @@ +use crate::server::worker_snapshot::KvItem; +use async_trait::async_trait; +use mudu::common::id::OID; +use mudu::common::result::RS; +use std::sync::Arc; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WorkerExecute { + BeginTx, + CommitTx, + RollbackTx, +} + +#[async_trait] +pub trait WorkerLocal: Send + Sync { + async fn open_async(&self) -> RS; + + async fn open_argv_async(&self, worker_id: OID) -> RS { + if worker_id == 0 { + self.open_async().await + } else { + Err(mudu::m_error!( + mudu::error::ec::EC::NotImplemented, + format!("worker-local open on worker {} is not supported", worker_id) + )) + } + } + + async fn close_async(&self, session_id: OID) -> RS<()>; + + async fn execute_async(&self, session_id: OID, instruction: WorkerExecute) -> RS<()>; + + async fn put_async(&self, session_id: OID, key: Vec, value: Vec) -> RS<()>; + + async fn delete_async(&self, session_id: OID, key: &[u8]) -> RS<()>; + + async fn get_async(&self, session_id: OID, key: &[u8]) -> RS>>; + + async fn range_async( + &self, + session_id: OID, + start_key: &[u8], + end_key: &[u8], + ) -> RS>; +} + +pub type WorkerLocalRef = Arc; diff --git a/mudu_kernel/src/server/worker_loop_stats.rs b/mudu_kernel/src/server/worker_loop_stats.rs new file mode 100644 index 0000000..e5b2997 --- /dev/null +++ b/mudu_kernel/src/server/worker_loop_stats.rs @@ -0,0 +1,31 @@ +#[derive(Debug, Default, Clone)] +pub(in crate::server) struct WorkerLoopStats { + pub worker_id: usize, + pub submit_calls: u64, + pub wait_cqe_calls: u64, + pub cqe_accept: u64, + pub cqe_mailbox: u64, + pub cqe_recv: u64, + pub cqe_send: u64, + #[allow(dead_code)] + pub cqe_log_open: u64, + #[allow(dead_code)] + pub cqe_file_close: u64, + pub cqe_log_write: u64, + pub cqe_close: u64, + pub recv_queue_push: u64, + pub recv_queue_pop: u64, + pub send_queue_push: u64, + pub send_queue_pop: u64, + pub recv_submit: u64, + pub send_submit: u64, + #[allow(dead_code)] + pub log_open_submit: u64, + #[allow(dead_code)] + pub file_close_submit: u64, + pub log_write_submit: u64, + pub accept_submit: u64, + pub mailbox_submit: u64, + pub mailbox_drained: u64, + pub local_register: u64, +} diff --git a/mudu_kernel/src/server/worker_mailbox.rs b/mudu_kernel/src/server/worker_mailbox.rs new file mode 100644 index 0000000..91529c3 --- /dev/null +++ b/mudu_kernel/src/server/worker_mailbox.rs @@ -0,0 +1,7 @@ +use crate::server::transferred_connection::TransferredConnection; + +#[derive(Debug)] +pub(in crate::server) enum WorkerMailboxMsg { + AdoptConnection(TransferredConnection), + Shutdown, +} diff --git a/mudu_kernel/src/server_ur/worker_registry.rs b/mudu_kernel/src/server/worker_registry.rs similarity index 100% rename from mudu_kernel/src/server_ur/worker_registry.rs rename to mudu_kernel/src/server/worker_registry.rs diff --git a/mudu_kernel/src/server/worker_ring_loop.rs b/mudu_kernel/src/server/worker_ring_loop.rs new file mode 100644 index 0000000..996a15d --- /dev/null +++ b/mudu_kernel/src/server/worker_ring_loop.rs @@ -0,0 +1,836 @@ +use crate::io::worker_ring::{set_current_worker_ring, unset_current_worker_ring, WorkerLocalRing}; +use crate::server::callback_registry::{ + AsyncCallback, CallbackDomain, CallbackEventKey, CallbackId, CallbackRegistry, CallbackTrigger, + PendingCallback, +}; +use crate::server::connection_worker_task::spawn_connection_worker_task; +use crate::server::inflight_op::{AcceptOp, InflightOp}; +use crate::server::loop_mailbox::{ + drain_messages, handle_read_completion, submit_read_if_needed, LoopMailboxSubmitCtx, +}; +use crate::server::loop_user_io::{ + handle_completion as handle_user_io_completion, submit as submit_user_io, LoopUserIoCtx, +}; +use crate::server::server_iouring; +use crate::server::server_iouring::RecoveryCoordinator; +use crate::server::worker::IoUringWorker; +use crate::server::worker_loop_stats::WorkerLoopStats; +use crate::server::worker_mailbox::WorkerMailboxMsg; +use crate::server::worker_task::{spawn_system_worker_task, WorkerTaskFuture}; +use crate::wal::worker_log::ChunkedWorkerLogBackend; +use crate::wal::xl_batch_worker_log::{new_xl_batch_worker_log, XLBatchWorkerLog}; +use crossbeam_queue::SegQueue; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use mudu_contract::protocol::encode_error_response; +use mudu_contract::protocol::{encode_session_create_response, SessionCreateResponse}; +use std::collections::HashMap; +use std::fs::OpenOptions; +use std::os::fd::{AsRawFd, RawFd}; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::Arc; +#[cfg(test)] +use std::thread; +use std::time::Duration; + +mod recovery; +mod runtime; + +type XLWorkerLog = + XLBatchWorkerLog; +/// Drives a single io_uring worker event loop. +/// +/// The loop owns the worker-local ring and multiplexes several kinds of work: +/// accepting new sockets, consuming inter-worker mailbox notifications, +/// completing user-triggered file/socket I/O, and coordinating connection task +/// lifecycle. It also performs worker-log recovery before the steady-state loop +/// starts so replayed state is visible to newly accepted connections. +pub(in crate::server) struct WorkerRingLoop { + worker: IoUringWorker, + log: Option, + ring: mudu_sys::uring::IoUring, + listener_fd: RawFd, + mailbox_fd: RawFd, + mailbox: Arc>, + mailboxes: Vec>>, + mailbox_fds: Vec, + conn_id_alloc: Arc, + recovery_coordinator: Arc, + worker_local_ring: Arc, + connection_task_fds: Arc>, + #[allow(dead_code)] + callback_registry: CallbackRegistry, + #[allow(dead_code)] + callback_sequence_frontiers: HashMap, + inflight: HashMap, + next_token: u64, + mailbox_read_submitted: bool, + shutdown_triggered: AtomicBool, + shutting_down: bool, + accept_submitted: bool, + stop: Arc, + stats: WorkerLoopStats, +} + +impl WorkerRingLoop { + /// Builds the runtime state for one worker loop and initializes its + /// private io_uring instance. + pub(in crate::server) fn new( + worker: IoUringWorker, + listener_fd: RawFd, + mailbox_fd: RawFd, + mailbox: Arc>, + mailboxes: Vec>>, + mailbox_fds: Vec, + conn_id_alloc: Arc, + recovery_coordinator: Arc, + stop: Arc, + ) -> RS { + let worker_id = worker.worker_index(); + let ring = mudu_sys::uring::IoUring::new(1024); + if let Err(rc) = ring { + return Err(m_error!( + EC::NetErr, + format!("io_uring_queue_init_params error {}", rc) + )); + } + let ring = ring.expect("checked above"); + let log = worker.worker_log().map(|backend| { + new_xl_batch_worker_log( + backend.clone(), + recovery::WorkerRingLoopRecoveryHandler { + worker: worker.clone(), + }, + ) + }); + Ok(Self { + log, + worker, + ring, + listener_fd, + mailbox_fd, + mailbox, + mailboxes, + mailbox_fds, + conn_id_alloc, + recovery_coordinator, + worker_local_ring: Arc::new(WorkerLocalRing::new()), + connection_task_fds: Arc::new(scc::HashMap::new()), + callback_registry: CallbackRegistry::new(), + callback_sequence_frontiers: HashMap::new(), + inflight: HashMap::new(), + next_token: 1, + mailbox_read_submitted: false, + shutdown_triggered: AtomicBool::new(false), + shutting_down: false, + accept_submitted: false, + stop, + stats: WorkerLoopStats { + worker_id, + ..WorkerLoopStats::default() + }, + }) + } + + /// Runs worker recovery and then enters the main service loop. + /// + /// The worker-local ring pointer is installed for the duration of the run + /// so user-level async file I/O can enqueue requests onto this loop. + pub(in crate::server) fn run(&mut self) -> RS { + set_current_worker_ring(self.worker_local_ring.clone()); + if let Err(err) = self.recover_worker_log() { + unset_current_worker_ring(); + self.recovery_coordinator.worker_failed(); + return Err(err); + } + self.recovery_coordinator.worker_succeeded()?; + let r = self.run_service_loop(); + unset_current_worker_ring(); + r + } + + #[allow(dead_code)] + pub(in crate::server) fn spawn(&self, conn_id: Option, future: WorkerTaskFuture) { + self.worker_local_ring + .worker_task_registry() + .spawn(conn_id, future); + } + + pub(in crate::server) fn process_cqe(&mut self, cqe: mudu_sys::uring::Cqe) -> RS<()> { + let token = cqe.user_data(); + let result = cqe.result(); + let op = self.inflight.remove(&token).ok_or_else(|| { + m_error!( + EC::InternalErr, + format!("unknown io_uring completion token {}", token) + ) + })?; + + // Completion dispatch is token-based: each submitted SQE inserts a + // matching inflight entry, and the CQE result is routed here. + match op { + InflightOp::Accept(op) => { + self.stats.cqe_accept += 1; + self.accept_submitted = false; + if result >= 0 { + let conn_fd = result as RawFd; + let remote_addr = server_iouring::sockaddr_to_socket_addr(op.addr())?; + server_iouring::set_connection_options(conn_fd)?; + let conn_id = self.conn_id_alloc.fetch_add(1, Ordering::Relaxed); + let target_worker = self.worker.route_connection(conn_id, remote_addr); + if target_worker == self.worker.worker_index() { + self.register_connection(conn_id, conn_fd, remote_addr)?; + } else { + Self::dispatch_mailbox_message( + &self.mailbox_fds, + &self.mailboxes, + target_worker, + WorkerMailboxMsg::AdoptConnection( + crate::server::transferred_connection::TransferredConnection::new( + crate::server::routing::ConnectionTransfer::new( + conn_id, + target_worker, + crate::server::fsm::ConnectionState::Accepted, + remote_addr, + ), + conn_fd, + Vec::new(), + None, + ), + ), + )?; + } + } + } + InflightOp::MailboxRead { .. } => { + handle_read_completion(&mut self.mailbox_read_submitted, &mut self.stats); + for msg in drain_messages(self.mailbox.as_ref(), &mut self.stats) { + self.handle_mailbox_message(msg)?; + } + } + InflightOp::UserIo(op) => { + handle_user_io_completion(&self.worker_local_ring, op, result)? + } + } + Ok(()) + } + + fn handle_mailbox_message(&self, msg: WorkerMailboxMsg) -> RS<()> { + match msg { + WorkerMailboxMsg::AdoptConnection(connection) => { + server_iouring::set_connection_options(connection.fd())?; + self.worker.adopt_connection_sessions( + connection.transfer().conn_id(), + connection.session_ids(), + )?; + let initial_response = if let Some(action) = connection.session_open_action() { + Some( + match self.worker.open_session_with_config( + connection.transfer().conn_id(), + action.config(), + ) { + Ok(session_id) => encode_session_create_response( + action.request_id(), + &SessionCreateResponse::new(session_id), + )?, + Err(err) => { + encode_error_response(action.request_id(), err.to_string())? + } + }, + ) + } else { + None + }; + self.start_connection_task( + connection.transfer().conn_id(), + connection.fd(), + connection.transfer().remote_addr(), + initial_response, + )?; + } + WorkerMailboxMsg::Shutdown => { + self.shutdown_triggered.store(true, Ordering::Relaxed); + } + } + Ok(()) + } + + pub(in crate::server) fn register_connection( + &mut self, + conn_id: u64, + fd: RawFd, + remote_addr: std::net::SocketAddr, + ) -> RS<()> { + self.stats.local_register += 1; + self.start_connection_task(conn_id, fd, remote_addr, None) + } + + pub(in crate::server) fn submit_accept_if_needed(&mut self) -> RS<()> { + if self.shutting_down || self.accept_submitted || self.listener_fd < 0 { + return Ok(()); + } + let token = self.alloc_token(); + let Some(mut sqe) = self.ring.next_sqe() else { + return Ok(()); + }; + let mut op = Box::new(AcceptOp::new(mudu_sys::uring::SockAddrBuf::new_empty())); + sqe.set_user_data(token); + sqe.prep_accept(self.listener_fd, op.addr_mut(), 0); + self.inflight.insert(token, InflightOp::Accept(op)); + self.accept_submitted = true; + self.stats.accept_submit += 1; + Ok(()) + } + + pub(in crate::server) fn submit_mailbox_read_if_needed(&mut self) -> RS<()> { + let mut ctx = LoopMailboxSubmitCtx { + ring: &mut self.ring, + mailbox_fd: self.mailbox_fd, + mailbox_read_submitted: &mut self.mailbox_read_submitted, + inflight: &mut self.inflight, + next_token: &mut self.next_token, + stats: &mut self.stats, + shutting_down: self.shutting_down, + }; + submit_read_if_needed(&mut ctx) + } + + pub(in crate::server) fn submit_user_ring_io_if_needed(&mut self) -> RS<()> { + let mut ctx = LoopUserIoCtx { + ring: &mut self.ring, + user_ring: &self.worker_local_ring, + inflight: &mut self.inflight, + next_token: &mut self.next_token, + }; + submit_user_io(&mut ctx) + } + + pub fn dispatch_mailbox_message( + mailbox_fds: &[RawFd], + mailboxes: &[Arc>], + target_worker: usize, + msg: WorkerMailboxMsg, + ) -> RS<()> { + let Some(mailbox) = mailboxes.get(target_worker) else { + return Err(m_error!( + EC::InternalErr, + format!("mailbox target worker {} is out of range", target_worker) + )); + }; + let Some(&fd) = mailbox_fds.get(target_worker) else { + return Err(m_error!( + EC::InternalErr, + format!( + "mailbox eventfd target worker {} is out of range", + target_worker + ) + )); + }; + mailbox.push(msg); + server_iouring::notify_mailbox_fd(fd) + } + + pub(in crate::server) fn alloc_token(&mut self) -> u64 { + let token = self.next_token; + self.next_token += 1; + token + } + + fn start_connection_task( + &self, + conn_id: u64, + fd: RawFd, + remote_addr: std::net::SocketAddr, + initial_response: Option>, + ) -> RS<()> { + let socket = crate::io::socket::IoSocket::from_raw_fd(fd); + let _ = self.connection_task_fds.insert_sync(conn_id, fd); + self.worker_local_ring.worker_task_registry().spawn( + Some(conn_id), + spawn_connection_worker_task( + self.worker.clone(), + self.mailbox_fds.clone(), + self.mailboxes.clone(), + self.connection_task_fds.clone(), + conn_id, + socket, + remote_addr, + initial_response, + ), + ); + Ok(()) + } + + #[allow(dead_code)] + pub(in crate::server) fn register_async_callback( + &mut self, + trigger: CallbackTrigger, + callback: AsyncCallback, + ) -> RS { + if let CallbackTrigger::Sequence { domain, target } = trigger { + if let Some(frontier) = self.callback_sequence_frontiers.get(&domain).copied() { + if frontier >= target { + let id = self.callback_registry.register(trigger, callback); + let ready = self.callback_registry.advance_sequence(domain, frontier); + self.spawn_ready_callbacks(ready)?; + return Ok(id); + } + } + } + Ok(self.callback_registry.register(trigger, callback)) + } + + #[allow(dead_code)] + pub(in crate::server) fn cancel_async_callback(&mut self, callback_id: CallbackId) -> bool { + self.callback_registry.cancel(callback_id) + } + + #[allow(dead_code)] + pub(in crate::server) fn fire_callback_event(&mut self, key: CallbackEventKey) -> RS<()> { + let ready = self.callback_registry.fire_event(key); + self.spawn_ready_callbacks(ready) + } + + #[allow(dead_code)] + pub(in crate::server) fn advance_callback_sequence( + &mut self, + domain: CallbackDomain, + value: u64, + ) -> RS<()> { + let frontier = self.callback_sequence_frontiers.entry(domain).or_insert(0); + if value <= *frontier { + return Ok(()); + } + *frontier = value; + let ready = self.callback_registry.advance_sequence(domain, value); + self.spawn_ready_callbacks(ready) + } + + #[allow(dead_code)] + fn spawn_ready_callbacks(&mut self, callbacks: Vec) -> RS<()> { + for pending in callbacks { + let future = (pending.callback)(); + self.worker_local_ring + .worker_task_registry() + .spawn_system(spawn_system_worker_task(future)); + } + Ok(()) + } +} + +#[cfg(all(test, target_os = "linux"))] +mod tests { + use super::*; + use crate::io::file::{close, flush, open, read, write}; + use crate::io::socket::{ + accept, close as close_socket, connect, recv, send, shutdown, socket, IoSocket, + }; + use crate::server::callback_registry::{CallbackDomain, CallbackEventKey, CallbackTrigger}; + use crate::server::routing::RoutingMode; + use crate::server::worker_registry::load_or_create_worker_registry; + use mudu::common::id::gen_oid; + use std::env::temp_dir; + use std::io::{Read, Write}; + use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; + use tokio::task::{yield_now, JoinHandle}; + + fn test_worker_loop() -> WorkerRingLoop { + let dir = temp_dir() + .join(format!("worker_ring_loop_test_{}", gen_oid())) + .to_string_lossy() + .into_owned(); + let registry = load_or_create_worker_registry(&dir, 1).unwrap(); + let identity = registry.worker(0).cloned().unwrap(); + let worker = IoUringWorker::new( + identity, + 1, + RoutingMode::ConnectionId, + dir.clone(), + dir.clone(), + 4096, + None, + registry, + ) + .unwrap(); + let mailbox_fd = mudu_sys::sync::eventfd().unwrap(); + WorkerRingLoop::new( + worker, + -1, + mailbox_fd, + Arc::new(SegQueue::new()), + vec![Arc::new(SegQueue::new())], + vec![mailbox_fd], + Arc::new(AtomicU64::new(1)), + Arc::new(RecoveryCoordinator::new(1)), + Arc::new(AtomicBool::new(false)), + ) + .unwrap() + } + + async fn drive_ring_future( + loop_state: &mut WorkerRingLoop, + handle: &JoinHandle>, + ) -> RS<()> + where + T: Send + 'static, + { + while !handle.is_finished() { + loop_state.submit_user_ring_io_if_needed()?; + let submitted = loop_state.ring.submit(); + if submitted < 0 { + return Err(m_error!( + EC::NetErr, + format!("io_uring_submit error {}", submitted) + )); + } + if loop_state.inflight.is_empty() { + yield_now().await; + continue; + } + let cqe = loop_state.ring.wait().map_err(|wait_rc| { + m_error!( + EC::NetErr, + format!("io_uring_wait_cqe error {}", wait_rc) + ) + })?; + loop_state.process_cqe(cqe)?; + yield_now().await; + } + Ok(()) + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_ring_loop_executes_user_file_io_via_cqe() { + let mut loop_state = match std::panic::catch_unwind(test_worker_loop) { + Ok(loop_state) => loop_state, + Err(_) => return, + }; + set_current_worker_ring(loop_state.worker_local_ring.clone()); + + let path = temp_dir().join(format!("iouring_file_io_{}", gen_oid())); + let path_str = path.to_string_lossy().into_owned(); + + let open_task = tokio::spawn({ + let path_str = path_str.clone(); + async move { + open( + &path_str, + libc::O_CREAT | libc::O_RDWR | libc::O_TRUNC | libc::O_CLOEXEC, + 0o644, + ) + .await + } + }); + yield_now().await; + drive_ring_future(&mut loop_state, &open_task) + .await + .unwrap(); + let file = open_task.await.unwrap().unwrap(); + + let fd = file.fd(); + let write_task = tokio::spawn(async move { + write( + &crate::io::file::IoFile::from_raw_fd(fd), + b"hello iouring".to_vec(), + 0, + ) + .await + }); + yield_now().await; + drive_ring_future(&mut loop_state, &write_task) + .await + .unwrap(); + assert_eq!(write_task.await.unwrap().unwrap(), b"hello iouring".len()); + + let fd = file.fd(); + let flush_task = + tokio::spawn(async move { flush(&crate::io::file::IoFile::from_raw_fd(fd)).await }); + yield_now().await; + drive_ring_future(&mut loop_state, &flush_task) + .await + .unwrap(); + flush_task.await.unwrap().unwrap(); + + let fd = file.fd(); + let read_task = + tokio::spawn( + async move { read(&crate::io::file::IoFile::from_raw_fd(fd), 13, 0).await }, + ); + yield_now().await; + drive_ring_future(&mut loop_state, &read_task) + .await + .unwrap(); + assert_eq!(read_task.await.unwrap().unwrap(), b"hello iouring".to_vec()); + + assert_eq!(std::fs::read(&path).unwrap(), b"hello iouring".to_vec()); + + let close_task = tokio::spawn(async move { close(file).await }); + yield_now().await; + drive_ring_future(&mut loop_state, &close_task) + .await + .unwrap(); + close_task.await.unwrap().unwrap(); + + unset_current_worker_ring(); + loop_state.ring.exit(); + mudu_sys::sync::close_fd(loop_state.mailbox_fd).unwrap(); + let _ = std::fs::remove_file(&path); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_ring_loop_executes_user_socket_connect_io_via_cqe() { + let mut loop_state = match std::panic::catch_unwind(test_worker_loop) { + Ok(loop_state) => loop_state, + Err(_) => return, + }; + set_current_worker_ring(loop_state.worker_local_ring.clone()); + + let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + let peer = thread::spawn(move || -> RS<()> { + let (mut stream, _) = listener.accept().unwrap(); + let mut buf = [0u8; 4]; + stream.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"ping"); + stream.write_all(b"pong").unwrap(); + let mut eof = [0u8; 1]; + let read = stream.read(&mut eof).unwrap(); + assert_eq!(read, 0); + Ok(()) + }); + + let socket_task = tokio::spawn(async { + socket(libc::AF_INET, libc::SOCK_STREAM | libc::SOCK_CLOEXEC, 0).await + }); + yield_now().await; + drive_ring_future(&mut loop_state, &socket_task) + .await + .unwrap(); + let sock = socket_task.await.unwrap().unwrap(); + + let fd = sock.fd(); + let connect_task = + tokio::spawn(async move { connect(&IoSocket::from_raw_fd(fd), addr).await }); + yield_now().await; + drive_ring_future(&mut loop_state, &connect_task) + .await + .unwrap(); + connect_task.await.unwrap().unwrap(); + + let fd = sock.fd(); + let send_task = + tokio::spawn( + async move { send(&IoSocket::from_raw_fd(fd), b"ping".to_vec(), 0).await }, + ); + yield_now().await; + drive_ring_future(&mut loop_state, &send_task) + .await + .unwrap(); + assert_eq!(send_task.await.unwrap().unwrap(), 4); + + let fd = sock.fd(); + let recv_task = tokio::spawn(async move { recv(&IoSocket::from_raw_fd(fd), 4, 0).await }); + yield_now().await; + drive_ring_future(&mut loop_state, &recv_task) + .await + .unwrap(); + assert_eq!(recv_task.await.unwrap().unwrap(), b"pong".to_vec()); + + let fd = sock.fd(); + let shutdown_task = + tokio::spawn(async move { shutdown(&IoSocket::from_raw_fd(fd), libc::SHUT_WR).await }); + yield_now().await; + drive_ring_future(&mut loop_state, &shutdown_task) + .await + .unwrap(); + shutdown_task.await.unwrap().unwrap(); + + let close_task = tokio::spawn(async move { close_socket(sock).await }); + yield_now().await; + drive_ring_future(&mut loop_state, &close_task) + .await + .unwrap(); + close_task.await.unwrap().unwrap(); + + peer.join().unwrap().unwrap(); + + unset_current_worker_ring(); + loop_state.ring.exit(); + mudu_sys::sync::close_fd(loop_state.mailbox_fd).unwrap(); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_ring_loop_executes_user_socket_accept_io_via_cqe() { + let mut loop_state = match std::panic::catch_unwind(test_worker_loop) { + Ok(loop_state) => loop_state, + Err(_) => return, + }; + set_current_worker_ring(loop_state.worker_local_ring.clone()); + + let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + let listener_fd = unsafe { libc::dup(listener.as_raw_fd()) }; + assert!(listener_fd >= 0); + let listener_sock = IoSocket::from_raw_fd(listener_fd); + + let peer = thread::spawn(move || -> RS<()> { + let mut stream = std::net::TcpStream::connect(addr).unwrap(); + stream.write_all(b"ping").unwrap(); + let mut buf = [0u8; 4]; + stream.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"pong"); + Ok(()) + }); + + let accept_fd = listener_sock.fd(); + let accept_task = + tokio::spawn(async move { accept(&IoSocket::from_raw_fd(accept_fd)).await }); + yield_now().await; + drive_ring_future(&mut loop_state, &accept_task) + .await + .unwrap(); + let (accepted, remote_addr) = accept_task.await.unwrap().unwrap(); + assert_eq!( + remote_addr.ip(), + std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST) + ); + + let accepted_fd = accepted.fd(); + let recv_task = + tokio::spawn(async move { recv(&IoSocket::from_raw_fd(accepted_fd), 4, 0).await }); + yield_now().await; + drive_ring_future(&mut loop_state, &recv_task) + .await + .unwrap(); + assert_eq!(recv_task.await.unwrap().unwrap(), b"ping".to_vec()); + + let accepted_fd = accepted.fd(); + let send_task = tokio::spawn(async move { + send(&IoSocket::from_raw_fd(accepted_fd), b"pong".to_vec(), 0).await + }); + yield_now().await; + drive_ring_future(&mut loop_state, &send_task) + .await + .unwrap(); + assert_eq!(send_task.await.unwrap().unwrap(), 4); + + let accepted_fd = accepted.fd(); + let shutdown_task = tokio::spawn(async move { + shutdown(&IoSocket::from_raw_fd(accepted_fd), libc::SHUT_WR).await + }); + yield_now().await; + drive_ring_future(&mut loop_state, &shutdown_task) + .await + .unwrap(); + shutdown_task.await.unwrap().unwrap(); + + let close_accepted_task = tokio::spawn(async move { close_socket(accepted).await }); + yield_now().await; + drive_ring_future(&mut loop_state, &close_accepted_task) + .await + .unwrap(); + close_accepted_task.await.unwrap().unwrap(); + + let close_listener_task = tokio::spawn(async move { close_socket(listener_sock).await }); + yield_now().await; + drive_ring_future(&mut loop_state, &close_listener_task) + .await + .unwrap(); + close_listener_task.await.unwrap().unwrap(); + + peer.join().unwrap().unwrap(); + + unset_current_worker_ring(); + loop_state.ring.exit(); + mudu_sys::sync::close_fd(loop_state.mailbox_fd).unwrap(); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_ring_loop_runs_event_callback_as_system_task() { + let mut loop_state = match std::panic::catch_unwind(test_worker_loop) { + Ok(loop_state) => loop_state, + Err(_) => return, + }; + let hit = Arc::new(AtomicUsize::new(0)); + let hit_clone = hit.clone(); + let callback_id = loop_state + .register_async_callback( + CallbackTrigger::Event(CallbackEventKey { kind: 7, id: 99 }), + Box::new(move || { + Box::pin(async move { + hit_clone.fetch_add(1, AtomicOrdering::SeqCst); + Ok(()) + }) + }), + ) + .unwrap(); + assert!(callback_id > 0); + + loop_state + .fire_callback_event(CallbackEventKey { kind: 7, id: 99 }) + .unwrap(); + loop_state.poll_ready_worker_tasks().unwrap(); + assert_eq!(hit.load(AtomicOrdering::SeqCst), 1); + + loop_state.ring.exit(); + mudu_sys::sync::close_fd(loop_state.mailbox_fd).unwrap(); + } + + #[tokio::test(flavor = "current_thread")] + async fn worker_ring_loop_runs_sequence_callback_when_frontier_advances_and_skips_cancelled() { + let mut loop_state = match std::panic::catch_unwind(test_worker_loop) { + Ok(loop_state) => loop_state, + Err(_) => return, + }; + let hit = Arc::new(AtomicUsize::new(0)); + + let first_hit = hit.clone(); + loop_state + .register_async_callback( + CallbackTrigger::Sequence { + domain: CallbackDomain::Generic(3), + target: 4, + }, + Box::new(move || { + Box::pin(async move { + first_hit.fetch_add(1, AtomicOrdering::SeqCst); + Ok(()) + }) + }), + ) + .unwrap(); + + let cancelled_hit = hit.clone(); + let cancelled = loop_state + .register_async_callback( + CallbackTrigger::Sequence { + domain: CallbackDomain::Generic(3), + target: 5, + }, + Box::new(move || { + Box::pin(async move { + cancelled_hit.fetch_add(100, AtomicOrdering::SeqCst); + Ok(()) + }) + }), + ) + .unwrap(); + assert!(loop_state.cancel_async_callback(cancelled)); + + loop_state + .advance_callback_sequence(CallbackDomain::Generic(3), 4) + .unwrap(); + loop_state.poll_ready_worker_tasks().unwrap(); + assert_eq!(hit.load(AtomicOrdering::SeqCst), 1); + + loop_state + .advance_callback_sequence(CallbackDomain::Generic(3), 5) + .unwrap(); + loop_state.poll_ready_worker_tasks().unwrap(); + assert_eq!(hit.load(AtomicOrdering::SeqCst), 1); + + loop_state.ring.exit(); + mudu_sys::sync::close_fd(loop_state.mailbox_fd).unwrap(); + } +} diff --git a/mudu_kernel/src/server/worker_ring_loop/recovery.rs b/mudu_kernel/src/server/worker_ring_loop/recovery.rs new file mode 100644 index 0000000..cc65c2e --- /dev/null +++ b/mudu_kernel/src/server/worker_ring_loop/recovery.rs @@ -0,0 +1,122 @@ +use super::*; +use crate::wal::lsn::LSN; +use crate::wal::typed_worker_log::WorkerLogRecoveryHandler; +use crate::wal::worker_log::{ChunkedWorkerLogBackend, WorkerLogBackend, WorkerLogRecoverySource}; +use crate::wal::xl_batch::XLBatch; +use std::path::{Path, PathBuf}; + +pub(super) struct WorkerRingLoopRecoveryHandler { + pub(super) worker: IoUringWorker, +} + +impl WorkerLogRecoveryHandler for WorkerRingLoopRecoveryHandler { + fn handle_entry(&self, entry: XLBatch, _start_lsn: LSN) -> RS<()> { + self.worker.replay_log_batch(entry) + } + + fn finish(&self) -> RS<()> { + Ok(()) + } +} + +struct WorkerRingLoopRecoverySource<'a> { + loop_ref: &'a mut WorkerRingLoop, + backend: ChunkedWorkerLogBackend, +} + +impl WorkerLogRecoverySource for WorkerRingLoopRecoverySource<'_> { + fn chunk_paths_sorted(&mut self) -> RS> { + self.backend.chunk_paths_sorted() + } + + fn read_chunk(&mut self, path: &Path) -> RS> { + let file = OpenOptions::new() + .read(true) + .open(path) + .map_err(|e| m_error!(EC::IOErr, "open worker log chunk for recovery error", e))?; + let size = file + .metadata() + .map_err(|e| { + m_error!( + EC::IOErr, + "read worker log chunk recovery metadata error", + e + ) + })? + .len() as usize; + if size == 0 { + return Ok(Vec::new()); + } + self.loop_ref.read_file_all_iouring(&file, size) + } +} + +impl WorkerRingLoop { + /// Replays persisted worker-log chunks before the worker starts serving + /// live traffic. + pub(super) fn recover_worker_log(&mut self) -> RS<()> { + let log = match self.log.take() { + Some(log) => log, + None => return Ok(()), + }; + let backend = log.backend().clone(); + let mut source = WorkerRingLoopRecoverySource { + loop_ref: self, + backend, + }; + let result = log.recover(&mut source); + self.log = Some(log); + result + } + + /// Reads a full file through this loop's io_uring instance. + /// + /// Recovery uses this helper to keep all I/O on the same ring that the + /// worker will later use for live operations. + fn read_file_all_iouring(&mut self, file: &std::fs::File, size: usize) -> RS> { + let mut buf = vec![0u8; size]; + let mut offset = 0usize; + while offset < size { + let Some(mut sqe) = self.ring.next_sqe() else { + let submitted = self.ring.submit(); + if submitted < 0 { + return Err(m_error!( + EC::IOErr, + format!("submit io_uring recovery read error {}", submitted) + )); + } + continue; + }; + sqe.set_user_data(0); + sqe.prep_read_raw(file.as_raw_fd(), buf[offset..].as_mut_ptr(), size - offset, offset as u64); + let submitted = self.ring.submit(); + if submitted < 0 { + return Err(m_error!( + EC::IOErr, + format!("submit io_uring recovery read error {}", submitted) + )); + } + let read = match self.ring.wait() { + Ok(cqe) => cqe.result(), + Err(wait_rc) => { + return Err(m_error!( + EC::IOErr, + format!("wait io_uring recovery read cqe error {}", wait_rc) + )) + } + }; + if read < 0 { + return Err(m_error!( + EC::IOErr, + format!("worker log recovery read completion error {}", read) + )); + } + if read == 0 { + break; + } + offset += read as usize; + } + buf.truncate(offset); + Ok(buf) + } +} diff --git a/mudu_kernel/src/server/worker_ring_loop/runtime.rs b/mudu_kernel/src/server/worker_ring_loop/runtime.rs new file mode 100644 index 0000000..45120a7 --- /dev/null +++ b/mudu_kernel/src/server/worker_ring_loop/runtime.rs @@ -0,0 +1,183 @@ +use super::*; + +impl WorkerRingLoop { + /// Main poll/submit loop for the worker. + /// + /// Each iteration: + /// 1. reacts to shutdown, + /// 2. drains mailbox work, + /// 3. advances connection tasks and log flushing, + /// 4. submits any missing io_uring operations, + /// 5. waits for and dispatches completions. + pub(super) fn run_service_loop(&mut self) -> RS { + loop { + if self.stop.load(Ordering::Relaxed) || self.shutdown_triggered.load(Ordering::Relaxed) + { + self.begin_shutdown()?; + } + if !self.shutting_down { + for msg in drain_messages(self.mailbox.as_ref(), &mut self.stats) { + self.handle_mailbox_message(msg)?; + } + } else { + self.shutdown_connection_tasks(); + } + self.poll_flush_log()?; + self.worker_local_ring + .worker_task_registry() + .drain_completions(); + self.poll_ready_worker_tasks()?; + self.submit_mailbox_read_if_needed()?; + self.submit_accept_if_needed()?; + self.submit_user_ring_io_if_needed()?; + self.stats.submit_calls += 1; + let submitted = self.ring.submit(); + if submitted < 0 { + return Err(m_error!( + EC::NetErr, + format!("io_uring_submit error {}", submitted) + )); + } + + if self.shutting_down && self.worker_local_ring.worker_task_registry().is_empty() { + return self.finish_shutdown(); + } + + if self.inflight.is_empty() { + mudu_sys::task::sleep_blocking(Duration::from_millis(1)); + continue; + } + + self.stats.wait_cqe_calls += 1; + let cqe = match self.wait_for_cqe()? { + Ok(cqe) => cqe, + Err(wait_rc) if wait_rc == -libc::ETIME => continue, + Err(wait_rc) if wait_rc == -libc::EINTR => continue, + Err(wait_rc) => { + return Err(m_error!( + EC::NetErr, + format!("io_uring_wait_cqe error {}", wait_rc) + )) + } + }; + self.process_cqe(cqe)?; + + loop { + let next_cqe = match self.ring.peek() { + Ok(Some(cqe)) => cqe, + Ok(None) => break, + Err(peek_rc) => { + return Err(m_error!( + EC::NetErr, + format!("io_uring_peek_cqe error {}", peek_rc) + )) + } + }; + self.process_cqe(next_cqe)?; + } + } + } + + fn begin_shutdown(&mut self) -> RS<()> { + // Shutdown is staged: stop taking new work, close the listener, and + // actively nudge connection tasks so they can drain and exit. + if self.shutting_down { + return Ok(()); + } + self.shutting_down = true; + self.close_pending_mailbox_connections()?; + self.shutdown_connection_tasks(); + if self.listener_fd >= 0 { + let rc = unsafe { libc::close(self.listener_fd) }; + if rc != 0 { + return Err(m_error!( + EC::NetErr, + "close io_uring listener during shutdown error", + std::io::Error::last_os_error() + )); + } + self.listener_fd = -1; + } + Ok(()) + } + + fn close_pending_mailbox_connections(&mut self) -> RS<()> { + while let Some(msg) = self.mailbox.pop() { + if let WorkerMailboxMsg::AdoptConnection(connection) = msg { + let rc = unsafe { libc::close(connection.fd()) }; + if rc != 0 { + return Err(m_error!( + EC::NetErr, + "close transferred io_uring connection during shutdown error", + std::io::Error::last_os_error() + )); + } + } + } + Ok(()) + } + + pub(in crate::server) fn poll_ready_worker_tasks(&mut self) -> RS<()> { + for completed in self.worker_local_ring.worker_task_registry().poll_ready() { + if completed.is_system() { + if let Err(_err) = completed.into_result() { + // Detached system callbacks should not disrupt the worker + // event loop. They are fire-and-forget tasks whose errors + // are isolated from connection lifecycle management. + } + continue; + } + let opt_conn_id = completed.conn_id(); + match completed.into_result() { + Ok(_) => {} + Err(_) => { + if let Some(conn_id) = opt_conn_id { + self.worker.close_connection_sessions(conn_id)?; + } + } + } + } + Ok(()) + } + + fn shutdown_connection_tasks(&mut self) { + self.connection_task_fds.iter_sync(|_, fd| { + unsafe { + libc::shutdown(*fd, libc::SHUT_RDWR); + } + true + }); + } + + fn finish_shutdown(&mut self) -> RS { + self.ring.exit(); + Ok(self.stats.clone()) + } + + fn wait_for_cqe(&mut self) -> RS> { + if let Some(timeout) = self.log_flush_wait_timeout()? { + return Ok(self.ring.wait_timeout(timeout)); + } + Ok(self.ring.wait()) + } + + fn log_flush_wait_timeout(&self) -> RS> { + let Some(log) = &self.log else { + return Ok(None); + }; + let Some(deadline) = log.backend().next_flush_deadline()? else { + return Ok(None); + }; + Ok(Some( + deadline.saturating_duration_since(mudu_sys::time::instant_now()), + )) + } + + fn poll_flush_log(&mut self) -> RS<()> { + let Some(log) = &self.log else { + return Ok(()); + }; + let _ = log.backend().poll_flush_log()?; + Ok(()) + } +} diff --git a/mudu_kernel/src/server/worker_session_manager.rs b/mudu_kernel/src/server/worker_session_manager.rs new file mode 100644 index 0000000..576e792 --- /dev/null +++ b/mudu_kernel/src/server/worker_session_manager.rs @@ -0,0 +1,283 @@ +use crate::server::worker_tx_manager::WorkerTxManager; +use mudu::common::id::OID; +use mudu::common::result::RS; +use mudu::common::xid::new_xid; +use mudu::error::ec::EC; +use mudu::m_error; +use scc::HashMap as SccHashMap; +use std::cell::UnsafeCell; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +pub(crate) struct WorkerSessionManager { + session_owner: SccHashMap, + connection_sessions: SccHashMap>>, + session_contexts: SccHashMap>, + active_sessions: Arc, +} + +#[derive(Default)] +pub(crate) struct SessionContext { + tx_manager: UnsafeCell>, +} + +unsafe impl Send for SessionContext {} +unsafe impl Sync for SessionContext {} + +impl WorkerSessionManager { + pub(crate) fn new(active_sessions: Arc) -> Self { + Self { + session_owner: SccHashMap::new(), + connection_sessions: SccHashMap::new(), + session_contexts: SccHashMap::new(), + active_sessions, + } + } + + pub(crate) fn create_session(&self, conn_id: u64) -> RS { + loop { + let session_id = new_xid(); + if self.session_owner.insert_sync(session_id, conn_id).is_err() { + continue; + } + let session_context = Arc::new(SessionContext::default()); + if self + .session_contexts + .insert_sync(session_id, session_context) + .is_err() + { + let _ = self.session_owner.remove_sync(&session_id); + continue; + } + let _ = self + .connection_sessions(conn_id) + .insert_sync(session_id, ()); + self.active_sessions.fetch_add(1, Ordering::Relaxed); + return Ok(session_id); + } + } + + pub(crate) fn close_session(&self, conn_id: u64, session_id: OID) -> RS { + match self + .session_owner + .get_sync(&session_id) + .map(|entry| *entry.get()) + { + Some(owner_conn_id) if owner_conn_id == conn_id => { + let removed_owner = self.session_owner.remove_sync(&session_id).is_some(); + let _ = self.session_contexts.remove_sync(&session_id); + if let Some(conn_sessions) = self.connection_sessions.get_sync(&conn_id) { + let conn_sessions = conn_sessions.get().clone(); + let _ = conn_sessions.remove_sync(&session_id); + } + if removed_owner { + self.active_sessions.fetch_sub(1, Ordering::Relaxed); + } + Ok(true) + } + Some(_) => Err(m_error!( + EC::TxErr, + format!( + "session {} does not belong to connection {}", + session_id, conn_id + ) + )), + None => Ok(false), + } + } + + pub(crate) fn close_connection_sessions(&self, conn_id: u64) -> RS<()> { + if let Some((_conn_id, session_ids)) = self.connection_sessions.remove_sync(&conn_id) { + session_ids.iter_sync(|session_id, _| { + if self.session_owner.remove_sync(session_id).is_some() { + self.active_sessions.fetch_sub(1, Ordering::Relaxed); + } + let _ = self.session_contexts.remove_sync(session_id); + true + }); + } + Ok(()) + } + + pub(crate) fn conn_id_for_session(&self, session_id: OID) -> RS { + self.session_owner + .get_sync(&session_id) + .map(|entry| *entry.get()) + .ok_or_else(|| { + m_error!( + EC::NoSuchElement, + format!("session {} does not exist", session_id) + ) + }) + } + + pub(crate) fn open_session(&self, session_id: OID) -> RS { + let conn_id = self.conn_id_for_session(session_id)?; + self.create_session(conn_id) + } + + pub(crate) fn close_session_by_id(&self, session_id: OID) -> RS<()> { + let conn_id = self.conn_id_for_session(session_id)?; + let closed = self.close_session(conn_id, session_id)?; + if closed { + Ok(()) + } else { + Err(m_error!( + EC::NoSuchElement, + format!("session {} does not exist", session_id) + )) + } + } + + pub(crate) fn session_context(&self, session_id: OID) -> RS> { + self.session_contexts + .get_sync(&session_id) + .map(|entry| entry.get().clone()) + .ok_or_else(|| { + m_error!( + EC::NoSuchElement, + format!("session {} does not exist", session_id) + ) + }) + } + + pub(crate) fn ensure_session_owned_by_connection( + &self, + conn_id: u64, + session_id: OID, + ) -> RS<()> { + match self + .session_owner + .get_sync(&session_id) + .map(|entry| *entry.get()) + { + Some(owner_conn_id) if owner_conn_id == conn_id => Ok(()), + Some(_) => Err(m_error!( + EC::TxErr, + format!( + "session {} does not belong to connection {}", + session_id, conn_id + ) + )), + None => Err(m_error!( + EC::NoSuchElement, + format!("session {} does not exist", session_id) + )), + } + } + + pub(crate) fn adopt_connection_sessions(&self, conn_id: u64, session_ids: &[OID]) -> RS<()> { + if session_ids.is_empty() { + return Ok(()); + } + let conn_sessions = self.connection_sessions(conn_id); + for &session_id in session_ids { + self.session_owner + .insert_sync(session_id, conn_id) + .map_err(|_| { + m_error!( + EC::ExistingSuchElement, + format!("session {} already exists on target worker", session_id) + ) + })?; + if self + .session_contexts + .insert_sync(session_id, Arc::new(SessionContext::default())) + .is_err() + { + let _ = self.session_owner.remove_sync(&session_id); + return Err(m_error!( + EC::ExistingSuchElement, + format!( + "session {} context already exists on target worker", + session_id + ) + )); + } + let _ = conn_sessions.insert_sync(session_id, ()); + self.active_sessions.fetch_add(1, Ordering::Relaxed); + } + Ok(()) + } + + pub(crate) fn connection_has_active_tx(&self, conn_id: u64) -> RS { + let session_ids = self.connection_session_ids(conn_id); + for session_id in session_ids { + let session = self.session_context(session_id)?; + if session.tx_manager_ref().is_some() { + return Ok(true); + } + } + Ok(false) + } + + pub(crate) fn detach_connection_sessions(&self, conn_id: u64) -> RS> { + let Some((_conn_id, conn_sessions)) = self.connection_sessions.remove_sync(&conn_id) else { + return Ok(Vec::new()); + }; + let mut session_ids = Vec::new(); + conn_sessions.iter_sync(|session_id, _| { + session_ids.push(*session_id); + true + }); + for &session_id in &session_ids { + if self.session_owner.remove_sync(&session_id).is_some() { + self.active_sessions.fetch_sub(1, Ordering::Relaxed); + } + let _ = self.session_contexts.remove_sync(&session_id); + } + Ok(session_ids) + } + + fn connection_sessions(&self, conn_id: u64) -> Arc> { + if let Some(existing) = self.connection_sessions.get_sync(&conn_id) { + return existing.get().clone(); + } + let created = Arc::new(SccHashMap::new()); + match self + .connection_sessions + .insert_sync(conn_id, created.clone()) + { + Ok(_) => created, + Err((_conn_id, created)) => { + if let Some(existing) = self.connection_sessions.get_sync(&conn_id) { + existing.get().clone() + } else { + created + } + } + } + } + + fn connection_session_ids(&self, conn_id: u64) -> Vec { + let Some(conn_sessions) = self.connection_sessions.get_sync(&conn_id) else { + return Vec::new(); + }; + let mut session_ids = Vec::new(); + conn_sessions.get().iter_sync(|session_id, _| { + session_ids.push(*session_id); + true + }); + session_ids + } +} + +impl SessionContext { + pub(crate) fn tx_manager_ref(&self) -> &Option { + unsafe { &*self.tx_manager.get() } + } + + pub(crate) fn tx_manager_mut(&self) -> &mut Option { + unsafe { &mut *self.tx_manager.get() } + } + + pub(crate) fn set_tx_manager(&self, tx_manager: Option) { + unsafe { + *self.tx_manager.get() = tx_manager; + } + } + + pub(crate) fn take_tx_manager(&self) -> Option { + self.tx_manager_mut().take() + } +} diff --git a/mudu_kernel/src/server/worker_snapshot.rs b/mudu_kernel/src/server/worker_snapshot.rs new file mode 100644 index 0000000..e6b1f76 --- /dev/null +++ b/mudu_kernel/src/server/worker_snapshot.rs @@ -0,0 +1,91 @@ +use crate::contract::snapshot::{RunningXList, Snapshot}; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct KvItem { + pub key: Vec, + pub value: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WorkerSnapshot { + xid: u64, + running: Vec, +} + +#[derive(Default)] +pub struct WorkerSnapshotMgr { + next_ts: u64, + running: Vec, +} + +impl WorkerSnapshot { + pub fn new(xid: u64, running: Vec) -> Self { + Self { xid, running } + } + + pub fn xid(&self) -> u64 { + self.xid + } + + pub fn is_visible(&self, version_xid: u64) -> bool { + is_visible_to_snapshot(version_xid, self) + } + + pub fn to_snapshot(&self) -> Snapshot { + Snapshot::from(RunningXList::new(self.xid, self.running.clone())) + } +} + +impl WorkerSnapshotMgr { + pub fn begin_tx(&mut self) -> WorkerSnapshot { + self.next_ts += 1; + let xid = self.next_ts; + let snapshot = WorkerSnapshot { + xid, + running: self.running.clone(), + }; + insert_sorted_unique(&mut self.running, xid); + snapshot + } + + pub fn alloc_committed_ts(&mut self) -> u64 { + self.next_ts += 1; + self.next_ts + } + + pub fn observe_committed_ts(&mut self, xid: u64) { + if self.next_ts < xid { + self.next_ts = xid; + } + } + + pub fn end_tx(&mut self, xid: u64) -> RS<()> { + match self.running.binary_search(&xid) { + Ok(index) => { + self.running.remove(index); + Ok(()) + } + Err(_) => Err(m_error!( + EC::NoSuchElement, + format!("transaction {} is not active", xid) + )), + } + } +} + +fn is_visible_to_snapshot(version_xid: u64, snapshot: &WorkerSnapshot) -> bool { + if version_xid > snapshot.xid { + return false; + } + snapshot.running.binary_search(&version_xid).is_err() +} + +fn insert_sorted_unique(values: &mut Vec, value: u64) { + match values.binary_search(&value) { + Ok(_) => {} + Err(index) => values.insert(index, value), + } +} diff --git a/mudu_kernel/src/server/worker_storage.rs b/mudu_kernel/src/server/worker_storage.rs new file mode 100644 index 0000000..60cd89b --- /dev/null +++ b/mudu_kernel/src/server/worker_storage.rs @@ -0,0 +1,871 @@ +use std::collections::{BTreeMap, Bound}; +use std::ops::Bound::{Excluded, Included, Unbounded}; +use std::sync::Arc; + +use mudu::common::id::OID; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use scc::HashMap as SccHashMap; + +use crate::contract::data_row::DataRow; +use crate::contract::meta_mgr::MetaMgr; +use crate::contract::schema_table::SchemaTable; +use crate::contract::table_desc::TableDesc; +use crate::contract::timestamp::Timestamp; +use crate::contract::version_tuple::VersionTuple; +use crate::index::index_key::key_tuple::KeyTuple; +use crate::server::worker_snapshot::{KvItem, WorkerSnapshot}; +use crate::server::worker_tx_manager::WorkerTxManager; +use crate::storage::relation::relation::Relation; +use crate::wal::xl_batch::XLBatch; +use crate::wal::xl_data_op::{XLDelete, XLInsert}; +use crate::wal::xl_entry::TxOp; +#[derive(Clone, Debug)] +pub(crate) struct PreparedWorkerCommit { + xid: u64, + relation_rows: BTreeMap, Option>>>, + kv_rows: BTreeMap, Option>>, + batch: XLBatch, +} + +pub struct WorkerStorage { + mgr: Arc, + partition_id: OID, + relation_path: String, + relation_store: SccHashMap, + kv_store: SccHashMap, DataRow>, +} + +impl WorkerStorage { + pub fn new(mgr: Arc, partition_id: OID, relation_path: String) -> Self { + Self { + mgr, + partition_id, + relation_path, + relation_store: SccHashMap::new(), + kv_store: SccHashMap::new(), + } + } + + pub async fn create_table_async(&self, schema: &SchemaTable) -> RS<()> { + let oid = schema.id(); + self.mgr.create_table(schema).await?; + let table_desc = self.mgr.get_table_by_id(oid).await?; + self.create_relation_index(oid, table_desc.as_ref())?; + Ok(()) + } + + pub async fn drop_table_async(&self, oid: OID) -> RS<()> { + self.mgr.drop_table(oid).await?; + let _ = self.relation_store.remove_sync(&oid); + Ok(()) + } + + #[allow(dead_code)] + pub fn contains_key(&self, oid: OID, key: &KeyTuple, txm: &mut WorkerTxManager) -> RS { + if let Some(staged) = txm.get_relation(oid, key.as_slice()) { + return Ok(staged.is_some()); + } + self.read_visible_relation_exists(oid, key, txm.snapshot()) + } + + pub fn get(&self, oid: OID, key: &[u8], txm: &mut WorkerTxManager) -> RS>> { + if let Some(staged) = txm.get_relation(oid, key) { + return Ok(staged); + } + + let key = KeyTuple::from(key.to_vec()); + self.read_visible_relation_value(oid, &key, txm.snapshot()) + } + + pub fn insert( + &self, + oid: OID, + key: Vec, + value: Vec, + txm: &mut WorkerTxManager, + ) -> RS<()> { + let key_tuple = KeyTuple::from(key.clone()); + self.ensure_no_relation_write_conflict(oid, &key_tuple, txm.snapshot())?; + txm.put_relation(oid, key, value); + Ok(()) + } + + pub fn remove(&self, oid: OID, key: &[u8], txm: &mut WorkerTxManager) -> RS>> { + let key_tuple = KeyTuple::from(key.to_vec()); + self.ensure_no_relation_write_conflict(oid, &key_tuple, txm.snapshot())?; + + let current = match txm.get_relation(oid, key) { + Some(staged) => staged, + None => self.read_visible_relation_value(oid, &key_tuple, txm.snapshot())?, + }; + + if current.is_some() { + txm.delete_relation(oid, key.to_vec()); + } + Ok(current) + } + + pub fn range( + &self, + oid: OID, + bounds: (Bound<&[u8]>, Bound<&[u8]>), + txm: &mut WorkerTxManager, + ) -> RS, Vec)>> { + let base_items = self.range_visible_relation(oid, bounds, txm.snapshot())?; + let (start_key, end_key) = bounds_to_scan(&bounds); + let staged_items = txm.staged_relation_items_in_range(oid, &start_key, &end_key); + + let mut merged = BTreeMap::new(); + for (key, value) in base_items { + merged.insert(key, Some(value)); + } + for (key, value) in staged_items { + merged.insert(key, value); + } + + Ok(merged + .into_iter() + .filter_map(|(key, value)| value.map(|value| (key, value))) + .collect()) + } + + pub fn worker_get(&self, key: &[u8], snapshot: Option<&WorkerSnapshot>) -> RS>> { + let row = self.kv_store.get_sync(key); + let version = match snapshot { + Some(snapshot) => row.and_then(|row| { + let snapshot = snapshot.to_snapshot(); + read_visible_version(row.get(), &snapshot) + }), + None => row.and_then(|row| latest_version(row.get())), + }; + Ok(version + .filter(|version| !version.is_deleted()) + .map(|version| version.tuple().clone())) + } + + pub fn worker_range( + &self, + start_key: &[u8], + end_key: &[u8], + snapshot: Option<&WorkerSnapshot>, + ) -> RS> { + let mut items = Vec::new(); + self.kv_store.iter_sync(|key, row| { + let in_range = if end_key.is_empty() { + key.as_slice() >= start_key + } else { + key.as_slice() >= start_key && key.as_slice() < end_key + }; + if !in_range { + return true; + } + + let visible = match snapshot { + Some(snapshot) => { + let snapshot = snapshot.to_snapshot(); + read_visible_version(row, &snapshot) + } + None => latest_version(row), + }; + if let Some(visible) = visible.filter(|version| !version.is_deleted()) { + items.push(KvItem { + key: key.clone(), + value: visible.tuple().clone(), + }); + } + true + }); + items.sort_by(|left, right| left.key.cmp(&right.key)); + Ok(items) + } + + #[allow(dead_code)] + pub(crate) fn commit_tx(&self, txm: &mut WorkerTxManager) -> RS<()> { + let prepared = self.prepare_commit(txm)?; + self.apply_prepared_commit(prepared) + } + + pub(crate) fn prepare_commit(&self, txm: &WorkerTxManager) -> RS { + self.prepare_commit_parts( + txm.snapshot(), + txm.xid(), + txm.staged_relation_ops().clone(), + txm.staged_put_items().into_iter().collect(), + txm.xl_batch(), + ) + } + + pub(crate) fn prepare_worker_kv_commit( + &self, + snapshot: &WorkerSnapshot, + xid: u64, + items: BTreeMap, Option>>, + batch: XLBatch, + ) -> RS { + self.prepare_commit_parts(snapshot, xid, BTreeMap::new(), items, batch) + } + + pub(crate) fn prepare_worker_kv_autocommit( + &self, + xid: u64, + key: Vec, + value: Option>, + batch: XLBatch, + ) -> PreparedWorkerCommit { + PreparedWorkerCommit { + xid, + relation_rows: BTreeMap::new(), + kv_rows: BTreeMap::from([(key, value)]), + batch, + } + } + + pub(crate) fn apply_prepared_commit(&self, prepared: PreparedWorkerCommit) -> RS<()> { + self.apply_relation_rows(&prepared)?; + self.apply_kv_rows(&prepared)?; + Ok(()) + } + + pub(crate) fn replay_batch(&self, batch: XLBatch) -> RS<()> { + for entry in batch.entries { + for op in entry.ops { + match op { + TxOp::Insert(insert) if insert.table_id == 0 => { + self.worker_put_local(insert.key, insert.value, entry.xid)?; + } + TxOp::Delete(delete) if delete.table_id == 0 => { + self.worker_delete_local(delete.key, entry.xid)?; + } + TxOp::Insert(insert) => { + self.apply_relation_replay_insert(insert, entry.xid)?; + } + TxOp::Delete(delete) if delete.table_id != 0 => { + self.apply_relation_replay_delete(delete, entry.xid)?; + } + _ => {} + } + } + } + Ok(()) + } + + pub(crate) fn worker_put_local(&self, key: Vec, value: Vec, xid: u64) -> RS<()> { + write_version_to_kv_store(&self.kv_store, key, Some(value), xid) + } + + pub(crate) fn worker_delete_local(&self, key: Vec, xid: u64) -> RS<()> { + write_version_to_kv_store(&self.kv_store, key, None, xid) + } + + fn prepare_commit_parts( + &self, + snapshot: &WorkerSnapshot, + xid: u64, + relation_rows: BTreeMap, Option>>>, + kv_rows: BTreeMap, Option>>, + batch: XLBatch, + ) -> RS { + self.ensure_no_relation_conflicts(snapshot, xid, &relation_rows)?; + self.ensure_no_kv_conflicts(snapshot, xid, &kv_rows)?; + + Ok(PreparedWorkerCommit { + xid, + relation_rows, + kv_rows, + batch, + }) + } + + fn ensure_no_relation_conflicts( + &self, + snapshot: &WorkerSnapshot, + xid: u64, + relation_rows: &BTreeMap, Option>>>, + ) -> RS<()> { + for (oid, rows) in relation_rows { + let relation = self + .relation_store + .get_sync(oid) + .ok_or_else(|| m_error!(EC::NoSuchElement, format!("no such table {}", oid)))?; + for key in rows.keys() { + let key_tuple = KeyTuple::from(key.clone()); + if relation.get().has_write_conflict(&key_tuple, snapshot)? { + return Err(m_error!( + EC::TxErr, + format!( + "write-write conflict on table {} key {:?} for transaction {}", + oid, key, xid + ) + )); + } + } + } + Ok(()) + } + + fn ensure_no_kv_conflicts( + &self, + snapshot: &WorkerSnapshot, + xid: u64, + kv_rows: &BTreeMap, Option>>, + ) -> RS<()> { + for key in kv_rows.keys() { + let conflict = self + .kv_store + .get_sync(key) + .and_then(|entry| latest_version(entry.get())) + .map(|latest| !snapshot.is_visible(latest.timestamp().c_min())) + .unwrap_or(false); + if conflict { + return Err(m_error!( + EC::TxErr, + format!( + "write-write conflict on key {:?} for transaction {}", + String::from_utf8_lossy(key), + xid + ) + )); + } + } + Ok(()) + } + + fn apply_relation_rows(&self, prepared: &PreparedWorkerCommit) -> RS<()> { + for (oid, rows) in &prepared.relation_rows { + let relation = self + .relation_store + .get_sync(oid) + .ok_or_else(|| m_error!(EC::NoSuchElement, format!("no such table {}", oid)))?; + for (key, value) in rows { + relation + .get() + .write_row(key.clone(), value.clone(), prepared.xid)?; + } + } + Ok(()) + } + + fn apply_kv_rows(&self, prepared: &PreparedWorkerCommit) -> RS<()> { + for (key, value) in &prepared.kv_rows { + write_version_to_kv_store(&self.kv_store, key.clone(), value.clone(), prepared.xid)?; + } + Ok(()) + } + + fn apply_relation_replay_insert(&self, insert: XLInsert, xid: u64) -> RS<()> { + let relation = self + .relation_store + .get_sync(&insert.table_id) + .ok_or_else(|| { + m_error!( + EC::NoSuchElement, + format!("no such table {}", insert.table_id) + ) + })?; + relation.get().write_value(insert.key, insert.value, xid) + } + + fn apply_relation_replay_delete(&self, delete: XLDelete, xid: u64) -> RS<()> { + let relation = self + .relation_store + .get_sync(&delete.table_id) + .ok_or_else(|| { + m_error!( + EC::NoSuchElement, + format!("no such table {}", delete.table_id) + ) + })?; + relation.get().write_delete(delete.key, xid) + } + + fn read_visible_relation_exists( + &self, + oid: OID, + key: &KeyTuple, + snapshot: &WorkerSnapshot, + ) -> RS { + let relation = self + .relation_store + .get_sync(&oid) + .ok_or_else(|| m_error!(EC::NoSuchElement, format!("no such table {}", oid)))?; + relation.get().has_visible_version(key, snapshot) + } + + fn read_visible_relation_value( + &self, + oid: OID, + key: &KeyTuple, + snapshot: &WorkerSnapshot, + ) -> RS>> { + let relation = self + .relation_store + .get_sync(&oid) + .ok_or_else(|| m_error!(EC::NoSuchElement, format!("no such table {}", oid)))?; + relation.get().visible_value(key, snapshot) + } + + fn range_visible_relation( + &self, + oid: OID, + bounds: (Bound<&[u8]>, Bound<&[u8]>), + snapshot: &WorkerSnapshot, + ) -> RS, Vec)>> { + let relation = self + .relation_store + .get_sync(&oid) + .ok_or_else(|| m_error!(EC::NoSuchElement, format!("no such table {}", oid)))?; + relation.get().visible_range(bounds, snapshot) + } + + fn ensure_no_relation_write_conflict( + &self, + oid: OID, + key: &KeyTuple, + snapshot: &WorkerSnapshot, + ) -> RS<()> { + let relation = self + .relation_store + .get_sync(&oid) + .ok_or_else(|| m_error!(EC::NoSuchElement, format!("no such table {}", oid)))?; + if relation.get().has_write_conflict(key, snapshot)? { + return Err(m_error!( + EC::TxErr, + format!( + "write-write conflict on table {} key {:?} for transaction {}", + oid, + key.as_slice(), + snapshot.xid() + ) + )); + } + Ok(()) + } + + fn create_relation_index(&self, oid: OID, table_desc: &TableDesc) -> RS<()> { + let _ = self.relation_store.insert_sync( + oid, + Relation::new( + oid, + self.partition_id, + self.relation_path.clone(), + table_desc, + ), + ); + Ok(()) + } +} + +impl PreparedWorkerCommit { + pub(crate) fn batch(&self) -> &XLBatch { + &self.batch + } +} + +fn new_value_version(xid: u64, value: Vec) -> VersionTuple { + VersionTuple::new(Timestamp::new(xid, u64::MAX), value) +} + +fn write_version_to_kv_store( + kv_store: &SccHashMap, DataRow>, + key: Vec, + value: Option>, + xid: u64, +) -> RS<()> { + let row = kv_store + .get_sync(&key) + .map(|entry| entry.get().clone()) + .unwrap_or_else(|| DataRow::new(0)); + let version = match value { + Some(value) => new_value_version(xid, value), + None => VersionTuple::new_delete(Timestamp::new(xid, u64::MAX)), + }; + row.write_sync(version, None)?; + let _ = kv_store.insert_sync(key, row); + Ok(()) +} + +fn latest_version(row: &DataRow) -> Option { + row.read_latest_sync().ok().flatten() +} + +fn read_visible_version( + row: &DataRow, + snapshot: &crate::contract::snapshot::Snapshot, +) -> Option { + row.read_sync(snapshot).ok().flatten() +} + +fn bounds_to_scan(bounds: &(Bound<&[u8]>, Bound<&[u8]>)) -> (Vec, Vec) { + let start = match bounds.0 { + Included(key) | Excluded(key) => key.to_vec(), + Unbounded => Vec::new(), + }; + let end = match bounds.1 { + Included(key) | Excluded(key) => key.to_vec(), + Unbounded => Vec::new(), + }; + (start, end) +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use std::ops::Bound; + use std::sync::Mutex; + + use mudu::common::id::OID; + use mudu_type::dat_type_id::DatTypeID; + use mudu_type::dt_info::DTInfo; + + use crate::contract::meta_mgr::MetaMgr; + use crate::contract::schema_column::SchemaColumn; + use crate::contract::table_info::TableInfo; + + use super::*; + + struct TestMetaMgr { + tables: Mutex>>, + } + + impl TestMetaMgr { + fn new() -> Self { + Self { + tables: Mutex::new(HashMap::new()), + } + } + } + + #[async_trait::async_trait] + impl MetaMgr for TestMetaMgr { + async fn get_table_by_id(&self, oid: OID) -> RS> { + self.tables + .lock() + .unwrap() + .get(&oid) + .cloned() + .ok_or_else(|| m_error!(EC::NoSuchElement, format!("no such table {}", oid))) + } + + async fn get_table_by_name(&self, name: &String) -> RS>> { + Ok(self + .tables + .lock() + .unwrap() + .values() + .find(|table| table.name() == name) + .cloned()) + } + + async fn create_table(&self, schema: &SchemaTable) -> RS<()> { + let table = TableInfo::new(schema.clone())?.table_desc()?; + self.tables.lock().unwrap().insert(schema.id(), table); + Ok(()) + } + + async fn drop_table(&self, table_id: OID) -> RS<()> { + self.tables.lock().unwrap().remove(&table_id); + Ok(()) + } + } + + fn test_schema() -> SchemaTable { + SchemaTable::new( + "t".to_string(), + vec![SchemaColumn::new( + "id".to_string(), + DatTypeID::I32, + DTInfo::from_text(DatTypeID::I32, String::new()), + )], + vec![SchemaColumn::new( + "v".to_string(), + DatTypeID::I32, + DTInfo::from_text(DatTypeID::I32, String::new()), + )], + ) + } + + fn test_storage() -> (WorkerStorage, OID) { + let mgr = Arc::new(TestMetaMgr::new()); + let storage = WorkerStorage::new( + mgr, + 0, + std::env::temp_dir() + .join(format!( + "worker_storage_test_{}", + mudu::common::id::gen_oid() + )) + .to_string_lossy() + .to_string(), + ); + let schema = test_schema(); + let oid = schema.id(); + futures::executor::block_on(storage.create_table_async(&schema)).unwrap(); + (storage, oid) + } + + fn begin_tx(xid: u64, running: Vec) -> WorkerTxManager { + WorkerTxManager::new(WorkerSnapshot::new(xid, running)) + } + + fn i32_bytes(v: i32) -> Vec { + v.to_be_bytes().to_vec() + } + + #[test] + fn worker_storage_reads_own_writes() { + let (storage, oid) = test_storage(); + let mut tx = begin_tx(10, vec![]); + + storage + .insert(oid, i32_bytes(1), i32_bytes(11), &mut tx) + .unwrap(); + + assert_eq!( + storage.get(oid, &i32_bytes(1), &mut tx).unwrap(), + Some(i32_bytes(11)) + ); + assert!(storage + .contains_key(oid, &KeyTuple::from(i32_bytes(1)), &mut tx) + .unwrap()); + } + + #[test] + fn worker_storage_snapshot_hides_later_commit() { + let (storage, oid) = test_storage(); + let mut tx1 = begin_tx(1, vec![]); + storage + .insert(oid, i32_bytes(1), i32_bytes(10), &mut tx1) + .unwrap(); + storage.commit_tx(&mut tx1).unwrap(); + + let mut old_tx = begin_tx(2, vec![]); + let mut new_tx = begin_tx(3, vec![2]); + storage + .insert(oid, i32_bytes(1), i32_bytes(20), &mut new_tx) + .unwrap(); + storage.commit_tx(&mut new_tx).unwrap(); + + assert_eq!( + storage.get(oid, &i32_bytes(1), &mut old_tx).unwrap(), + Some(i32_bytes(10)) + ); + } + + #[test] + fn worker_storage_range_is_stable_with_snapshot() { + let (storage, oid) = test_storage(); + let mut seed = begin_tx(1, vec![]); + storage + .insert(oid, i32_bytes(1), i32_bytes(10), &mut seed) + .unwrap(); + storage.commit_tx(&mut seed).unwrap(); + + let mut old_tx = begin_tx(2, vec![]); + let mut new_tx = begin_tx(3, vec![2]); + storage + .insert(oid, i32_bytes(2), i32_bytes(20), &mut new_tx) + .unwrap(); + storage.commit_tx(&mut new_tx).unwrap(); + + let rows = storage + .range( + oid, + ( + Bound::Included(i32_bytes(1).as_slice()), + Bound::Included(i32_bytes(9).as_slice()), + ), + &mut old_tx, + ) + .unwrap(); + assert_eq!(rows, vec![(i32_bytes(1), i32_bytes(10))]); + } + + #[test] + fn worker_storage_first_committer_wins() { + let (storage, oid) = test_storage(); + let mut seed = begin_tx(1, vec![]); + storage + .insert(oid, i32_bytes(1), i32_bytes(10), &mut seed) + .unwrap(); + storage.commit_tx(&mut seed).unwrap(); + + let mut tx1 = begin_tx(2, vec![]); + let mut tx2 = begin_tx(3, vec![2]); + storage + .insert(oid, i32_bytes(1), i32_bytes(11), &mut tx1) + .unwrap(); + storage + .insert(oid, i32_bytes(1), i32_bytes(12), &mut tx2) + .unwrap(); + storage.commit_tx(&mut tx1).unwrap(); + let err = storage.commit_tx(&mut tx2).unwrap_err(); + + assert!(err.to_string().contains("write-write conflict")); + } + + #[test] + fn worker_storage_delete_respects_snapshot() { + let (storage, oid) = test_storage(); + let mut seed = begin_tx(1, vec![]); + storage + .insert(oid, i32_bytes(1), i32_bytes(10), &mut seed) + .unwrap(); + storage.commit_tx(&mut seed).unwrap(); + + let mut old_tx = begin_tx(2, vec![]); + let mut delete_tx = begin_tx(3, vec![2]); + assert_eq!( + storage.remove(oid, &i32_bytes(1), &mut delete_tx).unwrap(), + Some(i32_bytes(10)) + ); + storage.commit_tx(&mut delete_tx).unwrap(); + + assert_eq!( + storage.get(oid, &i32_bytes(1), &mut old_tx).unwrap(), + Some(i32_bytes(10)) + ); + let mut fresh_tx = begin_tx(4, vec![]); + assert_eq!( + storage.get(oid, &i32_bytes(1), &mut fresh_tx).unwrap(), + None + ); + } + + #[test] + fn worker_storage_kv_snapshot_hides_later_commit() { + let (storage, _oid) = test_storage(); + storage + .worker_put_local(b"a".to_vec(), b"0".to_vec(), 1) + .unwrap(); + + let snapshot = WorkerSnapshot::new(2, vec![]); + let prepared = storage.prepare_worker_kv_autocommit( + 3, + b"a".to_vec(), + Some(b"1".to_vec()), + XLBatch { entries: vec![] }, + ); + storage.apply_prepared_commit(prepared).unwrap(); + + assert_eq!( + storage.worker_get(b"a", Some(&snapshot)).unwrap(), + Some(b"0".to_vec()) + ); + assert_eq!(storage.worker_get(b"a", None).unwrap(), Some(b"1".to_vec())); + } + + #[test] + fn worker_storage_kv_range_is_stable_with_snapshot() { + let (storage, _oid) = test_storage(); + storage + .worker_put_local(b"a".to_vec(), b"1".to_vec(), 1) + .unwrap(); + let snapshot = WorkerSnapshot::new(2, vec![]); + storage + .worker_put_local(b"b".to_vec(), b"2".to_vec(), 3) + .unwrap(); + + let rows = storage.worker_range(b"a", b"z", Some(&snapshot)).unwrap(); + assert_eq!( + rows, + vec![KvItem { + key: b"a".to_vec(), + value: b"1".to_vec() + }] + ); + } + + #[test] + fn worker_storage_kv_allows_concurrent_commits_on_different_keys() { + let (storage, _oid) = test_storage(); + let snapshot1 = WorkerSnapshot::new(1, vec![]); + let snapshot2 = WorkerSnapshot::new(2, vec![1]); + + let prepared1 = storage + .prepare_worker_kv_commit( + &snapshot1, + snapshot1.xid(), + BTreeMap::from([(b"a".to_vec(), Some(b"1".to_vec()))]), + XLBatch { entries: vec![] }, + ) + .unwrap(); + let prepared2 = storage + .prepare_worker_kv_commit( + &snapshot2, + snapshot2.xid(), + BTreeMap::from([(b"b".to_vec(), Some(b"2".to_vec()))]), + XLBatch { entries: vec![] }, + ) + .unwrap(); + + storage.apply_prepared_commit(prepared1).unwrap(); + storage.apply_prepared_commit(prepared2).unwrap(); + + assert_eq!(storage.worker_get(b"a", None).unwrap(), Some(b"1".to_vec())); + assert_eq!(storage.worker_get(b"b", None).unwrap(), Some(b"2".to_vec())); + } + + #[test] + fn worker_storage_replay_batch_restores_kv_and_relation_rows() { + let (storage, oid) = test_storage(); + let batch = XLBatch { + entries: vec![crate::wal::xl_entry::XLEntry { + xid: 9, + ops: vec![ + TxOp::Begin, + TxOp::Insert(XLInsert { + table_id: 0, + tuple_id: 0, + key: b"k".to_vec(), + value: b"v".to_vec(), + }), + TxOp::Insert(XLInsert { + table_id: oid, + tuple_id: 0, + key: i32_bytes(7), + value: i32_bytes(70), + }), + TxOp::Commit, + ], + }], + }; + + storage.replay_batch(batch).unwrap(); + + assert_eq!(storage.worker_get(b"k", None).unwrap(), Some(b"v".to_vec())); + let mut tx = begin_tx(10, vec![]); + assert_eq!( + storage.get(oid, &i32_bytes(7), &mut tx).unwrap(), + Some(i32_bytes(70)) + ); + } + + #[test] + fn worker_storage_replay_batch_applies_kv_delete() { + let (storage, _oid) = test_storage(); + storage + .worker_put_local(b"k".to_vec(), b"v".to_vec(), 1) + .unwrap(); + + let batch = XLBatch { + entries: vec![crate::wal::xl_entry::XLEntry { + xid: 2, + ops: vec![ + TxOp::Begin, + TxOp::Delete(XLDelete { + table_id: 0, + tuple_id: 0, + key: b"k".to_vec(), + }), + TxOp::Commit, + ], + }], + }; + + storage.replay_batch(batch).unwrap(); + + assert_eq!(storage.worker_get(b"k", None).unwrap(), None); + } +} diff --git a/mudu_kernel/src/server/worker_task.rs b/mudu_kernel/src/server/worker_task.rs new file mode 100644 index 0000000..9b5e531 --- /dev/null +++ b/mudu_kernel/src/server/worker_task.rs @@ -0,0 +1,66 @@ +use std::future::Future; +use std::pin::Pin; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use mudu::common::result::RS; + +pub type WorkerTaskFuture = Pin> + 'static>>; + +pub(in crate::server) struct WorkerTask { + conn_id: Option, + future: WorkerTaskFuture, + queued: Arc, + completed: Arc, + waiting_on: Option, +} + +impl WorkerTask { + pub(in crate::server) fn new(conn_id: Option, future: WorkerTaskFuture) -> Self { + Self { + conn_id, + future, + queued: Arc::new(AtomicBool::new(false)), + completed: Arc::new(AtomicBool::new(false)), + waiting_on: None, + } + } + + pub(in crate::server) fn conn_id(&self) -> Option { + self.conn_id + } + + pub(in crate::server) fn future_mut(&mut self) -> WorkerTaskFutureRef<'_> { + self.future.as_mut() + } + + pub(in crate::server) fn queued(&self) -> &Arc { + &self.queued + } + + pub(in crate::server) fn completed(&self) -> &Arc { + &self.completed + } + + pub(in crate::server) fn clear_queued(&self) { + self.queued.store(false, Ordering::Release); + } + + pub(in crate::server) fn take_waiting_on(&mut self) -> Option { + self.waiting_on.take() + } + + pub(in crate::server) fn set_waiting_on(&mut self, op_id: u64) { + self.waiting_on = Some(op_id); + } +} + +type WorkerTaskFutureRef<'a> = Pin<&'a mut (dyn Future> + 'static)>; + +#[allow(dead_code)] +pub(in crate::server) fn spawn_system_worker_task(future: F) -> WorkerTaskFuture +where + F: Future> + 'static, +{ + Box::pin(async move { future.await }) +} diff --git a/mudu_kernel/src/server/worker_tx_manager.rs b/mudu_kernel/src/server/worker_tx_manager.rs new file mode 100644 index 0000000..1a10563 --- /dev/null +++ b/mudu_kernel/src/server/worker_tx_manager.rs @@ -0,0 +1,173 @@ +use crate::server::worker_snapshot::WorkerSnapshot; +use crate::wal::xl_batch::XLBatch; +use crate::wal::xl_data_op::{XLDelete, XLInsert}; +use crate::wal::xl_entry::{TxOp, XLEntry}; +use mudu::common::id::OID; +use std::collections::BTreeMap; + +pub struct WorkerTxManager { + snapshot: WorkerSnapshot, + staged_puts: BTreeMap, Option>>, + staged_relation_ops: BTreeMap, Option>>>, + write_ops: Vec<(OID, Vec)>, + log_buffer: Vec, +} + +impl WorkerTxManager { + pub fn new(snapshot: WorkerSnapshot) -> Self { + Self { + snapshot, + staged_puts: BTreeMap::new(), + staged_relation_ops: BTreeMap::new(), + write_ops: vec![], + log_buffer: Vec::new(), + } + } + + pub fn xid(&self) -> u64 { + self.snapshot.xid() + } + + pub fn snapshot(&self) -> &WorkerSnapshot { + &self.snapshot + } + + pub fn put(&mut self, key: Vec, value: Vec) { + self.staged_puts.insert(key.clone(), Some(value.clone())); + self.log_buffer.push(TxOp::Insert(XLInsert { + table_id: 0, + tuple_id: 0, + key, + value, + })); + } + + pub fn delete(&mut self, key: Vec) { + self.staged_puts.insert(key.clone(), None); + self.log_buffer.push(TxOp::Delete(XLDelete { + table_id: 0, + tuple_id: 0, + key, + })); + } + + pub fn get(&self, key: &[u8]) -> Option>> { + self.staged_puts.get(key).cloned() + } + + pub fn put_relation(&mut self, oid: OID, key: Vec, value: Vec) { + self.staged_relation_ops + .entry(oid) + .or_default() + .insert(key.clone(), Some(value.clone())); + self.log_buffer.push(TxOp::Insert(XLInsert { + table_id: oid, + tuple_id: 0, + key, + value, + })); + } + + pub fn delete_relation(&mut self, oid: OID, key: Vec) { + self.staged_relation_ops + .entry(oid) + .or_default() + .insert(key.clone(), None); + self.log_buffer.push(TxOp::Delete(XLDelete { + table_id: oid, + tuple_id: 0, + key, + })); + } + + pub fn get_relation(&self, oid: OID, key: &[u8]) -> Option>> { + self.staged_relation_ops + .get(&oid) + .and_then(|rows| rows.get(key).map(|value| value.clone())) + } + + pub fn staged_relation_items_in_range( + &self, + oid: OID, + start_key: &[u8], + end_key: &[u8], + ) -> Vec<(Vec, Option>)> { + self.staged_relation_ops + .get(&oid) + .map(|rows| { + rows.iter() + .filter(|(key, _)| is_key_in_range(key, start_key, end_key)) + .map(|(key, value)| (key.clone(), value.clone())) + .collect() + }) + .unwrap_or_default() + } + + pub fn staged_relation_ops(&self) -> &BTreeMap, Option>>> { + &self.staged_relation_ops + } + + #[allow(dead_code)] + pub fn drain_relation_ops(&mut self) -> BTreeMap, Option>>> { + std::mem::take(&mut self.staged_relation_ops) + } + + pub fn staged_items_in_range( + &self, + start_key: &[u8], + end_key: &[u8], + ) -> Vec<(Vec, Option>)> { + self.staged_puts + .iter() + .filter(|(key, _)| is_key_in_range(key, start_key, end_key)) + .map(|(key, value)| (key.clone(), value.clone())) + .collect() + } + + pub fn staged_put_items(&self) -> BTreeMap, Option>> { + self.staged_puts.clone() + } + + pub fn write_ops(&self) -> &Vec<(OID, Vec)> { + &self.write_ops + } + + pub fn build_write_ops(&mut self) { + for (k, _) in self.staged_puts.iter() { + self.write_ops.push((0, k.clone())); + } + + for (oid, ops) in self.staged_relation_ops.iter() { + for (k, _) in ops.iter() { + self.write_ops.push((*oid, k.clone())); + } + } + self.write_ops.sort(); + } + + pub fn xl_batch(&self) -> XLBatch { + let xid = self.snapshot.xid(); + let mut ops = Vec::with_capacity(self.log_buffer.len() + 2); + ops.push(TxOp::Begin); + ops.extend(self.log_buffer.clone()); + ops.push(TxOp::Commit); + XLBatch { + entries: vec![XLEntry { xid, ops }], + } + } + + pub fn into_xl_batch(self) -> XLBatch { + let xid = self.snapshot.xid(); + let mut ops = Vec::with_capacity(self.log_buffer.len() + 2); + ops.push(TxOp::Begin); + ops.extend(self.log_buffer); + ops.push(TxOp::Commit); + XLBatch { + entries: vec![XLEntry { xid, ops }], + } + } +} + +fn is_key_in_range(key: &[u8], start_key: &[u8], end_key: &[u8]) -> bool { + key >= start_key && (end_key.is_empty() || key < end_key) +} diff --git a/mudu_kernel/src/server/x_contract.rs b/mudu_kernel/src/server/x_contract.rs new file mode 100644 index 0000000..1a90cfd --- /dev/null +++ b/mudu_kernel/src/server/x_contract.rs @@ -0,0 +1,1067 @@ +use async_trait::async_trait; +use mudu::common::buf::Buf; +use mudu::common::id::{AttrIndex, OID}; +use mudu::common::result::RS; +use mudu::common::xid::XID; +use mudu::error::ec::EC; +use mudu::m_error; +use mudu_contract::tuple::build_tuple::build_tuple; +use mudu_contract::tuple::tuple_binary::TupleBinary as TupleRaw; +use mudu_contract::tuple::update_tuple::update_tuple; +use std::collections::HashMap; +use std::ops::Bound; +use std::sync::{Arc, Mutex}; + +use crate::contract::meta_mgr::MetaMgr; +use crate::contract::schema_table::SchemaTable; +use crate::contract::table_desc::TableDesc; +use crate::server::worker_snapshot::{KvItem, WorkerSnapshot, WorkerSnapshotMgr}; +use crate::server::worker_storage::{PreparedWorkerCommit, WorkerStorage}; +use crate::server::worker_tx_manager::WorkerTxManager; +use crate::server::x_lock_mgr::XLockMgr; +use crate::wal::worker_log::ChunkedWorkerLogBackend; +use crate::wal::xl_batch::{new_xl_batch_writer, XLBatch}; +use crate::x_engine::api::{ + AlterTable, Filter, OptDelete, OptInsert, OptRead, OptUpdate, Predicate, RSCursor, RangeData, + TupleRow, VecDatum, VecSelTerm, XContract, +}; +type DatBin = Buf; + +pub struct IoUringXContract { + inner: Mutex, + // commit_gate: AsyncMutex<()>, +} + +struct IoUringXContractInner { + meta_mgr: Arc, + storage: Arc, + log: Option, + snapshot_mgr: WorkerSnapshotMgr, + tx_ctx: HashMap, + tx_lock: XLockMgr, +} + +struct VecCursor { + inner: Mutex, +} + +struct VecCursorInner { + rows: Vec, + index: usize, +} + +impl IoUringXContract { + pub fn new(meta_mgr: Arc) -> Self { + Self::with_log_and_data_dir(meta_mgr, None, 0, default_worker_storage_data_dir()) + } + + pub fn with_log(meta_mgr: Arc, log: Option) -> Self { + Self::with_log_and_data_dir(meta_mgr, log, 0, default_worker_storage_data_dir()) + } + + pub fn with_log_and_data_dir( + meta_mgr: Arc, + log: Option, + partition_id: OID, + data_dir: String, + ) -> Self { + Self { + inner: Mutex::new(IoUringXContractInner { + meta_mgr: meta_mgr.clone(), + storage: Arc::new(WorkerStorage::new(meta_mgr, partition_id, data_dir)), + log, + snapshot_mgr: WorkerSnapshotMgr::default(), + tx_ctx: HashMap::new(), + tx_lock: XLockMgr::new(), + }), + } + } + + pub fn with_worker_log(log: ChunkedWorkerLogBackend) -> Self { + Self::with_worker_log_and_data_dir(log, 0, default_worker_storage_data_dir()) + } + + pub fn with_worker_log_and_data_dir( + log: ChunkedWorkerLogBackend, + partition_id: OID, + data_dir: String, + ) -> Self { + let meta_mgr: Arc = Arc::new(NoopMetaMgr); + Self { + inner: Mutex::new(IoUringXContractInner { + meta_mgr: meta_mgr.clone(), + storage: Arc::new(WorkerStorage::new(meta_mgr, partition_id, data_dir)), + log: Some(log.clone()), + snapshot_mgr: WorkerSnapshotMgr::default(), + tx_ctx: HashMap::new(), + tx_lock: XLockMgr::new(), + }), + } + } + + fn lock_inner(&self) -> RS> { + self.inner + .lock() + .map_err(|_| m_error!(EC::InternalErr, "io_uring xcontract lock poisoned")) + } + + pub fn worker_log(&self) -> Option { + self.lock_inner().ok().and_then(|guard| guard.log.clone()) + } + + pub fn worker_begin_tx(&self) -> RS { + let mut guard = self.lock_inner()?; + Ok(guard.snapshot_mgr.begin_tx()) + } + + pub fn worker_rollback_tx(&self, xid: u64) -> RS<()> { + self.lock_inner()?.snapshot_mgr.end_tx(xid) + } + + pub fn worker_put(&self, key: Vec, value: Vec) -> RS<()> { + let prepared = { + let mut guard = self.lock_inner()?; + let xid = guard.snapshot_mgr.alloc_committed_ts(); + ( + guard.storage.clone(), + guard.log.clone(), + guard.storage.prepare_worker_kv_autocommit( + xid, + key.clone(), + Some(value.clone()), + single_put_batch(xid, key, value), + ), + ) + }; + let (storage, log, prepared) = prepared; + if let Some(log) = log { + new_xl_batch_writer(log).append_sync(prepared.batch())?; + } + storage.apply_prepared_commit(prepared) + } + + pub async fn worker_put_async(&self, key: Vec, value: Vec) -> RS<()> { + let (storage, log, prepared) = { + let mut guard = self.lock_inner()?; + let xid = guard.snapshot_mgr.alloc_committed_ts(); + ( + guard.storage.clone(), + guard.log.clone(), + guard.storage.prepare_worker_kv_autocommit( + xid, + key.clone(), + Some(value.clone()), + single_put_batch(xid, key, value), + ), + ) + }; + if let Some(log) = log { + new_xl_batch_writer(log).append(prepared.batch()).await?; + } + storage.apply_prepared_commit(prepared) + } + + pub fn worker_delete(&self, key: &[u8]) -> RS<()> { + let key = key.to_vec(); + let prepared = { + let mut guard = self.lock_inner()?; + let xid = guard.snapshot_mgr.alloc_committed_ts(); + ( + guard.storage.clone(), + guard.log.clone(), + guard.storage.prepare_worker_kv_autocommit( + xid, + key.clone(), + None, + single_delete_batch(xid, key), + ), + ) + }; + let (storage, log, prepared) = prepared; + if let Some(log) = log { + new_xl_batch_writer(log).append_sync(prepared.batch())?; + } + storage.apply_prepared_commit(prepared) + } + + pub async fn worker_delete_async(&self, key: &[u8]) -> RS<()> { + let key = key.to_vec(); + let (storage, log, prepared) = { + let mut guard = self.lock_inner()?; + let xid = guard.snapshot_mgr.alloc_committed_ts(); + ( + guard.storage.clone(), + guard.log.clone(), + guard.storage.prepare_worker_kv_autocommit( + xid, + key.clone(), + None, + single_delete_batch(xid, key), + ), + ) + }; + if let Some(log) = log { + new_xl_batch_writer(log).append(prepared.batch()).await?; + } + storage.apply_prepared_commit(prepared) + } + + pub fn worker_get(&self, key: &[u8]) -> RS>> { + let storage = { self.lock_inner()?.storage.clone() }; + storage.worker_get(key, None) + } + + pub fn worker_get_with_snapshot( + &self, + snapshot: &WorkerSnapshot, + key: &[u8], + ) -> RS>> { + let storage = { self.lock_inner()?.storage.clone() }; + storage.worker_get(key, Some(snapshot)) + } + + pub fn worker_range_scan(&self, start_key: &[u8], end_key: &[u8]) -> RS> { + let storage = { self.lock_inner()?.storage.clone() }; + storage.worker_range(start_key, end_key, None) + } + + pub fn worker_range_scan_with_snapshot( + &self, + snapshot: &WorkerSnapshot, + start_key: &[u8], + end_key: &[u8], + ) -> RS> { + let storage = { self.lock_inner()?.storage.clone() }; + storage.worker_range(start_key, end_key, Some(snapshot)) + } + + pub fn worker_commit_put_batch( + &self, + snapshot: &WorkerSnapshot, + xid: u64, + items: std::collections::BTreeMap, Option>>, + batch: XLBatch, + ) -> RS<()> { + if items.is_empty() { + return self.worker_rollback_tx(xid); + } + let (storage, log, prepared) = { + let guard = self.lock_inner()?; + let prepared = guard + .storage + .prepare_worker_kv_commit(snapshot, xid, items, batch)?; + (guard.storage.clone(), guard.log.clone(), prepared) + }; + if let Some(log) = log { + new_xl_batch_writer(log.clone()).append_sync(prepared.batch())?; + log.flush()?; + } + storage.apply_prepared_commit(prepared)?; + self.worker_rollback_tx(xid) + } + + pub async fn worker_commit_put_batch_async( + &self, + snapshot: &WorkerSnapshot, + xid: u64, + items: std::collections::BTreeMap, Option>>, + batch: XLBatch, + ) -> RS<()> { + if items.is_empty() { + return self.worker_rollback_tx(xid); + } + let (storage, log, prepared) = { + let guard = self.lock_inner()?; + let prepared = guard + .storage + .prepare_worker_kv_commit(snapshot, xid, items, batch)?; + (guard.storage.clone(), guard.log.clone(), prepared) + }; + if let Some(log) = log { + new_xl_batch_writer(log.clone()) + .append(prepared.batch()) + .await?; + log.flush_async().await?; + } + storage.apply_prepared_commit(prepared)?; + self.worker_rollback_tx(xid) + } + + pub fn replay_worker_log_batch(&self, batch: XLBatch) -> RS<()> { + let max_xid = batch.entries.iter().map(|entry| entry.xid).max(); + let storage = { + let mut guard = self.lock_inner()?; + if let Some(max_xid) = max_xid { + guard.snapshot_mgr.observe_committed_ts(max_xid); + } + guard.storage.clone() + }; + storage.replay_batch(batch) + } +} + +fn default_worker_storage_data_dir() -> String { + std::env::temp_dir() + .join(format!( + "mududb-worker-storage-{}", + mudu::common::id::gen_oid() + )) + .to_string_lossy() + .to_string() +} + +struct NoopMetaMgr; + +#[async_trait] +impl MetaMgr for NoopMetaMgr { + async fn get_table_by_id(&self, oid: OID) -> RS> { + Err(m_error!( + EC::NoSuchElement, + format!("no such table {} in worker-local io_uring xcontract", oid) + )) + } + + async fn get_table_by_name(&self, _name: &String) -> RS>> { + Ok(None) + } + + async fn create_table(&self, _schema: &SchemaTable) -> RS<()> { + Err(m_error!( + EC::NotImplemented, + "create table is not available in worker-local io_uring xcontract" + )) + } + + async fn drop_table(&self, _table_id: OID) -> RS<()> { + Err(m_error!( + EC::NotImplemented, + "drop table is not available in worker-local io_uring xcontract" + )) + } +} + +impl IoUringXContractInner { + fn begin_tx(&mut self) -> XID { + let snapshot = self.snapshot_mgr.begin_tx(); + let xid = snapshot.xid() as XID; + self.tx_ctx.insert(xid, WorkerTxManager::new(snapshot)); + xid + } + + #[allow(dead_code)] + fn commit_tx(&mut self, xid: XID) -> RS<()> { + let mut tx = self.take_tx(xid)?; + let result = self.storage.commit_tx(&mut tx); + self.end_tx(xid); + result + } + + fn commit_tx_prepare( + &mut self, + xid: XID, + ) -> RS<( + Option, + WorkerTxManager, + Arc, + Option, + )> { + let mut tx = self.take_tx(xid)?; + tx.build_write_ops(); + let can_commit = self.tx_lock.try_lock_some(xid, tx.write_ops()); + if can_commit { + let prepared = self.storage.prepare_commit(&tx)?; + Ok((Some(prepared), tx, self.storage.clone(), self.log.clone())) + } else { + Ok((None, tx, self.storage.clone(), self.log.clone())) + } + } + + fn finish_tx(&mut self, xid: XID) { + self.end_tx(xid); + } + + fn abort_tx(&mut self, xid: XID) -> RS<()> { + let _ = self.take_tx(xid)?; + self.end_tx(xid); + Ok(()) + } + + fn insert( + &mut self, + desc: Arc, + xid: XID, + table_id: OID, + keys: &VecDatum, + values: &VecDatum, + _opt_insert: &OptInsert, + ) -> RS<()> { + let key = build_key_tuple(keys, &desc)?; + let value = build_value_tuple(values, &desc)?; + let mut tx = self.take_tx(xid)?; + let result = self.storage.insert(table_id, key, value, &mut tx); + self.tx_ctx.insert(xid, tx); + result + } + + fn read_key( + &mut self, + desc: Arc, + xid: XID, + table_id: OID, + pred_key: &VecDatum, + select: &VecSelTerm, + _opt_read: &OptRead, + ) -> RS>> { + let key = build_key_tuple(pred_key, &desc)?; + let mut tx = self.take_tx(xid)?; + let opt_value = self.storage.get(table_id, &key, &mut tx)?; + self.tx_ctx.insert(xid, tx); + match opt_value { + Some(value) => project_selected_fields(&desc, &key, &value, select).map(Some), + None => Ok(None), + } + } + + fn read_range( + &mut self, + desc: Arc, + xid: XID, + table_id: OID, + pred_key: &RangeData, + pred_non_key: &Predicate, + select: &VecSelTerm, + _opt_read: &OptRead, + ) -> RS> { + ensure_supported_predicate(pred_non_key)?; + let start = build_bound_key(pred_key.start(), &desc)?; + let end = build_bound_key(pred_key.end(), &desc)?; + let mut tx = self.take_tx(xid)?; + let rows = self.storage.range(table_id, (start, end), &mut tx)?; + self.tx_ctx.insert(xid, tx); + let projected = rows + .into_iter() + .map(|(key, value)| { + project_selected_fields(&desc, &key, &value, select).map(TupleRow::new) + }) + .collect::>>()?; + Ok(Arc::new(VecCursor { + inner: Mutex::new(VecCursorInner { + rows: projected, + index: 0, + }), + })) + } + + fn delete( + &mut self, + desc: Arc, + xid: XID, + table_id: OID, + pred_key: &VecDatum, + pred_non_key: &Predicate, + _opt_delete: &OptDelete, + ) -> RS { + ensure_supported_predicate(pred_non_key)?; + let key = build_key_tuple(pred_key, &desc)?; + let mut tx = self.take_tx(xid)?; + let deleted = self.storage.remove(table_id, &key, &mut tx)?; + self.tx_ctx.insert(xid, tx); + Ok(usize::from(deleted.is_some())) + } + + fn update( + &mut self, + desc: Arc, + xid: XID, + table_id: OID, + pred_key: &VecDatum, + pred_non_key: &Predicate, + values: &VecDatum, + _opt_update: &OptUpdate, + ) -> RS { + ensure_supported_predicate(pred_non_key)?; + let key = build_key_tuple(pred_key, &desc)?; + let mut tx = self.take_tx(xid)?; + let current = self.storage.get(table_id, &key, &mut tx)?; + let Some(current) = current else { + self.tx_ctx.insert(xid, tx); + return Ok(0); + }; + let updated = apply_value_update(¤t, values, &desc)?; + let result = self.storage.insert(table_id, key, updated, &mut tx); + self.tx_ctx.insert(xid, tx); + result.map(|()| 1) + } + + fn take_tx(&mut self, xid: XID) -> RS { + self.tx_ctx + .remove(&xid) + .ok_or_else(|| m_error!(EC::NoSuchElement, format!("no such transaction {}", xid))) + } + + fn end_tx(&mut self, xid: XID) { + let _ = self.snapshot_mgr.end_tx(xid as u64); + } +} + +#[async_trait] +impl XContract for IoUringXContract { + async fn create_table(&self, _xid: XID, schema: &SchemaTable) -> RS<()> { + let storage = { + let guard = self.lock_inner()?; + guard.storage.clone() + }; + storage.create_table_async(schema).await + } + + async fn drop_table(&self, _xid: XID, oid: OID) -> RS<()> { + let storage = { + let guard = self.lock_inner()?; + guard.storage.clone() + }; + storage.drop_table_async(oid).await + } + + async fn alter_table(&self, _xid: XID, _oid: OID, _alter_table: &AlterTable) -> RS<()> { + Err(m_error!( + EC::NotImplemented, + "alter table is not implemented" + )) + } + + async fn begin_tx(&self) -> RS { + Ok(self.lock_inner()?.begin_tx()) + } + + async fn commit_tx(&self, xid: XID) -> RS<()> { + let prepared = { + let mut guard = self.lock_inner()?; + + guard.commit_tx_prepare(xid) + }; + let result = match prepared { + Ok((opt_prepared, tx, storage, log)) => { + if let Some(prepared) = opt_prepared { + if let Some(log) = log { + new_xl_batch_writer(log.clone()) + .append(prepared.batch()) + .await?; + log.flush_async().await?; + } + storage.apply_prepared_commit(prepared)?; + { + let guard = self.inner.lock().unwrap(); + guard.tx_lock.release(xid, tx.write_ops()); + Ok(()) + } + } else { + let guard = self.inner.lock().unwrap(); + guard.tx_lock.release(xid, tx.write_ops()); + Ok(()) + } + } + Err(err) => Err(err), + }; + self.lock_inner()?.finish_tx(xid); + result + } + + async fn abort_tx(&self, xid: XID) -> RS<()> { + self.lock_inner()?.abort_tx(xid) + } + + async fn update( + &self, + xid: XID, + table_id: OID, + pred_key: &VecDatum, + pred_non_key: &Predicate, + values: &VecDatum, + opt_update: &OptUpdate, + ) -> RS { + let meta_mgr = { self.lock_inner()?.meta_mgr.clone() }; + let desc = meta_mgr.get_table_by_id(table_id).await?; + self.lock_inner()?.update( + desc, + xid, + table_id, + pred_key, + pred_non_key, + values, + opt_update, + ) + } + + async fn read_key( + &self, + xid: XID, + table_id: OID, + pred_key: &VecDatum, + select: &VecSelTerm, + opt_read: &OptRead, + ) -> RS>> { + let meta_mgr = { self.lock_inner()?.meta_mgr.clone() }; + let desc = meta_mgr.get_table_by_id(table_id).await?; + self.lock_inner()? + .read_key(desc, xid, table_id, pred_key, select, opt_read) + } + + async fn read_range( + &self, + xid: XID, + table_id: OID, + pred_key: &RangeData, + pred_non_key: &Predicate, + select: &VecSelTerm, + opt_read: &OptRead, + ) -> RS> { + let meta_mgr = { self.lock_inner()?.meta_mgr.clone() }; + let desc = meta_mgr.get_table_by_id(table_id).await?; + self.lock_inner()?.read_range( + desc, + xid, + table_id, + pred_key, + pred_non_key, + select, + opt_read, + ) + } + + async fn delete( + &self, + xid: XID, + table_id: OID, + pred_key: &VecDatum, + pred_non_key: &Predicate, + opt_delete: &OptDelete, + ) -> RS { + let meta_mgr = { self.lock_inner()?.meta_mgr.clone() }; + let desc = meta_mgr.get_table_by_id(table_id).await?; + self.lock_inner()? + .delete(desc, xid, table_id, pred_key, pred_non_key, opt_delete) + } + + async fn insert( + &self, + xid: XID, + table_id: OID, + keys: &VecDatum, + values: &VecDatum, + opt_insert: &OptInsert, + ) -> RS<()> { + let meta_mgr = { self.lock_inner()?.meta_mgr.clone() }; + let desc = meta_mgr.get_table_by_id(table_id).await?; + self.lock_inner()? + .insert(desc, xid, table_id, keys, values, opt_insert) + } +} + +#[async_trait] +impl RSCursor for VecCursor { + async fn next(&self) -> RS> { + let mut inner = self + .inner + .lock() + .map_err(|_| m_error!(EC::InternalErr, "range cursor lock poisoned"))?; + if inner.index >= inner.rows.len() { + return Ok(None); + } + let row = inner.rows[inner.index].clone(); + inner.index += 1; + Ok(Some(row)) + } +} + +fn ensure_supported_predicate(predicate: &Predicate) -> RS<()> { + match predicate { + Predicate::CNF(items) | Predicate::DNF(items) if items.is_empty() => Ok(()), + Predicate::CNF(items) | Predicate::DNF(items) => { + let _ = items + .iter() + .flatten() + .map(|(_oid, _filter): &(AttrIndex, Filter)| ()) + .count(); + Err(m_error!( + EC::NotImplemented, + "non-key predicates are not implemented in io_uring xcontract" + )) + } + } +} + +fn build_key_tuple(data: &VecDatum, desc: &TableDesc) -> RS> { + build_tuple_for::(data.data(), desc) +} + +fn build_value_tuple(data: &VecDatum, desc: &TableDesc) -> RS> { + build_tuple_for::(data.data(), desc) +} + +fn build_tuple_for( + data: &Vec<(AttrIndex, DatBin)>, + desc: &TableDesc, +) -> RS> { + let mut vec_data = data.clone(); + let mut ok = true; + vec_data.sort_by(|(id1, _), (id2, _)| { + let (f1, f2) = (desc.get_attr(*id1), desc.get_attr(*id2)); + if f1.is_primary() != IS_KEY || f2.is_primary() != IS_KEY { + ok = false; + } + f1.datum_index().cmp(&f2.datum_index()) + }); + if !ok { + return Err(m_error!(EC::TupleErr)); + } + let values: Vec<_> = vec_data.into_iter().map(|(_, v)| v).collect(); + let tuple_desc = if IS_KEY { + desc.key_desc() + } else { + desc.value_desc() + }; + if tuple_desc.field_count() != values.len() { + return Err(m_error!(EC::TupleErr)); + } + build_tuple(&values, tuple_desc) +} + +fn build_bound_key( + bound: &Bound>, + desc: &TableDesc, +) -> RS> { + match bound { + Bound::Included(values) => { + let tuple = build_key_tuple(&VecDatum::new(values.clone()), desc)?; + Ok(Bound::Included(Box::leak(tuple.into_boxed_slice()))) + } + Bound::Excluded(values) => { + let tuple = build_key_tuple(&VecDatum::new(values.clone()), desc)?; + Ok(Bound::Excluded(Box::leak(tuple.into_boxed_slice()))) + } + Bound::Unbounded => Ok(Bound::Unbounded), + } +} + +fn project_selected_fields( + desc: &TableDesc, + key: &[u8], + value: &[u8], + select: &VecSelTerm, +) -> RS> { + let mut tuple_ret = vec![]; + for i in select.vec() { + let f = desc.get_attr(*i); + let index = f.datum_index(); + let field_desc = if f.is_primary() { + desc.key_desc().get_field_desc(index) + } else { + desc.value_desc().get_field_desc(index) + }; + let src = if f.is_primary() { key } else { value }; + let slice = field_desc.get(src)?; + tuple_ret.push(slice.to_vec()); + } + Ok(tuple_ret) +} + +fn apply_value_update(current: &TupleRaw, values: &VecDatum, desc: &TableDesc) -> RS> { + let mut updated = current.clone(); + let mut data = values.data().clone(); + data.sort_by(|(id1, _), (id2, _)| id1.cmp(id2)); + for (id, dat) in data.iter() { + let mut delta = vec![]; + update_tuple(*id as _, dat, desc.value_desc(), current, &mut delta)?; + for item in delta { + item.apply_to(&mut updated); + } + } + Ok(updated) +} + +fn single_put_batch(xid: u64, key: Vec, value: Vec) -> XLBatch { + XLBatch { + entries: vec![crate::wal::xl_entry::XLEntry { + xid, + ops: vec![ + crate::wal::xl_entry::TxOp::Begin, + crate::wal::xl_entry::TxOp::Insert(crate::wal::xl_data_op::XLInsert { + table_id: 0, + tuple_id: 0, + key, + value, + }), + crate::wal::xl_entry::TxOp::Commit, + ], + }], + } +} + +fn single_delete_batch(xid: u64, key: Vec) -> XLBatch { + XLBatch { + entries: vec![crate::wal::xl_entry::XLEntry { + xid, + ops: vec![ + crate::wal::xl_entry::TxOp::Begin, + crate::wal::xl_entry::TxOp::Delete(crate::wal::xl_data_op::XLDelete { + table_id: 0, + tuple_id: 0, + key, + }), + crate::wal::xl_entry::TxOp::Commit, + ], + }], + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::contract::schema_column::SchemaColumn; + use crate::contract::table_info::TableInfo; + use crate::wal::worker_log::{decode_frames, ChunkedWorkerLogBackend, WorkerLogLayout}; + use crate::wal::xl_data_op::XLInsert; + use crate::wal::xl_entry::TxOp; + use futures::executor::block_on; + use mudu::common::id::gen_oid; + use mudu_type::dat_type_id::DatTypeID; + use mudu_type::dt_info::DTInfo; + use std::collections::HashMap; + use std::env::temp_dir; + + struct TestMetaMgr { + tables: Mutex>>, + } + + impl TestMetaMgr { + fn new() -> Self { + Self { + tables: Mutex::new(HashMap::new()), + } + } + } + + #[async_trait] + impl MetaMgr for TestMetaMgr { + async fn get_table_by_id(&self, oid: OID) -> RS> { + self.tables + .lock() + .unwrap() + .get(&oid) + .cloned() + .ok_or_else(|| m_error!(EC::NoSuchElement, format!("no such table {}", oid))) + } + + async fn get_table_by_name(&self, name: &String) -> RS>> { + Ok(self + .tables + .lock() + .unwrap() + .values() + .find(|table| table.name() == name) + .cloned()) + } + + async fn create_table(&self, schema: &SchemaTable) -> RS<()> { + let table = TableInfo::new(schema.clone())?.table_desc()?; + self.tables.lock().unwrap().insert(schema.id(), table); + Ok(()) + } + + async fn drop_table(&self, table_id: OID) -> RS<()> { + self.tables.lock().unwrap().remove(&table_id); + Ok(()) + } + } + + fn test_schema() -> SchemaTable { + SchemaTable::new( + "t".to_string(), + vec![SchemaColumn::new( + "id".to_string(), + DatTypeID::I32, + DTInfo::from_text(DatTypeID::I32, String::new()), + )], + vec![SchemaColumn::new( + "v".to_string(), + DatTypeID::I32, + DTInfo::from_text(DatTypeID::I32, String::new()), + )], + ) + } + + fn datum(v: i32) -> Vec { + v.to_be_bytes().to_vec() + } + + fn key_row(v: i32) -> VecDatum { + VecDatum::new(vec![(0, datum(v))]) + } + + fn value_row(v: i32) -> VecDatum { + VecDatum::new(vec![(1, datum(v))]) + } + + #[test] + fn relation_commit_log_round_trips() { + let mgr = Arc::new(TestMetaMgr::new()); + let storage = WorkerStorage::new( + mgr.clone(), + 0, + std::env::temp_dir() + .join(format!( + "xcontract_relation_log_{}", + mudu::common::id::gen_oid() + )) + .to_string_lossy() + .to_string(), + ); + let schema = test_schema(); + let table_id = schema.id(); + block_on(storage.create_table_async(&schema)).unwrap(); + let mut txm = WorkerTxManager::new(crate::server::worker_snapshot::WorkerSnapshot::new( + 9, + vec![], + )); + storage + .insert(table_id, b"k1".to_vec(), b"v1".to_vec(), &mut txm) + .unwrap(); + storage.remove(table_id, b"k1", &mut txm).unwrap(); + let prepared = storage.prepare_commit(&txm).unwrap(); + + assert_eq!(prepared.batch().entries.len(), 1); + assert_eq!(prepared.batch().entries[0].xid, 9); + assert!(matches!(prepared.batch().entries[0].ops[0], TxOp::Begin)); + } + + #[test] + fn iouring_xcontract_commit_persists_relation_log() { + let dir = temp_dir().join(format!("iouring_xcontract_log_{}", gen_oid())); + let layout = WorkerLogLayout::new(dir, gen_oid(), 4096).unwrap(); + let log = ChunkedWorkerLogBackend::new(layout.clone()).unwrap(); + let meta_mgr = Arc::new(TestMetaMgr::new()); + let schema = test_schema(); + let table_id = schema.id(); + let contract = IoUringXContract::with_log(meta_mgr, Some(log)); + + block_on(contract.create_table(0, &schema)).unwrap(); + let xid = block_on(contract.begin_tx()).unwrap(); + block_on(contract.insert( + xid, + table_id, + &key_row(1), + &value_row(10), + &OptInsert::default(), + )) + .unwrap(); + block_on(contract.commit_tx(xid)).unwrap(); + + let bytes = std::fs::read(layout.chunk_path(0)).unwrap(); + let frames = decode_frames(&bytes).unwrap(); + let decoded = crate::wal::xl_batch::decode_xl_batches(&frames).unwrap(); + assert_eq!(decoded.len(), 1); + let insert = decoded[0].entries[0] + .ops + .iter() + .find_map(|op| match op { + TxOp::Insert(insert) => Some(insert), + _ => None, + }) + .unwrap(); + assert_eq!(insert.table_id, table_id); + assert_eq!( + insert.key, + build_key_tuple(&key_row(1), &meta_table(&schema).unwrap()).unwrap() + ); + assert_eq!( + insert.value, + build_value_tuple(&value_row(10), &meta_table(&schema).unwrap()).unwrap() + ); + } + + #[test] + fn iouring_xcontract_replay_restores_worker_kv_and_relation_rows() { + let meta_mgr = Arc::new(TestMetaMgr::new()); + let schema = test_schema(); + let table_id = schema.id(); + let contract = IoUringXContract::with_log(meta_mgr, None); + + block_on(contract.create_table(0, &schema)).unwrap(); + let batch = XLBatch { + entries: vec![crate::wal::xl_entry::XLEntry { + xid: 11, + ops: vec![ + TxOp::Begin, + TxOp::Insert(XLInsert { + table_id: 0, + tuple_id: 0, + key: b"wk".to_vec(), + value: b"wv".to_vec(), + }), + TxOp::Insert(XLInsert { + table_id, + tuple_id: 0, + key: build_key_tuple(&key_row(3), &meta_table(&schema).unwrap()).unwrap(), + value: build_value_tuple(&value_row(30), &meta_table(&schema).unwrap()) + .unwrap(), + }), + TxOp::Commit, + ], + }], + }; + + contract.replay_worker_log_batch(batch).unwrap(); + + assert_eq!(contract.worker_get(b"wk").unwrap(), Some(b"wv".to_vec())); + + let xid = block_on(contract.begin_tx()).unwrap(); + let relation = block_on(contract.read_key( + xid, + table_id, + &key_row(3), + &VecSelTerm::new(vec![1]), + &OptRead::default(), + )) + .unwrap(); + assert_eq!(relation, Some(vec![datum(30)])); + } + + #[test] + fn iouring_xcontract_replay_applies_worker_kv_delete() { + let contract = IoUringXContract::with_worker_log( + ChunkedWorkerLogBackend::new( + WorkerLogLayout::new( + temp_dir().join(format!("iouring_xcontract_worker_log_{}", gen_oid())), + gen_oid(), + 4096, + ) + .unwrap(), + ) + .unwrap(), + ); + + contract.worker_put(b"wk".to_vec(), b"wv".to_vec()).unwrap(); + let batch = XLBatch { + entries: vec![crate::wal::xl_entry::XLEntry { + xid: 7, + ops: vec![ + TxOp::Begin, + TxOp::Delete(crate::wal::xl_data_op::XLDelete { + table_id: 0, + tuple_id: 0, + key: b"wk".to_vec(), + }), + TxOp::Commit, + ], + }], + }; + + contract.replay_worker_log_batch(batch).unwrap(); + + assert_eq!(contract.worker_get(b"wk").unwrap(), None); + } + + fn meta_table(schema: &SchemaTable) -> RS> { + TableInfo::new(schema.clone())?.table_desc() + } +} diff --git a/mudu_kernel/src/server/x_lock_mgr.rs b/mudu_kernel/src/server/x_lock_mgr.rs new file mode 100644 index 0000000..16d143a --- /dev/null +++ b/mudu_kernel/src/server/x_lock_mgr.rs @@ -0,0 +1,40 @@ +use mudu::common::id::OID; +use std::cell::RefCell; +use std::collections::HashMap; + +pub struct XLockMgr { + lock: RefCell, OID>>>, +} + +impl XLockMgr { + pub fn new() -> Self { + Self { + lock: Default::default(), + } + } + + pub fn try_lock_some(&self, oid: OID, table_keys: &Vec<(OID, Vec)>) -> bool { + let mut lock = self.lock.borrow_mut(); + for (table_oid, key) in table_keys.iter() { + let map = lock.entry(table_oid.clone()).or_default(); + if map.contains_key(key) { + return false; + } else { + map.insert(key.clone(), oid); + } + } + true + } + + pub fn release(&self, oid: OID, table_keys: &Vec<(OID, Vec)>) { + let mut lock = self.lock.borrow_mut(); + for (table_oid, key) in table_keys.iter() { + let map = lock.entry(table_oid.clone()).or_default(); + if let Some(tx) = map.get(key) { + if *tx == oid { + map.remove(key); + } + } + } + } +} diff --git a/mudu_kernel/src/server_ur/inflight_op.rs b/mudu_kernel/src/server_ur/inflight_op.rs deleted file mode 100644 index 92dda50..0000000 --- a/mudu_kernel/src/server_ur/inflight_op.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::server_ur::worker_local_log::CloseFileRequest; -use crate::server_ur::worker_local_log::OpenFileRequest; - -pub(in crate::server_ur) struct AcceptOp { - addr: rliburing::sockaddr_storage, - addr_len: rliburing::socklen_t, -} - -impl AcceptOp { - pub(in crate::server_ur) fn new( - addr: rliburing::sockaddr_storage, - addr_len: rliburing::socklen_t, - ) -> Self { - Self { addr, addr_len } - } - - pub(in crate::server_ur) fn addr(&self) -> &rliburing::sockaddr_storage { - &self.addr - } - - pub(in crate::server_ur) fn addr_mut_ptr(&mut self) -> *mut rliburing::sockaddr { - &mut self.addr as *mut _ as *mut rliburing::sockaddr - } - - pub(in crate::server_ur) fn addr_len_mut(&mut self) -> *mut rliburing::socklen_t { - &mut self.addr_len - } - - pub(in crate::server_ur) fn addr_len(&self) -> rliburing::socklen_t { - self.addr_len - } -} - -pub(in crate::server_ur) struct OpenFileOp { - request: OpenFileRequest, -} - -impl OpenFileOp { - pub(in crate::server_ur) fn new(request: OpenFileRequest) -> Self { - Self { request } - } - - pub(in crate::server_ur) fn request_id(&self) -> u64 { - self.request.request_id() - } - - pub(in crate::server_ur) fn request(&self) -> &OpenFileRequest { - &self.request - } -} - -pub(in crate::server_ur) struct CloseFileOp { - request: CloseFileRequest, -} - -impl CloseFileOp { - pub(in crate::server_ur) fn new(request: CloseFileRequest) -> Self { - Self { request } - } - - pub(in crate::server_ur) fn request_id(&self) -> u64 { - self.request.request_id() - } - - pub(in crate::server_ur) fn request(&self) -> &CloseFileRequest { - &self.request - } -} - -pub(in crate::server_ur) enum InflightOp { - Accept(Box), - MailboxRead { value: Box }, - Recv { conn_id: u64 }, - Send { conn_id: u64 }, - OpenFile(Box), - CloseFile(Box), - LogWrite, - Close { conn_id: u64 }, -} diff --git a/mudu_kernel/src/server_ur/mod.rs b/mudu_kernel/src/server_ur/mod.rs deleted file mode 100644 index 75b21ba..0000000 --- a/mudu_kernel/src/server_ur/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! TCP server backend with a Linux-first `io_uring` implementation. -//! -//! The public `client` module name is kept for compatibility. On Linux the -//! backend uses the native `io_uring` worker loop; on other platforms the same -//! public API falls back to a portable thread-per-worker implementation. -//! Modules that depend on `rliburing` are therefore compiled only on Linux. - -pub mod fsm; -#[cfg(target_os = "linux")] -mod inflight_op; -mod pending_procedure_invocation; -#[cfg(all(test, target_os = "linux"))] -mod perf_test; -pub mod procedure_runtime; -mod procedure_task; -mod procedure_task_waker; -pub mod routing; -pub mod server; -#[cfg(target_os = "linux")] -mod server_iouring; -#[cfg(target_os = "linux")] -mod transferred_connection; -pub mod worker; -#[cfg(target_os = "linux")] -mod worker_connection; -pub mod worker_local; -#[cfg(target_os = "linux")] -mod worker_local_log; -#[cfg(target_os = "linux")] -mod worker_mailbox; -pub mod worker_registry; -#[cfg(target_os = "linux")] -mod worker_ring_loop; diff --git a/mudu_kernel/src/server_ur/pending_procedure_invocation.rs b/mudu_kernel/src/server_ur/pending_procedure_invocation.rs deleted file mode 100644 index 38bdb4f..0000000 --- a/mudu_kernel/src/server_ur/pending_procedure_invocation.rs +++ /dev/null @@ -1,39 +0,0 @@ -use mudu::common::result::RS; -use std::future::Future; -use std::pin::Pin; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; - -pub struct PendingProcedureInvocation { - conn_id: u64, - request_id: u64, - completed: Arc, - future: Pin>> + 'static>>, -} - -impl PendingProcedureInvocation { - pub fn new( - conn_id: u64, - request_id: u64, - completed: Arc, - future: Pin>> + 'static>>, - ) -> Self { - Self { - conn_id, - request_id, - completed, - future, - } - } - - pub fn into_parts( - self, - ) -> ( - u64, - u64, - Arc, - Pin>> + 'static>>, - ) { - (self.conn_id, self.request_id, self.completed, self.future) - } -} diff --git a/mudu_kernel/src/server_ur/perf_test.rs b/mudu_kernel/src/server_ur/perf_test.rs deleted file mode 100644 index f01fb67..0000000 --- a/mudu_kernel/src/server_ur/perf_test.rs +++ /dev/null @@ -1,526 +0,0 @@ -use crate::server_ur::routing::{route_worker, RoutingContext, RoutingMode}; -use crate::server_ur::server::{IoUringTcpBackend, IoUringTcpServerConfig}; -use crate::server_ur::worker_registry::load_or_create_worker_registry; -use log::info; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::error::err::MError; -use mudu::m_error; -use mudu_contract::protocol::{ - decode_error_response, decode_get_response, decode_put_response, - decode_session_create_response, encode_get_request, encode_put_request, - encode_session_create_request, Frame, GetRequest, MessageType, PutRequest, - SessionCreateRequest, HEADER_LEN, -}; -use mudu_utils::log::log_setup; -use mudu_utils::notifier::{notify_wait, NotifyWait}; -use mudu_utils::sync::unique_inner::UniqueInner; -use mudu_utils::task::{spawn_local_task, spawn_task}; -use mudu_utils::{debug, task_trace}; -use pgwire::tokio::tokio_rustls::rustls::internal::msgs::base::Payload; -use short_uuid::ShortUuid; -use std::env::temp_dir; -use std::net::{TcpListener, TcpStream}; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::Arc; -use std::thread; -use std::time::{Duration, Instant}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::net::TcpStream as TokioTcpStream; -use tokio::sync::Barrier; -use tokio::task::JoinSet; -use tracing::debug; -use uuid::Uuid; - -struct AsyncPerfClient { - stream: TokioTcpStream, - next_request_id: u64, - session_id: u128, -} - -impl AsyncPerfClient { - async fn connect(port: u16) -> RS { - let stream = TokioTcpStream::connect(("127.0.0.1", port)) - .await - .map_err(|e| m_error!(EC::NetErr, "connect io_uring tcp server error", e))?; - stream - .set_nodelay(true) - .map_err(|e| m_error!(EC::NetErr, "set tcp nodelay error", e))?; - let mut client = Self { - stream, - next_request_id: 1, - session_id: 0, - }; - client.session_id = client.create_session(None).await?; - Ok(client) - } - - async fn put(&mut self, key: Vec, value: Vec) -> RS<()> { - let _ = task_trace!(); - - let request_id = self.take_request_id(); - let payload = - encode_put_request(request_id, &PutRequest::new(self.session_id, key, value))?; - let frame = self.send_and_receive(&payload).await?; - self.ensure_success_frame(&frame)?; - if decode_put_response(&frame)?.ok() { - Ok(()) - } else { - Err(m_error!( - EC::NetErr, - "remote put operation returned failure" - )) - } - } - - async fn get(&mut self, key: Vec) -> RS>> { - let _t = task_trace!(); - let request_id = self.take_request_id(); - let payload = encode_get_request(request_id, &GetRequest::new(self.session_id, key))?; - let frame = self.send_and_receive(&payload).await?; - self.ensure_success_frame(&frame)?; - Ok(decode_get_response(&frame)?.into_value()) - } - - async fn create_session(&mut self, config_json: Option) -> RS { - let request_id = self.take_request_id(); - let payload = - encode_session_create_request(request_id, &SessionCreateRequest::new(config_json))?; - let frame = self.send_and_receive(&payload).await?; - self.ensure_success_frame(&frame)?; - Ok(decode_session_create_response(&frame)?.session_id()) - } - - fn take_request_id(&mut self) -> u64 { - let request_id = self.next_request_id; - self.next_request_id += 1; - request_id - } - - async fn send_and_receive(&mut self, payload: &[u8]) -> RS { - let _trace = task_trace!(); - self._send(payload).await?; - self._receive().await - } - - async fn _send(&mut self, payload: &[u8]) -> RS<()> { - let _trace = task_trace!(); - self.stream - .write_all(payload) - .await - .map_err(|e| m_error!(EC::NetErr, "write request frame error", e))?; - self.stream - .flush() - .await - .map_err(|e| m_error!(EC::NetErr, "flush request frame error", e))?; - Ok(()) - } - async fn _receive(&mut self) -> RS { - let _ = task_trace!(); - let mut header = [0u8; HEADER_LEN]; - self.stream - .read_exact(&mut header) - .await - .map_err(|e| m_error!(EC::NetErr, "read response header error", e))?; - let payload_len = - u32::from_be_bytes([header[16], header[17], header[18], header[19]]) as usize; - let mut frame_bytes = Vec::with_capacity(HEADER_LEN + payload_len); - frame_bytes.extend_from_slice(&header); - if payload_len > 0 { - let mut body = vec![0u8; payload_len]; - self.stream - .read_exact(&mut body) - .await - .map_err(|e| m_error!(EC::NetErr, "read response payload error", e))?; - frame_bytes.extend_from_slice(&body); - } - Frame::decode(&frame_bytes) - } - - fn ensure_success_frame(&self, frame: &Frame) -> RS<()> { - let _trace = task_trace!(); - if frame.header().message_type() == MessageType::Error { - let error = decode_error_response(frame)?; - return Err(m_error!(EC::NetErr, error.message())); - } - Ok(()) - } -} - -fn reserve_port() -> Option { - let listener = match TcpListener::bind("127.0.0.1:0") { - Ok(listener) => listener, - Err(err) => { - eprintln!("skip io_uring perf test: {err}"); - return None; - } - }; - Some(listener.local_addr().ok()?.port()) -} - -async fn wait_until_server_ready_async(port: u16) { - let deadline = Instant::now() + Duration::from_secs(10); - while Instant::now() < deadline { - if AsyncPerfClient::connect(port).await.is_ok() { - return; - } - tokio::time::sleep(Duration::from_millis(25)).await; - } - panic!("io_uring backend did not become ready on port {}", port); -} - -fn spawn_iouring_server( - port: u16, - worker_count: usize, - data_dir: &std::path::Path, - log_chunk_size: u64, -) -> (mudu_utils::notifier::Notifier, thread::JoinHandle>) { - let (stop_notifier, server_stop) = notify_wait(); - let server_cfg = IoUringTcpServerConfig::new( - worker_count, - "127.0.0.1".to_string(), - port, - data_dir.to_string_lossy().into_owned(), - RoutingMode::ConnectionId, - None, - ) - .unwrap() - .with_log_chunk_size(log_chunk_size); - let server_thread = - thread::spawn(move || IoUringTcpBackend::sync_serve_with_stop(server_cfg, server_stop)); - (stop_notifier, server_thread) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn iouring_backend_perf_put_get() -> RS<()> { - log_setup("info"); - let notifier = NotifyWait::new(); - { - let _n = notifier.clone(); - let _ = thread::spawn(move || { - debug::debug_serve(_n, 1800); - }); - }; - let Some(port) = reserve_port() else { - return Ok(()); - }; - let worker_count = 6usize; - let clients = 6usize; - let bench_duration = Duration::from_secs(10); - let data_dir = temp_dir().join(format!("mududb_iouring_perf_{}", uuid::Uuid::new_v4())); - std::fs::create_dir_all(&data_dir).unwrap(); - - let (stop_notifier, server_stop) = notifier.notify_wait(); - let server_cfg = IoUringTcpServerConfig::new( - worker_count, - "127.0.0.1".to_string(), - port, - data_dir.to_string_lossy().into_owned(), - RoutingMode::ConnectionId, - None, - ) - .unwrap(); - let server_thread = - thread::spawn(move || IoUringTcpBackend::sync_serve_with_stop(server_cfg, server_stop)); - - wait_until_server_ready_async(port).await; - - let start_barrier = Arc::new(Barrier::new(clients + 1)); - let stop_clients = Arc::new(AtomicBool::new(false)); - let put_ops = Arc::new(AtomicU64::new(0)); - let get_ops = Arc::new(AtomicU64::new(0)); - let mut join_set: JoinSet> = tokio::task::JoinSet::new(); - for client_id in 0..clients { - let start_barrier = start_barrier.clone(); - let stop_clients = stop_clients.clone(); - let put_ops = put_ops.clone(); - let get_ops = get_ops.clone(); - let join_handle = spawn_task( - notifier.clone(), - format!("task_cli_{}", client_id).as_str(), - async move { - let mut client = AsyncPerfClient::connect(port).await?; - start_barrier.wait().await; - let mut op_id = 0usize; - while !stop_clients.load(Ordering::Relaxed) { - let key = format!("client-{client_id:02}-key-{op_id:06}").into_bytes(); - let value = format!("value-{client_id:02}-{op_id:06}").into_bytes(); - debug!("client {} put key ", client_id); - client.put(key.clone(), value.clone()).await?; - debug!("client {} put key done", client_id); - put_ops.fetch_add(1, Ordering::Relaxed); - debug!("client {} get key", client_id); - let returned = client.get(key).await?; - debug!("client {} get key done", client_id); - assert_eq!(returned, Some(value)); - get_ops.fetch_add(1, Ordering::Relaxed); - op_id += 1; - } - Ok::<(), MError>(()) - }, - )?; - join_set.spawn(async move { - join_handle.await; - Ok::<(), MError>(()) - }); - } - - start_barrier.wait().await; - info!("begin testing"); - let started_at = Instant::now(); - tokio::time::sleep(bench_duration).await; - let elapsed = started_at.elapsed(); - stop_clients.store(true, Ordering::Relaxed); - - let total_put_ops = put_ops.load(Ordering::Relaxed) as usize; - let total_get_ops = get_ops.load(Ordering::Relaxed) as usize; - let total_ops = total_put_ops + total_get_ops; - let throughput = total_ops as f64 / elapsed.as_secs_f64(); - info!( - "io_uring kv perf: clients={}, puts={}, gets={}, total_ops={}, elapsed_ms={}, throughput_ops_per_sec={:.2}", - clients, - total_put_ops, - total_get_ops, - total_ops, - elapsed.as_millis(), - throughput - ); - - while let Some(result) = join_set.join_next().await { - result.unwrap()?; - } - stop_notifier.notify_all(); - server_thread.join().unwrap()?; - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn iouring_backend_recovery_replays_worker_logs() -> RS<()> { - let Some(port) = reserve_port() else { - return Ok(()); - }; - let worker_count = 2usize; - let data_dir = temp_dir().join(format!("mududb_iouring_recovery_{}", uuid::Uuid::new_v4())); - std::fs::create_dir_all(&data_dir).unwrap(); - let registry = load_or_create_worker_registry(&data_dir, worker_count)?; - let target_worker = registry.worker(0).unwrap(); - - let (stop_notifier, server_thread) = - spawn_iouring_server(port, worker_count, &data_dir, 64 * 1024 * 1024); - wait_until_server_ready_async(port).await; - - { - let mut client = AsyncPerfClient::connect(port).await?; - client.session_id = client - .create_session(Some( - serde_json::json!({ - "session_id": "0", - "worker_id": target_worker.worker_id.to_string(), - }) - .to_string(), - )) - .await?; - client.put(b"alpha".to_vec(), b"one".to_vec()).await?; - client.put(b"beta".to_vec(), b"two".to_vec()).await?; - assert_eq!(client.get(b"alpha".to_vec()).await?, Some(b"one".to_vec())); - assert_eq!(client.get(b"beta".to_vec()).await?, Some(b"two".to_vec())); - } - - stop_notifier.notify_all(); - server_thread.join().unwrap()?; - - let (stop_notifier, server_thread) = - spawn_iouring_server(port, worker_count, &data_dir, 64 * 1024 * 1024); - wait_until_server_ready_async(port).await; - - { - let mut client = AsyncPerfClient::connect(port).await?; - client.session_id = client - .create_session(Some( - serde_json::json!({ - "session_id": "0", - "worker_id": target_worker.worker_id.to_string(), - }) - .to_string(), - )) - .await?; - assert_eq!(client.get(b"alpha".to_vec()).await?, Some(b"one".to_vec())); - assert_eq!(client.get(b"beta".to_vec()).await?, Some(b"two".to_vec())); - } - - stop_notifier.notify_all(); - server_thread.join().unwrap()?; - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn iouring_backend_recovery_replays_across_multiple_chunks() -> RS<()> { - let Some(port) = reserve_port() else { - return Ok(()); - }; - let worker_count = 1usize; - let log_chunk_size = 64u64; - let data_dir = temp_dir().join(format!( - "mududb_iouring_recovery_multichunk_{}", - uuid::Uuid::new_v4() - )); - std::fs::create_dir_all(&data_dir).unwrap(); - let entries = vec![ - (b"alpha".to_vec(), b"one".to_vec()), - (b"beta".to_vec(), b"two".to_vec()), - (b"gamma".to_vec(), b"three".to_vec()), - (b"delta".to_vec(), b"four".to_vec()), - ]; - - let (stop_notifier, server_thread) = - spawn_iouring_server(port, worker_count, &data_dir, log_chunk_size); - wait_until_server_ready_async(port).await; - { - let mut client = AsyncPerfClient::connect(port).await?; - for (key, value) in &entries { - client.put(key.clone(), value.clone()).await?; - } - } - stop_notifier.notify_all(); - server_thread.join().unwrap()?; - - let chunk_count = std::fs::read_dir(&data_dir) - .unwrap() - .filter_map(|entry| entry.ok()) - .filter(|entry| entry.path().extension().and_then(|ext| ext.to_str()) == Some("xl")) - .count(); - assert!( - chunk_count >= 2, - "expected multiple log chunks, got {}", - chunk_count - ); - - let (stop_notifier, server_thread) = - spawn_iouring_server(port, worker_count, &data_dir, log_chunk_size); - wait_until_server_ready_async(port).await; - { - let mut client = AsyncPerfClient::connect(port).await?; - for (key, value) in &entries { - assert_eq!(client.get(key.clone()).await?, Some(value.clone())); - } - } - stop_notifier.notify_all(); - server_thread.join().unwrap()?; - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn iouring_backend_open_session_routes_connection_to_requested_partition() -> RS<()> { - let Some(port) = reserve_port() else { - return Ok(()); - }; - let worker_count = 2usize; - let data_dir = temp_dir().join(format!("mududb_iouring_route_{}", uuid::Uuid::new_v4())); - std::fs::create_dir_all(&data_dir).unwrap(); - - let initial_partition = route_worker( - &RoutingContext::new(1, "127.0.0.1:10000".parse().unwrap(), None), - RoutingMode::ConnectionId, - worker_count, - ); - let target_partition = (initial_partition + 1) % worker_count; - let registry = load_or_create_worker_registry(&data_dir, worker_count)?; - let target_worker = registry.worker(target_partition).unwrap(); - - let (stop_notifier, server_thread) = - spawn_iouring_server(port, worker_count, &data_dir, 64 * 1024 * 1024); - wait_until_server_ready_async(port).await; - - { - let mut client = AsyncPerfClient::connect(port).await?; - let session_id = client - .create_session(Some( - serde_json::json!({ - "session_id": "0", - "worker_id": target_worker.worker_id.to_string(), - }) - .to_string(), - )) - .await?; - client.session_id = session_id; - client - .put(b"route-key".to_vec(), b"route-val".to_vec()) - .await?; - assert_eq!( - client.get(b"route-key".to_vec()).await?, - Some(b"route-val".to_vec()) - ); - } - - stop_notifier.notify_all(); - server_thread.join().unwrap()?; - - let expected_prefix = - ShortUuid::from_uuid(&Uuid::from_u128(target_worker.worker_id)).to_string(); - let routed_chunk_count = std::fs::read_dir(&data_dir) - .unwrap() - .filter_map(|entry| entry.ok()) - .filter(|entry| { - entry - .file_name() - .to_str() - .map(|name| name.starts_with(&expected_prefix) && name.ends_with(".xl")) - .unwrap_or(false) - }) - .count(); - assert!( - routed_chunk_count > 0, - "expected log chunks for target partition {}", - target_partition - ); - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn iouring_backend_open_session_rebind_keeps_same_session_id() -> RS<()> { - let Some(port) = reserve_port() else { - return Ok(()); - }; - let worker_count = 2usize; - let data_dir = temp_dir().join(format!("mududb_iouring_rebind_{}", uuid::Uuid::new_v4())); - std::fs::create_dir_all(&data_dir).unwrap(); - - let initial_partition = route_worker( - &RoutingContext::new(1, "127.0.0.1:10001".parse().unwrap(), None), - RoutingMode::ConnectionId, - worker_count, - ); - let target_partition = (initial_partition + 1) % worker_count; - let registry = load_or_create_worker_registry(&data_dir, worker_count)?; - let target_worker = registry.worker(target_partition).unwrap(); - - let (stop_notifier, server_thread) = - spawn_iouring_server(port, worker_count, &data_dir, 64 * 1024 * 1024); - wait_until_server_ready_async(port).await; - - { - let mut client = AsyncPerfClient::connect(port).await?; - let original_session_id = client.session_id; - let rebound_session_id = client - .create_session(Some( - serde_json::json!({ - "session_id": original_session_id.to_string(), - "worker_id": target_worker.worker_id.to_string(), - }) - .to_string(), - )) - .await?; - assert_eq!(rebound_session_id, original_session_id); - client - .put(b"rebind-key".to_vec(), b"rebind-val".to_vec()) - .await?; - assert_eq!( - client.get(b"rebind-key".to_vec()).await?, - Some(b"rebind-val".to_vec()) - ); - } - - stop_notifier.notify_all(); - server_thread.join().unwrap()?; - Ok(()) -} diff --git a/mudu_kernel/src/server_ur/procedure_task.rs b/mudu_kernel/src/server_ur/procedure_task.rs deleted file mode 100644 index 9e86a6c..0000000 --- a/mudu_kernel/src/server_ur/procedure_task.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::server_ur::pending_procedure_invocation::PendingProcedureInvocation; -use crate::server_ur::routing::SessionOpenTransferAction; -use mudu::common::id::OID; -use mudu::common::result::RS; -use std::future::Future; -use std::pin::Pin; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; - -pub struct ProcedureTask { - task_id: u64, - conn_id: u64, - request_id: u64, - // The invoke future stays owned by the worker, so procedure progress is - // resumed from the ring loop instead of a separate async runtime. - future: Pin>> + 'static>>, - // This flag coalesces repeated wakeups while the task is already queued - // for polling in the worker loop. - queued: Arc, - completed: Arc, - // A pending poll is represented as a worker-local op id. Once that op is - // completed, the task is re-enqueued and polled again from the same thread. - waiting_on: Option, -} - -pub(in crate::server_ur) enum FrameDispatch { - Immediate(Vec), - Pending(PendingProcedureInvocation), - Transfer(SessionTransferDispatch), -} - -pub(in crate::server_ur) struct SessionTransferDispatch { - target_worker: usize, - session_ids: Vec, - action: SessionOpenTransferAction, -} - -impl SessionTransferDispatch { - pub(in crate::server_ur) fn new( - target_worker: usize, - session_ids: Vec, - action: SessionOpenTransferAction, - ) -> Self { - Self { - target_worker, - session_ids, - action, - } - } - - pub(in crate::server_ur) fn target_worker(&self) -> usize { - self.target_worker - } - - pub(in crate::server_ur) fn session_ids(&self) -> &[OID] { - &self.session_ids - } - - pub(in crate::server_ur) fn action(&self) -> SessionOpenTransferAction { - self.action - } -} - -impl ProcedureTask { - pub(in crate::server_ur) fn new( - task_id: u64, - conn_id: u64, - request_id: u64, - future: Pin>> + 'static>>, - completed: Arc, - ) -> Self { - Self { - task_id, - conn_id, - request_id, - future, - queued: Arc::new(AtomicBool::new(false)), - completed, - waiting_on: None, - } - } - - pub(in crate::server_ur) fn conn_id(&self) -> u64 { - self.conn_id - } - - pub(in crate::server_ur) fn request_id(&self) -> u64 { - self.request_id - } - - pub(in crate::server_ur) fn future_mut( - &mut self, - ) -> Pin<&mut (dyn Future>> + 'static)> { - self.future.as_mut() - } - - pub(in crate::server_ur) fn queued(&self) -> &Arc { - &self.queued - } - - pub(in crate::server_ur) fn completed(&self) -> &Arc { - &self.completed - } - pub(in crate::server_ur) fn clear_queued(&self) { - self.queued.store(false, Ordering::Release); - } - - pub(in crate::server_ur) fn waiting_on(&self) -> Option { - self.waiting_on - } - - pub(in crate::server_ur) fn take_waiting_on(&mut self) -> Option { - self.waiting_on.take() - } - - pub(in crate::server_ur) fn set_waiting_on(&mut self, op_id: u64) { - self.waiting_on = Some(op_id); - } -} diff --git a/mudu_kernel/src/server_ur/server_iouring.rs b/mudu_kernel/src/server_ur/server_iouring.rs deleted file mode 100644 index 483e1b0..0000000 --- a/mudu_kernel/src/server_ur/server_iouring.rs +++ /dev/null @@ -1,571 +0,0 @@ -use crate::server_ur::pending_procedure_invocation::PendingProcedureInvocation; -use crate::server_ur::procedure_task::{FrameDispatch, SessionTransferDispatch}; -use crate::server_ur::routing::{parse_session_open_config, SessionOpenTransferAction}; -use crate::server_ur::server::IoUringTcpServerConfig; -use crate::server_ur::worker::IoUringWorker; -use crate::server_ur::worker_local_log::WorkerLocalLog; -use crate::server_ur::worker_mailbox::WorkerMailboxMsg; -use crate::server_ur::worker_ring_loop::{WorkerLoopStats, WorkerRingLoop}; -use crossbeam_queue::SegQueue; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use mudu_contract::protocol::{ - decode_client_request, decode_get_request, decode_procedure_invoke_request, decode_put_request, - decode_range_scan_request, decode_session_close_request, decode_session_create_request, - encode_get_response, encode_put_response, encode_range_scan_response, encode_server_response, - encode_session_close_response, encode_session_create_response, Frame, GetResponse, KeyValue, - MessageType, PutResponse, RangeScanResponse, ServerResponse, SessionCloseResponse, - SessionCreateResponse, HEADER_LEN, -}; -use mudu_utils::notifier::Waiter; -use socket2::{Domain, Protocol, Socket, Type}; -use std::os::fd::{AsRawFd, IntoRawFd, RawFd}; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::{Arc, Condvar, Mutex}; -use std::thread; -use tracing::{debug, info}; - -pub(crate) struct RecoveryCoordinator { - total_workers: usize, - state: Mutex, - condvar: Condvar, -} - -#[derive(Default)] -struct RecoveryState { - recovered_workers: usize, - failed: bool, -} - -pub(crate) fn sync_serve_iouring(cfg: IoUringTcpServerConfig, stop: Waiter) -> RS<()> { - if cfg.worker_count() == 0 { - return Err(m_error!(EC::ParseErr, "invalid io_uring worker count")); - } - let listen_addr: std::net::SocketAddr = format!("{}:{}", cfg.listen_ip(), cfg.listen_port()) - .parse() - .map_err(|e| m_error!(EC::ParseErr, "parse io_uring tcp listen address error", e))?; - let conn_id_alloc = Arc::new(AtomicU64::new(1)); - let mailboxes: Vec<_> = (0..cfg.worker_count()) - .map(|_| Arc::new(SegQueue::::new())) - .collect(); - let mailbox_fds: Vec<_> = (0..cfg.worker_count()) - .map(|_| create_mailbox_event_fd()) - .collect::>>()?; - let stop_flag = Arc::new(AtomicBool::new(false)); - let recovery_coordinator = Arc::new(RecoveryCoordinator::new(cfg.worker_count())); - - let stop_for_notifier = stop.clone(); - let shutdown_mailboxes = mailboxes.clone(); - let shutdown_mailbox_fds = mailbox_fds.clone(); - let notifier_stop_flag = stop_flag.clone(); - let notifier = thread::Builder::new() - .name("iouring-shutdown-notifier".to_string()) - .spawn(move || { - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .map_err(|e| { - m_error!( - EC::TokioErr, - "create runtime for io_uring shutdown notifier error", - e - ) - })?; - runtime.block_on(stop_for_notifier.wait()); - notifier_stop_flag.store(true, Ordering::Relaxed); - for (mailbox, fd) in shutdown_mailboxes - .into_iter() - .zip(shutdown_mailbox_fds.into_iter()) - { - mailbox.push(WorkerMailboxMsg::Shutdown); - notify_mailbox_fd(fd)?; - } - debug!("notify shutdown"); - Ok(()) - }) - .map_err(|e| m_error!(EC::ThreadErr, "spawn io_uring shutdown notifier error", e))?; - - let mut handles = Vec::with_capacity(cfg.worker_count()); - for worker_id in 0..cfg.worker_count() { - let listen_addr = listen_addr; - let conn_id_alloc = conn_id_alloc.clone(); - let mailbox = mailboxes[worker_id].clone(); - let all_mailboxes = mailboxes.clone(); - let all_mailbox_fds = mailbox_fds.clone(); - let procedure_runtime = cfg.procedure_runtime_for_worker(worker_id); - let worker_identity = cfg - .worker_registry() - .worker(worker_id) - .cloned() - .ok_or_else(|| { - m_error!( - EC::NoSuchElement, - format!("missing worker identity {}", worker_id) - ) - })?; - let worker_registry = cfg.worker_registry(); - let routing_mode = cfg.routing_mode(); - let log_dir = cfg.log_dir().to_string(); - let log_chunk_size = cfg.log_chunk_size(); - let worker_count = cfg.worker_count(); - let stop = stop_flag.clone(); - let recovery_coordinator = recovery_coordinator.clone(); - let mailbox_fd = mailbox_fds[worker_id]; - let handle = thread::Builder::new() - .name(format!("iouring-ring-worker-{worker_id}")) - .spawn(move || { - let listener_fd = create_listener_fd(listen_addr)?; - let worker = IoUringWorker::new( - worker_identity, - worker_count, - routing_mode, - log_dir.clone(), - log_chunk_size, - procedure_runtime, - worker_registry, - )?; - let log = WorkerLocalLog::open(worker.log_layout())?; - let mut loop_state = WorkerRingLoop::new( - worker, - listener_fd, - mailbox_fd, - mailbox, - all_mailboxes, - all_mailbox_fds, - conn_id_alloc, - log, - recovery_coordinator, - stop, - )?; - let r = loop_state.run(); - r - }) - .map_err(|e| m_error!(EC::ThreadErr, "spawn io_uring worker error", e))?; - handles.push(handle); - } - - let mut worker_stats = Vec::::with_capacity(cfg.worker_count()); - for handle in handles { - let result = handle - .join() - .map_err(|_| m_error!(EC::ThreadErr, "join io_uring worker error"))?; - worker_stats.push(result?); - } - - let notify_result = notifier - .join() - .map_err(|_| m_error!(EC::ThreadErr, "join io_uring shutdown notifier error"))?; - notify_result?; - - for fd in mailbox_fds { - unsafe { - libc::close(fd); - } - } - log_worker_stats(&worker_stats); - Ok(()) -} - -impl RecoveryCoordinator { - pub(crate) fn new(total_workers: usize) -> Self { - Self { - total_workers, - state: Mutex::new(RecoveryState::default()), - condvar: Condvar::new(), - } - } - - pub(crate) fn worker_succeeded(&self) -> RS<()> { - let mut state = self - .state - .lock() - .map_err(|_| m_error!(EC::InternalErr, "recovery coordinator lock poisoned"))?; - if state.failed { - return Err(m_error!( - EC::ThreadErr, - "worker recovery aborted because another worker failed" - )); - } - state.recovered_workers += 1; - if state.recovered_workers == self.total_workers { - self.condvar.notify_all(); - return Ok(()); - } - // Recovery must be complete on every worker before the service loop - // starts. If one worker fails recovery, wake everybody and abort - // instead of leaving the successful workers stuck forever. - while !state.failed && state.recovered_workers < self.total_workers { - state = self.condvar.wait(state).map_err(|_| { - m_error!( - EC::InternalErr, - "recovery coordinator condvar wait poisoned" - ) - })?; - } - if state.failed { - return Err(m_error!( - EC::ThreadErr, - "worker recovery aborted because another worker failed" - )); - } - Ok(()) - } - - pub(crate) fn worker_failed(&self) { - if let Ok(mut state) = self.state.lock() { - state.failed = true; - self.condvar.notify_all(); - } - } -} - -pub fn dispatch_frame_iouring( - worker: &IoUringWorker, - conn_id: u64, - frame: &Frame, -) -> RS { - match frame.header().message_type() { - MessageType::Query | MessageType::Execute => { - let request = decode_client_request(frame)?; - Ok(FrameDispatch::Immediate(encode_server_response( - frame.header().request_id(), - &ServerResponse::new( - vec![], - vec![], - 0, - Some(format!( - "SQL interface is disabled in the client backend for app '{}'", - request.app_name() - )), - ), - )?)) - } - MessageType::Get => { - let request = decode_get_request(frame)?; - let value = worker.get_for_connection(conn_id, request.session_id(), request.key())?; - Ok(FrameDispatch::Immediate(encode_get_response( - frame.header().request_id(), - &GetResponse::new(value), - )?)) - } - MessageType::Put => { - let request = decode_put_request(frame)?; - let session_id = request.session_id(); - let (key, value) = request.into_parts(); - worker.put_for_connection(conn_id, session_id, key, value)?; - Ok(FrameDispatch::Immediate(encode_put_response( - frame.header().request_id(), - &PutResponse::new(true), - )?)) - } - MessageType::RangeScan => { - let request = decode_range_scan_request(frame)?; - let items = worker.range_for_connection( - conn_id, - request.session_id(), - request.start_key(), - request.end_key(), - )?; - Ok(FrameDispatch::Immediate(encode_range_scan_response( - frame.header().request_id(), - &RangeScanResponse::new( - items - .into_iter() - .map(|item| KeyValue::new(item.key, item.value)) - .collect(), - ), - )?)) - } - MessageType::ProcedureInvoke => { - let request = decode_procedure_invoke_request(frame)?; - let worker = worker.clone(); - let request_id = frame.header().request_id(); - let completed = Arc::new(AtomicBool::new(false)); - Ok(FrameDispatch::Pending(PendingProcedureInvocation::new( - conn_id, - request_id, - // The component invoke future is polled from the worker loop so - // the ring thread never blocks on procedure execution. - completed.clone(), - Box::pin(async move { - let response = worker.handle_procedure_request(conn_id, &request).await; - completed.store(true, Ordering::SeqCst); - Ok(response?.into_result()) - }), - ))) - } - MessageType::SessionCreate => { - let request = decode_session_create_request(frame)?; - let config = parse_session_open_config( - request.config_json(), - worker.worker_index(), - worker.worker_id(), - worker.registry().as_ref(), - )?; - if config.target_worker_index() == worker.worker_index() { - let session_id = worker.open_session_with_config(conn_id, config)?; - Ok(FrameDispatch::Immediate(encode_session_create_response( - frame.header().request_id(), - &SessionCreateResponse::new(session_id), - )?)) - } else { - let action = SessionOpenTransferAction::new(frame.header().request_id(), config); - let session_ids = worker.prepare_connection_transfer(conn_id, Some(action))?; - Ok(FrameDispatch::Transfer(SessionTransferDispatch::new( - config.target_worker_index(), - session_ids, - action, - ))) - } - } - MessageType::SessionClose => { - let request = decode_session_close_request(frame)?; - let closed = worker.close_session(conn_id, request.session_id())?; - Ok(FrameDispatch::Immediate(encode_session_close_response( - frame.header().request_id(), - &SessionCloseResponse::new(closed), - )?)) - } - MessageType::Handshake | MessageType::Auth | MessageType::Response | MessageType::Error => { - Err(m_error!( - EC::ParseErr, - format!( - "unsupported client message type {:?}", - frame.header().message_type() - ) - )) - } - } -} - -pub fn try_decode_next_frame(buf: &[u8]) -> RS> { - if buf.len() < HEADER_LEN { - return Ok(None); - } - let payload_len = u32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]) as usize; - let frame_len = HEADER_LEN + payload_len; - if buf.len() < frame_len { - return Ok(None); - } - let frame = Frame::decode(&buf[..frame_len])?; - Ok(Some((frame, frame_len))) -} - -fn create_listener_fd(listen_addr: std::net::SocketAddr) -> RS { - let domain = if listen_addr.is_ipv4() { - Domain::IPV4 - } else { - Domain::IPV6 - }; - let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP)) - .map_err(|e| m_error!(EC::NetErr, "create tcp listener socket error", e))?; - socket - .set_reuse_address(true) - .map_err(|e| m_error!(EC::NetErr, "enable SO_REUSEADDR error", e))?; - enable_reuse_port(&socket)?; - socket - .bind(&listen_addr.into()) - .map_err(|e| m_error!(EC::NetErr, "bind io_uring tcp listener error", e))?; - socket - .listen(1024) - .map_err(|e| m_error!(EC::NetErr, "listen io_uring tcp listener error", e))?; - Ok(socket.into_raw_fd()) -} - -fn enable_reuse_port(socket: &Socket) -> RS<()> { - let value: libc::c_int = 1; - let rc = unsafe { - libc::setsockopt( - socket.as_raw_fd(), - libc::SOL_SOCKET, - libc::SO_REUSEPORT, - &value as *const _ as *const libc::c_void, - std::mem::size_of_val(&value) as libc::socklen_t, - ) - }; - if rc != 0 { - return Err(m_error!( - EC::NetErr, - "enable SO_REUSEPORT error", - std::io::Error::last_os_error() - )); - } - Ok(()) -} - -pub fn set_connection_options(fd: RawFd) -> RS<()> { - let flag: libc::c_int = 1; - let rc = unsafe { - libc::setsockopt( - fd, - libc::IPPROTO_TCP, - libc::TCP_NODELAY, - &flag as *const _ as *const libc::c_void, - std::mem::size_of_val(&flag) as libc::socklen_t, - ) - }; - if rc != 0 { - return Err(m_error!( - EC::NetErr, - "set connection nodelay error", - std::io::Error::last_os_error() - )); - } - Ok(()) -} - -fn create_mailbox_event_fd() -> RS { - create_event_fd("create io_uring worker mailbox eventfd error") -} - -fn create_event_fd(message: &str) -> RS { - let fd = unsafe { libc::eventfd(0, libc::EFD_CLOEXEC) }; - if fd < 0 { - return Err(m_error!( - EC::NetErr, - message, - std::io::Error::last_os_error() - )); - } - Ok(fd) -} - -pub(super) fn notify_mailbox_fd(fd: RawFd) -> RS<()> { - notify_event_fd(fd, "write io_uring worker mailbox eventfd error") -} - -fn notify_event_fd(fd: RawFd, message: &str) -> RS<()> { - let value: u64 = 1; - let rc = unsafe { - libc::write( - fd, - &value as *const u64 as *const libc::c_void, - std::mem::size_of::(), - ) - }; - if rc as usize != std::mem::size_of::() { - return Err(m_error!( - EC::NetErr, - message, - std::io::Error::last_os_error() - )); - } - Ok(()) -} - -fn log_worker_stats(stats: &[WorkerLoopStats]) { - for stat in stats { - debug!( - "iouring worker stats: \n\ - worker={}, submit_calls={}, wait_cqe_calls={}, \n\ - accept_submit={}, mailbox_submit={}, recv_submit={}, send_submit={}, \ - log_write_submit={}, cqe_accept={}, cqe_mailbox={}, cqe_recv={}, cqe_send={}, \ - cqe_log_write={}, cqe_close={}, recv_queue_push={}, recv_queue_pop={}, \ - send_queue_push={}, send_queue_pop={}, mailbox_drained={}, local_register={}", - stat.worker_id, - stat.submit_calls, - stat.wait_cqe_calls, - stat.accept_submit, - stat.mailbox_submit, - stat.recv_submit, - stat.send_submit, - stat.log_write_submit, - stat.cqe_accept, - stat.cqe_mailbox, - stat.cqe_recv, - stat.cqe_send, - stat.cqe_log_write, - stat.cqe_close, - stat.recv_queue_push, - stat.recv_queue_pop, - stat.send_queue_push, - stat.send_queue_pop, - stat.mailbox_drained, - stat.local_register, - ); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::server_ur::routing::ConnectionTransfer; - use crate::server_ur::transferred_connection::TransferredConnection; - - #[test] - fn mailbox_eventfd_accumulates_wakeups() { - let fd = create_mailbox_event_fd().unwrap(); - notify_mailbox_fd(fd).unwrap(); - notify_mailbox_fd(fd).unwrap(); - - let mut value = 0u64; - let rc = unsafe { - libc::read( - fd, - (&mut value) as *mut u64 as *mut libc::c_void, - std::mem::size_of::(), - ) - }; - assert_eq!(rc as usize, std::mem::size_of::()); - assert_eq!(value, 2); - - unsafe { - libc::close(fd); - } - } - - #[test] - fn mailbox_can_store_shutdown_and_transfer_messages() { - let mailbox = SegQueue::new(); - mailbox.push(WorkerMailboxMsg::AdoptConnection( - TransferredConnection::new( - ConnectionTransfer::new( - 11, - 1, - crate::server_ur::fsm::ConnectionState::Accepted, - "127.0.0.1:9527".parse().unwrap(), - ), - -1, - Vec::new(), - None, - ), - )); - mailbox.push(WorkerMailboxMsg::Shutdown); - match mailbox.pop() { - Some(WorkerMailboxMsg::AdoptConnection(connection)) => { - assert_eq!(connection.transfer().conn_id(), 11); - assert_eq!(connection.transfer().target_worker(), 1); - } - other => panic!("unexpected first mailbox message: {other:?}"), - } - assert!(matches!(mailbox.pop(), Some(WorkerMailboxMsg::Shutdown))); - assert!(mailbox.pop().is_none()); - } -} - -pub fn sockaddr_to_socket_addr( - storage: &rliburing::sockaddr_storage, - _addr_len: rliburing::socklen_t, -) -> RS { - match storage.ss_family as i32 { - libc::AF_INET => { - let addr: libc::sockaddr_in = - unsafe { std::ptr::read(storage as *const _ as *const _) }; - let ip = std::net::Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr).to_be_bytes()); - let port = u16::from_be(addr.sin_port); - Ok(std::net::SocketAddr::from((ip, port))) - } - libc::AF_INET6 => { - let addr: libc::sockaddr_in6 = - unsafe { std::ptr::read(storage as *const _ as *const _) }; - let ip = std::net::Ipv6Addr::from(addr.sin6_addr.s6_addr); - let port = u16::from_be(addr.sin6_port); - Ok(std::net::SocketAddr::from((ip, port))) - } - family => Err(m_error!( - EC::NetErr, - format!("unsupported socket family {}", family) - )), - } -} diff --git a/mudu_kernel/src/server_ur/worker.rs b/mudu_kernel/src/server_ur/worker.rs deleted file mode 100644 index 633f6d6..0000000 --- a/mudu_kernel/src/server_ur/worker.rs +++ /dev/null @@ -1,1043 +0,0 @@ -use crate::server_ur::procedure_runtime::ProcInvokerPtr; -use crate::server_ur::routing::{ - route_worker, RoutingContext, RoutingMode, SessionOpenConfig, SessionOpenTransferAction, -}; -use crate::server_ur::worker_local::{WorkerExecute, WorkerLocal, WorkerLocalRef}; -use crate::server_ur::worker_registry::{WorkerIdentity, WorkerRegistry}; -use crate::storage::worker_kv_store::{KvItem, WorkerKvStore, WorkerSnapshot}; -use crate::x_log::worker_kv_log::{WorkerKvLog, WorkerLogLayout}; -use mudu::common::id::OID; -use mudu::common::result::RS; -use mudu::common::xid::new_xid; -use mudu::error::ec::EC; -use mudu::m_error; -use mudu_contract::protocol::{ProcedureInvokeRequest, ProcedureInvokeResponse}; -use scc::HashMap as SccHashMap; -use std::cell::UnsafeCell; -use std::collections::BTreeMap; -use std::net::SocketAddr; -use std::sync::Arc; - -#[derive(Clone)] -/// Per-worker execution context used by the `client` backend. -/// -/// The `IoUringWorker` name is also historical. The type is shared by both the -/// Linux native `io_uring` loop and the non-Linux fallback loop so upper -/// layers do not need target-specific worker abstractions. -pub struct IoUringWorker { - worker_index: usize, - worker_id: OID, - partition_ids: Vec, - worker_count: usize, - routing_mode: RoutingMode, - store: WorkerKvStore, - log_layout: WorkerLogLayout, - procedure_runtime: Option, - sessions: Arc, - registry: Arc, -} - -#[derive(Default)] -struct WorkerSessions { - session_owner: SccHashMap, - connection_sessions: SccHashMap>>, - session_contexts: SccHashMap>, -} - -struct SessionBoundWorkerLocal { - worker: Arc, - current_session_id: OID, -} - -#[derive(Default)] -pub(crate) struct SessionContext { - tx_manager: UnsafeCell>, -} - -struct WorkerTxManager { - snapshot: WorkerSnapshot, - staged_puts: BTreeMap, Vec>, - log_buffer: Vec<(Vec, Vec)>, -} - -unsafe impl Send for SessionContext {} -unsafe impl Sync for SessionContext {} - -impl IoUringWorker { - pub fn new( - identity: WorkerIdentity, - worker_count: usize, - routing_mode: RoutingMode, - log_dir: String, - log_chunk_size: u64, - procedure_runtime: Option, - registry: Arc, - ) -> RS { - let log_layout = WorkerLogLayout::new(log_dir, identity.worker_id, log_chunk_size)?; - let log = WorkerKvLog::new(log_layout.clone())?; - Ok(Self { - worker_index: identity.worker_index, - worker_id: identity.worker_id, - partition_ids: identity.partition_ids, - worker_count, - routing_mode, - store: WorkerKvStore::new(identity.worker_index, log), - log_layout, - procedure_runtime, - sessions: Arc::new(WorkerSessions::default()), - registry, - }) - } - - pub fn route_connection(&self, conn_id: u64, remote_addr: SocketAddr) -> usize { - let ctx = RoutingContext::new(conn_id, remote_addr, None); - route_worker(&ctx, self.routing_mode, self.worker_count) - } - - pub fn put(&self, key: Vec, value: Vec) -> RS<()> { - self.store.put(key, value) - } - - pub fn get(&self, key: &[u8]) -> RS>> { - self.store.get(key) - } - - pub async fn invoke_procedure( - &self, - session_id: OID, - procedure_name: &str, - procedure_parameters: Vec, - worker_local: WorkerLocalRef, - ) -> RS> { - let procedure_runtime = self - .procedure_runtime - .as_ref() - .ok_or_else(|| m_error!(EC::NotImplemented, "procedure runtime is not configured"))?; - procedure_runtime - .invoke( - session_id, - procedure_name, - procedure_parameters, - worker_local, - ) - .await - } - - pub fn create_session(&self, conn_id: u64) -> RS { - loop { - let session_id = new_xid(); - if self - .sessions - .session_owner - .insert_sync(session_id, conn_id) - .is_err() - { - continue; - } - let session_context = Arc::new(SessionContext::default()); - if self - .sessions - .session_contexts - .insert_sync(session_id, session_context) - .is_err() - { - let _ = self.sessions.session_owner.remove_sync(&session_id); - continue; - } - self.connection_sessions(conn_id) - .insert_sync(session_id, ()); - return Ok(session_id); - } - } - - pub fn close_session(&self, conn_id: u64, session_id: OID) -> RS { - match self - .sessions - .session_owner - .get_sync(&session_id) - .map(|entry| *entry.get()) - { - Some(owner_conn_id) if owner_conn_id == conn_id => { - let _ = self.sessions.session_owner.remove_sync(&session_id); - let _ = self.sessions.session_contexts.remove_sync(&session_id); - if let Some(conn_sessions) = self.sessions.connection_sessions.get_sync(&conn_id) { - let conn_sessions = conn_sessions.get().clone(); - let _ = conn_sessions.remove_sync(&session_id); - } - Ok(true) - } - Some(_) => Err(m_error!( - EC::TxErr, - format!( - "session {} does not belong to connection {}", - session_id, conn_id - ) - )), - None => Ok(false), - } - } - - pub fn close_connection_sessions(&self, conn_id: u64) -> RS<()> { - if let Some((_conn_id, session_ids)) = - self.sessions.connection_sessions.remove_sync(&conn_id) - { - session_ids.iter_sync(|session_id, _| { - let _ = self.sessions.session_owner.remove_sync(session_id); - let _ = self.sessions.session_contexts.remove_sync(session_id); - true - }); - } - Ok(()) - } - - fn conn_id_for_session(&self, session_id: OID) -> RS { - self.sessions - .session_owner - .get_sync(&session_id) - .map(|entry| *entry.get()) - .ok_or_else(|| { - m_error!( - EC::NoSuchElement, - format!("session {} does not exist", session_id) - ) - }) - } - - pub fn open_session(&self, session_id: OID) -> RS { - let conn_id = self.conn_id_for_session(session_id)?; - self.create_session(conn_id) - } - - pub fn close_session_by_id(&self, session_id: OID) -> RS<()> { - let conn_id = self.conn_id_for_session(session_id)?; - let closed = self.close_session(conn_id, session_id)?; - if closed { - Ok(()) - } else { - Err(m_error!( - EC::NoSuchElement, - format!("session {} does not exist", session_id) - )) - } - } - - fn connection_sessions(&self, conn_id: u64) -> Arc> { - if let Some(existing) = self.sessions.connection_sessions.get_sync(&conn_id) { - return existing.get().clone(); - } - let created = Arc::new(SccHashMap::new()); - match self - .sessions - .connection_sessions - .insert_sync(conn_id, created.clone()) - { - Ok(_) => created, - Err((_conn_id, created)) => { - if let Some(existing) = self.sessions.connection_sessions.get_sync(&conn_id) { - existing.get().clone() - } else { - created - } - } - } - } - - fn session_context(&self, session_id: OID) -> RS> { - self.sessions - .session_contexts - .get_sync(&session_id) - .map(|entry| entry.get().clone()) - .ok_or_else(|| { - m_error!( - EC::NoSuchElement, - format!("session {} does not exist", session_id) - ) - }) - } - - fn ensure_session_exists(&self, session_id: OID) -> RS<()> { - let _ = self.conn_id_for_session(session_id)?; - Ok(()) - } - - pub fn get_for_connection( - &self, - conn_id: u64, - session_id: OID, - key: &[u8], - ) -> RS>> { - self.ensure_session_owned_by_connection(conn_id, session_id)?; - ::get(self, session_id, key) - } - - pub fn put_for_connection( - &self, - conn_id: u64, - session_id: OID, - key: Vec, - value: Vec, - ) -> RS<()> { - self.ensure_session_owned_by_connection(conn_id, session_id)?; - ::put(self, session_id, key, value) - } - - pub fn range_for_connection( - &self, - conn_id: u64, - session_id: OID, - start_key: &[u8], - end_key: &[u8], - ) -> RS> { - self.ensure_session_owned_by_connection(conn_id, session_id)?; - ::range(self, session_id, start_key, end_key) - } - - fn execute_tx(&self, session_id: OID, instruction: WorkerExecute) -> RS<()> { - let session = self.session_context(session_id)?; - match instruction { - WorkerExecute::BeginTx => { - if session.tx_manager_ref().is_some() { - return Err(m_error!( - EC::ExistingSuchElement, - format!("session {} already has an active transaction", session_id) - )); - } - session.set_tx_manager(Some(WorkerTxManager::new(self.store.begin_tx()?))); - Ok(()) - } - WorkerExecute::CommitTx => { - let tx_manager = session.take_tx_manager().ok_or_else(|| { - m_error!( - EC::NoSuchElement, - format!("session {} has no active transaction", session_id) - ) - })?; - let snapshot = tx_manager.snapshot().clone(); - let xid = tx_manager.xid(); - self.store - .commit_put_batch(&snapshot, xid, tx_manager.into_log_buffer())?; - Ok(()) - } - WorkerExecute::RollbackTx => { - let tx_manager = session.take_tx_manager().ok_or_else(|| { - m_error!( - EC::NoSuchElement, - format!("session {} has no active transaction", session_id) - ) - })?; - self.store.rollback_tx(tx_manager.xid())?; - Ok(()) - } - } - } - - fn put_in_session(&self, session_id: OID, key: Vec, value: Vec) -> RS<()> { - let session = self.session_context(session_id)?; - match session.tx_manager_mut().as_mut() { - Some(tx_manager) => { - tx_manager.put(key, value); - Ok(()) - } - None => self.store.put(key, value), - } - } - - fn get_in_session(&self, session_id: OID, key: &[u8]) -> RS>> { - let session = self.session_context(session_id)?; - let staged = session - .tx_manager_ref() - .as_ref() - .and_then(|tx_manager| tx_manager.get(key)); - match staged { - Some(value) => Ok(Some(value)), - None => match session.tx_manager_ref().as_ref() { - Some(tx_manager) => self.store.get_with_snapshot(tx_manager.snapshot(), key), - None => self.store.get(key), - }, - } - } - - fn range_in_session( - &self, - session_id: OID, - start_key: &[u8], - end_key: &[u8], - ) -> RS> { - let session = self.session_context(session_id)?; - let staged = session - .tx_manager_ref() - .as_ref() - .map(|tx_manager| tx_manager.staged_items_in_range(start_key, end_key)) - .unwrap_or_default(); - - let mut merged = BTreeMap::new(); - let base_items = match session.tx_manager_ref().as_ref() { - Some(tx_manager) => { - self.store - .range_scan_with_snapshot(tx_manager.snapshot(), start_key, end_key)? - } - None => self.store.range_scan(start_key, end_key)?, - }; - for item in base_items { - merged.insert(item.key, item.value); - } - for (key, value) in staged { - merged.insert(key, value); - } - Ok(merged - .into_iter() - .map(|(key, value)| KvItem { key, value }) - .collect()) - } - - fn ensure_session_owned_by_connection(&self, conn_id: u64, session_id: OID) -> RS<()> { - match self - .sessions - .session_owner - .get_sync(&session_id) - .map(|entry| *entry.get()) - { - Some(owner_conn_id) if owner_conn_id == conn_id => Ok(()), - Some(_) => Err(m_error!( - EC::TxErr, - format!( - "session {} does not belong to connection {}", - session_id, conn_id - ) - )), - None => Err(m_error!( - EC::NoSuchElement, - format!("session {} does not exist", session_id) - )), - } - } - - pub async fn handle_procedure_request( - &self, - conn_id: u64, - request: &ProcedureInvokeRequest, - ) -> RS { - let session_id = request.session_id() as OID; - self.ensure_session_owned_by_connection(conn_id, session_id)?; - let worker_local: WorkerLocalRef = Arc::new(SessionBoundWorkerLocal { - worker: Arc::new(self.clone()), - current_session_id: session_id, - }); - let result = self - .invoke_procedure( - session_id, - request.procedure_name(), - request.procedure_parameters_owned(), - worker_local, - ) - .await?; - Ok(ProcedureInvokeResponse::new(result)) - } - - pub fn worker_index(&self) -> usize { - self.worker_index - } - - pub fn worker_id(&self) -> OID { - self.worker_id - } - - pub fn partition_ids(&self) -> &[OID] { - &self.partition_ids - } - - pub fn worker_count(&self) -> usize { - self.worker_count - } - - pub fn registry(&self) -> &Arc { - &self.registry - } - - pub fn log_layout(&self) -> WorkerLogLayout { - self.log_layout.clone() - } - - pub fn replay_log_entry(&self, key: Vec, value: Vec) -> RS<()> { - self.store.put_local(key, value) - } - - pub fn open_session_with_config(&self, conn_id: u64, config: SessionOpenConfig) -> RS { - if config.target_worker_index() != self.worker_index() - || config.worker_id() != self.worker_id() - { - return Err(m_error!( - EC::InternalErr, - format!( - "session open landed on worker index {} worker id {}, expected worker index {} worker id {}", - self.worker_index(), - self.worker_id(), - config.target_worker_index(), - config.worker_id() - ) - )); - } - if config.session_id() == 0 { - self.create_session(conn_id) - } else { - self.ensure_session_owned_by_connection(conn_id, config.session_id())?; - Ok(config.session_id()) - } - } - - pub fn prepare_connection_transfer( - &self, - conn_id: u64, - action: Option, - ) -> RS> { - if self.connection_has_active_tx(conn_id)? { - return Err(m_error!( - EC::TxErr, - format!( - "connection {} cannot be transferred while a session transaction is active", - conn_id - ) - )); - } - if let Some(action) = action { - let config = action.config(); - if config.session_id() != 0 { - self.ensure_session_owned_by_connection(conn_id, config.session_id())?; - } - } - self.detach_connection_sessions(conn_id) - } - - pub fn adopt_connection_sessions(&self, conn_id: u64, session_ids: &[OID]) -> RS<()> { - if session_ids.is_empty() { - return Ok(()); - } - let conn_sessions = self.connection_sessions(conn_id); - for &session_id in session_ids { - self.sessions - .session_owner - .insert_sync(session_id, conn_id) - .map_err(|_| { - m_error!( - EC::ExistingSuchElement, - format!("session {} already exists on target worker", session_id) - ) - })?; - if self - .sessions - .session_contexts - .insert_sync(session_id, Arc::new(SessionContext::default())) - .is_err() - { - let _ = self.sessions.session_owner.remove_sync(&session_id); - return Err(m_error!( - EC::ExistingSuchElement, - format!( - "session {} context already exists on target worker", - session_id - ) - )); - } - conn_sessions.insert_sync(session_id, ()); - } - Ok(()) - } - - fn connection_has_active_tx(&self, conn_id: u64) -> RS { - let session_ids = self.connection_session_ids(conn_id); - for session_id in session_ids { - let session = self.session_context(session_id)?; - if session.tx_manager_ref().is_some() { - return Ok(true); - } - } - Ok(false) - } - - fn connection_session_ids(&self, conn_id: u64) -> Vec { - let Some(conn_sessions) = self.sessions.connection_sessions.get_sync(&conn_id) else { - return Vec::new(); - }; - let mut session_ids = Vec::new(); - conn_sessions.get().iter_sync(|session_id, _| { - session_ids.push(*session_id); - true - }); - session_ids - } - - fn detach_connection_sessions(&self, conn_id: u64) -> RS> { - let Some((_conn_id, conn_sessions)) = - self.sessions.connection_sessions.remove_sync(&conn_id) - else { - return Ok(Vec::new()); - }; - let mut session_ids = Vec::new(); - conn_sessions.iter_sync(|session_id, _| { - session_ids.push(*session_id); - true - }); - for &session_id in &session_ids { - let _ = self.sessions.session_owner.remove_sync(&session_id); - let _ = self.sessions.session_contexts.remove_sync(&session_id); - } - Ok(session_ids) - } -} - -fn worker_log_oid(worker_id: usize) -> OID { - worker_id as u128 + 1 -} - -impl WorkerLocal for IoUringWorker { - fn open(&self) -> RS { - Err(m_error!( - EC::NotImplemented, - "open requires a session-bound worker local context" - )) - } - - fn open_argv(&self, worker_id: OID) -> RS { - if worker_id == 0 { - self.open() - } else { - Err(m_error!( - EC::NotImplemented, - format!( - "open on worker {} requires a session-bound worker local context", - worker_id - ) - )) - } - } - - fn close(&self, session_id: OID) -> RS<()> { - self.close_session_by_id(session_id) - } - - fn execute(&self, session_id: OID, instruction: WorkerExecute) -> RS<()> { - self.ensure_session_exists(session_id)?; - self.execute_tx(session_id, instruction) - } - - fn put(&self, session_id: OID, key: Vec, value: Vec) -> RS<()> { - self.put_in_session(session_id, key, value) - } - - fn get(&self, session_id: OID, key: &[u8]) -> RS>> { - self.get_in_session(session_id, key) - } - - fn range(&self, session_id: OID, start_key: &[u8], end_key: &[u8]) -> RS> { - self.range_in_session(session_id, start_key, end_key) - } -} - -impl WorkerLocal for SessionBoundWorkerLocal { - fn open(&self) -> RS { - self.worker.open_session(self.current_session_id) - } - - fn open_argv(&self, worker_id: OID) -> RS { - if worker_id == 0 || worker_id == self.worker.worker_id() { - self.open() - } else { - Err(m_error!( - EC::NotImplemented, - format!( - "worker-local open cannot move from worker {} to worker {}", - self.worker.worker_id(), - worker_id - ) - )) - } - } - - fn close(&self, session_id: OID) -> RS<()> { - self.worker.close_session_by_id(session_id) - } - - fn execute(&self, session_id: OID, instruction: WorkerExecute) -> RS<()> { - ::execute(self.worker.as_ref(), session_id, instruction) - } - - fn put(&self, session_id: OID, key: Vec, value: Vec) -> RS<()> { - ::put(self.worker.as_ref(), session_id, key, value) - } - - fn get(&self, session_id: OID, key: &[u8]) -> RS>> { - ::get(self.worker.as_ref(), session_id, key) - } - - fn range(&self, session_id: OID, start_key: &[u8], end_key: &[u8]) -> RS> { - ::range(self.worker.as_ref(), session_id, start_key, end_key) - } -} - -impl WorkerTxManager { - fn new(snapshot: WorkerSnapshot) -> Self { - Self { - snapshot, - staged_puts: BTreeMap::new(), - log_buffer: Vec::new(), - } - } - - fn xid(&self) -> u64 { - self.snapshot.xid() - } - - fn snapshot(&self) -> &WorkerSnapshot { - &self.snapshot - } - - fn put(&mut self, key: Vec, value: Vec) { - self.staged_puts.insert(key.clone(), value.clone()); - self.log_buffer.push((key, value)); - } - - fn get(&self, key: &[u8]) -> Option> { - self.staged_puts.get(key).cloned() - } - - fn staged_items_in_range(&self, start_key: &[u8], end_key: &[u8]) -> Vec<(Vec, Vec)> { - self.staged_puts - .iter() - .filter(|(key, _)| is_key_in_range(key, start_key, end_key)) - .map(|(key, value)| (key.clone(), value.clone())) - .collect() - } - - fn into_log_buffer(self) -> Vec<(Vec, Vec)> { - self.log_buffer - } -} - -impl SessionContext { - fn tx_manager_ref(&self) -> &Option { - unsafe { &*self.tx_manager.get() } - } - - fn tx_manager_mut(&self) -> &mut Option { - unsafe { &mut *self.tx_manager.get() } - } - - fn set_tx_manager(&self, tx_manager: Option) { - unsafe { - *self.tx_manager.get() = tx_manager; - } - } - - fn take_tx_manager(&self) -> Option { - self.tx_manager_mut().take() - } -} - -fn is_key_in_range(key: &[u8], start_key: &[u8], end_key: &[u8]) -> bool { - key >= start_key && (end_key.is_empty() || key < end_key) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::server_ur::procedure_runtime::ProcInvoker; - use crate::server_ur::worker_local::{WorkerExecute, WorkerLocal}; - use crate::server_ur::worker_registry::{load_or_create_worker_registry, WorkerRegistry}; - use async_trait::async_trait; - use futures::FutureExt; - use mudu::common::id::gen_oid; - use std::env::temp_dir; - use std::sync::{Arc, Mutex}; - - #[derive(Default)] - struct RecordingProcedureRuntime { - calls: Mutex)>>, - } - - #[async_trait] - impl ProcInvoker for RecordingProcedureRuntime { - async fn invoke( - &self, - session_id: OID, - procedure_name: &str, - procedure_parameters: Vec, - _worker_local: WorkerLocalRef, - ) -> RS> { - self.calls.lock().unwrap().push(( - session_id, - procedure_name.to_string(), - procedure_parameters.clone(), - )); - Ok(procedure_parameters) - } - } - - fn test_registry(worker_count: usize) -> (String, Arc) { - let dir = temp_dir() - .join(format!("worker_test_{}", gen_oid())) - .to_string_lossy() - .into_owned(); - let registry = load_or_create_worker_registry(&dir, worker_count).unwrap(); - (dir, registry) - } - - fn test_worker( - worker_index: usize, - worker_count: usize, - log_dir: &str, - registry: Arc, - procedure_runtime: Option, - ) -> IoUringWorker { - let identity = registry.worker(worker_index).cloned().unwrap(); - IoUringWorker::new( - identity, - worker_count, - RoutingMode::ConnectionId, - log_dir.to_string(), - 4096, - procedure_runtime, - registry, - ) - .unwrap() - } - - #[tokio::test] - async fn worker_invokes_configured_procedure_runtime() { - let runtime = Arc::new(RecordingProcedureRuntime::default()); - let (log_dir, registry) = test_registry(1); - let worker = test_worker(0, 1, &log_dir, registry, Some(runtime.clone())); - - let response = worker - .handle_procedure_request( - 11, - &ProcedureInvokeRequest::new(9, "app/mod/proc", b"payload".to_vec()), - ) - .await - .unwrap_err(); - assert!(response.to_string().contains("does not exist")); - - let session_id = worker.create_session(11).unwrap(); - let response = worker - .handle_procedure_request( - 11, - &ProcedureInvokeRequest::new(session_id, "app/mod/proc", b"payload".to_vec()), - ) - .await - .unwrap(); - assert_eq!(response.into_result(), b"payload".to_vec()); - - let calls = runtime.calls.lock().unwrap(); - assert_eq!(calls.len(), 1); - assert_eq!(calls[0].0, session_id); - assert_eq!(calls[0].1, "app/mod/proc"); - assert_eq!(calls[0].2, b"payload".to_vec()); - } - - #[test] - fn worker_session_lifecycle_is_connection_scoped() { - let (log_dir, registry) = test_registry(1); - let worker = test_worker(0, 1, &log_dir, registry, None); - - let session_id = worker.create_session(7).unwrap(); - assert!(worker.close_session(7, session_id).unwrap()); - - let session_id = worker.create_session(7).unwrap(); - let err = worker.close_session(8, session_id).unwrap_err(); - assert!(err.to_string().contains("does not belong to connection 8")); - - worker.close_connection_sessions(7).unwrap(); - let err = worker - .handle_procedure_request( - 7, - &ProcedureInvokeRequest::new(session_id, "app/mod/proc", b"payload".to_vec()), - ) - .now_or_never() - .unwrap() - .unwrap_err(); - assert!(err.to_string().contains("does not exist")); - } - - #[test] - fn worker_implements_worker_local_interface() { - let (log_dir, registry) = test_registry(1); - let worker = test_worker(0, 1, &log_dir, registry, None); - - let session_id = worker.create_session(1).unwrap(); - let local = SessionBoundWorkerLocal { - worker: Arc::new(worker.clone()), - current_session_id: session_id, - }; - let local: &dyn WorkerLocal = &local; - let opened = local.open().unwrap(); - local.execute(opened, WorkerExecute::BeginTx).unwrap(); - local.put(opened, b"a".to_vec(), b"1".to_vec()).unwrap(); - local.put(opened, b"b".to_vec(), b"2".to_vec()).unwrap(); - - assert_eq!(local.get(opened, b"a").unwrap(), Some(b"1".to_vec())); - assert_eq!(local.range(opened, b"a", b"z").unwrap().len(), 2); - local.execute(opened, WorkerExecute::CommitTx).unwrap(); - assert_eq!(worker.get(b"a").unwrap(), Some(b"1".to_vec())); - local.close(opened).unwrap(); - } - - #[test] - fn worker_rollback_discards_staged_writes() { - let (log_dir, registry) = test_registry(1); - let worker = test_worker(0, 1, &log_dir, registry, None); - - let session_id = worker.create_session(1).unwrap(); - let local = SessionBoundWorkerLocal { - worker: Arc::new(worker.clone()), - current_session_id: session_id, - }; - let local: &dyn WorkerLocal = &local; - - local.execute(session_id, WorkerExecute::BeginTx).unwrap(); - local.put(session_id, b"a".to_vec(), b"1".to_vec()).unwrap(); - assert_eq!(local.get(session_id, b"a").unwrap(), Some(b"1".to_vec())); - local - .execute(session_id, WorkerExecute::RollbackTx) - .unwrap(); - - assert_eq!(local.get(session_id, b"a").unwrap(), None); - assert_eq!(worker.get(b"a").unwrap(), None); - } - - #[test] - fn worker_can_transfer_connection_sessions_between_partitions() { - let (log_dir, registry) = test_registry(2); - let source = test_worker(0, 2, &log_dir, registry.clone(), None); - let target = test_worker(1, 2, &log_dir, registry.clone(), None); - - let conn_id = 41; - let session_a = source.create_session(conn_id).unwrap(); - let session_b = source.create_session(conn_id).unwrap(); - let target_identity = registry.worker(1).unwrap(); - let action = SessionOpenTransferAction::new( - 7, - SessionOpenConfig::new(session_a, target_identity.worker_id, 1), - ); - - let transferred = source - .prepare_connection_transfer(conn_id, Some(action)) - .unwrap(); - assert_eq!(transferred.len(), 2); - assert!(source.get_for_connection(conn_id, session_a, b"k").is_err()); - - target - .adopt_connection_sessions(conn_id, &transferred) - .unwrap(); - assert_eq!( - target - .open_session_with_config(conn_id, action.config()) - .unwrap(), - session_a - ); - target - .put_for_connection(conn_id, session_b, b"k".to_vec(), b"v".to_vec()) - .unwrap(); - assert_eq!( - target.get_for_connection(conn_id, session_b, b"k").unwrap(), - Some(b"v".to_vec()) - ); - } - - #[test] - fn worker_rejects_transfer_with_active_transaction() { - let (log_dir, registry) = test_registry(2); - let worker = test_worker(0, 2, &log_dir, registry.clone(), None); - let conn_id = 51; - let session_id = worker.create_session(conn_id).unwrap(); - worker - .execute_tx(session_id, WorkerExecute::BeginTx) - .unwrap(); - - let err = worker - .prepare_connection_transfer( - conn_id, - Some(SessionOpenTransferAction::new( - 1, - SessionOpenConfig::new(session_id, registry.worker(1).unwrap().worker_id, 1), - )), - ) - .unwrap_err(); - assert!(err.to_string().contains("cannot be transferred")); - } - - #[test] - fn worker_snapshot_isolation_hides_later_commits_from_existing_tx() { - let (log_dir, registry) = test_registry(1); - let worker = test_worker(0, 1, &log_dir, registry, None); - - let session_a = worker.create_session(1).unwrap(); - let session_b = worker.create_session(2).unwrap(); - worker - .execute_tx(session_a, WorkerExecute::BeginTx) - .unwrap(); - ::put(&worker, session_b, b"k".to_vec(), b"v1".to_vec()) - .unwrap(); - - assert_eq!( - ::get(&worker, session_a, b"k").unwrap(), - None - ); - assert_eq!( - ::get(&worker, session_b, b"k").unwrap(), - Some(b"v1".to_vec()) - ); - } - - #[test] - fn worker_snapshot_isolation_range_stays_stable_for_existing_tx() { - let (log_dir, registry) = test_registry(1); - let worker = test_worker(0, 1, &log_dir, registry, None); - - let session_a = worker.create_session(1).unwrap(); - let session_b = worker.create_session(2).unwrap(); - ::put(&worker, session_b, b"a".to_vec(), b"1".to_vec()) - .unwrap(); - worker - .execute_tx(session_a, WorkerExecute::BeginTx) - .unwrap(); - ::put(&worker, session_b, b"b".to_vec(), b"2".to_vec()) - .unwrap(); - - let rows = ::range(&worker, session_a, b"a", b"z").unwrap(); - assert_eq!( - rows, - vec![KvItem { - key: b"a".to_vec(), - value: b"1".to_vec() - }] - ); - } - - #[test] - fn worker_first_committer_wins_without_locks() { - let (log_dir, registry) = test_registry(1); - let worker = test_worker(0, 1, &log_dir, registry, None); - - let session_a = worker.create_session(1).unwrap(); - let session_b = worker.create_session(2).unwrap(); - worker - .execute_tx(session_a, WorkerExecute::BeginTx) - .unwrap(); - worker - .execute_tx(session_b, WorkerExecute::BeginTx) - .unwrap(); - ::put(&worker, session_a, b"k".to_vec(), b"v1".to_vec()) - .unwrap(); - ::put(&worker, session_b, b"k".to_vec(), b"v2".to_vec()) - .unwrap(); - - worker - .execute_tx(session_a, WorkerExecute::CommitTx) - .unwrap(); - let err = worker - .execute_tx(session_b, WorkerExecute::CommitTx) - .unwrap_err(); - - assert!(err.to_string().contains("write-write conflict")); - assert_eq!(worker.get(b"k").unwrap(), Some(b"v1".to_vec())); - } -} diff --git a/mudu_kernel/src/server_ur/worker_connection.rs b/mudu_kernel/src/server_ur/worker_connection.rs deleted file mode 100644 index efc8cb5..0000000 --- a/mudu_kernel/src/server_ur/worker_connection.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::net::SocketAddr; -use std::os::fd::RawFd; - -pub(in crate::server_ur) struct WorkerConnection { - fd: RawFd, - remote_addr: SocketAddr, - read_buf: Vec, - recv_buf: Box<[u8; 8192]>, - pending_write: Vec, - send_inflight: Option>, - recv_inflight: bool, - recv_ready_queued: bool, - send_ready_queued: bool, - close_submitted: bool, -} - -impl WorkerConnection { - pub(in crate::server_ur) fn new(fd: RawFd, remote_addr: SocketAddr) -> Self { - Self { - fd, - remote_addr, - read_buf: Vec::with_capacity(4096), - recv_buf: Box::new([0u8; 8192]), - pending_write: Vec::with_capacity(4096), - send_inflight: None, - recv_inflight: false, - recv_ready_queued: false, - send_ready_queued: false, - close_submitted: false, - } - } - - pub(in crate::server_ur) fn fd(&self) -> RawFd { - self.fd - } - - pub(in crate::server_ur) fn remote_addr(&self) -> SocketAddr { - self.remote_addr - } - - pub(in crate::server_ur) fn read_buf(&self) -> &[u8] { - &self.read_buf - } - - pub(in crate::server_ur) fn read_buf_mut(&mut self) -> &mut Vec { - &mut self.read_buf - } - - pub(in crate::server_ur) fn recv_buf_mut_ptr(&mut self) -> *mut u8 { - self.recv_buf.as_mut_ptr() - } - - pub(in crate::server_ur) fn recv_buf_len(&self) -> usize { - self.recv_buf.len() - } - - pub(in crate::server_ur) fn recv_slice(&self, len: usize) -> &[u8] { - &self.recv_buf[..len] - } - - pub(in crate::server_ur) fn pending_write(&self) -> &[u8] { - &self.pending_write - } - - pub(in crate::server_ur) fn pending_write_mut(&mut self) -> &mut Vec { - &mut self.pending_write - } - - pub(in crate::server_ur) fn extend_pending_write(&mut self, payload: &[u8]) { - self.pending_write.extend_from_slice(payload); - } - - pub(in crate::server_ur) fn take_pending_write(&mut self) -> Vec { - std::mem::take(&mut self.pending_write) - } - - pub(in crate::server_ur) fn send_inflight(&self) -> Option<&Vec> { - self.send_inflight.as_ref() - } - - pub(in crate::server_ur) fn set_send_inflight(&mut self, payload: Option>) { - self.send_inflight = payload; - } - - pub(in crate::server_ur) fn take_send_inflight(&mut self) -> Option> { - self.send_inflight.take() - } - - pub(in crate::server_ur) fn recv_inflight(&self) -> bool { - self.recv_inflight - } - - pub(in crate::server_ur) fn set_recv_inflight(&mut self, value: bool) { - self.recv_inflight = value; - } - - pub(in crate::server_ur) fn recv_ready_queued(&self) -> bool { - self.recv_ready_queued - } - - pub(in crate::server_ur) fn set_recv_ready_queued(&mut self, value: bool) { - self.recv_ready_queued = value; - } - - pub(in crate::server_ur) fn send_ready_queued(&self) -> bool { - self.send_ready_queued - } - - pub(in crate::server_ur) fn set_send_ready_queued(&mut self, value: bool) { - self.send_ready_queued = value; - } - - pub(in crate::server_ur) fn close_submitted(&self) -> bool { - self.close_submitted - } - - pub(in crate::server_ur) fn set_close_submitted(&mut self, value: bool) { - self.close_submitted = value; - } -} diff --git a/mudu_kernel/src/server_ur/worker_local.rs b/mudu_kernel/src/server_ur/worker_local.rs deleted file mode 100644 index cce3620..0000000 --- a/mudu_kernel/src/server_ur/worker_local.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::storage::worker_kv_store::KvItem; -use mudu::common::id::OID; -use mudu::common::result::RS; -use std::sync::Arc; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum WorkerExecute { - BeginTx, - CommitTx, - RollbackTx, -} - -pub trait WorkerLocal: Send + Sync { - fn open(&self) -> RS; - - fn open_argv(&self, worker_id: OID) -> RS { - if worker_id == 0 { - self.open() - } else { - Err(mudu::m_error!( - mudu::error::ec::EC::NotImplemented, - format!("worker-local open on worker {} is not supported", worker_id) - )) - } - } - - fn close(&self, session_id: OID) -> RS<()>; - - fn execute(&self, session_id: OID, instruction: WorkerExecute) -> RS<()>; - - fn put(&self, session_id: OID, key: Vec, value: Vec) -> RS<()>; - - fn get(&self, session_id: OID, key: &[u8]) -> RS>>; - - fn range(&self, session_id: OID, start_key: &[u8], end_key: &[u8]) -> RS>; -} - -pub type WorkerLocalRef = Arc; diff --git a/mudu_kernel/src/server_ur/worker_local_log.rs b/mudu_kernel/src/server_ur/worker_local_log.rs deleted file mode 100644 index d8f32c9..0000000 --- a/mudu_kernel/src/server_ur/worker_local_log.rs +++ /dev/null @@ -1,444 +0,0 @@ -use crate::x_log::worker_kv_log::{WorkerKvLog, WorkerLogLayout}; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use std::collections::{HashMap, VecDeque}; -use std::ffi::CString; -use std::fs::File; -use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; -use std::os::unix::ffi::OsStrExt; -use std::sync::Arc; - -pub(in crate::server_ur) struct WorkerLocalLog { - layout: WorkerLogLayout, - current_sequence: Option, - next_sequence: u64, - chunks: HashMap, - pending: VecDeque, - next_open_request_id: u64, - next_close_request_id: u64, - pending_close: VecDeque, - inflight_open: Option, - inflight_close: Option, - inflight_write: Option, -} - -#[derive(Clone)] -pub(in crate::server_ur) struct OpenFileRequest { - request_id: u64, - path: CString, - flags: i32, - mode: u32, -} - -#[derive(Clone)] -pub(in crate::server_ur) struct CloseFileRequest { - request_id: u64, - fd: RawFd, -} - -pub(in crate::server_ur) struct InflightLogWrite { - chunk_sequence: u64, - file: Arc, - offset: u64, - payload: Vec, -} - -struct PendingLogWrite { - chunk_sequence: u64, - offset: u64, - payload: Vec, -} - -struct InflightLogOpen { - request_id: u64, - chunk_sequence: u64, -} - -struct LogChunkState { - file: Option>, - opening: bool, - size: u64, -} - -impl WorkerLocalLog { - pub(in crate::server_ur) fn open(layout: WorkerLogLayout) -> RS { - let tail = layout.scan_tail()?; - let mut chunks = HashMap::new(); - if let Some(sequence) = tail.current_sequence { - chunks.insert( - sequence, - LogChunkState { - file: None, - opening: false, - size: tail.current_size, - }, - ); - } - Ok(Self { - layout, - current_sequence: tail.current_sequence, - next_sequence: tail.next_sequence, - chunks, - pending: VecDeque::new(), - next_open_request_id: 1, - next_close_request_id: 1, - pending_close: VecDeque::new(), - inflight_open: None, - inflight_close: None, - inflight_write: None, - }) - } - - pub(in crate::server_ur) fn enqueue_put(&mut self, key: &[u8], value: &[u8]) -> RS<()> { - self.enqueue_payload(WorkerKvLog::encode_put_record(key, value)) - } - - pub(in crate::server_ur) fn take_pending_open_file(&mut self) -> RS> { - if self.inflight_open.is_some() { - return Ok(None); - } - let Some(write) = self.pending.front() else { - return Ok(None); - }; - let Some(chunk) = self.chunks.get_mut(&write.chunk_sequence) else { - return Err(m_error!( - EC::InternalErr, - format!("missing log chunk state {}", write.chunk_sequence) - )); - }; - if chunk.file.is_some() || chunk.opening { - return Ok(None); - } - let path = self.layout.chunk_path(write.chunk_sequence); - let path = CString::new(path.as_os_str().as_bytes()) - .map_err(|_| m_error!(EC::ParseErr, "worker log chunk path contains NUL byte"))?; - let request_id = self.next_open_request_id; - self.next_open_request_id += 1; - chunk.opening = true; - self.inflight_open = Some(InflightLogOpen { - request_id, - chunk_sequence: write.chunk_sequence, - }); - Ok(Some(OpenFileRequest::new( - request_id, - path, - libc::O_CREAT | libc::O_RDWR | libc::O_CLOEXEC, - 0o644, - ))) - } - - pub(in crate::server_ur) fn rollback_pending_open_file(&mut self, request_id: u64) -> RS<()> { - let inflight = self.inflight_open.take().ok_or_else(|| { - m_error!( - EC::InternalErr, - "rollback worker log open without inflight open" - ) - })?; - if inflight.request_id != request_id { - return Err(m_error!( - EC::InternalErr, - format!( - "rollback worker log open request id mismatch: expected {}, got {}", - inflight.request_id, request_id - ) - )); - } - let chunk = self - .chunks - .get_mut(&inflight.chunk_sequence) - .ok_or_else(|| { - m_error!( - EC::InternalErr, - "worker log chunk disappeared during rollback" - ) - })?; - chunk.opening = false; - Ok(()) - } - - pub(in crate::server_ur) fn finish_pending_open_file( - &mut self, - request_id: u64, - fd: RawFd, - ) -> RS<()> { - let inflight = self.inflight_open.take().ok_or_else(|| { - m_error!( - EC::InternalErr, - "worker log open completion without inflight open" - ) - })?; - if inflight.request_id != request_id { - return Err(m_error!( - EC::InternalErr, - format!( - "worker log open completion request id mismatch: expected {}, got {}", - inflight.request_id, request_id - ) - )); - } - let chunk = self - .chunks - .get_mut(&inflight.chunk_sequence) - .ok_or_else(|| m_error!(EC::InternalErr, "worker log chunk disappeared during open"))?; - chunk.opening = false; - let file = unsafe { File::from_raw_fd(fd) }; - chunk.file = Some(Arc::new(file)); - Ok(()) - } - - pub(in crate::server_ur) fn take_pending_close_file(&mut self) -> Option { - if self.inflight_close.is_some() { - return None; - } - let request = self.pending_close.pop_front()?; - self.inflight_close = Some(request.request_id()); - Some(request) - } - - pub(in crate::server_ur) fn rollback_pending_close_file( - &mut self, - request: CloseFileRequest, - ) -> RS<()> { - if self.inflight_close.take() != Some(request.request_id()) { - return Err(m_error!( - EC::InternalErr, - format!( - "rollback worker log close request id mismatch for {}", - request.request_id() - ) - )); - } - self.pending_close.push_front(request); - Ok(()) - } - - pub(in crate::server_ur) fn finish_pending_close_file(&mut self, request_id: u64) -> RS<()> { - if self.inflight_close.take() != Some(request_id) { - return Err(m_error!( - EC::InternalErr, - format!( - "worker log close completion request id mismatch for {}", - request_id - ) - )); - } - Ok(()) - } - - pub(in crate::server_ur) fn take_pending_write(&mut self) -> Option { - if self.inflight_write.is_some() { - return None; - } - let write = self.pending.front()?; - let chunk = self.chunks.get(&write.chunk_sequence)?; - let file = chunk.file.clone()?; - let write = self.pending.pop_front()?; - Some(InflightLogWrite::new( - write.chunk_sequence, - file, - write.offset, - write.payload, - )) - } - - pub(in crate::server_ur) fn inflight_open(&self) -> bool { - self.inflight_open.is_some() - } - - pub(in crate::server_ur) fn inflight_close(&self) -> bool { - self.inflight_close.is_some() - } - - pub(in crate::server_ur) fn inflight_write(&self) -> Option<&InflightLogWrite> { - self.inflight_write.as_ref() - } - - pub(in crate::server_ur) fn take_inflight_write(&mut self) -> Option { - self.inflight_write.take() - } - - pub(in crate::server_ur) fn set_inflight_write(&mut self, inflight: Option) { - self.inflight_write = inflight; - } - - pub(in crate::server_ur) fn cleanup_chunk_if_unused(&mut self, chunk_sequence: u64) -> RS<()> { - let still_referenced = self.current_sequence == Some(chunk_sequence) - || self - .pending - .iter() - .any(|write| write.chunk_sequence == chunk_sequence) - || self - .inflight_open - .as_ref() - .map(|open| open.chunk_sequence == chunk_sequence) - .unwrap_or(false) - || self - .inflight_write - .as_ref() - .map(|write| write.chunk_sequence == chunk_sequence) - .unwrap_or(false); - if !still_referenced { - let Some(chunk) = self.chunks.remove(&chunk_sequence) else { - return Ok(()); - }; - if let Some(file) = chunk.file { - match Arc::try_unwrap(file) { - Ok(file) => { - let request_id = self.next_close_request_id; - self.next_close_request_id += 1; - self.pending_close - .push_back(CloseFileRequest::new(request_id, file.into_raw_fd())); - } - Err(_shared) => {} - } - } - } - Ok(()) - } - - fn enqueue_payload(&mut self, payload: Vec) -> RS<()> { - if payload.is_empty() { - return Ok(()); - } - let payload_len = payload.len() as u64; - let (chunk_sequence, offset) = if payload_len > self.layout.chunk_size() { - let chunk_sequence = self.allocate_chunk_sequence(); - self.chunks.insert( - chunk_sequence, - LogChunkState { - file: None, - opening: false, - size: payload_len, - }, - ); - (chunk_sequence, 0) - } else { - let chunk_sequence = self.select_current_chunk(payload_len); - let chunk = self.chunks.get_mut(&chunk_sequence).ok_or_else(|| { - m_error!( - EC::InternalErr, - format!("missing current log chunk state {}", chunk_sequence) - ) - })?; - let offset = chunk.size; - chunk.size += payload_len; - if chunk.size >= self.layout.chunk_size() - && self.current_sequence == Some(chunk_sequence) - { - self.current_sequence = None; - } - (chunk_sequence, offset) - }; - self.pending.push_back(PendingLogWrite { - chunk_sequence, - offset, - payload, - }); - Ok(()) - } - - fn select_current_chunk(&mut self, payload_len: u64) -> u64 { - if let Some(sequence) = self.current_sequence { - if let Some(chunk) = self.chunks.get(&sequence) { - if chunk.size + payload_len <= self.layout.chunk_size() { - return sequence; - } - } - } - let sequence = self.allocate_chunk_sequence(); - self.chunks.insert( - sequence, - LogChunkState { - file: None, - opening: false, - size: 0, - }, - ); - self.current_sequence = Some(sequence); - sequence - } - - fn allocate_chunk_sequence(&mut self) -> u64 { - let sequence = self.next_sequence; - self.next_sequence += 1; - sequence - } -} - -impl InflightLogWrite { - fn new(chunk_sequence: u64, file: Arc, offset: u64, payload: Vec) -> Self { - Self { - chunk_sequence, - file, - offset, - payload, - } - } - - pub(in crate::server_ur) fn chunk_sequence(&self) -> u64 { - self.chunk_sequence - } - - pub(in crate::server_ur) fn fd(&self) -> RawFd { - self.file.as_raw_fd() - } - - pub(in crate::server_ur) fn offset(&self) -> u64 { - self.offset - } - - pub(in crate::server_ur) fn payload(&self) -> &[u8] { - &self.payload - } - - pub(in crate::server_ur) fn payload_len(&self) -> usize { - self.payload.len() - } - - pub(in crate::server_ur) fn consume_prefix(&mut self, written: usize) { - self.payload.drain(0..written); - self.offset += written as u64; - } -} - -impl OpenFileRequest { - pub(in crate::server_ur) fn new(request_id: u64, path: CString, flags: i32, mode: u32) -> Self { - Self { - request_id, - path, - flags, - mode, - } - } - - pub(in crate::server_ur) fn request_id(&self) -> u64 { - self.request_id - } - - pub(in crate::server_ur) fn path(&self) -> &CString { - &self.path - } - - pub(in crate::server_ur) fn flags(&self) -> i32 { - self.flags - } - - pub(in crate::server_ur) fn mode(&self) -> u32 { - self.mode - } -} - -impl CloseFileRequest { - fn new(request_id: u64, fd: RawFd) -> Self { - Self { request_id, fd } - } - - pub(in crate::server_ur) fn request_id(&self) -> u64 { - self.request_id - } - - pub(in crate::server_ur) fn fd(&self) -> RawFd { - self.fd - } -} diff --git a/mudu_kernel/src/server_ur/worker_mailbox.rs b/mudu_kernel/src/server_ur/worker_mailbox.rs deleted file mode 100644 index dd7fd31..0000000 --- a/mudu_kernel/src/server_ur/worker_mailbox.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::server_ur::transferred_connection::TransferredConnection; - -#[derive(Debug)] -pub(in crate::server_ur) enum WorkerMailboxMsg { - AdoptConnection(TransferredConnection), - Shutdown, -} diff --git a/mudu_kernel/src/server_ur/worker_ring_loop.rs b/mudu_kernel/src/server_ur/worker_ring_loop.rs deleted file mode 100644 index f5e8d66..0000000 --- a/mudu_kernel/src/server_ur/worker_ring_loop.rs +++ /dev/null @@ -1,1054 +0,0 @@ -use crate::server_ur::inflight_op::{AcceptOp, CloseFileOp, InflightOp, OpenFileOp}; -use crate::server_ur::procedure_task::{FrameDispatch, ProcedureTask}; -use crate::server_ur::procedure_task_waker::ProcedureTaskWaker; -use crate::server_ur::server_iouring; -use crate::server_ur::server_iouring::RecoveryCoordinator; -use crate::server_ur::worker::IoUringWorker; -use crate::server_ur::worker_connection::WorkerConnection; -use crate::server_ur::worker_local_log::{InflightLogWrite, WorkerLocalLog}; -use crate::server_ur::worker_mailbox::WorkerMailboxMsg; -use crate::x_log::worker_kv_log::WorkerKvLog; -use crossbeam_queue::SegQueue; -use futures::task::waker; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use mudu_contract::protocol::{ - encode_error_response, encode_procedure_invoke_response, ProcedureInvokeResponse, -}; -use mudu_contract::protocol::{encode_session_create_response, SessionCreateResponse}; -use std::collections::{HashMap, VecDeque}; -use std::fs::OpenOptions; -use std::os::fd::{AsRawFd, RawFd}; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::thread; -use std::time::Duration; - -#[derive(Debug, Default, Clone)] -pub(in crate::server_ur) struct WorkerLoopStats { - pub worker_id: usize, - pub submit_calls: u64, - pub wait_cqe_calls: u64, - pub cqe_accept: u64, - pub cqe_mailbox: u64, - pub cqe_recv: u64, - pub cqe_send: u64, - pub cqe_log_open: u64, - pub cqe_file_close: u64, - pub cqe_log_write: u64, - pub cqe_close: u64, - pub recv_queue_push: u64, - pub recv_queue_pop: u64, - pub send_queue_push: u64, - pub send_queue_pop: u64, - pub recv_submit: u64, - pub send_submit: u64, - pub log_open_submit: u64, - pub file_close_submit: u64, - pub log_write_submit: u64, - pub accept_submit: u64, - pub mailbox_submit: u64, - pub mailbox_drained: u64, - pub local_register: u64, -} - -pub(in crate::server_ur) struct WorkerRingLoop { - worker: IoUringWorker, - ring: rliburing::io_uring, - listener_fd: RawFd, - mailbox_fd: RawFd, - mailbox: Arc>, - mailboxes: Vec>>, - mailbox_fds: Vec, - conn_id_alloc: Arc, - log: WorkerLocalLog, - recovery_coordinator: Arc, - connections: HashMap>, - ready_recv: VecDeque, - ready_send: VecDeque, - inflight: HashMap, - procedure_tasks: HashMap, - // Ready tasks are polled immediately by the worker loop. - procedure_ready_queue: Arc>, - // Completion notifications are translated back into task ids before the - // next poll, which keeps the scheduling model close to io_uring CQEs. - procedure_completion_queue: Arc>, - // Maps a worker-local async op id to the suspended procedure task waiting - // for that completion. - procedure_op_registry: HashMap, - next_token: u64, - next_task_id: u64, - next_op_id: u64, - mailbox_read_submitted: bool, - shutdown_triggered: bool, - shutting_down: bool, - accept_submitted: bool, - stop: Arc, - stats: WorkerLoopStats, -} - -impl WorkerRingLoop { - pub(in crate::server_ur) fn new( - worker: IoUringWorker, - listener_fd: RawFd, - mailbox_fd: RawFd, - mailbox: Arc>, - mailboxes: Vec>>, - mailbox_fds: Vec, - conn_id_alloc: Arc, - log: WorkerLocalLog, - recovery_coordinator: Arc, - stop: Arc, - ) -> RS { - let mut ring: rliburing::io_uring = unsafe { std::mem::zeroed() }; - let mut param: rliburing::io_uring_params = unsafe { std::mem::zeroed() }; - let worker_id = worker.worker_index(); - let rc = unsafe { rliburing::io_uring_queue_init_params(1024, &mut ring, &mut param) }; - if rc != 0 { - return Err(m_error!( - EC::NetErr, - format!("io_uring_queue_init_params error {}", rc) - )); - } - Ok(Self { - worker, - ring, - listener_fd, - mailbox_fd, - mailbox, - mailboxes, - mailbox_fds, - conn_id_alloc, - log, - recovery_coordinator, - connections: HashMap::new(), - ready_recv: VecDeque::new(), - ready_send: VecDeque::new(), - inflight: HashMap::new(), - procedure_tasks: HashMap::new(), - procedure_ready_queue: Arc::new(SegQueue::new()), - procedure_completion_queue: Arc::new(SegQueue::new()), - procedure_op_registry: HashMap::new(), - next_token: 1, - next_task_id: 1, - next_op_id: 1, - mailbox_read_submitted: false, - shutdown_triggered: false, - shutting_down: false, - accept_submitted: false, - stop, - stats: WorkerLoopStats { - worker_id, - ..WorkerLoopStats::default() - }, - }) - } - - pub(in crate::server_ur) fn run(&mut self) -> RS { - if let Err(err) = self.recover_worker_log() { - self.recovery_coordinator.worker_failed(); - return Err(err); - } - self.recovery_coordinator.worker_succeeded()?; - self.run_service_loop() - } - - fn run_service_loop(&mut self) -> RS { - loop { - if self.stop.load(Ordering::Relaxed) || self.shutdown_triggered { - self.begin_shutdown()?; - } - if !self.shutting_down { - self.drain_mailbox()?; - self.drain_procedure_completions(); - self.poll_ready_procedures()?; - } else { - self.submit_close_for_drained_connections()?; - } - self.submit_mailbox_read_if_needed()?; - self.submit_accept_if_needed()?; - self.submit_recv_ops()?; - self.submit_send_ops()?; - self.submit_log_open_if_needed()?; - self.submit_file_close_if_needed()?; - self.submit_log_write_if_needed()?; - self.stats.submit_calls += 1; - let submitted = unsafe { rliburing::io_uring_submit(&mut self.ring) }; - if submitted < 0 { - return Err(m_error!( - EC::NetErr, - format!("io_uring_submit error {}", submitted) - )); - } - - if self.shutting_down && self.connections.is_empty() { - return self.finish_shutdown(); - } - - if self.inflight.is_empty() { - thread::sleep(Duration::from_millis(1)); - continue; - } - - let mut cqe_ptr: *mut rliburing::io_uring_cqe = std::ptr::null_mut(); - self.stats.wait_cqe_calls += 1; - let wait_rc = unsafe { rliburing::io_uring_wait_cqe(&mut self.ring, &mut cqe_ptr) }; - if wait_rc == -libc::EINTR { - // A signal interrupted the wait. The ring state is still valid, - // so the worker should simply retry instead of aborting. - continue; - } - if wait_rc < 0 { - return Err(m_error!( - EC::NetErr, - format!("io_uring_wait_cqe error {}", wait_rc) - )); - } - self.process_cqe(cqe_ptr)?; - - loop { - let mut next_cqe_ptr: *mut rliburing::io_uring_cqe = std::ptr::null_mut(); - let peek_rc = - unsafe { rliburing::io_uring_peek_cqe(&mut self.ring, &mut next_cqe_ptr) }; - if peek_rc == -libc::EAGAIN || next_cqe_ptr.is_null() { - break; - } - if peek_rc < 0 { - return Err(m_error!( - EC::NetErr, - format!("io_uring_peek_cqe error {}", peek_rc) - )); - } - self.process_cqe(next_cqe_ptr)?; - } - } - } - - fn recover_worker_log(&mut self) -> RS<()> { - let chunk_paths = self.worker.log_layout().chunk_paths_sorted()?; - for path in chunk_paths { - let file = OpenOptions::new() - .read(true) - .open(&path) - .map_err(|e| m_error!(EC::IOErr, "open worker log chunk for recovery error", e))?; - let size = file - .metadata() - .map_err(|e| { - m_error!( - EC::IOErr, - "read worker log chunk recovery metadata error", - e - ) - })? - .len() as usize; - if size == 0 { - continue; - } - let bytes = self.read_file_all_iouring(&file, size)?; - for (key, value) in WorkerKvLog::decode_put_records(&bytes)? { - self.worker.replay_log_entry(key, value)?; - } - } - Ok(()) - } - - fn read_file_all_iouring(&mut self, file: &std::fs::File, size: usize) -> RS> { - let mut buf = vec![0u8; size]; - let mut offset = 0usize; - while offset < size { - let sqe = unsafe { rliburing::io_uring_get_sqe(&mut self.ring) }; - if sqe.is_null() { - let submitted = unsafe { rliburing::io_uring_submit(&mut self.ring) }; - if submitted < 0 { - return Err(m_error!( - EC::IOErr, - format!("submit io_uring recovery read error {}", submitted) - )); - } - continue; - } - unsafe { - (*sqe).user_data = 0; - rliburing::io_uring_prep_read( - sqe, - file.as_raw_fd(), - buf[offset..].as_mut_ptr() as *mut libc::c_void, - (size - offset) as _, - offset as _, - ); - } - let submitted = unsafe { rliburing::io_uring_submit(&mut self.ring) }; - if submitted < 0 { - return Err(m_error!( - EC::IOErr, - format!("submit io_uring recovery read error {}", submitted) - )); - } - let mut cqe_ptr: *mut rliburing::io_uring_cqe = std::ptr::null_mut(); - let wait_rc = unsafe { rliburing::io_uring_wait_cqe(&mut self.ring, &mut cqe_ptr) }; - if wait_rc < 0 { - return Err(m_error!( - EC::IOErr, - format!("wait io_uring recovery read cqe error {}", wait_rc) - )); - } - let read = unsafe { (*cqe_ptr).res }; - unsafe { rliburing::io_uring_cqe_seen(&mut self.ring, cqe_ptr) }; - if read < 0 { - return Err(m_error!( - EC::IOErr, - format!("worker log recovery read completion error {}", read) - )); - } - if read == 0 { - break; - } - offset += read as usize; - } - buf.truncate(offset); - Ok(buf) - } - - pub(in crate::server_ur) fn process_cqe( - &mut self, - cqe_ptr: *mut rliburing::io_uring_cqe, - ) -> RS<()> { - let token = unsafe { (*cqe_ptr).user_data }; - let result = unsafe { (*cqe_ptr).res }; - unsafe { rliburing::io_uring_cqe_seen(&mut self.ring, cqe_ptr) }; - let op = self.inflight.remove(&token).ok_or_else(|| { - m_error!( - EC::InternalErr, - format!("unknown io_uring completion token {}", token) - ) - })?; - - match op { - InflightOp::Accept(op) => { - self.stats.cqe_accept += 1; - self.accept_submitted = false; - if result >= 0 { - let conn_fd = result as RawFd; - let remote_addr = - server_iouring::sockaddr_to_socket_addr(op.addr(), op.addr_len())?; - server_iouring::set_connection_options(conn_fd)?; - let conn_id = self.conn_id_alloc.fetch_add(1, Ordering::Relaxed); - let target_worker = self.worker.route_connection(conn_id, remote_addr); - if target_worker == self.worker.worker_index() { - self.register_connection(conn_id, conn_fd, remote_addr)?; - } else { - self.dispatch_mailbox_message( - target_worker, - WorkerMailboxMsg::AdoptConnection( - crate::server_ur::transferred_connection::TransferredConnection::new( - crate::server_ur::routing::ConnectionTransfer::new( - conn_id, - target_worker, - crate::server_ur::fsm::ConnectionState::Accepted, - remote_addr, - ), - conn_fd, - Vec::new(), - None, - ), - ), - )?; - } - } - } - InflightOp::MailboxRead { .. } => { - self.stats.cqe_mailbox += 1; - self.mailbox_read_submitted = false; - self.drain_mailbox()?; - } - InflightOp::Recv { conn_id } => { - self.stats.cqe_recv += 1; - if let Some(connection) = self.connections.get_mut(&conn_id) { - connection.set_recv_inflight(false); - if result <= 0 { - self.submit_close_if_needed(conn_id)?; - } else { - let read = result as usize; - let chunk = connection.recv_slice(read).to_vec(); - connection.read_buf_mut().extend_from_slice(&chunk); - self.drain_frames(conn_id)?; - if self.shutting_down { - self.submit_close_if_drained(conn_id)?; - } else { - self.queue_recv_if_needed(conn_id); - } - } - } - } - InflightOp::Send { conn_id } => { - self.stats.cqe_send += 1; - if let Some(connection) = self.connections.get_mut(&conn_id) { - if let Some(mut inflight) = connection.take_send_inflight() { - if result <= 0 { - self.submit_close_if_needed(conn_id)?; - } else { - let written = result as usize; - if written < inflight.len() { - inflight.drain(0..written); - connection.set_send_inflight(Some(inflight)); - self.queue_send_inflight(conn_id); - } else if !connection.pending_write().is_empty() { - let pending_write = connection.take_pending_write(); - connection.set_send_inflight(Some(pending_write)); - self.queue_send_inflight(conn_id); - } else if self.shutting_down { - self.submit_close_if_drained(conn_id)?; - } - } - } - } - } - InflightOp::OpenFile(op) => { - self.stats.cqe_log_open += 1; - if result < 0 { - return Err(m_error!( - EC::IOErr, - format!("worker file open completion error {}", result) - )); - } - self.log - .finish_pending_open_file(op.request_id(), result as RawFd)?; - } - InflightOp::CloseFile(op) => { - self.stats.cqe_file_close += 1; - if result < 0 { - return Err(m_error!( - EC::IOErr, - format!("worker file close completion error {}", result) - )); - } - self.log.finish_pending_close_file(op.request_id())?; - } - InflightOp::LogWrite => { - self.stats.cqe_log_write += 1; - if result < 0 { - return Err(m_error!( - EC::IOErr, - format!("worker log write completion error {}", result) - )); - } - if let Some(mut inflight) = self.log.take_inflight_write() { - let written = result as usize; - if written < inflight.payload_len() { - inflight.consume_prefix(written); - self.log.set_inflight_write(Some(inflight)); - } else { - let chunk_sequence = inflight.chunk_sequence(); - drop(inflight); - self.log.cleanup_chunk_if_unused(chunk_sequence)?; - } - } - } - InflightOp::Close { conn_id } => { - self.stats.cqe_close += 1; - self.worker.close_connection_sessions(conn_id)?; - self.connections.remove(&conn_id); - } - } - Ok(()) - } - - pub(in crate::server_ur) fn drain_procedure_completions(&mut self) { - while let Some(op_id) = self.procedure_completion_queue.pop() { - let Some(task_id) = self.procedure_op_registry.remove(&op_id) else { - continue; - }; - let Some(task) = self.procedure_tasks.get(&task_id) else { - continue; - }; - if !task.queued().swap(true, Ordering::AcqRel) { - self.procedure_ready_queue.push(task_id); - } - } - } - - pub(in crate::server_ur) fn poll_ready_procedures(&mut self) -> RS<()> { - while let Some(task_id) = self.procedure_ready_queue.pop() { - let Some(mut task) = self.procedure_tasks.remove(&task_id) else { - continue; - }; - // The task has left the ready queue and is about to be polled. A - // later wake is required to enqueue it again. - task.clear_queued(); - if let Some(waiting_on) = task.take_waiting_on() { - self.procedure_op_registry.remove(&waiting_on); - } - let op_id = self.next_op_id; - self.next_op_id += 1; - - // Each poll of a pending procedure installs a fresh worker-local - // waker bound to a new op_id. Within that single pending interval, - // repeated wakeups are coalesced by the waker so the task is - // queued for one follow-up poll at most once. If the task remains - // pending after that poll, the worker allocates a new op_id and a - // new waker for the next interval. - let waker = waker(Arc::new(ProcedureTaskWaker::new( - op_id, - self.procedure_completion_queue.clone(), - task.completed().clone(), - ))); - let mut cx = Context::from_waker(&waker); - match task.future_mut().poll(&mut cx) { - Poll::Ready(Ok(result)) => { - if let Some(connection) = self.connections.get_mut(&task.conn_id()) { - let response = encode_procedure_invoke_response( - task.request_id(), - &ProcedureInvokeResponse::new(result), - )?; - connection.extend_pending_write(&response); - self.queue_send_if_needed(task.conn_id()); - } - } - Poll::Ready(Err(err)) => { - if let Some(connection) = self.connections.get_mut(&task.conn_id()) { - let response = encode_error_response(task.request_id(), err.to_string())?; - connection.extend_pending_write(&response); - self.queue_send_if_needed(task.conn_id()); - } - } - Poll::Pending => { - // A pending future is modeled as a worker-local async - // operation. The op is not tied to the kernel ring itself, - // but it follows the same "op_id -> completion -> task - // resume" semantics so the worker loop owns progression. - task.set_waiting_on(op_id); - self.procedure_op_registry.insert(op_id, task_id); - self.procedure_tasks.insert(task_id, task); - } - } - } - Ok(()) - } - - pub(in crate::server_ur) fn drain_mailbox(&mut self) -> RS<()> { - while let Some(msg) = self.mailbox.pop() { - self.stats.mailbox_drained += 1; - self.handle_mailbox_message(msg)?; - } - Ok(()) - } - - fn handle_mailbox_message(&mut self, msg: WorkerMailboxMsg) -> RS<()> { - match msg { - WorkerMailboxMsg::AdoptConnection(connection) => { - server_iouring::set_connection_options(connection.fd())?; - self.worker.adopt_connection_sessions( - connection.transfer().conn_id(), - connection.session_ids(), - )?; - self.register_connection( - connection.transfer().conn_id(), - connection.fd(), - connection.transfer().remote_addr(), - )?; - if let Some(action) = connection.session_open_action() { - let response = match self - .worker - .open_session_with_config(connection.transfer().conn_id(), action.config()) - { - Ok(session_id) => encode_session_create_response( - action.request_id(), - &SessionCreateResponse::new(session_id), - )?, - Err(err) => encode_error_response(action.request_id(), err.to_string())?, - }; - if let Some(conn) = self.connections.get_mut(&connection.transfer().conn_id()) { - conn.extend_pending_write(&response); - } - self.queue_send_if_needed(connection.transfer().conn_id()); - } - } - WorkerMailboxMsg::Shutdown => { - self.shutdown_triggered = true; - } - } - Ok(()) - } - - pub(in crate::server_ur) fn register_connection( - &mut self, - conn_id: u64, - fd: RawFd, - remote_addr: std::net::SocketAddr, - ) -> RS<()> { - self.stats.local_register += 1; - self.connections - .insert(conn_id, Box::new(WorkerConnection::new(fd, remote_addr))); - self.queue_recv_if_needed(conn_id); - Ok(()) - } - - pub(in crate::server_ur) fn submit_accept_if_needed(&mut self) -> RS<()> { - if self.shutting_down || self.accept_submitted || self.listener_fd < 0 { - return Ok(()); - } - let sqe = unsafe { rliburing::io_uring_get_sqe(&mut self.ring) }; - if sqe.is_null() { - return Ok(()); - } - let mut op = Box::new(AcceptOp::new( - unsafe { std::mem::zeroed() }, - std::mem::size_of::() as rliburing::socklen_t, - )); - let token = self.alloc_token(); - unsafe { - (*sqe).user_data = token; - rliburing::io_uring_prep_accept( - sqe, - self.listener_fd, - op.addr_mut_ptr(), - op.addr_len_mut(), - 0, - ); - } - self.inflight.insert(token, InflightOp::Accept(op)); - self.accept_submitted = true; - self.stats.accept_submit += 1; - Ok(()) - } - - pub(in crate::server_ur) fn submit_mailbox_read_if_needed(&mut self) -> RS<()> { - if self.mailbox_read_submitted || self.shutting_down { - return Ok(()); - } - let sqe = unsafe { rliburing::io_uring_get_sqe(&mut self.ring) }; - if sqe.is_null() { - return Ok(()); - } - let mut value = Box::new(0u64); - let token = self.alloc_token(); - unsafe { - (*sqe).user_data = token; - rliburing::io_uring_prep_read( - sqe, - self.mailbox_fd, - (&mut *value) as *mut u64 as *mut libc::c_void, - std::mem::size_of::() as _, - 0, - ); - } - self.inflight - .insert(token, InflightOp::MailboxRead { value }); - self.mailbox_read_submitted = true; - self.stats.mailbox_submit += 1; - Ok(()) - } - - pub(in crate::server_ur) fn submit_recv_ops(&mut self) -> RS<()> { - if self.shutting_down { - return Ok(()); - } - while let Some(conn_id) = self.ready_recv.pop_front() { - self.stats.recv_queue_pop += 1; - let Some(connection) = self.connections.get_mut(&conn_id) else { - continue; - }; - connection.set_recv_ready_queued(false); - if connection.recv_inflight() || connection.close_submitted() { - continue; - } - let fd = connection.fd(); - let recv_ptr = connection.recv_buf_mut_ptr(); - let recv_len = connection.recv_buf_len(); - connection.set_recv_inflight(true); - let _ = connection; - let sqe = unsafe { rliburing::io_uring_get_sqe(&mut self.ring) }; - if sqe.is_null() { - if let Some(connection) = self.connections.get_mut(&conn_id) { - connection.set_recv_inflight(false); - connection.set_recv_ready_queued(true); - } - self.ready_recv.push_front(conn_id); - break; - } - let token = self.alloc_token(); - unsafe { - (*sqe).user_data = token; - rliburing::io_uring_prep_recv( - sqe, - fd, - recv_ptr as *mut libc::c_void, - recv_len as _, - 0, - ); - } - self.inflight.insert(token, InflightOp::Recv { conn_id }); - self.stats.recv_submit += 1; - } - Ok(()) - } - - pub(in crate::server_ur) fn submit_send_ops(&mut self) -> RS<()> { - while let Some(conn_id) = self.ready_send.pop_front() { - self.stats.send_queue_pop += 1; - let Some(connection) = self.connections.get_mut(&conn_id) else { - continue; - }; - connection.set_send_ready_queued(false); - if connection.close_submitted() { - continue; - } - if connection.send_inflight().is_some() { - continue; - } - if !connection.pending_write().is_empty() { - let pending_write = connection.take_pending_write(); - connection.set_send_inflight(Some(pending_write)); - } - let Some(inflight) = connection.send_inflight() else { - continue; - }; - let fd = connection.fd(); - let send_ptr = inflight.as_ptr(); - let send_len = inflight.len(); - let _ = connection; - let sqe = unsafe { rliburing::io_uring_get_sqe(&mut self.ring) }; - if sqe.is_null() { - if let Some(connection) = self.connections.get_mut(&conn_id) { - connection.set_send_ready_queued(true); - } - self.ready_send.push_front(conn_id); - break; - } - let token = self.alloc_token(); - unsafe { - (*sqe).user_data = token; - rliburing::io_uring_prep_send( - sqe, - fd, - send_ptr as *const libc::c_void, - send_len as _, - 0, - ); - } - self.inflight.insert(token, InflightOp::Send { conn_id }); - self.stats.send_submit += 1; - } - Ok(()) - } - - pub(in crate::server_ur) fn submit_log_write_if_needed(&mut self) -> RS<()> { - if self.shutting_down { - return Ok(()); - } - if self.log.inflight_write().is_none() { - if let Some(pending) = self.log.take_pending_write() { - self.log.set_inflight_write(Some(pending)); - } - } - let Some(inflight) = self.log.inflight_write() else { - return Ok(()); - }; - let log_fd = inflight.fd(); - let payload_ptr = inflight.payload().as_ptr(); - let payload_len = inflight.payload_len(); - let payload_offset = inflight.offset(); - let sqe = unsafe { rliburing::io_uring_get_sqe(&mut self.ring) }; - if sqe.is_null() { - return Ok(()); - } - let token = self.alloc_token(); - unsafe { - (*sqe).user_data = token; - rliburing::io_uring_prep_write( - sqe, - log_fd, - payload_ptr as *const libc::c_void, - payload_len as _, - payload_offset as _, - ); - } - self.inflight.insert(token, InflightOp::LogWrite); - self.stats.log_write_submit += 1; - Ok(()) - } - - pub(in crate::server_ur) fn submit_log_open_if_needed(&mut self) -> RS<()> { - if self.shutting_down || self.log.inflight_open() { - return Ok(()); - } - let Some(request) = self.log.take_pending_open_file()? else { - return Ok(()); - }; - let sqe = unsafe { rliburing::io_uring_get_sqe(&mut self.ring) }; - if sqe.is_null() { - self.log.rollback_pending_open_file(request.request_id())?; - return Ok(()); - } - let token = self.alloc_token(); - let op = OpenFileOp::new(request); - unsafe { - (*sqe).user_data = token; - rliburing::io_uring_prep_openat( - sqe, - libc::AT_FDCWD, - op.request().path().as_ptr(), - op.request().flags(), - op.request().mode(), - ); - } - self.inflight - .insert(token, InflightOp::OpenFile(Box::new(op))); - self.stats.log_open_submit += 1; - Ok(()) - } - - pub(in crate::server_ur) fn submit_file_close_if_needed(&mut self) -> RS<()> { - if self.shutting_down || self.log.inflight_close() { - return Ok(()); - } - let Some(request) = self.log.take_pending_close_file() else { - return Ok(()); - }; - let sqe = unsafe { rliburing::io_uring_get_sqe(&mut self.ring) }; - if sqe.is_null() { - self.log.rollback_pending_close_file(request)?; - return Ok(()); - } - let token = self.alloc_token(); - let op = CloseFileOp::new(request); - unsafe { - (*sqe).user_data = token; - rliburing::io_uring_prep_close(sqe, op.request().fd()); - } - self.inflight - .insert(token, InflightOp::CloseFile(Box::new(op))); - self.stats.file_close_submit += 1; - Ok(()) - } - - pub(in crate::server_ur) fn submit_close_if_needed(&mut self, conn_id: u64) -> RS<()> { - let Some(connection) = self.connections.get_mut(&conn_id) else { - return Ok(()); - }; - if connection.close_submitted() { - return Ok(()); - } - let fd = connection.fd(); - connection.set_close_submitted(true); - let _ = connection; - let sqe = unsafe { rliburing::io_uring_get_sqe(&mut self.ring) }; - if sqe.is_null() { - if let Some(connection) = self.connections.get_mut(&conn_id) { - connection.set_close_submitted(false); - } - return Ok(()); - } - let token = self.alloc_token(); - unsafe { - (*sqe).user_data = token; - rliburing::io_uring_prep_close(sqe, fd); - } - self.inflight.insert(token, InflightOp::Close { conn_id }); - Ok(()) - } - - pub(in crate::server_ur) fn drain_frames(&mut self, conn_id: u64) -> RS<()> { - loop { - let Some(connection) = self.connections.get_mut(&conn_id) else { - return Ok(()); - }; - let Some((frame, consumed)) = - server_iouring::try_decode_next_frame(connection.read_buf())? - else { - return Ok(()); - }; - connection.read_buf_mut().drain(0..consumed); - match server_iouring::dispatch_frame_iouring(&self.worker, conn_id, &frame) { - Ok(FrameDispatch::Immediate(response)) => { - connection.extend_pending_write(&response) - } - Ok(FrameDispatch::Pending(pending)) => { - let task_id = self.next_task_id; - self.next_task_id += 1; - let (pending_conn_id, request_id, completed, future) = pending.into_parts(); - self.procedure_tasks.insert( - task_id, - ProcedureTask::new(task_id, pending_conn_id, request_id, future, completed), - ); - // Every new task is queued once for its initial poll. Any - // later poll must come from a completion-triggered wakeup. - self.procedure_ready_queue.push(task_id); - } - Ok(FrameDispatch::Transfer(transfer)) => { - let remote_addr = connection.remote_addr(); - let fd = connection.fd(); - self.connections.remove(&conn_id); - self.dispatch_mailbox_message( - transfer.target_worker(), - WorkerMailboxMsg::AdoptConnection( - crate::server_ur::transferred_connection::TransferredConnection::new( - crate::server_ur::routing::ConnectionTransfer::new( - conn_id, - transfer.target_worker(), - crate::server_ur::fsm::ConnectionState::Active, - remote_addr, - ), - fd, - transfer.session_ids().to_vec(), - Some(transfer.action()), - ), - ), - )?; - return Ok(()); - } - Err(err) => { - let response = - encode_error_response(frame.header().request_id(), err.to_string())?; - connection.extend_pending_write(&response); - } - } - self.queue_send_if_needed(conn_id); - } - } - - fn dispatch_mailbox_message(&self, target_worker: usize, msg: WorkerMailboxMsg) -> RS<()> { - let Some(mailbox) = self.mailboxes.get(target_worker) else { - return Err(m_error!( - EC::InternalErr, - format!("mailbox target worker {} is out of range", target_worker) - )); - }; - let Some(&fd) = self.mailbox_fds.get(target_worker) else { - return Err(m_error!( - EC::InternalErr, - format!( - "mailbox eventfd target worker {} is out of range", - target_worker - ) - )); - }; - mailbox.push(msg); - server_iouring::notify_mailbox_fd(fd) - } - - pub(in crate::server_ur) fn alloc_token(&mut self) -> u64 { - let token = self.next_token; - self.next_token += 1; - token - } - - fn queue_recv_if_needed(&mut self, conn_id: u64) { - if self.shutting_down { - return; - } - let Some(connection) = self.connections.get_mut(&conn_id) else { - return; - }; - if connection.close_submitted() - || connection.recv_inflight() - || connection.recv_ready_queued() - { - return; - } - connection.set_recv_ready_queued(true); - self.ready_recv.push_back(conn_id); - self.stats.recv_queue_push += 1; - } - - fn queue_send_if_needed(&mut self, conn_id: u64) { - let Some(connection) = self.connections.get_mut(&conn_id) else { - return; - }; - if connection.close_submitted() - || connection.send_inflight().is_some() - || connection.pending_write().is_empty() - || connection.send_ready_queued() - { - return; - } - connection.set_send_ready_queued(true); - self.ready_send.push_back(conn_id); - self.stats.send_queue_push += 1; - } - - fn queue_send_inflight(&mut self, conn_id: u64) { - let Some(connection) = self.connections.get_mut(&conn_id) else { - return; - }; - if connection.close_submitted() - || connection.send_inflight().is_none() - || connection.send_ready_queued() - { - return; - } - connection.set_send_ready_queued(true); - self.ready_send.push_back(conn_id); - self.stats.send_queue_push += 1; - } - - fn begin_shutdown(&mut self) -> RS<()> { - if self.shutting_down { - return Ok(()); - } - self.shutting_down = true; - self.close_pending_mailbox_connections()?; - self.ready_recv.clear(); - self.procedure_tasks.clear(); - self.procedure_op_registry.clear(); - while self.procedure_ready_queue.pop().is_some() {} - while self.procedure_completion_queue.pop().is_some() {} - if self.listener_fd >= 0 { - let rc = unsafe { libc::close(self.listener_fd) }; - if rc != 0 { - return Err(m_error!( - EC::NetErr, - "close io_uring listener during shutdown error", - std::io::Error::last_os_error() - )); - } - self.listener_fd = -1; - } - self.submit_close_for_drained_connections()?; - Ok(()) - } - - fn close_pending_mailbox_connections(&mut self) -> RS<()> { - while let Some(msg) = self.mailbox.pop() { - if let WorkerMailboxMsg::AdoptConnection(connection) = msg { - let rc = unsafe { libc::close(connection.fd()) }; - if rc != 0 { - return Err(m_error!( - EC::NetErr, - "close transferred io_uring connection during shutdown error", - std::io::Error::last_os_error() - )); - } - } - } - Ok(()) - } - - fn submit_close_for_drained_connections(&mut self) -> RS<()> { - let conn_ids: Vec = self.connections.keys().copied().collect(); - for conn_id in conn_ids { - self.submit_close_if_drained(conn_id)?; - } - Ok(()) - } - - fn submit_close_if_drained(&mut self, conn_id: u64) -> RS<()> { - let Some(connection) = self.connections.get(&conn_id) else { - return Ok(()); - }; - if connection.send_inflight().is_some() - || !connection.pending_write().is_empty() - || !connection.read_buf().is_empty() - { - return Ok(()); - } - self.submit_close_if_needed(conn_id) - } - - fn finish_shutdown(&mut self) -> RS { - unsafe { rliburing::io_uring_queue_exit(&mut self.ring) }; - Ok(self.stats.clone()) - } -} diff --git a/mudu_kernel/src/sql/binder.rs b/mudu_kernel/src/sql/binder.rs new file mode 100644 index 0000000..082b71a --- /dev/null +++ b/mudu_kernel/src/sql/binder.rs @@ -0,0 +1,436 @@ +use crate::contract::meta_mgr::MetaMgr; +use crate::contract::schema_column::SchemaColumn; +use crate::contract::schema_table::SchemaTable; +use crate::contract::table_desc::TableDesc; +use crate::executor::project_tuple_desc; +use crate::sql::bound_stmt::{ + BoundCommand, BoundCopyFrom, BoundCopyTo, BoundCreateTable, BoundDelete, BoundDropTable, + BoundInsert, BoundPredicate, BoundQuery, BoundSelect, BoundStmt, BoundUpdate, +}; +use crate::sql::copy_layout::CopyLayout; +use crate::sql::value_codec::ValueCodec; +use mudu::common::result::RS; +use mudu::error::ec::EC as ER; +use mudu::m_error; +use mudu_contract::database::sql_params::SQLParams; +use mudu_type::dt_info::DTInfo; +use sql_parser::ast::expr_compare::ExprCompare; +use sql_parser::ast::expr_item::{ExprItem, ExprValue}; +use sql_parser::ast::expr_operator::ValueCompare; +use sql_parser::ast::stmt_create_table::StmtCreateTable; +use sql_parser::ast::stmt_delete::StmtDelete; +use sql_parser::ast::stmt_drop_table::StmtDropTable; +use sql_parser::ast::stmt_insert::StmtInsert; +use sql_parser::ast::stmt_type::{StmtCommand, StmtType}; +use sql_parser::ast::stmt_update::{AssignedValue, StmtUpdate}; +use std::ops::Bound; +use std::sync::Arc; + +pub struct Binder { + meta_mgr: Arc, +} + +impl Binder { + pub fn new(meta_mgr: Arc) -> Self { + Self { meta_mgr } + } + + pub async fn bind(&self, stmt: StmtType, params: &dyn SQLParams) -> RS { + match stmt { + StmtType::Select(stmt) => Ok(BoundStmt::Query(BoundQuery::Select( + self.bind_select(stmt, params).await?, + ))), + StmtType::Command(command) => Ok(BoundStmt::Command( + self.bind_command(command, params).await?, + )), + } + } + + async fn bind_command(&self, command: StmtCommand, params: &dyn SQLParams) -> RS { + match command { + StmtCommand::CreateTable(stmt) => { + Ok(BoundCommand::CreateTable(self.bind_create_table(stmt)?)) + } + StmtCommand::DropTable(stmt) => { + Ok(BoundCommand::DropTable(self.bind_drop_table(stmt).await?)) + } + StmtCommand::Insert(stmt) => { + Ok(BoundCommand::Insert(self.bind_insert(stmt, params).await?)) + } + StmtCommand::Update(stmt) => { + Ok(BoundCommand::Update(self.bind_update(stmt, params).await?)) + } + StmtCommand::Delete(stmt) => { + Ok(BoundCommand::Delete(self.bind_delete(stmt, params).await?)) + } + StmtCommand::CopyFrom(stmt) => { + Ok(BoundCommand::CopyFrom(self.bind_copy_from(stmt).await?)) + } + StmtCommand::CopyTo(stmt) => Ok(BoundCommand::CopyTo(self.bind_copy_to(stmt).await?)), + } + } + + async fn bind_select( + &self, + stmt: sql_parser::ast::stmt_select::StmtSelect, + params: &dyn SQLParams, + ) -> RS { + let table_desc = self.get_table_by_name(stmt.get_table_reference()).await?; + let select_attrs = self.select_attrs(&table_desc, stmt.get_select_term_list())?; + let tuple_desc = project_tuple_desc( + &table_desc, + &crate::x_engine::api::VecSelTerm::new(select_attrs.clone()), + ); + let predicate = self.bind_predicate(&table_desc, stmt.get_where_predicate(), params)?; + Ok(BoundSelect { + table_id: table_desc.id(), + select_attrs, + tuple_desc, + predicate, + }) + } + + fn bind_create_table(&self, mut stmt: StmtCreateTable) -> RS { + stmt.assign_index_for_columns(); + let key_columns = stmt + .primary_columns() + .iter() + .map(Self::schema_column_from_ast) + .collect::>>()?; + let value_columns = stmt + .non_primary_columns() + .iter() + .map(Self::schema_column_from_ast) + .collect::>>()?; + Ok(BoundCreateTable { + schema: SchemaTable::new(stmt.table_name().clone(), key_columns, value_columns), + }) + } + + async fn bind_drop_table(&self, stmt: StmtDropTable) -> RS { + match self + .meta_mgr + .get_table_by_name(&stmt.table_name().to_string()) + .await? + { + Some(table_desc) => Ok(BoundDropTable { + table_id: table_desc.id(), + }), + None if stmt.drop_if_exists() => Err(m_error!( + ER::NoSuchElement, + "drop if exists is not implemented" + )), + None => Err(m_error!( + ER::NoSuchElement, + format!("cannot find table {}", stmt.table_name()) + )), + } + } + + async fn bind_insert(&self, stmt: StmtInsert, params: &dyn SQLParams) -> RS { + let table_desc = self.get_table_by_name(stmt.table_name()).await?; + if stmt.values_list().len() != 1 { + return Err(m_error!( + ER::NotImplemented, + "multi-row insert is not implemented" + )); + } + + let columns = if stmt.columns().is_empty() { + let total = table_desc.key_info().len() + table_desc.value_info().len(); + (0..total) + .map(|attr| table_desc.get_attr(attr).name().clone()) + .collect::>() + } else { + stmt.columns().clone() + }; + + let values = &stmt.values_list()[0]; + if columns.len() != values.len() { + return Err(m_error!(ER::IOErr, "insert column size mismatch")); + } + + let mut param_index = 0; + let mut key = vec![]; + let mut value = vec![]; + for (name, expr) in columns.iter().zip(values.iter()) { + let attr = self.attr_index_by_name(&table_desc, name)?; + let field = table_desc.get_attr(attr); + let binary = + ValueCodec::binary_from_expr(expr, field.type_desc(), params, &mut param_index)?; + if field.is_primary() { + key.push((attr, binary)); + } else { + value.push((attr, binary)); + } + } + + Ok(BoundInsert { + table_id: table_desc.id(), + key, + value, + }) + } + + async fn bind_copy_from( + &self, + stmt: sql_parser::ast::stmt_copy_from::StmtCopyFrom, + ) -> RS { + let table_desc = self.get_table_by_name(stmt.copy_to_table_name()).await?; + let layout = CopyLayout::new(&table_desc, stmt.table_columns())?; + Ok(BoundCopyFrom { + file_path: stmt.copy_from_file_path().clone(), + table_id: table_desc.id(), + key_index: layout.key_index().to_vec(), + value_index: layout.value_index().to_vec(), + }) + } + + async fn bind_copy_to( + &self, + stmt: sql_parser::ast::stmt_copy_to::StmtCopyTo, + ) -> RS { + let table_desc = self.get_table_by_name(stmt.copy_from_table_name()).await?; + let layout = CopyLayout::new(&table_desc, stmt.table_columns())?; + Ok(BoundCopyTo { + file_path: stmt.copy_to_file_path().clone(), + table_id: table_desc.id(), + key_indexing: layout.key_index().to_vec(), + value_indexing: layout.value_index().to_vec(), + }) + } + + async fn bind_update(&self, stmt: StmtUpdate, params: &dyn SQLParams) -> RS { + let table_desc = self.get_table_by_name(stmt.get_table_reference()).await?; + let mut param_index = 0; + let mut value = Vec::with_capacity(stmt.get_set_values().len()); + + for assignment in stmt.get_set_values() { + let attr = self.attr_index_by_name(&table_desc, assignment.get_column_reference())?; + let field = table_desc.get_attr(attr); + if field.is_primary() { + return Err(m_error!( + ER::NotImplemented, + "updating primary key columns is not implemented" + )); + } + let AssignedValue::Value(expr) = assignment.get_set_value() else { + return Err(m_error!( + ER::NotImplemented, + "expression updates are not implemented" + )); + }; + let binary = + ValueCodec::binary_from_expr(expr, field.type_desc(), params, &mut param_index)?; + value.push((attr, binary)); + } + let key = self.bind_exact_key_from( + &table_desc, + stmt.get_where_predicate(), + params, + &mut param_index, + )?; + + Ok(BoundUpdate { + table_id: table_desc.id(), + key, + value, + }) + } + + async fn bind_delete(&self, stmt: StmtDelete, params: &dyn SQLParams) -> RS { + let table_desc = self.get_table_by_name(stmt.get_table_reference()).await?; + let key = self.bind_exact_key(&table_desc, stmt.get_where_predicate(), params)?; + Ok(BoundDelete { + table_id: table_desc.id(), + key, + }) + } + + fn bind_predicate( + &self, + table_desc: &TableDesc, + predicates: &[ExprCompare], + params: &dyn SQLParams, + ) -> RS { + let mut param_index = 0; + self.bind_predicate_from(table_desc, predicates, params, &mut param_index) + } + + fn bind_predicate_from( + &self, + table_desc: &TableDesc, + predicates: &[ExprCompare], + params: &dyn SQLParams, + param_index: &mut usize, + ) -> RS { + if predicates.is_empty() { + return Ok(BoundPredicate::True); + } + + let mut eq_items = vec![]; + let mut start: Bound)>> = Bound::Unbounded; + let mut end: Bound)>> = Bound::Unbounded; + + for predicate in predicates { + let (field_name, expr_value, op) = + self.field_literal_compare(predicate).ok_or_else(|| { + m_error!( + ER::NotImplemented, + "only column/literal predicates are supported" + ) + })?; + let attr = self.attr_index_by_name(table_desc, field_name)?; + let field = table_desc.get_attr(attr); + if !field.is_primary() { + return Err(m_error!( + ER::NotImplemented, + "non-key predicates are not implemented" + )); + } + let binary = + ValueCodec::binary_from_expr(&expr_value, field.type_desc(), params, param_index)?; + match op { + ValueCompare::EQ => eq_items.push((attr, binary)), + ValueCompare::GE => start = Bound::Included(vec![(attr, binary)]), + ValueCompare::GT => start = Bound::Excluded(vec![(attr, binary)]), + ValueCompare::LE => end = Bound::Included(vec![(attr, binary)]), + ValueCompare::LT => end = Bound::Excluded(vec![(attr, binary)]), + ValueCompare::NE => { + return Err(m_error!( + ER::NotImplemented, + "not-equal predicates are not implemented" + )) + } + } + } + + if !eq_items.is_empty() + && matches!(start, Bound::Unbounded) + && matches!(end, Bound::Unbounded) + { + return Ok(BoundPredicate::KeyEq { key: eq_items }); + } + + if !eq_items.is_empty() { + return Err(m_error!( + ER::NotImplemented, + "mixed equality and range predicates are not implemented" + )); + } + + Ok(BoundPredicate::KeyRange { start, end }) + } + + fn bind_exact_key( + &self, + table_desc: &TableDesc, + predicates: &[ExprCompare], + params: &dyn SQLParams, + ) -> RS)>> { + let mut param_index = 0; + self.bind_exact_key_from(table_desc, predicates, params, &mut param_index) + } + + fn bind_exact_key_from( + &self, + table_desc: &TableDesc, + predicates: &[ExprCompare], + params: &dyn SQLParams, + param_index: &mut usize, + ) -> RS)>> { + match self.bind_predicate_from(table_desc, predicates, params, param_index)? { + BoundPredicate::KeyEq { mut key } => { + if key.len() != table_desc.key_info().len() { + return Err(m_error!( + ER::NotImplemented, + "update/delete require a complete primary key predicate" + )); + } + key.sort_by_key(|(attr, _)| *attr); + for (index, (attr, _)) in key.iter().enumerate() { + if *attr != index { + return Err(m_error!( + ER::NotImplemented, + "update/delete require one equality predicate for each primary key column" + )); + } + } + Ok(key) + } + BoundPredicate::True => Err(m_error!( + ER::NotImplemented, + "full-table update/delete is not implemented" + )), + BoundPredicate::KeyRange { .. } => Err(m_error!( + ER::NotImplemented, + "range update/delete is not implemented" + )), + } + } + + fn field_literal_compare<'a>( + &self, + predicate: &'a ExprCompare, + ) -> Option<(&'a String, ExprValue, ValueCompare)> { + match (predicate.left(), predicate.right()) { + (ExprItem::ItemName(name), ExprItem::ItemValue(value)) => { + Some((name.name(), value.clone(), *predicate.op())) + } + (ExprItem::ItemValue(value), ExprItem::ItemName(name)) => Some(( + name.name(), + value.clone(), + Self::reverse_compare(*predicate.op()), + )), + _ => None, + } + } + + fn reverse_compare(op: ValueCompare) -> ValueCompare { + match op { + ValueCompare::EQ => ValueCompare::EQ, + ValueCompare::LE => ValueCompare::GT, + ValueCompare::LT => ValueCompare::GE, + ValueCompare::GE => ValueCompare::LT, + ValueCompare::GT => ValueCompare::LE, + ValueCompare::NE => ValueCompare::NE, + } + } + + fn schema_column_from_ast(column: &sql_parser::ast::column_def::ColumnDef) -> RS { + let ty = column.data_type().clone().uni_to()?; + let mut schema_column = SchemaColumn::new( + column.column_name().clone(), + ty.dat_type_id(), + DTInfo::from_opt_object(&ty), + ); + schema_column.set_primary(column.is_primary_key()); + schema_column.set_index(column.column_index()); + Ok(schema_column) + } + + fn select_attrs( + &self, + table_desc: &TableDesc, + terms: &[sql_parser::ast::select_term::SelectTerm], + ) -> RS> { + terms + .iter() + .map(|term| self.attr_index_by_name(table_desc, term.field().name())) + .collect() + } + + fn attr_index_by_name(&self, table_desc: &TableDesc, name: &str) -> RS { + let total = table_desc.key_info().len() + table_desc.value_info().len(); + (0..total) + .find(|attr| table_desc.get_attr(*attr).name() == name) + .ok_or_else(|| m_error!(ER::NoSuchElement, format!("cannot find column {}", name))) + } + + async fn get_table_by_name(&self, name: &String) -> RS> { + self.meta_mgr + .get_table_by_name(name) + .await? + .ok_or_else(|| m_error!(ER::NoSuchElement, format!("cannot find table {}", name))) + } +} diff --git a/mudu_kernel/src/sql/bound_stmt.rs b/mudu_kernel/src/sql/bound_stmt.rs new file mode 100644 index 0000000..a75d2d2 --- /dev/null +++ b/mudu_kernel/src/sql/bound_stmt.rs @@ -0,0 +1,92 @@ +use crate::contract::schema_table::SchemaTable; +use mudu::common::id::{AttrIndex, OID}; +use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; +use std::ops::Bound; + +#[derive(Clone, Debug)] +pub enum BoundStmt { + Query(BoundQuery), + Command(BoundCommand), +} + +#[derive(Clone, Debug)] +pub enum BoundQuery { + Select(BoundSelect), +} + +#[derive(Clone, Debug)] +pub enum BoundCommand { + CreateTable(BoundCreateTable), + DropTable(BoundDropTable), + Insert(BoundInsert), + Update(BoundUpdate), + Delete(BoundDelete), + CopyFrom(BoundCopyFrom), + CopyTo(BoundCopyTo), +} + +#[derive(Clone, Debug)] +pub struct BoundSelect { + pub table_id: OID, + pub select_attrs: Vec, + pub tuple_desc: TupleFieldDesc, + pub predicate: BoundPredicate, +} + +#[derive(Clone, Debug)] +pub struct BoundCreateTable { + pub schema: SchemaTable, +} + +#[derive(Clone, Debug)] +pub struct BoundDropTable { + pub table_id: OID, +} + +#[derive(Clone, Debug)] +pub struct BoundInsert { + pub table_id: OID, + pub key: Vec<(AttrIndex, Vec)>, + pub value: Vec<(AttrIndex, Vec)>, +} + +#[derive(Clone, Debug)] +pub struct BoundUpdate { + pub table_id: OID, + pub key: Vec<(AttrIndex, Vec)>, + pub value: Vec<(AttrIndex, Vec)>, +} + +#[derive(Clone, Debug)] +pub struct BoundDelete { + pub table_id: OID, + pub key: Vec<(AttrIndex, Vec)>, +} + +#[derive(Clone, Debug)] +pub struct BoundCopyFrom { + pub file_path: String, + pub table_id: OID, + pub key_index: Vec, + pub value_index: Vec, +} + +#[derive(Clone, Debug)] +pub struct BoundCopyTo { + pub file_path: String, + pub table_id: OID, + pub key_indexing: Vec, + pub value_indexing: Vec, +} + +#[derive(Clone, Debug)] +pub enum BoundPredicate { + True, + KeyEq { + key: Vec<(AttrIndex, Vec)>, + }, + KeyRange { + start: Bound)>>, + end: Bound)>>, + }, +} diff --git a/mudu_kernel/src/sql/build_where_predicate.rs b/mudu_kernel/src/sql/build_where_predicate.rs index 10fb029..690e2b4 100644 --- a/mudu_kernel/src/sql/build_where_predicate.rs +++ b/mudu_kernel/src/sql/build_where_predicate.rs @@ -2,25 +2,36 @@ use crate::contract::table_desc::TableDesc; use mudu::common::buf::Buf as Datum; use mudu::common::id::OID; use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; use sql_parser::ast::expr_compare::ExprCompare; use sql_parser::ast::expr_literal::ExprLiteral; use sql_parser::ast::expr_name::ExprName; use sql_parser::ast::expr_operator::ValueCompare; fn convert_expr_compare_equal( - expr: &ExprName, - expr_literal: &ExprLiteral, - desc: &TableDesc, + _expr: &ExprName, + _expr_literal: &ExprLiteral, + _desc: &TableDesc, ) -> RS<(OID, Datum)> { - todo!() + Err(m_error!( + EC::NotImplemented, + "equality predicate conversion is not implemented" + )) } -fn convert_expr_compare(expr: &ExprCompare, desc: &TableDesc) -> RS<(OID, Datum)> { +fn convert_expr_compare(expr: &ExprCompare, _desc: &TableDesc) -> RS<(OID, Datum)> { match expr.op() { ValueCompare::EQ => match (expr.left(), expr.right()) { - _ => Err(todo!()), + _ => Err(m_error!( + EC::NotImplemented, + "only simple equality predicates are supported" + )), }, - _ => Err(todo!()), + _ => Err(m_error!( + EC::NotImplemented, + "non-equality predicates are not implemented" + )), } } diff --git a/mudu_kernel/src/sql/copy_layout.rs b/mudu_kernel/src/sql/copy_layout.rs new file mode 100644 index 0000000..567d2de --- /dev/null +++ b/mudu_kernel/src/sql/copy_layout.rs @@ -0,0 +1,77 @@ +use crate::contract::table_desc::TableDesc; +use mudu::common::result::RS; +use mudu::error::ec::EC as ER; +use mudu::m_error; +use std::collections::HashMap; + +pub(crate) struct CopyLayout { + key_index: Vec, + value_index: Vec, +} + +impl CopyLayout { + pub(crate) fn new(table_desc: &TableDesc, columns: &[String]) -> RS { + let columns = if columns.is_empty() { + Self::ordered_columns(table_desc) + } else if columns.len() == table_desc.oid2col().len() { + columns.to_vec() + } else { + return Err(m_error!( + ER::IOErr, + format!( + "the columns of table {} is not equal to the size specified {}", + table_desc.name(), + columns.len() + ) + )); + }; + + let mut name_to_position = HashMap::new(); + for (index, name) in columns.iter().enumerate() { + name_to_position.insert(name.clone(), index); + } + + let mut key_index = vec![]; + let mut value_index = vec![]; + for (target, oids) in [ + (&mut key_index, table_desc.key_field_oid()), + (&mut value_index, table_desc.value_field_oid()), + ] { + for oid in oids { + let info = table_desc.oid2col().get(oid).ok_or_else(|| { + m_error!(ER::NoSuchElement, format!("cannot find column oid {}", oid)) + })?; + let position = name_to_position.get(info.name()).ok_or_else(|| { + m_error!( + ER::NoSuchElement, + format!("cannot find column name {}", info.name()) + ) + })?; + target.push(*position); + } + } + + Ok(Self { + key_index, + value_index, + }) + } + + pub(crate) fn key_index(&self) -> &[usize] { + &self.key_index + } + + pub(crate) fn value_index(&self) -> &[usize] { + &self.value_index + } + + fn ordered_columns(table_desc: &TableDesc) -> Vec { + let mut columns: Vec<_> = table_desc + .oid2col() + .values() + .map(|field| (field.column_index(), field.name().clone())) + .collect(); + columns.sort_by(|left, right| left.0.cmp(&right.0)); + columns.into_iter().map(|(_, name)| name).collect() + } +} diff --git a/mudu_kernel/src/sql/current_tx.rs b/mudu_kernel/src/sql/current_tx.rs index a7a7040..1f3ec81 100644 --- a/mudu_kernel/src/sql/current_tx.rs +++ b/mudu_kernel/src/sql/current_tx.rs @@ -1,13 +1,13 @@ use crate::contract::ssn_ctx::SsnCtx; use mudu::common::result::RS; -use mudu::common::xid::XID; +use mudu::common::xid::{new_xid, XID}; pub async fn get_tx(ctx: &dyn SsnCtx) -> RS { let opt_tx = ctx.current_tx(); let xid = match opt_tx { Some(id) => id, None => { - let id = todo!(); + let id = new_xid(); ctx.begin_tx(id)?; id } diff --git a/mudu_kernel/src/sql/describer.rs b/mudu_kernel/src/sql/describer.rs new file mode 100644 index 0000000..6877b03 --- /dev/null +++ b/mudu_kernel/src/sql/describer.rs @@ -0,0 +1,63 @@ +use crate::contract::meta_mgr::MetaMgr; +use crate::contract::table_desc::TableDesc; +use crate::executor::project_tuple_desc; +use mudu::common::result::RS; +use mudu::error::ec::EC as ER; +use mudu::m_error; +use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; +use sql_parser::ast::stmt_type::StmtType; +use std::sync::Arc; + +pub struct Describer { + meta_mgr: Arc, +} + +impl Describer { + pub fn new(meta_mgr: Arc) -> Self { + Self { meta_mgr } + } + + pub async fn describe(&self, stmt: StmtType) -> RS { + match stmt { + StmtType::Select(stmt) => self.describe_select(stmt).await, + StmtType::Command(_) => Ok(TupleFieldDesc::new(Vec::new())), + } + } + + async fn describe_select( + &self, + stmt: sql_parser::ast::stmt_select::StmtSelect, + ) -> RS { + let table_desc = self.get_table_by_name(stmt.get_table_reference()).await?; + let select_attrs = self.select_attrs(&table_desc, stmt.get_select_term_list())?; + Ok(project_tuple_desc( + &table_desc, + &crate::x_engine::api::VecSelTerm::new(select_attrs), + )) + } + + fn select_attrs( + &self, + table_desc: &TableDesc, + terms: &[sql_parser::ast::select_term::SelectTerm], + ) -> RS> { + terms + .iter() + .map(|term| self.attr_index_by_name(table_desc, term.field().name())) + .collect() + } + + fn attr_index_by_name(&self, table_desc: &TableDesc, name: &str) -> RS { + let total = table_desc.key_info().len() + table_desc.value_info().len(); + (0..total) + .find(|attr| table_desc.get_attr(*attr).name() == name) + .ok_or_else(|| m_error!(ER::NoSuchElement, format!("cannot find column {}", name))) + } + + async fn get_table_by_name(&self, name: &String) -> RS> { + self.meta_mgr + .get_table_by_name(name) + .await? + .ok_or_else(|| m_error!(ER::NoSuchElement, format!("cannot find table {}", name))) + } +} diff --git a/mudu_kernel/src/sql/mod.rs b/mudu_kernel/src/sql/mod.rs index 8524581..96972e8 100644 --- a/mudu_kernel/src/sql/mod.rs +++ b/mudu_kernel/src/sql/mod.rs @@ -1,7 +1,16 @@ +#![allow(dead_code)] + mod cmp_pred; +mod copy_layout; +mod value_codec; pub mod stmt_cmd_run; +pub mod binder; +pub mod bound_stmt; +pub mod describer; +pub mod plan_ctx; +pub mod planner; pub mod proj_list; pub mod stmt_cmd; diff --git a/mudu_kernel/src/sql/plan_ctx.rs b/mudu_kernel/src/sql/plan_ctx.rs new file mode 100644 index 0000000..84e7cb1 --- /dev/null +++ b/mudu_kernel/src/sql/plan_ctx.rs @@ -0,0 +1,11 @@ +use crate::contract::meta_mgr::MetaMgr; +use crate::x_engine::api::XContract; +use mudu::common::xid::XID; +use std::sync::Arc; + +#[derive(Clone)] +pub struct PlanCtx { + pub xid: XID, + pub meta_mgr: Arc, + pub x_contract: Arc, +} diff --git a/mudu_kernel/src/sql/planner.rs b/mudu_kernel/src/sql/planner.rs new file mode 100644 index 0000000..71e742a --- /dev/null +++ b/mudu_kernel/src/sql/planner.rs @@ -0,0 +1,189 @@ +use crate::command::create_table::CreateTable; +use crate::command::delete_key_value::DeleteKeyValue; +use crate::command::drop_table::DropTable; +use crate::command::insert_key_value::InsertKeyValue; +use crate::command::load_from_file::LoadFromFile; +use crate::command::save_to_file::SaveToFile; +use crate::command::update_key_value::UpdateKeyValue; +use crate::contract::cmd_exec::CmdExec; +use crate::contract::query_exec::QueryExec; +use crate::sql::bound_stmt::{ + BoundCommand, BoundCopyFrom, BoundCopyTo, BoundCreateTable, BoundDelete, BoundDropTable, + BoundInsert, BoundPredicate, BoundQuery, BoundSelect, BoundUpdate, +}; +use crate::sql::plan_ctx::PlanCtx; +use crate::x_engine::api::{OptRead, Predicate, RangeData, VecDatum, VecSelTerm}; +use crate::x_engine::x_param::{ + PAccessKey, PAccessRange, PCreateTable, PDeleteKeyValue, PDropTable, PInsertKeyValue, + PUpdateKeyValue, +}; +use mudu::common::result::RS; +use std::sync::Arc; + +pub struct Planner { + ctx: PlanCtx, +} + +impl Planner { + pub fn new(ctx: PlanCtx) -> Self { + Self { ctx } + } + + pub async fn plan_query(&self, query: BoundQuery) -> RS> { + match query { + BoundQuery::Select(select) => self.plan_select(select).await, + } + } + + pub async fn plan_command(&self, command: BoundCommand) -> RS> { + match command { + BoundCommand::CreateTable(stmt) => Ok(Arc::new(self.plan_create_table(stmt))), + BoundCommand::DropTable(stmt) => Ok(Arc::new(self.plan_drop_table(stmt))), + BoundCommand::Insert(stmt) => Ok(Arc::new(self.plan_insert(stmt))), + BoundCommand::Update(stmt) => Ok(Arc::new(self.plan_update(stmt))), + BoundCommand::Delete(stmt) => Ok(Arc::new(self.plan_delete(stmt))), + BoundCommand::CopyFrom(stmt) => Ok(Arc::new(self.plan_copy_from(stmt))), + BoundCommand::CopyTo(stmt) => Ok(Arc::new(self.plan_copy_to(stmt))), + } + } + + async fn plan_select(&self, stmt: BoundSelect) -> RS> { + let select = VecSelTerm::new(stmt.select_attrs.clone()); + match stmt.predicate { + BoundPredicate::True => { + let exec = crate::executor::index_access_range::IndexAccessRange::new( + PAccessRange { + xid: self.ctx.xid, + table_id: stmt.table_id, + pred_key: RangeData::new( + std::ops::Bound::Unbounded, + std::ops::Bound::Unbounded, + ), + pred_non_key: Predicate::CNF(Vec::new()), + select, + opt_read: OptRead::default(), + }, + self.ctx.x_contract.clone(), + self.ctx.meta_mgr.clone(), + ) + .await?; + Ok(Arc::new(exec)) + } + BoundPredicate::KeyEq { key } => { + let exec = crate::executor::index_access_key::IndexAccessKey::new( + PAccessKey { + xid: self.ctx.xid, + table_id: stmt.table_id, + pred_key: VecDatum::new(key), + select, + opt_read: OptRead::default(), + }, + self.ctx.x_contract.clone(), + self.ctx.meta_mgr.clone(), + ) + .await?; + Ok(Arc::new(exec)) + } + BoundPredicate::KeyRange { start, end } => { + let exec = crate::executor::index_access_range::IndexAccessRange::new( + PAccessRange { + xid: self.ctx.xid, + table_id: stmt.table_id, + pred_key: RangeData::new(start, end), + pred_non_key: Predicate::CNF(Vec::new()), + select, + opt_read: OptRead::default(), + }, + self.ctx.x_contract.clone(), + self.ctx.meta_mgr.clone(), + ) + .await?; + Ok(Arc::new(exec)) + } + } + } + + fn plan_create_table(&self, stmt: BoundCreateTable) -> CreateTable { + CreateTable::new( + PCreateTable { + xid: self.ctx.xid, + schema: stmt.schema, + }, + self.ctx.x_contract.clone(), + self.ctx.meta_mgr.clone(), + ) + } + + fn plan_drop_table(&self, stmt: BoundDropTable) -> DropTable { + DropTable::new( + PDropTable { + xid: self.ctx.xid, + oid: Some(stmt.table_id), + }, + self.ctx.x_contract.clone(), + self.ctx.meta_mgr.clone(), + ) + } + + fn plan_insert(&self, stmt: BoundInsert) -> InsertKeyValue { + InsertKeyValue::new( + PInsertKeyValue { + xid: self.ctx.xid, + table_id: stmt.table_id, + key: VecDatum::new(stmt.key), + value: VecDatum::new(stmt.value), + }, + self.ctx.x_contract.clone(), + self.ctx.meta_mgr.clone(), + ) + } + + fn plan_update(&self, stmt: BoundUpdate) -> UpdateKeyValue { + UpdateKeyValue::new( + PUpdateKeyValue { + xid: self.ctx.xid, + table_id: stmt.table_id, + key: VecDatum::new(stmt.key), + value: VecDatum::new(stmt.value), + }, + self.ctx.x_contract.clone(), + self.ctx.meta_mgr.clone(), + ) + } + + fn plan_delete(&self, stmt: BoundDelete) -> DeleteKeyValue { + DeleteKeyValue::new( + PDeleteKeyValue { + xid: self.ctx.xid, + table_id: stmt.table_id, + key: VecDatum::new(stmt.key), + }, + self.ctx.x_contract.clone(), + self.ctx.meta_mgr.clone(), + ) + } + + fn plan_copy_from(&self, stmt: BoundCopyFrom) -> LoadFromFile { + LoadFromFile::new( + stmt.file_path, + self.ctx.xid, + stmt.table_id, + stmt.key_index, + stmt.value_index, + self.ctx.x_contract.clone(), + self.ctx.meta_mgr.clone(), + ) + } + + fn plan_copy_to(&self, stmt: BoundCopyTo) -> SaveToFile { + SaveToFile::new( + stmt.file_path, + self.ctx.xid, + stmt.table_id, + stmt.key_indexing, + stmt.value_indexing, + self.ctx.x_contract.clone(), + self.ctx.meta_mgr.clone(), + ) + } +} diff --git a/mudu_kernel/src/sql/stmt_copy_from.rs b/mudu_kernel/src/sql/stmt_copy_from.rs index 8fa5016..62c6af1 100644 --- a/mudu_kernel/src/sql/stmt_copy_from.rs +++ b/mudu_kernel/src/sql/stmt_copy_from.rs @@ -49,7 +49,7 @@ impl StmtCopyFrom { } } - fn build_copy_to_cmd(p: LoadParam, thd_ctx: &dyn SsnCtx) -> LoadFromFile { + fn build_copy_to_cmd(_p: LoadParam, _thd_ctx: &dyn SsnCtx) -> LoadFromFile { todo!() /* LoadFromFile::new( @@ -75,7 +75,7 @@ impl StmtCopyFrom { Ok(Arc::new(cmd)) } - async fn build_param(&self, ctx: &dyn SsnCtx) -> RS<()> { + async fn build_param(&self, _ctx: &dyn SsnCtx) -> RS<()> { todo!() /* let opt_table = ctx diff --git a/mudu_kernel/src/sql/stmt_query_run.rs b/mudu_kernel/src/sql/stmt_query_run.rs index cb785e6..3926bf1 100644 --- a/mudu_kernel/src/sql/stmt_query_run.rs +++ b/mudu_kernel/src/sql/stmt_query_run.rs @@ -25,13 +25,12 @@ pub async fn run_query_stmt( Arc>, impl Stream>, )> { - let xid = get_tx(ctx).await?; + let _xid = get_tx(ctx).await?; let r = run_query_stmt_gut(stmt, ctx).await; match r { Ok(r) => Ok(r), Err(e) => { error!("run query error: {}", e); - todo!(); //ctx.thd_ctx().abort_tx(xid).await?; ctx.end_tx()?; Err(e) diff --git a/mudu_kernel/src/sql/value_codec.rs b/mudu_kernel/src/sql/value_codec.rs new file mode 100644 index 0000000..8de8324 --- /dev/null +++ b/mudu_kernel/src/sql/value_codec.rs @@ -0,0 +1,42 @@ +use mudu::common::buf::Buf; +use mudu::common::result::RS; +use mudu::error::ec::EC as ER; +use mudu::m_error; +use mudu_contract::database::sql_params::SQLParams; +use mudu_type::datum::DatumDyn; +use mudu_type::dt_fn_param::DatType; +use sql_parser::ast::expr_item::ExprValue; +use sql_parser::ast::expr_literal::ExprLiteral; + +pub(crate) struct ValueCodec; + +impl ValueCodec { + pub(crate) fn binary_from_expr( + expr: &ExprValue, + dat_type: &DatType, + params: &dyn SQLParams, + param_index: &mut usize, + ) -> RS { + match expr { + ExprValue::ValueLiteral(literal) => Self::binary_from_literal(literal, dat_type), + ExprValue::ValuePlaceholder => { + let index = *param_index as u64; + let datum = params.get_idx(index).ok_or_else(|| { + m_error!(ER::IndexOutOfRange, format!("missing parameter {}", index)) + })?; + *param_index += 1; + datum.to_binary(dat_type).map(|binary| binary.into()) + } + } + } + + pub(crate) fn binary_from_literal(literal: &ExprLiteral, dat_type: &DatType) -> RS { + match literal { + ExprLiteral::DatumLiteral(typed) => typed + .dat_internal() + .to_binary(dat_type) + .map(|binary| binary.into()) + .map_err(|e| m_error!(ER::TypeBaseErr, "literal type mismatch", e)), + } + } +} diff --git a/mudu_kernel/src/storage/bufer_manager.rs b/mudu_kernel/src/storage/bufer_manager.rs deleted file mode 100644 index 8c611e1..0000000 --- a/mudu_kernel/src/storage/bufer_manager.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::storage::disk_io::DiskIO; -use crate::storage::frame::Frame; -use crate::storage::page_block::PageBlock; -use crate::storage::page_index::PageIndex; -use crate::storage::storage_cfg::StorageCfg; -use mcslock::raw::Mutex; -use mcslock::relax::Spin; -use mudu::common::result::RS; -use rand::rng as thread_rng; -use rand::seq::IteratorRandom; -use scc::HashMap; -use std::sync::Arc; - -pub struct BufferManager { - inner: Arc, -} -pub struct BufferManagerInner { - page_size: u64, - page_cache: HashMap, - frame_array: Vec, - free_list: Mutex, - disk_io: DiskIO, -} - -impl BufferManager { - pub fn new(cfg: &StorageCfg) -> RS { - Ok(Self { - inner: Arc::new(BufferManagerInner::new(cfg)?), - }) - } - - pub async fn get_page(&self, page_index: &PageIndex) -> RS { - self.inner.get_page(page_index).await - } -} - -struct FreeList { - vec: Vec, -} - -impl FreeList { - fn new(vec: Vec) -> FreeList { - Self { vec } - } - - fn get_a_free_frame_index(&mut self) -> Option { - let mut rng = thread_rng(); - if let Some(index) = (0..self.vec.len()).choose(&mut rng) { - // Use swap_remove(index) to remove the element efficiently (O(1)) - // The last element is swapped into the removed element's place - let removed_element = self.vec.swap_remove(index); - Some(removed_element) - } else { - None - } - } - - fn add_a_free_frame_index(&mut self, fram_index: u64) { - let result = self.vec.binary_search(&fram_index); - match result { - Ok(_) => {} - Err(index) => { - self.vec.insert(index, fram_index); - } - } - } - - fn remove_free_frame_index(&mut self, fram_index: u64) { - let result = self.vec.binary_search(&fram_index); - match result { - Ok(index) => { - self.vec.remove(index); - } - Err(_) => {} - } - } -} - -impl BufferManagerInner { - fn new(cfg: &StorageCfg) -> RS { - let mut buffer = Vec::with_capacity(cfg.buffer_num_pages as usize); - let mut free_list = Vec::with_capacity(cfg.buffer_num_pages as usize); - for frame_index in 0..cfg.buffer_num_pages { - let frame = Frame::new_empty(frame_index, cfg.page_size); - free_list.push(frame_index); - } - - Ok(Self { - page_size: cfg.page_size, - page_cache: Default::default(), - frame_array: buffer, - free_list: Mutex::new(FreeList::new(free_list)), - disk_io: DiskIO::new(cfg.path.clone(), cfg.page_size)?, - }) - } - - fn get_a_free_frame(&self) -> Option { - let mut rng = thread_rng(); - let opt = self.free_list.lock_then(|fl| fl.get_a_free_frame_index()); - match opt { - Some(index) => self.frame_array.get(index as usize).cloned(), - None => None, - } - } - - async fn locate_a_frame(&self) -> RS { - let frame = self.get_a_free_frame().unwrap(); - let page_index = frame.page_index().clone(); - // remove from the cache if it exists in cache - self.page_cache.remove_sync(&page_index); - let mut page_block = PageBlock::new_empty(self.page_size); - frame.copy_page(&mut page_block)?; - let is_dirty = frame.swap_out().await?; - if is_dirty { - self.disk_io.write_page(page_index, page_block).await?; - frame.reset(); - } - Ok(frame) - } - - pub async fn get_page(&self, page_index: &PageIndex) -> RS { - let opt = self.page_cache.get_sync(page_index); - match opt { - Some(frame) => Ok(frame.clone()), - None => { - let frame = self.locate_a_frame().await?; - let mut page_block = PageBlock::new_empty(self.page_size); - self.disk_io - .read_page(page_index.clone(), &mut page_block) - .await?; - frame.swap_page(&mut page_block); - Ok(frame) - } - } - } -} diff --git a/mudu_kernel/src/storage/constant.rs b/mudu_kernel/src/storage/constant.rs deleted file mode 100644 index 1eb64db..0000000 --- a/mudu_kernel/src/storage/constant.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::ops::Range; - -pub const PAGE_TAIL_SIZE: usize = 64usize; -pub const PAGE_HEADER_SIZE: usize = 64usize; - -pub const DATA_HEADER_SIZE: usize = 64usize; -pub const EXTEND_HEADER_SIZE: usize = 64usize; - -pub fn offset_page_id() -> usize { - 0 -} - -pub fn size_page_id() -> usize { - size_of::() -} - -pub fn offset_lsn() -> usize { - size_of::() -} - -pub fn size_lsn() -> usize { - size_of::() -} - -pub fn page_offset_range_page_id() -> Range { - offset_page_id()..offset_page_id() + size_page_id() -} - -pub fn page_offset_range_lsn() -> Range { - offset_lsn()..offset_lsn() + size_lsn() -} - -fn payload_offset_extent_meta_range() -> Range { - 0..PAGE_HEADER_SIZE + EXTEND_HEADER_SIZE -} - -struct OffsetPage {} - -impl OffsetPage { - pub fn total() -> Range { - 0..PAGE_HEADER_SIZE - } - - pub fn lsn() -> Range { - page_offset_range_lsn() - } - - pub fn page_id() -> Range { - page_offset_range_page_id() - } -} -pub struct LayoutExtentHeader {} - -pub struct LayoutDataHeader {} - -impl LayoutExtentHeader { - pub fn size() -> usize { - EXTEND_HEADER_SIZE - } - - pub fn range_of_page() -> Range { - PAGE_HEADER_SIZE..PAGE_HEADER_SIZE + EXTEND_HEADER_SIZE - } - - pub fn range() -> Range { - payload_offset_extent_meta_range() - } - - pub fn range_extent_id() -> Range { - 0..size_of::() - } - - pub fn range_start_page() -> Range { - let start = Self::range_extent_id().end; - start..start + size_of::() - } - - pub fn range_page_count() -> Range { - let start = Self::range_start_page().end; - start..start + size_of::() - } - - pub fn offset_bitmap() -> usize { - EXTEND_HEADER_SIZE - } -} - -impl LayoutDataHeader { - fn range_of_page() -> Range { - PAGE_HEADER_SIZE..PAGE_HEADER_SIZE + DATA_HEADER_SIZE - } - - fn range_free_begin() -> Range { - 0..size_of::() - } - - fn range_free_end_page() -> Range { - let start = Self::range_free_begin().end; - start..start + size_of::() - } -} diff --git a/mudu_kernel/src/storage/data_file.rs b/mudu_kernel/src/storage/data_file.rs deleted file mode 100644 index c50b1ac..0000000 --- a/mudu_kernel/src/storage/data_file.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::storage::disk_io::DiskIO; -use crate::storage::extent::Extent; -use crate::storage::log_entry::LogEntry; -use crate::storage::page_block::{PageBlock, PageUpdate}; -use crate::storage::page_index::PageIndex; -use crate::storage::storage_cfg::StorageCfg; -use crate::storage::storage_context::StorageContext; -use mcslock::raw::Mutex; -use mcslock::relax::Spin; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use roaring::RoaringBitmap; -use scc::HashMap; -use std::path::PathBuf; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::Arc; - -static NEXT_FILE_ID: AtomicU64 = AtomicU64::new(1); - -#[derive(Clone)] -pub struct DataFile { - inner: Arc, -} - -struct DataFileInner { - extent_pages: u64, - page_size: u64, - file_id: u64, - path: String, - disk_io: DiskIO, - total_allocated: AtomicU64, - roaring: Mutex, - allocated_extents: HashMap, -} - -impl DataFile { - pub fn open(cfg: &StorageCfg) -> RS { - Ok(Self { - inner: Arc::new(DataFileInner::open(cfg)?), - }) - } - - pub fn current_file_id() -> u64 { - _current_file_id() - } - - pub fn next_file_id() -> u64 { - _next_file_id() - } - - pub fn file_create_extent(&self, table_space: u64, ctx: &StorageContext) -> RS { - self.inner.create_extent(table_space, ctx) - } - - pub async fn write_block(&self, block: PageBlock) -> RS<()> { - self.inner.write_block(block).await?; - Ok(()) - } -} - -fn _current_file_id() -> u64 { - NEXT_FILE_ID.load(Ordering::SeqCst) -} - -fn _next_file_id() -> u64 { - NEXT_FILE_ID.fetch_add(1, Ordering::SeqCst) -} - -impl DataFileInner { - fn open(cfg: &StorageCfg) -> RS { - let (file_id, path_buf) = loop { - let file_id = _next_file_id(); - let file_path = PathBuf::from(&cfg.path).join(file_id.to_string()); - if !file_path.exists() { - break (file_id, file_path); - } - }; - - Ok(Self::new(cfg, file_id)?) - } - - fn new(cfg: &StorageCfg, file_id: u64) -> RS { - Ok(Self { - extent_pages: cfg.extent_pages, - page_size: cfg.page_size, - file_id, - path: cfg.path.clone(), - disk_io: DiskIO::new(cfg.path.clone(), cfg.page_size)?, - total_allocated: AtomicU64::new(0), - roaring: Default::default(), - allocated_extents: Default::default(), - }) - } - - fn create_extent(&self, table_space_id: u64, context: &StorageContext) -> RS { - let opt = self.get_free_extent(); - if let Some(n) = opt { - let extent_id = self.extent_page_id(n); - let extent = context - .extent_get(&PageIndex::new(self.file_id, extent_id)) - .map_or_else( - || Err(m_error!(EC::StorageErr, "cannot get extent")), - |e| Ok(e), - )?; - Ok(extent) - } else { - let index = self.total_allocated.load(Ordering::SeqCst); - let extent_id = self.extent_page_id(index); - let extent = Extent::new( - self.file_id, - table_space_id, - extent_id, - extent_id + 1, - self.extent_pages, - ); - let mut block = PageBlock::new_empty(self.page_size); - block.set_page_id(extent_id); - block.set_lsn(0); - - let page_id = extent_id; - let mut log_entry = LogEntry::new(self.file_id, extent_id * self.page_size); - block.set_lsn(log_entry.lsn()); - block.reset_checksum(); - log_entry.add_update_delta(PageUpdate::new(page_id, 0, block.block().to_vec())); - context.append_log_entry(log_entry); - - self.total_allocated.fetch_add(1, Ordering::SeqCst); - Ok(extent) - } - } - - fn extent_page_id(&self, index: u64) -> u64 { - index * self.extent_pages - } - - async fn write_block(&self, block: PageBlock) -> RS<()> { - let page_id = block.get_page_id(); - self.disk_io - .write_page(PageIndex::new(self.file_id, page_id), block) - .await - } - - fn get_free_extent(&self) -> Option { - self.roaring.lock_then(|roaring| { - let opt = roaring.iter().next(); - if let Some(n) = opt { - roaring.remove(n); - Some(n as u64) - } else { - None - } - }) - } - - fn push_free_extent(&self, extent: u64) { - self.roaring.lock_then(|roaring| { - roaring.insert(extent as u32); - }) - } -} diff --git a/mudu_kernel/src/storage/data_header.rs b/mudu_kernel/src/storage/data_header.rs deleted file mode 100644 index c88d698..0000000 --- a/mudu_kernel/src/storage/data_header.rs +++ /dev/null @@ -1,4 +0,0 @@ -struct DataHeader { - free_begin: u64, - free_end: u64, -} diff --git a/mudu_kernel/src/storage/disk_io.rs b/mudu_kernel/src/storage/disk_io.rs deleted file mode 100644 index 68565e0..0000000 --- a/mudu_kernel/src/storage/disk_io.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::storage::page_block::PageBlock; -use crate::storage::page_index::PageIndex; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use scc::HashMap; -use std::io::SeekFrom; -use std::path::{Path, PathBuf}; -use std::sync::Arc; -use tokio::fs::File; -use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; -use tokio::sync::Mutex as AsMutex; - -pub struct DiskIO { - path: String, - page_size: u64, - files: HashMap, -} - -#[derive(Clone)] -struct DiskFile { - file: Arc>, -} - -impl DiskIO { - pub fn new(path: String, page_size: u64) -> RS { - Ok(Self { - path, - page_size, - files: HashMap::new(), - }) - } - - pub async fn write_page(&self, page_index: PageIndex, page_block: PageBlock) -> RS<()> { - let file = self.file_get_or_create(page_index.file_id)?; - let offset = page_index.page_id * self.page_size; - file.write_page(offset, page_block.block()).await?; - Ok(()) - } - - pub async fn read_page(&self, page_index: PageIndex, page_block: &mut PageBlock) -> RS<()> { - let file = self.file_get_or_create(page_index.file_id)?; - let offset = page_index.page_id * self.page_size; - file.read_page(offset, page_block.block_mut()).await?; - Ok(()) - } - - fn file_get_or_create(&self, file_id: u64) -> RS { - let opt = self.files.get_sync(&file_id); - let file = match opt { - Some(file) => file.clone(), - None => { - let path_buf = PathBuf::from(&self.path).join(file_id.to_string()); - let file = DiskFile::new(&path_buf)?; - self.files.insert_sync(file_id, file.clone()); - file - } - }; - Ok(file) - } -} - -impl DiskFile { - fn new>(path: P) -> RS { - let file = std::fs::File::open(path.as_ref()) - .map_err(|e| m_error!(EC::IOErr, "open file error", e))?; - let file = File::from_std(file); - Ok(Self { - file: Arc::new(AsMutex::new(file)), - }) - } - - async fn write_page(&self, offset: u64, block: &[u8]) -> RS<()> { - let mut file = self.file.lock().await; - file.seek(SeekFrom::Start(offset)) - .await - .map_err(|e| m_error!(EC::IOErr, "seek file error", e))?; - file.write_all(&block) - .await - .map_err(|e| m_error!(EC::IOErr, "write block error", e))?; - Ok(()) - } - - async fn read_page(&self, offset: u64, block: &mut [u8]) -> RS<()> { - let mut file = self.file.lock().await; - file.seek(SeekFrom::Start(offset)) - .await - .map_err(|e| m_error!(EC::IOErr, "seek file error", e))?; - file.read_exact(block) - .await - .map_err(|e| m_error!(EC::IOErr, "read block error", e))?; - Ok(()) - } -} diff --git a/mudu_kernel/src/storage/extent.rs b/mudu_kernel/src/storage/extent.rs deleted file mode 100644 index c1c38f9..0000000 --- a/mudu_kernel/src/storage/extent.rs +++ /dev/null @@ -1,755 +0,0 @@ -use crate::storage::constant::LayoutExtentHeader; -use mcslock::raw::Mutex; -use mcslock::relax::Spin; -use mudu::common::endian; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::Arc; -use tokio::sync::RwLock; - -struct ExtentMeta { - // file id and table space id would not to be persistent - file_id: u64, - table_space_id: u64, - // extent header info - extent_id: u64, - start_page: u64, - page_count: u64, -} - -struct ExtentData { - meta: ExtentMeta, - payload: Mutex, -} -#[derive(Clone)] -pub struct Extent { - inner: Arc, -} - -impl Extent { - pub fn extent_id(&self) -> u64 { - self.inner.meta.extent_id - } - - pub fn new( - file_id: u64, - table_space_id: u64, - extent_id: u64, - start_page: u64, - page_count: u64, - ) -> Extent { - Self { - inner: Arc::new(ExtentData { - meta: ExtentMeta::new(file_id, table_space_id, extent_id, start_page, page_count), - payload: Mutex::new(ExtentPayload::new(page_count)), - }), - } - } - - // from binary exclude page header and page tailer - pub fn from(file_id: u64, table_space_id: u64, slice: &[u8]) -> RS { - let meta = ExtentMeta::from(file_id, table_space_id, slice)?; - let payload = ExtentPayload::from(slice, &meta)?; - Ok(Self { - inner: Arc::new(ExtentData { - meta, - payload: Mutex::new(payload), - }), - }) - } - - pub fn extent_allocate_page(&self) -> Option { - self.inner - .payload - .lock_then(|payload| payload.allocate_page()) - } - - pub fn file_id(&self) -> u64 { - self.inner.meta.file_id - } -} - -pub struct ExtentPayload { - bitmap: QuadBitmap, - next_write_page: u64, - full_page_count: u64, -} - -impl ExtentMeta { - fn from(file_id: u64, table_space_id: u64, vec: &[u8]) -> RS { - if vec.len() < LayoutExtentHeader::size() { - return Err(m_error!(EC::FatalError, "cannot fit into extent meta data")); - } - let extent_id = endian::read_u64(&vec[LayoutExtentHeader::range_extent_id()]); - let start_page = endian::read_u64(&vec[LayoutExtentHeader::range_start_page()]); - let page_count = endian::read_u64(&vec[LayoutExtentHeader::range_page_count()]); - Ok(Self { - file_id, - table_space_id, - extent_id, - start_page, - page_count, - }) - } - - fn new( - file_id: u64, - tablespace_id: u64, - extent_id: u64, - start_page: u64, - page_count: u64, - ) -> Self { - Self { - file_id: 0, - table_space_id: 0, - extent_id, - start_page, - page_count, - } - } - - fn data_pages(&self) -> u64 { - self.page_count - 1 - } - - fn to_vec(&self) -> Vec { - let n = LayoutExtentHeader::size(); - let mut vec = Vec::with_capacity(n); - vec.resize(n, 0); - endian::write_u64( - &mut vec[LayoutExtentHeader::range_extent_id()], - self.extent_id, - ); - endian::write_u64( - &mut vec[LayoutExtentHeader::range_start_page()], - self.start_page, - ); - endian::write_u64( - &mut vec[LayoutExtentHeader::range_page_count()], - self.page_count, - ); - vec - } -} - -impl ExtentPayload { - fn from(vec: &[u8], meta: &ExtentMeta) -> RS { - let bitmap_off = LayoutExtentHeader::offset_bitmap(); - let bytes = Self::bytes_of_bitmap(meta.data_pages()); - let bitmap = QuadBitmap::from(&vec[bitmap_off..bitmap_off + bytes]); - let (next_write_page, _) = bitmap - .find_first_state012() - .unwrap_or((0, QuadState::State0)); - let full_page_count = meta.data_pages() - bitmap.count_state012() as u64; - let extent = Self { - bitmap, - next_write_page: next_write_page as _, - full_page_count, - }; - Ok(extent) - } - - fn bytes_of_bitmap(page_count: u64) -> usize { - (page_count as usize) / 4 - } - - fn new(pages: u64) -> Self { - let bitmap = QuadBitmap::new(pages as usize); - Self { - bitmap, - next_write_page: 0, - full_page_count: 0, - } - } - - /// Allocate a page from this extent - pub fn allocate_page(&mut self) -> Option { - if let Some(page_offset) = self.find_free() { - self.bitmap.set(page_offset, QuadState::State1); - Some(page_offset as u64) - } else { - None - } - } - - fn find_free(&mut self) -> Option { - self.bitmap.find_first_state0() - } - - /// Free a page in this extent - fn free_page(&mut self, page_number: u64, meta: &ExtentMeta) -> bool { - if page_number >= meta.start_page && page_number < meta.start_page + meta.data_pages() { - let offset = (page_number - meta.start_page) as usize; - self.bitmap.set(offset, QuadState::State0); - true - } else { - false - } - } - - /// Check if extent has free pages - pub fn has_free_pages(&self) -> bool { - self.bitmap.find_first_state0().is_some() - } - - /// Get number of free pages in this extent - fn free_page_count(&self) -> usize { - self.bitmap.count_state0() - } - - fn to_vec(&self, meta: &ExtentMeta) -> Vec { - let size = self.bitmap.data.len(); - let mut vec = Vec::with_capacity(size); - vec.resize(size, 0); - vec[LayoutExtentHeader::offset_bitmap()..].copy_from_slice(&self.bitmap.data); - vec - } - - fn size(meta: &ExtentMeta) -> usize { - LayoutExtentHeader::size() + (meta.data_pages() as usize) / 4 - } -} - -/// Four-state enumeration, each state represented by 2 bits -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum QuadState { - State0 = 0b00, // 00 - State1 = 0b01, // 01 - State2 = 0b10, // 10 - State3 = 0b11, // 11 -} - -/// Bitmap that stores QuadState values efficiently (2 bits per state) -pub struct QuadBitmap { - // Storage vector where each byte holds 4 QuadState values - data: Vec, - // Number of states stored in the bitmap - len: usize, -} - -impl QuadBitmap { - /// Creates a new bitmap with the specified capacity - /// Each byte can store 4 states, so we calculate the required byte capacity - pub fn new(capacity: usize) -> Self { - // Calculate bytes needed: (capacity + 3) / 4 rounds up to nearest byte - let byte_capacity = (capacity + 3) / 4; - Self { - data: vec![0; byte_capacity], - len: capacity, - } - } - - pub fn from(vec: &[u8]) -> Self { - let len = vec.len() * 4; - Self { - data: vec.to_vec(), - len, - } - } - - /// Returns the number of states in the bitmap - pub fn len(&self) -> usize { - self.len - } - - /// Returns true if the bitmap contains no states - pub fn is_empty(&self) -> bool { - self.len == 0 - } - - /// Gets the state at the specified index - /// Returns None if index is out of bounds - pub fn get(&self, index: usize) -> Option { - if index >= self.len { - return None; - } - - let byte_index = index / 4; - let bit_offset = (index % 4) * 2; - let mask = 0b11 << bit_offset; - let value = (self.data[byte_index] & mask) >> bit_offset; - - match value { - 0b00 => Some(QuadState::State0), - 0b01 => Some(QuadState::State1), - 0b10 => Some(QuadState::State2), - 0b11 => Some(QuadState::State3), - _ => unreachable!(), // Should never happen since we only use 2 bits - } - } - - /// Sets the state at the specified index - /// Returns Err if index is out of bounds - pub fn set(&mut self, index: usize, state: QuadState) -> Result<(), &'static str> { - if index >= self.len { - return Err("Index out of bounds"); - } - - let byte_index = index / 4; - let bit_offset = (index % 4) * 2; - let mask = 0b11 << bit_offset; - - // Clear the existing bits and set new value - self.data[byte_index] &= !mask; - self.data[byte_index] |= (state as u8) << bit_offset; - - Ok(()) - } - - /// Returns an iterator over all states in the bitmap - pub fn iter(&self) -> QuadStateBitmapIter<'_> { - QuadStateBitmapIter { - bitmap: self, - index: 0, - } - } - - /// Finds the first occurrence of State0 in the bitmap - /// Returns Some(index) if found, None otherwise - pub fn find_first_state0(&self) -> Option { - self.find_first_state0_from(0) - } - - /// Finds the first occurrence of State0 starting from the specified position - /// Returns Some(index) if found, None otherwise - pub fn find_first_state0_from(&self, start: usize) -> Option { - if start >= self.len { - return None; - } - - // Calculate starting byte and bit offset within that byte - let start_byte = start / 4; - let start_bit_offset = (start % 4) * 2; - - // Iterate through each byte in storage - for byte_index in start_byte..self.data.len() { - let byte = self.data[byte_index]; - - // Check each of the 4 states stored in this byte - for bit_offset in 0..4 { - let global_index = byte_index * 4 + bit_offset; - - // Skip states before our starting position - if byte_index == start_byte && bit_offset < start_bit_offset / 2 { - continue; - } - - // Check if we've exceeded the bitmap length - if global_index >= self.len { - return None; - } - - // Extract the 2-bit state value - let mask = 0b11 << (bit_offset * 2); - let value = (byte & mask) >> (bit_offset * 2); - - // Check if this is State0 - if value == QuadState::State0 as u8 { - return Some(global_index); - } - } - } - - None - } - - /// Finds the first occurrence of State0, State1, or State2 in the bitmap - /// Returns Some((index, state)) if found, None if only State3 exists - pub fn find_first_state012(&self) -> Option<(usize, QuadState)> { - self.find_first_state012_from(0) - } - - /// Finds the first occurrence of State0, State1, or State2 starting from specified position - /// Returns Some((index, state)) if found, None if only State3 exists from start position - pub fn find_first_state012_from(&self, start: usize) -> Option<(usize, QuadState)> { - if start >= self.len { - return None; - } - - let start_byte = start / 4; - let start_bit_offset = (start % 4) * 2; - - for byte_index in start_byte..self.data.len() { - let byte = self.data[byte_index]; - - for bit_offset in 0..4 { - let global_index = byte_index * 4 + bit_offset; - - // Skip states before our starting position - if byte_index == start_byte && bit_offset < start_bit_offset / 2 { - continue; - } - - if global_index >= self.len { - return None; - } - - let mask = 0b11 << (bit_offset * 2); - let value = (byte & mask) >> (bit_offset * 2); - - // Check if this is State0, State1, or State2 (any value except 0b11) - if value != QuadState::State3 as u8 { - let state = match value { - 0b00 => QuadState::State0, - 0b01 => QuadState::State1, - 0b10 => QuadState::State2, - _ => unreachable!(), // We already filtered out State3 - }; - return Some((global_index, state)); - } - } - } - - None - } - - /// Optimized version for finding State0, State1, or State2 - pub fn find_first_state012_fast(&self) -> Option<(usize, QuadState)> { - self.find_first_state012_from_fast(0) - } - - /// Optimized version using bit operations for faster search - pub fn find_first_state012_from_fast(&self, start: usize) -> Option<(usize, QuadState)> { - if start >= self.len { - return None; - } - - let start_byte = start / 4; - let start_offset = start % 4; - - for byte_index in start_byte..self.data.len() { - let mut byte = self.data[byte_index]; - - // Mask out bits before start position for the starting byte - if byte_index == start_byte && start_offset > 0 { - let mask = (1 << (start_offset * 2)) - 1; - byte &= !mask; - } - - // Fast path: if byte is not 0xFF, it contains at least one non-State3 value - if byte != 0xFF { - for bit_offset in 0..4 { - let global_index = byte_index * 4 + bit_offset; - - // Skip states before start position - if byte_index == start_byte && bit_offset < start_offset { - continue; - } - - if global_index >= self.len { - return None; - } - - let mask = 0b11 << (bit_offset * 2); - let value = (byte & mask) >> (bit_offset * 2); - - if value != QuadState::State3 as u8 { - let state = match value { - 0b00 => QuadState::State0, - 0b01 => QuadState::State1, - 0b10 => QuadState::State2, - _ => unreachable!(), - }; - return Some((global_index, state)); - } - } - } - } - - None - } - - /// Finds all positions that contain State0, State1, or State2 - /// Returns a vector of (index, state) tuples - pub fn find_all_state012(&self) -> Vec<(usize, QuadState)> { - let mut results = Vec::new(); - let mut current_pos = 0; - - while let Some((pos, state)) = self.find_first_state012_from(current_pos) { - results.push((pos, state)); - current_pos = pos + 1; - } - - results - } - - /// Finds the first occurrence of a specific state - /// Returns Some(index) if found, None otherwise - pub fn find_first_state(&self, target_state: QuadState) -> Option { - self.find_first_state_from(target_state, 0) - } - - /// Finds the first occurrence of a specific state starting from specified position - pub fn find_first_state_from(&self, target_state: QuadState, start: usize) -> Option { - if start >= self.len { - return None; - } - - let start_byte = start / 4; - let start_bit_offset = (start % 4) * 2; - - for byte_index in start_byte..self.data.len() { - let byte = self.data[byte_index]; - - for bit_offset in 0..4 { - let global_index = byte_index * 4 + bit_offset; - - if byte_index == start_byte && bit_offset < start_bit_offset / 2 { - continue; - } - - if global_index >= self.len { - return None; - } - - let mask = 0b11 << (bit_offset * 2); - let value = (byte & mask) >> (bit_offset * 2); - - if value == target_state as u8 { - return Some(global_index); - } - } - } - - None - } - - /// Returns count of indices which is State0 - pub fn count_state0(&self) -> usize { - let mut n = 0; - let mut current_pos = 0; - while let Some(pos) = self.find_first_state0_from(current_pos) { - n += 1; - current_pos = pos + 1; - } - n - } - - pub fn count_state012(&self) -> usize { - let mut n = 0; - let mut current_pos = 0; - while let Some((pos, _)) = self.find_first_state012_from(current_pos) { - n += 1; - current_pos = pos + 1; - } - n - } - - /// Finds all positions that contain State0 - /// Returns a vector of indices where State0 is found - pub fn find_all_state0(&self) -> Vec { - let mut positions = Vec::new(); - let mut current_pos = 0; - - while let Some(pos) = self.find_first_state0_from(current_pos) { - positions.push(pos); - current_pos = pos + 1; - } - - positions - } - - /// Sets a range of states starting from the specified position - /// Returns Err if the range would exceed bitmap bounds - pub fn set_range(&mut self, start: usize, states: &[QuadState]) -> Result<(), &'static str> { - if start + states.len() > self.len { - return Err("Range out of bounds"); - } - - for (i, &state) in states.iter().enumerate() { - self.set(start + i, state)?; - } - Ok(()) - } - - /// Creates a bitmap from a slice of QuadState values - pub fn from_slice(states: &[QuadState]) -> Self { - let mut bitmap = Self::new(states.len()); - for (i, &state) in states.iter().enumerate() { - bitmap.set(i, state).unwrap(); - } - bitmap - } -} - -/// Iterator for QuadStateBitmap -pub struct QuadStateBitmapIter<'a> { - bitmap: &'a QuadBitmap, - index: usize, -} - -impl<'a> Iterator for QuadStateBitmapIter<'a> { - type Item = QuadState; - - fn next(&mut self) -> Option { - if self.index >= self.bitmap.len { - None - } else { - let state = self.bitmap.get(self.index).unwrap(); - self.index += 1; - Some(state) - } - } -} - -impl std::fmt::Debug for QuadBitmap { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "QuadStateBitmap[")?; - for (i, state) in self.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{:?}", state)?; - } - write!(f, "]") - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // Example usage and demonstration - #[test] - fn test() { - // Test case: Mixed states - let mut bitmap = QuadBitmap::new(10); - - // Set various states - bitmap.set(0, QuadState::State3).unwrap(); // Skip this one - bitmap.set(1, QuadState::State3).unwrap(); // Skip this one - bitmap.set(2, QuadState::State1).unwrap(); // First State0/1/2 - bitmap.set(3, QuadState::State3).unwrap(); // Skip - bitmap.set(4, QuadState::State0).unwrap(); // Second State0/1/2 - bitmap.set(5, QuadState::State2).unwrap(); // Third State0/1/2 - bitmap.set(6, QuadState::State3).unwrap(); // Skip - bitmap.set(7, QuadState::State3).unwrap(); // Skip - - println!("Bitmap: {:?}", bitmap); - - // Find first State0, State1, or State2 - if let Some((pos, state)) = bitmap.find_first_state012() { - println!("First State0/1/2 at position {}: {:?}", pos, state); // Expected: position 2, State1 - } else { - println!("No State0, State1, or State2 found"); - } - - // Find from specific position - if let Some((pos, state)) = bitmap.find_first_state012_from(3) { - println!("First State0/1/2 from position 3: {} - {:?}", pos, state); - // Expected: position 4, State0 - } - - // Find all State0/1/2 positions - let all_state012 = bitmap.find_all_state012(); - println!("All State0/1/2 positions: {:?}", all_state012); // Expected: [(2, State1), (4, State0), (5, State2)] - - // Test case: Only State3 - let mut bitmap2 = QuadBitmap::new(4); - bitmap2.set(0, QuadState::State3).unwrap(); - bitmap2.set(1, QuadState::State3).unwrap(); - bitmap2.set(2, QuadState::State3).unwrap(); - bitmap2.set(3, QuadState::State3).unwrap(); - - match bitmap2.find_first_state012() { - Some((pos, state)) => println!("Found {:?} at {}", state, pos), - None => println!("No State0, State1, or State2 found in bitmap2"), // Expected output - } - - // Test optimized version - if let Some((pos, state)) = bitmap.find_first_state012_fast() { - println!("Fast search found: {} - {:?}", pos, state); - } - } - - #[test] - fn test_find_first_state012() { - let mut bitmap = QuadBitmap::new(6); - bitmap.set(0, QuadState::State3).unwrap(); - bitmap.set(1, QuadState::State3).unwrap(); - bitmap.set(2, QuadState::State1).unwrap(); // First match - bitmap.set(3, QuadState::State0).unwrap(); - bitmap.set(4, QuadState::State3).unwrap(); - bitmap.set(5, QuadState::State2).unwrap(); - - let result = bitmap.find_first_state012(); - assert_eq!(result, Some((2, QuadState::State1))); - } - - #[test] - fn test_find_first_state012_from() { - let mut bitmap = QuadBitmap::new(8); - bitmap.set(1, QuadState::State0).unwrap(); - bitmap.set(3, QuadState::State1).unwrap(); - bitmap.set(5, QuadState::State2).unwrap(); - - assert_eq!( - bitmap.find_first_state012_from(5), - Some((5, QuadState::State2)) - ); - } - - #[test] - fn test_find_first_state012_only_state3() { - let mut bitmap = QuadBitmap::new(3); - bitmap.set(0, QuadState::State3).unwrap(); - bitmap.set(1, QuadState::State3).unwrap(); - bitmap.set(2, QuadState::State3).unwrap(); - - assert_eq!(bitmap.find_first_state012(), None); - assert_eq!(bitmap.find_first_state012_from(1), None); - } - - #[test] - fn test_find_all_state012() { - let bitmap = QuadBitmap::from_slice(&[ - QuadState::State3, - QuadState::State0, - QuadState::State3, - QuadState::State1, - QuadState::State2, - QuadState::State3, - ]); - - let expected = vec![ - (1, QuadState::State0), - (3, QuadState::State1), - (4, QuadState::State2), - ]; - assert_eq!(bitmap.find_all_state012(), expected); - } - - #[test] - fn test_find_first_state() { - let mut bitmap = QuadBitmap::new(5); - bitmap.set(0, QuadState::State0).unwrap(); - bitmap.set(1, QuadState::State1).unwrap(); - bitmap.set(2, QuadState::State0).unwrap(); - bitmap.set(3, QuadState::State2).unwrap(); - bitmap.set(4, QuadState::State0).unwrap(); - - assert_eq!(bitmap.find_first_state(QuadState::State0), Some(0)); - assert_eq!(bitmap.find_first_state(QuadState::State1), Some(1)); - assert_eq!(bitmap.find_first_state(QuadState::State2), Some(3)); - assert_eq!(bitmap.find_first_state(QuadState::State3), None); - assert_eq!(bitmap.find_first_state_from(QuadState::State0, 1), Some(2)); - } - - #[test] - fn test_fast_vs_normal_search() { - let mut bitmap = QuadBitmap::new(10); - bitmap.set(7, QuadState::State1).unwrap(); - - // Both methods should return the same result - assert_eq!( - bitmap.find_first_state012(), - bitmap.find_first_state012_fast() - ); - assert_eq!( - bitmap.find_first_state012_from(3), - bitmap.find_first_state012_from_fast(3) - ); - } -} diff --git a/mudu_kernel/src/storage/frame.rs b/mudu_kernel/src/storage/frame.rs deleted file mode 100644 index ccf908e..0000000 --- a/mudu_kernel/src/storage/frame.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::storage::frame_ctrl::FrameCtrl; -use crate::storage::page_block::PageBlock; -use crate::storage::page_id; -use crate::storage::page_index::PageIndex; -use mcslock::raw::Mutex; -use mcslock::relax::Spin; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use std::sync::Arc; -use tokio::sync::RwLock; - -#[derive(Clone)] -pub struct Frame { - inner: Arc, -} - -pub struct FrameInner { - page_index: PageIndex, - page: Mutex, -} - -pub struct CtrlBlock { - frame_ctrl: FrameCtrl, - page_block: PageBlock, -} - -impl FrameInner { - fn new_empty(frame_id: u64, page_size: u64) -> Self { - Self { - page_index: PageIndex::invalid_index(), - page: Mutex::new(CtrlBlock::new_empty(frame_id, page_size)), - } - } -} - -impl CtrlBlock { - fn new_empty(frame_id: u64, page_size: u64) -> Self { - Self { - frame_ctrl: FrameCtrl::new_empty(frame_id), - page_block: PageBlock::new_empty(page_size), - } - } - - fn new(frame_ctrl: FrameCtrl, page_block: PageBlock) -> CtrlBlock { - Self { - frame_ctrl, - page_block, - } - } - - fn swap_page(&mut self, block: &mut PageBlock) -> RS<()> { - self.page_block.swap(block); - Ok(()) - } - - fn read_block(&mut self, block: &mut PageBlock) { - self.page_block.copy_block(block); - } -} - -impl Frame { - pub fn new_empty(frame_id: u64, page_size: u64) -> Frame { - Self { - inner: Arc::new(FrameInner::new_empty(frame_id, page_size)), - } - } - - pub fn from_page(frame_ctrl: FrameCtrl, file_id: u64, page_block: PageBlock) -> Frame { - let page_id = page_block.get_page_id(); - let ctrl_block = CtrlBlock::new(frame_ctrl, page_block); - Self { - inner: Arc::new(FrameInner { - page_index: PageIndex::new(file_id, page_id), - page: Mutex::new(ctrl_block), - }), - } - } - - pub fn page_index(&self) -> &PageIndex { - &self.inner.page_index - } - - pub fn set_fixed(&self, fixed: bool) -> RS<()> { - self.inner.page.lock_then(|cb| { - cb.frame_ctrl.set_fixed(fixed); - Ok(()) - })?; - Ok(()) - } - - pub fn swap_page(&self, block: &mut PageBlock) -> RS<()> { - self.inner.page.lock_then(move |cb| { - cb.swap_page(block)?; - Ok(()) - })?; - Ok(()) - } - - pub fn copy_page(&self, block: &mut PageBlock) -> RS<()> { - self.inner.page.lock_then(|cb| { - cb.read_block(block); - Ok(()) - }) - } - - // swap out a page, return true if its dirty flag was set. - pub async fn swap_out(&self) -> RS { - let ctrl = self.inner.page.lock_then(|cb| cb.frame_ctrl.clone()); - let is_dirty = ctrl.swap_out().await?; - Ok(is_dirty) - } - - pub fn reset(&self) -> RS<()> { - self.inner.page.lock_then(|cb| { - cb.frame_ctrl.reset(); - }); - Ok(()) - } -} diff --git a/mudu_kernel/src/storage/frame_ctrl.rs b/mudu_kernel/src/storage/frame_ctrl.rs deleted file mode 100644 index 4a269d9..0000000 --- a/mudu_kernel/src/storage/frame_ctrl.rs +++ /dev/null @@ -1,146 +0,0 @@ -use crate::storage::constant::PAGE_TAIL_SIZE; -use crate::storage::page_block::{Checksum, PageBlock}; -use crate::storage::{constant, page_id}; -use mudu::common::endian; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use std::ops::Range; -use std::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicU64, AtomicU8, Ordering}; -use std::sync::Arc; -use tokio::sync::Mutex as AsMutex; -use tokio_condvar::Condvar as AsCondvar; - -const PAGE_UN_SETUP: u8 = 0; -const PAGE_LOADED: u8 = 1; -const PAGE_SWAPPED: u8 = 2; - -enum State { - PageUnSetup, - PageLoaded, - PageSwapped, -} - -pub struct CtrlInner { - frame_index: u64, - is_dirty: AtomicBool, - is_fixed: AtomicBool, - is_swaping: AtomicBool, - used_count: AtomicU64, - state: AsMutex, - condvar: AsCondvar, -} - -#[derive(Clone)] -pub struct FrameCtrl { - inner: Arc, -} - -impl CtrlInner { - fn new(frame_index: u64) -> Self { - Self { - frame_index, - is_fixed: AtomicBool::new(false), - is_dirty: AtomicBool::new(false), - is_swaping: AtomicBool::new(false), - used_count: AtomicU64::new(0), - state: AsMutex::new(State::PageUnSetup), - condvar: Default::default(), - } - } -} - -fn write_header(page: &mut PageBlock, page_id: u64, lsn: u64) { - page.set_page_id(page_id); - page.set_lsn(lsn); -} - -impl FrameCtrl { - pub fn new_empty(frame_index: u64) -> Self { - let inner = Arc::new(CtrlInner::new(frame_index)); - Self { - inner: inner.clone(), - } - } - - pub fn notify(&self) { - self.inner.condvar.notify_all(); - } - - pub fn set_fixed(&mut self, fixed: bool) { - self.inner.is_fixed.store(fixed, Ordering::SeqCst); - } - - pub fn set_dirty(&mut self, dirty: bool) { - self.inner.is_dirty.store(dirty, Ordering::SeqCst); - } - - /// Calculate checksum for page data integrity - fn calculate_checksum(page: &PageBlock) -> Checksum { - page.block()[0..page.block().len() - PAGE_TAIL_SIZE] - .iter() - .fold(0u64, |acc, &byte| acc.wrapping_add(byte as u64)) - } - - /// Write data to the page at specific offset - fn write_data(&mut self, offset: usize, data: &[u8], page: &mut PageBlock) -> RS<()> { - if offset + data.len() > page.block().len() - PAGE_TAIL_SIZE { - return Err(m_error!(EC::StorageErr, "Data exceeds page boundary"))?; - } - page.block_mut()[offset..offset + data.len()].copy_from_slice(data); - let checksum = Self::calculate_checksum(page); - page.set_checksum(checksum); - self.set_dirty(true); - Ok(()) - } - - /// Read data from the page - pub fn read_data(&self, offset: usize, length: usize, page: &PageBlock) -> RS> { - if offset + length > page.block().len() - PAGE_TAIL_SIZE { - return Err(m_error!(EC::StorageErr, "Read beyond page boundary"))?; - } - Ok(page.block()[offset..offset + length].to_vec()) - } - - pub fn use_frame(&mut self) -> bool { - self.inner.used_count.fetch_add(1, Ordering::SeqCst); - if self.inner.is_swaping.load(Ordering::SeqCst) { - self.inner.used_count.fetch_sub(1, Ordering::SeqCst); - false - } else { - true - } - } - - pub fn unuse_frame(&mut self) { - let n = self.inner.used_count.fetch_sub(1, Ordering::SeqCst); - if n == 1 && self.inner.is_swaping.load(Ordering::SeqCst) { - self.inner.condvar.notify_all(); - } - } - - pub async fn swap_out(&self) -> RS { - let is_fixed = self.inner.is_fixed.load(Ordering::SeqCst); - if is_fixed { - return Err(m_error!(EC::StorageErr, "cannot sawp out a fixed page")); - } - self.inner.is_swaping.store(true, Ordering::SeqCst); - loop { - let mut guard = self.inner.state.lock().await; - if self.inner.used_count.load(Ordering::SeqCst) == 0 { - *guard = State::PageSwapped; - break; - } - self.inner.condvar.wait(guard).await; - } - let is_dirty = self.inner.is_dirty.load(Ordering::SeqCst); - Ok(is_dirty) - } - - pub fn reset(&mut self) { - self.inner.is_dirty.store(false, Ordering::SeqCst); - self.inner.is_fixed.store(false, Ordering::SeqCst); - self.inner.is_swaping.store(false, Ordering::SeqCst); - self.inner.used_count.store(0, Ordering::SeqCst); - } -} diff --git a/mudu_kernel/src/storage/log_entry.rs b/mudu_kernel/src/storage/log_entry.rs deleted file mode 100644 index 4a86d43..0000000 --- a/mudu_kernel/src/storage/log_entry.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::storage::page_block::PageUpdate; -use std::sync::atomic::{AtomicU64, Ordering}; - -static LSN: AtomicU64 = AtomicU64::new(0); - -pub fn lsn_init(n: u64) { - LSN.store(n, Ordering::SeqCst) -} - -pub fn next_lsn() -> u64 { - LSN.fetch_add(1, Ordering::SeqCst) -} - -pub struct LogEntry { - lsn: u64, - file_id: u64, - page_id: u64, - update: Vec, -} - -impl LogEntry { - pub fn new(file_id: u64, page_id: u64) -> LogEntry { - Self { - lsn: next_lsn(), - file_id, - page_id, - update: vec![], - } - } - - pub fn add_update_delta(&mut self, update: PageUpdate) { - self.update.push(update); - } - - pub fn lsn(&self) -> u64 { - self.lsn - } - - pub fn file_id(&self) -> u64 { - self.file_id - } - - pub fn page_id(&self) -> u64 { - self.page_id - } - - pub fn update(&self) -> &Vec { - &self.update - } -} diff --git a/mudu_kernel/src/storage/mem_table.rs b/mudu_kernel/src/storage/mem_table.rs index 23c9a52..1a7bcd8 100644 --- a/mudu_kernel/src/storage/mem_table.rs +++ b/mudu_kernel/src/storage/mem_table.rs @@ -3,7 +3,6 @@ use mudu::common::buf::Buf; use mudu::common::result::RS; use mudu_contract::tuple::tuple_binary_desc::TupleBinaryDesc as TupleDesc; use mudu_contract::tuple::tuple_key::{_KeyRef, TupleKey}; -use scc::Guard; use scc::TreeIndex; use std::collections::Bound; use std::sync::Arc; @@ -45,7 +44,7 @@ impl MemTableI { } pub fn read_key>(&self, key: K) -> RS> { - let key_ref = _KeyRef::new(&key); + let _key_ref = _KeyRef::new(&key); todo!(); /* let opt_r = self.tree_index.peek(todo!(), &g); @@ -54,7 +53,7 @@ impl MemTableI { */ } - pub fn read_range>(&self, begin: Bound, end: Bound) -> RS> { + pub fn read_range>(&self, _begin: Bound, _end: Bound) -> RS> { todo!() /* let mut rows = vec![]; diff --git a/mudu_kernel/src/storage/mod.rs b/mudu_kernel/src/storage/mod.rs index 9722f30..687708b 100644 --- a/mudu_kernel/src/storage/mod.rs +++ b/mudu_kernel/src/storage/mod.rs @@ -1,27 +1,13 @@ mod mem_store; pub mod mem_store_factory; mod mem_table; +pub mod page; pub mod pst_op_ch; mod pst_op_ch_impl; -mod bufer_manager; -pub mod constant; -mod data_file; -mod data_header; -mod disk_io; -mod extent; -mod frame; -mod frame_ctrl; -mod log_entry; -mod page_block; -mod page_id; -mod page_index; pub mod pst_store_factory; mod pst_store_impl; -mod space_manager; -pub mod storage_cfg; -mod storage_context; -mod table_space; +pub mod relation; mod test_pst_store; -pub mod worker_kv_store; +pub mod time_series; diff --git a/mudu_kernel/src/storage/page/mod.rs b/mudu_kernel/src/storage/page/mod.rs new file mode 100644 index 0000000..bb7537d --- /dev/null +++ b/mudu_kernel/src/storage/page/mod.rs @@ -0,0 +1,9 @@ +pub mod page_block_ref; +pub mod page_block_ref_mut; +pub mod page_header; +mod page_id; +pub mod page_tailer; +pub mod record_slot; +pub mod record_slot_ref; + +pub use page_id::PageId; diff --git a/mudu_kernel/src/storage/page/page_block_ref.rs b/mudu_kernel/src/storage/page/page_block_ref.rs new file mode 100644 index 0000000..16ece23 --- /dev/null +++ b/mudu_kernel/src/storage/page/page_block_ref.rs @@ -0,0 +1,293 @@ +use crate::storage::page::page_header::{ + PageHeader, NONE_PAGE_ID, PAGE_HEADER_MAGIC, PAGE_HEADER_SIZE, +}; +use crate::storage::page::page_tailer::{PageTailer, PAGE_TAILER_SIZE}; +use crate::storage::page::record_slot::{RecordSlot, RECORD_SLOT_SIZE}; +use crate::storage::page::record_slot_ref::RecordSlotRef; +use crate::storage::page::PageId; +use mudu::common::crc::crc16; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; + +pub const PAGE_SIZE: usize = 4096; +pub const RECORD_ALIGN: usize = 8; + +pub(crate) fn align_up(value: usize, align: usize) -> usize { + debug_assert!(align.is_power_of_two()); + (value + (align - 1)) & !(align - 1) +} + +/// `PageBlock` exposes a slotted-page view with the following physical layout: +/// `header / data / slot array / tailer`. +/// +/// The data area grows toward higher addresses. +/// The slot array grows toward lower addresses from the page tailer. +/// +/// Slot ordering intentionally differs between logical order and address order: +/// timestamps are sorted in ascending order, but smaller timestamps are stored at +/// higher addresses and larger timestamps are stored at lower addresses. +/// This means the physical slot array is descending when scanned from low to high +/// addresses, while the logical view returned by this module is ascending. +pub struct PageBlockRef<'a> { + page: &'a [u8], +} + +impl<'a> PageBlockRef<'a> { + pub fn new(page: &'a [u8]) -> Self { + Self { page } + } + + pub fn page(&self) -> &[u8] { + self.page + } + + pub fn header(&self) -> RS { + self.check_page_len()?; + PageHeader::decode(&self.page[..PAGE_HEADER_SIZE]) + } + + pub fn tailer(&self) -> RS { + self.check_page_len()?; + PageTailer::decode( + &self.page[self.tailer_offset()..self.tailer_offset() + PAGE_TAILER_SIZE], + ) + } + + pub fn slot_count(&self) -> RS { + Ok(self.header()?.record_count() as usize) + } + + pub fn slot(&self, sorted_index: usize) -> RS { + Ok(self.slot_ref(sorted_index)?.to_owned()) + } + + pub fn slot_ref(&self, sorted_index: usize) -> RS> { + self.check_page_len()?; + let count = self.slot_count()?; + if sorted_index >= count { + return Err(m_error!( + EC::DecodeErr, + format!("slot index {} out of range {}", sorted_index, count) + )); + } + + let offset = self.slot_offset(sorted_index)?; + RecordSlotRef::new(&self.page[offset..offset + RECORD_SLOT_SIZE]) + } + + pub fn slots(&self) -> RS> { + let count = self.slot_count()?; + (0..count).map(|idx| self.slot(idx)).collect() + } + + pub fn record_bytes(&self, sorted_index: usize) -> RS<&[u8]> { + let slot = self.slot_ref(sorted_index)?; + let offset = slot.offset() as usize; + let size = slot.size() as usize; + let slot_start = self.slot_array_low_bound()?; + if offset < PAGE_HEADER_SIZE || offset + size > slot_start { + return Err(m_error!( + EC::DecodeErr, + format!( + "record range [{}, {}) overlaps page metadata or slot array", + offset, + offset + size + ) + )); + } + Ok(&self.page[offset..offset + size]) + } + + pub fn free_bytes(&self) -> RS { + Ok(self.header()?.free_bytes() as usize) + } + + pub fn is_empty(&self) -> RS { + Ok(self.slot_count()? == 0) + } + + pub fn timestamp_bounds(&self) -> RS> { + let count = self.slot_count()?; + if count == 0 { + return Ok(None); + } + + let min_ts = self.slot_ref(0)?.timestamp(); + let max_ts = self.slot_ref(count - 1)?.timestamp(); + Ok(Some((min_ts, max_ts))) + } + + pub fn find_slot_index(&self, timestamp: u64, tuple_id: u64) -> RS> { + let count = self.slot_count()?; + if count == 0 { + return Ok(None); + } + + let mut low = 0usize; + let mut high = count; + while low < high { + let mid = low + ((high - low) / 2); + let mid_ts = self.slot_ref(mid)?.timestamp(); + if mid_ts < timestamp { + low = mid + 1; + } else { + high = mid; + } + } + + let mut idx = low; + while idx < count { + let slot = self.slot_ref(idx)?; + if slot.timestamp() != timestamp { + break; + } + if slot.tuple_id() == tuple_id { + return Ok(Some(idx)); + } + idx += 1; + } + Ok(None) + } + + pub fn active_prev_page(&self) -> RS> { + let prev = self.header()?.prev_page(); + Ok((prev != NONE_PAGE_ID).then_some(prev)) + } + + pub fn active_next_page(&self) -> RS> { + let next = self.header()?.next_page(); + Ok((next != NONE_PAGE_ID).then_some(next)) + } + + pub fn validate_layout(&self) -> RS<()> { + let header = self.header()?; + if header.magic() != PAGE_HEADER_MAGIC { + return Err(m_error!( + EC::DecodeErr, + format!("invalid page magic {:#x}", header.magic()) + )); + } + + let count = header.record_count() as usize; + let first_free = header.first_free_offset() as usize; + let slot_start = self.slot_region_start_for_count(count); + if first_free < PAGE_HEADER_SIZE || first_free > slot_start { + return Err(m_error!( + EC::DecodeErr, + format!( + "invalid free region boundary: first_free_offset={}, slot_start={}", + first_free, slot_start + ) + )); + } + + let expected_free = slot_start - first_free; + if header.free_bytes() as usize != expected_free { + return Err(m_error!( + EC::DecodeErr, + format!( + "free_bytes mismatch: header={}, expected={}", + header.free_bytes(), + expected_free + ) + )); + } + + let mut prev: Option = None; + for idx in 0..count { + let slot = self.slot(idx)?; + if let Some(prev_slot) = prev { + if prev_slot.cmp_key(&slot).is_gt() { + return Err(m_error!( + EC::DecodeErr, + format!("slot order is not ascending at index {}", idx) + )); + } + } + + let offset = slot.offset() as usize; + let size = slot.size() as usize; + if offset % RECORD_ALIGN != 0 { + return Err(m_error!( + EC::DecodeErr, + format!( + "record offset {} is not {}-byte aligned", + offset, RECORD_ALIGN + ) + )); + } + if offset < PAGE_HEADER_SIZE || offset + size > slot_start { + return Err(m_error!( + EC::DecodeErr, + format!("slot {} points outside the data region", idx) + )); + } + let payload = &self.page[offset..offset + size]; + let checksum = crc16(payload); + if slot.check_sum() != checksum { + return Err(m_error!( + EC::DecodeErr, + format!( + "slot {} payload checksum mismatch: stored={}, actual={}", + idx, + slot.check_sum(), + checksum + ) + )); + } + prev = Some(slot); + } + + let tailer = self.tailer()?; + if header.lsn() != tailer.lsn() { + return Err(m_error!( + EC::DecodeErr, + format!( + "page lsn mismatch: header={}, tailer={}", + header.lsn(), + tailer.lsn() + ) + )); + } + tailer.validate_checksum(self.page)?; + Ok(()) + } + + fn check_page_len(&self) -> RS<()> { + if self.page.len() < PAGE_SIZE { + return Err(m_error!( + EC::DecodeErr, + format!( + "page block requires {} bytes, got {}", + PAGE_SIZE, + self.page.len() + ) + )); + } + Ok(()) + } + + fn tailer_offset(&self) -> usize { + PAGE_SIZE - PAGE_TAILER_SIZE + } + + fn slot_region_start_for_count(&self, count: usize) -> usize { + self.tailer_offset() - (count * RECORD_SLOT_SIZE) + } + + fn slot_array_low_bound(&self) -> RS { + Ok(self.slot_region_start_for_count(self.slot_count()?)) + } + + fn slot_offset(&self, sorted_index: usize) -> RS { + let count = self.slot_count()?; + if sorted_index >= count { + return Err(m_error!( + EC::DecodeErr, + format!("slot index {} out of range {}", sorted_index, count) + )); + } + Ok(self.tailer_offset() - ((sorted_index + 1) * RECORD_SLOT_SIZE)) + } +} diff --git a/mudu_kernel/src/storage/page/page_block_ref_mut.rs b/mudu_kernel/src/storage/page/page_block_ref_mut.rs new file mode 100644 index 0000000..c307760 --- /dev/null +++ b/mudu_kernel/src/storage/page/page_block_ref_mut.rs @@ -0,0 +1,477 @@ +use crate::storage::page::page_block_ref::{align_up, PageBlockRef, PAGE_SIZE, RECORD_ALIGN}; +use crate::storage::page::page_header::{PageHeader, PAGE_HEADER_SIZE}; +use crate::storage::page::page_tailer::{PageTailer, PAGE_TAILER_SIZE}; +use crate::storage::page::record_slot::{RecordSlot, RECORD_SLOT_SIZE}; +use crate::storage::page::record_slot_ref::RecordSlotRef; +use crate::storage::page::PageId; +use mudu::common::crc::crc16; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; + +/// `PageBlockRefMut` keeps the same layout and ordering guarantees as `PageBlockRef`. +/// +/// Record payloads are always written on 8-byte boundaries in the data area. +/// `RecordSlot::size` stores the real payload length rather than the aligned size. +/// +/// Update and delete paths compact the page after the change so that the data +/// area stays contiguous and `free_bytes` remains an exact value instead of a +/// fragmented estimate. +/// +/// The page LSN is mirrored in both the header and the tailer. Initialization +/// starts at LSN 1, and every logical page rewrite bumps the LSN before the +/// tailer checksum is recomputed. +pub struct PageBlockRefMut<'a> { + page: &'a mut [u8], +} + +impl<'a> PageBlockRefMut<'a> { + pub fn new(page: &'a mut [u8]) -> Self { + Self { page } + } + + pub fn page(&self) -> &[u8] { + self.page + } + + pub fn init_empty(&mut self, page_id: PageId) -> RS<()> { + self.check_page_len()?; + self.page.fill(0); + + let mut header = PageHeader::new(page_id); + header.set_lsn(1); + header.set_first_free_offset(PAGE_HEADER_SIZE as u32); + header.set_last_record_offset(PAGE_HEADER_SIZE as u32); + header.set_record_count(0); + header.set_free_bytes((self.tailer_offset() - PAGE_HEADER_SIZE) as u32); + header.encode(&mut self.page[..PAGE_HEADER_SIZE])?; + + let tailer_offset = self.tailer_offset(); + let mut tailer = PageTailer::default(); + tailer.set_lsn(1); + tailer.refresh_checksum(self.page)?; + tailer.encode(&mut self.page[tailer_offset..tailer_offset + PAGE_TAILER_SIZE])?; + Ok(()) + } + + pub fn header(&self) -> RS { + PageBlockRef::new(self.page).header() + } + + pub fn tailer(&self) -> RS { + PageBlockRef::new(self.page).tailer() + } + + pub fn slot(&self, sorted_index: usize) -> RS { + PageBlockRef::new(self.page).slot(sorted_index) + } + + pub fn slot_ref(&self, sorted_index: usize) -> RS> { + self.check_page_len()?; + let count = self.header()?.record_count() as usize; + if sorted_index >= count { + return Err(m_error!( + EC::DecodeErr, + format!("slot index {} out of range {}", sorted_index, count) + )); + } + + let offset = self.slot_offset_for_sorted_index(sorted_index); + RecordSlotRef::new(&self.page[offset..offset + RECORD_SLOT_SIZE]) + } + + pub fn record_bytes(&self, sorted_index: usize) -> RS<&[u8]> { + let slot = self.slot_ref(sorted_index)?; + let offset = slot.offset() as usize; + let size = slot.size() as usize; + let slot_start = self.slot_region_start_for_count(self.header()?.record_count() as usize); + if offset < PAGE_HEADER_SIZE || offset + size > slot_start { + return Err(m_error!( + EC::DecodeErr, + format!( + "record range [{}, {}) overlaps page metadata or slot array", + offset, + offset + size + ) + )); + } + Ok(&self.page[offset..offset + size]) + } + + pub fn set_page_links(&mut self, prev_page: PageId, next_page: PageId) -> RS<()> { + let mut header = self.header()?; + header.set_lsn(header.lsn().saturating_add(1)); + header.set_prev_page(prev_page); + header.set_next_page(next_page); + header.encode(&mut self.page[..PAGE_HEADER_SIZE])?; + self.refresh_tailer_checksum()?; + Ok(()) + } + + pub fn insert_record(&mut self, timestamp: u64, tuple_id: u64, payload: &[u8]) -> RS { + self.check_page_len()?; + + let header = self.header()?; + let count = header.record_count() as usize; + let data_offset = align_up(header.first_free_offset() as usize, RECORD_ALIGN); + let padding = data_offset - header.first_free_offset() as usize; + let next_slot_start = self.slot_region_start_for_count(count + 1); + let record_end = data_offset + payload.len(); + if record_end > next_slot_start + || padding + payload.len() + RECORD_SLOT_SIZE > header.free_bytes() as usize + { + return Err(m_error!( + EC::InsufficientBufferSpace, + "page does not have enough free space" + )); + } + + self.page[data_offset..record_end].copy_from_slice(payload); + let mut slot = RecordSlot::new( + data_offset as u32, + payload.len() as u32, + timestamp, + tuple_id, + ); + slot.set_check_sum(crc16(payload)); + let insert_at = self.find_insert_position_by_key(&slot)?; + self.insert_slot_bytes(insert_at, &slot)?; + self.rewrite_header_after_insert( + &header, + count + 1, + record_end as u32, + data_offset as u32, + )?; + Ok(insert_at) + } + + pub fn delete_record(&mut self, sorted_index: usize) -> RS<()> { + self.check_page_len()?; + let mut slots = self.read_all_slots()?; + if sorted_index >= slots.len() { + return Err(m_error!( + EC::DecodeErr, + format!("slot index {} out of range {}", sorted_index, slots.len()) + )); + } + + slots.remove(sorted_index); + self.compact_with_slots(slots) + } + + pub fn update_record( + &mut self, + sorted_index: usize, + timestamp: u64, + tuple_id: u64, + payload: &[u8], + ) -> RS { + self.check_page_len()?; + let mut slots = self.read_all_slots()?; + if sorted_index >= slots.len() { + return Err(m_error!( + EC::DecodeErr, + format!("slot index {} out of range {}", sorted_index, slots.len()) + )); + } + + slots.remove(sorted_index); + let mut new_slot = RecordSlot::new(0, payload.len() as u32, timestamp, tuple_id); + new_slot.set_check_sum(crc16(payload)); + let mut entries = self.materialize_entries(&slots)?; + entries.push((new_slot, payload.to_vec())); + let mut sorted_slots: Vec = entries.iter().map(|(slot, _)| *slot).collect(); + sorted_slots.sort_by(|left, right| left.cmp_key(right)); + let new_index = sorted_slots + .iter() + .position(|slot| slot.cmp_key(&new_slot).is_eq()) + .ok_or_else(|| m_error!(EC::EncodeErr, "updated slot is missing after sorting"))?; + self.rebuild_from_entries(entries)?; + Ok(new_index) + } + + pub fn compact(&mut self) -> RS<()> { + let slots = self.read_all_slots()?; + self.compact_with_slots(slots) + } + + fn compact_with_slots(&mut self, slots: Vec) -> RS<()> { + let entries = self.materialize_entries(&slots)?; + self.rebuild_from_entries(entries)?; + Ok(()) + } + + fn rebuild_from_entries(&mut self, mut entries: Vec<(RecordSlot, Vec)>) -> RS<()> { + let header = self.header()?; + entries.sort_by(|(left, _), (right, _)| left.cmp_key(right)); + + let tailer_offset = self.tailer_offset(); + self.page[PAGE_HEADER_SIZE..tailer_offset].fill(0); + + let mut next_data = PAGE_HEADER_SIZE; + let mut slots = Vec::with_capacity(entries.len()); + for (mut slot, payload) in entries { + let offset = align_up(next_data, RECORD_ALIGN); + let record_end = offset + payload.len(); + let slot_start = self.slot_region_start_for_count(slots.len() + 1); + if record_end > slot_start { + return Err(m_error!( + EC::InsufficientBufferSpace, + "page does not have enough free space" + )); + } + + self.page[offset..record_end].copy_from_slice(&payload); + slot.set_offset(offset as u32); + slot.set_size(payload.len() as u32); + slot.set_check_sum(crc16(&payload)); + slots.push(slot); + next_data = record_end; + } + + self.rewrite_header_and_slots(header.page_id(), slots, next_data as u32)?; + Ok(()) + } + + fn rewrite_header_and_slots( + &mut self, + page_id: PageId, + slots: Vec, + first_free_offset: u32, + ) -> RS<()> { + let slot_region_start = self.slot_region_start_for_count(slots.len()); + let tailer_offset = self.tailer_offset(); + self.page[slot_region_start..tailer_offset].fill(0); + + for (idx, slot) in slots.iter().enumerate() { + let offset = tailer_offset - ((idx + 1) * RECORD_SLOT_SIZE); + slot.encode(&mut self.page[offset..offset + RECORD_SLOT_SIZE])?; + } + + let free_bytes = slot_region_start - first_free_offset as usize; + let last_record_offset = slots + .iter() + .map(|slot| slot.offset()) + .max() + .unwrap_or(PAGE_HEADER_SIZE as u32); + let next_lsn = self.header()?.lsn().saturating_add(1); + + let mut header = self.header()?; + debug_assert_eq!(header.page_id(), page_id); + header.set_lsn(next_lsn); + header.set_record_count(slots.len() as u32); + header.set_first_free_offset(first_free_offset); + header.set_last_record_offset(last_record_offset); + header.set_free_bytes(free_bytes as u32); + header.encode(&mut self.page[..PAGE_HEADER_SIZE])?; + self.refresh_tailer_checksum()?; + Ok(()) + } + + fn materialize_entries(&self, slots: &[RecordSlot]) -> RS)>> { + let page = PageBlockRef::new(self.page); + slots + .iter() + .map(|slot| { + let offset = slot.offset() as usize; + let size = slot.size() as usize; + let data = page.page()[offset..offset + size].to_vec(); + Ok((*slot, data)) + }) + .collect() + } + + fn read_all_slots(&self) -> RS> { + let page = PageBlockRef::new(self.page); + let count = page.slot_count()?; + (0..count).map(|idx| page.slot(idx)).collect() + } + + fn check_page_len(&self) -> RS<()> { + if self.page.len() < PAGE_SIZE { + return Err(m_error!( + EC::EncodeErr, + format!( + "page block requires {} bytes, got {}", + PAGE_SIZE, + self.page.len() + ) + )); + } + Ok(()) + } + + fn tailer_offset(&self) -> usize { + PAGE_SIZE - PAGE_TAILER_SIZE + } + + fn slot_region_start_for_count(&self, count: usize) -> usize { + self.tailer_offset() - (count * RECORD_SLOT_SIZE) + } + + fn slot_offset_for_sorted_index(&self, sorted_index: usize) -> usize { + self.tailer_offset() - ((sorted_index + 1) * RECORD_SLOT_SIZE) + } + + fn refresh_tailer_checksum(&mut self) -> RS<()> { + let tailer_offset = self.tailer_offset(); + let mut tailer = + PageTailer::decode(&self.page[tailer_offset..tailer_offset + PAGE_TAILER_SIZE])?; + tailer.set_lsn(self.header()?.lsn()); + tailer.refresh_checksum(self.page)?; + tailer.encode(&mut self.page[tailer_offset..tailer_offset + PAGE_TAILER_SIZE])?; + Ok(()) + } + + fn find_insert_position_by_key(&self, new_slot: &RecordSlot) -> RS { + let page = PageBlockRef::new(self.page); + let count = page.slot_count()?; + let mut low = 0usize; + let mut high = count; + while low < high { + let mid = low + ((high - low) / 2); + let slot = page.slot_ref(mid)?; + if slot.cmp_key(new_slot).is_lt() { + low = mid + 1; + } else { + high = mid; + } + } + Ok(low) + } + + fn insert_slot_bytes(&mut self, insert_at: usize, slot: &RecordSlot) -> RS<()> { + let count = self.header()?.record_count() as usize; + let old_region_start = self.slot_region_start_for_count(count); + let new_region_start = self.slot_region_start_for_count(count + 1); + let moved_region_end = self.tailer_offset() - (insert_at * RECORD_SLOT_SIZE); + if insert_at < count { + self.page + .copy_within(old_region_start..moved_region_end, new_region_start); + } + + let slot_offset = self.slot_offset_for_sorted_index(insert_at); + slot.encode(&mut self.page[slot_offset..slot_offset + RECORD_SLOT_SIZE])?; + Ok(()) + } + + fn rewrite_header_after_insert( + &mut self, + header: &PageHeader, + new_count: usize, + first_free_offset: u32, + last_record_offset: u32, + ) -> RS<()> { + let mut updated = header.clone(); + updated.set_lsn(header.lsn().saturating_add(1)); + updated.set_record_count(new_count as u32); + updated.set_first_free_offset(first_free_offset); + updated.set_last_record_offset(last_record_offset); + updated.set_free_bytes( + (self.slot_region_start_for_count(new_count) - first_free_offset as usize) as u32, + ); + updated.encode(&mut self.page[..PAGE_HEADER_SIZE])?; + self.refresh_tailer_checksum()?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::PageBlockRefMut; + use crate::storage::page::page_block_ref::{PageBlockRef, PAGE_SIZE, RECORD_ALIGN}; + + #[test] + fn init_empty_page_sets_layout_boundaries() { + let mut raw = [0u8; PAGE_SIZE]; + let mut page = PageBlockRefMut::new(&mut raw); + page.init_empty(7).unwrap(); + + let ro = PageBlockRef::new(&raw); + let header = ro.header().unwrap(); + assert_eq!(header.page_id(), 7); + assert_eq!(header.lsn(), 1); + assert_eq!(header.record_count(), 0); + assert_eq!(header.first_free_offset() as usize, 128); + assert_eq!(ro.tailer().unwrap().lsn(), 1); + ro.validate_layout().unwrap(); + } + + #[test] + fn insert_keeps_slots_sorted_and_records_aligned() { + let mut raw = [0u8; PAGE_SIZE]; + let mut page = PageBlockRefMut::new(&mut raw); + page.init_empty(1).unwrap(); + + page.insert_record(30, 1, b"ccc").unwrap(); + page.insert_record(10, 2, b"aaa").unwrap(); + page.insert_record(20, 3, b"bbb").unwrap(); + + let ro = PageBlockRef::new(&raw); + ro.validate_layout().unwrap(); + assert_eq!(ro.slot(0).unwrap().timestamp(), 10); + assert_eq!(ro.slot(1).unwrap().timestamp(), 20); + assert_eq!(ro.slot(2).unwrap().timestamp(), 30); + assert_eq!(ro.record_bytes(0).unwrap(), b"aaa"); + assert_eq!(ro.record_bytes(1).unwrap(), b"bbb"); + assert_eq!(ro.record_bytes(2).unwrap(), b"ccc"); + assert_eq!(ro.slot(0).unwrap().offset() as usize % RECORD_ALIGN, 0); + assert_eq!(ro.slot(1).unwrap().offset() as usize % RECORD_ALIGN, 0); + assert_eq!(ro.slot(2).unwrap().offset() as usize % RECORD_ALIGN, 0); + assert_eq!(ro.header().unwrap().lsn(), 4); + assert_eq!(ro.tailer().unwrap().lsn(), 4); + } + + #[test] + fn delete_and_update_keep_layout_valid() { + let mut raw = [0u8; PAGE_SIZE]; + let mut page = PageBlockRefMut::new(&mut raw); + page.init_empty(3).unwrap(); + + page.insert_record(10, 1, b"alpha").unwrap(); + page.insert_record(20, 2, b"beta").unwrap(); + page.insert_record(30, 3, b"gamma").unwrap(); + page.delete_record(1).unwrap(); + page.update_record(1, 15, 3, b"delta").unwrap(); + + let ro = PageBlockRef::new(&raw); + ro.validate_layout().unwrap(); + assert_eq!(ro.slot_count().unwrap(), 2); + assert_eq!(ro.slot(0).unwrap().timestamp(), 10); + assert_eq!(ro.slot(1).unwrap().timestamp(), 15); + assert_eq!(ro.record_bytes(0).unwrap(), b"alpha"); + assert_eq!(ro.record_bytes(1).unwrap(), b"delta"); + assert_eq!(ro.header().unwrap().lsn(), 6); + assert_eq!(ro.tailer().unwrap().lsn(), 6); + } + + #[test] + fn validate_layout_rejects_lsn_mismatch() { + let mut raw = [0u8; PAGE_SIZE]; + let mut page = PageBlockRefMut::new(&mut raw); + page.init_empty(9).unwrap(); + + let tailer_offset = PAGE_SIZE - crate::storage::page::page_tailer::PAGE_TAILER_SIZE; + let mut tailer = PageBlockRef::new(&raw).tailer().unwrap(); + tailer.set_lsn(tailer.lsn() + 1); + tailer.encode(&mut raw[tailer_offset..PAGE_SIZE]).unwrap(); + + let err = PageBlockRef::new(&raw).validate_layout().unwrap_err(); + let msg = format!("{err:?}"); + assert!(msg.contains("page lsn mismatch")); + } + + #[test] + fn validate_layout_rejects_bad_tailer_checksum() { + let mut raw = [0u8; PAGE_SIZE]; + let mut page = PageBlockRefMut::new(&mut raw); + page.init_empty(5).unwrap(); + page.insert_record(10, 1, b"abc").unwrap(); + + let record_offset = PageBlockRef::new(&raw).slot(0).unwrap().offset() as usize; + raw[record_offset] ^= 0x1; + let err = PageBlockRef::new(&raw).validate_layout().unwrap_err(); + let msg = format!("{err:?}"); + assert!(msg.contains("checksum mismatch")); + } +} diff --git a/mudu_kernel/src/storage/page/page_header.rs b/mudu_kernel/src/storage/page/page_header.rs new file mode 100644 index 0000000..cf8bd79 --- /dev/null +++ b/mudu_kernel/src/storage/page/page_header.rs @@ -0,0 +1,249 @@ +use crate::storage::page::PageId; +use crate::wal::lsn::LSN; +use byteorder::{ByteOrder, LittleEndian}; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; + +pub const PAGE_HEADER_SIZE: usize = 128; +pub const NONE_PAGE_ID: PageId = PageId::MAX; +/// Magic value for the ASCII tag `PAGE`. +/// As a numeric constant it is `0x5041_4745` (`'P' 'A' 'G' 'E'`). +/// Because the header is encoded in little-endian order, the on-page byte +/// sequence is `45 47 41 50`, which corresponds to `E G A P` if read byte by byte. +pub const PAGE_HEADER_MAGIC: u32 = 0x5041_4745; +const PAGE_HEADER_FIELD_COUNT: usize = 10; +const PAGE_HEADER_FIXED_SIZE: usize = PAGE_HEADER_FIELD_COUNT * std::mem::size_of::(); +const PAGE_HEADER_RESERVED_SIZE: usize = PAGE_HEADER_SIZE - PAGE_HEADER_FIXED_SIZE; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PageHeader { + magic: u32, + page_id: PageId, + prev_page: PageId, + next_page: PageId, + lsn: LSN, + flags: u32, + record_count: u32, + first_free_offset: u32, + free_bytes: u32, + last_record_offset: u32, + reserved: [u8; PAGE_HEADER_RESERVED_SIZE], +} + +impl PageHeader { + pub fn new(page_id: PageId) -> Self { + Self { + magic: PAGE_HEADER_MAGIC, + page_id, + prev_page: NONE_PAGE_ID, + next_page: NONE_PAGE_ID, + lsn: 0, + flags: 0, + record_count: 0, + first_free_offset: PAGE_HEADER_SIZE as u32, + free_bytes: 0, + last_record_offset: 0, + reserved: [0; PAGE_HEADER_RESERVED_SIZE], + } + } + + pub fn decode(input: &[u8]) -> RS { + if input.len() < PAGE_HEADER_SIZE { + return Err(m_error!( + EC::DecodeErr, + format!( + "page header requires {} bytes, got {}", + PAGE_HEADER_SIZE, + input.len() + ) + )); + } + + let mut offset = 0usize; + let read_u32 = |input: &[u8], offset: &mut usize| -> u32 { + let value = LittleEndian::read_u32(&input[*offset..*offset + 4]); + *offset += 4; + value + }; + + let magic = read_u32(input, &mut offset); + let page_id = read_u32(input, &mut offset); + let prev_page = read_u32(input, &mut offset); + let next_page = read_u32(input, &mut offset); + let lsn = read_u32(input, &mut offset) as LSN; + let flags = read_u32(input, &mut offset); + let record_count = read_u32(input, &mut offset); + let first_free_offset = read_u32(input, &mut offset); + let free_bytes = read_u32(input, &mut offset); + let last_record_offset = read_u32(input, &mut offset); + let mut reserved = [0u8; PAGE_HEADER_RESERVED_SIZE]; + reserved.copy_from_slice(&input[offset..offset + PAGE_HEADER_RESERVED_SIZE]); + + Ok(Self { + magic, + page_id, + prev_page, + next_page, + lsn, + flags, + record_count, + first_free_offset, + free_bytes, + last_record_offset, + reserved, + }) + } + + pub fn encode(&self, out: &mut [u8]) -> RS<()> { + if out.len() < PAGE_HEADER_SIZE { + return Err(m_error!( + EC::EncodeErr, + format!( + "page header encode requires {} bytes, got {}", + PAGE_HEADER_SIZE, + out.len() + ) + )); + } + + let mut offset = 0usize; + + let write_u32 = |out: &mut [u8], offset: &mut usize, value: u32| { + LittleEndian::write_u32(&mut out[*offset..*offset + 4], value); + *offset += 4; + }; + + write_u32(out, &mut offset, self.magic); + write_u32(out, &mut offset, self.page_id); + write_u32(out, &mut offset, self.prev_page); + write_u32(out, &mut offset, self.next_page); + write_u32(out, &mut offset, self.lsn); + write_u32(out, &mut offset, self.flags); + write_u32(out, &mut offset, self.record_count); + write_u32(out, &mut offset, self.first_free_offset); + write_u32(out, &mut offset, self.free_bytes); + write_u32(out, &mut offset, self.last_record_offset); + out[offset..offset + PAGE_HEADER_RESERVED_SIZE].copy_from_slice(&self.reserved); + Ok(()) + } + + pub fn magic(&self) -> u32 { + self.magic + } + + pub fn page_id(&self) -> PageId { + self.page_id + } + + pub fn prev_page(&self) -> PageId { + self.prev_page + } + + pub fn next_page(&self) -> PageId { + self.next_page + } + + pub fn lsn(&self) -> LSN { + self.lsn + } + + pub fn flags(&self) -> u32 { + self.flags + } + + pub fn record_count(&self) -> u32 { + self.record_count + } + + pub fn first_free_offset(&self) -> u32 { + self.first_free_offset + } + + pub fn free_bytes(&self) -> u32 { + self.free_bytes + } + + pub fn last_record_offset(&self) -> u32 { + self.last_record_offset + } + + pub fn reserved(&self) -> &[u8; PAGE_HEADER_RESERVED_SIZE] { + &self.reserved + } + + pub fn set_prev_page(&mut self, prev_page: PageId) { + self.prev_page = prev_page; + } + + pub fn set_next_page(&mut self, next_page: PageId) { + self.next_page = next_page; + } + + pub fn set_lsn(&mut self, lsn: LSN) { + self.lsn = lsn; + } + + pub fn set_flags(&mut self, flags: u32) { + self.flags = flags; + } + + pub fn set_record_count(&mut self, record_count: u32) { + self.record_count = record_count; + } + + pub fn set_first_free_offset(&mut self, first_free_offset: u32) { + self.first_free_offset = first_free_offset; + } + + pub fn set_free_bytes(&mut self, free_bytes: u32) { + self.free_bytes = free_bytes; + } + + pub fn set_last_record_offset(&mut self, last_record_offset: u32) { + self.last_record_offset = last_record_offset; + } + + pub fn reserved_mut(&mut self) -> &mut [u8; PAGE_HEADER_RESERVED_SIZE] { + &mut self.reserved + } +} + +impl Default for PageHeader { + fn default() -> Self { + Self::new(0) + } +} + +#[cfg(test)] +mod tests { + use super::{PageHeader, PAGE_HEADER_MAGIC, PAGE_HEADER_SIZE}; + + #[test] + fn page_header_encodes_to_fixed_128_bytes() { + let header = PageHeader::default(); + let mut encoded = [0u8; PAGE_HEADER_SIZE]; + header.encode(&mut encoded).unwrap(); + assert_eq!(encoded.len(), PAGE_HEADER_SIZE); + assert_eq!(header.magic(), PAGE_HEADER_MAGIC); + } + + #[test] + fn page_header_roundtrip() { + let mut header = PageHeader::new(7); + header.set_prev_page(5); + header.set_next_page(9); + header.set_lsn(11); + header.set_flags(17); + header.set_record_count(19); + header.set_first_free_offset(23); + header.set_free_bytes(29); + header.set_last_record_offset(31); + header.reserved_mut()[0] = 37; + + let mut encoded = [0u8; PAGE_HEADER_SIZE]; + header.encode(&mut encoded).unwrap(); + let decoded = PageHeader::decode(&encoded).unwrap(); + assert_eq!(decoded, header); + } +} diff --git a/mudu_kernel/src/storage/page/page_id.rs b/mudu_kernel/src/storage/page/page_id.rs new file mode 100644 index 0000000..d1625a9 --- /dev/null +++ b/mudu_kernel/src/storage/page/page_id.rs @@ -0,0 +1 @@ +pub type PageId = u32; diff --git a/mudu_kernel/src/storage/page/page_tailer.rs b/mudu_kernel/src/storage/page/page_tailer.rs new file mode 100644 index 0000000..16b80b2 --- /dev/null +++ b/mudu_kernel/src/storage/page/page_tailer.rs @@ -0,0 +1,140 @@ +use crate::wal::lsn::LSN; +use byteorder::{ByteOrder, LittleEndian}; +use mudu::common::crc::crc32; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; + +pub const PAGE_TAILER_SIZE: usize = 8; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct PageTailer { + lsn: LSN, + checksum: u32, +} + +impl PageTailer { + pub fn new(lsn: LSN, checksum: u32) -> Self { + Self { lsn, checksum } + } + + pub fn decode(input: &[u8]) -> RS { + if input.len() < PAGE_TAILER_SIZE { + return Err(m_error!( + EC::DecodeErr, + format!( + "page tailer requires {} bytes, got {}", + PAGE_TAILER_SIZE, + input.len() + ) + )); + } + + Ok(Self { + lsn: LittleEndian::read_u32(&input[0..4]) as LSN, + checksum: LittleEndian::read_u32(&input[4..8]), + }) + } + + pub fn encode(&self, out: &mut [u8]) -> RS<()> { + if out.len() < PAGE_TAILER_SIZE { + return Err(m_error!( + EC::EncodeErr, + format!( + "page tailer encode requires {} bytes, got {}", + PAGE_TAILER_SIZE, + out.len() + ) + )); + } + + LittleEndian::write_u32(&mut out[0..4], self.lsn); + LittleEndian::write_u32(&mut out[4..8], self.checksum); + Ok(()) + } + + /// Computes the page CRC32 over the full page except for the trailing tailer. + /// The covered range is `[0, page.len() - PAGE_TAILER_SIZE)`. + pub fn checksum_for_page(page: &[u8]) -> RS { + if page.len() < PAGE_TAILER_SIZE { + return Err(m_error!( + EC::EncodeErr, + format!( + "page checksum requires at least {} bytes, got {}", + PAGE_TAILER_SIZE, + page.len() + ) + )); + } + + Ok(crc32(&page[..page.len() - PAGE_TAILER_SIZE])) + } + + pub fn refresh_checksum(&mut self, page: &[u8]) -> RS<()> { + self.checksum = Self::checksum_for_page(page)?; + Ok(()) + } + + pub fn validate_checksum(&self, page: &[u8]) -> RS<()> { + let actual = Self::checksum_for_page(page)?; + if self.checksum != actual { + return Err(m_error!( + EC::DecodeErr, + format!( + "page checksum mismatch: stored={}, actual={}", + self.checksum, actual + ) + )); + } + Ok(()) + } + + pub fn lsn(&self) -> LSN { + self.lsn + } + + pub fn checksum(&self) -> u32 { + self.checksum + } + + pub fn set_lsn(&mut self, lsn: LSN) { + self.lsn = lsn; + } + + pub fn set_checksum(&mut self, checksum: u32) { + self.checksum = checksum; + } +} + +#[cfg(test)] +mod tests { + use super::{PageTailer, PAGE_TAILER_SIZE}; + + #[test] + fn page_tailer_encodes_to_fixed_8_bytes() { + let tailer = PageTailer::default(); + let mut encoded = [0u8; PAGE_TAILER_SIZE]; + tailer.encode(&mut encoded).unwrap(); + assert_eq!(encoded.len(), PAGE_TAILER_SIZE); + } + + #[test] + fn page_tailer_roundtrip() { + let tailer = PageTailer::new(17, 29); + let mut encoded = [0u8; PAGE_TAILER_SIZE]; + tailer.encode(&mut encoded).unwrap(); + let decoded = PageTailer::decode(&encoded).unwrap(); + assert_eq!(decoded, tailer); + } + + #[test] + fn page_tailer_checksum_covers_page_except_tailer() { + let mut page = [0u8; 32]; + page[..24].copy_from_slice(&[1u8; 24]); + page[24..].copy_from_slice(&[9u8; 8]); + + let checksum = PageTailer::checksum_for_page(&page).unwrap(); + page[24..].copy_from_slice(&[7u8; 8]); + assert_eq!(checksum, PageTailer::checksum_for_page(&page).unwrap()); + } +} diff --git a/mudu_kernel/src/storage/page/record_slot.rs b/mudu_kernel/src/storage/page/record_slot.rs new file mode 100644 index 0000000..2ff1d5b --- /dev/null +++ b/mudu_kernel/src/storage/page/record_slot.rs @@ -0,0 +1,250 @@ +use byteorder::{ByteOrder, LittleEndian}; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use std::cmp::Ordering; + +pub const RECORD_SLOT_SIZE: usize = 32; + +/// The low 2 bits of `flag` describe how the payload fragment should be +/// interpreted inside the logical record stream. +pub const RECORD_SLOT_FLAG_FRAGMENT_MASK: u16 = 0b11; +/// The slot contains a complete record payload. +pub const RECORD_SLOT_FLAG_COMPLETE: u16 = 0; +/// The slot contains a partial record and more fragments continue in the next page. +pub const RECORD_SLOT_FLAG_PARTIAL_CONTINUED: u16 = 1; +/// The slot contains the last fragment of an incomplete record. +pub const RECORD_SLOT_FLAG_PARTIAL_LAST: u16 = 2; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct RecordSlot { + offset: u32, + size: u32, + timestamp: u64, + tuple_id: u64, + checksum: u16, + flag: u16, + reserved: u32, +} + +impl RecordSlot { + pub fn new(offset: u32, size: u32, timestamp: u64, tuple_id: u64) -> Self { + Self { + offset, + size, + timestamp, + tuple_id, + checksum: 0, + flag: RECORD_SLOT_FLAG_COMPLETE, + reserved: 0, + } + } + + pub(crate) fn from_raw( + offset: u32, + size: u32, + timestamp: u64, + tuple_id: u64, + check_sum: u16, + flag: u16, + reserved: u32, + ) -> Self { + Self { + offset, + size, + timestamp, + tuple_id, + checksum: check_sum, + flag, + reserved, + } + } + + pub fn decode(input: &[u8]) -> RS { + if input.len() < RECORD_SLOT_SIZE { + return Err(m_error!( + EC::DecodeErr, + format!( + "record slot requires {} bytes, got {}", + RECORD_SLOT_SIZE, + input.len() + ) + )); + } + + Ok(Self { + offset: LittleEndian::read_u32(&input[0..4]), + size: LittleEndian::read_u32(&input[4..8]), + timestamp: LittleEndian::read_u64(&input[8..16]), + tuple_id: LittleEndian::read_u64(&input[16..24]), + checksum: LittleEndian::read_u16(&input[24..26]), + flag: LittleEndian::read_u16(&input[26..28]), + reserved: LittleEndian::read_u32(&input[28..32]), + }) + } + + pub fn encode(&self, out: &mut [u8]) -> RS<()> { + if out.len() < RECORD_SLOT_SIZE { + return Err(m_error!( + EC::EncodeErr, + format!( + "record slot encode requires {} bytes, got {}", + RECORD_SLOT_SIZE, + out.len() + ) + )); + } + + LittleEndian::write_u32(&mut out[0..4], self.offset); + LittleEndian::write_u32(&mut out[4..8], self.size); + LittleEndian::write_u64(&mut out[8..16], self.timestamp); + LittleEndian::write_u64(&mut out[16..24], self.tuple_id); + LittleEndian::write_u16(&mut out[24..26], self.checksum); + LittleEndian::write_u16(&mut out[26..28], self.flag); + LittleEndian::write_u32(&mut out[28..32], self.reserved); + Ok(()) + } + + pub fn cmp_key(&self, other: &Self) -> Ordering { + self.timestamp + .cmp(&other.timestamp) + .then_with(|| self.tuple_id.cmp(&other.tuple_id)) + .then_with(|| self.offset.cmp(&other.offset)) + } + + pub fn fragment_kind(&self) -> u16 { + self.flag & RECORD_SLOT_FLAG_FRAGMENT_MASK + } + + pub fn is_complete(&self) -> bool { + self.fragment_kind() == RECORD_SLOT_FLAG_COMPLETE + } + + pub fn is_partial_continued(&self) -> bool { + self.fragment_kind() == RECORD_SLOT_FLAG_PARTIAL_CONTINUED + } + + pub fn is_partial_last(&self) -> bool { + self.fragment_kind() == RECORD_SLOT_FLAG_PARTIAL_LAST + } + + pub fn offset(&self) -> u32 { + self.offset + } + + pub fn size(&self) -> u32 { + self.size + } + + pub fn timestamp(&self) -> u64 { + self.timestamp + } + + pub fn tuple_id(&self) -> u64 { + self.tuple_id + } + + pub fn check_sum(&self) -> u16 { + self.checksum + } + + pub fn flag(&self) -> u16 { + self.flag + } + + pub fn reserved(&self) -> u32 { + self.reserved + } + + pub fn set_offset(&mut self, offset: u32) { + self.offset = offset; + } + + pub fn set_size(&mut self, size: u32) { + self.size = size; + } + + pub fn set_timestamp(&mut self, timestamp: u64) { + self.timestamp = timestamp; + } + + pub fn set_tuple_id(&mut self, tuple_id: u64) { + self.tuple_id = tuple_id; + } + + pub fn set_check_sum(&mut self, check_sum: u16) { + self.checksum = check_sum; + } + + pub fn set_flag(&mut self, flag: u16) { + self.flag = flag; + } + + pub fn set_fragment_kind(&mut self, fragment_kind: u16) { + self.flag = (self.flag & !RECORD_SLOT_FLAG_FRAGMENT_MASK) + | (fragment_kind & RECORD_SLOT_FLAG_FRAGMENT_MASK); + } + + pub fn set_reserved(&mut self, reserved: u32) { + self.reserved = reserved; + } +} + +#[cfg(test)] +mod tests { + use super::{ + RecordSlot, RECORD_SLOT_FLAG_COMPLETE, RECORD_SLOT_FLAG_PARTIAL_CONTINUED, + RECORD_SLOT_FLAG_PARTIAL_LAST, RECORD_SLOT_SIZE, + }; + use crate::storage::page::record_slot_ref::RecordSlotRef; + + #[test] + fn record_slot_roundtrip() { + let mut slot = RecordSlot::new(64, 23, 17, 29); + slot.set_check_sum(31); + slot.set_flag(37); + slot.set_reserved(41); + + let mut encoded = [0u8; RECORD_SLOT_SIZE]; + slot.encode(&mut encoded).unwrap(); + let decoded = RecordSlot::decode(&encoded).unwrap(); + assert_eq!(decoded, slot); + } + + #[test] + fn record_slot_low_two_flag_bits_describe_fragment_kind() { + let mut slot = RecordSlot::new(64, 23, 17, 29); + slot.set_flag(0b1111_0000); + slot.set_fragment_kind(RECORD_SLOT_FLAG_COMPLETE); + assert!(slot.is_complete()); + assert_eq!(slot.flag(), 0b1111_0000); + + slot.set_fragment_kind(RECORD_SLOT_FLAG_PARTIAL_CONTINUED); + assert!(slot.is_partial_continued()); + assert_eq!(slot.flag(), 0b1111_0001); + + slot.set_fragment_kind(RECORD_SLOT_FLAG_PARTIAL_LAST); + assert!(slot.is_partial_last()); + assert_eq!(slot.flag(), 0b1111_0010); + } + + #[test] + fn record_slot_ref_reads_fields_without_full_decode() { + let mut slot = RecordSlot::new(64, 23, 17, 29); + slot.set_check_sum(31); + slot.set_flag(37); + slot.set_reserved(41); + + let mut encoded = [0u8; RECORD_SLOT_SIZE]; + slot.encode(&mut encoded).unwrap(); + let slot_ref = RecordSlotRef::new(&encoded).unwrap(); + assert_eq!(slot_ref.offset(), 64); + assert_eq!(slot_ref.size(), 23); + assert_eq!(slot_ref.timestamp(), 17); + assert_eq!(slot_ref.tuple_id(), 29); + assert_eq!(slot_ref.checksum(), 31); + assert_eq!(slot_ref.flag(), 37); + assert_eq!(slot_ref.reserved(), 41); + assert_eq!(slot_ref.to_owned(), slot); + } +} diff --git a/mudu_kernel/src/storage/page/record_slot_ref.rs b/mudu_kernel/src/storage/page/record_slot_ref.rs new file mode 100644 index 0000000..ad04e66 --- /dev/null +++ b/mudu_kernel/src/storage/page/record_slot_ref.rs @@ -0,0 +1,96 @@ +use crate::storage::page::record_slot::{ + RecordSlot, RECORD_SLOT_FLAG_COMPLETE, RECORD_SLOT_FLAG_FRAGMENT_MASK, + RECORD_SLOT_FLAG_PARTIAL_CONTINUED, RECORD_SLOT_FLAG_PARTIAL_LAST, RECORD_SLOT_SIZE, +}; +use byteorder::{ByteOrder, LittleEndian}; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use std::cmp::Ordering; + +/// `RecordSlotRef` is a zero-copy slot reader backed by the original page bytes. +/// It is used on hot paths such as slot binary search where only a subset of +/// fields is needed and decoding the whole slot array would be wasted work. +pub struct RecordSlotRef<'a> { + slot: &'a [u8], +} + +impl<'a> RecordSlotRef<'a> { + pub fn new(slot: &'a [u8]) -> RS { + if slot.len() < RECORD_SLOT_SIZE { + return Err(m_error!( + EC::DecodeErr, + format!( + "record slot requires {} bytes, got {}", + RECORD_SLOT_SIZE, + slot.len() + ) + )); + } + + Ok(Self { slot }) + } + + pub fn offset(&self) -> u32 { + LittleEndian::read_u32(&self.slot[0..4]) + } + + pub fn size(&self) -> u32 { + LittleEndian::read_u32(&self.slot[4..8]) + } + + pub fn timestamp(&self) -> u64 { + LittleEndian::read_u64(&self.slot[8..16]) + } + + pub fn tuple_id(&self) -> u64 { + LittleEndian::read_u64(&self.slot[16..24]) + } + + pub fn checksum(&self) -> u16 { + LittleEndian::read_u16(&self.slot[24..26]) + } + + pub fn flag(&self) -> u16 { + LittleEndian::read_u16(&self.slot[26..28]) + } + + pub fn reserved(&self) -> u32 { + LittleEndian::read_u32(&self.slot[28..32]) + } + + pub fn fragment_kind(&self) -> u16 { + self.flag() & RECORD_SLOT_FLAG_FRAGMENT_MASK + } + + pub fn is_complete(&self) -> bool { + self.fragment_kind() == RECORD_SLOT_FLAG_COMPLETE + } + + pub fn is_partial_continued(&self) -> bool { + self.fragment_kind() == RECORD_SLOT_FLAG_PARTIAL_CONTINUED + } + + pub fn is_partial_last(&self) -> bool { + self.fragment_kind() == RECORD_SLOT_FLAG_PARTIAL_LAST + } + + pub fn cmp_key(&self, other: &RecordSlot) -> Ordering { + self.timestamp() + .cmp(&other.timestamp()) + .then_with(|| self.tuple_id().cmp(&other.tuple_id())) + .then_with(|| self.offset().cmp(&other.offset())) + } + + pub fn to_owned(&self) -> RecordSlot { + RecordSlot::from_raw( + self.offset(), + self.size(), + self.timestamp(), + self.tuple_id(), + self.checksum(), + self.flag(), + self.reserved(), + ) + } +} diff --git a/mudu_kernel/src/storage/page_block.rs b/mudu_kernel/src/storage/page_block.rs deleted file mode 100644 index c248526..0000000 --- a/mudu_kernel/src/storage/page_block.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::storage::constant; -use crate::storage::constant::PAGE_TAIL_SIZE; -use mudu::common::endian; -use std::ops::Range; - -pub struct PageBlock { - data: Vec, -} - -pub struct PageUpdate { - page_id: u64, - offset: u64, - update_data: Vec, -} - -impl PageUpdate { - pub fn new(page_id: u64, offset: u64, update_data: Vec) -> PageUpdate { - Self { - page_id, - offset, - update_data, - } - } - - pub fn page_id(&self) -> u64 { - self.page_id - } - - pub fn offset(&self) -> u64 { - self.offset - } - - pub fn update_data(&self) -> &[u8] { - &self.update_data - } -} - -impl PageBlock { - pub fn new_empty(page_size: u64) -> PageBlock { - Self { - data: vec![0; page_size as usize], - } - } - - pub fn new(data: Vec) -> PageBlock { - Self { data } - } - - pub fn get_page_id(&self) -> u64 { - endian::read_u64(&self.data[constant::page_offset_range_page_id()]) - } - - pub fn get_lsn(&self) -> u64 { - endian::read_u64(&self.data[constant::page_offset_range_lsn()]) - } - - pub fn set_page_id(&mut self, page_id: u64) { - endian::write_u64( - &mut self.data[constant::page_offset_range_page_id()], - page_id, - ); - } - - pub fn set_lsn(&mut self, lsn: u64) { - endian::write_u64(&mut self.data[constant::page_offset_range_lsn()], lsn); - } - - pub fn update_checksum(&self) {} - pub fn update_set_lsn(&mut self, lsn: u64) -> PageUpdate { - let range = constant::page_offset_range_lsn(); - let page_id = self.get_page_id(); - let offset = range.start as _; - let update_data = &mut self.data[range]; - endian::write_u64(update_data, lsn); - PageUpdate { - page_id, - offset, - update_data: update_data.to_vec(), - } - } - - pub fn set_checksum(&mut self, checksum: u64) { - let range = self.range_checksum(); - endian::write_u64(&mut self.data[range], checksum); - } - - pub fn get_checksum(&self) -> Checksum { - let range = self.range_checksum(); - endian::read_u64(&self.data[range]) - } - - pub fn range_checksum(&self) -> Range { - self.data.len() - size_of::()..self.data.len() - } - - pub fn block(&self) -> &[u8] { - if self.data.is_empty() { - println!("Empty page block"); - } - &self.data - } - - pub fn block_mut(&mut self) -> &mut [u8] { - if self.data.is_empty() { - panic!("Empty page block"); - } - &mut self.data - } - - pub fn len(&self) -> usize { - self.data.len() - } - - pub fn set_block(&mut self, block: &mut PageBlock) { - self.swap(block); - } - - pub fn use_block(&mut self) -> Vec { - let mut vec = Vec::new(); - std::mem::swap(&mut self.data, &mut vec); - vec - } - - pub fn swap(&mut self, new_block: &mut PageBlock) { - std::mem::swap(&mut self.data, &mut new_block.data); - } - - pub fn copy_block(&mut self, block: &mut PageBlock) { - block.data.resize(self.data.len(), 0); - block.data.copy_from_slice(&self.data); - } - - pub fn update_block(&mut self, block: &mut PageBlock) {} - - pub fn reset_checksum(&mut self) { - let checksum = self.calculate_checksum(); - self.set_checksum(checksum); - } - - fn calculate_checksum(&self) -> Checksum { - self.data[0..self.range_checksum().start as _] - .iter() - .fold(0u64, |acc, &byte| acc.wrapping_add(byte as u64)) - } -} - -pub type Checksum = u64; diff --git a/mudu_kernel/src/storage/page_id.rs b/mudu_kernel/src/storage/page_id.rs deleted file mode 100644 index bae0e5b..0000000 --- a/mudu_kernel/src/storage/page_id.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub type PageId = u64; - -pub const INVALID_PAGE_ID: u64 = u64::MAX; -pub const INVALID_FILE_ID: u64 = u64::MAX; diff --git a/mudu_kernel/src/storage/page_index.rs b/mudu_kernel/src/storage/page_index.rs deleted file mode 100644 index afd0df6..0000000 --- a/mudu_kernel/src/storage/page_index.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::storage::page_id; -use serde::{Deserialize, Serialize}; -use std::hash::{Hash, Hasher}; - -#[derive(Clone, Serialize, Deserialize)] -pub struct PageIndex { - pub file_id: u64, - pub page_id: u64, -} - -impl PageIndex { - pub fn invalid_index() -> Self { - Self { - file_id: page_id::INVALID_FILE_ID, - page_id: page_id::INVALID_PAGE_ID, - } - } - - pub fn new(file_id: u64, page_id: u64) -> PageIndex { - Self { file_id, page_id } - } -} - -impl PartialEq for PageIndex { - fn eq(&self, other: &Self) -> bool { - self.file_id == other.file_id && self.page_id == other.page_id - } -} - -impl Eq for PageIndex {} - -impl Hash for PageIndex { - fn hash(&self, state: &mut H) { - self.file_id.hash(state); - self.page_id.hash(state); - } -} diff --git a/mudu_kernel/src/storage/pst_store_impl.rs b/mudu_kernel/src/storage/pst_store_impl.rs index f73c7a0..298cdcf 100644 --- a/mudu_kernel/src/storage/pst_store_impl.rs +++ b/mudu_kernel/src/storage/pst_store_impl.rs @@ -181,7 +181,8 @@ impl PersistedStore { } let text = fs::read_to_string(path) .map_err(|e| m_error!(ER::IOErr, "read pst store file error", e))?; - serde_json::from_str(&text).map_err(|e| m_error!(ER::ParseErr, "parse pst store file error", e)) + serde_json::from_str(&text) + .map_err(|e| m_error!(ER::ParseErr, "parse pst store file error", e)) } fn save(&self, path: &Path) -> RS<()> { diff --git a/mudu_kernel/src/storage/relation/mod.rs b/mudu_kernel/src/storage/relation/mod.rs new file mode 100644 index 0000000..1d8f1f1 --- /dev/null +++ b/mudu_kernel/src/storage/relation/mod.rs @@ -0,0 +1 @@ +pub mod relation; diff --git a/mudu_kernel/src/storage/relation/relation.rs b/mudu_kernel/src/storage/relation/relation.rs new file mode 100644 index 0000000..e052526 --- /dev/null +++ b/mudu_kernel/src/storage/relation/relation.rs @@ -0,0 +1,428 @@ +use std::ops::Bound; +use std::sync::Mutex; + +use mudu::common::id::{TupleID, OID}; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use mudu_contract::tuple::comparator::TupleComparator; + +use crate::contract::data_row::DataRow; +use crate::contract::snapshot::Snapshot; +use crate::contract::table_desc::TableDesc; +use crate::contract::timestamp::Timestamp; +use crate::contract::version_tuple::VersionTuple; +use crate::index::btree::btree_index::BTreeIndex; +use crate::index::index_key::compare_context::CompareContext; +use crate::index::index_key::key_tuple::KeyTuple; +use crate::server::worker_snapshot::WorkerSnapshot; +use crate::storage::time_series::time_series_file::{TimeSeriesFile, TimeSeriesFileIdentity}; + +// Relation WAL does not use string file kinds. The relation layer alone owns +// the mapping from logical role to numeric file index. +const KEY_FILE_INDEX: u32 = 0; +const VALUE_FILE_INDEX: u32 = 1; + +pub struct Relation { + inner: Mutex, +} + +struct RelationInner { + _table_id: OID, + _partition_id: OID, + index: BTreeIndex, + key_file: TimeSeriesFile, + value_file: TimeSeriesFile, + next_tuple_id: TupleID, +} + +impl Relation { + pub fn new(table_id: OID, partition_id: OID, path: String, table_desc: &TableDesc) -> Self { + Self { + inner: Mutex::new(RelationInner::new(table_id, partition_id, path, table_desc)), + } + } + + pub fn has_visible_version(&self, key: &KeyTuple, snapshot: &WorkerSnapshot) -> RS { + Ok(self.lock_inner()?.visible_meta(key, snapshot)?.is_some()) + } + + pub fn visible_value(&self, key: &KeyTuple, snapshot: &WorkerSnapshot) -> RS>> { + self.lock_inner()?.visible_value(key, snapshot) + } + + pub fn visible_range( + &self, + bounds: (Bound<&[u8]>, Bound<&[u8]>), + snapshot: &WorkerSnapshot, + ) -> RS, Vec)>> { + self.lock_inner()?.visible_range(bounds, snapshot) + } + + pub fn has_write_conflict(&self, key: &KeyTuple, snapshot: &WorkerSnapshot) -> RS { + self.lock_inner()?.has_write_conflict(key, snapshot) + } + + pub fn write_value(&self, key: Vec, value: Vec, xid: u64) -> RS<()> { + self.lock_inner()?.write_row(key, Some(value), xid) + } + + pub fn write_delete(&self, key: Vec, xid: u64) -> RS<()> { + self.lock_inner()?.write_row(key, None, xid) + } + + pub fn write_row(&self, key: Vec, value: Option>, xid: u64) -> RS<()> { + self.lock_inner()?.write_row(key, value, xid) + } + + fn lock_inner(&self) -> RS> { + self.inner + .lock() + .map_err(|_| m_error!(EC::InternalErr, "relation lock poisoned")) + } +} + +impl RelationInner { + fn new(table_id: OID, partition_id: OID, path: String, table_desc: &TableDesc) -> Self { + let key_identity = TimeSeriesFileIdentity { + partition_id, + table_id, + file_index: KEY_FILE_INDEX, + }; + let value_identity = TimeSeriesFileIdentity { + partition_id, + table_id, + file_index: VALUE_FILE_INDEX, + }; + + let mut relation = Self { + _table_id: table_id, + _partition_id: partition_id, + index: BTreeIndex::new(CompareContext { + result: Ok(()), + comparator: TupleComparator::new(), + desc: table_desc.key_desc().clone(), + }), + key_file: TimeSeriesFile::open_relation_file_sync(&path, key_identity, true) + .unwrap_or_else(|e| panic!("open relation key file failed: {e}")), + value_file: TimeSeriesFile::open_relation_file_sync(&path, value_identity, true) + .unwrap_or_else(|e| panic!("open relation value file failed: {e}")), + next_tuple_id: 1, + }; + relation + .rebuild_from_files() + .unwrap_or_else(|e| panic!("rebuild relation from files failed: {e}")); + relation + } + + fn rebuild_from_files(&mut self) -> RS<()> { + let rows = self.key_file.scan_range_sync(0, u64::MAX)?; + let mut max_tuple_id = 0; + + for key_row in rows { + let tuple_id = key_row.tuple_id as TupleID; + max_tuple_id = max_tuple_id.max(tuple_id); + + let key_tuple = KeyTuple::from(key_row.payload.clone()); + let row = match self.index.get(&key_tuple)?.cloned() { + Some(row) => { + let existing_tuple_id = row + .tuple_id_sync()? + .ok_or_else(|| m_error!(EC::InternalErr, "missing tuple id"))?; + if existing_tuple_id as u64 != key_row.tuple_id { + return Err(m_error!( + EC::DecodeErr, + format!( + "tuple id mismatch for key rebuild: key={:?} existing={} file={}", + key_tuple.as_slice(), + existing_tuple_id, + key_row.tuple_id + ) + )); + } + row + } + None => DataRow::new(tuple_id), + }; + + let timestamp = Timestamp::new(key_row.timestamp, u64::MAX); + let version = match self + .value_file + .get_sync(key_row.timestamp, key_row.tuple_id)? + { + Some(_) => VersionTuple::new(timestamp, Vec::new()), + None => VersionTuple::new_delete(timestamp), + }; + row.write_sync(version, None)?; + let _ = self.index.insert(key_tuple, row)?; + } + + self.next_tuple_id = max_tuple_id.saturating_add(1).max(1); + Ok(()) + } + + fn visible_meta( + &self, + key: &KeyTuple, + snapshot: &WorkerSnapshot, + ) -> RS> { + let row = match self.index.get(key)? { + Some(row) => row, + None => return Ok(None), + }; + let tuple_id = row + .tuple_id_sync()? + .ok_or_else(|| m_error!(EC::InternalErr, "missing tuple id"))?; + let snapshot = snapshot.to_snapshot(); + Ok(read_visible_version(row, &snapshot) + .filter(|version| !version.is_deleted()) + .map(|version| (tuple_id, version))) + } + + fn visible_value(&self, key: &KeyTuple, snapshot: &WorkerSnapshot) -> RS>> { + let Some((tuple_id, version)) = self.visible_meta(key, snapshot)? else { + return Ok(None); + }; + self.read_value_payload(version.timestamp().c_min(), tuple_id) + .map(Some) + } + + fn visible_range( + &self, + bounds: (Bound<&[u8]>, Bound<&[u8]>), + snapshot: &WorkerSnapshot, + ) -> RS, Vec)>> { + let begin_key = bounds.0.as_ref().map(|key| KeyTuple::from(key.to_vec())); + let end_key = bounds.1.as_ref().map(|key| KeyTuple::from(key.to_vec())); + let rows = self + .index + .range((bound_key_ref(&begin_key), bound_key_ref(&end_key)))?; + + rows.into_iter() + .filter_map(|(_key, row)| { + let snapshot = snapshot.to_snapshot(); + match visible_payloads(&self.key_file, &self.value_file, row, &snapshot) { + Ok(Some(pair)) => Some(Ok(pair)), + Ok(None) => None, + Err(err) => Some(Err(err)), + } + }) + .collect() + } + + fn has_write_conflict(&self, key: &KeyTuple, snapshot: &WorkerSnapshot) -> RS { + Ok(self + .index + .get(key)? + .and_then(latest_version) + .map(|latest| !snapshot.is_visible(latest.timestamp().c_min())) + .unwrap_or(false)) + } + + fn write_row(&mut self, key: Vec, value: Option>, xid: u64) -> RS<()> { + let key_tuple = KeyTuple::from(key.clone()); + let row = match self.index.get(&key_tuple)?.cloned() { + Some(row) => row, + None => { + let tuple_id = self.alloc_tuple_id(); + DataRow::new(tuple_id as u64) + } + }; + + let tuple_id = row + .tuple_id_sync()? + .ok_or_else(|| m_error!(EC::InternalErr, "missing tuple id"))?; + let timestamp = Timestamp::new(xid, u64::MAX); + self.key_file + .insert_sync(timestamp.c_min(), tuple_id as u64, &key)?; + if let Some(value) = value.as_ref() { + self.value_file + .insert_sync(timestamp.c_min(), tuple_id as u64, value)?; + } + + let version = match value { + Some(_) => VersionTuple::new(timestamp, Vec::new()), + None => VersionTuple::new_delete(timestamp), + }; + row.write_sync(version, None)?; + let _ = self.index.insert(key_tuple, row)?; + Ok(()) + } + + fn alloc_tuple_id(&mut self) -> TupleID { + let tuple_id = self.next_tuple_id; + self.next_tuple_id += 1; + tuple_id + } + + fn read_value_payload(&self, timestamp: u64, tuple_id: OID) -> RS> { + self.value_file + .get_sync(timestamp, tuple_id as u64)? + .map(|record| record.payload) + .ok_or_else(|| { + m_error!( + EC::NoSuchElement, + format!("missing value payload ts={timestamp} tuple_id={tuple_id}") + ) + }) + } +} + +#[cfg(test)] +mod tests { + use std::env::temp_dir; + + use mudu_type::dat_type_id::DatTypeID; + use mudu_type::dt_info::DTInfo; + + use crate::contract::schema_column::SchemaColumn; + use crate::contract::schema_table::SchemaTable; + use crate::contract::table_info::TableInfo; + use crate::server::worker_snapshot::WorkerSnapshot; + + use super::*; + + fn test_schema() -> SchemaTable { + SchemaTable::new( + "t".to_string(), + vec![SchemaColumn::new( + "id".to_string(), + DatTypeID::I32, + DTInfo::from_text(DatTypeID::I32, String::new()), + )], + vec![SchemaColumn::new( + "v".to_string(), + DatTypeID::I32, + DTInfo::from_text(DatTypeID::I32, String::new()), + )], + ) + } + + fn relation_path() -> String { + temp_dir() + .join(format!("relation_rebuild_{}", mudu::common::id::gen_oid())) + .to_string_lossy() + .to_string() + } + + fn i32_bytes(v: i32) -> Vec { + v.to_be_bytes().to_vec() + } + + #[test] + fn rebuilds_index_and_next_tuple_id_from_relation_files() { + let schema = test_schema(); + let table_desc = TableInfo::new(schema.clone()) + .unwrap() + .table_desc() + .unwrap(); + let table_id = schema.id(); + let partition_id = 7; + let path = relation_path(); + + let relation = Relation::new(table_id, partition_id, path.clone(), table_desc.as_ref()); + relation + .write_value(i32_bytes(1), i32_bytes(11), 1) + .unwrap(); + relation.write_delete(i32_bytes(1), 2).unwrap(); + relation + .write_value(i32_bytes(2), i32_bytes(22), 3) + .unwrap(); + drop(relation); + + let reopened = Relation::new(table_id, partition_id, path.clone(), table_desc.as_ref()); + assert_eq!( + reopened + .visible_value( + &KeyTuple::from(i32_bytes(1)), + &WorkerSnapshot::new(1, vec![]) + ) + .unwrap(), + Some(i32_bytes(11)) + ); + assert_eq!( + reopened + .visible_value( + &KeyTuple::from(i32_bytes(1)), + &WorkerSnapshot::new(2, vec![]) + ) + .unwrap(), + None + ); + assert_eq!( + reopened + .visible_value( + &KeyTuple::from(i32_bytes(2)), + &WorkerSnapshot::new(3, vec![]) + ) + .unwrap(), + Some(i32_bytes(22)) + ); + + reopened + .write_value(i32_bytes(3), i32_bytes(33), 4) + .unwrap(); + let key_file = TimeSeriesFile::open_ts_file_sync( + TimeSeriesFile::relation_file_path(&path, partition_id, table_id, 0), + false, + ) + .unwrap(); + let rows = key_file.scan_range_sync(0, u64::MAX).unwrap(); + let k3_row = rows + .into_iter() + .find(|row| row.timestamp == 4 && row.payload == i32_bytes(3)) + .unwrap(); + assert_eq!(k3_row.tuple_id, 3); + } +} + +fn visible_payloads( + key_file: &TimeSeriesFile, + value_file: &TimeSeriesFile, + row: &DataRow, + snapshot: &Snapshot, +) -> RS, Vec)>> { + let tuple_id = row + .tuple_id_sync()? + .ok_or_else(|| m_error!(EC::InternalErr, "missing tuple id"))?; + let Some(version) = read_visible_version(row, snapshot).filter(|version| !version.is_deleted()) + else { + return Ok(None); + }; + let ts = version.timestamp().c_min(); + let key = key_file + .get_sync(ts, tuple_id as u64)? + .map(|record| record.payload) + .ok_or_else(|| { + m_error!( + EC::NoSuchElement, + format!("missing key payload ts={ts} tuple_id={tuple_id}") + ) + })?; + let value = value_file + .get_sync(ts, tuple_id as u64)? + .map(|record| record.payload) + .ok_or_else(|| { + m_error!( + EC::NoSuchElement, + format!("missing value payload ts={ts} tuple_id={tuple_id}") + ) + })?; + Ok(Some((key, value))) +} + +fn latest_version(row: &DataRow) -> Option { + row.read_latest_sync().ok().flatten() +} + +fn read_visible_version(row: &DataRow, snapshot: &Snapshot) -> Option { + row.read_sync(snapshot).ok().flatten() +} + +fn bound_key_ref(bound: &Bound) -> Bound<&KeyTuple> { + match bound { + Bound::Included(key) => Bound::Included(key), + Bound::Excluded(key) => Bound::Excluded(key), + Bound::Unbounded => Bound::Unbounded, + } +} diff --git a/mudu_kernel/src/storage/space_manager.rs b/mudu_kernel/src/storage/space_manager.rs deleted file mode 100644 index 1340a45..0000000 --- a/mudu_kernel/src/storage/space_manager.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::storage::storage_cfg::StorageCfg; -use crate::storage::table_space::TableSpace; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use scc::HashMap; - -pub struct SpaceManager { - tablespaces: HashMap, - base_path: String, -} - -impl SpaceManager { - pub fn new(cfg: &StorageCfg) -> RS { - std::fs::create_dir_all(&cfg.path) - .map_err(|e| m_error!(EC::IOErr, "create dir error", e))?; - Ok(Self { - tablespaces: HashMap::new(), - base_path: cfg.path.clone(), - }) - } - - /// Get the new tablespace - pub fn get_tablespace(&self, id: u64) -> RS { - let opt = self.tablespaces.get_sync(&id); - match opt { - Some(tablespace) => Ok(tablespace.clone()), - None => { - let table_space = TableSpace::new(id, self.base_path.clone())?; - self.tablespaces.insert_sync(id, table_space.clone()); - Ok(table_space) - } - } - } -} diff --git a/mudu_kernel/src/storage/storage_cfg.rs b/mudu_kernel/src/storage/storage_cfg.rs deleted file mode 100644 index bfeebb5..0000000 --- a/mudu_kernel/src/storage/storage_cfg.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[derive(Clone)] -pub struct StorageCfg { - pub buffer_num_pages: u64, - pub page_size: u64, - pub extent_pages: u64, - pub path: String, -} diff --git a/mudu_kernel/src/storage/storage_context.rs b/mudu_kernel/src/storage/storage_context.rs deleted file mode 100644 index 932b00c..0000000 --- a/mudu_kernel/src/storage/storage_context.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::storage::bufer_manager::BufferManager; -use crate::storage::data_file::DataFile; -use crate::storage::extent::Extent; -use crate::storage::log_entry::LogEntry; -use crate::storage::page_index::PageIndex; -use crate::storage::space_manager::SpaceManager; -use crate::storage::storage_cfg::StorageCfg; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use scc::HashMap; -use std::sync::Arc; - -#[derive(Clone)] -pub struct StorageContext { - inner: Arc, -} - -impl StorageContext { - pub fn new(cfg: &StorageCfg) -> RS { - Ok(Self { - inner: Arc::new(ContextInner::new(cfg)?), - }) - } - - pub fn extent_insert(&self, page_index: PageIndex, extent: Extent) -> RS<()> { - self.inner.extent_insert(page_index, extent) - } - - pub fn extent_get(&self, page_index: &PageIndex) -> Option { - self.inner.extent_get(page_index) - } - - pub fn file_get(&self, data_file_id: u64) -> Option { - self.inner.file_get(data_file_id) - } - - pub fn file_insert(&self, data_file_id: u64, data_file: DataFile) -> RS<()> { - self.inner.file_insert(data_file_id, data_file) - } - pub fn append_log_entry(&self, _log: LogEntry) { - todo!() - } - - pub fn buffer_manager(&self) -> &BufferManager { - self.inner.buffer_manager() - } - - pub fn cfg(&self) -> &StorageCfg { - &self.inner.cfg - } -} - -impl ContextInner { - pub fn new(cfg: &StorageCfg) -> RS { - let buffer_manager = BufferManager::new(cfg)?; - let space_manager = SpaceManager::new(cfg)?; - Ok(Self { - cfg: cfg.clone(), - extent: Default::default(), - data_files: Default::default(), - buffer_manager, - space_manager, - }) - } - - pub fn extent_insert(&self, page_index: PageIndex, extent: Extent) -> RS<()> { - self.extent - .insert_sync(page_index, extent) - .map_err(|_| m_error!(EC::StorageErr, "exsiting such extent")) - } - - pub fn extent_get(&self, page_index: &PageIndex) -> Option { - self.extent.get_sync(page_index).map(|x| x.clone()) - } - - fn file_insert(&self, data_file_id: u64, data_file: DataFile) -> RS<()> { - self.data_files.insert_sync(data_file_id, data_file); - Ok(()) - } - - fn file_get(&self, data_file_id: u64) -> Option { - self.data_files.get_sync(&data_file_id).map(|x| x.clone()) - } - - pub fn buffer_manager(&self) -> &BufferManager { - &self.buffer_manager - } -} - -pub struct ContextInner { - cfg: StorageCfg, - extent: HashMap, - data_files: HashMap, - buffer_manager: BufferManager, - space_manager: SpaceManager, -} diff --git a/mudu_kernel/src/storage/table_space.rs b/mudu_kernel/src/storage/table_space.rs deleted file mode 100644 index 7588012..0000000 --- a/mudu_kernel/src/storage/table_space.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::storage::data_file::DataFile; -use crate::storage::extent::Extent; -use crate::storage::page_index::PageIndex; -use crate::storage::storage_context::StorageContext; -use mcslock::raw::Mutex; -use mcslock::relax::Spin; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use mudu::utils::json::to_json_str; -use serde::{Deserialize, Serialize}; -use std::fs; -use std::path::PathBuf; -use std::sync::Arc; - -#[derive(Clone)] -pub struct TableSpace { - inner: Arc, -} - -#[derive(Serialize, Deserialize)] -struct TableExtent { - table_id: u64, - extent: Vec, -} - -struct TableSpaceInner { - table_id: u64, - name: String, - path: String, - extent_index: Mutex, Spin>, -} - -impl TableSpace { - pub fn new(id: u64, path: String) -> RS { - Ok(Self { - inner: Arc::new(TableSpaceInner::new(id, path)?), - }) - } - - pub fn allocate_page(&self, ctx: &StorageContext) -> RS { - self.inner.allocate_page(ctx) - } -} - -impl TableSpaceInner { - fn flush(&self) -> RS<()> { - let table_extent = self.to_table_extent(); - let path_buf = PathBuf::from(&self.path).join(self.table_id.to_string()); - let json = to_json_str(&table_extent)?; - fs::write(path_buf, json) - .map_err(|e| m_error!(EC::IOErr, "save table space extent error"))?; - Ok(()) - } - - fn new(id: u64, path: String) -> RS { - let path_buf = PathBuf::from(&path); - if path_buf.exists() { - if !path_buf.is_dir() { - return Err(m_error!( - EC::IOErr, - format!("{:?} is not a directory", path_buf) - )); - } - } else { - fs::create_dir_all(&path_buf).map_err(|e| m_error!(EC::IOErr, "create dir error"))?; - } - Ok(Self { - table_id: id, - name: "".to_string(), - extent_index: Mutex::new(vec![]), - path, - }) - } - - fn table_space_create_extent(&self, ctx: &StorageContext) -> RS { - let id = DataFile::current_file_id(); - let opt_data_file = ctx.file_get(DataFile::current_file_id()); - let data_file = match opt_data_file { - Some(file) => file, - None => { - let file = DataFile::open(ctx.cfg())?; - file - } - }; - let extent = data_file.file_create_extent(self.table_id, ctx)?; - Ok(extent) - } - - fn allocate_page(&self, ctx: &StorageContext) -> RS { - let opt = match self.extent_index_last() { - Some(index) => ctx.extent_get(&index), - None => None, - }; - let extent = if let Some(extent) = opt { - extent - } else { - let extent = self.table_space_create_extent(ctx)?; - let page_index = PageIndex::new(extent.file_id(), extent.extent_id()); - self.extent_index_push(page_index); - extent - }; - let opt_page = extent.extent_allocate_page(); - let page_index = if let Some(page_id) = opt_page { - PageIndex::new(extent.file_id(), page_id) - } else { - let extent = self.table_space_create_extent(ctx)?; - let opt = extent.extent_allocate_page(); - if let Some(page_id) = opt { - PageIndex::new(extent.file_id(), page_id) - } else { - return Err(m_error!(EC::StorageErr, "cannot allocate page")); - } - }; - Ok(page_index) - } - - fn to_table_extent(&self) -> TableExtent { - TableExtent { - table_id: self.table_id, - extent: self.copy_extent_index(), - } - } - - fn copy_extent_index(&self) -> Vec { - self.extent_index.lock_then(|i| i.clone()) - } - - fn extent_index_push(&self, index: PageIndex) { - self.extent_index.lock_then(|i| i.push(index)) - } - - fn extent_index_last(&self) -> Option { - self.extent_index.lock_then(|i| i.last().cloned()) - } -} diff --git a/mudu_kernel/src/storage/test_pst_store.rs b/mudu_kernel/src/storage/test_pst_store.rs index 0ad938a..bb2599c 100644 --- a/mudu_kernel/src/storage/test_pst_store.rs +++ b/mudu_kernel/src/storage/test_pst_store.rs @@ -6,8 +6,6 @@ mod _test { use crate::contract::timestamp::Timestamp; use crate::storage::pst_store_factory::PstStoreFactory; use mudu::common::result::RS; - use mudu_utils::notifier::NotifyWait; - use mudu_utils::log::log_setup; use tokio::sync::oneshot; use tracing::{error, info}; @@ -20,7 +18,7 @@ mod _test { } fn _test_pst_store() -> RS<()> { - let db = format!("/tmp/test_pst_store_{}", uuid::Uuid::new_v4()); + let db = format!("/tmp/test_pst_store_{}", mudu_sys::random::uuid_v4()); let (task, ch) = PstStoreFactory::create(db).unwrap(); let thd_task = thread::Builder::new().spawn(move || { let r = task.run_once(); @@ -45,13 +43,11 @@ mod _test { ops.push_update(i, i, Timestamp::new(2, 3), Default::default()); ch.async_run(ops).unwrap(); - let notifier = NotifyWait::new(); let mut ops = PstOpList::new(); ops.push_delete(i, i); ch.async_run(ops).unwrap(); } let (s, r) = oneshot::channel(); - let notifier = NotifyWait::new(); let mut ops = PstOpList::new(); ops.push_stop(s); ch.async_run(ops).unwrap(); diff --git a/mudu_kernel/src/storage/time_series/mod.rs b/mudu_kernel/src/storage/time_series/mod.rs new file mode 100644 index 0000000..806c08a --- /dev/null +++ b/mudu_kernel/src/storage/time_series/mod.rs @@ -0,0 +1 @@ +pub mod time_series_file; diff --git a/mudu_kernel/src/storage/time_series/time_series_file.rs b/mudu_kernel/src/storage/time_series/time_series_file.rs new file mode 100644 index 0000000..d721eb8 --- /dev/null +++ b/mudu_kernel/src/storage/time_series/time_series_file.rs @@ -0,0 +1,1701 @@ +use crate::io::file; +use crate::io::file::IoFile; +use crate::io::worker_ring; +use crate::storage::page::page_block_ref::{PageBlockRef, PAGE_SIZE}; +use crate::storage::page::page_block_ref_mut::PageBlockRefMut; +use crate::storage::page::page_header::NONE_PAGE_ID; +use crate::storage::page::PageId; +use crate::wal::pl_batch::{new_pl_batch_writer, PLBatch}; +use crate::wal::pl_entry::{PLEntry, PLFileId, PLOp, PageUpdate}; +use crate::wal::worker_log::{ChunkedWorkerLogBackend, WorkerLogBackend, WorkerLogLayout}; +use mudu::common::id::OID; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use scc::HashMap; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; + +const FILE_MODE_644: u32 = 0o644; +const RELATION_WAL_CHUNK_SIZE: u64 = 256 * 1024; + +/// Logical identity for one physical time-series file. +/// +/// The relation layer assigns `file_index` values and WAL only works with this +/// numeric identity, never with `"key"` / `"value"` strings. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct TimeSeriesFileIdentity { + pub partition_id: OID, + pub table_id: OID, + pub file_index: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TimeSeriesRecord { + pub timestamp: u64, + pub tuple_id: u64, + pub payload: Vec, + pub page_id: PageId, + pub slot_index: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum PageInsertLocation { + Existing(PageId), + Before(PageId), + After(PageId), + EmptyFile, +} + +pub struct TimeSeriesFile { + // Relation-owned files carry a stable identity and a dedicated PL backend. + // Standalone test files leave both fields as `None`. + identity: Option, + path: PathBuf, + file: IoFile, + wal_backend: Option, + page_cache: HashMap>, + page_count: PageId, + head_page_id: Option, + tail_page_id: Option, +} + +#[derive(Clone)] +struct PlannedPageWrite { + page_id: PageId, + image: Vec, +} + +// A complete physical mutation to one file. The write path first builds this +// in memory, persists it as PL, and only then applies the page images. +#[derive(Clone, Default)] +struct TimeSeriesFileMutationPlan { + create_file: bool, + delete_file: bool, + page_writes: Vec, + next_page_count: Option, + next_head_page_id: Option>, + next_tail_page_id: Option>, +} + +impl TimeSeriesFile { + pub fn relation_file_path>( + base_path: P, + partition_id: OID, + table_id: OID, + file_index: u32, + ) -> PathBuf { + let mut path_buf = base_path.as_ref().to_path_buf(); + path_buf.push("relation"); + path_buf.push(format!("{partition_id}.{table_id}.{file_index}.dat")); + path_buf + } + + /// Opens a relation-owned time-series file and replays its dedicated PL + /// stream before any file state is observed. + pub async fn open_relation_file>( + base_path: P, + identity: TimeSeriesFileIdentity, + create_if_missing: bool, + ) -> RS { + let base_path = base_path.as_ref().to_path_buf(); + let path = Self::relation_file_path( + &base_path, + identity.partition_id, + identity.table_id, + identity.file_index, + ); + let wal_backend = new_relation_wal_backend(&base_path, &identity)?; + recover_relation_file(&base_path, &identity, &wal_backend)?; + if create_if_missing && !path.exists() { + append_file_create_async(&wal_backend, &identity).await?; + } + Self::open_inner(path, Some(identity), Some(wal_backend), create_if_missing).await + } + + /// Sync version of [`TimeSeriesFile::open_relation_file`]. + pub fn open_relation_file_sync>( + base_path: P, + identity: TimeSeriesFileIdentity, + create_if_missing: bool, + ) -> RS { + let base_path = base_path.as_ref().to_path_buf(); + let path = Self::relation_file_path( + &base_path, + identity.partition_id, + identity.table_id, + identity.file_index, + ); + let wal_backend = new_relation_wal_backend(&base_path, &identity)?; + recover_relation_file(&base_path, &identity, &wal_backend)?; + if create_if_missing && !path.exists() { + append_file_create_sync(&wal_backend, &identity)?; + } + Self::open_inner_sync(path, Some(identity), Some(wal_backend), create_if_missing) + } + + pub async fn open_ts_file>(path: P, create_if_missing: bool) -> RS { + Self::open_inner(path.as_ref().to_path_buf(), None, None, create_if_missing).await + } + + pub fn open_ts_file_sync>(path: P, create_if_missing: bool) -> RS { + Self::open_inner_sync(path.as_ref().to_path_buf(), None, None, create_if_missing) + } + + async fn open_inner( + path: PathBuf, + identity: Option, + wal_backend: Option, + create_if_missing: bool, + ) -> RS { + let path = path.to_path_buf(); + if let Some(parent) = path.parent() { + mudu_sys::fs::create_dir_all(parent) + .map_err(|e| m_error!(EC::IOErr, "create time series dir error", e))?; + } + + let flags = if create_if_missing { + libc::O_CREAT | libc::O_RDWR | file::cloexec_flag() + } else { + libc::O_RDWR | file::cloexec_flag() + }; + let file = open_rw(&path, flags).await?; + let len = mudu_sys::fs::metadata_len(&path) + .map_err(|e| m_error!(EC::IOErr, "read time series file metadata error", e))?; + if len % PAGE_SIZE as u64 != 0 { + return Err(m_error!( + EC::DecodeErr, + format!( + "time series file length {} is not aligned to page size {}", + len, PAGE_SIZE + ) + )); + } + + let page_count = (len / PAGE_SIZE as u64) as u32; + let (head_page_id, tail_page_id) = load_chain_metadata(&file, page_count).await?; + Ok(Self { + identity, + path, + file, + wal_backend, + page_cache: HashMap::new(), + page_count, + head_page_id, + tail_page_id, + }) + } + + fn open_inner_sync( + path: PathBuf, + identity: Option, + wal_backend: Option, + create_if_missing: bool, + ) -> RS { + let path = path.to_path_buf(); + if let Some(parent) = path.parent() { + mudu_sys::fs::create_dir_all(parent) + .map_err(|e| m_error!(EC::IOErr, "create time series dir error", e))?; + } + + let flags = if create_if_missing { + libc::O_CREAT | libc::O_RDWR | file::cloexec_flag() + } else { + libc::O_RDWR | file::cloexec_flag() + }; + let file = open_rw_sync(&path, flags)?; + let len = mudu_sys::fs::metadata_len(&path) + .map_err(|e| m_error!(EC::IOErr, "read time series file metadata error", e))?; + if len % PAGE_SIZE as u64 != 0 { + return Err(m_error!( + EC::DecodeErr, + format!( + "time series file length {} is not aligned to page size {}", + len, PAGE_SIZE + ) + )); + } + + let page_count = (len / PAGE_SIZE as u64) as u32; + let (head_page_id, tail_page_id) = load_chain_metadata_sync(&file, page_count)?; + Ok(Self { + identity, + path, + file, + wal_backend, + page_cache: HashMap::new(), + page_count, + head_page_id, + tail_page_id, + }) + } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn identity(&self) -> Option<&TimeSeriesFileIdentity> { + self.identity.as_ref() + } + + pub fn page_count(&self) -> PageId { + self.page_count + } + + pub fn head_page_id(&self) -> Option { + self.head_page_id + } + + pub fn tail_page_id(&self) -> Option { + self.tail_page_id + } + + pub async fn flush(&self) -> RS<()> { + flush_file(&self.file).await + } + + pub async fn close(self) -> RS<()> { + close_file(self.file).await + } + + pub fn close_sync(self) -> RS<()> { + file::close_sync(self.file) + } + + pub async fn delete_file(mut self) -> RS<()> { + if let Some(identity) = self.identity.as_ref() { + let backend = self + .wal_backend + .clone() + .ok_or_else(|| m_error!(EC::InternalErr, "missing time series wal backend"))?; + let writer = new_pl_batch_writer(backend); + writer + .append(&PLBatch { + entries: vec![PLEntry { + file: PLFileId { + partition_id: identity.partition_id, + table_id: identity.table_id, + file_index: identity.file_index, + }, + ops: vec![PLOp::Delete], + }], + }) + .await?; + } + close_file(std::mem::take(&mut self.file)).await?; + remove_file_if_exists(&self.path) + } + + pub fn delete_file_sync(mut self) -> RS<()> { + if let Some(identity) = self.identity.as_ref() { + let backend = self + .wal_backend + .clone() + .ok_or_else(|| m_error!(EC::InternalErr, "missing time series wal backend"))?; + let writer = new_pl_batch_writer(backend); + writer.append_sync(&PLBatch { + entries: vec![PLEntry { + file: PLFileId { + partition_id: identity.partition_id, + table_id: identity.table_id, + file_index: identity.file_index, + }, + ops: vec![PLOp::Delete], + }], + })?; + } + file::close_sync(std::mem::take(&mut self.file))?; + remove_file_if_exists(&self.path) + } + + pub async fn get(&self, timestamp: u64, tuple_id: u64) -> RS> { + let mut current = self.head_page_id; + while let Some(page_id) = current { + let page_buf = self.read_page(page_id).await?; + let page = PageBlockRef::new(&page_buf); + if let Some((min_ts, max_ts)) = page.timestamp_bounds()? { + if timestamp > max_ts { + return Ok(None); + } + if timestamp < min_ts { + current = page.active_next_page()?; + continue; + } + if let Some(slot_index) = page.find_slot_index(timestamp, tuple_id)? { + return Ok(Some(TimeSeriesRecord { + timestamp, + tuple_id, + payload: page.record_bytes(slot_index)?.to_vec(), + page_id, + slot_index, + })); + } + } + current = page.active_next_page()?; + } + Ok(None) + } + + pub fn get_sync(&self, timestamp: u64, tuple_id: u64) -> RS> { + let mut current = self.head_page_id; + while let Some(page_id) = current { + let page_buf = self.read_page_sync(page_id)?; + let page = PageBlockRef::new(&page_buf); + if let Some((min_ts, max_ts)) = page.timestamp_bounds()? { + if timestamp > max_ts { + return Ok(None); + } + if timestamp < min_ts { + current = page.active_next_page()?; + continue; + } + if let Some(slot_index) = page.find_slot_index(timestamp, tuple_id)? { + return Ok(Some(TimeSeriesRecord { + timestamp, + tuple_id, + payload: page.record_bytes(slot_index)?.to_vec(), + page_id, + slot_index, + })); + } + } + current = page.active_next_page()?; + } + Ok(None) + } + + pub async fn scan_range(&self, begin_ts: u64, end_ts: u64) -> RS> { + if begin_ts > end_ts { + return Ok(vec![]); + } + + let mut current = self.head_page_id; + let mut rows = vec![]; + while let Some(page_id) = current { + let page_buf = self.read_page(page_id).await?; + let page = PageBlockRef::new(&page_buf); + if let Some((min_ts, max_ts)) = page.timestamp_bounds()? { + if max_ts < begin_ts { + break; + } + if min_ts <= end_ts && max_ts >= begin_ts { + let count = page.slot_count()?; + for slot_index in 0..count { + let slot = page.slot_ref(slot_index)?; + let ts = slot.timestamp(); + if ts < begin_ts || ts > end_ts { + continue; + } + rows.push(TimeSeriesRecord { + timestamp: ts, + tuple_id: slot.tuple_id(), + payload: page.record_bytes(slot_index)?.to_vec(), + page_id, + slot_index, + }); + } + } + } + current = page.active_next_page()?; + } + + rows.sort_by(|left, right| { + left.timestamp + .cmp(&right.timestamp) + .then_with(|| left.tuple_id.cmp(&right.tuple_id)) + }); + Ok(rows) + } + + pub fn scan_range_sync(&self, begin_ts: u64, end_ts: u64) -> RS> { + if begin_ts > end_ts { + return Ok(vec![]); + } + + let mut current = self.head_page_id; + let mut rows = vec![]; + while let Some(page_id) = current { + let page_buf = self.read_page_sync(page_id)?; + let page = PageBlockRef::new(&page_buf); + if let Some((min_ts, max_ts)) = page.timestamp_bounds()? { + if max_ts < begin_ts { + break; + } + if min_ts <= end_ts && max_ts >= begin_ts { + let count = page.slot_count()?; + for slot_index in 0..count { + let slot = page.slot_ref(slot_index)?; + let ts = slot.timestamp(); + if ts < begin_ts || ts > end_ts { + continue; + } + rows.push(TimeSeriesRecord { + timestamp: ts, + tuple_id: slot.tuple_id(), + payload: page.record_bytes(slot_index)?.to_vec(), + page_id, + slot_index, + }); + } + } + } + current = page.active_next_page()?; + } + + rows.sort_by(|left, right| { + left.timestamp + .cmp(&right.timestamp) + .then_with(|| left.tuple_id.cmp(&right.tuple_id)) + }); + Ok(rows) + } + + pub async fn insert(&mut self, timestamp: u64, tuple_id: u64, payload: &[u8]) -> RS<()> { + self.insert_sync(timestamp, tuple_id, payload) + } + + pub fn insert_sync(&mut self, timestamp: u64, tuple_id: u64, payload: &[u8]) -> RS<()> { + match self.find_insert_location_sync(timestamp)? { + PageInsertLocation::EmptyFile => { + let page_id = self.page_count; + let mut page_buf = empty_page_image(page_id)?; + { + let mut page = PageBlockRefMut::new(&mut page_buf); + page.insert_record(timestamp, tuple_id, payload)?; + } + let mut plan = TimeSeriesFileMutationPlan::default(); + plan.page_writes.push(PlannedPageWrite { + page_id, + image: page_buf, + }); + plan.next_page_count = Some(page_id + 1); + plan.next_head_page_id = Some(Some(page_id)); + plan.next_tail_page_id = Some(Some(page_id)); + self.persist_plan_sync(plan)?; + } + PageInsertLocation::Existing(page_id) => { + let page_buf = self.read_page_sync(page_id)?; + let page = PageBlockRef::new(&page_buf); + if let Some(slot_index) = page.find_slot_index(timestamp, tuple_id)? { + self.update_in_page_sync(page_id, slot_index, timestamp, tuple_id, payload)?; + return Ok(()); + } + + let mut page_buf = page_buf; + let insert_result = { + let mut page_mut = PageBlockRefMut::new(&mut page_buf); + page_mut.insert_record(timestamp, tuple_id, payload) + }; + match insert_result { + Ok(_) => self.write_page_sync(page_id, &page_buf)?, + Err(err) if err.ec() == EC::InsufficientBufferSpace => { + self.split_insert_full_page_sync(page_id, timestamp, tuple_id, payload)?; + } + Err(err) => return Err(err), + } + } + PageInsertLocation::Before(next_page_id) => { + let page_id = self.page_count; + let next_page_buf = self.read_page_sync(next_page_id)?; + let next_page = PageBlockRef::new(&next_page_buf); + let prev_page_id = next_page.active_prev_page()?; + let mut new_page_buf = empty_page_image(page_id)?; + { + let mut page = PageBlockRefMut::new(&mut new_page_buf); + page.set_page_links(prev_page_id.unwrap_or(NONE_PAGE_ID), next_page_id)?; + page.insert_record(timestamp, tuple_id, payload)?; + } + + let mut updated_next_buf = next_page_buf.clone(); + { + let header = PageBlockRef::new(&updated_next_buf).header()?; + let mut page = PageBlockRefMut::new(&mut updated_next_buf); + page.set_page_links(page_id, header.next_page())?; + } + + let mut plan = TimeSeriesFileMutationPlan::default(); + plan.page_writes.push(PlannedPageWrite { + page_id, + image: new_page_buf, + }); + plan.page_writes.push(PlannedPageWrite { + page_id: next_page_id, + image: updated_next_buf, + }); + if let Some(prev_page_id) = prev_page_id { + let prev_page_buf = self.read_page_sync(prev_page_id)?; + let mut updated_prev_buf = prev_page_buf.clone(); + let header = PageBlockRef::new(&updated_prev_buf).header()?; + { + let mut page = PageBlockRefMut::new(&mut updated_prev_buf); + page.set_page_links(header.prev_page(), page_id)?; + } + plan.page_writes.push(PlannedPageWrite { + page_id: prev_page_id, + image: updated_prev_buf, + }); + } else { + plan.next_head_page_id = Some(Some(page_id)); + } + plan.next_page_count = Some(page_id + 1); + self.persist_plan_sync(plan)?; + } + PageInsertLocation::After(prev_page_id) => { + let page_id = self.page_count; + let prev_page_buf = self.read_page_sync(prev_page_id)?; + let prev_page = PageBlockRef::new(&prev_page_buf); + let next_page_id = prev_page.active_next_page()?; + let mut new_page_buf = empty_page_image(page_id)?; + { + let mut page = PageBlockRefMut::new(&mut new_page_buf); + page.set_page_links(prev_page_id, next_page_id.unwrap_or(NONE_PAGE_ID))?; + page.insert_record(timestamp, tuple_id, payload)?; + } + + let mut updated_prev_buf = prev_page_buf.clone(); + { + let header = PageBlockRef::new(&updated_prev_buf).header()?; + let mut page = PageBlockRefMut::new(&mut updated_prev_buf); + page.set_page_links(header.prev_page(), page_id)?; + } + + let mut plan = TimeSeriesFileMutationPlan::default(); + plan.page_writes.push(PlannedPageWrite { + page_id, + image: new_page_buf, + }); + plan.page_writes.push(PlannedPageWrite { + page_id: prev_page_id, + image: updated_prev_buf, + }); + if let Some(next_page_id) = next_page_id { + let next_page_buf = self.read_page_sync(next_page_id)?; + let mut updated_next_buf = next_page_buf.clone(); + let header = PageBlockRef::new(&updated_next_buf).header()?; + { + let mut page = PageBlockRefMut::new(&mut updated_next_buf); + page.set_page_links(page_id, header.next_page())?; + } + plan.page_writes.push(PlannedPageWrite { + page_id: next_page_id, + image: updated_next_buf, + }); + } else { + plan.next_tail_page_id = Some(Some(page_id)); + } + if self.head_page_id.is_none() { + plan.next_head_page_id = Some(Some(page_id)); + } + plan.next_page_count = Some(page_id + 1); + self.persist_plan_sync(plan)?; + } + } + Ok(()) + } + + pub async fn delete(&mut self, timestamp: u64, tuple_id: u64) -> RS { + self.delete_sync(timestamp, tuple_id) + } + + pub fn delete_sync(&mut self, timestamp: u64, tuple_id: u64) -> RS { + let mut current = self.head_page_id; + while let Some(page_id) = current { + let page_buf = self.read_page_sync(page_id)?; + let page = PageBlockRef::new(&page_buf); + if let Some((min_ts, max_ts)) = page.timestamp_bounds()? { + if timestamp > max_ts { + return Ok(false); + } + if timestamp < min_ts { + current = page.active_next_page()?; + continue; + } + if let Some(slot_index) = page.find_slot_index(timestamp, tuple_id)? { + let mut page_buf = page_buf; + { + let mut page_mut = PageBlockRefMut::new(&mut page_buf); + page_mut.delete_record(slot_index)?; + } + let mut plan = TimeSeriesFileMutationPlan::default(); + plan.page_writes.push(PlannedPageWrite { + page_id, + image: page_buf, + }); + self.persist_plan_sync(plan)?; + return Ok(true); + } + } + current = page.active_next_page()?; + } + Ok(false) + } + + fn find_split_index(&self, entries: &[TimeSeriesRecord]) -> RS { + for split_at in 1..entries.len() { + if page_entries_fit(&entries[..split_at]) && page_entries_fit(&entries[split_at..]) { + return Ok(split_at); + } + } + Err(m_error!( + EC::InsufficientBufferSpace, + "records do not fit into two time series pages" + )) + } + + fn page_entries(&self, page: &PageBlockRef<'_>, page_id: PageId) -> RS> { + let count = page.slot_count()?; + let mut entries = Vec::with_capacity(count); + for slot_index in 0..count { + let slot = page.slot_ref(slot_index)?; + entries.push(TimeSeriesRecord { + timestamp: slot.timestamp(), + tuple_id: slot.tuple_id(), + payload: page.record_bytes(slot_index)?.to_vec(), + page_id, + slot_index, + }); + } + Ok(entries) + } + + async fn read_page(&self, page_id: PageId) -> RS> { + if page_id >= self.page_count { + return Err(m_error!( + EC::IndexOutOfRange, + format!("page {} out of range {}", page_id, self.page_count) + )); + } + if let Some(entry) = self.page_cache.get_sync(&page_id) { + return Ok(entry.get().clone()); + } + + let page = read_file_exact(&self.file, PAGE_SIZE, page_offset(page_id)?).await?; + let _ = self.page_cache.remove_sync(&page_id); + let _ = self.page_cache.insert_sync(page_id, page.clone()); + Ok(page) + } + + fn update_in_page_sync( + &mut self, + page_id: PageId, + slot_index: usize, + timestamp: u64, + tuple_id: u64, + payload: &[u8], + ) -> RS<()> { + let mut page_buf = self.read_page_sync(page_id)?; + { + let mut page_mut = PageBlockRefMut::new(&mut page_buf); + page_mut.update_record(slot_index, timestamp, tuple_id, payload)?; + } + let mut plan = TimeSeriesFileMutationPlan::default(); + plan.page_writes.push(PlannedPageWrite { + page_id, + image: page_buf, + }); + self.persist_plan_sync(plan) + } + + fn split_insert_full_page_sync( + &mut self, + page_id: PageId, + timestamp: u64, + tuple_id: u64, + payload: &[u8], + ) -> RS<()> { + let page_buf = self.read_page_sync(page_id)?; + let page = PageBlockRef::new(&page_buf); + let mut entries = self.page_entries(&page, page_id)?; + entries.push(TimeSeriesRecord { + timestamp, + tuple_id, + payload: payload.to_vec(), + page_id, + slot_index: 0, + }); + entries.sort_by(|left, right| { + left.timestamp + .cmp(&right.timestamp) + .then_with(|| left.tuple_id.cmp(&right.tuple_id)) + }); + + let split_at = self.find_split_index(&entries)?; + let lower_entries = entries[..split_at].to_vec(); + let upper_entries = entries[split_at..].to_vec(); + + let header = page.header()?; + let old_next_page_id = page.active_next_page()?; + let new_page_id = self.page_count; + let current_page_buf = + build_entries_page_image(page_id, header.prev_page(), new_page_id, &upper_entries)?; + let new_page_buf = build_entries_page_image( + new_page_id, + page_id, + old_next_page_id.unwrap_or(NONE_PAGE_ID), + &lower_entries, + )?; + + let mut plan = TimeSeriesFileMutationPlan::default(); + plan.page_writes.push(PlannedPageWrite { + page_id, + image: current_page_buf, + }); + plan.page_writes.push(PlannedPageWrite { + page_id: new_page_id, + image: new_page_buf, + }); + if let Some(next_page_id) = old_next_page_id { + let next_page_buf = self.read_page_sync(next_page_id)?; + let mut updated_next_buf = next_page_buf.clone(); + let next_header = PageBlockRef::new(&updated_next_buf).header()?; + { + let mut page = PageBlockRefMut::new(&mut updated_next_buf); + page.set_page_links(new_page_id, next_header.next_page())?; + } + plan.page_writes.push(PlannedPageWrite { + page_id: next_page_id, + image: updated_next_buf, + }); + } else { + plan.next_tail_page_id = Some(Some(new_page_id)); + } + plan.next_page_count = Some(new_page_id + 1); + self.persist_plan_sync(plan) + } + + fn find_insert_location_sync(&self, timestamp: u64) -> RS { + let Some(mut current) = self.head_page_id else { + return Ok(PageInsertLocation::EmptyFile); + }; + + let mut last_non_empty = None; + loop { + let page_buf = self.read_page_sync(current)?; + let page = PageBlockRef::new(&page_buf); + match page.timestamp_bounds()? { + Some((min_ts, max_ts)) => { + last_non_empty = Some(current); + if timestamp > max_ts { + return Ok(PageInsertLocation::Before(current)); + } + if timestamp >= min_ts { + return Ok(PageInsertLocation::Existing(current)); + } + } + None => {} + } + + match page.active_next_page()? { + Some(next) => current = next, + None => return Ok(PageInsertLocation::After(last_non_empty.unwrap_or(current))), + } + } + } + + fn read_page_sync(&self, page_id: PageId) -> RS> { + if page_id >= self.page_count { + return Err(m_error!( + EC::IndexOutOfRange, + format!("page {} out of range {}", page_id, self.page_count) + )); + } + if let Some(entry) = self.page_cache.get_sync(&page_id) { + return Ok(entry.get().clone()); + } + + let page = read_file_exact_sync(&self.file, PAGE_SIZE, page_offset(page_id)?)?; + let _ = self.page_cache.remove_sync(&page_id); + let _ = self.page_cache.insert_sync(page_id, page.clone()); + Ok(page) + } + + fn write_page_sync(&mut self, page_id: PageId, page: &[u8]) -> RS<()> { + if page.len() != PAGE_SIZE { + return Err(m_error!( + EC::EncodeErr, + format!( + "page write requires {} bytes, got {}", + PAGE_SIZE, + page.len() + ) + )); + } + let mut plan = TimeSeriesFileMutationPlan::default(); + plan.page_writes.push(PlannedPageWrite { + page_id, + image: page.to_vec(), + }); + self.persist_plan_sync(plan) + } + + fn persist_plan_sync(&mut self, plan: TimeSeriesFileMutationPlan) -> RS<()> { + // Physical WAL must reach durable storage before any data-page update. + if let Some(batch) = self.build_pl_batch(&plan)? { + let backend = self + .wal_backend + .clone() + .ok_or_else(|| m_error!(EC::InternalErr, "missing time series wal backend"))?; + let writer = new_pl_batch_writer(backend); + writer.append_sync(&batch)?; + } + self.apply_plan_sync(&plan) + } + + fn apply_plan_sync(&mut self, plan: &TimeSeriesFileMutationPlan) -> RS<()> { + if plan.create_file { + ensure_time_series_file_exists_sync(&self.path)?; + } + for write in &plan.page_writes { + self.apply_page_write_sync(write.page_id, &write.image)?; + } + if plan.delete_file { + file::close_sync(std::mem::take(&mut self.file))?; + remove_file_if_exists(&self.path)?; + self.page_cache = HashMap::new(); + } + if let Some(page_count) = plan.next_page_count { + self.page_count = page_count; + } + if let Some(head_page_id) = plan.next_head_page_id { + self.head_page_id = head_page_id; + } + if let Some(tail_page_id) = plan.next_tail_page_id { + self.tail_page_id = tail_page_id; + } + Ok(()) + } + + fn apply_page_write_sync(&self, page_id: PageId, page: &[u8]) -> RS<()> { + let _ = self.page_cache.remove_sync(&page_id); + write_file_all_sync(&self.file, page, page_offset(page_id)?)?; + let _ = self.page_cache.insert_sync(page_id, page.to_vec()); + Ok(()) + } + + fn build_pl_batch(&self, plan: &TimeSeriesFileMutationPlan) -> RS> { + let Some(identity) = self.identity.as_ref() else { + return Ok(None); + }; + let mut ops = Vec::new(); + if plan.create_file { + ops.push(PLOp::Create); + } + for write in &plan.page_writes { + if write.image.len() != PAGE_SIZE { + return Err(m_error!( + EC::EncodeErr, + format!( + "page write requires {} bytes, got {}", + PAGE_SIZE, + write.image.len() + ) + )); + } + ops.push(PLOp::PageUpdate(PageUpdate { + page_id: write.page_id, + offset: 0, + data: write.image.clone(), + })); + } + if plan.delete_file { + ops.push(PLOp::Delete); + } + if ops.is_empty() { + return Ok(None); + } + Ok(Some(PLBatch { + entries: vec![PLEntry { + file: PLFileId { + partition_id: identity.partition_id, + table_id: identity.table_id, + file_index: identity.file_index, + }, + ops, + }], + })) + } +} + +fn build_entries_page_image( + page_id: PageId, + prev_page_id: PageId, + next_page_id: PageId, + entries: &[TimeSeriesRecord], +) -> RS> { + let mut page_buf = empty_page_image(page_id)?; + { + let mut page = PageBlockRefMut::new(&mut page_buf); + page.set_page_links(prev_page_id, next_page_id)?; + } + for entry in entries { + let mut page = PageBlockRefMut::new(&mut page_buf); + page.insert_record(entry.timestamp, entry.tuple_id, &entry.payload)?; + } + Ok(page_buf) +} + +fn empty_page_image(page_id: PageId) -> RS> { + let mut page_buf = vec![0u8; PAGE_SIZE]; + { + let mut page = PageBlockRefMut::new(&mut page_buf); + page.init_empty(page_id)?; + } + Ok(page_buf) +} + +fn page_entries_fit(entries: &[TimeSeriesRecord]) -> bool { + let mut buf = vec![0u8; PAGE_SIZE]; + let mut page = PageBlockRefMut::new(&mut buf); + if page.init_empty(0).is_err() { + return false; + } + for entry in entries { + if page + .insert_record(entry.timestamp, entry.tuple_id, &entry.payload) + .is_err() + { + return false; + } + } + true +} + +async fn load_chain_metadata( + file: &IoFile, + page_count: PageId, +) -> RS<(Option, Option)> { + if page_count == 0 { + return Ok((None, None)); + } + + let mut headers = Vec::with_capacity(page_count as usize); + for page_id in 0..page_count { + let buf = read_file_exact(file, PAGE_SIZE, page_offset(page_id)?).await?; + let page = PageBlockRef::new(&buf); + page.validate_layout()?; + headers.push(page.header()?); + } + + let heads: Vec = headers + .iter() + .filter(|header| header.prev_page() == NONE_PAGE_ID) + .map(|header| header.page_id()) + .collect(); + let tails: Vec = headers + .iter() + .filter(|header| header.next_page() == NONE_PAGE_ID) + .map(|header| header.page_id()) + .collect(); + if heads.len() != 1 || tails.len() != 1 { + return Err(m_error!( + EC::DecodeErr, + format!( + "time series file requires exactly one head and one tail, got heads={}, tails={}", + heads.len(), + tails.len() + ) + )); + } + + let head = heads[0]; + let tail = tails[0]; + let mut current = head; + let mut visited = vec![false; page_count as usize]; + let mut prev_non_empty_min = None; + loop { + if visited[current as usize] { + return Err(m_error!( + EC::DecodeErr, + "time series page chain has a cycle" + )); + } + visited[current as usize] = true; + let header = &headers[current as usize]; + if let Some(next) = (header.next_page() != NONE_PAGE_ID).then_some(header.next_page()) { + let next_header = &headers[next as usize]; + if next_header.prev_page() != current { + return Err(m_error!( + EC::DecodeErr, + format!("broken page link {} -> {}", current, next) + )); + } + } + + let buf = read_file_exact(file, PAGE_SIZE, page_offset(current)?).await?; + let page = PageBlockRef::new(&buf); + if let Some((min_ts, _max_ts)) = page.timestamp_bounds()? { + if let Some(prev_min) = prev_non_empty_min { + let page_max = page.timestamp_bounds()?.unwrap().1; + if page_max > prev_min { + return Err(m_error!( + EC::DecodeErr, + format!( + "time series chain order broken between pages: page {} max_ts {} > previous min_ts {}", + current, page_max, prev_min + ) + )); + } + } + prev_non_empty_min = Some(min_ts); + } + + match (header.next_page() != NONE_PAGE_ID).then_some(header.next_page()) { + Some(next) => current = next, + None => break, + } + } + + if visited.iter().any(|seen| !seen) { + return Err(m_error!( + EC::DecodeErr, + "time series file contains disconnected pages" + )); + } + + Ok((Some(head), Some(tail))) +} + +fn load_chain_metadata_sync( + file: &IoFile, + page_count: PageId, +) -> RS<(Option, Option)> { + if page_count == 0 { + return Ok((None, None)); + } + + let mut headers = Vec::with_capacity(page_count as usize); + for page_id in 0..page_count { + let buf = read_file_exact_sync(file, PAGE_SIZE, page_offset(page_id)?)?; + let page = PageBlockRef::new(&buf); + page.validate_layout()?; + headers.push(page.header()?); + } + + let heads: Vec = headers + .iter() + .filter(|header| header.prev_page() == NONE_PAGE_ID) + .map(|header| header.page_id()) + .collect(); + let tails: Vec = headers + .iter() + .filter(|header| header.next_page() == NONE_PAGE_ID) + .map(|header| header.page_id()) + .collect(); + if heads.len() != 1 || tails.len() != 1 { + return Err(m_error!( + EC::DecodeErr, + format!( + "time series file requires exactly one head and one tail, got heads={}, tails={}", + heads.len(), + tails.len() + ) + )); + } + + let head = heads[0]; + let tail = tails[0]; + let mut current = head; + let mut visited = vec![false; page_count as usize]; + let mut prev_non_empty_min = None; + loop { + if visited[current as usize] { + return Err(m_error!( + EC::DecodeErr, + "time series page chain has a cycle" + )); + } + visited[current as usize] = true; + let header = &headers[current as usize]; + if let Some(next) = (header.next_page() != NONE_PAGE_ID).then_some(header.next_page()) { + let next_header = &headers[next as usize]; + if next_header.prev_page() != current { + return Err(m_error!( + EC::DecodeErr, + format!("broken page link {} -> {}", current, next) + )); + } + } + + let buf = read_file_exact_sync(file, PAGE_SIZE, page_offset(current)?)?; + let page = PageBlockRef::new(&buf); + if let Some((min_ts, _max_ts)) = page.timestamp_bounds()? { + if let Some(prev_min) = prev_non_empty_min { + let page_max = page.timestamp_bounds()?.unwrap().1; + if page_max > prev_min { + return Err(m_error!( + EC::DecodeErr, + format!( + "time series chain order broken between pages: page {} max_ts {} > previous min_ts {}", + current, page_max, prev_min + ) + )); + } + } + prev_non_empty_min = Some(min_ts); + } + + match (header.next_page() != NONE_PAGE_ID).then_some(header.next_page()) { + Some(next) => current = next, + None => break, + } + } + + if visited.iter().any(|seen| !seen) { + return Err(m_error!( + EC::DecodeErr, + "time series file contains disconnected pages" + )); + } + + Ok((Some(head), Some(tail))) +} + +fn page_offset(page_id: PageId) -> RS { + (page_id as u64) + .checked_mul(PAGE_SIZE as u64) + .ok_or_else(|| m_error!(EC::IndexOutOfRange, "time series page offset overflow")) +} + +async fn open_rw(path: &Path, flags: i32) -> RS { + if worker_ring::has_current_worker_ring() { + file::open(path, flags, FILE_MODE_644).await + } else { + file::open_sync(path, flags, FILE_MODE_644) + } +} + +fn open_rw_sync(path: &Path, flags: i32) -> RS { + file::open_sync(path, flags, FILE_MODE_644) +} + +async fn read_file_exact(file: &IoFile, len: usize, offset: u64) -> RS> { + if worker_ring::has_current_worker_ring() { + file::read(file, len, offset).await + } else { + file::read_sync(file, len, offset) + } +} + +fn read_file_exact_sync(file: &IoFile, len: usize, offset: u64) -> RS> { + file::read_sync(file, len, offset) +} + +fn write_file_all_sync(file: &IoFile, payload: &[u8], offset: u64) -> RS<()> { + file::write_sync(file, payload, offset) +} + +async fn flush_file(file: &IoFile) -> RS<()> { + if worker_ring::has_current_worker_ring() { + file::flush(file).await + } else { + file::flush_sync(file) + } +} + +async fn close_file(file: IoFile) -> RS<()> { + if worker_ring::has_current_worker_ring() { + file::close(file).await + } else { + file::close_sync(file) + } +} + +fn new_relation_wal_backend( + base_path: &Path, + identity: &TimeSeriesFileIdentity, +) -> RS { + // Each relation file gets its own physical-log stream so recovery can + // replay one file independently of the rest of the worker state. + let log_dir = base_path.join("relation_wal"); + let layout = WorkerLogLayout::new( + log_dir, + time_series_log_oid(identity), + RELATION_WAL_CHUNK_SIZE, + )?; + ChunkedWorkerLogBackend::new(layout) +} + +fn time_series_log_oid(identity: &TimeSeriesFileIdentity) -> OID { + identity.partition_id.rotate_left(17) + ^ identity.table_id.rotate_left(53) + ^ (identity.file_index as u128).rotate_left(97) + ^ 0x706c5f74735f66696c655f77616c_u128 +} + +fn recover_relation_file( + base_path: &Path, + identity: &TimeSeriesFileIdentity, + backend: &ChunkedWorkerLogBackend, +) -> RS<()> { + // Recovery is file-local: replay the PL stream for this exact file id + // before opening the data file and rebuilding in-memory metadata. + let path = TimeSeriesFile::relation_file_path( + base_path, + identity.partition_id, + identity.table_id, + identity.file_index, + ); + for chunk_path in backend.chunk_paths_sorted()? { + let bytes = mudu_sys::fs::read_all(&chunk_path) + .map_err(|e| m_error!(EC::IOErr, "read time series wal chunk error", e))?; + if bytes.is_empty() { + continue; + } + let frames = crate::wal::worker_log::decode_frames(&bytes)?; + let batches = crate::wal::pl_batch::decode_pl_batches(&frames)?; + for batch in batches { + for entry in batch.entries { + if entry.file + != (PLFileId { + partition_id: identity.partition_id, + table_id: identity.table_id, + file_index: identity.file_index, + }) + { + continue; + } + apply_recovered_entry(&path, &entry)?; + } + } + } + Ok(()) +} + +fn apply_recovered_entry(path: &Path, entry: &PLEntry) -> RS<()> { + for op in &entry.ops { + match op { + PLOp::Create => ensure_time_series_file_exists_sync(path)?, + PLOp::Delete => remove_file_if_exists(path)?, + PLOp::PageUpdate(update) => { + ensure_time_series_file_exists_sync(path)?; + let file = open_rw_sync(path, libc::O_CREAT | libc::O_RDWR | file::cloexec_flag())?; + let result = write_file_all_sync( + &file, + &update.data, + page_offset(update.page_id)? + update.offset as u64, + ); + let close_result = file::close_sync(file); + result?; + close_result?; + } + } + } + Ok(()) +} + +async fn append_file_create_async( + backend: &ChunkedWorkerLogBackend, + identity: &TimeSeriesFileIdentity, +) -> RS<()> { + let writer = new_pl_batch_writer(backend.clone()); + writer + .append(&PLBatch { + entries: vec![PLEntry { + file: PLFileId { + partition_id: identity.partition_id, + table_id: identity.table_id, + file_index: identity.file_index, + }, + ops: vec![PLOp::Create], + }], + }) + .await?; + Ok(()) +} + +fn append_file_create_sync( + backend: &ChunkedWorkerLogBackend, + identity: &TimeSeriesFileIdentity, +) -> RS<()> { + let writer = new_pl_batch_writer(backend.clone()); + writer.append_sync(&PLBatch { + entries: vec![PLEntry { + file: PLFileId { + partition_id: identity.partition_id, + table_id: identity.table_id, + file_index: identity.file_index, + }, + ops: vec![PLOp::Create], + }], + }) +} + +fn ensure_time_series_file_exists_sync(path: &Path) -> RS<()> { + if let Some(parent) = path.parent() { + mudu_sys::fs::create_dir_all(parent) + .map_err(|e| m_error!(EC::IOErr, "create time series dir error", e))?; + } + if path.exists() { + return Ok(()); + } + let file = open_rw_sync(path, libc::O_CREAT | libc::O_RDWR | file::cloexec_flag())?; + file::close_sync(file) +} + +fn remove_file_if_exists(path: &Path) -> RS<()> { + mudu_sys::fs::remove_file_if_exists(path) +} + +#[cfg(test)] +mod tests { + use super::{TimeSeriesFile, TimeSeriesFileIdentity, PAGE_SIZE}; + use crate::storage::page::PageId; + use project_root::get_project_root; + + fn temp_ts_path(name: &str) -> std::path::PathBuf { + let root = get_project_root().unwrap(); + root.join("target").join("tmp").join(format!( + "tsf-{}-{}.dat", + name, + mudu_sys::random::uuid_v4() + )) + } + + fn temp_relation_base(name: &str) -> std::path::PathBuf { + let root = get_project_root().unwrap(); + root.join("target").join("tmp").join(format!( + "tsf-rel-{}-{}", + name, + mudu_sys::random::uuid_v4() + )) + } + + fn payload(byte: u8, len: usize) -> Vec { + vec![byte; len] + } + + #[tokio::test(flavor = "current_thread")] + async fn open_create_empty_file() { + let path = temp_ts_path("empty"); + let file = TimeSeriesFile::open_ts_file(&path, true).await.unwrap(); + assert_eq!(file.page_count(), 0 as PageId); + assert_eq!(file.head_page_id(), None); + assert_eq!(file.tail_page_id(), None); + file.close().await.unwrap(); + let _ = std::fs::remove_file(path); + } + + #[tokio::test(flavor = "current_thread")] + async fn insert_get_update_delete_roundtrip() { + let path = temp_ts_path("roundtrip"); + let mut file = TimeSeriesFile::open_ts_file(&path, true).await.unwrap(); + + file.insert(100, 1, b"v1").await.unwrap(); + file.insert(90, 2, b"v2").await.unwrap(); + file.insert(100, 1, b"v1-new").await.unwrap(); + + let row = file.get(100, 1).await.unwrap().unwrap(); + assert_eq!(row.payload, b"v1-new"); + assert_eq!(row.timestamp, 100); + assert_eq!(row.tuple_id, 1); + + let row = file.get(90, 2).await.unwrap().unwrap(); + assert_eq!(row.payload, b"v2"); + + assert!(file.delete(90, 2).await.unwrap()); + assert_eq!(file.get(90, 2).await.unwrap(), None); + assert!(!file.delete(90, 2).await.unwrap()); + + file.close().await.unwrap(); + let _ = std::fs::remove_file(path); + } + + #[tokio::test(flavor = "current_thread")] + async fn scan_range_returns_sorted_records() { + let path = temp_ts_path("scan"); + let mut file = TimeSeriesFile::open_ts_file(&path, true).await.unwrap(); + + file.insert(120, 4, b"d").await.unwrap(); + file.insert(100, 2, b"b").await.unwrap(); + file.insert(100, 1, b"a").await.unwrap(); + file.insert(110, 3, b"c").await.unwrap(); + file.insert(90, 5, b"e").await.unwrap(); + + let rows = file.scan_range(95, 115).await.unwrap(); + let keys: Vec<(u64, u64, Vec)> = rows + .into_iter() + .map(|row| (row.timestamp, row.tuple_id, row.payload)) + .collect(); + assert_eq!( + keys, + vec![ + (100, 1, b"a".to_vec()), + (100, 2, b"b".to_vec()), + (110, 3, b"c".to_vec()), + ] + ); + + file.close().await.unwrap(); + let _ = std::fs::remove_file(path); + } + + #[tokio::test(flavor = "current_thread")] + async fn reopen_preserves_records() { + let path = temp_ts_path("reopen"); + { + let mut file = TimeSeriesFile::open_ts_file(&path, true).await.unwrap(); + file.insert(100, 1, b"alpha").await.unwrap(); + file.insert(80, 2, b"beta").await.unwrap(); + file.flush().await.unwrap(); + file.close().await.unwrap(); + } + + let file = TimeSeriesFile::open_ts_file(&path, false).await.unwrap(); + let row = file.get(100, 1).await.unwrap().unwrap(); + assert_eq!(row.payload, b"alpha"); + let row = file.get(80, 2).await.unwrap().unwrap(); + assert_eq!(row.payload, b"beta"); + assert_eq!( + file.scan_range(0, 200) + .await + .unwrap() + .into_iter() + .map(|row| (row.timestamp, row.tuple_id)) + .collect::>(), + vec![(80, 2), (100, 1)] + ); + file.close().await.unwrap(); + let _ = std::fs::remove_file(path); + } + + #[tokio::test(flavor = "current_thread")] + async fn insert_creates_multiple_pages_when_page_is_full() { + let path = temp_ts_path("split"); + let mut file = TimeSeriesFile::open_ts_file(&path, true).await.unwrap(); + + for idx in 0..16u64 { + let ts = 10_000 - idx; + let data = payload((idx % 251) as u8, 700); + file.insert(ts, idx, &data).await.unwrap(); + } + + assert!(file.page_count() > 1); + assert!(file.head_page_id().is_some()); + assert!(file.tail_page_id().is_some()); + + for idx in 0..16u64 { + let ts = 10_000 - idx; + let row = file.get(ts, idx).await.unwrap().unwrap(); + assert_eq!(row.timestamp, ts); + assert_eq!(row.tuple_id, idx); + assert_eq!(row.payload.len(), 700); + } + + let rows = file.scan_range(9_980, 10_000).await.unwrap(); + assert_eq!(rows.len(), 16); + assert!(rows.iter().all(|row| row.payload.len() == 700)); + + file.close().await.unwrap(); + let _ = std::fs::remove_file(path); + } + + #[tokio::test(flavor = "current_thread")] + async fn cached_pages_are_reused_after_writes() { + let path = temp_ts_path("cache"); + let mut file = TimeSeriesFile::open_ts_file(&path, true).await.unwrap(); + + file.insert(100, 1, b"cached").await.unwrap(); + let page_count = file.page_count(); + assert_eq!(page_count, 1); + + let first = file.get(100, 1).await.unwrap().unwrap(); + let second = file.get(100, 1).await.unwrap().unwrap(); + assert_eq!(first.payload, second.payload); + assert_eq!(first.page_id, 0); + + let file_len = std::fs::metadata(file.path()).unwrap().len() as usize; + assert_eq!(file_len % PAGE_SIZE, 0); + + file.close().await.unwrap(); + let _ = std::fs::remove_file(path); + } + + #[tokio::test(flavor = "current_thread")] + async fn integrated_api_flow_covers_all_public_operations() { + let path = temp_ts_path("integrated"); + let mut file = TimeSeriesFile::open_ts_file(&path, true).await.unwrap(); + + assert_eq!(file.page_count(), 0); + assert_eq!(file.head_page_id(), None); + assert_eq!(file.tail_page_id(), None); + assert_eq!(file.get(1, 1).await.unwrap(), None); + assert!(file.scan_range(1, 10).await.unwrap().is_empty()); + assert!(!file.delete(1, 1).await.unwrap()); + + for idx in 0..12u64 { + let ts = 1_000 - idx; + let value = payload((idx % 251) as u8, 768); + file.insert(ts, idx, &value).await.unwrap(); + } + + assert!(file.page_count() > 1); + let head = file.head_page_id().unwrap(); + let tail = file.tail_page_id().unwrap(); + assert!(head <= tail); + + for idx in 0..12u64 { + let ts = 1_000 - idx; + let row = file.get(ts, idx).await.unwrap().unwrap(); + assert_eq!(row.timestamp, ts); + assert_eq!(row.tuple_id, idx); + assert_eq!(row.payload, payload((idx % 251) as u8, 768)); + } + + let rows = file.scan_range(993, 1_000).await.unwrap(); + let keys: Vec<(u64, u64)> = rows + .iter() + .map(|row| (row.timestamp, row.tuple_id)) + .collect(); + assert_eq!( + keys, + vec![ + (993, 7), + (994, 6), + (995, 5), + (996, 4), + (997, 3), + (998, 2), + (999, 1), + (1000, 0), + ] + ); + + file.insert(997, 3, b"updated").await.unwrap(); + let updated = file.get(997, 3).await.unwrap().unwrap(); + assert_eq!(updated.payload, b"updated"); + + assert!(file.delete(995, 5).await.unwrap()); + assert_eq!(file.get(995, 5).await.unwrap(), None); + assert!(!file.delete(995, 5).await.unwrap()); + + file.flush().await.unwrap(); + let persisted_page_count = file.page_count(); + let persisted_head = file.head_page_id(); + let persisted_tail = file.tail_page_id(); + file.close().await.unwrap(); + + let reopened = TimeSeriesFile::open_ts_file(&path, false).await.unwrap(); + assert_eq!(reopened.page_count(), persisted_page_count); + assert_eq!(reopened.head_page_id(), persisted_head); + assert_eq!(reopened.tail_page_id(), persisted_tail); + assert_eq!(reopened.get(995, 5).await.unwrap(), None); + assert_eq!( + reopened.get(997, 3).await.unwrap().unwrap().payload, + b"updated" + ); + + let reopened_rows = reopened.scan_range(989, 1_000).await.unwrap(); + let reopened_keys: Vec<(u64, u64)> = reopened_rows + .iter() + .map(|row| (row.timestamp, row.tuple_id)) + .collect(); + assert_eq!( + reopened_keys, + vec![ + (989, 11), + (990, 10), + (991, 9), + (992, 8), + (993, 7), + (994, 6), + (996, 4), + (997, 3), + (998, 2), + (999, 1), + (1000, 0), + ] + ); + reopened.close().await.unwrap(); + let _ = std::fs::remove_file(path); + } + + #[test] + fn wal_recovers_relation_file_after_data_loss() { + let base = temp_relation_base("recover"); + let identity = TimeSeriesFileIdentity { + partition_id: 7, + table_id: 11, + file_index: 0, + }; + let path = TimeSeriesFile::relation_file_path( + &base, + identity.partition_id, + identity.table_id, + identity.file_index, + ); + + let mut file = + TimeSeriesFile::open_relation_file_sync(&base, identity.clone(), true).unwrap(); + file.insert_sync(100, 1, b"alpha").unwrap(); + file.insert_sync(90, 2, b"beta").unwrap(); + file.delete_sync(90, 2).unwrap(); + file.close_sync().unwrap(); + std::fs::remove_file(&path).unwrap(); + + let reopened = TimeSeriesFile::open_relation_file_sync(&base, identity, false).unwrap(); + assert_eq!( + reopened.get_sync(100, 1).unwrap().unwrap().payload, + b"alpha".to_vec() + ); + assert_eq!(reopened.get_sync(90, 2).unwrap(), None); + reopened.close_sync().unwrap(); + std::fs::remove_dir_all(base).unwrap(); + } + + #[test] + fn wal_recovers_empty_file_from_create_record() { + let base = temp_relation_base("create"); + let identity = TimeSeriesFileIdentity { + partition_id: 17, + table_id: 23, + file_index: 1, + }; + let path = TimeSeriesFile::relation_file_path( + &base, + identity.partition_id, + identity.table_id, + identity.file_index, + ); + + let file = TimeSeriesFile::open_relation_file_sync(&base, identity.clone(), true).unwrap(); + file.close_sync().unwrap(); + std::fs::remove_file(&path).unwrap(); + + let reopened = TimeSeriesFile::open_relation_file_sync(&base, identity, false).unwrap(); + assert_eq!(reopened.page_count(), 0); + reopened.close_sync().unwrap(); + std::fs::remove_dir_all(base).unwrap(); + } + + #[test] + fn wal_replays_terminal_delete_before_open() { + let base = temp_relation_base("delete"); + let identity = TimeSeriesFileIdentity { + partition_id: 29, + table_id: 31, + file_index: 0, + }; + let path = TimeSeriesFile::relation_file_path( + &base, + identity.partition_id, + identity.table_id, + identity.file_index, + ); + + let mut file = + TimeSeriesFile::open_relation_file_sync(&base, identity.clone(), true).unwrap(); + file.insert_sync(42, 9, b"payload").unwrap(); + file.delete_file_sync().unwrap(); + + let stray = TimeSeriesFile::open_ts_file_sync(&path, true).unwrap(); + stray.close_sync().unwrap(); + assert!(path.exists()); + + let err = TimeSeriesFile::open_relation_file_sync(&base, identity, false) + .err() + .unwrap(); + assert!(!path.exists()); + assert!(err.to_string().contains("open file error")); + std::fs::remove_dir_all(base).unwrap(); + } +} diff --git a/mudu_kernel/src/storage/worker_kv_store.rs b/mudu_kernel/src/storage/worker_kv_store.rs deleted file mode 100644 index 05b4e9c..0000000 --- a/mudu_kernel/src/storage/worker_kv_store.rs +++ /dev/null @@ -1,431 +0,0 @@ -use crate::contract::timestamp::Timestamp; -use crate::contract::version_tuple::VersionTuple; -use crate::x_log::worker_kv_log::WorkerKvLog; -use mudu::common::buf::Buf; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use std::collections::BTreeMap; -use std::ops::Bound::{Excluded, Included, Unbounded}; -use std::sync::{Arc, Mutex}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct KvItem { - pub key: Vec, - pub value: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct WorkerSnapshot { - xid: u64, - running: Vec, -} - -#[derive(Clone)] -pub struct WorkerKvStore { - worker_id: usize, - inner: Arc>, - log: WorkerKvLog, -} - -#[derive(Default)] -struct WorkerKvState { - rows: BTreeMap, Vec>, - snapshot_mgr: WorkerSnapshotMgr, -} - -#[derive(Default)] -struct WorkerSnapshotMgr { - next_ts: u64, - running: Vec, -} - -impl WorkerKvStore { - pub fn new(worker_id: usize, log: WorkerKvLog) -> Self { - Self { - worker_id, - inner: Arc::new(Mutex::new(WorkerKvState::default())), - log, - } - } - - pub fn worker_id(&self) -> usize { - self.worker_id - } - - pub fn begin_tx(&self) -> RS { - let mut guard = self - .inner - .lock() - .map_err(|_| m_error!(EC::InternalErr, "worker kv store lock poisoned"))?; - Ok(guard.snapshot_mgr.begin_tx()) - } - - pub fn rollback_tx(&self, xid: u64) -> RS<()> { - let mut guard = self - .inner - .lock() - .map_err(|_| m_error!(EC::InternalErr, "worker kv store lock poisoned"))?; - guard.snapshot_mgr.end_tx(xid) - } - - pub fn get>(&self, key: K) -> RS>> { - let guard = self - .inner - .lock() - .map_err(|_| m_error!(EC::InternalErr, "worker kv store lock poisoned"))?; - Ok(guard.get_latest(key.as_ref())) - } - - pub fn get_with_snapshot>( - &self, - snapshot: &WorkerSnapshot, - key: K, - ) -> RS>> { - let guard = self - .inner - .lock() - .map_err(|_| m_error!(EC::InternalErr, "worker kv store lock poisoned"))?; - Ok(guard.get_with_snapshot(snapshot, key.as_ref())) - } - - pub fn put, V: Into>(&self, key: K, value: V) -> RS<()> { - let key = key.into(); - let value = value.into(); - self.log.append_put(&key, &value)?; - self.put_local(key, value) - } - - pub fn put_local, V: Into>(&self, key: K, value: V) -> RS<()> { - let key = key.into(); - let value = value.into(); - let mut guard = self - .inner - .lock() - .map_err(|_| m_error!(EC::InternalErr, "worker kv store lock poisoned"))?; - let commit_ts = guard.snapshot_mgr.alloc_committed_ts(); - guard.put_version(key, value, commit_ts); - Ok(()) - } - - pub fn commit_put_batch( - &self, - snapshot: &WorkerSnapshot, - xid: u64, - items: Vec<(Vec, Vec)>, - ) -> RS<()> { - if items.is_empty() { - return self.rollback_tx(xid); - } - let mut guard = self - .inner - .lock() - .map_err(|_| m_error!(EC::InternalErr, "worker kv store lock poisoned"))?; - for (key, _) in &items { - if guard.has_write_conflict(snapshot, key) { - guard.snapshot_mgr.end_tx(xid)?; - return Err(m_error!( - EC::TxErr, - format!( - "write-write conflict on key {:?} for transaction {}", - String::from_utf8_lossy(key), - xid - ) - )); - } - } - let mut payload = Vec::new(); - for (key, value) in &items { - payload.extend_from_slice(&WorkerKvLog::encode_put_record(key, value)); - } - self.log.append_raw(&payload)?; - self.log.flush()?; - for (key, value) in items { - guard.put_version(key, value, xid); - } - guard.snapshot_mgr.end_tx(xid)?; - Ok(()) - } - - pub fn range_scan, E: AsRef<[u8]>>( - &self, - start_key: S, - end_key: E, - ) -> RS> { - self.range_scan_internal(None, start_key.as_ref(), end_key.as_ref()) - } - - pub fn range_scan_with_snapshot, E: AsRef<[u8]>>( - &self, - snapshot: &WorkerSnapshot, - start_key: S, - end_key: E, - ) -> RS> { - self.range_scan_internal(Some(snapshot), start_key.as_ref(), end_key.as_ref()) - } - - fn range_scan_internal( - &self, - snapshot: Option<&WorkerSnapshot>, - start_key: &[u8], - end_key: &[u8], - ) -> RS> { - let start_key = start_key.to_vec(); - let end_key = end_key.to_vec(); - let guard = self - .inner - .lock() - .map_err(|_| m_error!(EC::InternalErr, "worker kv store lock poisoned"))?; - let iter = if end_key.is_empty() { - guard.rows.range((Included(start_key), Unbounded)) - } else { - guard.rows.range((Included(start_key), Excluded(end_key))) - }; - Ok(iter - .filter_map(|(key, versions)| { - let visible = match snapshot { - Some(snapshot) => latest_visible_version_for_snapshot(versions, snapshot), - None => latest_version(versions), - }?; - Some(KvItem { - key: key.clone(), - value: visible.tuple().clone(), - }) - }) - .collect()) - } -} - -impl WorkerSnapshot { - pub fn xid(&self) -> u64 { - self.xid - } -} - -impl WorkerKvState { - fn get_latest(&self, key: &[u8]) -> Option> { - self.rows - .get(key) - .and_then(|versions| latest_version(versions)) - .map(|version| version.tuple().clone()) - } - - fn get_with_snapshot(&self, snapshot: &WorkerSnapshot, key: &[u8]) -> Option> { - self.rows - .get(key) - .and_then(|versions| latest_visible_version_for_snapshot(versions, snapshot)) - .map(|version| version.tuple().clone()) - } - - fn put_version(&mut self, key: Vec, value: Vec, commit_ts: u64) { - self.rows.entry(key).or_default().push(VersionTuple::new( - Timestamp::new(commit_ts, u64::MAX), - value, - )); - } - - fn has_write_conflict(&self, snapshot: &WorkerSnapshot, key: &[u8]) -> bool { - self.rows - .get(key) - .and_then(|versions| latest_version(versions)) - .map(|latest| !is_visible_to_snapshot(latest.timestamp().c_min(), snapshot)) - .unwrap_or(false) - } -} - -impl WorkerSnapshotMgr { - fn begin_tx(&mut self) -> WorkerSnapshot { - self.next_ts += 1; - let xid = self.next_ts; - let snapshot = WorkerSnapshot { - xid, - running: self.running.clone(), - }; - insert_sorted_unique(&mut self.running, xid); - snapshot - } - - fn alloc_committed_ts(&mut self) -> u64 { - self.next_ts += 1; - self.next_ts - } - - fn end_tx(&mut self, xid: u64) -> RS<()> { - match self.running.binary_search(&xid) { - Ok(index) => { - self.running.remove(index); - Ok(()) - } - Err(_) => Err(m_error!( - EC::NoSuchElement, - format!("transaction {} is not active", xid) - )), - } - } -} - -fn latest_version(versions: &[VersionTuple]) -> Option<&VersionTuple> { - versions.last() -} - -fn latest_visible_version_for_snapshot<'a>( - versions: &'a [VersionTuple], - snapshot: &WorkerSnapshot, -) -> Option<&'a VersionTuple> { - versions - .iter() - .rev() - .find(|version| is_visible_to_snapshot(version.timestamp().c_min(), snapshot)) -} - -fn is_visible_to_snapshot(version_xid: u64, snapshot: &WorkerSnapshot) -> bool { - if version_xid > snapshot.xid { - return false; - } - snapshot.running.binary_search(&version_xid).is_err() -} - -fn insert_sorted_unique(values: &mut Vec, value: u64) { - match values.binary_search(&value) { - Ok(_) => {} - Err(index) => values.insert(index, value), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::x_log::worker_kv_log::{WorkerKvLog, WorkerLogLayout}; - use mudu::common::id::gen_oid; - use std::env::temp_dir; - - fn test_store(prefix: &str) -> WorkerKvStore { - let dir = temp_dir().join(format!("{}_{}", prefix, gen_oid())); - let layout = WorkerLogLayout::new(dir, gen_oid(), 4096).unwrap(); - let log = WorkerKvLog::new(layout).unwrap(); - WorkerKvStore::new(1, log) - } - - #[test] - fn worker_store_persists_local_state() { - let store = test_store("worker_kv_store_test"); - store.put(b"a".to_vec(), b"1".to_vec()).unwrap(); - store.put(b"b".to_vec(), b"2".to_vec()).unwrap(); - assert_eq!(store.get(b"a").unwrap(), Some(b"1".to_vec())); - let rows = store.range_scan(b"a", b"c").unwrap(); - assert_eq!(rows.len(), 2); - } - - #[test] - fn worker_store_snapshot_does_not_see_later_commit() { - let store = test_store("worker_kv_store_snapshot"); - store.put(b"a".to_vec(), b"0".to_vec()).unwrap(); - let snapshot = store.begin_tx().unwrap(); - store.put(b"a".to_vec(), b"1".to_vec()).unwrap(); - - assert_eq!( - store.get_with_snapshot(&snapshot, b"a").unwrap(), - Some(b"0".to_vec()) - ); - assert_eq!(store.get(b"a").unwrap(), Some(b"1".to_vec())); - } - - #[test] - fn worker_store_snapshot_range_is_stable() { - let store = test_store("worker_kv_store_range_snapshot"); - store.put(b"a".to_vec(), b"1".to_vec()).unwrap(); - let snapshot = store.begin_tx().unwrap(); - store.put(b"b".to_vec(), b"2".to_vec()).unwrap(); - store.put(b"c".to_vec(), b"3".to_vec()).unwrap(); - - let rows = store - .range_scan_with_snapshot(&snapshot, b"a", b"z") - .unwrap(); - assert_eq!( - rows, - vec![KvItem { - key: b"a".to_vec(), - value: b"1".to_vec() - }] - ); - } - - #[test] - fn worker_store_commit_makes_tx_visible_to_future_snapshots() { - let store = test_store("worker_kv_store_commit_visible"); - let first = store.begin_tx().unwrap(); - store - .commit_put_batch(&first, first.xid(), vec![(b"a".to_vec(), b"1".to_vec())]) - .unwrap(); - let later = store.begin_tx().unwrap(); - - assert_eq!( - store.get_with_snapshot(&later, b"a").unwrap(), - Some(b"1".to_vec()) - ); - } - - #[test] - fn worker_store_rollback_keeps_previous_visible_version() { - let store = test_store("worker_kv_store_rollback_visible"); - store.put(b"a".to_vec(), b"base".to_vec()).unwrap(); - let snapshot = store.begin_tx().unwrap(); - store.rollback_tx(snapshot.xid()).unwrap(); - - assert_eq!(store.get(b"a").unwrap(), Some(b"base".to_vec())); - } - - #[test] - fn worker_store_multiple_versions_choose_latest_visible() { - let store = test_store("worker_kv_store_multiversion"); - store.put(b"a".to_vec(), b"v0".to_vec()).unwrap(); - let old_snapshot = store.begin_tx().unwrap(); - store.put(b"a".to_vec(), b"v1".to_vec()).unwrap(); - let new_snapshot = store.begin_tx().unwrap(); - - assert_eq!( - store.get_with_snapshot(&old_snapshot, b"a").unwrap(), - Some(b"v0".to_vec()) - ); - assert_eq!( - store.get_with_snapshot(&new_snapshot, b"a").unwrap(), - Some(b"v1".to_vec()) - ); - } - - #[test] - fn worker_store_first_committer_wins_on_same_key() { - let store = test_store("worker_kv_store_first_committer"); - store.put(b"a".to_vec(), b"base".to_vec()).unwrap(); - - let tx1 = store.begin_tx().unwrap(); - let tx2 = store.begin_tx().unwrap(); - - store - .commit_put_batch(&tx1, tx1.xid(), vec![(b"a".to_vec(), b"v1".to_vec())]) - .unwrap(); - let err = store - .commit_put_batch(&tx2, tx2.xid(), vec![(b"a".to_vec(), b"v2".to_vec())]) - .unwrap_err(); - - assert!(err.to_string().contains("write-write conflict")); - assert_eq!(store.get(b"a").unwrap(), Some(b"v1".to_vec())); - } - - #[test] - fn worker_store_allows_concurrent_commits_on_different_keys() { - let store = test_store("worker_kv_store_disjoint_keys"); - let tx1 = store.begin_tx().unwrap(); - let tx2 = store.begin_tx().unwrap(); - - store - .commit_put_batch(&tx1, tx1.xid(), vec![(b"a".to_vec(), b"v1".to_vec())]) - .unwrap(); - store - .commit_put_batch(&tx2, tx2.xid(), vec![(b"b".to_vec(), b"v2".to_vec())]) - .unwrap(); - - assert_eq!(store.get(b"a").unwrap(), Some(b"v1".to_vec())); - assert_eq!(store.get(b"b").unwrap(), Some(b"v2".to_vec())); - } -} diff --git a/mudu_kernel/src/tx/mod.rs b/mudu_kernel/src/tx/mod.rs index 3fb0434..7334373 100644 --- a/mudu_kernel/src/tx/mod.rs +++ b/mudu_kernel/src/tx/mod.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + mod lock_slot; pub mod lock_table; diff --git a/mudu_kernel/src/tx/test_x_snap_mgr.rs b/mudu_kernel/src/tx/test_x_snap_mgr.rs index 8e7c23b..5c7f6a9 100644 --- a/mudu_kernel/src/tx/test_x_snap_mgr.rs +++ b/mudu_kernel/src/tx/test_x_snap_mgr.rs @@ -6,7 +6,7 @@ mod _test { use mudu_utils::log::log_setup; use mudu_utils::task::spawn_local_task; use std::sync::{Arc, Mutex}; - use std::time::{Duration, Instant}; + use std::time::Duration; use tokio::runtime::Builder; use tokio::task::LocalSet; use tracing::info; @@ -17,7 +17,6 @@ mod _test { let canceller = NotifyWait::new(); let x_snap_mgr = XSnapMgr::new(canceller.clone(), 100, 10); let handler = x_snap_mgr.snap_assign_task(); - let c = canceller.clone(); let thread = std::thread::spawn(move || { let ls = LocalSet::new(); ls.spawn_local(async move { @@ -37,7 +36,7 @@ mod _test { fn run_request(request: SnapshotRequester, num_x: usize, num_task: usize, num_threads: usize) { let mut threads = vec![]; let duration = Arc::new(Mutex::new(Duration::new(0, 0))); - for i in 0..num_threads { + for _i in 0..num_threads { let r = request.clone(); let d = duration.clone(); let thd = std::thread::spawn(move || { @@ -115,7 +114,7 @@ mod _test { let mut xids = vec![]; let mut duration = Duration::new(0, 0); for _i in 0..n { - let start = Instant::now(); + let start = mudu_sys::time::instant_now(); let snapshot = requester.start_tx().await; duration += start.elapsed(); xids.push(snapshot.unwrap().xid()); diff --git a/mudu_kernel/src/wal/log_frame.rs b/mudu_kernel/src/wal/log_frame.rs new file mode 100644 index 0000000..0c29c06 --- /dev/null +++ b/mudu_kernel/src/wal/log_frame.rs @@ -0,0 +1,277 @@ +use crate::wal::lsn::LSN; +use mudu::common::crc::calc_crc; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::sync::atomic::{AtomicU32, Ordering}; + +const LOG_FRAME_MAGIC: u32 = 0x584C_5042; // "XLPB" +pub const LOG_FRAME_HEADER_SIZE: usize = 16; +pub const LOG_FRAME_TAILER_SIZE: usize = 8; + +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct LogFrameHeader { + magic: u32, + lsn: LSN, + size: u32, + n_part: u32, +} + +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct LogFrameTailer { + n_part: u32, + checksum: u32, +} + +impl LogFrameHeader { + fn new(lsn: LSN, n_part: u32, size: usize) -> Self { + Self { + magic: LOG_FRAME_MAGIC, + lsn, + n_part, + size: size as u32, + } + } + + pub fn n_part(&self) -> u32 { + self.n_part + } + + pub fn lsn(&self) -> LSN { + self.lsn + } + + pub fn size(&self) -> u32 { + self.size + } + + fn encode(&self, out: &mut Vec) { + out.extend_from_slice(&self.magic.to_be_bytes()); + out.extend_from_slice(&self.lsn.to_be_bytes()); + out.extend_from_slice(&self.size.to_be_bytes()); + out.extend_from_slice(&self.n_part.to_be_bytes()); + } + + pub(crate) fn decode(input: &[u8]) -> RS { + if input.len() < LOG_FRAME_HEADER_SIZE { + return Err(m_error!(EC::DecodeErr, "log frame header is truncated")); + } + let magic = u32::from_be_bytes(input[0..4].try_into().unwrap()); + let lsn = u32::from_be_bytes(input[4..8].try_into().unwrap()) as LSN; + let size = u32::from_be_bytes(input[8..12].try_into().unwrap()); + let n_part = u32::from_be_bytes(input[12..16].try_into().unwrap()); + if magic != LOG_FRAME_MAGIC { + return Err(m_error!(EC::DecodeErr, "invalid log frame magic")); + } + Ok(Self { + magic, + lsn, + size, + n_part, + }) + } +} + +impl LogFrameTailer { + fn new(n_part: u32, payload: &[u8]) -> Self { + Self { + n_part, + checksum: payload_checksum(payload), + } + } + + pub fn n_part(&self) -> u32 { + self.n_part + } + + pub fn checksum(&self) -> u32 { + self.checksum + } + + fn encode(&self, out: &mut Vec) { + out.extend_from_slice(&self.n_part.to_be_bytes()); + out.extend_from_slice(&self.checksum.to_be_bytes()); + } + + fn decode(input: &[u8]) -> RS { + if input.len() < LOG_FRAME_TAILER_SIZE { + return Err(m_error!(EC::DecodeErr, "log frame tailer is truncated")); + } + let n_part = u32::from_be_bytes(input[0..4].try_into().unwrap()); + let checksum = u32::from_be_bytes(input[4..8].try_into().unwrap()); + Ok(Self { n_part, checksum }) + } +} + +pub fn serialize_entry( + value: &L, + max_part_size: usize, + next_lsn: &AtomicU32, +) -> RS>> { + let payload = rmp_serde::to_vec(value) + .map_err(|e| m_error!(EC::EncodeErr, "encode log entry to msgpack error", e))?; + if max_part_size <= LOG_FRAME_HEADER_SIZE + LOG_FRAME_TAILER_SIZE { + return Err(m_error!( + EC::ParseErr, + "max_part_size must be larger than header + tailer" + )); + } + + let max_payload_size = max_part_size - LOG_FRAME_HEADER_SIZE - LOG_FRAME_TAILER_SIZE; + let total_parts = payload.len().div_ceil(max_payload_size).max(1); + let mut result = Vec::with_capacity(total_parts); + for (index, chunk) in payload.chunks(max_payload_size).enumerate() { + let remaining = (total_parts - index - 1) as u32; + let lsn = next_lsn.fetch_add(1, Ordering::SeqCst); + let header = LogFrameHeader::new(lsn, remaining, chunk.len()); + let tailer = LogFrameTailer::new(remaining, chunk); + let mut frame = + Vec::with_capacity(LOG_FRAME_HEADER_SIZE + chunk.len() + LOG_FRAME_TAILER_SIZE); + header.encode(&mut frame); + frame.extend_from_slice(chunk); + tailer.encode(&mut frame); + result.push(frame); + } + + Ok(result) +} + +pub fn frame_lsn(frame: &[u8]) -> RS { + Ok(split_frame(frame)?.0.lsn()) +} + +pub fn frame_lsns(frames: &[Vec]) -> RS> { + frames.iter().map(|frame| frame_lsn(frame)).collect() +} + +pub fn last_frame_lsn(frames: &[Vec]) -> RS { + let frame = frames + .last() + .ok_or_else(|| m_error!(EC::DecodeErr, "log frames are empty"))?; + frame_lsn(frame) +} + +pub fn deserialize_entry(parts: &[Vec]) -> RS { + let payload = deserialize_frames_payload(parts)?; + rmp_serde::from_slice(&payload) + .map_err(|e| m_error!(EC::DecodeErr, "decode log entry from msgpack error", e)) +} + +pub fn split_frame(frame: &[u8]) -> RS<(LogFrameHeader, Vec, LogFrameTailer)> { + let expected_len = frame_len(frame)?; + if frame.len() != expected_len { + return Err(m_error!( + EC::DecodeErr, + format!( + "log frame length mismatch, expected {}, got {}", + expected_len, + frame.len() + ) + )); + } + split_frame_exact(frame) +} + +pub fn frame_len(input: &[u8]) -> RS { + if input.len() < LOG_FRAME_HEADER_SIZE + LOG_FRAME_TAILER_SIZE { + return Err(m_error!(EC::DecodeErr, "log frame is truncated")); + } + let header = LogFrameHeader::decode(&input[..LOG_FRAME_HEADER_SIZE])?; + let payload_end = LOG_FRAME_HEADER_SIZE + header.size() as usize; + let expected_len = payload_end + LOG_FRAME_TAILER_SIZE; + if input.len() < expected_len { + return Err(m_error!( + EC::DecodeErr, + format!( + "log frame is truncated, expected at least {}, got {}", + expected_len, + input.len() + ) + )); + } + Ok(expected_len) +} + +pub fn decode_entries_with_pending( + frames: &[Vec], + pending_frames: &mut Vec>, + pending_start_lsn: &mut Option, +) -> RS> { + let mut result = Vec::new(); + if pending_start_lsn.is_none() && !pending_frames.is_empty() { + let (header, _, _) = split_frame(&pending_frames[0])?; + *pending_start_lsn = Some(header.lsn()); + } + for frame in frames { + let (header, _, _) = split_frame(frame)?; + if pending_frames.is_empty() { + *pending_start_lsn = Some(header.lsn()); + } + pending_frames.push(frame.clone()); + if header.n_part() != 0 { + continue; + } + + let entry = deserialize_entry(pending_frames)?; + let start_lsn = pending_start_lsn.take().ok_or_else(|| { + m_error!( + EC::InternalErr, + "missing starting lsn for decoded log entry" + ) + })?; + pending_frames.clear(); + result.push((start_lsn, entry)); + } + Ok(result) +} + +fn split_frame_exact(frame: &[u8]) -> RS<(LogFrameHeader, Vec, LogFrameTailer)> { + let header = LogFrameHeader::decode(&frame[..LOG_FRAME_HEADER_SIZE])?; + let payload_end = LOG_FRAME_HEADER_SIZE + header.size() as usize; + let payload = frame[LOG_FRAME_HEADER_SIZE..payload_end].to_vec(); + let tailer = LogFrameTailer::decode(&frame[payload_end..])?; + if header.n_part() != tailer.n_part() { + return Err(m_error!( + EC::DecodeErr, + "log frame header/tailer n_part mismatch" + )); + } + if payload_checksum(&payload) != tailer.checksum() { + return Err(m_error!( + EC::DecodeErr, + "log frame payload checksum mismatch" + )); + } + Ok((header, payload, tailer)) +} + +fn deserialize_frames_payload(frames: &[Vec]) -> RS> { + if frames.is_empty() { + return Err(m_error!(EC::DecodeErr, "log frames are empty")); + } + + let mut payload = Vec::new(); + let total_parts = frames.len(); + for (index, frame) in frames.iter().enumerate() { + let (header, body, _tailer) = split_frame(frame)?; + let expected_remaining = (total_parts - index - 1) as u32; + if header.n_part() != expected_remaining { + return Err(m_error!( + EC::DecodeErr, + format!( + "unexpected log frame order, expected remaining {}, got {}", + expected_remaining, + header.n_part() + ) + )); + } + payload.extend_from_slice(&body); + } + Ok(payload) +} + +fn payload_checksum(payload: &[u8]) -> u32 { + calc_crc(payload) as u32 +} diff --git a/mudu_kernel/src/wal/lsn.rs b/mudu_kernel/src/wal/lsn.rs new file mode 100644 index 0000000..f142734 --- /dev/null +++ b/mudu_kernel/src/wal/lsn.rs @@ -0,0 +1 @@ +pub type LSN = u32; diff --git a/mudu_kernel/src/wal/mod.rs b/mudu_kernel/src/wal/mod.rs new file mode 100644 index 0000000..cef4438 --- /dev/null +++ b/mudu_kernel/src/wal/mod.rs @@ -0,0 +1,16 @@ +mod test_xl_batch; +mod xid; +mod xl_c_abort; +pub mod xl_data_op; +pub mod xl_entry; + +pub mod log_frame; +pub mod lsn; +pub mod pl_batch; +pub mod pl_batch_worker_log; +pub mod pl_entry; +pub mod typed_worker_log; +pub mod worker_log; +mod worker_wal_backend; +pub mod xl_batch; +pub mod xl_batch_worker_log; diff --git a/mudu_kernel/src/wal/pl_batch.rs b/mudu_kernel/src/wal/pl_batch.rs new file mode 100644 index 0000000..805b518 --- /dev/null +++ b/mudu_kernel/src/wal/pl_batch.rs @@ -0,0 +1,17 @@ +use crate::wal::pl_entry::PLEntry; +use serde::{Deserialize, Serialize}; + +/// A batch of physical log entries. +/// +/// [`PLBatch`] groups physical log records that describe updates to +/// corresponding pages in files. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct PLBatch { + pub entries: Vec, +} + +pub use crate::wal::pl_batch_worker_log::{ + append_pl_batch, append_pl_batch_async, decode_pl_batches, decode_pl_batches_with_pending, + deserialize_pl_batch, new_pl_batch_worker_log, new_pl_batch_writer, serialize_pl_batch, + NoopPLBatchRecoveryHandler, PLBatchWorkerLog, +}; diff --git a/mudu_kernel/src/wal/pl_batch_worker_log.rs b/mudu_kernel/src/wal/pl_batch_worker_log.rs new file mode 100644 index 0000000..613df02 --- /dev/null +++ b/mudu_kernel/src/wal/pl_batch_worker_log.rs @@ -0,0 +1,175 @@ +use crate::wal::log_frame::decode_entries_with_pending; +use crate::wal::log_frame::{deserialize_entry, serialize_entry}; +use crate::wal::lsn::LSN; +use crate::wal::pl_batch::PLBatch; +use crate::wal::typed_worker_log::{TypedWorkerLog, WorkerLogRecoveryHandler}; +use crate::wal::worker_log::WorkerLogBackend; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use std::sync::atomic::AtomicU32; + +/// Typed worker-log wrapper specialized for [`PLBatch`]. +/// +/// This wrapper is intended for physical redo records that describe page-level +/// mutations, such as partial byte updates to a specific page in a file. +pub type PLBatchWorkerLog = TypedWorkerLog; + +/// No-op recovery handler for write-only physical-log paths. +pub struct NoopPLBatchRecoveryHandler; + +impl WorkerLogRecoveryHandler for NoopPLBatchRecoveryHandler { + fn handle_entry(&self, _entry: PLBatch, _start_lsn: LSN) -> RS<()> { + Ok(()) + } +} + +pub fn new_pl_batch_worker_log(backend: B, handler: H) -> PLBatchWorkerLog +where + B: WorkerLogBackend, + H: WorkerLogRecoveryHandler, +{ + TypedWorkerLog::new(backend, handler) +} + +/// Builds a [`PLBatchWorkerLog`] for append/flush paths. +pub fn new_pl_batch_writer(backend: B) -> PLBatchWorkerLog +where + B: WorkerLogBackend, +{ + TypedWorkerLog::new(backend, NoopPLBatchRecoveryHandler) +} + +pub fn serialize_pl_batch( + batch: &PLBatch, + max_part_size: usize, + next_lsn: &AtomicU32, +) -> RS>> { + serialize_entry(batch, max_part_size, next_lsn) +} + +pub fn deserialize_pl_batch(parts: &[Vec]) -> RS { + deserialize_entry(parts) +} + +pub fn decode_pl_batches(frames: &[Vec]) -> RS> { + let mut pending = Vec::new(); + let mut pending_start_lsn = None; + let batches = decode_pl_batches_with_pending(frames, &mut pending, &mut pending_start_lsn)?; + if !pending.is_empty() { + return Err(m_error!(EC::DecodeErr, "trailing partial pl batch frames")); + } + Ok(batches) +} + +pub fn decode_pl_batches_with_pending( + frames: &[Vec], + pending: &mut Vec>, + pending_start_lsn: &mut Option, +) -> RS> { + Ok( + decode_entries_with_pending(frames, pending, pending_start_lsn)? + .into_iter() + .map(|(_, batch)| batch) + .collect(), + ) +} + +pub fn append_pl_batch(backend: &B, batch: &PLBatch) -> RS<()> { + let frames = backend.serialize_entry(batch)?; + backend.append_frames_sync(frames) +} + +pub async fn append_pl_batch_async(backend: &B, batch: &PLBatch) -> RS { + let frames = backend.serialize_entry(batch)?; + backend.append_frames_async(frames).await +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::wal::log_frame::{ + frame_lsns, split_frame, LOG_FRAME_HEADER_SIZE, LOG_FRAME_TAILER_SIZE, + }; + use crate::wal::pl_entry::{PLEntry, PLFileId, PLOp, PageUpdate}; + + fn sample_batch(entry_count: usize, patch_size: usize) -> PLBatch { + let mut entries = Vec::with_capacity(entry_count); + for i in 0..entry_count { + entries.push(PLEntry { + file: PLFileId { + partition_id: 7, + table_id: 100u128 + i as u128, + file_index: 0, + }, + ops: vec![ + PLOp::Create, + PLOp::PageUpdate(PageUpdate { + page_id: i as u32, + offset: 16, + data: vec![i as u8 + 1; patch_size], + }), + ], + }); + } + PLBatch { entries } + } + + #[test] + fn pl_batch_single_part_round_trip() { + let batch = sample_batch(1, 24); + let next_lsn = AtomicU32::new(0); + let parts = serialize_pl_batch(&batch, 4096, &next_lsn).unwrap(); + let lsns = frame_lsns(&parts).unwrap(); + assert_eq!(parts.len(), 1); + assert_eq!(lsns, vec![0]); + let (header, payload, tailer) = split_frame(&parts[0]).unwrap(); + assert_eq!(header.lsn(), 0); + assert_eq!(header.n_part(), 0); + assert_eq!(tailer.n_part(), 0); + assert_eq!(payload.len(), header.size() as usize); + assert_eq!(deserialize_pl_batch(&parts).unwrap(), batch); + } + + #[test] + fn pl_batch_splits_large_payload_into_multiple_parts() { + let batch = sample_batch(4, 256); + let next_lsn = AtomicU32::new(7); + let parts = serialize_pl_batch(&batch, 180, &next_lsn).unwrap(); + let lsns = frame_lsns(&parts).unwrap(); + assert!(parts.len() > 1); + assert_eq!(lsns.len(), parts.len()); + for (index, part) in parts.iter().enumerate() { + assert!(part.len() <= 180); + let (header, _, tailer) = split_frame(part).unwrap(); + let expected = (parts.len() - index - 1) as u32; + assert_eq!(header.lsn(), lsns[index]); + assert_eq!(header.n_part(), expected); + assert_eq!(tailer.n_part(), expected); + } + assert_eq!(deserialize_pl_batch(&parts).unwrap(), batch); + } + + #[test] + fn pl_batch_rejects_corrupted_payload_checksum() { + let batch = sample_batch(1, 32); + let next_lsn = AtomicU32::new(0); + let mut parts = serialize_pl_batch(&batch, 4096, &next_lsn).unwrap(); + parts[0][LOG_FRAME_HEADER_SIZE] ^= 0x7f; + let err = deserialize_pl_batch(&parts).unwrap_err(); + assert!(err.to_string().contains("checksum")); + } + + #[test] + fn pl_batch_rejects_invalid_part_size_configuration() { + let batch = sample_batch(1, 8); + let next_lsn = AtomicU32::new(0); + let err = serialize_pl_batch( + &batch, + LOG_FRAME_HEADER_SIZE + LOG_FRAME_TAILER_SIZE, + &next_lsn, + ) + .unwrap_err(); + assert!(err.to_string().contains("max_part_size")); + } +} diff --git a/mudu_kernel/src/wal/pl_entry.rs b/mudu_kernel/src/wal/pl_entry.rs new file mode 100644 index 0000000..d4f8365 --- /dev/null +++ b/mudu_kernel/src/wal/pl_entry.rs @@ -0,0 +1,57 @@ +use mudu::common::id::OID; +use serde::{Deserialize, Serialize}; + +/// Stable physical file identity used by time-series WAL records. +/// +/// The corresponding on-disk relation file is addressed as: +/// `{partition_id}.{table_id}.{file_index}.dat`. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct PLFileId { + pub partition_id: OID, + pub table_id: OID, + pub file_index: u32, +} + +/// A physical log entry for one file object. +/// +/// [`PLEntry`] describes physical updates to pages in the corresponding file, +/// rather than a logical SQL-level operation. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct PLEntry { + /// Target file object identity. + pub file: PLFileId, + /// Ordered physical operations to apply to that file object. + /// + /// The operations are replayed in sequence and together describe the + /// low-level file/page changes captured by this log entry. + pub ops: Vec, +} + +/// Physical page-level operations captured in the log. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub enum PLOp { + /// Create the target file object identified by [`PLEntry::file`]. + Create, + /// Delete the target file object identified by [`PLEntry::file`]. + Delete, + /// Apply an in-place byte-range update to one page in the file object. + PageUpdate(PageUpdate), +} + +/// A physical delta applied to a page in a file. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct PageUpdate { + /// Logical page number inside the target file object. + /// + /// Recovery uses this to locate which page should receive the byte patch. + pub page_id: u32, + /// Byte offset from the start of the page where the patch begins. + /// + /// The update writes `data.len()` bytes starting at this offset. + pub offset: u32, + /// Replacement bytes to copy into the page at [`PageUpdate::offset`]. + /// + /// This is a partial page image, not necessarily a full page. Callers must + /// ensure `offset + data.len()` does not exceed the page size. + pub data: Vec, +} diff --git a/mudu_kernel/src/x_log/test_xl_batch.rs b/mudu_kernel/src/wal/test_xl_batch.rs similarity index 100% rename from mudu_kernel/src/x_log/test_xl_batch.rs rename to mudu_kernel/src/wal/test_xl_batch.rs diff --git a/mudu_kernel/src/wal/typed_worker_log.rs b/mudu_kernel/src/wal/typed_worker_log.rs new file mode 100644 index 0000000..7ebfaf1 --- /dev/null +++ b/mudu_kernel/src/wal/typed_worker_log.rs @@ -0,0 +1,265 @@ +use crate::wal::log_frame::decode_entries_with_pending; +use crate::wal::lsn::LSN; +use crate::wal::worker_log::{decode_frames, WorkerLogBackend, WorkerLogRecoverySource}; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::future::Future; +use std::marker::PhantomData; + +pub trait WorkerLogRecoveryHandler: Send + Sync + 'static +where + L: Serialize + DeserializeOwned + Send + Sync + 'static, +{ + fn handle_entry(&self, entry: L, start_lsn: LSN) -> RS<()>; + + fn finish(&self) -> RS<()> { + Ok(()) + } +} + +pub struct TypedWorkerLog +where + L: Serialize + DeserializeOwned + Send + Sync + 'static, + B: WorkerLogBackend, + H: WorkerLogRecoveryHandler, +{ + backend: B, + handler: H, + _marker: PhantomData L>, +} + +impl TypedWorkerLog +where + L: Serialize + DeserializeOwned + Send + Sync + 'static, + B: WorkerLogBackend, + H: WorkerLogRecoveryHandler, +{ + pub fn new(backend: B, handler: H) -> Self { + Self { + backend, + handler, + _marker: PhantomData, + } + } + + /// Returns a reference to the wrapped backend. + pub fn backend(&self) -> &B { + &self.backend + } + + pub async fn append(&self, entry: &L) -> RS { + let frames = self.backend.serialize_entry(entry)?; + self.backend.append_frames_async(frames).await + } + + pub async fn append_owned(&self, entry: L) -> RS { + self.append(&entry).await + } + + pub async fn append_callback(&self, entry: L, callback: F) -> RS<()> + where + F: Fn(&L) -> RS, + { + self.append(&entry).await?; + let _ = callback(&entry)?; + Ok(()) + } + + pub async fn append_async_callback(&self, entry: L, callback: F) -> RS<()> + where + F: Fn(&L) -> Fut, + Fut: Future>, + { + self.append(&entry).await?; + let _ = callback(&entry).await?; + Ok(()) + } + + pub fn append_sync(&self, entry: &L) -> RS<()> { + let frames = self.backend.serialize_entry(entry)?; + self.backend.append_frames_sync(frames) + } + + pub fn flush(&self) -> RS<()> { + self.backend.flush() + } + + pub async fn flush_async(&self) -> RS<()> { + self.backend.flush_async().await + } + + pub fn recover(&self, source: &mut S) -> RS<()> + where + S: WorkerLogRecoverySource, + { + let chunk_paths = source.chunk_paths_sorted()?; + let mut pending_frames = Vec::new(); + let mut pending_start_lsn = None; + for path in chunk_paths { + let bytes = source.read_chunk(path.as_path())?; + if bytes.is_empty() { + continue; + } + let frames = decode_frames(&bytes)?; + let entries = decode_entries_with_pending::( + &frames, + &mut pending_frames, + &mut pending_start_lsn, + )?; + for (start_lsn, entry) in entries { + self.handler.handle_entry(entry, start_lsn)?; + } + } + + if !pending_frames.is_empty() { + return Err(m_error!(EC::DecodeErr, "trailing partial log frames")); + } + + self.handler.finish() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::wal::worker_log::{ + ChunkedWorkerLogBackend, WorkerLogBackend, WorkerLogLayout, WorkerLogRecoverySource, + }; + use mudu::common::id::gen_oid; + use serde::{Deserialize, Serialize}; + use std::env::temp_dir; + use std::path::{Path, PathBuf}; + use std::sync::{Arc, Mutex}; + + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] + struct TestEntry { + id: u64, + payload: Vec, + } + + #[derive(Default)] + struct CollectingHandler { + entries: Mutex>, + } + + impl WorkerLogRecoveryHandler for Arc { + fn handle_entry(&self, entry: TestEntry, start_lsn: LSN) -> RS<()> { + self.entries.lock().unwrap().push((start_lsn, entry)); + Ok(()) + } + } + + struct FileRecoverySource { + paths: Vec, + } + + struct NoopHandler; + + impl WorkerLogRecoveryHandler for NoopHandler { + fn handle_entry(&self, _entry: TestEntry, _start_lsn: LSN) -> RS<()> { + Ok(()) + } + } + + impl WorkerLogRecoverySource for FileRecoverySource { + fn chunk_paths_sorted(&mut self) -> RS> { + Ok(self.paths.clone()) + } + + fn read_chunk(&mut self, path: &Path) -> RS> { + std::fs::read(path) + .map_err(|e| m_error!(EC::IOErr, "read worker log chunk for recovery error", e)) + } + } + + #[tokio::test] + async fn typed_worker_log_appends_and_recovers_generic_entries() { + let dir = temp_dir().join(format!("typed_worker_log_{}", gen_oid())); + let raw = ChunkedWorkerLogBackend::new(WorkerLogLayout::new(dir, gen_oid(), 256).unwrap()) + .unwrap(); + let handler = Arc::new(CollectingHandler::default()); + let log = TypedWorkerLog::new(raw.clone(), handler.clone()); + + let first = TestEntry { + id: 1, + payload: vec![1; 32], + }; + let second = TestEntry { + id: 2, + payload: vec![2; 512], + }; + + let _first_last_lsn = log.append(&first).await.unwrap(); + let _second_last_lsn = log.append(&second).await.unwrap(); + raw.flush_async().await.unwrap(); + let mut source = FileRecoverySource { + paths: raw.chunk_paths_sorted().unwrap(), + }; + log.recover(&mut source).unwrap(); + + let recovered = handler.entries.lock().unwrap().clone(); + assert_eq!(recovered, vec![(0, first), (1, second)]); + } + + #[tokio::test] + async fn typed_worker_log_append_callback_runs_after_log_is_persisted() { + let dir = temp_dir().join(format!("typed_worker_log_append_callback_{}", gen_oid())); + let layout = WorkerLogLayout::new(dir, gen_oid(), 4096).unwrap(); + let path = layout.chunk_path(0); + let backend = ChunkedWorkerLogBackend::new(layout).unwrap(); + let log = TypedWorkerLog::new(backend, NoopHandler); + let entry = TestEntry { + id: 7, + payload: vec![7; 32], + }; + + let expected = entry.clone(); + let callback_path = path.clone(); + log.append_callback(entry, move |written| { + assert_eq!(written, &expected); + let bytes = std::fs::read(&callback_path) + .map_err(|e| m_error!(EC::IOErr, "read callback-persisted worker log", e))?; + assert!(!bytes.is_empty()); + Ok(()) + }) + .await + .unwrap(); + } + + #[tokio::test] + async fn typed_worker_log_append_async_callback_runs_after_log_is_persisted() { + let dir = temp_dir().join(format!( + "typed_worker_log_append_async_callback_{}", + gen_oid() + )); + let layout = WorkerLogLayout::new(dir, gen_oid(), 4096).unwrap(); + let path = layout.chunk_path(0); + let backend = ChunkedWorkerLogBackend::new(layout).unwrap(); + let log = TypedWorkerLog::new(backend, NoopHandler); + let entry = TestEntry { + id: 8, + payload: vec![8; 32], + }; + + let expected = entry.clone(); + let callback_path = path.clone(); + log.append_async_callback(entry, move |written| { + let path = callback_path.clone(); + let expected = expected.clone(); + let written = written.clone(); + async move { + assert_eq!(written, expected); + let bytes = tokio::fs::read(&path).await.map_err(|e| { + m_error!(EC::IOErr, "read async callback-persisted worker log", e) + })?; + assert!(!bytes.is_empty()); + Ok(()) + } + }) + .await + .unwrap(); + } +} diff --git a/mudu_kernel/src/wal/worker_log.rs b/mudu_kernel/src/wal/worker_log.rs new file mode 100644 index 0000000..41553a3 --- /dev/null +++ b/mudu_kernel/src/wal/worker_log.rs @@ -0,0 +1,40 @@ +use crate::wal::log_frame::{frame_len, split_frame}; +use crate::wal::lsn::LSN; +pub use crate::wal::worker_wal_backend::{ + WorkerLogBatching, WorkerLogLayout, WorkerLogTail, WorkerWALBackend as ChunkedWorkerLogBackend, +}; +use async_trait::async_trait; +use mudu::common::result::RS; +use serde::Serialize; +use std::path::{Path, PathBuf}; + +#[async_trait] +pub trait WorkerLogBackend: Clone + Send + Sync + 'static { + fn frame_size_limit(&self) -> RS; + + fn serialize_entry(&self, entry: &L) -> RS>>; + fn chunk_paths_sorted(&self) -> RS>; + fn append_frames_sync(&self, frames: Vec>) -> RS<()>; + async fn append_frames_async(&self, frames: Vec>) -> RS; + fn flush(&self) -> RS<()>; + async fn flush_async(&self) -> RS<()>; +} + +pub trait WorkerLogRecoverySource { + fn chunk_paths_sorted(&mut self) -> RS>; + fn read_chunk(&mut self, path: &Path) -> RS>; +} + +pub fn decode_frames(payload: &[u8]) -> RS>> { + let mut offset = 0usize; + let mut frames = Vec::new(); + while offset < payload.len() { + let remaining = &payload[offset..]; + let next_frame_len = frame_len(remaining)?; + let frame = &remaining[..next_frame_len]; + split_frame(frame)?; + frames.push(frame.to_vec()); + offset += next_frame_len; + } + Ok(frames) +} diff --git a/mudu_kernel/src/wal/worker_wal_backend.rs b/mudu_kernel/src/wal/worker_wal_backend.rs new file mode 100644 index 0000000..d1ffa2d --- /dev/null +++ b/mudu_kernel/src/wal/worker_wal_backend.rs @@ -0,0 +1,1289 @@ +use crate::io::file::{self, IoFile}; +use crate::io::worker_ring; +use crate::wal::log_frame::{ + frame_len, frame_lsns, last_frame_lsn, serialize_entry, LogFrameHeader, +}; +use crate::wal::lsn::LSN; +use crate::wal::worker_log::WorkerLogBackend; +use async_trait::async_trait; +use futures::task::noop_waker_ref; +use mudu::common::id::OID; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use serde::Serialize; +use short_uuid::ShortUuid; +use std::collections::{HashMap, HashSet}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; +use std::time::{Duration, Instant}; +use tokio::sync::Notify; +use uuid::Uuid; + +#[derive(Clone)] +pub struct WorkerWALBackend { + inner: Arc, +} + +struct WorkerLogInner { + log_queue: Mutex>, + notify: Notify, + flush_task: Mutex> + Send>>>>, + batching: WorkerLogBatching, + active_sessions: Arc, + // next log sequence + next_lsn: AtomicU32, + + flush_waiter: WaitLsn, + + state: Mutex, +} + +#[derive(Clone, Debug)] +pub struct WorkerLogLayout { + log_dir: PathBuf, + log_oid: OID, + chunk_size: u64, + short_oid: String, + batching: WorkerLogBatching, +} + +#[derive(Clone, Copy, Debug)] +pub struct WorkerLogBatching { + trigger_bytes: usize, + trigger_frames: usize, + max_wait: Duration, + max_batch_bytes: usize, + sessions_per_step: usize, + bytes_per_step: usize, + frames_per_step: usize, + max_trigger_bytes: usize, + max_trigger_frames: usize, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct WorkerLogTail { + pub current_sequence: Option, + pub current_size: u64, + pub next_sequence: u64, + pub next_lsn: LSN, +} + +struct WaitLsn { + next_wait_lsn: AtomicU32, + ready_lsns: Mutex>, + notify: Notify, +} + +struct ChunkedWorkerLog { + layout: WorkerLogLayout, + current_sequence: Option, + current_size: u64, + current_file: Option<(PathBuf, IoFile)>, + // next chunk sequence + next_sequence: u64, +} + +struct AppendReservation { + path: PathBuf, + offset: u64, + flush_after_write: bool, +} + +struct MergedWrite { + path: PathBuf, + offset: u64, + payload: Vec, +} + +struct QueuedLogBatch { + frames: Vec>, + lsns: Vec, + bytes: usize, + enqueued_at: Instant, +} + +struct PreparedFlushBatch { + writes: Vec, + flush_paths: Vec, + ready_lsns: Vec, +} + +#[derive(Clone, Copy)] +struct EffectiveBatching { + trigger_bytes: usize, + trigger_frames: usize, + max_wait: Duration, + max_batch_bytes: usize, +} + +impl WaitLsn { + pub fn new(next_wait_lsn: LSN, ready_lsns: Vec) -> Self { + Self { + next_wait_lsn: AtomicU32::new(next_wait_lsn), + ready_lsns: Mutex::new(ready_lsns), + notify: Notify::new(), + } + } + + pub async fn wait_lsn(&self, lsn: LSN) { + loop { + let notified = self.notify.notified(); + if self.next_wait_lsn.load(Ordering::Acquire) > lsn { + return; + } + notified.await; + } + } + + pub fn ready(&self, lsns: Vec) { + if lsns.is_empty() { + return; + } + let next_wait_lsn = self.next_wait_lsn.load(Ordering::Acquire); + let mut ready_lsns = self + .ready_lsns + .lock() + .expect("worker log ready lsns poisoned"); + ready_lsns.extend(lsns); + ready_lsns.sort_unstable(); + ready_lsns.dedup(); + + let Some(first) = ready_lsns.first().copied() else { + return; + }; + if first != next_wait_lsn { + return; + } + + let mut new_next_wait_lsn = next_wait_lsn; + let mut drain_end = 0usize; + for lsn in ready_lsns.iter().copied() { + if lsn != new_next_wait_lsn { + break; + } + new_next_wait_lsn = new_next_wait_lsn.saturating_add(1); + drain_end += 1; + } + ready_lsns.drain(..drain_end); + drop(ready_lsns); + + self.next_wait_lsn + .store(new_next_wait_lsn, Ordering::Release); + self.notify.notify_waiters(); + } +} + +impl WorkerWALBackend { + fn current_chunk_path(&self) -> RS> { + let guard = self + .inner + .state + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker kv log lock poisoned"))?; + Ok(guard.current_path()) + } + + pub(crate) fn layout(&self) -> RS { + let guard = self + .inner + .state + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker kv log lock poisoned"))?; + Ok(guard.layout.clone()) + } + + fn effective_batching(&self) -> EffectiveBatching { + let active_sessions = self.inner.active_sessions.load(Ordering::Relaxed); + let cfg = self.inner.batching; + let steps = if cfg.sessions_per_step == 0 { + 0 + } else { + active_sessions / cfg.sessions_per_step + }; + let trigger_bytes = cfg + .trigger_bytes + .saturating_add(steps.saturating_mul(cfg.bytes_per_step)) + .min(cfg.max_trigger_bytes.max(cfg.trigger_bytes)); + let trigger_frames = cfg + .trigger_frames + .saturating_add(steps.saturating_mul(cfg.frames_per_step)) + .min(cfg.max_trigger_frames.max(cfg.trigger_frames)); + EffectiveBatching::new( + trigger_bytes, + trigger_frames, + cfg.max_wait, + cfg.max_batch_bytes.max(trigger_bytes), + ) + } + + pub(crate) fn next_flush_deadline(&self) -> RS> { + let flush_task_active = self + .inner + .flush_task + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker log flush task lock poisoned"))? + .is_some(); + if flush_task_active { + return Ok(None); + } + + let queue = self + .inner + .log_queue + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker log queue lock poisoned"))?; + if queue.is_empty() { + return Ok(None); + } + let batching = self.effective_batching(); + if Self::should_start_flush(queue.as_slice(), batching) { + return Ok(Some(mudu_sys::time::instant_now())); + } + let oldest = queue + .iter() + .map(|batch| batch.enqueued_at) + .min() + .expect("non-empty queue must have oldest enqueue time"); + Ok(Some(oldest + batching.max_wait)) + } + + pub(crate) fn poll_flush_log(&self) -> RS { + let mut task = + { + let mut guard = self.inner.flush_task.lock().map_err(|_| { + m_error!(EC::InternalErr, "worker log flush task lock poisoned") + })?; + if guard.is_none() { + let should_start = { + let queue = self.inner.log_queue.lock().map_err(|_| { + m_error!(EC::InternalErr, "worker log queue lock poisoned") + })?; + !queue.is_empty() + && Self::should_start_flush(queue.as_slice(), self.effective_batching()) + }; + if !should_start { + return Ok(false); + } + let log = self.clone(); + *guard = Some(Box::pin(async move { log.run_flush_log().await })); + } + guard.take().expect("flush task must exist") + }; + + let waker = noop_waker_ref(); + let mut cx = Context::from_waker(waker); + match task.as_mut().poll(&mut cx) { + Poll::Ready(result) => { + result?; + Ok(true) + } + Poll::Pending => { + let mut guard = self.inner.flush_task.lock().map_err(|_| { + m_error!(EC::InternalErr, "worker log flush task lock poisoned") + })?; + *guard = Some(task); + Ok(true) + } + } + } + + pub(crate) fn append_log(&self, logs: Vec<(Vec>, Vec)>) { + let mut guard = self.inner.log_queue.lock().unwrap(); + let now = mudu_sys::time::instant_now(); + for (frames, lsns) in logs { + let bytes = frames.iter().map(|frame| frame.len()).sum(); + guard.push(QueuedLogBatch { + frames, + lsns, + bytes, + enqueued_at: now, + }); + } + self.inner.notify.notify_waiters(); + } + + pub fn new(layout: WorkerLogLayout) -> RS { + Self::new_with_active_sessions(layout, Arc::new(AtomicUsize::new(0))) + } + + pub fn new_with_active_sessions( + layout: WorkerLogLayout, + active_sessions: Arc, + ) -> RS { + let tail = layout.scan_tail()?; + Ok(Self { + inner: Arc::new(WorkerLogInner { + log_queue: Mutex::new(Default::default()), + notify: Notify::new(), + flush_task: Mutex::new(None), + batching: layout.batching(), + active_sessions, + next_lsn: AtomicU32::new(tail.next_lsn), + flush_waiter: WaitLsn::new(tail.next_lsn, vec![]), + state: Mutex::new(ChunkedWorkerLog::new(layout, tail)?), + }), + }) + } + pub(crate) async fn append_raw_async_vec( + &self, + payload: Vec>, + lsns: Vec, + ) -> RS<()> { + if payload.is_empty() { + return Ok(()); + } + + if !worker_ring::has_current_worker_ring() { + for frame in &payload { + self.append_raw(frame)?; + } + self.flush()?; + self.complete_persisted_lsns(lsns)?; + return Ok(()); + } + + let reservations = self.reserve_appends(&payload)?; + + let merged_writes = Self::merge_reserved_writes(&reservations, &payload); + let mut write_handles = Vec::with_capacity(merged_writes.len()); + for write in merged_writes { + let file = self.take_or_open_async_file(&write.path).await?; + let write_handle = file::write_submit(&file, write.payload, write.offset)?; + write_handles.push((write.path, file, write_handle)); + } + for (path, file, write_handle) in write_handles { + let write_result = write_handle.wait().await.map(|_| ()); + self.finish_async_file_use(path.as_path(), file, write_result) + .await?; + } + + let flush_paths = Self::collect_flush_paths(&reservations); + + let last_index = flush_paths.len().saturating_sub(1); + let mut flush_handles = Vec::with_capacity(flush_paths.len()); + for (index, path) in flush_paths.into_iter().enumerate() { + let file = self.take_or_open_async_file(&path).await?; + let flush_handle = if index == last_index { + file::flush_submit_lsn(&file, lsns.clone())? + } else { + file::flush_submit_lsn(&file, Vec::::new())? + }; + flush_handles.push((path, file, flush_handle)); + } + for (path, file, flush_handle) in flush_handles { + let flushed_lsns = self + .finish_async_file_use_with_value(&path, file, flush_handle.wait().await) + .await?; + if !flushed_lsns.is_empty() { + self.complete_persisted_lsns(flushed_lsns)?; + } + } + Ok(()) + } + + pub(crate) fn append_raw(&self, payload: &[u8]) -> RS<()> { + if payload.is_empty() { + return Ok(()); + } + let mut guard = self + .inner + .state + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker kv log lock poisoned"))?; + let reservation = guard.reserve_append(payload.len() as u64)?; + drop(guard); + self.append_reserved_sync(reservation, payload) + } + + pub fn flush(&self) -> RS<()> { + let path = self.current_chunk_path()?; + if let Some(path) = path { + self.flush_path_sync(&path)?; + } + Ok(()) + } + + pub async fn flush_async(&self) -> RS<()> { + let path = self.current_chunk_path()?; + let Some(path) = path else { + return Ok(()); + }; + if worker_ring::has_current_worker_ring() { + self.flush_path_async(&path).await + } else { + self.flush_path_sync(&path) + } + } + + fn append_reserved_sync(&self, reservation: AppendReservation, payload: &[u8]) -> RS<()> { + let file = self.take_or_open_sync_file(&reservation.path)?; + let write_result = file::write_sync(&file, payload, reservation.offset); + let flush_result = if reservation.flush_after_write { + Self::flush_sync(&file) + } else { + Ok(()) + }; + let close_result = self.release_sync_file(reservation.path.as_path(), file); + write_result?; + flush_result?; + close_result?; + Ok(()) + } + + async fn flush_path_async(&self, path: &Path) -> RS<()> { + let file = self.take_or_open_async_file(path).await?; + let flush_result = file::flush(&file).await; + self.finish_async_file_use(path, file, flush_result).await?; + Ok(()) + } + + fn flush_path_sync(&self, path: &Path) -> RS<()> { + let file = self.take_or_open_sync_file(path)?; + let flush_result = Self::flush_sync(&file); + let close_result = self.release_sync_file(path, file); + flush_result?; + close_result?; + Ok(()) + } + + async fn take_or_open_async_file(&self, path: &Path) -> RS { + if let Some(file) = self.take_cached_file(path)? { + return Ok(file); + } + Self::open_async(path).await + } + + fn take_or_open_sync_file(&self, path: &Path) -> RS { + if let Some(file) = self.take_cached_file(path)? { + return Ok(file); + } + Self::open_sync(path) + } + + fn take_cached_file(&self, path: &Path) -> RS> { + let mut guard = self + .inner + .state + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker kv log lock poisoned"))?; + Ok(guard.take_current_file(path)) + } + + async fn release_async_file(&self, path: &Path, file: IoFile) -> RS<()> { + if let Some(file) = self.put_cached_file(path, file)? { + file::close(file).await?; + } + Ok(()) + } + + async fn finish_async_file_use(&self, path: &Path, file: IoFile, result: RS<()>) -> RS<()> { + self.finish_async_file_use_with_value(path, file, result) + .await?; + Ok(()) + } + + async fn finish_async_file_use_with_value( + &self, + path: &Path, + file: IoFile, + result: RS, + ) -> RS { + let close_result = self.release_async_file(path, file).await; + let value = result?; + close_result?; + Ok(value) + } + + fn release_sync_file(&self, path: &Path, file: IoFile) -> RS<()> { + if let Some(file) = self.put_cached_file(path, file)? { + Self::close_sync(file)?; + } + Ok(()) + } + + fn put_cached_file(&self, path: &Path, file: IoFile) -> RS> { + let mut guard = self + .inner + .state + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker kv log lock poisoned"))?; + Ok(guard.store_current_file(path, file)) + } + + async fn open_async(path: &Path) -> RS { + file::open( + path, + libc::O_CREAT | libc::O_RDWR | file::cloexec_flag(), + 0o644, + ) + .await + } + + fn open_sync(path: &Path) -> RS { + file::open_sync( + path, + libc::O_CREAT | libc::O_RDWR | file::cloexec_flag(), + 0o644, + ) + } + + fn flush_sync(file: &IoFile) -> RS<()> { + file::flush_sync(file) + } + + fn close_sync(file: IoFile) -> RS<()> { + file::close_sync(file) + } + + fn reserve_appends(&self, payload: &[Vec]) -> RS> { + let mut guard = self + .inner + .state + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker kv log lock poisoned"))?; + let mut reservations = Vec::with_capacity(payload.len()); + for frame in payload { + reservations.push(guard.reserve_append(frame.len() as u64)?); + } + Ok(reservations) + } + + fn collect_flush_paths(reservations: &[AppendReservation]) -> Vec { + let mut flush_paths = Vec::new(); + let mut seen = HashSet::new(); + for reservation in reservations { + if seen.insert(reservation.path.clone()) { + flush_paths.push(reservation.path.clone()); + } + } + flush_paths + } + + fn merge_reserved_writes( + reservations: &[AppendReservation], + payload: &[Vec], + ) -> Vec { + let mut merged = Vec::::new(); + for (reservation, frame) in reservations.iter().zip(payload.iter()) { + match merged.last_mut() { + Some(last) + if last.path == reservation.path + && last.offset + last.payload.len() as u64 == reservation.offset => + { + last.payload.extend_from_slice(frame); + } + _ => merged.push(MergedWrite { + path: reservation.path.clone(), + offset: reservation.offset, + payload: frame.clone(), + }), + } + } + merged + } + + fn should_start_flush(queue: &[QueuedLogBatch], batching: EffectiveBatching) -> bool { + if queue.is_empty() { + return false; + } + let pending_bytes: usize = queue.iter().map(|batch| batch.bytes).sum(); + if pending_bytes >= batching.trigger_bytes { + return true; + } + let pending_frames: usize = queue.iter().map(|batch| batch.frames.len()).sum(); + if pending_frames >= batching.trigger_frames { + return true; + } + queue + .iter() + .any(|batch| batch.enqueued_at.elapsed() >= batching.max_wait) + } + + async fn run_flush_log(&self) -> RS<()> { + let mut open_files = HashMap::new(); + loop { + let pending = self.drain_pending_batches(self.effective_batching())?; + if pending.is_empty() { + self.release_flush_open_files(open_files).await?; + return Ok(()); + } + let prepared = self.prepare_flush_batch(pending)?; + self.execute_flush_batch(prepared, &mut open_files).await?; + } + } + + fn drain_pending_batches(&self, batching: EffectiveBatching) -> RS> { + let mut queue = self + .inner + .log_queue + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker log queue lock poisoned"))?; + if queue.is_empty() { + return Ok(Vec::new()); + } + let mut total_bytes = 0usize; + let mut split_at = 0usize; + for batch in queue.iter() { + if split_at > 0 && total_bytes.saturating_add(batch.bytes) > batching.max_batch_bytes { + break; + } + total_bytes = total_bytes.saturating_add(batch.bytes); + split_at += 1; + } + if split_at == 0 { + split_at = 1; + } + Ok(queue.drain(..split_at).collect()) + } + + fn prepare_flush_batch(&self, pending: Vec) -> RS { + let mut frames = Vec::new(); + let mut lsns = Vec::new(); + for batch in pending { + frames.extend(batch.frames); + lsns.extend(batch.lsns); + } + let reservations = self.reserve_appends(&frames)?; + + let writes = Self::merge_reserved_writes(&reservations, &frames); + let flush_paths = Self::collect_flush_paths(&reservations); + Ok(PreparedFlushBatch { + writes, + flush_paths, + ready_lsns: lsns, + }) + } + + async fn execute_flush_batch( + &self, + prepared: PreparedFlushBatch, + open_files: &mut HashMap, + ) -> RS<()> { + if prepared.writes.is_empty() { + return Ok(()); + } + + let mut write_handles = Vec::with_capacity(prepared.writes.len()); + for write in prepared.writes { + let file = self.checkout_flush_file(&write.path, open_files).await?; + let write_handle = file::write_submit(&file, write.payload, write.offset)?; + write_handles.push((write.path, file, write_handle)); + } + for (path, file, write_handle) in write_handles { + write_handle.wait().await?; + open_files.insert(path, file); + } + + let last_index = prepared.flush_paths.len().saturating_sub(1); + let mut flush_handles = Vec::with_capacity(prepared.flush_paths.len()); + for (index, path) in prepared.flush_paths.into_iter().enumerate() { + let file = self.checkout_flush_file(&path, open_files).await?; + let flush_handle = if index == last_index { + file::flush_submit_lsn(&file, prepared.ready_lsns.clone())? + } else { + file::flush_submit_lsn(&file, Vec::::new())? + }; + flush_handles.push((path, file, flush_handle)); + } + for (path, file, flush_handle) in flush_handles { + let flushed_lsns = flush_handle.wait().await?; + if !flushed_lsns.is_empty() { + self.complete_persisted_lsns(flushed_lsns)?; + } + open_files.insert(path, file); + } + Ok(()) + } + + fn complete_persisted_lsns(&self, lsns: Vec) -> RS<()> { + if lsns.is_empty() { + return Ok(()); + } + self.inner.flush_waiter.ready(lsns); + Ok(()) + } + + async fn checkout_flush_file( + &self, + path: &Path, + open_files: &mut HashMap, + ) -> RS { + if let Some(file) = open_files.remove(path) { + return Ok(file); + } + self.take_or_open_async_file(path).await + } + + async fn release_flush_open_files(&self, open_files: HashMap) -> RS<()> { + for (path, file) in open_files { + self.release_async_file(&path, file).await?; + } + Ok(()) + } +} + +impl WorkerLogLayout { + pub fn new>(log_dir: P, log_oid: OID, chunk_size: u64) -> RS { + if chunk_size == 0 { + return Err(m_error!( + EC::ParseErr, + "worker log chunk size must be greater than zero" + )); + } + Ok(Self { + log_dir: log_dir.into(), + log_oid, + chunk_size, + short_oid: ShortUuid::from_uuid(&Uuid::from_u128(log_oid)).to_string(), + batching: WorkerLogBatching::default(), + }) + } + + pub fn with_batching(mut self, batching: WorkerLogBatching) -> Self { + self.batching = batching; + self + } + + pub fn log_oid(&self) -> OID { + self.log_oid + } + + pub fn chunk_size(&self) -> u64 { + self.chunk_size + } + + pub fn chunk_path(&self, sequence: u64) -> PathBuf { + self.log_dir + .join(format!("{}.{}.xl", self.short_oid, sequence)) + } + + pub fn frame_size_limit(&self) -> usize { + self.chunk_size as usize + } + + pub fn batching(&self) -> WorkerLogBatching { + self.batching + } + + pub fn scan_tail(&self) -> RS { + mudu_sys::fs::create_dir_all(&self.log_dir) + .map_err(|e| m_error!(EC::IOErr, "create worker kv log directory error", e))?; + let mut max_sequence: Option = None; + for path in mudu_sys::fs::read_dir(&self.log_dir) + .map_err(|e| m_error!(EC::IOErr, "scan worker kv log directory error", e))? + { + if let Some(sequence) = self.parse_chunk_sequence(path.as_path()) { + max_sequence = Some(max_sequence.map_or(sequence, |current| current.max(sequence))); + } + } + let Some(sequence) = max_sequence else { + return Ok(WorkerLogTail { + current_sequence: None, + current_size: 0, + next_sequence: 0, + next_lsn: 0, + }); + }; + let path = self.chunk_path(sequence); + let size = mudu_sys::fs::metadata_len(&path) + .map_err(|e| m_error!(EC::IOErr, "read worker kv chunk metadata error", e))?; + let next_lsn = self.scan_next_lsn()?; + if size < self.chunk_size { + Ok(WorkerLogTail { + current_sequence: Some(sequence), + current_size: size, + next_sequence: sequence + 1, + next_lsn, + }) + } else { + Ok(WorkerLogTail { + current_sequence: None, + current_size: 0, + next_sequence: sequence + 1, + next_lsn, + }) + } + } + + pub fn chunk_paths_sorted(&self) -> RS> { + mudu_sys::fs::create_dir_all(&self.log_dir) + .map_err(|e| m_error!(EC::IOErr, "create worker kv log directory error", e))?; + let mut entries = Vec::<(u64, PathBuf)>::new(); + for path in mudu_sys::fs::read_dir(&self.log_dir) + .map_err(|e| m_error!(EC::IOErr, "scan worker kv log directory error", e))? + { + if let Some(sequence) = self.parse_chunk_sequence(path.as_path()) { + entries.push((sequence, path)); + } + } + entries.sort_by_key(|(sequence, _)| *sequence); + Ok(entries.into_iter().map(|(_, path)| path).collect()) + } + + fn parse_chunk_sequence(&self, path: &Path) -> Option { + let file_name = path.file_name()?.to_str()?; + let prefix = format!("{}.", self.short_oid); + let suffix = ".xl"; + if !file_name.starts_with(&prefix) || !file_name.ends_with(suffix) { + return None; + } + let sequence = &file_name[prefix.len()..file_name.len() - suffix.len()]; + sequence.parse::().ok() + } + + fn scan_next_lsn(&self) -> RS { + let mut max_lsn: Option = None; + for path in self.chunk_paths_sorted()? { + let bytes = mudu_sys::fs::read_all(&path) + .map_err(|e| m_error!(EC::IOErr, "read worker kv chunk for lsn scan error", e))?; + let mut offset = 0usize; + while offset < bytes.len() { + let remaining = &bytes[offset..]; + let next_frame_len = frame_len(remaining)?; + let header = LogFrameHeader::decode(&remaining[..16])?; + max_lsn = Some(max_lsn.map_or(header.lsn(), |current| current.max(header.lsn()))); + offset += next_frame_len; + } + } + Ok(max_lsn.map_or(0, |lsn| lsn.saturating_add(1))) + } +} + +impl WorkerLogBatching { + pub const fn new( + trigger_bytes: usize, + trigger_frames: usize, + max_wait: Duration, + max_batch_bytes: usize, + ) -> Self { + Self { + trigger_bytes, + trigger_frames, + max_wait, + max_batch_bytes, + sessions_per_step: 8, + bytes_per_step: 32 * 1024, + frames_per_step: 16, + max_trigger_bytes: 512 * 1024, + max_trigger_frames: 256, + } + } + + pub const fn with_session_scaling( + mut self, + sessions_per_step: usize, + bytes_per_step: usize, + frames_per_step: usize, + max_trigger_bytes: usize, + max_trigger_frames: usize, + ) -> Self { + self.sessions_per_step = sessions_per_step; + self.bytes_per_step = bytes_per_step; + self.frames_per_step = frames_per_step; + self.max_trigger_bytes = max_trigger_bytes; + self.max_trigger_frames = max_trigger_frames; + self + } +} + +impl EffectiveBatching { + fn new( + trigger_bytes: usize, + trigger_frames: usize, + max_wait: Duration, + max_batch_bytes: usize, + ) -> Self { + Self { + trigger_bytes, + trigger_frames, + max_wait, + max_batch_bytes, + } + } +} + +impl Default for WorkerLogBatching { + fn default() -> Self { + Self::new(64 * 1024, 32, Duration::from_micros(200), 256 * 1024) + } +} + +#[async_trait] +impl WorkerLogBackend for WorkerWALBackend { + fn frame_size_limit(&self) -> RS { + Ok(self + .inner + .state + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker log lock poisoned"))? + .layout + .frame_size_limit()) + } + + fn serialize_entry(&self, entry: &L) -> RS>> { + let guard = self + .inner + .state + .lock() + .map_err(|_| m_error!(EC::InternalErr, "worker kv log lock poisoned"))?; + serialize_entry(entry, guard.layout.frame_size_limit(), &self.inner.next_lsn) + } + + fn chunk_paths_sorted(&self) -> RS> { + self.layout()?.chunk_paths_sorted() + } + + fn append_frames_sync(&self, frames: Vec>) -> RS<()> { + for frame in frames { + self.append_raw(&frame)?; + } + Ok(()) + } + + async fn append_frames_async(&self, frames: Vec>) -> RS { + let lsns = frame_lsns(&frames)?; + let last_lsn = last_frame_lsn(&frames)?; + if !worker_ring::has_current_worker_ring() { + self.append_raw_async_vec(frames, lsns).await?; + self.inner.flush_waiter.wait_lsn(last_lsn).await; + return Ok(last_lsn); + } + + self.append_log(vec![(frames, lsns)]); + self.inner.flush_waiter.wait_lsn(last_lsn).await; + Ok(last_lsn) + } + + fn flush(&self) -> RS<()> { + Self::flush(self) + } + + async fn flush_async(&self) -> RS<()> { + Self::flush_async(self).await + } +} + +impl ChunkedWorkerLog { + fn new(layout: WorkerLogLayout, tail: WorkerLogTail) -> RS { + Ok(Self { + layout, + current_sequence: tail.current_sequence, + current_size: tail.current_size, + current_file: None, + next_sequence: tail.next_sequence, + }) + } + + fn reserve_append(&mut self, payload_len: u64) -> RS { + if payload_len == 0 { + return Ok(AppendReservation { + path: self + .layout + .chunk_path(self.current_sequence.unwrap_or(self.next_sequence)), + offset: self.current_size, + flush_after_write: false, + }); + } + + if payload_len > self.layout.chunk_size() { + let sequence = self.next_sequence; + self.next_sequence += 1; + self.current_sequence = None; + self.current_size = 0; + return Ok(AppendReservation { + path: self.layout.chunk_path(sequence), + offset: 0, + flush_after_write: true, + }); + } + + if self.current_sequence.is_none() + || self.current_size + payload_len > self.layout.chunk_size() + { + self.current_sequence = Some(self.next_sequence); + self.current_size = 0; + self.next_sequence += 1; + } + + let sequence = self.current_sequence.expect("current sequence must exist"); + let offset = self.current_size; + self.current_size += payload_len; + if self.current_size >= self.layout.chunk_size() { + self.current_sequence = None; + self.current_size = 0; + } + Ok(AppendReservation { + path: self.layout.chunk_path(sequence), + offset, + flush_after_write: false, + }) + } + + fn current_path(&self) -> Option { + self.current_sequence + .map(|sequence| self.layout.chunk_path(sequence)) + } + + fn take_current_file(&mut self, path: &Path) -> Option { + let (cached_path, file) = self.current_file.take()?; + if cached_path == path { + Some(file) + } else { + self.current_file = Some((cached_path, file)); + None + } + } + + fn store_current_file(&mut self, path: &Path, file: IoFile) -> Option { + let Some(current_path) = self.current_path() else { + return Some(file); + }; + if current_path != path { + return Some(file); + } + let replaced = self.current_file.take().map(|(_, file)| file); + self.current_file = Some((path.to_path_buf(), file)); + replaced + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::wal::log_frame::split_frame; + use crate::wal::worker_log::decode_frames; + use crate::wal::xl_batch::{ + append_xl_batch, decode_xl_batches, decode_xl_batches_with_pending, serialize_batch, + XLBatch, + }; + use crate::wal::xl_data_op::XLInsert; + use crate::wal::xl_entry::{TxOp, XLEntry}; + use mudu::common::id::gen_oid; + use std::env::temp_dir; + use std::sync::atomic::AtomicU32; + + fn sample_batch() -> XLBatch { + XLBatch { + entries: vec![XLEntry { + xid: 1, + ops: vec![ + TxOp::Begin, + TxOp::Insert(XLInsert { + table_id: 0, + tuple_id: 0, + key: b"k1".to_vec(), + value: b"v1".to_vec(), + }), + TxOp::Commit, + ], + }], + } + } + + #[test] + fn worker_log_appends_batch_frames() { + let dir = temp_dir().join(format!("worker_kv_log_test_{}", gen_oid())); + let layout = WorkerLogLayout::new(dir, gen_oid(), 4096).unwrap(); + let path = layout.chunk_path(0); + let log = WorkerWALBackend::new(layout).unwrap(); + append_xl_batch(&log, &sample_batch()).unwrap(); + let bytes = std::fs::read(path).unwrap(); + assert!(!bytes.is_empty()); + } + + #[test] + fn worker_log_round_trips_batch_frames() { + let batch = sample_batch(); + let log = WorkerWALBackend::new( + WorkerLogLayout::new( + temp_dir().join(format!("worker_log_round_{}", gen_oid())), + gen_oid(), + 4096, + ) + .unwrap(), + ) + .unwrap(); + let next_lsn = AtomicU32::new(0); + let frames = serialize_batch(&batch, log.frame_size_limit().unwrap(), &next_lsn).unwrap(); + let decoded = decode_xl_batches(&frames).unwrap(); + assert_eq!(decoded, vec![batch]); + } + + #[test] + fn worker_log_decodes_multiple_frames_from_single_chunk_payload() { + let first = sample_batch(); + let second = XLBatch { + entries: vec![XLEntry { + xid: 2, + ops: vec![ + TxOp::Begin, + TxOp::Insert(XLInsert { + table_id: 0, + tuple_id: 0, + key: b"k2".to_vec(), + value: b"v2".to_vec(), + }), + TxOp::Commit, + ], + }], + }; + let mut bytes = Vec::new(); + let next_lsn = AtomicU32::new(0); + bytes.extend( + serialize_batch(&first, 4096, &next_lsn) + .unwrap() + .into_iter() + .flatten(), + ); + bytes.extend( + serialize_batch(&second, 4096, &next_lsn) + .unwrap() + .into_iter() + .flatten(), + ); + + let frames = decode_frames(&bytes).unwrap(); + let batches = decode_xl_batches(&frames).unwrap(); + assert_eq!(batches, vec![first, second]); + } + + #[test] + fn worker_log_decodes_batch_frames_across_chunk_boundaries() { + let batch = sample_xl_batch_1(); + let next_lsn = AtomicU32::new(0); + let frames = serialize_batch(&batch, 128, &next_lsn).unwrap(); + assert!(frames.len() > 1); + + let split_at = frames.len() / 2; + let first_chunk_frames = frames[..split_at].to_vec(); + let second_chunk_frames = frames[split_at..].to_vec(); + let mut pending = Vec::new(); + let mut pending_start_lsn = None; + + let first_batches = decode_xl_batches_with_pending( + &first_chunk_frames, + &mut pending, + &mut pending_start_lsn, + ) + .unwrap(); + assert!(first_batches.is_empty()); + assert!(!pending.is_empty()); + + let second_batches = decode_xl_batches_with_pending( + &second_chunk_frames, + &mut pending, + &mut pending_start_lsn, + ) + .unwrap(); + assert!(pending.is_empty()); + assert_eq!(second_batches, vec![batch]); + } + + #[test] + fn worker_log_rotates_chunks_by_size() { + let dir = temp_dir().join(format!("worker_kv_log_chunk_{}", gen_oid())); + let layout = WorkerLogLayout::new(dir.clone(), gen_oid(), 40).unwrap(); + let prefix = layout.short_oid.clone(); + let log = WorkerWALBackend::new(layout).unwrap(); + log.append_raw(&vec![1u8; 20]).unwrap(); + log.append_raw(&vec![2u8; 20]).unwrap(); + log.append_raw(&vec![3u8; 20]).unwrap(); + assert!(dir.join(format!("{}.0.xl", prefix)).exists()); + assert!(dir.join(format!("{}.1.xl", prefix)).exists()); + } + + fn sample_xl_batch_1() -> XLBatch { + XLBatch { + entries: vec![XLEntry { + xid: 1, + ops: vec![ + TxOp::Begin, + TxOp::Insert(XLInsert { + table_id: 0, + tuple_id: 0, + key: b"k".to_vec(), + value: vec![9u8; 512], + }), + TxOp::Commit, + ], + }], + } + } + #[test] + fn worker_log_serializes_frame_headers_with_monotonic_lsn() { + let batch = sample_xl_batch_1(); + let log = WorkerWALBackend::new( + WorkerLogLayout::new( + temp_dir().join(format!("worker_log_lsn_{}", gen_oid())), + gen_oid(), + 128, + ) + .unwrap(), + ) + .unwrap(); + let next_lsn = AtomicU32::new(0); + let frames = serialize_batch(&batch, log.frame_size_limit().unwrap(), &next_lsn).unwrap(); + assert!(frames.len() > 1); + for (index, frame) in frames.iter().enumerate() { + let (header, _, _) = split_frame(frame).unwrap(); + assert_eq!(header.lsn(), index as u32); + } + } + + #[test] + fn worker_log_places_oversized_entry_in_dedicated_chunk() { + let dir = temp_dir().join(format!("worker_kv_log_oversized_{}", gen_oid())); + let layout = WorkerLogLayout::new(dir.clone(), gen_oid(), 32).unwrap(); + let prefix = layout.short_oid.clone(); + let log = WorkerWALBackend::new(layout).unwrap(); + log.append_raw(&vec![1u8; 8]).unwrap(); + log.append_raw(&vec![2u8; 64]).unwrap(); + log.append_raw(&vec![3u8; 8]).unwrap(); + assert_eq!( + std::fs::metadata(dir.join(format!("{}.0.xl", prefix))) + .unwrap() + .len(), + 8 + ); + assert_eq!( + std::fs::metadata(dir.join(format!("{}.1.xl", prefix))) + .unwrap() + .len(), + 64 + ); + assert_eq!( + std::fs::metadata(dir.join(format!("{}.2.xl", prefix))) + .unwrap() + .len(), + 8 + ); + } + + #[tokio::test] + async fn wait_lsn_notifies_only_after_contiguous_flush() { + let waiter = Arc::new(WaitLsn::new(0, vec![])); + let wait_task = { + let waiter = waiter.clone(); + tokio::spawn(async move { + waiter.wait_lsn(2).await; + }) + }; + + waiter.ready(vec![1, 2]); + tokio::task::yield_now().await; + assert!(!wait_task.is_finished()); + + waiter.ready(vec![0]); + wait_task.await.unwrap(); + } + + #[tokio::test] + async fn wait_lsn_returns_immediately_after_flush_advances() { + let waiter = WaitLsn::new(3, vec![]); + waiter.ready(vec![3, 4]); + waiter.wait_lsn(4).await; + } +} diff --git a/mudu_kernel/src/wal/xid.rs b/mudu_kernel/src/wal/xid.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/mudu_kernel/src/wal/xid.rs @@ -0,0 +1 @@ + diff --git a/mudu_kernel/src/wal/xl_batch.rs b/mudu_kernel/src/wal/xl_batch.rs new file mode 100644 index 0000000..09eda9d --- /dev/null +++ b/mudu_kernel/src/wal/xl_batch.rs @@ -0,0 +1,18 @@ +use crate::wal::xl_entry::XLEntry; +use serde::{Deserialize, Serialize}; + +/// A transaction-log batch in WAL. +/// +/// Each [`XLBatch`] contains one or more transaction log entries that describe +/// transaction-level CRUD operations and transaction control records such as +/// begin, commit, and abort. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct XLBatch { + pub entries: Vec, +} + +pub use crate::wal::xl_batch_worker_log::{ + append_xl_batch, append_xl_batch_async, decode_xl_batches, decode_xl_batches_with_pending, + deserialize_batch, new_xl_batch_worker_log, new_xl_batch_writer, serialize_batch, + NoopXLBatchRecoveryHandler, XLBatchWorkerLog, +}; diff --git a/mudu_kernel/src/wal/xl_batch_worker_log.rs b/mudu_kernel/src/wal/xl_batch_worker_log.rs new file mode 100644 index 0000000..a114850 --- /dev/null +++ b/mudu_kernel/src/wal/xl_batch_worker_log.rs @@ -0,0 +1,217 @@ +use crate::wal::log_frame::decode_entries_with_pending; +use crate::wal::log_frame::{deserialize_entry, serialize_entry}; +use crate::wal::lsn::LSN; +use crate::wal::typed_worker_log::{TypedWorkerLog, WorkerLogRecoveryHandler}; +use crate::wal::worker_log::WorkerLogBackend; +use crate::wal::xl_batch::XLBatch; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use std::sync::atomic::AtomicU32; + +/// Typed worker-log wrapper specialized for [`XLBatch`]. +/// +/// Typical write path: +/// +/// ```ignore +/// let writer = new_xl_batch_writer(log_backend.clone()); +/// writer.append_sync(&batch)?; +/// writer.flush()?; +/// ``` +/// +/// Typical recovery path: +/// +/// ```ignore +/// struct RecoveryHandler { +/// worker: IoUringWorker, +/// } +/// +/// impl WorkerLogRecoveryHandler for RecoveryHandler { +/// fn handle_entry(&self, entry: XLBatch, _start_lsn: LSN) -> RS<()> { +/// self.worker.replay_log_batch(entry) +/// } +/// } +/// +/// let typed_log = new_xl_batch_worker_log(log_backend.clone(), RecoveryHandler { worker }); +/// typed_log.recover(&mut recovery_source)?; +/// ``` +pub type XLBatchWorkerLog = TypedWorkerLog; + +/// No-op recovery handler for write-only paths. +/// +/// Use this when the caller only needs typed append/flush APIs and does not +/// plan to invoke `recover(...)` on the wrapper instance. +pub struct NoopXLBatchRecoveryHandler; + +impl WorkerLogRecoveryHandler for NoopXLBatchRecoveryHandler { + fn handle_entry(&self, _entry: XLBatch, _start_lsn: LSN) -> RS<()> { + Ok(()) + } +} + +pub fn new_xl_batch_worker_log(backend: B, handler: H) -> XLBatchWorkerLog +where + B: WorkerLogBackend, + H: WorkerLogRecoveryHandler, +{ + TypedWorkerLog::new(backend, handler) +} + +/// Builds an [`XLBatchWorkerLog`] for append/flush paths. +/// +/// This is the usual choice for commit/write flows, where the caller wants the +/// typed `XLBatch` append APIs but does not need a recovery handler. +pub fn new_xl_batch_writer(backend: B) -> XLBatchWorkerLog +where + B: WorkerLogBackend, +{ + TypedWorkerLog::new(backend, NoopXLBatchRecoveryHandler) +} + +pub fn serialize_batch( + batch: &XLBatch, + max_part_size: usize, + next_lsn: &AtomicU32, +) -> RS>> { + serialize_entry(batch, max_part_size, next_lsn) +} + +pub fn deserialize_batch(parts: &[Vec]) -> RS { + deserialize_entry(parts) +} + +pub fn decode_xl_batches(frames: &[Vec]) -> RS> { + let mut pending = Vec::new(); + let mut pending_start_lsn = None; + let batches = decode_xl_batches_with_pending(frames, &mut pending, &mut pending_start_lsn)?; + if !pending.is_empty() { + return Err(m_error!(EC::DecodeErr, "trailing partial xl batch frames")); + } + Ok(batches) +} + +pub fn decode_xl_batches_with_pending( + frames: &[Vec], + pending: &mut Vec>, + pending_start_lsn: &mut Option, +) -> RS> { + Ok( + decode_entries_with_pending(frames, pending, pending_start_lsn)? + .into_iter() + .map(|(_, batch)| batch) + .collect(), + ) +} + +pub fn append_xl_batch(backend: &B, batch: &XLBatch) -> RS<()> { + let frames = backend.serialize_entry(batch)?; + backend.append_frames_sync(frames) +} + +pub async fn append_xl_batch_async(backend: &B, batch: &XLBatch) -> RS { + let frames = backend.serialize_entry(batch)?; + backend.append_frames_async(frames).await +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::wal::log_frame::{ + frame_lsns, split_frame, LOG_FRAME_HEADER_SIZE, LOG_FRAME_TAILER_SIZE, + }; + use crate::wal::xl_data_op::XLInsert; + use crate::wal::xl_entry::{TxOp, XLEntry}; + + fn sample_batch(entry_count: usize, payload_size: usize) -> XLBatch { + let mut entries = Vec::with_capacity(entry_count); + for xid in 0..entry_count { + entries.push(XLEntry { + xid: xid as u64 + 1, + ops: vec![ + TxOp::Begin, + TxOp::Insert(XLInsert { + table_id: 7, + tuple_id: xid as u64 + 10, + key: format!("key-{xid}").into_bytes(), + value: vec![xid as u8; payload_size], + }), + TxOp::Commit, + ], + }); + } + XLBatch { entries } + } + + #[test] + fn xl_batch_single_part_round_trip() { + let batch = sample_batch(1, 32); + let next_lsn = AtomicU32::new(0); + let parts = serialize_batch(&batch, 4096, &next_lsn).unwrap(); + let lsns = frame_lsns(&parts).unwrap(); + assert_eq!(parts.len(), 1); + assert_eq!(lsns, vec![0]); + let (header, payload, tailer) = split_frame(&parts[0]).unwrap(); + assert_eq!(header.lsn(), 0); + assert_eq!(header.n_part(), 0); + assert_eq!(tailer.n_part(), 0); + assert_eq!(payload.len(), header.size() as usize); + assert_eq!(deserialize_batch(&parts).unwrap(), batch); + } + + #[test] + fn xl_batch_splits_large_payload_into_multiple_parts() { + let batch = sample_batch(4, 256); + let next_lsn = AtomicU32::new(10); + let parts = serialize_batch(&batch, 180, &next_lsn).unwrap(); + let lsns = frame_lsns(&parts).unwrap(); + assert!(parts.len() > 1); + assert_eq!(lsns.len(), parts.len()); + for (index, part) in parts.iter().enumerate() { + assert!(part.len() <= 180); + let (header, _, tailer) = split_frame(part).unwrap(); + let expected = (parts.len() - index - 1) as u32; + assert_eq!(header.lsn(), lsns[index]); + assert_eq!(header.n_part(), expected); + assert_eq!(tailer.n_part(), expected); + } + assert_eq!(deserialize_batch(&parts).unwrap(), batch); + } + + #[test] + fn xl_batch_rejects_corrupted_payload_checksum() { + let batch = sample_batch(1, 32); + let next_lsn = AtomicU32::new(0); + let mut parts = serialize_batch(&batch, 4096, &next_lsn).unwrap(); + parts[0][LOG_FRAME_HEADER_SIZE] ^= 0x7f; + let err = deserialize_batch(&parts).unwrap_err(); + assert!(err.to_string().contains("checksum")); + } + + #[test] + fn xl_batch_rejects_part_order_mismatch() { + let batch = sample_batch(4, 256); + let next_lsn = AtomicU32::new(0); + let mut parts = serialize_batch(&batch, 180, &next_lsn).unwrap(); + parts.swap(0, 1); + let err = deserialize_batch(&parts).unwrap_err(); + let msg = err.to_string(); + assert!( + msg.contains("unexpected log frame order") || msg.contains("checksum"), + "{}", + msg + ); + } + + #[test] + fn xl_batch_rejects_invalid_part_size_configuration() { + let batch = sample_batch(1, 8); + let next_lsn = AtomicU32::new(0); + let err = serialize_batch( + &batch, + LOG_FRAME_HEADER_SIZE + LOG_FRAME_TAILER_SIZE, + &next_lsn, + ) + .unwrap_err(); + assert!(err.to_string().contains("max_part_size")); + } +} diff --git a/mudu_kernel/src/wal/xl_c_abort.rs b/mudu_kernel/src/wal/xl_c_abort.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/mudu_kernel/src/wal/xl_c_abort.rs @@ -0,0 +1 @@ + diff --git a/mudu_kernel/src/wal/xl_data_op.rs b/mudu_kernel/src/wal/xl_data_op.rs new file mode 100644 index 0000000..2839fa7 --- /dev/null +++ b/mudu_kernel/src/wal/xl_data_op.rs @@ -0,0 +1,56 @@ +use mudu::common::id::OID; +use serde::{Deserialize, Serialize}; + +/// Logical WAL payload for inserting one tuple into a table. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct XLInsert { + /// Target table object identifier. + /// + /// Recovery uses this to locate which table should receive the inserted + /// tuple. + pub table_id: OID, + /// Tuple identifier assigned to the inserted row version. + /// + /// This is the logical tuple id within the target table, not a physical + /// page/slot address. + pub tuple_id: u64, + /// Primary lookup key or record key bytes for the tuple. + /// + /// This key is recorded in WAL so recovery can rebuild the same logical + /// insert operation. + pub key: Vec, + /// Full value bytes of the tuple to insert. + /// + /// Unlike updates, inserts persist the complete row payload here. + pub value: Vec, +} + +/// Logical WAL payload for deleting one tuple from a table. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct XLDelete { + /// Target table object identifier. + pub table_id: OID, + /// Tuple identifier of the row version being deleted. + pub tuple_id: u64, + /// Key bytes of the tuple to delete. + /// + /// This allows recovery to identify the same logical record that was + /// removed. + pub key: Vec, +} + +/// Logical WAL payload for updating one tuple in a table. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct XLUpdate { + /// Target table object identifier. + pub table_id: OID, + /// Tuple identifier of the row version being updated. + pub tuple_id: u64, + /// Key bytes of the tuple to update. + pub key: Vec, + /// Encoded logical delta for the new tuple contents. + /// + /// This is not necessarily the full row image. It stores the change set + /// needed to transform the previous value into the new value. + pub delta: Vec, +} diff --git a/mudu_kernel/src/wal/xl_entry.rs b/mudu_kernel/src/wal/xl_entry.rs new file mode 100644 index 0000000..117701b --- /dev/null +++ b/mudu_kernel/src/wal/xl_entry.rs @@ -0,0 +1,45 @@ +use crate::wal::xl_data_op::{XLDelete, XLInsert, XLUpdate}; +use serde::{Deserialize, Serialize}; + +/// A transaction-log entry for a single transaction. +/// +/// An [`XLEntry`] represents transaction-level CRUD operations together with +/// transaction control records such as begin transaction, commit transaction, +/// and abort transaction. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct XLEntry { + /// Transaction identifier that owns all operations in this log entry. + /// + /// Recovery uses this to group begin/data/commit-or-abort records that + /// belong to the same transaction. + pub xid: u64, + /// Ordered transaction operations captured for this transaction. + /// + /// The sequence typically includes transaction control markers such as + /// [`TxOp::Begin`] / [`TxOp::Commit`] together with zero or more logical + /// row-level data operations in between. + pub ops: Vec, +} + +/// Transaction operations captured in WAL. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub enum TxOp { + /// Marks the beginning of a transaction's WAL record sequence. + Begin, + /// Marks successful transaction commit. + /// + /// Changes before this marker should become durable and visible after + /// recovery replays the entry. + Commit, + /// Marks transaction abort. + /// + /// Recovery can use this to ignore or roll back the transaction's pending + /// logical effects. + Abort, + /// Insert one tuple into a table. + Insert(XLInsert), + /// Update one existing tuple in a table. + Update(XLUpdate), + /// Delete one tuple from a table. + Delete(XLDelete), +} diff --git a/mudu_kernel/src/x_engine/api.rs b/mudu_kernel/src/x_engine/api.rs index b8cf039..d08860d 100644 --- a/mudu_kernel/src/x_engine/api.rs +++ b/mudu_kernel/src/x_engine/api.rs @@ -5,47 +5,54 @@ use std::sync::Arc; use crate::contract::schema_table::SchemaTable; use crate::x_engine::dat_bin::DatBin; use crate::x_engine::operator::Operator; -use mudu::common::id::OID; +use mudu::common::id::{AttrIndex, OID}; use mudu::common::result::RS; use mudu::common::xid::XID; use mudu_contract::tuple::tuple_field::TupleField; pub type TupleRow = TupleField; -/// Result set cursor +/// Asynchronous cursor over a result set produced by [`XContract::read_range`]. #[async_trait] pub trait RSCursor: Send + Sync { + /// Returns the next projected row, or `None` when the cursor is exhausted. async fn next(&self) -> RS>; } pub type Filter = Operator; -/// object id and datum +/// A compact row fragment keyed by attribute index. +/// +/// The contract uses this type for exact-key predicates, inserted key/value +/// columns, and update payloads. Each pair is `(attribute_index, binary_value)`. #[derive(Clone, Default, Debug)] pub struct VecDatum { - data: Vec<(OID, DatBin)>, + data: Vec<(AttrIndex, DatBin)>, } -/// range data +/// Key-range bounds used by [`XContract::read_range`]. +/// +/// Bounds are expressed over the same `(attribute_index, binary_value)` shape as +/// [`VecDatum`], but allow inclusive, exclusive, or unbounded range scans. #[derive(Clone)] pub struct RangeData { - start: Bound>, - end: Bound>, + start: Bound>, + end: Bound>, } -/// select term list +/// Projection list for read operations. #[derive(Clone, Debug)] pub struct VecSelTerm { - vec: Vec, + vec: Vec, } -/// predicate for non-primary-key +/// Predicate over non-key columns. #[derive(Clone, Debug)] pub enum Predicate { /// conjunctive normal form, it is a conjunction of disjunctions of literals - CNF(Vec>), + CNF(Vec>), /// disjunctive normal form, it is a disjunction of conjunctions of literals - DNF(Vec>), + DNF(Vec>), } /// alter table parameter @@ -75,33 +82,45 @@ pub struct OptInsert {} pub struct OptDelete {} /////////////////////////////////////////////////////////////////////////////// -/// MKI trait +/// Transactional relational execution interface used by the kernel. /// -/// A trait of XContract interface, which is a transaction processing abstraction on relational model -/// All the tables, columns, types, transactions etc. are reference by a unique and immutable object -/// id, [`OID`] +/// [`XContract`] is the storage-facing contract behind SQL execution and the +/// worker-local runtime. All stable schema objects are addressed by immutable +/// object identifiers such as [`OID`], while each write/read statement is +/// executed inside a transaction identified by [`XID`]. /// +/// Conventions: +/// - `table_id` always identifies the target table by OID. +/// - `pred_key` carries exact primary-key components for point operations. +/// - `pred_non_key` refines the operation with additional non-key predicates. +/// - `select` lists projected columns for read operations. +/// - row-count return values report how many visible rows were affected. #[async_trait] pub trait XContract: Send + Sync { - /// create a table, which is described by `schema` + /// Creates a table described by `schema`. + /// + /// `xid` is accepted for interface uniformity; implementations may treat + /// DDL as autocommit if transactional DDL is not supported. async fn create_table(&self, xid: XID, schema: &SchemaTable) -> RS<()>; - /// drop a table specified by its OID + /// Drops the table identified by `oid`. async fn drop_table(&self, xid: XID, oid: OID) -> RS<()>; - /// alter table + /// Applies an alter-table operation to the target table. async fn alter_table(&self, xid: XID, oid: OID, alter_table: &AlterTable) -> RS<()>; - /// start a transaction + /// Starts a new transaction and returns its transaction id. async fn begin_tx(&self) -> RS; - /// commit a transaction specified by its XID + /// Commits the transaction identified by `xid`. async fn commit_tx(&self, xid: XID) -> RS<()>; - /// abort a transaction specified by its XID + /// Aborts the transaction identified by `xid`. async fn abort_tx(&self, xid: XID) -> RS<()>; - /// update by a collection of predicate + /// Updates rows that match the provided key and non-key predicates. + /// + /// Returns the number of visible rows updated. async fn update( &self, xid: XID, @@ -112,7 +131,9 @@ pub trait XContract: Send + Sync { opt_update: &OptUpdate, ) -> RS; - /// read by a exact key + /// Reads one row by exact key. + /// + /// Returns `None` when the key is not visible in the transaction snapshot. async fn read_key( &self, xid: XID, @@ -122,7 +143,10 @@ pub trait XContract: Send + Sync { opt_read: &OptRead, ) -> RS>>; - /// read by a collection of predicate + /// Reads rows from a key range plus optional non-key predicates. + /// + /// The returned cursor yields projected rows in the implementation-defined + /// order of the range scan. async fn read_range( &self, xid: XID, @@ -133,7 +157,9 @@ pub trait XContract: Send + Sync { opt_read: &OptRead, ) -> RS>; - /// delete by a collection of predicate + /// Deletes rows that match the provided key and non-key predicates. + /// + /// Returns the number of visible rows deleted. async fn delete( &self, xid: XID, @@ -143,7 +169,7 @@ pub trait XContract: Send + Sync { opt_delete: &OptDelete, ) -> RS; - /// insert a row + /// Inserts one row identified by `keys` with payload columns from `values`. async fn insert( &self, xid: XID, @@ -155,7 +181,7 @@ pub trait XContract: Send + Sync { } impl VecDatum { - pub fn new(data: Vec<(OID, DatBin)>) -> Self { + pub fn new(data: Vec<(AttrIndex, DatBin)>) -> Self { Self { data } } @@ -163,21 +189,38 @@ impl VecDatum { std::mem::swap(&mut self.data, &mut other.data); } - pub fn data(&self) -> &Vec<(OID, DatBin)> { + pub fn data(&self) -> &Vec<(AttrIndex, DatBin)> { &self.data } - pub fn into_data(self) -> Vec<(OID, DatBin)> { + pub fn into_data(self) -> Vec<(AttrIndex, DatBin)> { self.data } } +impl RangeData { + pub fn new( + start: Bound>, + end: Bound>, + ) -> Self { + Self { start, end } + } + + pub fn start(&self) -> &Bound> { + &self.start + } + + pub fn end(&self) -> &Bound> { + &self.end + } +} + impl VecSelTerm { - pub fn new(proj_list: Vec) -> Self { + pub fn new(proj_list: Vec) -> Self { Self { vec: proj_list } } - pub fn vec(&self) -> &Vec { + pub fn vec(&self) -> &Vec { &self.vec } } diff --git a/mudu_kernel/src/x_engine/mod.rs b/mudu_kernel/src/x_engine/mod.rs index 55b94da..ccd896e 100644 --- a/mudu_kernel/src/x_engine/mod.rs +++ b/mudu_kernel/src/x_engine/mod.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + pub mod api; pub mod operator; diff --git a/mudu_kernel/src/x_engine/thd_ctx.rs b/mudu_kernel/src/x_engine/thd_ctx.rs index 26fd9f0..ec907be 100644 --- a/mudu_kernel/src/x_engine/thd_ctx.rs +++ b/mudu_kernel/src/x_engine/thd_ctx.rs @@ -1,11 +1,9 @@ use crate::contract::mem_store::MemStore; use crate::contract::meta_mgr::MetaMgr; use crate::contract::x_lock_mgr::{LockResult, XLockMgr}; -use crate::contract::x_log::XLog; use crate::tx::x_snap_mgr::SnapshotRequester; use async_trait::async_trait; use std::cell::RefCell; -use std::cmp::Ordering; use std::sync::Arc; use crate::contract::data_row::DataRow; @@ -18,7 +16,7 @@ use crate::x_engine::api::{ VecSelTerm, XContract, }; use mudu::common::buf::Buf; -use mudu::common::id::{ThdID, OID}; +use mudu::common::id::{AttrIndex, ThdID, OID}; use mudu::common::result::RS; use mudu::common::result_of::rs_of_opt; use mudu::common::update_delta::UpdateDelta; @@ -42,7 +40,6 @@ struct ThdCtxInner { meta_mgr: Arc, snap_req: Arc, x_lock_mgr: Arc, - x_log: Vec>, tree_store: Arc, pst_op_ch: Arc, tx_ctx: HashMap, @@ -54,13 +51,12 @@ impl ThdCtx { meta_mgr: Arc, snap_req: Arc, x_lock_mgr: Arc, - x_log: Vec>, tree_store: Arc, pst_op_ch: Arc, ) -> Self { Self { inner: Arc::new(ThdCtxInner::new( - id, meta_mgr, snap_req, x_lock_mgr, x_log, tree_store, pst_op_ch, + id, meta_mgr, snap_req, x_lock_mgr, tree_store, pst_op_ch, )), } } @@ -73,10 +69,6 @@ impl ThdCtx { self.inner.snap_req() } - pub fn x_log(&self) -> &Vec> { - self.inner.x_log() - } - pub fn meta_mgr(&self) -> &dyn MetaMgr { self.inner.meta_mgr() } @@ -100,7 +92,6 @@ impl ThdCtxInner { meta_mgr: Arc, snap_req: Arc, x_lock_mgr: Arc, - x_log: Vec>, tree_store: Arc, pst_op_ch: Arc, ) -> Self { @@ -108,7 +99,6 @@ impl ThdCtxInner { id, snap_req, meta_mgr, - x_log, tree_store, x_lock_mgr, pst_op_ch, @@ -120,10 +110,6 @@ impl ThdCtxInner { self.snap_req.as_ref() } - fn x_log(&self) -> &Vec> { - &self.x_log - } - fn meta_mgr(&self) -> &dyn MetaMgr { self.meta_mgr.as_ref() } @@ -176,50 +162,37 @@ impl ThdCtxInner { // build update tuple for this row fn _update_tuple( tuple: &TupleRaw, - datum: &Vec<(OID, Buf)>, + datum: &Vec<(AttrIndex, Buf)>, table_desc: &TableDesc, ) -> RS> { let mut delta = vec![]; for (id, dat) in datum.iter() { - let opt = table_desc.oid2col().get(id); - let field = rs_of_opt(opt, || { - m_error!( + let field = table_desc.get_attr(*id); + if field.is_primary() { + return Err(m_error!( ER::IOErr, - format!("no column {} in table {}", id, table_desc.id()) - ) - })?; + format!( + "column {} in table {} is a primary key", + id, + table_desc.id() + ) + )); + } let datum_index = field.datum_index(); update_tuple(datum_index, dat, table_desc.value_desc(), tuple, &mut delta)?; } Ok(delta) } - fn _build_tuple(data: &Vec<(OID, Buf)>, desc: &TableDesc) -> RS { + fn _build_tuple(data: &Vec<(AttrIndex, Buf)>, desc: &TableDesc) -> RS { let mut vec_data = data.clone(); let ok = RefCell::new(true); vec_data.sort_by(|(id1, _), (id2, _)| { - let (opt1, opt2) = (desc.oid2col().get(id1), desc.oid2col().get(id2)); - let mut b_ok = ok.borrow_mut(); - match (opt1, opt2) { - (Some(i1), Some(i2)) => { - if i1.is_primary() != i2.is_primary() { - *b_ok = false; - } - i1.datum_index().cmp(&i2.datum_index()) - } - (None, None) => { - *b_ok = false; - Ordering::Equal - } - (Some(_), None) => { - *b_ok = false; - Ordering::Greater - } - (None, Some(_)) => { - *b_ok = false; - Ordering::Less - } + let (f1, f2) = (desc.get_attr(*id1), desc.get_attr(*id2)); + if f1.is_primary() != IS_KEY || f2.is_primary() != IS_KEY { + *ok.borrow_mut() = false; } + f1.datum_index().cmp(&f2.datum_index()) }); if !*ok.borrow() { return Err(m_error!(ER::TupleErr)); @@ -285,7 +258,7 @@ impl ThdCtxInner { format!("existing key for table {}", table_id) )); } - let data_row = DataRow::new(); + let data_row = DataRow::new(0); tx_ctx.insert(table_id, key, value, data_row).await?; Ok(()) } @@ -361,17 +334,15 @@ impl ThdCtxInner { }; let mut tuple_ret = vec![]; for i in select.vec() { - let opt = desc.oid2col().get(i); - if let Some(f) = opt { - let index = f.datum_index(); - let desc = if f.is_primary() { - desc.key_desc().get_field_desc(index) - } else { - desc.value_desc().get_field_desc(index) - }; - let slice = desc.get(tuple)?; - tuple_ret.push(slice.to_vec()); - } + let f = desc.get_attr(*i); + let index = f.datum_index(); + let desc = if f.is_primary() { + desc.key_desc().get_field_desc(index) + } else { + desc.value_desc().get_field_desc(index) + }; + let slice = desc.get(tuple)?; + tuple_ret.push(slice.to_vec()); } Ok(Some(tuple_ret)) } @@ -404,7 +375,7 @@ impl ThdCtxInner { } fn remove_tx_ctx(&self, xid: XID) { - self.tx_ctx.remove_async(&xid); + let _ = self.tx_ctx.remove_sync(&xid); } } diff --git a/mudu_kernel/src/x_engine/x_param.rs b/mudu_kernel/src/x_engine/x_param.rs index e088bbe..aa20bd8 100644 --- a/mudu_kernel/src/x_engine/x_param.rs +++ b/mudu_kernel/src/x_engine/x_param.rs @@ -48,3 +48,10 @@ pub struct PUpdateKeyValue { pub key: VecDatum, pub value: VecDatum, } + +#[derive(Clone, Debug)] +pub struct PDeleteKeyValue { + pub xid: XID, + pub table_id: OID, + pub key: VecDatum, +} diff --git a/mudu_kernel/src/x_log/fsync_task.rs b/mudu_kernel/src/x_log/fsync_task.rs deleted file mode 100644 index 4266f88..0000000 --- a/mudu_kernel/src/x_log/fsync_task.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::contract::lsn::LSN; -use crate::x_log::lsn_syncer::LSNSyncer; -use crate::x_log::x_log_file::XLogFile; -#[cfg(target_os = "linux")] -use crate::x_log::x_log_file_iou::f_sync_io_uring; -use crate::x_log::xl_file_info::XLFileInfo; -#[cfg(target_os = "linux")] -use crate::x_log::xl_path::xl_file_path; -use async_trait::async_trait; -use mudu::common::buf::Buf; -use mudu::common::result::RS; -use mudu_utils::notifier::NotifyWait; -use mudu_utils::sync::a_task::ATask; -use mudu_utils::task_trace; -use tokio::sync::mpsc::Receiver; -use tokio::sync::oneshot; -use tracing::error; - -#[cfg(target_os = "linux")] -async fn sync_io(f: XLFileInfo, receiver: Receiver<(Buf, LSN)>, lsn_syncer: LSNSyncer) -> RS<()> { - if f.cfg.x_log_use_io_uring { - let path_buf = xl_file_path( - &f.cfg.x_log_path, - &f.channel_name, - &f.cfg.x_log_ext_name, - f.file_no, - ); - let path_string = path_buf - .as_path() - .to_str() - .expect("expected path to string") - .to_string(); - f_sync_io_uring(vec![path_string], receiver, lsn_syncer).await?; - } else { - let mut file = XLogFile::new(f.cfg, f.channel_name, f.file_size, f.file_no)?; - file.f_sync_loop(receiver, lsn_syncer).await?; - } - Ok(()) -} - -#[cfg(not(target_os = "linux"))] -async fn sync_io(f: XLFileInfo, receiver: Receiver<(Buf, LSN)>, lsn_syncer: LSNSyncer) -> RS<()> { - let mut file = XLogFile::new(f.cfg, f.channel_name, f.file_size, f.file_no)?; - file.f_sync_loop(receiver, lsn_syncer).await?; - Ok(()) -} - -impl FsyncTask { - pub fn new( - canceller: NotifyWait, - name: String, - x_log_file: XLogFileReceiver, - receiver: Receiver<(Buf, LSN)>, - lsn_syncer: LSNSyncer, - ) -> Self { - Self { - canceller, - name, - file_receiver: x_log_file, - log_receiver: receiver, - lsn_syncer, - } - } -} - -#[async_trait] -impl ATask for FsyncTask { - fn notifier(&self) -> NotifyWait { - self.canceller.clone() - } - - fn name(&self) -> String { - self.name.clone() - } - - async fn run(self) -> RS<()> { - self.run_fsync_task().await?; - Ok(()) - } -} - -impl FsyncTask { - async fn run_fsync_task(self) -> RS<()> { - let _trace = task_trace!(); - let r = Self::fsync_task(self.file_receiver, self.log_receiver, self.lsn_syncer).await; - match r { - Ok(_) => {} - Err(e) => { - error!("fsync task error: {}", e); - return Err(e); - } - } - Ok(()) - } - - async fn fsync_task( - x_log_file_ch: XLogFileReceiver, - receiver: Receiver<(Buf, LSN)>, - lsn_syncer: LSNSyncer, - ) -> RS<()> { - let _trace = task_trace!(); - let r = x_log_file_ch.await; - let f = match r { - Ok(f) => f, - Err(e) => { - panic!("receive error {:?}", e) - } - }; - sync_io(f, receiver, lsn_syncer).await?; - Ok(()) - } -} - -pub struct FsyncTask { - canceller: NotifyWait, - name: String, - file_receiver: XLogFileReceiver, - log_receiver: Receiver<(Buf, LSN)>, - lsn_syncer: LSNSyncer, -} - -pub type XLogFileReceiver = oneshot::Receiver; diff --git a/mudu_kernel/src/x_log/iou.rs b/mudu_kernel/src/x_log/iou.rs deleted file mode 100644 index 4ab115d..0000000 --- a/mudu_kernel/src/x_log/iou.rs +++ /dev/null @@ -1,513 +0,0 @@ -use libc::iovec; -use mudu::common::buf::Buf; -use std::fmt::Debug; -use std::fs::OpenOptions; -use std::io::Result; -use std::os::fd::RawFd; -use std::os::unix::fs::OpenOptionsExt; -use std::os::unix::io::AsRawFd; -use std::time::Duration; -use tokio::sync::mpsc::{Receiver, Sender}; -use tokio::time::sleep; -use tracing::debug; - -pub type IoChRecv = Receiver; -pub type IoChSender = Sender; - -const SECTOR_SIZE: u64 = 512; -const QUEUE_SIZE: u64 = 64; - -const BUF_COUNT: u64 = 64; -const BUF_SIZE: u64 = 1024 * 1024; - -pub struct IOUSetting { - pub sector_size: u64, - pub buffer_size: u64, - pub buffer_count: u64, - pub queue_size: u64, -} - -impl Default for IOUSetting { - fn default() -> Self { - Self { - sector_size: SECTOR_SIZE, - buffer_size: BUF_SIZE, - buffer_count: BUF_COUNT, - queue_size: QUEUE_SIZE, - } - } -} -struct IoUring { - ring: rliburing::io_uring, - param: rliburing::io_uring_params, - iovec: Vec, - fds: Vec, - align_byte: u64, -} - -unsafe impl Send for IoUring {} - -#[derive(Clone)] -struct IoVec { - vec: iovec, -} -unsafe impl Send for IoVec {} -pub async fn io_uring_event_loop< - E: Send + 'static, - U: Clone + Debug + Send + 'static, - F: Fn(E) -> (Buf, U), - C: Fn(Vec), ->( - file_path: Vec, - receiver: IoChRecv, - to_user_data: F, - on_completion: C, - setting: IOUSetting, -) -> Result<()> { - _io_uring_event_loop(file_path, receiver, to_user_data, on_completion, setting).await -} - -async fn iou_event_loop_handle< - E: Send + 'static, - U: Clone + Debug + Send + 'static, - F: Fn(E) -> (Buf, U), - C: Fn(Vec), ->( - receiver: IoChRecv, - iovec: Vec, - fds: Vec, - to_user_data: F, - on_completion: C, - setting: IOUSetting, -) -> Result<()> { - iou_event_loop_handle_gut( - receiver, - iovec, - fds, - &to_user_data, - &on_completion, - &setting, - ) - .await -} - -struct IoWrite { - file_index: u32, - buf_index: u32, - submit_size: u32, - file_offset: u64, -} - -struct IoVecBuf { - available_buf: usize, - next_buf_index: usize, - align_byte: u64, - buf: Vec<(IoVec, Option>)>, -} - -struct FileOffset { - file_fd: u32, - file_index: u32, - offset: u64, -} - -fn other_io_error(string: String) -> std::io::Error { - std::io::Error::new(std::io::ErrorKind::Other, string) -} - -macro_rules! iou_error { - ($($arg:tt)*) => { - other_io_error(format!($($arg)*)) - } -} - -fn write_to_iovec_one( - log_write: &mut Vec<(Buf, U)>, - file_offset: &mut FileOffset, - iovec_buf: &mut IoVecBuf, - io_write: &mut Vec, - iovec_index: usize, - to_write_index: usize, -) -> usize { - let pair = &mut iovec_buf.buf[iovec_index]; - let n = log_write.len(); - let mut buf_offset = 0; - let mut write_index = n; - let mut user_data = vec![]; - for i in to_write_index..n { - let (buf, u) = &log_write[i]; - if buf.len() > pair.0.vec.iov_len as _ { - panic!("buffer overflow") - } - if buf_offset + buf.len() > pair.0.vec.iov_len as _ { - write_index = i; - break; - } - unsafe { - libc::memcpy( - pair.0.vec.iov_base.add(buf_offset) as _, - buf.as_ptr() as _, - buf.len() as _, - ) - }; - buf_offset += buf.len(); - user_data.push(u.clone()); - } - let to_write_len = round_up_align(buf_offset as _, iovec_buf.align_byte); - if to_write_len > pair.0.vec.iov_len as _ { - panic!("buffer overflow") - } - unsafe { - libc::memset( - pair.0.vec.iov_base.add(buf_offset) as _, - 0, - (to_write_len - buf_offset as u64) as _, - ); - } - pair.1 = Some(user_data); - - let write = IoWrite { - file_index: file_offset.file_index, - buf_index: iovec_index as u32, - // O_DIRECT writes must use an aligned length. The original payload - // length is still preserved by the caller's record framing. - submit_size: to_write_len as u32, - file_offset: file_offset.offset, - }; - let buf_num = iovec_buf.buf.len(); - iovec_buf.next_buf_index = (iovec_index + 1) % buf_num; - iovec_buf.available_buf -= 1; - // Keep the next write offset aligned as well, otherwise subsequent - // io_uring fixed-buffer writes can fail with EINVAL. - file_offset.offset += to_write_len; - io_write.push(write); - - write_index -} - -fn write_to_iovec( - log_write: &mut Vec<(Buf, U)>, - file_offset: &mut FileOffset, - iovec_buf: &mut IoVecBuf, - io_write: &mut Vec, -) { - if log_write.is_empty() { - return; - } - if iovec_buf.available_buf == 0 { - return; - } - let mut opt_split_index = None; - let mut to_write_index = 0usize; - let total_to_write = log_write.len(); - while to_write_index < total_to_write { - let mut index = iovec_buf.next_buf_index; - let opt_buf_index = loop { - if iovec_buf.buf[index].1.is_none() { - break Some(index); - } - index = (index + 1) % iovec_buf.buf.len(); - if index == iovec_buf.next_buf_index { - break None; - } - }; - match opt_buf_index { - Some(buf_index) => { - let n = write_to_iovec_one( - log_write, - file_offset, - iovec_buf, - io_write, - buf_index, - to_write_index, - ); - to_write_index = n; - } - None => opt_split_index = Some(to_write_index), - } - to_write_index += 1; - } - match opt_split_index { - Some(split_index) => { - *log_write = log_write.split_off(split_index); - } - None => { - log_write.clear(); - } - } -} - -impl IoUring { - fn create(iovec: Vec, fds: Vec, setting: &IOUSetting) -> Result { - let mut ring: rliburing::io_uring = unsafe { std::mem::zeroed() }; - let mut param: rliburing::io_uring_params = unsafe { std::mem::zeroed() }; - param.flags = rliburing::IORING_SETUP_SQPOLL; - param.sq_thread_idle = 2000; - let r = unsafe { - rliburing::io_uring_queue_init_params(setting.queue_size as _, &mut ring, &mut param) - }; - if r != 0 { - return Err(iou_error!("io_uring_queue_init_params error {}", r).into()); - } - - let r = unsafe { - rliburing::io_uring_register_files( - &mut ring, - fds.as_slice().as_ptr() as _, - fds.len() as _, - ) - }; - if r != 0 { - return Err(iou_error!("io_uring_register_files error {}", r).into()); - } - - let r = unsafe { - rliburing::io_uring_register_buffers(&mut ring, iovec.as_ptr() as _, iovec.len() as _) - }; - if r != 0 { - return Err(iou_error!("io_uring_buffers error {}", r).into()); - } - Ok(Self { - ring, - param, - iovec, - fds, - align_byte: setting.sector_size, - }) - } -} - -async fn iou_event_loop_handle_gut< - E: Send + 'static, - U: Clone + Debug + Send + 'static, - F: Fn(E) -> (Buf, U), - C: Fn(Vec), ->( - receiver: Receiver, - iovec: Vec, - fds: Vec, - to_user_data: &F, - on_completion: &C, - setting: &IOUSetting, -) -> Result<()> { - let mut ring = IoUring::create(iovec, fds, setting)?; - let mut n_submitted = 0; - let mut write_log = vec![]; - let mut write_io = vec![]; - let mut has_poll_events = false; - let mut receiver = receiver; - let mut io_vec_buf = IoVecBuf { - available_buf: ring.iovec.len(), - next_buf_index: 0, - align_byte: setting.sector_size, - buf: ring.iovec.iter().map(|iov| (iov.clone(), None)).collect(), - }; - let mut file_offset: Vec = ring - .fds - .iter() - .enumerate() - .map(|(i, f)| FileOffset { - file_fd: f.as_raw_fd() as u32, - file_index: i as u32, - offset: 0, - }) - .collect(); - - loop { - if !has_poll_events && io_vec_buf.available_buf > 0 { - let mut recv = vec![]; - let n = receiver - .recv_many(&mut recv, io_vec_buf.available_buf as _) - .await; - if n == 0 { - break; - } - let mut u_vec = vec![]; - for e in recv { - let (buf, data) = to_user_data(e); - u_vec.push(data.clone()); - write_log.push((buf, data)); - } - debug!("append log {:?}", u_vec); - } else { - sleep(Duration::from_millis(2)).await; - } - write_to_iovec( - &mut write_log, - &mut file_offset[0], - &mut io_vec_buf, - &mut write_io, - ); - - let n = iou_submit(&mut ring, &mut write_io, &io_vec_buf)?; - n_submitted += n; - - let complete = iou_complete(&mut ring, &mut io_vec_buf, n_submitted, on_completion)?; - n_submitted -= complete; - - has_poll_events = n_submitted > 0 || !write_log.is_empty() || !write_io.is_empty() - } - Ok(()) -} - -fn iou_submit( - ring: &mut IoUring, - write_ops: &mut Vec, - io_vec_buf: &IoVecBuf, -) -> Result { - if write_ops.is_empty() { - return Ok(0); - } - let mut submit_count: usize = 0; - let mut opt_split_index = None; // if queue is full, this would be set - for (n, op) in write_ops.iter().enumerate() { - let buf_iovec = &ring.iovec[op.buf_index as usize]; - - let cqe = unsafe { - let cqe = rliburing::io_uring_get_sqe(&mut ring.ring); - cqe - }; - if cqe == std::ptr::null_mut() { - opt_split_index = Some(n); - break; - } - unsafe { - (*cqe).user_data = op.buf_index as _; - rliburing::io_uring_prep_write_fixed( - cqe, - ring.fds[op.file_index as usize], - buf_iovec.vec.iov_base as _, - op.submit_size as _, - op.file_offset as _, - op.buf_index as _, - ) - } - debug!("submit {:?}", io_vec_buf.buf[op.buf_index as usize].1); - submit_count += 1; - } - if let Some(n) = opt_split_index { - *write_ops = write_ops.split_off(n); - } else { - write_ops.clear(); - } - if submit_count > 0 { - let n = unsafe { rliburing::io_uring_submit(&mut ring.ring) }; - if n as usize != submit_count { - panic!("submit count error expeted {}, but {}", submit_count, n); - } - } - Ok(submit_count) -} - -fn iou_complete)>( - ring: &mut IoUring, - io_vec_buf: &mut IoVecBuf, - in_submitting: usize, - on_completion: &C, -) -> Result { - let mut complete = 0; - if in_submitting == 0 { - return Ok(0); - } - let mut user_data = vec![]; - for _i in 0..in_submitting { - let mut cqe_ptr: *mut rliburing::io_uring_cqe = unsafe { std::mem::zeroed() }; - let r = unsafe { rliburing::io_uring_peek_cqe(&mut ring.ring, &mut cqe_ptr) }; - if r == -libc::EAGAIN { - break; - } else if r < 0 { - return Err(iou_error!("io_uring_peek_cqe error {}", r)); - } - let completion_result = unsafe { (*cqe_ptr).res }; - if completion_result < 0 { - unsafe { rliburing::io_uring_cqe_seen(&mut ring.ring, cqe_ptr) }; - return Err(iou_error!( - "io_uring write completion error {}", - completion_result - )); - } - let buf_index = unsafe { (*cqe_ptr).user_data } as usize; - io_vec_buf.available_buf += 1; - let pair = &mut io_vec_buf.buf[buf_index]; - let mut opt_user_data = None; - std::mem::swap(&mut pair.1, &mut opt_user_data); - user_data.extend(opt_user_data.unwrap()); - - io_vec_buf.buf[buf_index].1 = None; - unsafe { rliburing::io_uring_cqe_seen(&mut ring.ring, cqe_ptr) }; - complete += 1; - } - if !user_data.is_empty() { - on_completion(user_data); - } - Ok(complete) -} - -fn round_up_align(x: u64, size: u64) -> u64 { - let mask = size - 1; - if x & mask == 0 { - x & !mask - } else { - (x & !mask) + size - } -} - -async fn _io_uring_event_loop< - E: Send + 'static, - U: Clone + Debug + Send + 'static, - F: Fn(E) -> (Buf, U), - C: Fn(Vec), ->( - file_path: Vec, - receiver: IoChRecv, - to_user_data: F, - on_completion: C, - setting: IOUSetting, -) -> Result<()> { - let mut files = vec![]; - let mut fds = vec![]; - let mut buf_iovec = vec![]; - let _mmap_ptr = unsafe { - let sector_size = setting.sector_size; - let buf_size = round_up_align(setting.buffer_size, sector_size); - let size = setting.buffer_count * buf_size; - let ptr = libc::mmap( - 0 as _, - size as usize, - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, - -1, - 0, - ); - for _i in 0..setting.buffer_count { - buf_iovec.push(IoVec { - vec: iovec { - iov_base: ptr.add((_i * buf_size) as usize), - iov_len: buf_size as usize, - }, - }); - } - ptr - }; - - for p in file_path { - let file = OpenOptions::new() - .write(true) - .create(true) - .custom_flags(libc::O_DIRECT) - .open(&p)?; - fds.push(file.as_raw_fd()); - files.push(file); - } - - iou_event_loop_handle( - receiver, - buf_iovec, - fds, - to_user_data, - on_completion, - setting, - ) - .await?; - Ok(()) -} diff --git a/mudu_kernel/src/x_log/lsn_allocator.rs b/mudu_kernel/src/x_log/lsn_allocator.rs deleted file mode 100644 index 03ba190..0000000 --- a/mudu_kernel/src/x_log/lsn_allocator.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::contract::lsn::LSN; -use mudu::common::result::RS; -use std::sync::atomic::AtomicU64; -use std::sync::Arc; - -#[derive(Clone)] -pub struct LSNAllocator { - lsn: Arc, -} - -impl LSNAllocator { - pub fn new() -> Self { - Self { - lsn: Arc::new(AtomicU64::new(0)), - } - } - - pub fn next(&self) -> LSN { - let n = self.lsn.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - n + 1 - } - - pub fn alloc_max(&self) -> LSN { - self.lsn.load(std::sync::atomic::Ordering::SeqCst) - } - - pub fn recovery(&self, lsn: LSN) -> RS<()> { - self.lsn.store(lsn, std::sync::atomic::Ordering::SeqCst); - Ok(()) - } -} diff --git a/mudu_kernel/src/x_log/lsn_syncer.rs b/mudu_kernel/src/x_log/lsn_syncer.rs deleted file mode 100644 index 5212083..0000000 --- a/mudu_kernel/src/x_log/lsn_syncer.rs +++ /dev/null @@ -1,346 +0,0 @@ -use crate::contract::lsn::LSN; -use lazy_static::lazy_static; -use mudu::common::_debug::enable_debug; -use mudu::common::id::OID; -use mudu::common::result::RS; -use mudu_utils::debug::register_debug_url; - -use mudu_utils::task_trace; -use std::cmp::Reverse; -use std::collections::BinaryHeap; -use std::collections::HashMap; -use std::fmt::{Debug, Formatter, Write}; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::Arc; -use std::sync::Mutex as StdMutex; -use tokio::sync::Notify; - -#[derive(Clone, Debug)] -pub struct LSNSyncer { - inner: Arc, -} - -lazy_static! { - static ref _LSN_SYNCER: StdMutex>> = - StdMutex::new(HashMap::new()); -} - -struct LSNWait { - waiting_list: Vec<(LSN, Arc)>, -} - -#[derive(Debug, Clone)] -struct LSNReadyInfo { - max_flushed: LSN, - min_ready: LSN, - opt_flush_ready: Option, - ready_lsn: Vec>, -} - -struct LSNSyncerInner { - id: OID, - max_flushed_lsn: AtomicU64, - ready_info: StdMutex, - lsn_wait: StdMutex, -} - -impl LSNSyncer { - pub fn new(oid: OID) -> Self { - Self { - inner: Arc::new(LSNSyncerInner::new(oid)), - } - } - - pub fn recovery(&self, last_lsn: LSN) -> RS<()> { - if enable_debug() { - let mut _map = _LSN_SYNCER.lock().unwrap(); - let _ = _map.insert(self.inner.id(), self.inner.clone()); - register_debug_url("/lsn_syncer".to_string(), _debug_lsn_syncer); - } - self.inner.recovery(last_lsn) - } - - pub fn finalize(&self) { - if enable_debug() { - let mut _map = _LSN_SYNCER.lock().unwrap(); - let _ = _map.remove(&self.inner.id()); - } - } - pub async fn flush(&self, lsn: LSN) { - let _trace = task_trace!(); - self.inner.wait_flush(lsn).await - } - - pub fn ready(&self, lsn: Vec) { - let _trace = task_trace!(); - self.inner.add_ready_lsn(lsn); - } -} - -impl Debug for LSNSyncerInner { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let max_flushed_lsn = self.max_flushed_lsn.load(Ordering::Relaxed); - f.write_fmt(format_args!("max_flushed_lsn:{}\n", max_flushed_lsn))?; - let ready_info = self.ready_info.try_lock().map_err(|_| std::fmt::Error)?; - f.write_fmt(format_args!("ready_info:{:?}\n", &*ready_info))?; - let lsn_wait = self.lsn_wait.try_lock().map_err(|_| std::fmt::Error)?; - f.write_fmt(format_args!("lsn_wait:{:?}\n", &*lsn_wait))?; - - Ok(()) - } -} - -impl LSNWait { - fn new() -> Self { - Self { - waiting_list: vec![], - } - } - - fn add_new_wait(&mut self, lsn: LSN) -> Option> { - let notify: Arc = Arc::new(Notify::new()); - self.waiting_list.push((lsn, notify.clone())); - Some(notify) - } - - fn update_notify(&mut self, max_lsn: LSN) { - let mut result = vec![]; - let mut waiting_list = vec![]; - std::mem::swap(&mut waiting_list, &mut self.waiting_list); - //info!("waiting list {:?}", waiting_list); - let pair = (&mut result, &mut self.waiting_list); - for (lsn, notify) in waiting_list { - if lsn <= max_lsn { - // to return result - pair.0.push((lsn, notify)); - } else { - // new self.waiting_list - pair.1.push((lsn, notify)); - } - } - for (_n, notify) in pair.0 { - //debug!("notify {}", _n); - notify.notify_waiters(); - } - } -} - -impl Debug for LSNWait { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str("waiting_list")?; - f.write_str("\t[")?; - for (lsn, _) in self.waiting_list.iter() { - f.write_fmt(format_args!("{} ", lsn))?; - } - f.write_str("]\n")?; - Ok(()) - } -} -impl LSNSyncerInner { - pub fn recovery(&self, last_lsn: LSN) -> RS<()> { - self.max_flushed_lsn.store(last_lsn, Ordering::SeqCst); - let mut info = self.ready_info.lock().unwrap(); - info.max_flushed = last_lsn; - info.min_ready = LSN::MAX; - Ok(()) - } - - fn new(id: OID) -> LSNSyncerInner { - Self { - id, - max_flushed_lsn: AtomicU64::new(0), - ready_info: StdMutex::new(LSNReadyInfo { - max_flushed: 0, - min_ready: LSN::MAX, - opt_flush_ready: None, - ready_lsn: vec![], - }), - lsn_wait: StdMutex::new(LSNWait::new()), - } - } - - fn id(&self) -> OID { - self.id - } - - async fn wait_flush(&self, lsn: LSN) { - let _trace = task_trace!(); - let last_lsn = self.max_flushed_lsn.load(Ordering::SeqCst); - if last_lsn < lsn { - let opt_notify = { - let mut _g = self.lsn_wait.lock().unwrap(); - _g.add_new_wait(lsn) - }; - if let Some(notify) = opt_notify { - let last_lsn = self.max_flushed_lsn.load(Ordering::SeqCst); - if last_lsn < lsn { - notify.notified().await; - } - } - } - } - - fn notify_waiter(&self, lsn: LSN) { - let _trace = task_trace!(); - let last_lsn = self.max_flushed_lsn.load(Ordering::SeqCst); - if last_lsn < lsn { - let mut g = self.lsn_wait.lock().unwrap(); - //info!("notify all lsn <= {}", lsn); - g.update_notify(lsn); - self.max_flushed_lsn.store(lsn, Ordering::SeqCst); - } - } - - fn update_lsn_ready_info( - &self, - _min_flushed: LSN, - max_ready: LSN, - lsn_vec: Vec, - ) -> Option<(LSN, Vec>)> { - let _trace = task_trace!(); - let mut info = self.ready_info.lock().unwrap(); - if !lsn_vec.is_empty() { - info.ready_lsn.push(lsn_vec); - } - if info.max_flushed < max_ready { - if info.opt_flush_ready.is_none() { - panic!( - "cannot possible, ready info {:?}, max_flushed:{}", - info, max_ready - ); - } - let mut min_ready = LSN::MAX; - for vec in info.ready_lsn.iter() { - if let Some(l) = vec.first() { - if *l < min_ready { - min_ready = *l; - } - assert!(*l > max_ready); - } - } - info.max_flushed = max_ready; - info.min_ready = min_ready; - info.opt_flush_ready = None; - if info.max_flushed + 1 == info.min_ready { - info.opt_flush_ready = Some(info.min_ready); - let mut ready = vec![]; - std::mem::swap(&mut ready, &mut info.ready_lsn); - Some((info.min_ready, ready)) - } else { - None - } - } else { - None - } - } - - fn notify_ready(&self, min_ready: LSN, ready: Vec>) { - let _trace = task_trace!(); - let (lsn_contiguous_vec, lsn_vec_new) = merge_sorted::(ready); - if let Some(lsn) = lsn_contiguous_vec.last() { - let max_flushed = *lsn; - self.notify_waiter(max_flushed); - let opt_new_ready = self.update_lsn_ready_info(min_ready, max_flushed, lsn_vec_new); - if let Some((min, new_ready)) = opt_new_ready { - self.notify_ready(min, new_ready); - } - } else { - assert!(lsn_contiguous_vec.is_empty()); - self.add_ready_lsn(lsn_vec_new); - } - } - - fn add_ready_lsn(&self, lsn_vec: Vec) { - let _trace = task_trace!(); - let mut lsn_vec = lsn_vec; - lsn_vec.sort(); - let first_lsn = match lsn_vec.first() { - Some(lsn) => *lsn, - None => { - return; - } - }; - let opt_ready = { - let mut lsn_bound = self.ready_info.lock().unwrap(); - lsn_bound.ready_lsn.push(lsn_vec); - if lsn_bound.max_flushed < first_lsn && lsn_bound.min_ready > first_lsn { - lsn_bound.min_ready = first_lsn - } - if lsn_bound.max_flushed + 1 == lsn_bound.min_ready - && lsn_bound.opt_flush_ready.is_none() - { - lsn_bound.opt_flush_ready = Some(lsn_bound.min_ready); - let mut ready = vec![]; - std::mem::swap(&mut lsn_bound.ready_lsn, &mut ready); - Some((lsn_bound.min_ready, ready)) - } else { - None - } - }; - - if let Some((min, ready)) = opt_ready { - self.notify_ready(min, ready); - } - } -} - -fn merge_sorted(to_merge: Vec>) -> (Vec, Vec) { - let mut heap = BinaryHeap::new(); - let mut result = (Vec::new(), Vec::new()); - - // initialize heap - // put the first element of each Vec to the heap - for (i, vec) in to_merge.iter().enumerate() { - if let Some(&value) = vec.first() { - heap.push(Reverse((value, i, 0))); // (item value,vec index,item position) - } - } - - let mut push_to_first = true; - let mut prev_value = None; - // peek the minimum item, and insert it to the heap - while let Some(Reverse((value, i, j))) = heap.pop() { - if SPLIT_NON_CONTINUOUS { - if push_to_first { - push_to_first = match prev_value { - Some(v) => v + 1 == value, - None => true, - }; - - prev_value = Some(value); - if push_to_first { - result.0.push(value); - } else { - result.1.push(value); - } - } else { - result.1.push(value); - } - } else { - result.0.push(value); - } - // if there are some items, put the items to heap - if let Some(&next_value) = to_merge[i].get(j + 1) { - heap.push(Reverse((next_value, i, j + 1))); - } - } - - result -} - -fn _debug_lsn_syncer(_path: String) -> RS { - let _map = _LSN_SYNCER.lock().unwrap(); - let mut s = String::new(); - for (_, syncer) in _map.iter() { - s.write_str(format!("{:?}", syncer).as_str()).unwrap() - } - Ok(s) -} - -#[test] -fn test_merge_sorted() { - let sorted_vec = vec![vec![1, 9], vec![2, 4, 6], vec![3, 7, 8]]; - - let merged = merge_sorted::(sorted_vec); - println!("{:?}", merged); -} diff --git a/mudu_kernel/src/x_log/mod.rs b/mudu_kernel/src/x_log/mod.rs deleted file mode 100644 index fbc83f0..0000000 --- a/mudu_kernel/src/x_log/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -mod fsync_task; -#[cfg(target_os = "linux")] -mod iou; -mod lsn_allocator; -mod lsn_syncer; -mod recovery_task; -pub mod test_x_log; -mod test_xl_batch; -mod x_log_file; -#[cfg(target_os = "linux")] -mod x_log_file_iou; - -mod x_log_impl; -pub mod x_log_service; -mod xid; -mod xl_c_abort; -mod xl_c_begin; -mod xl_c_commit; -pub mod xl_cfg; - -mod xl_file_info; - -pub mod worker_kv_log; -mod xl_path; - -// mod iou; diff --git a/mudu_kernel/src/x_log/recovery_task.rs b/mudu_kernel/src/x_log/recovery_task.rs deleted file mode 100644 index 62cb143..0000000 --- a/mudu_kernel/src/x_log/recovery_task.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::x_log::lsn_allocator::LSNAllocator; -use crate::x_log::lsn_syncer::LSNSyncer; -use crate::x_log::x_log_file::XLogFile; -use crate::x_log::xl_cfg::XLCfg; -use crate::x_log::xl_file_info::XLFileInfo; -use async_trait::async_trait; -use mudu::common::result::RS; -use mudu_utils::notifier::NotifyWait; -use mudu_utils::sync::a_task::ATask; -use tracing::info; - -type XLFileInfoSender = tokio::sync::oneshot::Sender; -pub struct RecoveryTask { - canceller: NotifyWait, - task: String, - recovery_done: NotifyWait, - conf: XLCfg, - vec_file_sender: Vec, - lsn_syncer: LSNSyncer, - lsn_allocator: LSNAllocator, -} - -impl RecoveryTask { - pub fn new( - canceller: NotifyWait, - task: String, - recovery_done: NotifyWait, - conf: XLCfg, - vec_file_sender: Vec, - lsn_syncer: LSNSyncer, - lsn_allocator: LSNAllocator, - ) -> Self { - Self { - canceller, - task, - recovery_done, - conf, - vec_file_sender, - lsn_syncer, - lsn_allocator, - } - } -} -#[async_trait] -impl ATask for RecoveryTask { - fn notifier(&self) -> NotifyWait { - self.canceller.clone() - } - - fn name(&self) -> String { - self.task.clone() - } - - async fn run(self) -> RS<()> { - let cfg = self.conf; - let mut vec_file_sender = self.vec_file_sender; - - let mut vec_log_file = vec![]; - for _n in 0..cfg.x_log_channels { - let name = (_n + 1).to_string(); - let x_log_file = XLogFile::recovery(cfg.clone(), name.clone()).await?; - vec_log_file.push(x_log_file.file_info()); - } - - // todo! compute last lsn - let last_lsn = 0; - self.lsn_syncer.recovery(last_lsn)?; - self.lsn_allocator.recovery(last_lsn)?; - - for f in vec_log_file.into_iter().rev() { - let sender = vec_file_sender.pop().unwrap(); - let r = sender.send(f); - match r { - Ok(()) => {} - Err(_) => { - panic!("oneshot channel send error") - } - } - } - let _ = self.recovery_done.notify_all(); - info!("recovery task finished"); - Ok(()) - } -} diff --git a/mudu_kernel/src/x_log/test_x_log.rs b/mudu_kernel/src/x_log/test_x_log.rs deleted file mode 100644 index 4d863d7..0000000 --- a/mudu_kernel/src/x_log/test_x_log.rs +++ /dev/null @@ -1,367 +0,0 @@ -#[cfg(test)] -mod test { - use crate::contract::x_log::{OptAppend, XLog}; - use crate::contract::xl_rec::XLRec; - use crate::x_log::x_log_service::XLogService; - use crate::x_log::xl_cfg::XLCfg; - use chrono::{DateTime, Local}; - use mudu::common::result::RS; - use mudu::error::ec::EC as ER; - use mudu::error::err::MError; - use mudu::m_error; - use mudu_utils::debug; - use mudu_utils::log::log_setup; - use mudu_utils::notifier::NotifyWait; - use mudu_utils::sync::a_task::AsyncTask; - use mudu_utils::sync::s_mutex::SMutex; - use mudu_utils::task::spawn_local_task; - use mudu_utils::task_trace; - use std::sync::Arc; - use std::thread::Builder; - use std::time; - use std::time::{Duration, SystemTime}; - use test_utils::_arbitrary::_arbitrary_data; - use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; - use tokio::task::LocalSet; - use tracing::info; - - type DataReceiver = UnboundedReceiver>>; - type DataSender = UnboundedSender>>; - - fn _fuzz_log_rec(data: &[u8]) -> Vec { - let r = _arbitrary_data(data); - r - } - - //#[test] - #[allow(dead_code)] - fn test_uring_log() { - x_log_run(10, 2, 20, true, None); - } - - //#[test] - #[allow(dead_code)] - fn test_tokio_log() { - x_log_run(10, 2, 20, false, None); - } - - fn x_log_run( - num_entries: usize, - num_threads: usize, - num_tasks: usize, - use_io_uring: bool, - opt_data: Option<&[u8]>, - ) { - log_setup("info"); - let folder = { - let curr_time = SystemTime::now(); - let dt: DateTime = curr_time.into(); - format!("/tmp/xl_{}", dt.format("%+")) - }; - let cfg = XLCfg::new( - folder, - "xl".to_string(), - num_threads as u32, - 1000, - use_io_uring, - ); - let canceller = NotifyWait::new(); - let service = recovery(cfg.clone(), canceller.clone(), NotifyWait::new()); - let x_log_vec = service.x_log_channel(); - let mut thd_debug = vec![]; - { - let c = canceller.clone(); - let thd = Builder::new() - .spawn(move || debug::debug_serve(c, 2024)) - .unwrap(); - thd_debug.push(thd); - } - let mut senders = vec![]; - let mut thd_task = vec![]; - for (x_log, param) in x_log_vec { - let mut receiver = vec![]; - for _n in 0..num_tasks { - let (s, r) = unbounded_channel(); - receiver.push(r); - senders.push(s); - } - let thd = Builder::new() - .spawn(move || thread_run(x_log, param, receiver)) - .unwrap(); - thd_task.push(thd); - } - - let n = num_entries; - for _i in 0..n { - for s in senders.iter() { - let data = match opt_data { - None => { - let mut v = Vec::new(); - v.resize(_i % 1000, (_i % 100) as u8); - v - } - Some(data) => data.to_vec(), - }; - let _ = s.send(Some(data)); - } - } - - for _i in 0..n { - for s in senders.iter() { - let _ = s.send(None); - } - } - - let mut duration: Duration = Default::default(); - let mut count = 0; - for t in thd_task { - let (d, n) = t.join().unwrap(); - duration += d; - count += n; - } - - if duration.as_secs() != 0 { - let n = (count * num_tasks as u64 * num_threads as u64) / duration.as_secs(); - info!("log write per second: {}", n); - } - // done, stop debug service - canceller.notify_all(); - - for t in thd_debug { - t.join().unwrap(); - } - } - - fn recovery( - folder: XLCfg, - canceller: NotifyWait, - recovery_done_notifier: NotifyWait, - ) -> XLogService { - let (sender, s) = std::sync::mpsc::channel::(); - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - let ls = LocalSet::new(); - let c = canceller.clone(); - let _ = runtime.block_on(async move { - ls.spawn_local(async move { - let _r = spawn_local_task(c.clone(), "", async move { - let service = async_recovery(folder, c, recovery_done_notifier.clone()).await; - sender.send(service).unwrap(); - Ok::<(), ER>(()) - }); - match _r { - Ok(j) => { - let _ = j.await; - } - Err(_e) => {} - } - }); - - ls.await; - }); - s.recv().unwrap() - } - - async fn async_recovery( - cfg: XLCfg, - canceller: NotifyWait, - recovery_done_notifier: NotifyWait, - ) -> XLogService { - let _trace = task_trace!(); - let service = XLogService::new(cfg, canceller, recovery_done_notifier).unwrap(); - let task = service.recovery_task(); - task.run_once().await.unwrap(); - service - } - - fn thread_run( - x_log: Arc, - param: AsyncTask, - channel: Vec, - ) -> (Duration, u64) { - let canceler: NotifyWait = NotifyWait::new(); - let c = canceler.clone(); - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - let ls = LocalSet::new(); - let ret: Arc> = Arc::new(SMutex::new(Default::default())); - let _ret = ret.clone(); - runtime.block_on(async { - ls.spawn_local(async move { - let _r = spawn_local_task(c, "task", async move { - let (d, n) = thread_task(canceler, x_log, param, channel).await?; - Ok::<_, MError>((d, n)) - }); - match _r { - Ok(j) => { - let r = j.await; - if let Ok(Some(Ok(d))) = r { - let mut g = _ret.lock().unwrap(); - *g = d; - } - } - Err(_e) => {} - } - Ok::<_, ER>(()) - }); - ls.await; - }); - - let g = ret.lock().unwrap(); - (*g).clone() - } - - async fn thread_task( - canceler: NotifyWait, - x_log: Arc, - fsync_task: AsyncTask, - channel: Vec, - ) -> RS<(Duration, u64)> { - let c = canceler.clone(); - let mut vec_write_task = vec![]; - let mut ret: (Duration, u64) = Default::default(); - - let j = spawn_local_task(canceler.clone(), "", async move { - let r = write_log_multi_task(c, x_log, channel).await; - r - }) - .unwrap(); - vec_write_task.push(j); - - let c = canceler.clone(); - let mut vec_fsync_task = vec![]; - - let _j = spawn_local_task(canceler.clone(), "", async move { - fsync_task.run_once().await.unwrap(); - }) - .unwrap(); - vec_fsync_task.push(_j); - for j in vec_write_task { - let opt = j - .await - .map_err(|e| m_error!(ER::MuduError, "join write log task error", e))?; - if let Some(r) = opt { - let (d, n) = r?; - ret.0 += d; - ret.1 += n; - } - } - c.notify_all(); - for j in vec_fsync_task { - let _ = j.await; - } - Ok(ret) - } - - async fn write_log_multi_task( - canceler: NotifyWait, - x_log: Arc, - channel: Vec, - ) -> RS<(Duration, u64)> { - let mut vec_j = vec![]; - for (_i, ch) in channel.into_iter().enumerate() { - let xl = x_log.clone(); - let c = canceler.clone(); - let j = spawn_local_task(canceler.clone(), "write log", async move { - let r = write_log(c, xl, ch).await; - r - }) - .unwrap(); - vec_j.push(j); - } - let mut duration = Duration::default(); - let mut count = 0; - for j in vec_j { - let r_join = j - .await - .map_err(|e| m_error!(ER::MuduError, "join error", e))?; - if let Some(r) = r_join { - let (d, n) = r?; - duration += d; - count += n; - } - } - - Ok((duration, count)) - } - - async fn write_log( - _canceler: NotifyWait, - x_log: Arc, - channel: DataReceiver, - ) -> RS<(Duration, u64)> { - let _trace = task_trace!(); - let _r = _write_log(x_log, channel).await; - match _r { - Ok(d) => Ok(d), - Err(e) => { - println!("{:?}", e); - Err(e) - } - } - } - - async fn _write_log(x_log: Arc, channel: DataReceiver) -> RS<(Duration, u64)> { - let _trace = task_trace!(); - let mut ch = channel; - let mut opt_max_lsn = None; - let mut n = 0; - let mut duration = Duration::from_millis(0); - loop { - let opt = ch.recv().await; - let log_rec: Vec = match opt { - None => { - break; - } - Some(opt_data) => { - if let Some(data) = opt_data { - _arbitrary_data(&data) - } else { - break; - } - } - }; - let inst = time::Instant::now(); - let (lsn, _) = x_log.append(log_rec, OptAppend::default()).await?; - let max_lsn = lsn; - if max_lsn % 10 == 9 { - let f = x_log.flush(max_lsn).await?; - //info!("wait log {}", max_lsn); - f.wait().await?; - //info!("flush log {}", max_lsn); - } - if let Some(lsn) = &mut opt_max_lsn { - *lsn = max_lsn - } - duration += inst.elapsed(); - n += 1; - } - if let Some(lsn) = opt_max_lsn { - let inst = time::Instant::now(); - let f = x_log.flush(lsn).await?; - f.wait().await?; - //info!("flush log {}", lsn); - duration += inst.elapsed(); - } - - Ok((duration, n)) - } - - pub fn _x_log_append(data: &[u8]) { - x_log_run(1, 10, 10, false, Some(data)); - } - - #[cfg(test)] - mod _test { - use crate::fuzz::_test_target::_test::_test_target; - - //#[test] - fn _x_log_append() { - _test_target("_x_log_append"); - } - } -} diff --git a/mudu_kernel/src/x_log/worker_kv_log.rs b/mudu_kernel/src/x_log/worker_kv_log.rs deleted file mode 100644 index 0d95f36..0000000 --- a/mudu_kernel/src/x_log/worker_kv_log.rs +++ /dev/null @@ -1,423 +0,0 @@ -use mudu::common::buf::Buf; -use mudu::common::id::OID; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use short_uuid::ShortUuid; -use std::fs::OpenOptions; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; -use uuid::Uuid; - -#[derive(Clone)] -pub struct WorkerKvLog { - inner: Arc, -} - -struct WorkerKvLogInner { - state: Mutex, -} - -#[derive(Clone, Debug)] -pub struct WorkerLogLayout { - log_dir: PathBuf, - log_oid: OID, - chunk_size: u64, - short_oid: String, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct WorkerLogTail { - pub current_sequence: Option, - pub current_size: u64, - pub next_sequence: u64, -} - -struct ChunkedWorkerKvLog { - layout: WorkerLogLayout, - current: Option, - next_sequence: u64, -} - -struct ActiveChunk { - file: std::fs::File, - size: u64, -} - -impl WorkerKvLog { - pub fn encode_put_record, V: AsRef<[u8]>>(key: K, value: V) -> Buf { - encode_put_payload(key.as_ref(), value.as_ref()) - } - - pub fn new(layout: WorkerLogLayout) -> RS { - let tail = layout.scan_tail()?; - Ok(Self { - inner: Arc::new(WorkerKvLogInner { - state: Mutex::new(ChunkedWorkerKvLog::new(layout, tail)?), - }), - }) - } - - pub fn append_put, V: AsRef<[u8]>>(&self, key: K, value: V) -> RS<()> { - let payload = Self::encode_put_record(key, value); - self.append_raw(&payload) - } - - pub fn append_raw(&self, payload: &[u8]) -> RS<()> { - let mut guard = self - .inner - .state - .lock() - .map_err(|_| m_error!(EC::InternalErr, "worker kv log lock poisoned"))?; - guard.append(payload) - } - - pub fn flush(&self) -> RS<()> { - let mut guard = self - .inner - .state - .lock() - .map_err(|_| m_error!(EC::InternalErr, "worker kv log lock poisoned"))?; - guard.flush() - } - - pub fn decode_put_records(payload: &[u8]) -> RS, Vec)>> { - let mut offset = 0usize; - let mut records = Vec::new(); - while offset < payload.len() { - if payload.len() - offset < 10 { - return Err(m_error!( - EC::DecodeErr, - "worker kv log record header is truncated" - )); - } - let header = &payload[offset..offset + 4]; - if header != b"MKVL" { - return Err(m_error!( - EC::DecodeErr, - "invalid worker kv log record magic" - )); - } - let version = payload[offset + 4]; - let kind = payload[offset + 5]; - if version != 1 || kind != 1 { - return Err(m_error!( - EC::DecodeErr, - format!( - "unsupported worker kv log record version={} kind={}", - version, kind - ) - )); - } - let key_len = - u32::from_be_bytes(payload[offset + 6..offset + 10].try_into().unwrap()) as usize; - let key_start = offset + 10; - let key_end = key_start + key_len; - if key_end + 4 > payload.len() { - return Err(m_error!( - EC::DecodeErr, - "worker kv log key payload is truncated" - )); - } - let value_len = - u32::from_be_bytes(payload[key_end..key_end + 4].try_into().unwrap()) as usize; - let value_start = key_end + 4; - let value_end = value_start + value_len; - if value_end > payload.len() { - return Err(m_error!( - EC::DecodeErr, - "worker kv log value payload is truncated" - )); - } - records.push(( - payload[key_start..key_end].to_vec(), - payload[value_start..value_end].to_vec(), - )); - offset = value_end; - } - Ok(records) - } -} - -impl WorkerLogLayout { - pub fn new>(log_dir: P, log_oid: OID, chunk_size: u64) -> RS { - if chunk_size == 0 { - return Err(m_error!( - EC::ParseErr, - "worker log chunk size must be greater than zero" - )); - } - Ok(Self { - log_dir: log_dir.into(), - log_oid, - chunk_size, - short_oid: ShortUuid::from_uuid(&Uuid::from_u128(log_oid)).to_string(), - }) - } - - pub fn log_oid(&self) -> OID { - self.log_oid - } - - pub fn chunk_size(&self) -> u64 { - self.chunk_size - } - - pub fn chunk_path(&self, sequence: u64) -> PathBuf { - self.log_dir - .join(format!("{}.{}.xl", self.short_oid, sequence)) - } - - pub fn scan_tail(&self) -> RS { - std::fs::create_dir_all(&self.log_dir) - .map_err(|e| m_error!(EC::IOErr, "create worker kv log directory error", e))?; - let mut max_sequence: Option = None; - for entry in std::fs::read_dir(&self.log_dir) - .map_err(|e| m_error!(EC::IOErr, "scan worker kv log directory error", e))? - { - let entry = - entry.map_err(|e| m_error!(EC::IOErr, "read worker kv log directory entry", e))?; - if let Some(sequence) = self.parse_chunk_sequence(entry.path().as_path()) { - max_sequence = Some(max_sequence.map_or(sequence, |current| current.max(sequence))); - } - } - let Some(sequence) = max_sequence else { - return Ok(WorkerLogTail { - current_sequence: None, - current_size: 0, - next_sequence: 0, - }); - }; - let size = std::fs::metadata(self.chunk_path(sequence)) - .map_err(|e| m_error!(EC::IOErr, "read worker kv chunk metadata error", e))? - .len(); - if size < self.chunk_size { - Ok(WorkerLogTail { - current_sequence: Some(sequence), - current_size: size, - next_sequence: sequence + 1, - }) - } else { - Ok(WorkerLogTail { - current_sequence: None, - current_size: 0, - next_sequence: sequence + 1, - }) - } - } - - pub fn chunk_paths_sorted(&self) -> RS> { - std::fs::create_dir_all(&self.log_dir) - .map_err(|e| m_error!(EC::IOErr, "create worker kv log directory error", e))?; - let mut entries = Vec::<(u64, PathBuf)>::new(); - for entry in std::fs::read_dir(&self.log_dir) - .map_err(|e| m_error!(EC::IOErr, "scan worker kv log directory error", e))? - { - let entry = - entry.map_err(|e| m_error!(EC::IOErr, "read worker kv log directory entry", e))?; - let path = entry.path(); - if let Some(sequence) = self.parse_chunk_sequence(path.as_path()) { - entries.push((sequence, path)); - } - } - entries.sort_by_key(|(sequence, _)| *sequence); - Ok(entries.into_iter().map(|(_, path)| path).collect()) - } - - fn parse_chunk_sequence(&self, path: &Path) -> Option { - let file_name = path.file_name()?.to_str()?; - let prefix = format!("{}.", self.short_oid); - let suffix = ".xl"; - if !file_name.starts_with(&prefix) || !file_name.ends_with(suffix) { - return None; - } - let sequence = &file_name[prefix.len()..file_name.len() - suffix.len()]; - sequence.parse::().ok() - } -} - -impl ChunkedWorkerKvLog { - fn new(layout: WorkerLogLayout, tail: WorkerLogTail) -> RS { - let current = match tail.current_sequence { - Some(sequence) => Some(Self::open_existing_chunk( - &layout, - sequence, - tail.current_size, - )?), - None => None, - }; - Ok(Self { - layout, - current, - next_sequence: tail.next_sequence, - }) - } - - fn append(&mut self, payload: &[u8]) -> RS<()> { - if payload.is_empty() { - return Ok(()); - } - let payload_len = payload.len() as u64; - if payload_len > self.layout.chunk_size() { - self.current = None; - let mut dedicated = self.open_fresh_chunk()?; - Self::write_payload(&mut dedicated, payload)?; - Self::flush_chunk(&mut dedicated)?; - return Ok(()); - } - - let needs_rotate = self - .current - .as_ref() - .map(|chunk| chunk.size + payload_len > self.layout.chunk_size()) - .unwrap_or(true); - if needs_rotate { - self.current = Some(self.open_fresh_chunk()?); - } - - let chunk = self.current.as_mut().expect("current chunk must exist"); - Self::write_payload(chunk, payload)?; - if chunk.size >= self.layout.chunk_size() { - self.current = None; - } - Ok(()) - } - - fn flush(&mut self) -> RS<()> { - if let Some(chunk) = self.current.as_mut() { - Self::flush_chunk(chunk)?; - } - Ok(()) - } - - fn write_payload(chunk: &mut ActiveChunk, payload: &[u8]) -> RS<()> { - chunk - .file - .write_all(payload) - .map_err(|e| m_error!(EC::IOErr, "append worker kv log payload error", e))?; - chunk.size += payload.len() as u64; - Ok(()) - } - - fn flush_chunk(chunk: &mut ActiveChunk) -> RS<()> { - chunk - .file - .flush() - .map_err(|e| m_error!(EC::IOErr, "flush worker kv log payload error", e))?; - Ok(()) - } - - fn open_fresh_chunk(&mut self) -> RS { - let sequence = self.next_sequence; - self.next_sequence += 1; - Self::open_chunk_file(&self.layout, sequence) - } - - fn open_existing_chunk(layout: &WorkerLogLayout, sequence: u64, size: u64) -> RS { - let mut chunk = Self::open_chunk_file(layout, sequence)?; - chunk.size = size; - Ok(chunk) - } - - fn open_chunk_file(layout: &WorkerLogLayout, sequence: u64) -> RS { - let path = layout.chunk_path(sequence); - let file = OpenOptions::new() - .create(true) - .read(true) - .append(true) - .open(path) - .map_err(|e| m_error!(EC::IOErr, "open worker kv log chunk error", e))?; - let size = file - .metadata() - .map_err(|e| m_error!(EC::IOErr, "read worker kv log chunk metadata error", e))? - .len(); - Ok(ActiveChunk { file, size }) - } -} - -fn encode_put_payload(key: &[u8], value: &[u8]) -> Buf { - let mut payload = Vec::with_capacity(14 + key.len() + value.len()); - // The record starts with a short fixed header so recovery can distinguish - // the real payload from the zero padding required by O_DIRECT writes. - payload.extend_from_slice(b"MKVL"); - payload.push(1u8); - payload.push(1u8); - payload.extend_from_slice(&(key.len() as u32).to_be_bytes()); - payload.extend_from_slice(key); - payload.extend_from_slice(&(value.len() as u32).to_be_bytes()); - payload.extend_from_slice(value); - payload -} - -#[cfg(test)] -mod tests { - use super::*; - use mudu::common::id::gen_oid; - use std::env::temp_dir; - - #[test] - fn worker_log_appends_payload() { - let dir = temp_dir().join(format!("worker_kv_log_test_{}", gen_oid())); - let layout = WorkerLogLayout::new(dir, gen_oid(), 4096).unwrap(); - let path = layout.chunk_path(0); - let log = WorkerKvLog::new(layout).unwrap(); - log.append_put(b"k1", b"v1").unwrap(); - let bytes = std::fs::read(path).unwrap(); - assert!(!bytes.is_empty()); - } - - #[test] - fn worker_log_encodes_framed_put_record() { - let payload = encode_put_payload(b"k1", b"v1"); - assert_eq!(&payload[0..4], b"MKVL"); - assert_eq!(payload[4], 1); - assert_eq!(payload[5], 1); - assert_eq!(u32::from_be_bytes(payload[6..10].try_into().unwrap()), 2); - assert_eq!(&payload[10..12], b"k1"); - assert_eq!(u32::from_be_bytes(payload[12..16].try_into().unwrap()), 2); - assert_eq!(&payload[16..18], b"v1"); - } - - #[test] - fn worker_log_rotates_chunks_by_size() { - let dir = temp_dir().join(format!("worker_kv_log_chunk_{}", gen_oid())); - let layout = WorkerLogLayout::new(dir.clone(), gen_oid(), 40).unwrap(); - let prefix = layout.short_oid.clone(); - let log = WorkerKvLog::new(layout).unwrap(); - log.append_raw(&vec![1u8; 20]).unwrap(); - log.append_raw(&vec![2u8; 20]).unwrap(); - log.append_raw(&vec![3u8; 20]).unwrap(); - assert!(dir.join(format!("{}.0.xl", prefix)).exists()); - assert!(dir.join(format!("{}.1.xl", prefix)).exists()); - } - - #[test] - fn worker_log_places_oversized_entry_in_dedicated_chunk() { - let dir = temp_dir().join(format!("worker_kv_log_oversized_{}", gen_oid())); - let layout = WorkerLogLayout::new(dir.clone(), gen_oid(), 32).unwrap(); - let prefix = layout.short_oid.clone(); - let log = WorkerKvLog::new(layout).unwrap(); - log.append_raw(&vec![1u8; 8]).unwrap(); - log.append_raw(&vec![2u8; 64]).unwrap(); - log.append_raw(&vec![3u8; 8]).unwrap(); - assert_eq!( - std::fs::metadata(dir.join(format!("{}.0.xl", prefix))) - .unwrap() - .len(), - 8 - ); - assert_eq!( - std::fs::metadata(dir.join(format!("{}.1.xl", prefix))) - .unwrap() - .len(), - 64 - ); - assert_eq!( - std::fs::metadata(dir.join(format!("{}.2.xl", prefix))) - .unwrap() - .len(), - 8 - ); - } -} diff --git a/mudu_kernel/src/x_log/x_log_file.rs b/mudu_kernel/src/x_log/x_log_file.rs deleted file mode 100644 index 9a1558a..0000000 --- a/mudu_kernel/src/x_log/x_log_file.rs +++ /dev/null @@ -1,389 +0,0 @@ -use mudu::common::bc_dec::decode_binary; -use mudu::common::buf::{resize_buf, Buf}; -use regex::Regex; -use std::cmp::min; - -use std::fs::File as StdFile; -use std::path::PathBuf; -use std::str::FromStr; - -use crate::contract::lsn::LSN; -use crate::contract::xl_batch::XLBatch; -use crate::contract::xl_chunk::{ - decode_chunk, decode_chunk_hdr, write_chunk_to_u_file, ChunkHdr, ChunkTail, XLChunk, - XLChunkType, LOG_C_COMMON_HDR_SIZE, LOG_C_TAIL_SIZE, -}; -use crate::io::file::{File, TFile}; -use crate::x_log::lsn_syncer::LSNSyncer; -use crate::x_log::xl_cfg::XLCfg; -use crate::x_log::xl_file_info::XLFileInfo; -use crate::x_log::xl_path; -use crate::x_log::xl_path::xl_file_path; -use mudu::common::result::RS; -use mudu::common::result_of::std_io_error; -use mudu::error::ec::EC as ER; -use mudu::m_error; -use mudu_utils::task_trace; -use tokio::fs::read_dir; -use tokio::io::AsyncReadExt; -use tokio::sync::mpsc::Receiver; -use tracing::error; - -pub struct XLogFile { - conf: XLCfg, - file: AsyncFile, -} - -struct AsyncFile { - channel_name: String, - log_file_seq_no: u32, - _buf: Vec, - file: File, - file_size: u64, -} - -async fn new_file(folder: &String, name: &String, ext: &String, no: u32) -> RS { - let path = PathBuf::from(folder); - let path = path.join(xl_path::format_xl_file_name(name, ext, no)); - let _r = File::create(path).await; - let file = std_io_error(_r)?; - Ok(file) -} - -impl AsyncFile { - fn new(channel_name: String, file: File, file_size: u64, log_file_seq_no: u32) -> AsyncFile { - Self { - channel_name, - log_file_seq_no, - _buf: Default::default(), - file, - file_size, - } - } - - async fn fsync(&mut self) -> RS<()> { - let r = self.file.sync_all().await; - std_io_error(r) - } - - async fn write_all(&mut self, lsn: LSN, buf: Buf, cfg: &XLCfg) -> RS<()> { - let _trace = task_trace!(); - if self.file_size > cfg.log_file_size_limit() { - panic!("size limit exceeded"); - } - let mut _write_buf_offset = 0; - let capacity_low = - self.file_size as usize + ChunkHdr::size_of() + ChunkTail::size_of() + buf.len() - > cfg.log_file_size_limit() as usize; - let mut file_offset = self.file_size; - if capacity_low { - let mut seq = 0; - let mut last_chunk = false; - while buf.len() > _write_buf_offset { - let possible_write_len = - cfg.log_file_size_limit() as usize - self.file_size as usize; - if possible_write_len > ChunkHdr::size_of() + ChunkTail::size_of() { - let len1 = possible_write_len - ChunkHdr::size_of() - ChunkTail::size_of(); - let len = min(len1, buf.len() - _write_buf_offset); - let _buf = &buf[_write_buf_offset.._write_buf_offset + len]; - let size = - write_chunk_to_u_file(&mut self.file, lsn, _buf, Some((seq, last_chunk))) - .await?; - _write_buf_offset += _buf.len(); - self.file_size += size as u64; - seq += 1; - last_chunk = buf.len() == _write_buf_offset; - self.fsync().await?; - } - self.file_size = 0; - self.log_file_seq_no += 1; - let file = new_file( - cfg.log_path(), - &self.channel_name, - cfg.log_ext_name(), - self.log_file_seq_no, - ) - .await?; - self.file = file; - } - } else { - let size = write_chunk_to_u_file(&mut self.file, lsn, &buf, None).await?; - file_offset += size as u64; - _write_buf_offset += size; - self.file_size = file_offset; - } - - Ok(()) - } -} - -impl XLogFile { - pub fn new(cfg: XLCfg, channel_name: String, file_size: u64, log_file_no: u32) -> RS { - let path = xl_file_path( - &cfg.x_log_path, - &channel_name, - &cfg.x_log_ext_name, - log_file_no, - ); - let r = std::fs::OpenOptions::new().write(true).open(path); - let file = std_io_error(r)?; - let async_file = File::from_std(file); - Ok(Self { - conf: cfg, - file: AsyncFile::new(channel_name, async_file, file_size, log_file_no), - }) - } - - pub fn file_info(&self) -> XLFileInfo { - XLFileInfo { - cfg: self.conf.clone(), - file_no: self.file.log_file_seq_no, - file_size: self.file.file_size, - channel_name: self.file.channel_name.clone(), - } - } - - pub async fn recovery(conf: XLCfg, name: String) -> RS { - let _trace = task_trace!(); - let min = LOG_C_COMMON_HDR_SIZE * 10usize; - let max = i32::MAX as usize; - if (conf.x_log_file_size_limit as usize) < min - && (conf.x_log_file_size_limit as usize) > max - { - panic!("log file size limit must between {} {}", min, max); - } - - Self::recovery_gut(conf, name).await - } - - pub async fn f_sync_loop( - &mut self, - receiver: Receiver<(Buf, u64)>, - lsn_syncer: LSNSyncer, - ) -> RS<()> { - let _trace = task_trace!(); - let r = self.f_sync_loop_gut(receiver, lsn_syncer).await; - match r { - Ok(_) => Ok(()), - Err(e) => { - error!("fsync loop error: {}", e); - Err(e) - } - } - } - - async fn recovery_gut(conf: XLCfg, name: String) -> RS { - let _trace = task_trace!(); - let regex = - Regex::new(format!(r"^{}_([0-9]+).{}$", name, &conf.x_log_ext_name).as_str()).unwrap(); - let _r = read_dir(&conf.x_log_path).await; - let mut read_dir = std_io_error(_r)?; - let mut vec_no = Vec::new(); - loop { - let _r = read_dir.next_entry().await; - let opt = std_io_error(_r)?; - if let Some(entry) = opt { - let name = entry.file_name().to_str().unwrap().to_string(); - let _rc = regex.captures(&name); - if let Some(c) = _rc { - if let Some(m) = c.get(1) { - let no = u32::from_str(m.as_str()).unwrap(); - vec_no.push(no); - } - } - } else { - break; - } - } - vec_no.sort(); - - let (seq_no, file, size) = if let Some(no) = vec_no.last() { - let file = Self::open_std_file(&conf.x_log_path, &name, &conf.x_log_ext_name, *no)?; - let _r = file.metadata(); - let meta = std_io_error(_r)?; - let size = meta.len(); - if size > conf.x_log_file_size_limit { - panic!("file size limit exceeded"); - } - (*no, File::from_std(file), size) - } else { - ( - 1, - new_file(&conf.x_log_path, &name, &conf.x_log_ext_name, 1).await?, - 0, - ) - }; - - let s = Self { - conf, - file: AsyncFile::new(name, file, size, seq_no), - }; - Ok(s) - } - - fn open_std_file(folder: &String, name: &String, ext: &String, no: u32) -> RS { - let _trace = task_trace!(); - let path = PathBuf::from(folder); - let path = path.join(xl_path::format_xl_file_name(name, ext, no)); - //info!("open file {}", path.as_path().display()); - let _r = StdFile::open(path); - let file = std_io_error(_r)?; - Ok(file) - } - - // if prev_seq_no == 0, then, - #[allow(dead_code)] - fn consolidate_part(vec: Vec) -> RS { - let mut buf = Buf::new(); - for v in vec { - buf.extend(v); - } - let _r = decode_binary::(&buf); - let l = match _r { - Ok(l) => l, - Err(e) => { - return Err(m_error!(ER::DecodeErr, "", e)); - } - }; - Ok(l) - } - - async fn read_from_all( - conf: &XLCfg, - name: &String, - log_file_ids: Vec, - ) -> RS> { - let _trace = task_trace!(); - let mut res = vec![]; - let mut prev_part: Vec = Vec::::new(); - for id in log_file_ids.iter() { - let r = Self::read_from_log_file(conf, name, *id, false).await?; - let parts = match r { - Ok(vec) => vec, - Err(_) => { - panic!("log entry cannot start from log part "); - } - }; - for part in parts { - match part { - XLChunk::Part(p) => { - prev_part.extend(p); - } - XLChunk::Whole(l) => { - if prev_part.len() > 1 { - let mut part_vec = Vec::new(); - std::mem::swap(&mut part_vec, &mut prev_part); - let xl = Self::consolidate_part(part_vec)?; - res.push(xl); - } - res.push(l); - } - } - } - } - Ok(res) - } - async fn read_to_buf(conf: &XLCfg, name: &String, log_file_id: u32) -> RS { - let _trace = task_trace!(); - let std_file = - Self::open_std_file(&conf.x_log_path, name, &conf.x_log_ext_name, log_file_id)?; - let mut file = TFile::from_std(std_file); - let mut buf = Buf::new(); - resize_buf(&mut buf, conf.x_log_file_size_limit as usize); - let _r = file.read_to_end(&mut buf).await; - let n = std_io_error(_r)?; - buf.resize(n, 0); - Ok(buf) - } - - async fn read_from_log_file( - conf: &XLCfg, - name: &String, - log_file_id: u32, - read_first_part: bool, - ) -> RS, u32>> { - let _trace = task_trace!(); - let mut buf = Self::read_to_buf(conf, name, log_file_id).await?; - let _buf_slice = &mut buf.as_mut_slice(); - if read_first_part { - let _r = decode_chunk_hdr(_buf_slice); - if let Ok(_hdr) = _r { - // todo - } else { - panic!("todo, corrupt log"); - } - } - let (vec, _) = Self::read_from_buf(_buf_slice)?; - Ok(Ok(vec)) - } - - fn read_from_buf(slice: &[u8]) -> RS<(Vec, usize)> { - let mut offset = 0usize; - let mut result = vec![]; - while offset < slice.len() { - let slice = &slice[offset..]; - let _r = decode_chunk(slice); - let (hdr, body) = match _r { - Ok((h, body)) => (h, body), - Err(e) => { - // todo! corrupted log - return Err(m_error!(ER::DecodeErr, "", e)); - } - }; - - let header_size = ChunkHdr::size_of(); - let body_size = hdr.body_length() as usize; - offset += header_size + body_size + LOG_C_TAIL_SIZE; - let xl_part = match hdr.chunk_type() { - XLChunkType::Part => { - let body_buf = Buf::from(body); - XLChunk::Part(vec![body_buf]) - } - XLChunkType::Whole => { - let _r = decode_binary::(body); - let xl_batch = match _r { - Ok(l) => l, - Err(_e) => { - // log corrupt - return Err(m_error!(ER::DecodeErr, "", _e)); - } - }; - XLChunk::Whole(xl_batch) - } - }; - result.push(xl_part); - } - assert_eq!(slice.len(), offset); - Ok((result, offset)) - } - - async fn f_sync_loop_gut( - &mut self, - receiver: Receiver<(Buf, u64)>, - lsn_syncer: LSNSyncer, - ) -> RS<()> { - let _trace = task_trace!(); - let mut receiver = receiver; - loop { - let mut vec = vec![]; - let mut ready_lsn = vec![]; - let r = receiver.recv_many(&mut vec, 10).await; - if r == 0 { - break; - } - for (buf, lsn) in vec { - //info!("handle lsn {}", lsn); - self.file.write_all(lsn, buf, &self.conf).await?; - ready_lsn.push(lsn); - //info!("handle lsn {} done", lsn); - } - self.file.fsync().await?; - //info!("ready lsn {:?}", ready_lsn); - lsn_syncer.ready(ready_lsn); - } - Ok(()) - } -} - -unsafe impl Sync for XLogFile {} -unsafe impl Send for XLogFile {} diff --git a/mudu_kernel/src/x_log/x_log_file_iou.rs b/mudu_kernel/src/x_log/x_log_file_iou.rs deleted file mode 100644 index 31c70e5..0000000 --- a/mudu_kernel/src/x_log/x_log_file_iou.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::x_log::iou::{io_uring_event_loop, IOUSetting}; -use crate::x_log::lsn_syncer::LSNSyncer; -use mudu::common::buf::Buf; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use tokio::sync::mpsc::Receiver; - -struct XLogFileIOU { - channel_name: String, -} - -const SECTOR_SIZE: u64 = 512; - -pub async fn f_sync_io_uring( - file_path: Vec, - receiver: Receiver<(Buf, u64)>, - lsn_syncer: LSNSyncer, -) -> RS<()> { - io_uring_event_loop( - file_path, - receiver, - |d| d, - |u| lsn_syncer.ready(u), - IOUSetting::default(), - ) - .await - .map_err(|e| m_error!(EC::IOErr, "io_uring event loop", e))?; - - Ok(()) -} diff --git a/mudu_kernel/src/x_log/x_log_impl.rs b/mudu_kernel/src/x_log/x_log_impl.rs deleted file mode 100644 index 9fe19d7..0000000 --- a/mudu_kernel/src/x_log/x_log_impl.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::contract::lsn::LSN; -use crate::contract::x_log::{OptAppend, XLog}; -use crate::contract::xl_batch::XLBatch; -use crate::contract::xl_rec::XLRec; -use crate::x_log::lsn_allocator::LSNAllocator; -use crate::x_log::lsn_syncer::LSNSyncer; -use mudu::common::bc_enc::encode_binary; -use mudu::common::buf::Buf; -use mudu::common::result::RS; - -use crate::contract::waiter::Waiter; -use async_trait::async_trait; -use mudu_utils::task_trace; -use std::sync::Arc; -use tokio::sync::mpsc::{Receiver, Sender}; - -pub struct XLogImpl { - inner: Arc, -} - -struct XLogImplInner { - lsn_allocator: LSNAllocator, - lsn_syncer: LSNSyncer, - sender: Vec, -} - -#[derive(Clone)] -pub struct XLogImplFlusher { - lsn: LSN, - syncer: LSNSyncer, -} - -impl XLogImpl { - pub fn new(lsn_allocator: LSNAllocator, lsn_syncer: LSNSyncer, sender: Vec) -> Self { - Self { - inner: Arc::new(XLogImplInner::new(lsn_allocator, lsn_syncer, sender)), - } - } -} - -impl XLogImplFlusher { - fn new(lsn: LSN, syncer: LSNSyncer) -> XLogImplFlusher { - Self { lsn, syncer } - } -} - -impl XLogImplInner { - pub fn new(lsn_allocator: LSNAllocator, lsn_syncer: LSNSyncer, sender: Vec) -> Self { - if sender.len() == 0 { - panic!("log sender size cannot be 0") - } - Self { - lsn_allocator, - lsn_syncer, - sender, - } - } - - async fn append_gut( - &self, - log_rec: Vec, - wait: bool, - ) -> RS<(LSN, Option>>)> { - let _trace = task_trace!(); - let lsn = self.lsn_allocator.next(); - let batch = XLBatch::new(lsn, log_rec); - let buf = encode_binary(&batch).unwrap(); - let n = (lsn as usize) % self.sender.len(); - //info!("append log lsn {}", lsn); - let r = self.sender[n].send((buf, lsn)).await; - r.expect("append log error"); - let opt_flusher: Option>> = if wait { - let flusher = Self::new_flusher(self.lsn_syncer.clone(), lsn); - Some(Arc::new(flusher)) - } else { - None - }; - Ok((lsn, opt_flusher)) - } - - fn flush_all_gut(&self) -> RS { - let lsn = self.lsn_allocator.alloc_max(); - let flusher = Self::new_flusher(self.lsn_syncer.clone(), lsn); - Ok(flusher) - } - - fn flush_gut(&self, lsn: LSN) -> RS { - let flusher = Self::new_flusher(self.lsn_syncer.clone(), lsn); - Ok(flusher) - } - - fn new_flusher(syncer: LSNSyncer, lsn: LSN) -> XLogImplFlusher { - XLogImplFlusher::new(lsn, syncer) - } -} - -#[async_trait] -impl XLog for XLogImpl { - async fn append( - &self, - log_rec: Vec, - opt: OptAppend, - ) -> RS<(LSN, Option>>)> { - let _trace = task_trace!(); - self.inner.append_gut(log_rec, opt.wait).await - } - - async fn flush(&self, lsn: LSN) -> RS>> { - let _trace = task_trace!(); - let f = self.inner.flush_gut(lsn)?; - Ok(Arc::new(f)) - } - - async fn flush_all(&self) -> RS>> { - let _trace = task_trace!(); - let f = self.inner.flush_all_gut()?; - Ok(Arc::new(f)) - } -} - -unsafe impl Send for XLogImpl {} -unsafe impl Sync for XLogImpl {} - -#[async_trait] -impl Waiter for XLogImplFlusher { - async fn wait(&self) -> RS { - let _ = task_trace!(); - self.syncer.flush(self.lsn).await; - Ok(self.lsn) - } -} - -unsafe impl Send for XLogImplFlusher {} -unsafe impl Sync for XLogImplFlusher {} - -pub type LogSender = Sender<(Buf, LSN)>; -pub type LogReceiver = Receiver<(Buf, LSN)>; diff --git a/mudu_kernel/src/x_log/x_log_service.rs b/mudu_kernel/src/x_log/x_log_service.rs deleted file mode 100644 index f69e1af..0000000 --- a/mudu_kernel/src/x_log/x_log_service.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::contract::x_log::XLog; -use crate::x_log::fsync_task::FsyncTask; -use crate::x_log::lsn_allocator::LSNAllocator; -use crate::x_log::lsn_syncer::LSNSyncer; -use crate::x_log::recovery_task::RecoveryTask; -use crate::x_log::x_log_impl::XLogImpl; -use crate::x_log::xl_cfg::XLCfg; -use crate::x_log::xl_file_info::XLFileInfo; -use mudu::common::result::RS; -use mudu::error::ec::EC as ER; -use mudu::m_error; -use mudu_utils::notifier::NotifyWait; -use mudu_utils::sync::a_task::{ATaskRef, AsyncTask}; -use mudu_utils::sync::s_mutex::SMutex; -use mudu_utils::sync::unique_inner::UniqueInner; -use std::fs; -use std::sync::Arc; -use tokio::sync::mpsc::channel; -use tokio::sync::oneshot; - -pub struct XLogService { - lsn_syncer: LSNSyncer, - lsn_allocator: LSNAllocator, - recovery_task: SMutex>, - x_log_channel: SMutex, AsyncTask)>>, -} - -/// 10MB -const LOG_FILE_LIMIT: usize = 1024 * 1024 * 10; - -impl XLogService { - pub fn new(cfg: XLCfg, canceller: NotifyWait, recovery_done: NotifyWait) -> RS { - let lsn_syncer = LSNSyncer::new(0); - let lsn_allocator = LSNAllocator::new(); - - let mut vec_x_log: Vec<(Arc, AsyncTask)> = vec![]; - let mut vec_file_sender = vec![]; - if !fs::exists(&cfg.x_log_path) - .map_err(|e| m_error!(ER::IOErr, "XLogService::new, fs::exists error", e))? - { - fs::create_dir_all(&cfg.x_log_path) - .map_err(|e| m_error!(ER::IOErr, "XLogService::new, fs::create_dir_all error", e))? - } - for _n in 0..cfg.x_log_channels { - let (sender, receiver) = channel(10000); - let name = (_n + 1).to_string(); - let (x_log_file_s, x_log_file_r) = oneshot::channel::(); - let param = FsyncTask::new( - canceller.clone(), - name, - x_log_file_r, - receiver, - lsn_syncer.clone(), - ); - vec_file_sender.push(x_log_file_s); - let f_task: Arc = Arc::new(UniqueInner::new(param)); - let x_log: Arc = Arc::new(XLogImpl::new( - lsn_allocator.clone(), - lsn_syncer.clone(), - vec![sender], - )); - vec_x_log.push((x_log, f_task)); - } - - let recovery_task: Option = Some(Arc::new(UniqueInner::new(RecoveryTask::new( - canceller.clone(), - "recovery".to_string(), - recovery_done, - cfg, - vec_file_sender, - lsn_syncer.clone(), - lsn_allocator.clone(), - )))); - - Ok(Self { - lsn_syncer, - lsn_allocator, - recovery_task: SMutex::new(recovery_task), - x_log_channel: SMutex::new(vec_x_log), - }) - } - - /// transaction log channel, the x log and its fsync task should be run in the same thread - pub fn x_log_channel(&self) -> Vec<(Arc, AsyncTask)> { - let mut x_log = vec![]; - let mut _x_log = self.x_log_channel.lock().unwrap(); - std::mem::swap(&mut *_x_log, &mut x_log); - x_log - } - - pub fn recovery_task(&self) -> AsyncTask { - let r = self.recovery_task.lock(); - let mut guard = match r { - Ok(g) => g, - Err(e) => { - panic!("recovery_task mutex poisoned: {}", e); - } - }; - - let mut task: Option = None; - std::mem::swap(&mut *guard, &mut task); - match task { - Some(task) => task, - None => { - panic!("recovery_task can only be invoked once"); - } - } - } -} - -impl Drop for XLogService { - fn drop(&mut self) { - self.lsn_syncer.finalize(); - } -} diff --git a/mudu_kernel/src/x_log/xid.rs b/mudu_kernel/src/x_log/xid.rs deleted file mode 100644 index 1434c0e..0000000 --- a/mudu_kernel/src/x_log/xid.rs +++ /dev/null @@ -1 +0,0 @@ -pub type XID = u64; diff --git a/mudu_kernel/src/x_log/xl_c_abort.rs b/mudu_kernel/src/x_log/xl_c_abort.rs deleted file mode 100644 index 6006ee5..0000000 --- a/mudu_kernel/src/x_log/xl_c_abort.rs +++ /dev/null @@ -1,22 +0,0 @@ -use arbitrary::Arbitrary; -use mudu::common::bc_dec::{DecErr, Decode, Decoder}; -use mudu::common::bc_enc::{EncErr, Encode, Encoder}; - -#[derive(Arbitrary, Debug, Eq, PartialEq)] -pub struct XLCAbort {} - -impl Encode for XLCAbort { - fn encode(&self, _encoder: &mut E) -> Result<(), EncErr> { - Ok(()) - } - - fn size(&self) -> Result { - Ok(0) - } -} - -impl Decode for XLCAbort { - fn decode(_decoder: &mut E) -> Result { - Ok(Self {}) - } -} diff --git a/mudu_kernel/src/x_log/xl_c_begin.rs b/mudu_kernel/src/x_log/xl_c_begin.rs deleted file mode 100644 index 4bf3381..0000000 --- a/mudu_kernel/src/x_log/xl_c_begin.rs +++ /dev/null @@ -1,22 +0,0 @@ -use arbitrary::Arbitrary; -use mudu::common::bc_dec::{DecErr, Decode, Decoder}; -use mudu::common::bc_enc::{EncErr, Encode, Encoder}; - -#[derive(Arbitrary, Debug, Eq, PartialEq)] -pub struct XLCBegin {} - -impl Encode for XLCBegin { - fn encode(&self, _encoder: &mut E) -> Result<(), EncErr> { - Ok(()) - } - - fn size(&self) -> Result { - Ok(0) - } -} - -impl Decode for XLCBegin { - fn decode(_decoder: &mut E) -> Result { - Ok(Self {}) - } -} diff --git a/mudu_kernel/src/x_log/xl_c_commit.rs b/mudu_kernel/src/x_log/xl_c_commit.rs deleted file mode 100644 index e07127d..0000000 --- a/mudu_kernel/src/x_log/xl_c_commit.rs +++ /dev/null @@ -1,29 +0,0 @@ -use arbitrary::Arbitrary; -use mudu::common::bc_dec::{DecErr, Decode, Decoder}; -use mudu::common::bc_enc::{EncErr, Encode, Encoder}; - -#[derive(Arbitrary, Debug, Eq, PartialEq)] -pub struct XLCCommit {} - -impl Encode for XLCCommit { - fn encode(&self, _encoder: &mut E) -> Result<(), EncErr> { - Ok(()) - } - - fn size(&self) -> Result { - let len = 0; - Ok(len) - } -} - -impl Decode for XLCCommit { - fn decode(_decoder: &mut E) -> Result { - Ok(Self {}) - } -} - -impl XLCCommit { - fn new() -> Self { - Self {} - } -} diff --git a/mudu_kernel/src/x_log/xl_cfg.rs b/mudu_kernel/src/x_log/xl_cfg.rs deleted file mode 100644 index 2536ef4..0000000 --- a/mudu_kernel/src/x_log/xl_cfg.rs +++ /dev/null @@ -1,44 +0,0 @@ -impl XLCfg { - pub fn new( - path: String, - ext: String, - x_log_channels: u32, - x_log_file_size_limit: u64, - use_io_uring: bool, - ) -> XLCfg { - Self { - x_log_use_io_uring: use_io_uring, - x_log_path: path, - x_log_ext_name: ext, - x_log_channels, - x_log_file_size_limit, - } - } -} - -#[derive(Clone, Debug)] -pub struct XLCfg { - pub x_log_use_io_uring: bool, - pub x_log_path: String, - pub x_log_ext_name: String, - pub x_log_channels: u32, - pub x_log_file_size_limit: u64, -} - -impl XLCfg { - pub fn log_path(&self) -> &String { - &self.x_log_path - } - - pub fn log_ext_name(&self) -> &String { - &self.x_log_ext_name - } - - pub fn log_channels(&self) -> u32 { - self.x_log_channels - } - - pub fn log_file_size_limit(&self) -> u64 { - self.x_log_file_size_limit - } -} diff --git a/mudu_kernel/src/x_log/xl_file_info.rs b/mudu_kernel/src/x_log/xl_file_info.rs deleted file mode 100644 index 28e1142..0000000 --- a/mudu_kernel/src/x_log/xl_file_info.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::x_log::xl_cfg::XLCfg; - -#[derive(Clone, Debug)] -pub struct XLFileInfo { - pub cfg: XLCfg, - pub file_no: u32, - pub file_size: u64, - pub channel_name: String, -} diff --git a/mudu_kernel/src/x_log/xl_path.rs b/mudu_kernel/src/x_log/xl_path.rs deleted file mode 100644 index d70ff9e..0000000 --- a/mudu_kernel/src/x_log/xl_path.rs +++ /dev/null @@ -1,10 +0,0 @@ -use std::path::PathBuf; - -pub fn xl_file_path(folder: &String, name: &String, ext: &String, no: u32) -> PathBuf { - let path = PathBuf::from(folder); - path.join(format_xl_file_name(name, ext, no)) -} - -pub fn format_xl_file_name(name: &String, ext: &String, no: u32) -> String { - format!("{}_{}.{}", name, no, ext) -} diff --git a/mudu_macro/src/lib.rs b/mudu_macro/src/lib.rs index a990925..6adf9eb 100644 --- a/mudu_macro/src/lib.rs +++ b/mudu_macro/src/lib.rs @@ -44,15 +44,6 @@ pub fn mudu_proc(_args: TokenStream, input: TokenStream) -> TokenStream { fn_name.span(), ); - let fn_wrapper_ident = syn::Ident::new( - &format!( - "{}{}", - mudu_contract::procedure::proc::MUDU_PROC_PREFIX, - fn_name - ), - fn_name.span(), - ); - let fn_inner_ident = syn::Ident::new( &format!( "{}{}", @@ -195,15 +186,6 @@ world mudu-app-{} {{ ) } - #[unsafe(no_mangle)] - pub extern "C" fn #fn_wrapper_ident (p1_ptr: *const u8, p1_len: usize, p2_ptr: *mut u8, p2_len: usize) -> i32 { - ::mudu_binding::procedure::procedure_invoke::invoke_wrapper( - p1_ptr, p1_len, - p2_ptr, p2_len, - #fn_inner_ident - ) - } - pub fn #fn_inner_ident( param: &::mudu_contract::procedure::procedure_param::ProcedureParam, ) -> ::mudu::common::result::RS<::mudu_contract::procedure::procedure_result::ProcedureResult> { @@ -216,10 +198,7 @@ world mudu-app-{} {{ pub fn #fn_inner_ident_p2( param: ::mudu_contract::procedure::procedure_param::ProcedureParam, ) -> ::mudu::common::result::RS<::mudu_contract::procedure::procedure_result::ProcedureResult> { - // generate tuple desc - let desc = <(#(#types),*) as ::mudu_contract::tuple::tuple_datum::TupleDatum>::tuple_desc_static(&#code_arg_names); - - #invoke_handling + #fn_inner_ident(¶m) } pub fn #fn_argv_desc() -> &'static ::mudu_contract::tuple::tuple_field_desc::TupleFieldDesc { diff --git a/mudu_runtime/Cargo.toml b/mudu_runtime/Cargo.toml index 6bebff6..5b257bf 100644 --- a/mudu_runtime/Cargo.toml +++ b/mudu_runtime/Cargo.toml @@ -12,6 +12,7 @@ debug_trace = ["mudu_kernel/debug_trace", "mudu_utils/debug_trace"] [dependencies] mudu_type = { workspace = true } +mudu_sys = { workspace = true } mudu_contract = { workspace = true } mudu_binding = { workspace = true } mudu_kernel = { workspace = true } @@ -45,14 +46,11 @@ async-trait = { workspace = true } futures = { workspace = true } turso = {version = "0.4.0-pre.19"} -as-any = {version = "0.3.2"} + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] postgres = { workspace = true } scc = { workspace = true } -[dev-dependencies] -reqwest = { workspace = true } -[build-dependencies] diff --git a/mudu_runtime/src/async_utils/blocking.rs b/mudu_runtime/src/async_utils/blocking.rs index 3b7f509..34c0161 100644 --- a/mudu_runtime/src/async_utils/blocking.rs +++ b/mudu_runtime/src/async_utils/blocking.rs @@ -1,23 +1,20 @@ use mudu::common::result::RS; use mudu::error::ec::EC; use mudu::m_error; -use std::thread; - +use std::future::Future; pub fn run_async(future: F) -> RS where F: Future + Send + 'static, T: Send + 'static, { - let thread = thread::Builder::new().spawn(move || { + let thread = mudu_sys::task::spawn_thread(move || { let runtime = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap(); runtime.block_on(async move { future.await }) - }); - - let result = thread.map_err(|e| m_error!(EC::InternalErr, "join thread error", e))?; - let r = result + })?; + let r = thread .join() .map_err(|_e| m_error!(EC::InternalErr, "join thread error"))?; Ok(r) diff --git a/mudu_runtime/src/backend/app_mgr.rs b/mudu_runtime/src/backend/app_mgr.rs index 4619a83..3eb19f9 100644 --- a/mudu_runtime/src/backend/app_mgr.rs +++ b/mudu_runtime/src/backend/app_mgr.rs @@ -3,7 +3,7 @@ use crate::backend::mududb_cfg::MuduDBCfg; use crate::service::app_list::AppList; use async_trait::async_trait; use mudu::common::result::RS; -use mudu_kernel::server_ur::procedure_runtime::ProcInvoker; +use mudu_kernel::server::async_func_runtime::AsyncFuncInvoker; use std::sync::Arc; #[async_trait(?Send)] @@ -36,7 +36,7 @@ pub trait AppMgr: Send + Sync { /// Create a new procedure invoker for one runtime consumer. /// /// This is a factory method, not a shared accessor. Each returned - /// `Arc` must own an independent invocation environment and + /// `Arc` must own an independent invocation environment and /// must not share mutable runtime internals, stores, or other execution /// state with invokers returned from other calls. This requirement exists /// so backends such as the io_uring worker runtime can safely create one @@ -45,5 +45,5 @@ pub trait AppMgr: Send + Sync { /// The implementation must reuse the current procedure invocation /// mechanism, and it must not change or bypass any of the existing public /// runtime behavior for legacy `p1` or component-based execution. - async fn create_invoker(&self, cfg: &MuduDBCfg) -> RS>; + async fn create_invoker(&self, cfg: &MuduDBCfg) -> RS>; } diff --git a/mudu_runtime/src/backend/backend.rs b/mudu_runtime/src/backend/backend.rs index 9e97f52..439be46 100644 --- a/mudu_runtime/src/backend/backend.rs +++ b/mudu_runtime/src/backend/backend.rs @@ -27,9 +27,8 @@ impl Backend { pub fn sync_serve_with_stop(cfg: MuduDBCfg, stop: Waiter) -> RS<()> { info!( server_mode = ?cfg.server_mode, - runtime_target = ?cfg.runtime_target(), + component_target = ?cfg.component_target(), enable_async = cfg.enable_async, - enable_p2 = cfg.enable_p2, listen_ip = %cfg.listen_ip, http_listen_port = cfg.http_listen_port, pg_listen_port = cfg.pg_listen_port, @@ -102,7 +101,7 @@ impl Backend { service.register(TaskWrapper::new_async_local(ls, accept_task))?; let session_task = - SessionHandleTask::new(cfg.data_path.clone(), receivers, canceller.clone()); + SessionHandleTask::new(cfg.db_path.clone(), receivers, canceller.clone()); let ls = LocalSet::new(); service.register(TaskWrapper::new_async_local(ls, session_task))?; Ok(()) diff --git a/mudu_runtime/src/backend/http_api/io_uring_http_api.rs b/mudu_runtime/src/backend/http_api/io_uring_http_api.rs index aef627d..c972ce2 100644 --- a/mudu_runtime/src/backend/http_api/io_uring_http_api.rs +++ b/mudu_runtime/src/backend/http_api/io_uring_http_api.rs @@ -1,6 +1,6 @@ use super::{ - AsyncIoUringInvokeClientFactory, HttpApi, ServerTopology, TokioIoUringInvokeClientFactory, - WorkerTopology, find_app, parse_json_object_body, to_param, + find_app, parse_json_object_body, to_param, AsyncIoUringInvokeClientFactory, HttpApi, + ServerTopology, TokioIoUringInvokeClientFactory, WorkerTopology, }; use crate::backend::app_mgr::AppMgr; use crate::backend::mududb_cfg::MuduDBCfg; @@ -9,7 +9,7 @@ use mudu::common::result::RS; use mudu::utils::json::JsonValue; use mudu_binding::procedure::procedure_invoke; use mudu_contract::procedure::proc_desc::ProcDesc; -use mudu_kernel::server_ur::worker_registry::WorkerRegistry; +use mudu_kernel::server::worker_registry::WorkerRegistry; use serde_json::Value; use std::sync::Arc; diff --git a/mudu_runtime/src/backend/http_api/legacy_http_api.rs b/mudu_runtime/src/backend/http_api/legacy_http_api.rs index 482d1aa..19df831 100644 --- a/mudu_runtime/src/backend/http_api/legacy_http_api.rs +++ b/mudu_runtime/src/backend/http_api/legacy_http_api.rs @@ -1,6 +1,6 @@ use super::{ - HttpApi, legacy_invoke_async_proc, legacy_invoke_sync_proc, parse_json_object_body, - runtime_get_app_and_desc, + legacy_invoke_async_proc, legacy_invoke_sync_proc, parse_json_object_body, + runtime_get_app_and_desc, HttpApi, }; use crate::service::runtime::Runtime; use async_trait::async_trait; diff --git a/mudu_runtime/src/backend/http_api/mod.rs b/mudu_runtime/src/backend/http_api/mod.rs index 1a5000d..e750b8c 100644 --- a/mudu_runtime/src/backend/http_api/mod.rs +++ b/mudu_runtime/src/backend/http_api/mod.rs @@ -28,7 +28,7 @@ use crate::service::app_inst::AppInst; use crate::service::runtime::Runtime; use actix_cors::Cors; use actix_web::http::StatusCode; -use actix_web::{App, HttpResponse, HttpServer, Responder, delete, get, post, web}; +use actix_web::{delete, get, post, web, App, HttpResponse, HttpServer, Responder}; use async_trait::async_trait; use base64::Engine; use mudu::common::id::OID; @@ -217,7 +217,7 @@ pub async fn serve_http_api_on_listener_with_stop( if let Some(stop) = stop { let handle = server.handle(); - tokio::spawn(async move { + mudu_sys::task::spawn_tokio(async move { stop.wait().await; handle.stop(true).await; }); @@ -514,7 +514,7 @@ async fn find_app(app_mgr: &dyn AppMgr, app_name: &str) -> RS { #[cfg(test)] mod test { use super::*; - use actix_web::{App, test}; + use actix_web::{test, App}; #[cfg(target_os = "linux")] use mudu::common::app_info::AppInfo; #[cfg(target_os = "linux")] @@ -523,7 +523,7 @@ mod test { use mudu_contract::procedure::procedure_result::ProcedureResult; use mudu_contract::tuple::tuple_datum::TupleDatum; #[cfg(target_os = "linux")] - use mudu_kernel::server_ur::procedure_runtime::ProcInvoker; + use mudu_kernel::server::async_func_runtime::AsyncFuncInvoker; #[cfg(target_os = "linux")] use std::sync::Mutex; @@ -689,7 +689,7 @@ mod test { }) } - async fn create_invoker(&self, _cfg: &MuduDBCfg) -> RS> { + async fn create_invoker(&self, _cfg: &MuduDBCfg) -> RS> { Err(m_error!(EC::NotImplemented, "unused in test")) } } @@ -700,7 +700,7 @@ mod test { let log_dir = std::env::temp_dir().join(format!("http_api_test_{}", mudu::common::id::gen_oid())); let registry = - mudu_kernel::server_ur::worker_registry::load_or_create_worker_registry(&log_dir, 4) + mudu_kernel::server::worker_registry::load_or_create_worker_registry(&log_dir, 4) .unwrap(); let requests = Arc::new(Mutex::new(Vec::new())); let api = IoUringHttpApi::with_client_factory( diff --git a/mudu_runtime/src/backend/iouring_admin.rs b/mudu_runtime/src/backend/iouring_admin.rs index 56d2235..e88682f 100644 --- a/mudu_runtime/src/backend/iouring_admin.rs +++ b/mudu_runtime/src/backend/iouring_admin.rs @@ -6,7 +6,7 @@ use crate::backend::mududb_cfg::MuduDBCfg; use mudu::common::result::RS; use mudu::error::ec::EC; use mudu::m_error; -use mudu_kernel::server_ur::worker_registry::WorkerRegistry; +use mudu_kernel::server::worker_registry::WorkerRegistry; use mudu_utils::notifier::Waiter; use std::sync::Arc; use std::sync::mpsc; diff --git a/mudu_runtime/src/backend/mod.rs b/mudu_runtime/src/backend/mod.rs index 471c06d..0e11117 100644 --- a/mudu_runtime/src/backend/mod.rs +++ b/mudu_runtime/src/backend/mod.rs @@ -18,5 +18,9 @@ mod app_mgr; mod iouring_admin; #[cfg(target_os = "linux")] pub mod mudu_app_mgr; +pub mod mudu_conn_async; +mod mudu_conn_core; +mod mudu_prepared_stmt; +mod mudu_result_set_async; #[cfg(target_os = "linux")] pub mod server_ur; diff --git a/mudu_runtime/src/backend/mudu_app_mgr.rs b/mudu_runtime/src/backend/mudu_app_mgr.rs index 050daf4..322c0b6 100644 --- a/mudu_runtime/src/backend/mudu_app_mgr.rs +++ b/mudu_runtime/src/backend/mudu_app_mgr.rs @@ -11,8 +11,8 @@ use mudu::common::result::RS; use mudu::error::ec::EC; use mudu::m_error; use mudu_binding::procedure::procedure_invoke; -use mudu_kernel::server_ur::procedure_runtime::ProcInvoker; -use mudu_kernel::server_ur::worker_local::WorkerLocalRef; +use mudu_kernel::server::async_func_runtime::AsyncFuncInvoker; +use mudu_kernel::server::worker_local::WorkerLocalRef; use std::collections::HashSet; use std::env::temp_dir; use std::fs; @@ -49,7 +49,7 @@ impl MuduProcInvoker { } #[async_trait] -impl ProcInvoker for MuduProcInvoker { +impl AsyncFuncInvoker for MuduProcInvoker { async fn invoke( &self, session_id: OID, @@ -191,23 +191,23 @@ impl AppMgr for MuduAppMgr { Ok(AppList { apps }) } - async fn create_invoker(&self, cfg: &MuduDBCfg) -> RS> { + async fn create_invoker(&self, cfg: &MuduDBCfg) -> RS> { let cfg = cfg.clone(); let invoker = build_owned_proc_invoker(&cfg).await?; self.register_invoker(&invoker); - Ok(invoker as Arc) + Ok(invoker as Arc) } } async fn create_runtime_from_cfg(cfg: &MuduDBCfg) -> RS> { - let runtime_target = cfg.runtime_target(); - let enable_async = cfg.enable_async && runtime_target.uses_component_model(); + let component_target = cfg.component_target(); + let enable_async = cfg.enable_async; create_runtime_service( &cfg.mpk_path, - &cfg.data_path, + &cfg.db_path, None, RuntimeOpt { - target: runtime_target, + component_target, enable_async, }, ) @@ -216,7 +216,7 @@ async fn create_runtime_from_cfg(cfg: &MuduDBCfg) -> RS> { async fn build_owned_proc_invoker(cfg: &MuduDBCfg) -> RS> { let runtime = create_runtime_from_cfg(cfg).await?; - let enable_async = cfg.enable_async && cfg.runtime_target().uses_component_model(); + let enable_async = cfg.enable_async; Ok(Arc::new(MuduProcInvoker::new( cfg.clone(), runtime, diff --git a/mudu_runtime/src/backend/mudu_conn_async.rs b/mudu_runtime/src/backend/mudu_conn_async.rs new file mode 100644 index 0000000..42454c9 --- /dev/null +++ b/mudu_runtime/src/backend/mudu_conn_async.rs @@ -0,0 +1,94 @@ +use crate::backend::mudu_conn_core::MuduConnCore; +use crate::backend::mudu_prepared_stmt::MuduPreparedStmt; +use async_trait::async_trait; +use mudu::common::result::RS; +use mudu::common::xid::XID; +use mudu_contract::database::db_conn::DBConnAsync; +use mudu_contract::database::prepared_stmt::PreparedStmt; +use mudu_contract::database::result_set::ResultSetAsync; +use mudu_contract::database::sql_params::SQLParams; +use mudu_contract::database::sql_stmt::SQLStmt; +use mudu_kernel::contract::meta_mgr::MetaMgr; +use mudu_kernel::x_engine::api::XContract; +use std::sync::Arc; + +pub struct MuduConnAsync { + core: Arc, +} + +impl MuduConnAsync { + pub fn new(meta_mgr: Arc, x_contract: Arc) -> Self { + Self { + core: Arc::new(MuduConnCore::new(meta_mgr, x_contract)), + } + } +} + +#[async_trait] +impl DBConnAsync for MuduConnAsync { + async fn prepare(&self, stmt: Box) -> RS> { + let parsed = self.core.parse_one(stmt.as_ref())?; + let desc = self.core.describe_stmt(parsed.clone()).await?; + Ok(Arc::new(MuduPreparedStmt::new( + self.core.clone(), + parsed, + desc, + ))) + } + + async fn exec_silent(&self, sql_text: String) -> RS<()> { + let stmts = self.core.parse_many(&sql_text)?; + for stmt in stmts { + match stmt { + sql_parser::ast::stmt_type::StmtType::Select(_) => { + let _ = self.core.query(stmt, Box::new(())).await?; + } + sql_parser::ast::stmt_type::StmtType::Command(_) => { + let _ = self.core.execute(stmt, Box::new(())).await?; + } + } + } + Ok(()) + } + + async fn begin_tx(&self) -> RS { + self.core.begin_tx().await + } + + async fn rollback_tx(&self) -> RS<()> { + self.core.rollback_tx().await + } + + async fn commit_tx(&self) -> RS<()> { + self.core.commit_tx().await + } + + async fn query( + &self, + sql: Box, + param: Box, + ) -> RS> { + let parsed = self.core.parse_one(sql.as_ref())?; + self.core.query(parsed, param).await + } + + async fn execute(&self, sql: Box, param: Box) -> RS { + let parsed = self.core.parse_one(sql.as_ref())?; + self.core.execute(parsed, param).await + } + + async fn batch(&self, sql: Box, param: Box) -> RS { + if param.size() != 0 { + return Err(mudu::m_error!( + mudu::error::ec::EC::NotImplemented, + "batch with parameters is not implemented" + )); + } + let stmts = self.core.parse_many(sql.as_ref())?; + let mut total = 0; + for stmt in stmts { + total += self.core.execute(stmt, Box::new(())).await?; + } + Ok(total) + } +} diff --git a/mudu_runtime/src/backend/mudu_conn_core.rs b/mudu_runtime/src/backend/mudu_conn_core.rs new file mode 100644 index 0000000..4aaa8a9 --- /dev/null +++ b/mudu_runtime/src/backend/mudu_conn_core.rs @@ -0,0 +1,201 @@ +use crate::backend::mudu_result_set_async::MuduResultSetAsync; +use mudu::common::result::RS; +use mudu::common::xid::XID; +use mudu::error::ec::EC; +use mudu::m_error; +use mudu_contract::database::result_set::ResultSetAsync; +use mudu_contract::database::sql_params::SQLParams; +use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; +use mudu_contract::tuple::tuple_value::TupleValue; +use mudu_contract::tuple::typed_bin::TypedBin; +use mudu_kernel::contract::meta_mgr::MetaMgr; +use mudu_kernel::contract::query_exec::QueryExec; +use mudu_kernel::sql::binder::Binder; +use mudu_kernel::sql::bound_stmt::BoundStmt; +use mudu_kernel::sql::describer::Describer; +use mudu_kernel::sql::plan_ctx::PlanCtx; +use mudu_kernel::sql::planner::Planner; +use mudu_kernel::x_engine::api::XContract; +use mudu_type::datum::DatumDyn; +use sql_parser::ast::parser::SQLParser; +use sql_parser::ast::stmt_type::StmtType; +use std::sync::Arc; +use tokio::sync::Mutex; + +pub struct MuduConnCore { + meta_mgr: Arc, + x_contract: Arc, + parser: Arc, + tx_state: Arc>>, +} + +enum TxScope { + Auto(XID), + Existing, +} + +impl MuduConnCore { + pub fn new(meta_mgr: Arc, x_contract: Arc) -> Self { + Self { + meta_mgr, + x_contract, + parser: Arc::new(SQLParser::new()), + tx_state: Arc::new(Mutex::new(None)), + } + } + + pub fn parse_one(&self, sql: &dyn mudu_contract::database::sql_stmt::SQLStmt) -> RS { + let stmt_list = self.parser.parse(&sql.to_sql_string())?; + let mut stmts = stmt_list.into_stmts(); + if stmts.len() != 1 { + return Err(m_error!(EC::ParseErr, "expected exactly one statement")); + } + Ok(stmts.remove(0)) + } + + pub fn parse_many( + &self, + sql: &dyn mudu_contract::database::sql_stmt::SQLStmt, + ) -> RS> { + Ok(self.parser.parse(&sql.to_sql_string())?.into_stmts()) + } + + pub async fn describe_stmt(&self, stmt: StmtType) -> RS> { + let desc = Describer::new(self.meta_mgr.clone()).describe(stmt).await?; + Ok(Arc::new(desc)) + } + + pub async fn query( + &self, + stmt: StmtType, + params: Box, + ) -> RS> { + let (scope, xid) = self.enter_tx().await?; + let result = self.query_inner(stmt, params, xid).await; + match self.leave_tx(scope, result.is_ok()).await { + Ok(()) => {} + Err(e) => return Err(e), + } + result.map(|rs| Arc::new(rs) as Arc) + } + + pub async fn execute(&self, stmt: StmtType, params: Box) -> RS { + let (scope, xid) = self.enter_tx().await?; + let result = self.execute_inner(stmt, params, xid).await; + self.leave_tx(scope, result.is_ok()).await?; + result + } + + async fn query_inner( + &self, + stmt: StmtType, + params: Box, + xid: XID, + ) -> RS { + let bound = Binder::new(self.meta_mgr.clone()) + .bind(stmt, params.as_ref()) + .await?; + let BoundStmt::Query(bound_query) = bound else { + return Err(m_error!(EC::TypeErr, "statement is not a query")); + }; + let planner = Planner::new(PlanCtx { + xid, + meta_mgr: self.meta_mgr.clone(), + x_contract: self.x_contract.clone(), + }); + let exec = planner.plan_query(bound_query).await?; + MuduResultSetAsync::from_query_exec(exec).await + } + + async fn execute_inner(&self, stmt: StmtType, params: Box, xid: XID) -> RS { + let bound = Binder::new(self.meta_mgr.clone()) + .bind(stmt, params.as_ref()) + .await?; + let BoundStmt::Command(bound_command) = bound else { + return Err(m_error!(EC::TypeErr, "statement is not a command")); + }; + let planner = Planner::new(PlanCtx { + xid, + meta_mgr: self.meta_mgr.clone(), + x_contract: self.x_contract.clone(), + }); + let cmd = planner.plan_command(bound_command).await?; + cmd.prepare().await?; + cmd.run().await?; + cmd.affected_rows().await + } + + async fn enter_tx(&self) -> RS<(TxScope, XID)> { + let guard = self.tx_state.lock().await; + if let Some(xid) = *guard { + return Ok((TxScope::Existing, xid)); + } + drop(guard); + let xid = self.x_contract.begin_tx().await?; + Ok((TxScope::Auto(xid), xid)) + } + + async fn leave_tx(&self, scope: TxScope, success: bool) -> RS<()> { + match scope { + TxScope::Existing => Ok(()), + TxScope::Auto(xid) => { + if success { + self.x_contract.commit_tx(xid).await + } else { + self.x_contract.abort_tx(xid).await + } + } + } + } + + pub async fn begin_tx(&self) -> RS { + let mut guard = self.tx_state.lock().await; + if let Some(xid) = *guard { + return Ok(xid); + } + let xid = self.x_contract.begin_tx().await?; + *guard = Some(xid); + Ok(xid) + } + + pub async fn commit_tx(&self) -> RS<()> { + let mut guard = self.tx_state.lock().await; + let xid = guard + .take() + .ok_or_else(|| m_error!(EC::NoSuchElement, "no active transaction"))?; + drop(guard); + self.x_contract.commit_tx(xid).await + } + + pub async fn rollback_tx(&self) -> RS<()> { + let mut guard = self.tx_state.lock().await; + let xid = guard + .take() + .ok_or_else(|| m_error!(EC::NoSuchElement, "no active transaction"))?; + drop(guard); + self.x_contract.abort_tx(xid).await + } +} + +pub async fn query_exec_to_rows(exec: Arc) -> RS<(Vec, TupleFieldDesc)> { + exec.open().await?; + let desc = exec.tuple_desc()?; + let mut rows = Vec::new(); + while let Some(row) = exec.next().await? { + rows.push(tuple_field_to_value(row, &desc)?); + } + Ok((rows, desc)) +} + +fn tuple_field_to_value( + row: mudu_contract::tuple::tuple_field::TupleField, + desc: &TupleFieldDesc, +) -> RS { + let mut values = Vec::with_capacity(row.fields().len()); + for (index, field) in row.fields().iter().enumerate() { + let datum_desc = &desc.fields()[index]; + let typed = TypedBin::new(datum_desc.dat_type_id(), field.clone()); + values.push(typed.to_value(datum_desc.dat_type())?); + } + Ok(TupleValue::from(values)) +} diff --git a/mudu_runtime/src/backend/mudu_prepared_stmt.rs b/mudu_runtime/src/backend/mudu_prepared_stmt.rs new file mode 100644 index 0000000..097d01e --- /dev/null +++ b/mudu_runtime/src/backend/mudu_prepared_stmt.rs @@ -0,0 +1,40 @@ +use crate::backend::mudu_conn_core::MuduConnCore; +use async_trait::async_trait; +use mudu::common::result::RS; +use mudu_contract::database::prepared_stmt::PreparedStmt; +use mudu_contract::database::result_set::ResultSetAsync; +use mudu_contract::database::sql_params::SQLParams; +use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; +use sql_parser::ast::stmt_type::StmtType; +use std::sync::Arc; + +pub struct MuduPreparedStmt { + core: Arc, + stmt: StmtType, + desc: Arc, +} + +impl MuduPreparedStmt { + pub fn new(core: Arc, stmt: StmtType, desc: Arc) -> Self { + Self { core, stmt, desc } + } +} + +#[async_trait] +impl PreparedStmt for MuduPreparedStmt { + async fn query(&self, params: Box) -> RS> { + self.core.query(self.stmt.clone(), params).await + } + + async fn execute(&self, params: Box) -> RS { + self.core.execute(self.stmt.clone(), params).await + } + + async fn desc(&self) -> RS> { + Ok(self.desc.clone()) + } + + async fn reset(&self) -> RS<()> { + Ok(()) + } +} diff --git a/mudu_runtime/src/backend/mudu_result_set_async.rs b/mudu_runtime/src/backend/mudu_result_set_async.rs new file mode 100644 index 0000000..eb8b8b6 --- /dev/null +++ b/mudu_runtime/src/backend/mudu_result_set_async.rs @@ -0,0 +1,47 @@ +use crate::backend::mudu_conn_core::query_exec_to_rows; +use async_trait::async_trait; +use mudu::common::result::RS; +use mudu_contract::database::result_set::ResultSetAsync; +use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; +use mudu_contract::tuple::tuple_value::TupleValue; +use std::sync::Arc; +use tokio::sync::Mutex; + +pub struct MuduResultSetAsync { + desc: Arc, + inner: Mutex, +} + +struct ResultRows { + rows: Vec, + index: usize, +} + +impl MuduResultSetAsync { + pub async fn from_query_exec( + exec: Arc, + ) -> RS { + let (rows, desc) = query_exec_to_rows(exec).await?; + Ok(Self { + desc: Arc::new(desc), + inner: Mutex::new(ResultRows { rows, index: 0 }), + }) + } +} + +#[async_trait] +impl ResultSetAsync for MuduResultSetAsync { + async fn next(&self) -> RS> { + let mut inner = self.inner.lock().await; + if inner.index >= inner.rows.len() { + return Ok(None); + } + let index = inner.index; + let row = inner.rows.remove(index); + Ok(Some(row)) + } + + fn desc(&self) -> &TupleFieldDesc { + self.desc.as_ref() + } +} diff --git a/mudu_runtime/src/backend/mududb_cfg.rs b/mudu_runtime/src/backend/mududb_cfg.rs index ea86c9e..6cdb7e4 100644 --- a/mudu_runtime/src/backend/mududb_cfg.rs +++ b/mudu_runtime/src/backend/mududb_cfg.rs @@ -1,4 +1,4 @@ -use crate::service::runtime_opt::{ComponentTarget, RuntimeTarget}; +use crate::service::runtime_opt::ComponentTarget; use mudu::common::result::RS; use mudu::error::ec::EC; use mudu::m_error; @@ -29,7 +29,8 @@ pub enum RoutingMode { #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] pub struct MuduDBCfg { pub mpk_path: String, - pub data_path: String, + #[serde(alias = "data_path")] + pub db_path: String, pub listen_ip: String, pub http_listen_port: u16, #[serde(default = "default_http_worker_threads")] @@ -37,8 +38,6 @@ pub struct MuduDBCfg { pub pg_listen_port: u16, #[serde(default)] pub component_target: Option, - #[serde(default)] - pub enable_p2: bool, pub enable_async: bool, #[serde(default)] pub server_mode: ServerMode, @@ -64,11 +63,11 @@ pub struct MuduDBCfg { impl Display for MuduDBCfg { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - let runtime_target = self.runtime_target(); + let component_target = self.component_target(); write!(f, "MuduDB Setting:\n")?; write!(f, "-------------------\n")?; write!(f, " -> Package path: {}\n", self.mpk_path)?; - write!(f, " -> Data path: {}\n", self.data_path)?; + write!(f, " -> Data path: {}\n", self.db_path)?; write!(f, " -> Listen IP address: {}\n", self.listen_ip)?; write!(f, " -> HTTP Listening port: {}\n", self.http_listen_port)?; write!( @@ -77,7 +76,7 @@ impl Display for MuduDBCfg { self.http_worker_threads )?; write!(f, " -> PG Listening port: {}\n", self.pg_listen_port)?; - write!(f, " -> Runtime target: {:?}\n", runtime_target)?; + write!(f, " -> Component target: {:?}\n", component_target)?; write!(f, " -> Enable Async: {}\n", self.enable_async)?; write!(f, " -> Server mode: {:?}\n", self.server_mode)?; write!(f, " -> TCP Listening port: {}\n", self.tcp_listen_port)?; @@ -126,13 +125,12 @@ impl Default for MuduDBCfg { fn default() -> Self { Self { mpk_path: temp_dir().to_str().unwrap().to_string(), - data_path: temp_dir().to_str().unwrap().to_string(), + db_path: temp_dir().to_str().unwrap().to_string(), listen_ip: "127.0.0.1".to_string(), http_listen_port: 8300, http_worker_threads: default_http_worker_threads(), pg_listen_port: 5432, component_target: None, - enable_p2: true, enable_async: true, server_mode: ServerMode::Legacy, tcp_listen_port: default_tcp_listen_port(), @@ -151,12 +149,8 @@ impl Default for MuduDBCfg { const MUDUDB_CFG_TOML_PATH: &str = ".mudu/mududb_cfg.toml"; impl MuduDBCfg { - pub fn runtime_target(&self) -> RuntimeTarget { - match self.component_target { - Some(target) => RuntimeTarget::Component(target), - None if self.enable_p2 => RuntimeTarget::Component(ComponentTarget::P2), - None => RuntimeTarget::P1, - } + pub fn component_target(&self) -> ComponentTarget { + self.component_target.unwrap_or(ComponentTarget::P2) } pub fn effective_worker_threads(&self) -> usize { @@ -276,7 +270,6 @@ listen_ip = "127.0.0.1" http_listen_port = 8300 http_worker_threads = 1 pg_listen_port = 5432 -enable_p2 = true enable_async = true # 0 = Legacy @@ -307,6 +300,7 @@ routing_mode = 0 cfg.routing_mode, crate::backend::mududb_cfg::RoutingMode::ConnectionId ); + assert_eq!(cfg.db_path, "/tmp/data"); assert_eq!(cfg.http_worker_threads, 1); } } diff --git a/mudu_runtime/src/backend/server_ur/server.rs b/mudu_runtime/src/backend/server_ur/server.rs index d2f560a..224b6f7 100644 --- a/mudu_runtime/src/backend/server_ur/server.rs +++ b/mudu_runtime/src/backend/server_ur/server.rs @@ -5,9 +5,9 @@ use crate::backend::mududb_cfg::MuduDBCfg; use mudu::common::result::RS; use mudu::error::ec::EC; use mudu::m_error; -use mudu_kernel::server_ur::routing::RoutingMode; -use mudu_kernel::server_ur::server::IoUringTcpBackend as KernelIoUringTcpBackend; -use mudu_kernel::server_ur::server::IoUringTcpServerConfig; +use mudu_kernel::server::routing::RoutingMode; +use mudu_kernel::server::server::IoUringTcpBackend as KernelIoUringTcpBackend; +use mudu_kernel::server::server::IoUringTcpServerConfig; use mudu_utils::notifier::{Waiter, notify_wait}; use std::sync::Arc; @@ -48,7 +48,8 @@ impl IoUringBackend { worker_count, cfg.listen_ip.clone(), cfg.tcp_listen_port, - cfg.data_path.clone(), + cfg.db_path.clone(), + cfg.db_path.clone(), routing_mode, None, )? diff --git a/mudu_runtime/src/backend/server_ur/test_mpk.rs b/mudu_runtime/src/backend/server_ur/test_mpk.rs index 580e4b0..868ea34 100644 --- a/mudu_runtime/src/backend/server_ur/test_mpk.rs +++ b/mudu_runtime/src/backend/server_ur/test_mpk.rs @@ -9,16 +9,16 @@ use mudu_binding::procedure::procedure_invoke; use mudu_cli::client::client::SyncClient; use mudu_contract::procedure::procedure_param::ProcedureParam; use mudu_contract::tuple::tuple_datum::TupleDatum; -use mudu_kernel::server_ur::procedure_runtime::ProcInvokerPtr; -use mudu_kernel::server_ur::routing::RoutingMode as KernelRoutingMode; -use mudu_kernel::server_ur::server::{IoUringTcpBackend, IoUringTcpServerConfig}; +use mudu_kernel::server::async_func_runtime::AsyncFuncInvokerPtr; +use mudu_kernel::server::routing::RoutingMode as KernelRoutingMode; +use mudu_kernel::server::server::{IoUringTcpBackend, IoUringTcpServerConfig}; use mudu_utils::notifier::notify_wait; use std::env::temp_dir; use std::net::TcpListener; use std::path::{Path, PathBuf}; use std::process::Command; use std::thread; -use std::time::{Duration, Instant}; +use std::time::Duration; fn reserve_port() -> Option { match TcpListener::bind("127.0.0.1:0") { @@ -29,12 +29,12 @@ fn reserve_port() -> Option { } fn wait_until_server_ready(port: u16) { - let deadline = Instant::now() + Duration::from_secs(10); - while Instant::now() < deadline { + let deadline = mudu_sys::time::instant_now() + Duration::from_secs(10); + while mudu_sys::time::instant_now() < deadline { if std::net::TcpStream::connect(("127.0.0.1", port)).is_ok() { return; } - std::thread::sleep(Duration::from_millis(25)); + mudu_sys::task::sleep_blocking(Duration::from_millis(25)); } panic!("io_uring backend did not become ready on port {}", port); } @@ -105,18 +105,17 @@ fn ensure_kv_package_built() -> RS { } fn temp_dir_with_prefix(prefix: &str) -> PathBuf { - temp_dir().join(format!("{}_{}", prefix, uuid::Uuid::new_v4())) + temp_dir().join(format!("{}_{}", prefix, mudu_sys::random::uuid_v4())) } fn build_cfg(port: u16, mpk_path: &Path, data_path: &Path) -> MuduDBCfg { let mut cfg = MuduDBCfg::default(); cfg.mpk_path = mpk_path.to_string_lossy().into_owned(); - cfg.data_path = data_path.to_string_lossy().into_owned(); + cfg.db_path = data_path.to_string_lossy().into_owned(); cfg.listen_ip = "127.0.0.1".to_string(); cfg.server_mode = ServerMode::IOUring; cfg.tcp_listen_port = port; cfg.io_uring_worker_threads = 2; - cfg.enable_p2 = true; cfg.component_target = Some(ComponentTarget::P2); cfg.enable_async = true; cfg.routing_mode = RoutingMode::ConnectionId; @@ -139,7 +138,10 @@ fn install_kv_package(app_mgr: &MuduAppMgr, package_path: &Path) -> RS<()> { runtime.block_on(async { app_mgr.install(pkg_binary).await }) } -fn create_procedure_runtimes(app_mgr: &MuduAppMgr, cfg: &MuduDBCfg) -> RS> { +fn create_procedure_runtimes( + app_mgr: &MuduAppMgr, + cfg: &MuduDBCfg, +) -> RS> { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build() @@ -203,7 +205,8 @@ fn kv_mpk_can_be_used_by_iouring_backend() -> RS<()> { cfg.effective_worker_threads(), cfg.listen_ip.clone(), cfg.tcp_listen_port, - cfg.data_path.clone(), + cfg.db_path.clone(), + cfg.db_path.clone(), KernelRoutingMode::ConnectionId, None, )? diff --git a/mudu_runtime/src/backend/test_backend.rs b/mudu_runtime/src/backend/test_backend.rs index 0badb96..16bfa58 100644 --- a/mudu_runtime/src/backend/test_backend.rs +++ b/mudu_runtime/src/backend/test_backend.rs @@ -8,7 +8,10 @@ pub mod tests { use std::fs; fn test_db_path() -> String { - let tmp = temp_dir().join(format!("test_bakend_{}", uuid::Uuid::new_v4().to_string())); + let tmp = temp_dir().join(format!( + "test_bakend_{}", + mudu_sys::random::next_uuid_v4_string() + )); if !tmp.as_path().exists() { fs::create_dir_all(tmp.as_path()).unwrap(); } @@ -18,13 +21,12 @@ pub mod tests { fn _cfg() -> MuduDBCfg { let cfg = MuduDBCfg { mpk_path: wasm_mod_path(), - data_path: test_db_path(), + db_path: test_db_path(), listen_ip: "0.0.0.0".to_string(), http_listen_port: 8000, http_worker_threads: 1, pg_listen_port: 5432, component_target: None, - enable_p2: true, enable_async: false, ..Default::default() }; diff --git a/mudu_runtime/src/backend/web_serve.rs b/mudu_runtime/src/backend/web_serve.rs index 9455702..b0a35c4 100644 --- a/mudu_runtime/src/backend/web_serve.rs +++ b/mudu_runtime/src/backend/web_serve.rs @@ -17,15 +17,15 @@ pub async fn async_serve( stop: Waiter, opt_initialized_notifier: Option, ) -> RS<()> { - let runtime_target = cfg.runtime_target(); - let enable_async = cfg.enable_async && runtime_target.uses_component_model(); + let component_target = cfg.component_target(); + let enable_async = cfg.enable_async; let runtime_opt = RuntimeOpt { - target: runtime_target, + component_target, enable_async, }; let service = create_runtime_service( &cfg.mpk_path, - &cfg.data_path, + &cfg.db_path, opt_initialized_notifier, runtime_opt, ) @@ -34,9 +34,9 @@ pub async fn async_serve( error!( listen_ip = %cfg.listen_ip, http_listen_port = cfg.http_listen_port, - data_path = %cfg.data_path, + data_path = %cfg.db_path, mpk_path = %cfg.mpk_path, - runtime_target = ?runtime_target, + component_target = ?component_target, enable_async = enable_async, "initialize legacy runtime before starting management http service failed: {}", e @@ -48,7 +48,7 @@ pub async fn async_serve( listen_ip = %cfg.listen_ip, http_listen_port = cfg.http_listen_port, http_worker_threads = cfg.http_worker_threads, - runtime_target = ?runtime_target, + component_target = ?component_target, enable_async = enable_async, capabilities = ?HttpApiCapabilities::LEGACY, "legacy management http service listening" diff --git a/mudu_runtime/src/db_libsql_async/libsql_async_conn_inner.rs b/mudu_runtime/src/db_libsql_async/libsql_async_conn_inner.rs index 93c34c6..192c877 100644 --- a/mudu_runtime/src/db_libsql_async/libsql_async_conn_inner.rs +++ b/mudu_runtime/src/db_libsql_async/libsql_async_conn_inner.rs @@ -4,9 +4,9 @@ use crate::db_libsql_async::result_set::{LibSQLAsyncResultSet, ResultSetLease}; use async_trait::async_trait; use futures::TryFutureExt; use lazy_static::lazy_static; -use libsql::{Builder, Connection, Database, Statement, Transaction, params_from_iter}; +use libsql::{params_from_iter, Builder, Connection, Database, Statement, Transaction}; use mudu::common::result::RS; -use mudu::common::xid::{XID, new_xid}; +use mudu::common::xid::{new_xid, XID}; use mudu::error::ec::EC; use mudu::error::err::MError; use mudu::m_error; @@ -158,7 +158,12 @@ impl PreparedStmtImpl { .lock() .map_err(|_| m_error!(EC::MutexError, "lock prepared stmt error"))? .take() - .ok_or_else(|| m_error!(EC::ExistingSuchElement, "prepared statement is still in use")) + .ok_or_else(|| { + m_error!( + EC::ExistingSuchElement, + "prepared statement is still in use" + ) + }) } fn restore_prepared(&self, prepared: Prepared) -> RS<()> { @@ -392,11 +397,11 @@ impl ResultSetLease for PreparedSlotLease { #[cfg(test)] mod tests { - use libsql::{Builder, Value, params}; + use libsql::{params, Builder, Value}; use std::time::{SystemTime, UNIX_EPOCH}; fn temp_db_path(label: &str) -> String { - let nanos = SystemTime::now() + let nanos = mudu_sys::time::system_time_now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); diff --git a/mudu_runtime/src/db_postgres/pg_interactive_conn.rs b/mudu_runtime/src/db_postgres/pg_interactive_conn.rs index 2c65c02..9c898e4 100644 --- a/mudu_runtime/src/db_postgres/pg_interactive_conn.rs +++ b/mudu_runtime/src/db_postgres/pg_interactive_conn.rs @@ -40,7 +40,7 @@ impl DBConnSync for PGInteractive { fn begin_tx(&self) -> RS { let mut conn = self.db_conn.lock().unwrap(); let transaction = conn.0.transaction().unwrap(); - let xid = uuid::Uuid::new_v4().as_u128() as XID; + let xid = mudu_sys::random::uuid_v4().as_u128() as XID; let r = TxPg::new(transaction, xid); conn.1 = Some(r); Ok(xid) diff --git a/mudu_runtime/src/db_turso/turso_conn_inner.rs b/mudu_runtime/src/db_turso/turso_conn_inner.rs index 04d1fc2..5167d5d 100644 --- a/mudu_runtime/src/db_turso/turso_conn_inner.rs +++ b/mudu_runtime/src/db_turso/turso_conn_inner.rs @@ -161,7 +161,12 @@ impl PreparedStmtImpl { .lock() .map_err(|_| m_error!(EC::MutexError, "lock prepared stmt error"))? .take() - .ok_or_else(|| m_error!(EC::ExistingSuchElement, "prepared statement is still in use")) + .ok_or_else(|| { + m_error!( + EC::ExistingSuchElement, + "prepared statement is still in use" + ) + }) } fn restore_prepared(&self, prepared: Prepared) -> RS<()> { diff --git a/mudu_runtime/src/interface/kernel.rs b/mudu_runtime/src/interface/kernel.rs index 12f5613..65afe38 100644 --- a/mudu_runtime/src/interface/kernel.rs +++ b/mudu_runtime/src/interface/kernel.rs @@ -1,3 +1,4 @@ +use crate::async_utils::blocking::run_async; use mudu::common::id::OID; use mudu::common::result::RS; use mudu::error::ec::EC; @@ -6,7 +7,7 @@ use mudu_binding::codec::handle_sys_session; use mudu_contract::database::result_batch::ResultBatch; use mudu_contract::database::sql::Context; use mudu_contract::tuple::tuple_field_desc::TupleFieldDesc; -use mudu_kernel::server_ur::worker_local::WorkerLocalRef; +use mudu_kernel::server::worker_local::WorkerLocalRef; /// Execute a SQL query with parameters pub fn query_internal(query_in: &[u8]) -> Vec { @@ -47,7 +48,8 @@ fn _command_internal(command_in: &[u8]) -> RS { } fn _batch_internal(batch_in: &[u8]) -> RS { - let (oid, stmt, param) = mudu_binding::system::command_invoke::deserialize_command_param(batch_in)?; + let (oid, stmt, param) = + mudu_binding::system::command_invoke::deserialize_command_param(batch_in)?; let context = get_context(oid)?; context.batch(stmt.as_ref(), param.as_ref()) } @@ -92,7 +94,8 @@ async fn _async_command_internal(command_in: Vec) -> RS { } async fn _async_batch_internal(batch_in: Vec) -> RS { - let (oid, stmt, param) = mudu_binding::system::command_invoke::deserialize_command_param(&batch_in)?; + let (oid, stmt, param) = + mudu_binding::system::command_invoke::deserialize_command_param(&batch_in)?; let context = get_context(oid)?; context.batch_async(stmt, param).await } @@ -113,8 +116,9 @@ pub fn open_internal_with_worker_local( worker_local: Option<&WorkerLocalRef>, ) -> RS> { let open_argv = handle_sys_session::deserialize_open_param(open_in)?; - let worker_local = require_worker_local(worker_local)?; - let opened = worker_local.open_argv(open_argv.worker_oid())?; + let worker_local = require_worker_local(worker_local)?.clone(); + let worker_oid = open_argv.worker_oid(); + let opened = run_async(async move { worker_local.open_argv_async(worker_oid).await })??; Ok(handle_sys_session::serialize_open_result(opened)) } @@ -123,8 +127,8 @@ pub fn close_internal_with_worker_local( worker_local: Option<&WorkerLocalRef>, ) -> RS> { let session_id: OID = handle_sys_session::deserialize_close_param(close_in)?; - let worker_local = require_worker_local(worker_local)?; - worker_local.close(session_id)?; + let worker_local = require_worker_local(worker_local)?.clone(); + run_async(async move { worker_local.close_async(session_id).await })??; Ok(handle_sys_session::serialize_close_result()) } @@ -137,12 +141,10 @@ pub fn get_internal_with_worker_local( get_in: &[u8], worker_local: Option<&WorkerLocalRef>, ) -> RS> { - let result = - handle_sys_session::deserialize_session_get_param(get_in).and_then(|(session_id, key)| { - let worker_local = require_worker_local(worker_local)?; - worker_local.get(session_id, &key) - }); - Ok(handle_sys_session::serialize_get_result(result?.as_deref())) + let (session_id, key) = handle_sys_session::deserialize_session_get_param(get_in)?; + let worker_local = require_worker_local(worker_local)?.clone(); + let result = run_async(async move { worker_local.get_async(session_id, &key).await })??; + Ok(handle_sys_session::serialize_get_result(result.as_deref())) } pub fn put_internal(put_in: &[u8]) -> Vec { @@ -154,16 +156,27 @@ pub fn put_internal_with_worker_local( put_in: &[u8], worker_local: Option<&WorkerLocalRef>, ) -> RS> { - let result = handle_sys_session::deserialize_session_put_param(put_in).and_then( - |(session_id, key, value)| { - let worker_local = require_worker_local(worker_local)?; - worker_local.put(session_id, key, value) - }, - ); - result?; + let (session_id, key, value) = handle_sys_session::deserialize_session_put_param(put_in)?; + let worker_local = require_worker_local(worker_local)?.clone(); + run_async(async move { worker_local.put_async(session_id, key, value).await })??; Ok(handle_sys_session::serialize_put_result()) } +pub fn delete_internal(delete_in: &[u8]) -> Vec { + delete_internal_with_worker_local(delete_in, None) + .unwrap_or_else(|e| panic!("worker-local delete is not available: {}", e)) +} + +pub fn delete_internal_with_worker_local( + delete_in: &[u8], + worker_local: Option<&WorkerLocalRef>, +) -> RS> { + let (session_id, key) = handle_sys_session::deserialize_session_delete_param(delete_in)?; + let worker_local = require_worker_local(worker_local)?.clone(); + run_async(async move { worker_local.delete_async(session_id, &key).await })??; + Ok(handle_sys_session::serialize_delete_result()) +} + pub fn range_internal(range_in: &[u8]) -> Vec { range_internal_with_worker_local(range_in, None) .unwrap_or_else(|e| panic!("worker-local range is not available: {}", e)) @@ -173,17 +186,15 @@ pub fn range_internal_with_worker_local( range_in: &[u8], worker_local: Option<&WorkerLocalRef>, ) -> RS> { - let result = handle_sys_session::deserialize_session_range_param(range_in).and_then( - |(session_id, start, end)| { - let worker_local = require_worker_local(worker_local)?; - Ok(worker_local - .range(session_id, &start, &end)? - .into_iter() - .map(|item| (item.key, item.value)) - .collect::>()) - }, - ); - Ok(handle_sys_session::serialize_range_result(&result?)) + let (session_id, start, end) = handle_sys_session::deserialize_session_range_param(range_in)?; + let worker_local = require_worker_local(worker_local)?.clone(); + let result = + run_async(async move { worker_local.range_async(session_id, &start, &end).await })??; + let result = result + .into_iter() + .map(|item| (item.key, item.value)) + .collect::>(); + Ok(handle_sys_session::serialize_range_result(&result)) } pub async fn async_get_internal(get_in: Vec) -> Vec { @@ -194,24 +205,53 @@ pub async fn async_get_internal_with_worker_local( get_in: Vec, worker_local: Option<&WorkerLocalRef>, ) -> Vec { - get_internal_with_worker_local(&get_in, worker_local) - .unwrap_or_else(|e| panic!("worker-local get is not available: {}", e)) + let result = + handle_sys_session::deserialize_session_get_param(&get_in).and_then(|(session_id, key)| { + let worker_local = require_worker_local(worker_local)?; + Ok((worker_local.clone(), session_id, key)) + }); + match result { + Ok((worker_local, session_id, key)) => handle_sys_session::serialize_get_result( + worker_local + .get_async(session_id, &key) + .await + .unwrap_or_else(|e| panic!("worker-local get is not available: {}", e)) + .as_deref(), + ), + Err(e) => panic!("worker-local get is not available: {}", e), + } } pub async fn async_open_internal_with_worker_local( open_in: Vec, worker_local: Option<&WorkerLocalRef>, ) -> Vec { - open_internal_with_worker_local(&open_in, worker_local) + let open_argv = handle_sys_session::deserialize_open_param(&open_in) + .unwrap_or_else(|e| panic!("worker-local open is not available: {}", e)); + let worker_local = require_worker_local(worker_local) .unwrap_or_else(|e| panic!("worker-local open is not available: {}", e)) + .clone(); + let opened = worker_local + .open_argv_async(open_argv.worker_oid()) + .await + .unwrap_or_else(|e| panic!("worker-local open is not available: {}", e)); + handle_sys_session::serialize_open_result(opened) } pub async fn async_close_internal_with_worker_local( close_in: Vec, worker_local: Option<&WorkerLocalRef>, ) -> Vec { - close_internal_with_worker_local(&close_in, worker_local) + let session_id: OID = handle_sys_session::deserialize_close_param(&close_in) + .unwrap_or_else(|e| panic!("worker-local close is not available: {}", e)); + let worker_local = require_worker_local(worker_local) .unwrap_or_else(|e| panic!("worker-local close is not available: {}", e)) + .clone(); + worker_local + .close_async(session_id) + .await + .unwrap_or_else(|e| panic!("worker-local close is not available: {}", e)); + handle_sys_session::serialize_close_result() } pub async fn async_put_internal(put_in: Vec) -> Vec { @@ -222,8 +262,36 @@ pub async fn async_put_internal_with_worker_local( put_in: Vec, worker_local: Option<&WorkerLocalRef>, ) -> Vec { - put_internal_with_worker_local(&put_in, worker_local) + let (session_id, key, value) = handle_sys_session::deserialize_session_put_param(&put_in) + .unwrap_or_else(|e| panic!("worker-local put is not available: {}", e)); + let worker_local = require_worker_local(worker_local) .unwrap_or_else(|e| panic!("worker-local put is not available: {}", e)) + .clone(); + worker_local + .put_async(session_id, key, value) + .await + .unwrap_or_else(|e| panic!("worker-local put is not available: {}", e)); + handle_sys_session::serialize_put_result() +} + +pub async fn async_delete_internal(delete_in: Vec) -> Vec { + delete_internal(&delete_in) +} + +pub async fn async_delete_internal_with_worker_local( + delete_in: Vec, + worker_local: Option<&WorkerLocalRef>, +) -> Vec { + let (session_id, key) = handle_sys_session::deserialize_session_delete_param(&delete_in) + .unwrap_or_else(|e| panic!("worker-local delete is not available: {}", e)); + let worker_local = require_worker_local(worker_local) + .unwrap_or_else(|e| panic!("worker-local delete is not available: {}", e)) + .clone(); + worker_local + .delete_async(session_id, &key) + .await + .unwrap_or_else(|e| panic!("worker-local delete is not available: {}", e)); + handle_sys_session::serialize_delete_result() } pub async fn async_range_internal(range_in: Vec) -> Vec { @@ -234,8 +302,19 @@ pub async fn async_range_internal_with_worker_local( range_in: Vec, worker_local: Option<&WorkerLocalRef>, ) -> Vec { - range_internal_with_worker_local(&range_in, worker_local) + let (session_id, start, end) = handle_sys_session::deserialize_session_range_param(&range_in) + .unwrap_or_else(|e| panic!("worker-local range is not available: {}", e)); + let worker_local = require_worker_local(worker_local) .unwrap_or_else(|e| panic!("worker-local range is not available: {}", e)) + .clone(); + let rows = worker_local + .range_async(session_id, &start, &end) + .await + .unwrap_or_else(|e| panic!("worker-local range is not available: {}", e)) + .into_iter() + .map(|item| (item.key, item.value)) + .collect::>(); + handle_sys_session::serialize_range_result(&rows) } fn require_worker_local(worker_local: Option<&WorkerLocalRef>) -> RS<&WorkerLocalRef> { @@ -269,5 +348,24 @@ mod tests { err.to_string() .contains("worker local interface is not configured") ); + + let delete = handle_sys_session::serialize_session_delete_param(1, b"alpha"); + let err = delete_internal_with_worker_local(&delete, None).unwrap_err(); + assert!( + err.to_string() + .contains("worker local interface is not configured") + ); + } + + #[test] + fn delete_session_codec_round_trips() { + let encoded = handle_sys_session::serialize_session_delete_param(9, b"alpha"); + let decoded = handle_sys_session::deserialize_session_delete_param(&encoded).unwrap(); + assert_eq!(decoded.0, 9); + assert_eq!(decoded.1, b"alpha".to_vec()); + handle_sys_session::deserialize_delete_result( + &handle_sys_session::serialize_delete_result(), + ) + .unwrap(); } } diff --git a/mudu_runtime/src/procedure/mod.rs b/mudu_runtime/src/procedure/mod.rs index d639a53..b3c780a 100644 --- a/mudu_runtime/src/procedure/mod.rs +++ b/mudu_runtime/src/procedure/mod.rs @@ -1,2 +1 @@ pub mod procedure; -pub mod wasi_context; diff --git a/mudu_runtime/src/procedure/wasi_context.rs b/mudu_runtime/src/procedure/wasi_context.rs deleted file mode 100644 index da0756b..0000000 --- a/mudu_runtime/src/procedure/wasi_context.rs +++ /dev/null @@ -1,78 +0,0 @@ -use mudu_kernel::server_ur::worker_local::WorkerLocalRef; -use std::sync::atomic::AtomicU32; -use wasmtime_wasi::WasiCtxBuilder; -use wasmtime_wasi::p1::WasiP1Ctx; - -pub struct ContextData { - auto_increase: AtomicU32, - host_mem: scc::HashMap>, -} - -pub struct WasiContext { - data: ContextData, - worker_local: Option, - // .. other custom state here .. - wasi: WasiP1Ctx, -} - -pub fn build_wasi_p1_context(worker_local: Option) -> WasiContext { - let wasi = WasiCtxBuilder::new() - .inherit_stdio() - .inherit_args() - .build_p1(); - let context = WasiContext::new(wasi, worker_local); - context -} - -impl ContextData { - pub fn new() -> Self { - Self { - auto_increase: Default::default(), - host_mem: Default::default(), - } - } - - pub fn add_memory(&self, vec: Vec) -> u32 { - let mut vec = vec; - loop { - let n = self - .auto_increase - .fetch_add(1, std::sync::atomic::Ordering::SeqCst); - let result = self.host_mem.insert_sync(n, vec); - match result { - Ok(_) => return n, - Err((_n, v)) => vec = v, - } - } - } - - pub fn get_memory(&self, id: u32) -> Option> { - self.host_mem.remove_sync(&id).map(|(_k, v)| v) - } -} - -impl WasiContext { - pub fn new(wasi: WasiP1Ctx, worker_local: Option) -> Self { - WasiContext { - data: ContextData::new(), - worker_local, - wasi, - } - } - - pub fn context_ptr(&self) -> *const ContextData { - &self.data - } - - pub fn context_ref(&self) -> &ContextData { - &self.data - } - - pub fn wasi_mut(&mut self) -> &mut WasiP1Ctx { - &mut self.wasi - } - - pub fn worker_local(&self) -> Option<&WorkerLocalRef> { - self.worker_local.as_ref() - } -} diff --git a/mudu_runtime/src/service/app_inst.rs b/mudu_runtime/src/service/app_inst.rs index 15d8f73..075f361 100644 --- a/mudu_runtime/src/service/app_inst.rs +++ b/mudu_runtime/src/service/app_inst.rs @@ -5,7 +5,7 @@ use mudu_contract::database::sql::DBConn; use mudu_contract::procedure::proc_desc::ProcDesc; use mudu_contract::procedure::procedure_param::ProcedureParam; use mudu_contract::procedure::procedure_result::ProcedureResult; -use mudu_kernel::server_ur::worker_local::WorkerLocalRef; +use mudu_kernel::server::worker_local::WorkerLocalRef; use mudu_utils::task_id::TaskID; use std::sync::Arc; diff --git a/mudu_runtime/src/service/app_inst_impl.rs b/mudu_runtime/src/service/app_inst_impl.rs index cb5be60..1d6315e 100644 --- a/mudu_runtime/src/service/app_inst_impl.rs +++ b/mudu_runtime/src/service/app_inst_impl.rs @@ -5,8 +5,7 @@ use crate::service::app_inst::AppInst; use crate::service::mudu_package::MuduPackage; use crate::service::package_module::PackageModule; use crate::service::procedure_invoke_component::ProcedureInvokeComponent; -use crate::service::procedure_invoke_p1::ProcedureInvoke1; -use crate::service::runtime_opt::RuntimeTarget; +use crate::service::runtime_opt::ComponentTarget; use async_trait::async_trait; use mudu::common::app_info::AppInfo; use mudu::common::result::RS; @@ -17,7 +16,7 @@ use mudu_contract::database::sql::{Context, DBConn}; use mudu_contract::procedure::proc_desc::ProcDesc; use mudu_contract::procedure::procedure_param::ProcedureParam; use mudu_contract::procedure::procedure_result::ProcedureResult; -use mudu_kernel::server_ur::worker_local::WorkerLocalRef; +use mudu_kernel::server::worker_local::WorkerLocalRef; use mudu_utils::task_id::{TaskID, new_task_id}; use scc::HashMap; use std::fs::File; @@ -36,7 +35,7 @@ struct AppInstImplInner { schema_mgr: SchemaMgr, modules: HashMap, _conn: HashMap, - runtime_target: RuntimeTarget, + component_target: ComponentTarget, } impl AppInstImpl { @@ -44,7 +43,7 @@ impl AppInstImpl { db_path: &String, package: &MuduPackage, vec_modules: Vec<(String, PackageModule)>, - runtime_target: RuntimeTarget, + component_target: ComponentTarget, enable_async: bool, ) -> RS { Ok(Self { @@ -53,7 +52,7 @@ impl AppInstImpl { db_path, package, vec_modules, - runtime_target, + component_target, enable_async, ) .await?, @@ -91,7 +90,7 @@ impl AppInstImplInner { db_path: &String, package: &MuduPackage, vec_modules: Vec<(String, PackageModule)>, - runtime_target: RuntimeTarget, + component_target: ComponentTarget, enable_async: bool, ) -> RS { let modules = HashMap::new(); @@ -112,7 +111,7 @@ impl AppInstImplInner { schema_mgr, modules, _conn: Default::default(), - runtime_target, + component_target, }) } @@ -146,18 +145,13 @@ impl AppInstImplInner { let (procedure, param, new_tx) = self.pre_invoke(task_id, mod_name, proc_name, param).await?; let xid = param.session_id(); - let result = match self.runtime_target { - RuntimeTarget::P1 => { - ProcedureInvoke1::call(&procedure, Default::default(), param, worker_local) - } - RuntimeTarget::Component(component_target) => ProcedureInvokeComponent::call( - &procedure, - component_target, - Default::default(), - param, - worker_local, - ), - }; + let result = ProcedureInvokeComponent::call( + &procedure, + self.component_target, + Default::default(), + param, + worker_local, + ); if new_tx { if result.is_ok() { Context::commit(xid)?; @@ -185,24 +179,14 @@ impl AppInstImplInner { let (procedure, param, new_tx) = self.pre_invoke(task_id, mod_name, proc_name, param).await?; let xid = param.session_id(); - let result = match self.runtime_target { - RuntimeTarget::P1 => { - return Err(m_error!( - EC::DBInternalError, - "async invocation is only supported for component targets" - )); - } - RuntimeTarget::Component(component_target) => { - ProcedureInvokeComponent::call_async( - &procedure, - component_target, - Default::default(), - param, - worker_local, - ) - .await - } - }; + let result = ProcedureInvokeComponent::call_async( + &procedure, + self.component_target, + Default::default(), + param, + worker_local, + ) + .await; if new_tx { if result.is_ok() { Context::commit_async(xid).await?; @@ -294,7 +278,9 @@ async fn initdb( enable_async: bool, ) -> RS<()> { let init_db_lock = PathBuf::from(&db_path).join(format!("{}.lock", app_name)); - if init_db_lock.exists() && is_schema_initialized(db_path, app_name, schema_mgr, enable_async).await? { + if init_db_lock.exists() + && is_schema_initialized(db_path, app_name, schema_mgr, enable_async).await? + { return Ok(()); } let conn = new_conn(db_path, app_name, enable_async).await?; diff --git a/mudu_runtime/src/service/kernel_function_p1.rs b/mudu_runtime/src/service/kernel_function_p1.rs deleted file mode 100644 index 8ddf622..0000000 --- a/mudu_runtime/src/service/kernel_function_p1.rs +++ /dev/null @@ -1,392 +0,0 @@ -use crate::interface::kernel; -use crate::procedure::wasi_context::{ContextData, WasiContext}; -use mudu::common::endian::write_u32; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::error::err::MError; -use mudu::m_error; -use mudu_contract::database::err_no; -use std::cmp::min; -use wasmtime::{Caller, Extern, Memory}; - -// ============================================================================= -// Public Kernel Interface Functions -// ============================================================================= -/// Execute a SQL query from WebAssembly guest -pub fn kernel_query( - caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - out_buf_ptr: u32, - out_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32, -) -> i32 { - handle_guest_invoke_host::<_>( - caller, - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_mem_ptr, - out_mem_len, - |input| Ok(kernel::query_internal(input)), - ) -} - -/// Fetch next row from a query result cursor -pub fn kernel_fetch_p1( - caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - output_buf_ptr: u32, - output_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32, -) -> i32 { - handle_guest_invoke_host::<_>( - caller, - param_buf_ptr, - param_buf_len, - output_buf_ptr, - output_buf_len, - out_mem_ptr, - out_mem_len, - |input| Ok(kernel::fetch_internal(input)), - ) -} - -/// Execute a SQL command (INSERT, UPDATE, DELETE) from WebAssembly guest -pub fn kernel_command_p1( - caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - output_buf_ptr: u32, - output_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32, -) -> i32 { - handle_guest_invoke_host::<_>( - caller, - param_buf_ptr, - param_buf_len, - output_buf_ptr, - output_buf_len, - out_mem_ptr, - out_mem_len, - |input| Ok(kernel::command_internal(input)), - ) -} - -pub fn kernel_batch_p1( - caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - output_buf_ptr: u32, - output_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32, -) -> i32 { - handle_guest_invoke_host::<_>( - caller, - param_buf_ptr, - param_buf_len, - output_buf_ptr, - output_buf_len, - out_mem_ptr, - out_mem_len, - |input| Ok(kernel::batch_internal(input)), - ) -} - -pub fn kernel_open_p1( - caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - output_buf_ptr: u32, - output_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32, -) -> i32 { - let worker_local = caller.data().worker_local().cloned(); - handle_guest_invoke_host::<_>( - caller, - param_buf_ptr, - param_buf_len, - output_buf_ptr, - output_buf_len, - out_mem_ptr, - out_mem_len, - move |input| kernel::open_internal_with_worker_local(input, worker_local.as_ref()), - ) -} - -pub fn kernel_close_p1( - caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - output_buf_ptr: u32, - output_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32, -) -> i32 { - let worker_local = caller.data().worker_local().cloned(); - handle_guest_invoke_host::<_>( - caller, - param_buf_ptr, - param_buf_len, - output_buf_ptr, - output_buf_len, - out_mem_ptr, - out_mem_len, - move |input| kernel::close_internal_with_worker_local(input, worker_local.as_ref()), - ) -} - -/// Read a value from the kernel pull-push interface. -pub fn kernel_get_p1( - caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - output_buf_ptr: u32, - output_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32, -) -> i32 { - let worker_local = caller.data().worker_local().cloned(); - handle_guest_invoke_host::<_>( - caller, - param_buf_ptr, - param_buf_len, - output_buf_ptr, - output_buf_len, - out_mem_ptr, - out_mem_len, - move |input| kernel::get_internal_with_worker_local(input, worker_local.as_ref()), - ) -} - -/// Write a pull-push pair into the kernel pull-push interface. -pub fn kernel_put_p1( - caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - output_buf_ptr: u32, - output_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32, -) -> i32 { - let worker_local = caller.data().worker_local().cloned(); - handle_guest_invoke_host::<_>( - caller, - param_buf_ptr, - param_buf_len, - output_buf_ptr, - output_buf_len, - out_mem_ptr, - out_mem_len, - move |input| kernel::put_internal_with_worker_local(input, worker_local.as_ref()), - ) -} - -/// Scan a half-open key range from the kernel pull-push interface. -pub fn kernel_range_p1( - caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - output_buf_ptr: u32, - output_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32, -) -> i32 { - let worker_local = caller.data().worker_local().cloned(); - handle_guest_invoke_host::<_>( - caller, - param_buf_ptr, - param_buf_len, - output_buf_ptr, - output_buf_len, - out_mem_ptr, - out_mem_len, - move |input| kernel::range_internal_with_worker_local(input, worker_local.as_ref()), - ) -} - -/// Retrieve memory chunk by ID from the context -pub fn kernel_get_memory_p1( - caller: Caller<'_, WasiContext>, - mem_id: u32, - output_buf_ptr: u32, - output_buf_len: u32, -) -> i32 { - let opt_mem = { caller.data().context_ref().get_memory(mem_id) }; - match opt_mem { - Some(mem) => { - let size = min(mem.len(), output_buf_len as usize); - let mut caller = caller; - let memory = get_memory(&mut caller).unwrap(); - let data = memory.data_mut(&mut caller); - let _output_buf_ptr = output_buf_ptr as usize; - let _output_buf_len = output_buf_len as usize; - check_bounds(_output_buf_ptr, _output_buf_len, data.len()).unwrap(); - data[_output_buf_ptr.._output_buf_ptr + size].copy_from_slice(&mem[..size]); - size as i32 - } - None => -1, - } -} - -// ============================================================================= -// Kernel Function -// ============================================================================= - -fn serialize_and_write_output( - result: Vec, - context: *const ContextData, - caller: &mut Caller<'_, WasiContext>, - memory: &Memory, - output_buf_ptr: u32, - output_buf_len: u32, - out_len_ptr: u32, - mem_id_ptr: u32, -) -> RS { - let mem_buf = memory.data_mut(caller); - let _out_buf_ptr = output_buf_ptr as usize; - let _out_buf_len = output_buf_len as usize; - check_bounds(_out_buf_ptr, _out_buf_len, mem_buf.len())?; - let _out_len_ptr = out_len_ptr as usize; - let total_size = result.len() as u32; - let _mem_id_ptr = mem_id_ptr as usize; - - // write the expected output buffer size - check_bounds(_out_len_ptr, size_of::(), mem_buf.len())?; - write_u32( - &mut mem_buf[_out_len_ptr.._out_len_ptr + size_of::()], - total_size, - ); - let out_param = &mut mem_buf[_out_buf_ptr.._out_buf_ptr + _out_buf_len]; - if result.len() > out_param.len() { - handle_insufficient_buffer(result, context, mem_buf, mem_id_ptr) - } else { - out_param.copy_from_slice(&result); - Ok(err_no::EN_OK) - } -} - -/// Handle case when output buffer is too small -fn handle_insufficient_buffer( - result: Vec, - context_ptr: *const ContextData, - memory_data: &mut [u8], - mem_id_ptr: u32, -) -> RS { - let context_ref = unsafe { &*context_ptr }; - let memory_id = context_ref.add_memory(result); - write_memory_id(memory_data, mem_id_ptr, memory_id)?; - Ok(err_no::EN_INSUFFICIENT_BUFFER_LENGTH_FOR_OUTPUT) -} - -fn write_memory_id(memory_data: &mut [u8], mem_id_ptr: u32, memory_id: u32) -> RS<()> { - let ptr = mem_id_ptr as usize; - check_bounds(ptr, size_of::(), memory_data.len())?; - write_u32(&mut memory_data[ptr..ptr + size_of::()], memory_id); - Ok(()) -} -// ============================================================================= -// Core Host-Guest Communication -// ============================================================================= -/// Process a single host invocation with error handling -fn handle_wasm_guest_invoke_host_gut RS> + 'static>( - caller: &mut Caller<'_, WasiContext>, - context: *const ContextData, - memory: &Memory, - param_buf_ptr: u32, - param_buf_len: u32, - output_buf_ptr: u32, - output_buf_len: u32, - out_len_ptr: u32, - mem_id_ptr: u32, - f: F, -) -> (i32, Option) { - let buf = memory.data(&caller); - let _buf_ptr = param_buf_ptr as usize; - let _buf_len = param_buf_len as usize; - let r = check_bounds(_buf_ptr, _buf_len, buf.len()); - match r { - Err(e) => { - return (err_no::EN_DECODE_PARAM, Some(e)); - } - _ => {} - } - let in_param = &buf[_buf_ptr.._buf_ptr + _buf_len]; - - // Execute the handler function - let result = match f(in_param) { - Ok(result) => result, - Err(e) => return (err_no::EN_INVOKE, Some(e)), - }; - - // Serialize and write output - match serialize_and_write_output( - result, - context, - caller, - &memory, - output_buf_ptr, - output_buf_len, - out_len_ptr, - mem_id_ptr, - ) { - Ok(output) => (output, None), - Err(e) => (err_no::EN_ENCODE_RESULT, Some(e)), - } -} - -/// Generic handler for WebAssembly guest to host invocations -fn handle_guest_invoke_host RS> + 'static>( - caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - output_buf_ptr: u32, - output_buf_len: u32, - out_len_ptr: u32, - mem_id_ptr: u32, - function: F, -) -> i32 { - let context = caller.data().context_ptr(); - let mut caller = caller; - - let memory = match get_memory(&mut caller) { - Ok(mem) => mem, - Err(_) => return err_no::EN_NO_OUTPUT_MEMORY, - }; - let (result_code, _) = handle_wasm_guest_invoke_host_gut( - &mut caller, - context, - &memory, - param_buf_ptr, - param_buf_len, - output_buf_ptr, - output_buf_len, - out_len_ptr, - mem_id_ptr, - function, - ); - result_code -} - -/// Validate memory access bounds -fn check_bounds(ptr: usize, len: usize, memory_size: usize) -> RS<()> { - if ptr + len > memory_size { - Err(m_error!(EC::WASMMemoryAccessError, "memory bound error")) - } else { - Ok(()) - } -} - -fn get_memory(caller: &mut Caller<'_, WasiContext>) -> RS { - match caller.get_export("memory") { - Some(Extern::Memory(mem)) => Ok(mem), - _ => Err(m_error!(EC::MuduError, "get memory export error")), - } -} diff --git a/mudu_runtime/src/service/kernel_function_p2.rs b/mudu_runtime/src/service/kernel_function_p2.rs index e18226a..f6ac857 100644 --- a/mudu_runtime/src/service/kernel_function_p2.rs +++ b/mudu_runtime/src/service/kernel_function_p2.rs @@ -1,5 +1,5 @@ use crate::interface::kernel; -use mudu_kernel::server_ur::worker_local::WorkerLocalRef; +use mudu_kernel::server::worker_local::WorkerLocalRef; pub fn host_query(query_in: Vec) -> Vec { kernel::query_internal(&query_in) @@ -37,6 +37,11 @@ pub fn host_put(put_in: Vec, worker_local: Option<&WorkerLocalRef>) -> Vec, worker_local: Option<&WorkerLocalRef>) -> Vec { + kernel::delete_internal_with_worker_local(&delete_in, worker_local) + .unwrap_or_else(|e| panic!("worker-local delete is not available: {}", e)) +} + pub fn host_range(range_in: Vec, worker_local: Option<&WorkerLocalRef>) -> Vec { kernel::range_internal_with_worker_local(&range_in, worker_local) .unwrap_or_else(|e| panic!("worker-local range is not available: {}", e)) diff --git a/mudu_runtime/src/service/kernel_function_p2_async.rs b/mudu_runtime/src/service/kernel_function_p2_async.rs index a27194c..885a1b2 100644 --- a/mudu_runtime/src/service/kernel_function_p2_async.rs +++ b/mudu_runtime/src/service/kernel_function_p2_async.rs @@ -1,5 +1,5 @@ use crate::interface::kernel; -use mudu_kernel::server_ur::worker_local::WorkerLocalRef; +use mudu_kernel::server::worker_local::WorkerLocalRef; pub async fn async_host_query(query_in: Vec) -> Vec { kernel::async_query_internal(query_in).await @@ -33,6 +33,13 @@ pub async fn async_host_put(put_in: Vec, worker_local: Option<&WorkerLocalRe kernel::async_put_internal_with_worker_local(put_in, worker_local).await } +pub async fn async_host_delete( + delete_in: Vec, + worker_local: Option<&WorkerLocalRef>, +) -> Vec { + kernel::async_delete_internal_with_worker_local(delete_in, worker_local).await +} + pub async fn async_host_range(range_in: Vec, worker_local: Option<&WorkerLocalRef>) -> Vec { kernel::async_range_internal_with_worker_local(range_in, worker_local).await } diff --git a/mudu_runtime/src/service/mod.rs b/mudu_runtime/src/service/mod.rs index 3e567ba..0299009 100644 --- a/mudu_runtime/src/service/mod.rs +++ b/mudu_runtime/src/service/mod.rs @@ -2,10 +2,8 @@ pub mod app_cfg; pub mod app_inst; pub mod app_inst_impl; mod file_name; -mod kernel_function_p1; pub(crate) mod mudu_package; pub mod package_module; -mod procedure_invoke_p1; pub mod runtime; pub mod runtime_impl; mod runtime_simple; @@ -17,7 +15,6 @@ mod service_impl; mod service_trait; mod test_runtime_simple; pub mod wt_instance_pre; -pub mod wt_runtime_p1; mod wt_runtime; diff --git a/mudu_runtime/src/service/mudu_package.rs b/mudu_runtime/src/service/mudu_package.rs index 07406f5..e9f98e3 100644 --- a/mudu_runtime/src/service/mudu_package.rs +++ b/mudu_runtime/src/service/mudu_package.rs @@ -153,11 +153,11 @@ mod tests { use std::env::temp_dir; use std::fs; use std::io::Write; - use uuid::Uuid; #[test] fn test_app_package() { - let package_file = temp_dir().join(format!("app_json_desc_{}.mpk", Uuid::new_v4())); + let package_file = + temp_dir().join(format!("app_json_desc_{}.mpk", mudu_sys::random::uuid_v4())); let file = fs::File::create(&package_file).unwrap(); let mut zip = zip::ZipWriter::new(file); let options = zip::write::SimpleFileOptions::default(); @@ -189,7 +189,10 @@ mod tests { #[test] fn test_single_module_package_aligns_desc_module_name() { - let package_file = temp_dir().join(format!("app_json_align_{}.mpk", Uuid::new_v4())); + let package_file = temp_dir().join(format!( + "app_json_align_{}.mpk", + mudu_sys::random::uuid_v4() + )); let file = fs::File::create(&package_file).unwrap(); let mut zip = zip::ZipWriter::new(file); let options = zip::write::SimpleFileOptions::default(); diff --git a/mudu_runtime/src/service/procedure_invoke_component.rs b/mudu_runtime/src/service/procedure_invoke_component.rs index b648194..20b2adf 100644 --- a/mudu_runtime/src/service/procedure_invoke_component.rs +++ b/mudu_runtime/src/service/procedure_invoke_component.rs @@ -1,6 +1,6 @@ use crate::procedure::procedure::Procedure; use crate::service::runtime_opt::ComponentTarget; -use crate::service::wasi_context_component::{WasiContextComponent, build_wasi_component_context}; +use crate::service::wasi_context_component::{build_wasi_component_context, WasiContextComponent}; use mudu::common::result::RS; use mudu::error::ec::EC; use mudu::m_error; @@ -8,11 +8,10 @@ use mudu::utils::case_convert::to_kebab_case; use mudu_binding::procedure::procedure_invoke; use mudu_contract::procedure::procedure_param::ProcedureParam; use mudu_contract::procedure::procedure_result::ProcedureResult; -use mudu_kernel::server_ur::worker_local::WorkerLocalRef; +use mudu_kernel::server::worker_local::WorkerLocalRef; use std::sync::Mutex; -use std::thread; -use wasmtime::Store; use wasmtime::component::{InstancePre, TypedFunc}; +use wasmtime::Store; pub struct ProcedureInvokeComponent { inner: Mutex, @@ -83,10 +82,10 @@ impl ProcedureInvokeComponent { let inner: ProcedureInvokeInner = inner .into_inner() .map_err(|e| m_error!(EC::MuduError, "mutex into inner error", e))?; - let thread = thread::spawn(move || { + let thread = mudu_sys::task::spawn_thread(move || { let ret = inner.invoke(param); ret - }); + })?; let result = thread .join() .map_err(|_e| m_error!(EC::MuduError, "invoke thread join error"))?; diff --git a/mudu_runtime/src/service/procedure_invoke_p1.rs b/mudu_runtime/src/service/procedure_invoke_p1.rs deleted file mode 100644 index 45f6545..0000000 --- a/mudu_runtime/src/service/procedure_invoke_p1.rs +++ /dev/null @@ -1,300 +0,0 @@ -use crate::procedure::procedure::Procedure; -use crate::procedure::wasi_context::{WasiContext, build_wasi_p1_context}; -use anyhow::Context; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use mudu_binding::procedure::procedure_invoke; -use mudu_contract::procedure::procedure_param::ProcedureParam; -use mudu_contract::procedure::procedure_result::ProcedureResult; -use mudu_kernel::server_ur::worker_local::WorkerLocalRef; -use std::sync::Mutex; -use std::thread; -use wasmtime::{InstancePre, Memory, Store, TypedFunc}; - -pub struct ProcedureInvoke1 { - inner: Mutex, -} - -impl ProcedureInvoke1 { - pub fn call( - procedure: &Procedure, - proc_opt: ProcOpt, - param: ProcedureParam, - worker_local: Option, - ) -> RS { - let name = format!( - "{}{}", - mudu_contract::procedure::proc::MUDU_PROC_PREFIX, - procedure.proc_name() - ); - let context = build_wasi_p1_context(worker_local); - let this: Self = Self::new( - context, - procedure.instance().as_p1_instance_pre(), - name, - proc_opt, - )?; - this.invoke(param) - } - - #[allow(unused)] - pub async fn call_async( - procedure: &Procedure, - proc_opt: ProcOpt, - param: ProcedureParam, - worker_local: Option, - ) -> RS { - let name = format!( - "{}{}", - mudu_contract::procedure::proc::MUDU_PROC_PREFIX, - procedure.proc_name() - ); - let context = build_wasi_p1_context(worker_local); - let this: Self = Self::new_async( - context, - procedure.instance().as_p1_instance_pre(), - name, - proc_opt, - ) - .await?; - this.invoke_async(param).await - } - - fn new( - context: WasiContext, - instance_pre: &InstancePre, - name: String, - proc_opt: ProcOpt, - ) -> RS { - Ok(Self { - inner: Mutex::new(ProcedureInvokeInner::new( - context, - instance_pre, - name, - proc_opt, - )?), - }) - } - - async fn new_async( - context: WasiContext, - instance_pre: &InstancePre, - name: String, - proc_opt: ProcOpt, - ) -> RS { - Ok(Self { - inner: Mutex::new( - ProcedureInvokeInner::new_async(context, instance_pre, name, proc_opt).await?, - ), - }) - } - fn invoke(self, param: ProcedureParam) -> RS { - let inner = self.inner; - let inner: ProcedureInvokeInner = inner - .into_inner() - .map_err(|e| m_error!(EC::MuduError, "", e))?; - let thread = thread::spawn(move || { - let ret = inner.invoke(param); - ret - }); - let result = thread - .join() - .map_err(|_e| m_error!(EC::MuduError, "invoke thread join error"))?; - result - } - - async fn invoke_async(self, param: ProcedureParam) -> RS { - let inner = self.inner; - let inner: ProcedureInvokeInner = inner - .into_inner() - .map_err(|e| m_error!(EC::MuduError, "", e))?; - inner.invoke_async(param).await - } -} - -struct ProcedureInvokeInner { - store: Store, - typed_func: TypedFunc<(u32, u32, u32, u32), i32>, - _proc_opt: ProcOpt, - memory: Memory, -} - -const PAGE_SIZE: u64 = 65536; - -#[allow(unused)] -pub struct ProcOpt { - pub memory: u64, - pub async_call: bool, -} - -impl ProcOpt { - fn memory_size(&self) -> u64 { - self.memory - } -} - -impl Default for ProcOpt { - fn default() -> Self { - Self { - memory: PAGE_SIZE * 2000, - async_call: false, - } - } -} - -struct InvokeParam { - in_ptr: u32, - in_size: u32, - out_ptr: u32, - out_size: u32, -} - -fn page_align_size(size: u64) -> u64 { - (size + PAGE_SIZE - 1) / PAGE_SIZE -} - -impl ProcedureInvokeInner { - fn new( - context: WasiContext, - instance_pre: &InstancePre, - name: String, - proc_opt: ProcOpt, - ) -> RS { - let mut store = Store::new(instance_pre.module().engine(), context); - let instance = instance_pre - .instantiate(&mut store) - .expect(&format!("failed to instantiate procedure: {}", name)); - let typed_func = instance - .get_typed_func::<(u32, u32, u32, u32), i32>(&mut store, &name) - .expect(&format!("get_typed_func: {}", name)); - let memory = instance - .get_memory(&mut store, "memory") - .context("Memory not found".to_string()) - .map_err(|e| m_error!(EC::MuduError, "", e))?; - - let size = page_align_size(proc_opt.memory_size()); - memory - .grow(&mut store, size) - .map_err(|e| m_error!(EC::MuduError, "", e))?; - - Ok(Self { - store, - typed_func, - _proc_opt: proc_opt, - memory, - }) - } - - async fn new_async( - context: WasiContext, - instance_pre: &InstancePre, - name: String, - proc_opt: ProcOpt, - ) -> RS { - let mut store = Store::new(instance_pre.module().engine(), context); - let instance = instance_pre - .instantiate_async(&mut store) - .await - .expect(&format!("failed to instantiate procedure: {}", name)); - let typed_func = instance - .get_typed_func::<(u32, u32, u32, u32), i32>(&mut store, &name) - .expect(&format!("get_typed_func: {}", name)); - let memory = instance - .get_memory(&mut store, "memory") - .context("Memory not found".to_string()) - .map_err(|e| m_error!(EC::MuduError, "", e))?; - - let size = page_align_size(proc_opt.memory_size()); - memory - .grow(&mut store, size) - .map_err(|e| m_error!(EC::MuduError, "", e))?; - - Ok(Self { - store, - typed_func, - _proc_opt: proc_opt, - memory, - }) - } - fn process_invoke_param(&mut self, param: ProcedureParam) -> RS { - let buf = self.memory.data_mut(&mut self.store); - let param_b = procedure_invoke::serialize_param(param)?; - if param_b.len() > buf.len() { - return Err(m_error!( - EC::InsufficientBufferSpace, - format!( - "failed to serialize procedure: buffer size not efficient {}", - buf.len() - ) - )); - } - buf.copy_from_slice(¶m_b); - let in_ptr = 0u32; - let in_size = param_b.len() as u32; - let out_ptr = in_size; - let out_size = buf.len() as u32 - out_ptr; - Ok(InvokeParam { - in_ptr, - in_size, - out_ptr, - out_size, - }) - } - - fn process_invoke_result( - &mut self, - invoke_param: &InvokeParam, - r: wasmtime::Result, - ) -> RS { - match r { - Ok(code) => { - if code == 0 { - let buf = self.memory.data_mut(&mut self.store); - let buf = &buf[invoke_param.out_ptr as usize..invoke_param.out_size as usize]; - let result = procedure_invoke::deserialize_result(buf)?; - Ok(result) - } else { - Err(m_error!( - EC::MuduError, - format!("procedure invoke error, returned code {}", code) - )) - } - } - Err(e) => Err(m_error!(EC::MuduError, "", e)), - } - } - - pub fn invoke(self, param: ProcedureParam) -> RS { - let mut this = self; - let invoke_param = this.process_invoke_param(param)?; - let r = this.typed_func.call( - &mut this.store, - ( - invoke_param.in_ptr, - invoke_param.in_size, - invoke_param.out_ptr, - invoke_param.out_size, - ), - ); - this.process_invoke_result(&invoke_param, r) - } - - pub async fn invoke_async(self, param: ProcedureParam) -> RS { - let mut this = self; - let invoke_param = this.process_invoke_param(param)?; - let r = this - .typed_func - .call_async( - &mut this.store, - ( - invoke_param.in_ptr, - invoke_param.in_size, - invoke_param.out_ptr, - invoke_param.out_size, - ), - ) - .await; - this.process_invoke_result(&invoke_param, r) - } -} diff --git a/mudu_runtime/src/service/runtime_opt.rs b/mudu_runtime/src/service/runtime_opt.rs index 617204a..8385a97 100644 --- a/mudu_runtime/src/service/runtime_opt.rs +++ b/mudu_runtime/src/service/runtime_opt.rs @@ -8,59 +8,23 @@ pub enum ComponentTarget { P3, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Default)] -#[serde(rename_all = "snake_case")] -pub enum RuntimeTarget { - #[default] - P1, - Component(ComponentTarget), -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RuntimeOpt { - pub target: RuntimeTarget, + #[serde(default)] + pub component_target: ComponentTarget, pub enable_async: bool, } impl RuntimeOpt { - pub fn from_legacy_enable_p2(enable_p2: bool, enable_async: bool) -> Self { - let target = if enable_p2 { - RuntimeTarget::Component(ComponentTarget::P2) - } else { - RuntimeTarget::P1 - }; - Self { - target, - enable_async, - } - } - - pub fn uses_component_model(&self) -> bool { - self.target.uses_component_model() - } - - pub fn component_target(&self) -> Option { - self.target.component_target() - } -} - -impl RuntimeTarget { - pub fn uses_component_model(self) -> bool { - matches!(self, Self::Component(_)) - } - - pub fn component_target(self) -> Option { - match self { - Self::P1 => None, - Self::Component(target) => Some(target), - } + pub fn component_target(&self) -> ComponentTarget { + self.component_target } } impl Default for RuntimeOpt { fn default() -> Self { Self { - target: RuntimeTarget::P1, + component_target: ComponentTarget::P2, enable_async: false, } } diff --git a/mudu_runtime/src/service/runtime_simple.rs b/mudu_runtime/src/service/runtime_simple.rs index 5ba85f2..3f87a8a 100644 --- a/mudu_runtime/src/service/runtime_simple.rs +++ b/mudu_runtime/src/service/runtime_simple.rs @@ -91,11 +91,7 @@ impl RuntimeSimple { db_path: &String, rt_opt: RuntimeOpt, ) -> RS { - let wt_runtime = if rt_opt.uses_component_model() { - WTRuntime::build_component(&rt_opt)? - } else { - WTRuntime::build_p1()? - }; + let wt_runtime = WTRuntime::build_component(&rt_opt)?; Ok(Self { rt_opt, package_path: package_path.clone(), @@ -142,7 +138,7 @@ impl RuntimeSimple { &self.db_path, &app_package, modules, - self.rt_opt.target, + self.rt_opt.component_target(), self.rt_opt.enable_async, ) .await?; diff --git a/mudu_runtime/src/service/test_runtime_simple.rs b/mudu_runtime/src/service/test_runtime_simple.rs index 5cb8d8e..6f64b08 100644 --- a/mudu_runtime/src/service/test_runtime_simple.rs +++ b/mudu_runtime/src/service/test_runtime_simple.rs @@ -57,12 +57,12 @@ mod tests { } fn db_path() -> String { - let n = uuid::Uuid::new_v4().to_string(); + let n = mudu_sys::random::next_uuid_v4_string(); let path = PathBuf::from(temp_dir()).join(format!("test_runtime_service_{}", n)); path.to_str().unwrap().to_string() } - async fn test_async_runtime_simple(enable_component: bool, test_kind: TestProc) -> RS<()> { + async fn test_async_runtime_simple(_enable_component: bool, test_kind: TestProc) -> RS<()> { let pkg_path = wasm_mod_path(); let db_path = db_path(); let enable_async = @@ -71,7 +71,10 @@ mod tests { &pkg_path, &db_path, None, - RuntimeOpt::from_legacy_enable_p2(enable_component, enable_async), + RuntimeOpt { + component_target: crate::service::runtime_opt::ComponentTarget::P2, + enable_async, + }, ) .await?; diff --git a/mudu_runtime/src/service/wasi_context_component.rs b/mudu_runtime/src/service/wasi_context_component.rs index 61bd2af..53e391e 100644 --- a/mudu_runtime/src/service/wasi_context_component.rs +++ b/mudu_runtime/src/service/wasi_context_component.rs @@ -1,4 +1,4 @@ -use mudu_kernel::server_ur::worker_local::WorkerLocalRef; +use mudu_kernel::server::worker_local::WorkerLocalRef; use wasmtime::component::ResourceTable; use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; @@ -41,8 +41,8 @@ pub fn build_wasi_component_context(worker_local: Option) -> Was pub mod sync_host { use super::WasiContextComponent; use crate::service::kernel_function_p2::{ - host_batch, host_close, host_command, host_fetch, host_get, host_open, host_put, host_query, - host_range, + host_batch, host_close, host_command, host_delete, host_fetch, host_get, host_open, + host_put, host_query, host_range, }; use wasmtime::component::bindgen; @@ -80,6 +80,10 @@ pub mod sync_host { host_put(put_in, self.worker_local()) } + fn delete(&mut self, delete_in: Vec) -> Vec { + host_delete(delete_in, self.worker_local()) + } + fn range(&mut self, range_in: Vec) -> Vec { host_range(range_in, self.worker_local()) } @@ -89,8 +93,9 @@ pub mod sync_host { pub mod async_host { use super::WasiContextComponent; use crate::service::kernel_function_p2_async::{ - async_host_batch, async_host_close, async_host_command, async_host_fetch, async_host_get, - async_host_open, async_host_put, async_host_query, async_host_range, + async_host_batch, async_host_close, async_host_command, async_host_delete, + async_host_fetch, async_host_get, async_host_open, async_host_put, async_host_query, + async_host_range, }; use wasmtime::component::bindgen; @@ -135,6 +140,10 @@ pub mod async_host { async_host_put(put_in, self.worker_local()).await } + async fn delete(&mut self, delete_in: Vec) -> Vec { + async_host_delete(delete_in, self.worker_local()).await + } + async fn range(&mut self, range_in: Vec) -> Vec { async_host_range(range_in, self.worker_local()).await } diff --git a/mudu_runtime/src/service/wt_instance_pre.rs b/mudu_runtime/src/service/wt_instance_pre.rs index 5eff327..ec7e515 100644 --- a/mudu_runtime/src/service/wt_instance_pre.rs +++ b/mudu_runtime/src/service/wt_instance_pre.rs @@ -1,46 +1,23 @@ -use crate::procedure::wasi_context::WasiContext; use crate::service::wasi_context_component::WasiContextComponent; use std::sync::Arc; -#[derive(Clone)] -enum InsPreType { - P1(Arc>), - Component(Arc>), -} - #[derive(Clone)] pub struct WTInstancePre { - inner: InsPreType, + inner: Arc>, } impl WTInstancePre { - pub fn from_p1(instance_pre: wasmtime::InstancePre) -> Self { - Self { - inner: InsPreType::P1(Arc::new(instance_pre)), - } - } - pub fn from_component( instance_pre: wasmtime::component::InstancePre, ) -> Self { Self { - inner: InsPreType::Component(Arc::new(instance_pre)), - } - } - - pub fn as_p1_instance_pre(&self) -> &wasmtime::InstancePre { - match &self.inner { - InsPreType::P1(instance_pre) => instance_pre.as_ref(), - _ => unsafe { std::hint::unreachable_unchecked() }, + inner: Arc::new(instance_pre), } } pub fn as_component_instance_pre( &self, ) -> &wasmtime::component::InstancePre { - match &self.inner { - InsPreType::Component(instance_pre) => instance_pre.as_ref(), - _ => unsafe { std::hint::unreachable_unchecked() }, - } + self.inner.as_ref() } } diff --git a/mudu_runtime/src/service/wt_runtime.rs b/mudu_runtime/src/service/wt_runtime.rs index ed3ac51..7eb5b6a 100644 --- a/mudu_runtime/src/service/wt_runtime.rs +++ b/mudu_runtime/src/service/wt_runtime.rs @@ -2,23 +2,16 @@ use crate::service::mudu_package::MuduPackage; use crate::service::package_module::PackageModule; use crate::service::runtime_opt::RuntimeOpt; use crate::service::wt_runtime_component::WTRuntimeComponent; -use crate::service::wt_runtime_p1::WTRuntimeP1; use mudu::common::result::RS; pub struct WTRuntime { - inner: WTRuntimeKind, + inner: WTRuntimeComponent, } impl WTRuntime { - pub fn build_p1() -> RS { - Ok(Self { - inner: WTRuntimeKind::build_p1()?, - }) - } - pub fn build_component(runtime_opt: &RuntimeOpt) -> RS { Ok(Self { - inner: WTRuntimeKind::build_component(runtime_opt)?, + inner: WTRuntimeComponent::build(runtime_opt)?, }) } @@ -30,32 +23,3 @@ impl WTRuntime { self.inner.compile_modules(package) } } - -enum WTRuntimeKind { - P1(WTRuntimeP1), - Component(WTRuntimeComponent), -} - -impl WTRuntimeKind { - fn build_p1() -> RS { - Ok(Self::P1(WTRuntimeP1::build()?)) - } - - fn build_component(runtime_opt: &RuntimeOpt) -> RS { - Ok(Self::Component(WTRuntimeComponent::build(runtime_opt)?)) - } - - fn instantiate(&mut self) -> RS<()> { - match self { - WTRuntimeKind::P1(r) => r.instantiate(), - WTRuntimeKind::Component(r) => r.instantiate(), - } - } - - fn compile_modules(&self, package: &MuduPackage) -> RS> { - match self { - WTRuntimeKind::P1(r) => r.compile_modules(package), - WTRuntimeKind::Component(r) => r.compile_modules(package), - } - } -} diff --git a/mudu_runtime/src/service/wt_runtime_component.rs b/mudu_runtime/src/service/wt_runtime_component.rs index 8672923..defe12e 100644 --- a/mudu_runtime/src/service/wt_runtime_component.rs +++ b/mudu_runtime/src/service/wt_runtime_component.rs @@ -40,12 +40,7 @@ impl WTRuntimeComponent { } pub fn instantiate(&mut self) -> RS<()> { - let component_target = self.runtime_opt.component_target().ok_or_else(|| { - m_error!( - EC::InternalErr, - "component runtime requested without a component target" - ) - })?; + let component_target = self.runtime_opt.component_target(); wasi_context_component::async_host::mududb::async_api::system::add_to_linker::<_, HasSelf<_>>( &mut self.linker, |c| c, @@ -90,7 +85,7 @@ fn instantiate_component( return Err(m_error!( EC::MuduError, format!( - "package module {} is a WebAssembly module, but runtime target is component; disable enable_p2 or rebuild the package for wasm32-wasip2", + "package module {} is a WebAssembly module, but runtime target is component; rebuild the package for wasm32-wasip2", name ), component_err diff --git a/mudu_runtime/src/service/wt_runtime_p1.rs b/mudu_runtime/src/service/wt_runtime_p1.rs deleted file mode 100644 index fb67ef6..0000000 --- a/mudu_runtime/src/service/wt_runtime_p1.rs +++ /dev/null @@ -1,340 +0,0 @@ -use crate::procedure::wasi_context::WasiContext; -use crate::service::kernel_function_p1; -use crate::service::mudu_package::MuduPackage; -use crate::service::package_module::PackageModule; -use crate::service::wt_instance_pre::WTInstancePre; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use mudu_contract::procedure::mod_proc_desc::ModProcDesc; -use mudu_contract::procedure::proc_desc::ProcDesc; -use wasmtime::component::Component; -use wasmtime::{Caller, Config, Engine, Linker, Module}; - -pub struct WTRuntimeP1 { - engine: Engine, - linker: Linker, -} - -impl WTRuntimeP1 { - pub fn build() -> RS { - let mut cfg = Config::new(); - let engine = Engine::new(&mut cfg) - .map_err(|e| m_error!(EC::InternalErr, "failed create new wasm runtime engine", e))?; - // Configure linker with host functions - let linker = Linker::new(&engine); - Ok(Self { engine, linker }) - } - - pub fn instantiate(&mut self) -> RS<()> { - register_sys_call(&mut self.linker)?; - wasmtime_wasi::p1::add_to_linker_sync(&mut self.linker, |ctx| ctx.wasi_mut()) - .map_err(|e| m_error!(EC::MuduError, "wasmtime_wasi add_to_linker_sync error", e))?; - Ok(()) - } - - pub fn compile_modules(&self, package: &MuduPackage) -> RS> { - let modules = instantiate_mpk_modules(&self.engine, &self.linker, package)?; - Ok(modules) - } -} - -fn instantiate_module( - engine: &Engine, - linker: &Linker, - name: String, - byte_code: &Vec, - desc_vec: &Vec, -) -> RS { - let module = match Module::from_binary(&engine, &byte_code) { - Ok(module) => module, - Err(module_err) => { - if Component::from_binary(engine, byte_code).is_ok() { - return Err(m_error!( - EC::MuduError, - format!( - "package module {} is a WebAssembly component, but runtime target is P1; set enable_p2 = true or component_target = \"p2\"", - name - ), - module_err - )); - } - return Err(m_error!( - EC::MuduError, - format!("build module {} from binary error", name), - module_err - )); - } - }; - - let instance_pre = linker.instantiate_pre(&module).map_err(|e| { - m_error!( - EC::MuduError, - format!("instantiate module {} error", name), - e - ) - })?; - PackageModule::new(WTInstancePre::from_p1(instance_pre), desc_vec.clone()) -} - -fn instantiate_mpk_modules( - engine: &Engine, - linker: &Linker, - package: &MuduPackage, -) -> RS> { - let mut modules = Vec::new(); - let app_proc_desc: &ModProcDesc = &package.package_desc; - for (mod_name, vec_desc) in app_proc_desc.modules() { - let byte_code = package - .modules - .get(mod_name) - .ok_or_else(|| m_error!(EC::NoneErr, format!("no such module named {}", mod_name)))?; - let module = instantiate_module(engine, linker, mod_name.clone(), byte_code, vec_desc)?; - modules.push((mod_name.clone(), module)); - } - Ok(modules) -} - -fn register_sys_call(linker: &mut Linker) -> RS<()> { - let module_name = "env"; - linker - .func_wrap( - module_name, - "sys_query", - |caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - out_buf_ptr: u32, - out_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32| - -> i32 { - kernel_function_p1::kernel_query( - caller, - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_mem_ptr, - out_mem_len, - ) - }, - ) - .map_err(|e| m_error!(EC::MuduError, "register query error", e))?; - - linker - .func_wrap( - module_name, - "sys_command", - |caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - out_buf_ptr: u32, - out_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32| - -> i32 { - kernel_function_p1::kernel_command_p1( - caller, - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_mem_ptr, - out_mem_len, - ) - }, - ) - .map_err(|e| m_error!(EC::MuduError, "register command error", e))?; - - linker - .func_wrap( - module_name, - "sys_batch", - |caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - out_buf_ptr: u32, - out_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32| - -> i32 { - kernel_function_p1::kernel_batch_p1( - caller, - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_mem_ptr, - out_mem_len, - ) - }, - ) - .map_err(|e| m_error!(EC::MuduError, "register batch error", e))?; - - linker - .func_wrap( - module_name, - "sys_fetch", - |caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - out_buf_ptr: u32, - out_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32| - -> i32 { - kernel_function_p1::kernel_fetch_p1( - caller, - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_mem_ptr, - out_mem_len, - ) - }, - ) - .map_err(|e| m_error!(EC::MuduError, "register fetch error", e))?; - - linker - .func_wrap( - module_name, - "sys_open", - |caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - out_buf_ptr: u32, - out_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32| - -> i32 { - kernel_function_p1::kernel_open_p1( - caller, - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_mem_ptr, - out_mem_len, - ) - }, - ) - .map_err(|e| m_error!(EC::MuduError, "register open error", e))?; - - linker - .func_wrap( - module_name, - "sys_close", - |caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - out_buf_ptr: u32, - out_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32| - -> i32 { - kernel_function_p1::kernel_close_p1( - caller, - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_mem_ptr, - out_mem_len, - ) - }, - ) - .map_err(|e| m_error!(EC::MuduError, "register close error", e))?; - - linker - .func_wrap( - module_name, - "sys_get", - |caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - out_buf_ptr: u32, - out_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32| - -> i32 { - kernel_function_p1::kernel_get_p1( - caller, - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_mem_ptr, - out_mem_len, - ) - }, - ) - .map_err(|e| m_error!(EC::MuduError, "register get error", e))?; - - linker - .func_wrap( - module_name, - "sys_put", - |caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - out_buf_ptr: u32, - out_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32| - -> i32 { - kernel_function_p1::kernel_put_p1( - caller, - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_mem_ptr, - out_mem_len, - ) - }, - ) - .map_err(|e| m_error!(EC::MuduError, "register put error", e))?; - - linker - .func_wrap( - module_name, - "sys_range", - |caller: Caller<'_, WasiContext>, - param_buf_ptr: u32, - param_buf_len: u32, - out_buf_ptr: u32, - out_buf_len: u32, - out_mem_ptr: u32, - out_mem_len: u32| - -> i32 { - kernel_function_p1::kernel_range_p1( - caller, - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_mem_ptr, - out_mem_len, - ) - }, - ) - .map_err(|e| m_error!(EC::MuduError, "register range error", e))?; - - linker - .func_wrap( - module_name, - "sys_get_memory", - |caller: Caller<'_, WasiContext>, - mem_id: u32, - out_buf_ptr: u32, - out_buf_len: u32| - -> i32 { - kernel_function_p1::kernel_get_memory_p1(caller, mem_id, out_buf_ptr, out_buf_len) - }, - ) - .map_err(|e| m_error!(EC::MuduError, "", e))?; - - Ok(()) -} diff --git a/mudu_runtime/wit/api.wit b/mudu_runtime/wit/api.wit index 69d3b4f..a3d3251 100644 --- a/mudu_runtime/wit/api.wit +++ b/mudu_runtime/wit/api.wit @@ -17,6 +17,8 @@ interface system { put: func(put-in: list) -> list; + delete: func(delete-in: list) -> list; + range: func(range-in: list) -> list; } diff --git a/mudu_runtime/wit/async-api.wit b/mudu_runtime/wit/async-api.wit index f1df886..3ec9622 100644 --- a/mudu_runtime/wit/async-api.wit +++ b/mudu_runtime/wit/async-api.wit @@ -17,6 +17,8 @@ interface system { put: func(put-in: list) -> list; + delete: func(delete-in: list) -> list; + range: func(range-in: list) -> list; } diff --git a/mudu_sys/Cargo.toml b/mudu_sys/Cargo.toml new file mode 100644 index 0000000..1b475ea --- /dev/null +++ b/mudu_sys/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mudu_sys" +version = "0.1.0" +edition = "2021" + +[dependencies] +mudu = { workspace = true } +uuid = { workspace = true } +chrono = { workspace = true } +libc = { workspace = true } +socket2 = "0.5.8" +async-trait = { workspace = true } +tokio = { workspace = true } + +[target.'cfg(target_os = "linux")'.dependencies] +rliburing = { git = "https://github.com/scuptio/rliburing.git" } diff --git a/mudu_sys/src/api/env.rs b/mudu_sys/src/api/env.rs new file mode 100644 index 0000000..fcb4c45 --- /dev/null +++ b/mudu_sys/src/api/env.rs @@ -0,0 +1,15 @@ +use crate::api::fs::SysFs; +use crate::api::net::SysNet; +use crate::api::random::SysRandom; +use crate::api::sync::SysSync; +use crate::api::task::SysTask; +use crate::api::time::SysTime; + +pub trait SysEnv: Send + Sync { + fn time(&self) -> &dyn SysTime; + fn random(&self) -> &dyn SysRandom; + fn fs(&self) -> &dyn SysFs; + fn net(&self) -> &dyn SysNet; + fn task(&self) -> &dyn SysTask; + fn sync(&self) -> &dyn SysSync; +} diff --git a/mudu_sys/src/api/fs.rs b/mudu_sys/src/api/fs.rs new file mode 100644 index 0000000..da8e209 --- /dev/null +++ b/mudu_sys/src/api/fs.rs @@ -0,0 +1,17 @@ +use mudu::common::result::RS; +use std::fs::File; +use std::path::{Path, PathBuf}; + +pub trait SysFs: Send + Sync { + fn open(&self, path: &Path, flags: i32, mode: u32) -> RS; + fn read_exact_at(&self, file: &File, len: usize, offset: u64) -> RS>; + fn write_all_at(&self, file: &File, payload: &[u8], offset: u64) -> RS<()>; + fn fsync(&self, file: &File) -> RS<()>; + fn close(&self, file: File) -> RS<()>; + + fn create_dir_all(&self, path: &Path) -> RS<()>; + fn read_dir(&self, path: &Path) -> RS>; + fn metadata_len(&self, path: &Path) -> RS; + fn read_all(&self, path: &Path) -> RS>; + fn remove_file_if_exists(&self, path: &Path) -> RS<()>; +} diff --git a/mudu_sys/src/api/mod.rs b/mudu_sys/src/api/mod.rs new file mode 100644 index 0000000..52734cf --- /dev/null +++ b/mudu_sys/src/api/mod.rs @@ -0,0 +1,7 @@ +pub mod env; +pub mod fs; +pub mod net; +pub mod random; +pub mod sync; +pub mod task; +pub mod time; diff --git a/mudu_sys/src/api/net.rs b/mudu_sys/src/api/net.rs new file mode 100644 index 0000000..d3855a5 --- /dev/null +++ b/mudu_sys/src/api/net.rs @@ -0,0 +1,9 @@ +use mudu::common::result::RS; +use std::net::SocketAddr; + +use crate::fd::RawFd; + +pub trait SysNet: Send + Sync { + fn create_tcp_listener_fd(&self, listen_addr: SocketAddr, backlog: i32) -> RS; + fn set_tcp_nodelay(&self, fd: RawFd) -> RS<()>; +} diff --git a/mudu_sys/src/api/random.rs b/mudu_sys/src/api/random.rs new file mode 100644 index 0000000..85b3a7c --- /dev/null +++ b/mudu_sys/src/api/random.rs @@ -0,0 +1,14 @@ +use crate::env::default_env; +use uuid::Uuid; + +pub trait SysRandom: Send + Sync { + fn uuid_v4(&self) -> Uuid; +} + +pub fn uuid_v4() -> Uuid { + default_env().random().uuid_v4() +} + +pub fn next_uuid_v4_string() -> String { + uuid_v4().to_string() +} diff --git a/mudu_sys/src/api/sync.rs b/mudu_sys/src/api/sync.rs new file mode 100644 index 0000000..7989347 --- /dev/null +++ b/mudu_sys/src/api/sync.rs @@ -0,0 +1,10 @@ +use mudu::common::result::RS; + +use crate::fd::RawFd; + +pub trait SysSync: Send + Sync { + fn eventfd(&self) -> RS; + fn notify_eventfd(&self, fd: RawFd) -> RS<()>; + fn read_eventfd(&self, fd: RawFd) -> RS; + fn close_fd(&self, fd: RawFd) -> RS<()>; +} diff --git a/mudu_sys/src/api/task.rs b/mudu_sys/src/api/task.rs new file mode 100644 index 0000000..4ca6210 --- /dev/null +++ b/mudu_sys/src/api/task.rs @@ -0,0 +1,9 @@ +use async_trait::async_trait; +use mudu::common::result::RS; +use std::time::Duration; + +#[async_trait] +pub trait SysTask: Send + Sync { + async fn sleep(&self, dur: Duration) -> RS<()>; + fn sleep_blocking(&self, dur: Duration); +} diff --git a/mudu_sys/src/api/time.rs b/mudu_sys/src/api/time.rs new file mode 100644 index 0000000..6dc8bed --- /dev/null +++ b/mudu_sys/src/api/time.rs @@ -0,0 +1,21 @@ +use crate::env::default_env; +use chrono::{DateTime, Utc}; +use std::time::{Instant, SystemTime}; + +pub trait SysTime: Send + Sync { + fn instant_now(&self) -> Instant; + fn system_time_now(&self) -> SystemTime; + fn utc_now(&self) -> DateTime; +} + +pub fn instant_now() -> Instant { + default_env().time().instant_now() +} + +pub fn system_time_now() -> SystemTime { + default_env().time().system_time_now() +} + +pub fn utc_now() -> DateTime { + default_env().time().utc_now() +} diff --git a/mudu_sys/src/env.rs b/mudu_sys/src/env.rs new file mode 100644 index 0000000..bab9494 --- /dev/null +++ b/mudu_sys/src/env.rs @@ -0,0 +1,27 @@ +use crate::api::env::SysEnv; +use crate::linux::env::LinuxSysEnv; +use std::sync::{Arc, OnceLock, RwLock}; + +static DEFAULT_ENV: OnceLock>> = OnceLock::new(); + +fn default_env_cell() -> &'static RwLock> { + DEFAULT_ENV.get_or_init(|| RwLock::new(Arc::new(LinuxSysEnv::new()))) +} + +pub fn default_env() -> Arc { + default_env_cell() + .read() + .expect("default sys env lock poisoned") + .clone() +} + +pub fn set_default_env(env: Arc) { + let mut guard = default_env_cell() + .write() + .expect("default sys env lock poisoned"); + *guard = env; +} + +pub fn reset_default_env() { + set_default_env(Arc::new(LinuxSysEnv::new())); +} diff --git a/mudu_sys/src/fd.rs b/mudu_sys/src/fd.rs new file mode 100644 index 0000000..fdb43af --- /dev/null +++ b/mudu_sys/src/fd.rs @@ -0,0 +1,5 @@ +#[cfg(unix)] +pub type RawFd = std::os::fd::RawFd; + +#[cfg(not(unix))] +pub type RawFd = libc::c_int; diff --git a/mudu_sys/src/fs.rs b/mudu_sys/src/fs.rs new file mode 100644 index 0000000..2c80532 --- /dev/null +++ b/mudu_sys/src/fs.rs @@ -0,0 +1,44 @@ +use crate::env::default_env; +use mudu::common::result::RS; +use std::fs::File; +use std::path::{Path, PathBuf}; + +pub fn open(path: &Path, flags: i32, mode: u32) -> RS { + default_env().fs().open(path, flags, mode) +} + +pub fn read_exact_at(file: &File, len: usize, offset: u64) -> RS> { + default_env().fs().read_exact_at(file, len, offset) +} + +pub fn write_all_at(file: &File, payload: &[u8], offset: u64) -> RS<()> { + default_env().fs().write_all_at(file, payload, offset) +} + +pub fn fsync(file: &File) -> RS<()> { + default_env().fs().fsync(file) +} + +pub fn close(file: File) -> RS<()> { + default_env().fs().close(file) +} + +pub fn create_dir_all(path: &Path) -> RS<()> { + default_env().fs().create_dir_all(path) +} + +pub fn read_dir(path: &Path) -> RS> { + default_env().fs().read_dir(path) +} + +pub fn metadata_len(path: &Path) -> RS { + default_env().fs().metadata_len(path) +} + +pub fn read_all(path: &Path) -> RS> { + default_env().fs().read_all(path) +} + +pub fn remove_file_if_exists(path: &Path) -> RS<()> { + default_env().fs().remove_file_if_exists(path) +} diff --git a/mudu_sys/src/lib.rs b/mudu_sys/src/lib.rs new file mode 100644 index 0000000..9617156 --- /dev/null +++ b/mudu_sys/src/lib.rs @@ -0,0 +1,18 @@ +pub mod api; +pub mod env; +pub mod fd; +pub mod fs; +pub mod linux; +pub mod net; +pub mod sync; +pub mod task; +#[cfg(target_os = "linux")] +pub mod uring; + +pub mod random { + pub use crate::api::random::{next_uuid_v4_string, uuid_v4}; +} + +pub mod time { + pub use crate::api::time::{instant_now, system_time_now, utc_now}; +} diff --git a/mudu_sys/src/linux/env.rs b/mudu_sys/src/linux/env.rs new file mode 100644 index 0000000..5cc0c4d --- /dev/null +++ b/mudu_sys/src/linux/env.rs @@ -0,0 +1,61 @@ +use crate::api::env::SysEnv; +use crate::api::fs::SysFs; +use crate::api::net::SysNet; +use crate::api::random::SysRandom; +use crate::api::sync::SysSync; +use crate::api::task::SysTask; +use crate::api::time::SysTime; +use crate::linux::fs::LinuxFs; +use crate::linux::net::LinuxNet; +use crate::linux::random::LinuxRandom; +use crate::linux::sync::LinuxSync; +use crate::linux::task::LinuxTask; +use crate::linux::time::LinuxTime; + +pub struct LinuxSysEnv { + time: LinuxTime, + random: LinuxRandom, + fs: LinuxFs, + net: LinuxNet, + task: LinuxTask, + sync: LinuxSync, +} + +impl LinuxSysEnv { + pub fn new() -> Self { + Self { + time: LinuxTime, + random: LinuxRandom, + fs: LinuxFs, + net: LinuxNet, + task: LinuxTask, + sync: LinuxSync, + } + } +} + +impl SysEnv for LinuxSysEnv { + fn time(&self) -> &dyn SysTime { + &self.time + } + + fn random(&self) -> &dyn SysRandom { + &self.random + } + + fn fs(&self) -> &dyn SysFs { + &self.fs + } + + fn net(&self) -> &dyn SysNet { + &self.net + } + + fn task(&self) -> &dyn SysTask { + &self.task + } + + fn sync(&self) -> &dyn SysSync { + &self.sync + } +} diff --git a/mudu_sys/src/linux/fs.rs b/mudu_sys/src/linux/fs.rs new file mode 100644 index 0000000..be41007 --- /dev/null +++ b/mudu_sys/src/linux/fs.rs @@ -0,0 +1,114 @@ +use crate::api::fs::SysFs; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use std::fs::{File, OpenOptions}; +#[cfg(unix)] +use std::os::unix::fs::{FileExt, OpenOptionsExt}; +#[cfg(windows)] +use std::os::windows::fs::FileExt; +use std::path::{Path, PathBuf}; + +pub struct LinuxFs; + +impl SysFs for LinuxFs { + fn open(&self, path: &Path, flags: i32, _mode: u32) -> RS { + let mut options = OpenOptions::new(); + let read = (flags & libc::O_RDWR) != 0 || (flags & libc::O_WRONLY) == 0; + let write = (flags & libc::O_RDWR) != 0 || (flags & libc::O_WRONLY) != 0; + options.read(read); + options.write(write); + options.create((flags & libc::O_CREAT) != 0); + options.truncate((flags & libc::O_TRUNC) != 0); + options.append((flags & libc::O_APPEND) != 0); + #[cfg(unix)] + { + options.mode(_mode); + } + options + .open(path) + .map_err(|e| m_error!(EC::IOErr, "open file error", e)) + } + + fn read_exact_at(&self, file: &File, len: usize, offset: u64) -> RS> { + let mut buf = vec![0u8; len]; + let mut read = 0usize; + while read < len { + #[cfg(unix)] + let rc = file + .read_at(&mut buf[read..], offset + read as u64) + .map_err(|e| m_error!(EC::IOErr, "read file error", e))?; + #[cfg(windows)] + let rc = file + .seek_read(&mut buf[read..], offset + read as u64) + .map_err(|e| m_error!(EC::IOErr, "read file error", e))?; + if rc == 0 { + return Err(m_error!(EC::IOErr, "unexpected EOF while reading file")); + } + read += rc; + } + Ok(buf) + } + + fn write_all_at(&self, file: &File, payload: &[u8], offset: u64) -> RS<()> { + let mut written = 0usize; + while written < payload.len() { + #[cfg(unix)] + let rc = file + .write_at(&payload[written..], offset + written as u64) + .map_err(|e| m_error!(EC::IOErr, "write file error", e))?; + #[cfg(windows)] + let rc = file + .seek_write(&payload[written..], offset + written as u64) + .map_err(|e| m_error!(EC::IOErr, "write file error", e))?; + if rc == 0 { + return Err(m_error!(EC::IOErr, "write file returned zero bytes")); + } + written += rc; + } + Ok(()) + } + + fn fsync(&self, file: &File) -> RS<()> { + file.sync_all() + .map_err(|e| m_error!(EC::IOErr, "flush file error", e)) + } + + fn close(&self, file: File) -> RS<()> { + drop(file); + Ok(()) + } + + fn create_dir_all(&self, path: &Path) -> RS<()> { + std::fs::create_dir_all(path).map_err(|e| m_error!(EC::IOErr, "create directory error", e)) + } + + fn read_dir(&self, path: &Path) -> RS> { + let mut paths = Vec::new(); + for entry in + std::fs::read_dir(path).map_err(|e| m_error!(EC::IOErr, "read directory error", e))? + { + let entry = entry.map_err(|e| m_error!(EC::IOErr, "read directory entry error", e))?; + paths.push(entry.path()); + } + Ok(paths) + } + + fn metadata_len(&self, path: &Path) -> RS { + std::fs::metadata(path) + .map_err(|e| m_error!(EC::IOErr, "read file metadata error", e)) + .map(|metadata| metadata.len()) + } + + fn read_all(&self, path: &Path) -> RS> { + std::fs::read(path).map_err(|e| m_error!(EC::IOErr, "read file error", e)) + } + + fn remove_file_if_exists(&self, path: &Path) -> RS<()> { + match std::fs::remove_file(path) { + Ok(()) => Ok(()), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()), + Err(err) => Err(m_error!(EC::IOErr, "remove file error", err)), + } + } +} diff --git a/mudu_sys/src/linux/mod.rs b/mudu_sys/src/linux/mod.rs new file mode 100644 index 0000000..52734cf --- /dev/null +++ b/mudu_sys/src/linux/mod.rs @@ -0,0 +1,7 @@ +pub mod env; +pub mod fs; +pub mod net; +pub mod random; +pub mod sync; +pub mod task; +pub mod time; diff --git a/mudu_sys/src/linux/net.rs b/mudu_sys/src/linux/net.rs new file mode 100644 index 0000000..dc0fcfb --- /dev/null +++ b/mudu_sys/src/linux/net.rs @@ -0,0 +1,96 @@ +use crate::api::net::SysNet; +use crate::fd::RawFd; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +#[cfg(target_os = "linux")] +use socket2::{Domain, Protocol, Socket, Type}; +use std::net::SocketAddr; +#[cfg(target_os = "linux")] +use std::os::fd::{AsRawFd, IntoRawFd}; + +pub struct LinuxNet; + +impl SysNet for LinuxNet { + #[cfg(target_os = "linux")] + fn create_tcp_listener_fd(&self, listen_addr: SocketAddr, backlog: i32) -> RS { + let domain = if listen_addr.is_ipv4() { + Domain::IPV4 + } else { + Domain::IPV6 + }; + let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP)) + .map_err(|e| m_error!(EC::NetErr, "create tcp listener socket error", e))?; + socket + .set_reuse_address(true) + .map_err(|e| m_error!(EC::NetErr, "enable SO_REUSEADDR error", e))?; + enable_reuse_port(&socket)?; + socket + .bind(&listen_addr.into()) + .map_err(|e| m_error!(EC::NetErr, "bind io_uring tcp listener error", e))?; + socket + .listen(backlog) + .map_err(|e| m_error!(EC::NetErr, "listen io_uring tcp listener error", e))?; + Ok(socket.into_raw_fd()) + } + + #[cfg(not(target_os = "linux"))] + fn create_tcp_listener_fd(&self, _listen_addr: SocketAddr, _backlog: i32) -> RS { + Err(m_error!( + EC::NotImplemented, + "create_tcp_listener_fd is only available on Linux" + )) + } + + #[cfg(target_os = "linux")] + fn set_tcp_nodelay(&self, fd: RawFd) -> RS<()> { + let flag: libc::c_int = 1; + let rc = unsafe { + libc::setsockopt( + fd, + libc::IPPROTO_TCP, + libc::TCP_NODELAY, + &flag as *const _ as *const libc::c_void, + std::mem::size_of_val(&flag) as libc::socklen_t, + ) + }; + if rc != 0 { + return Err(m_error!( + EC::NetErr, + "set connection nodelay error", + std::io::Error::last_os_error() + )); + } + Ok(()) + } + + #[cfg(not(target_os = "linux"))] + fn set_tcp_nodelay(&self, _fd: RawFd) -> RS<()> { + Err(m_error!( + EC::NotImplemented, + "set_tcp_nodelay is only available on Linux" + )) + } +} + +#[cfg(target_os = "linux")] +fn enable_reuse_port(socket: &Socket) -> RS<()> { + let value: libc::c_int = 1; + let rc = unsafe { + libc::setsockopt( + socket.as_raw_fd(), + libc::SOL_SOCKET, + libc::SO_REUSEPORT, + &value as *const _ as *const libc::c_void, + std::mem::size_of_val(&value) as libc::socklen_t, + ) + }; + if rc != 0 { + return Err(m_error!( + EC::NetErr, + "enable SO_REUSEPORT error", + std::io::Error::last_os_error() + )); + } + Ok(()) +} diff --git a/mudu_sys/src/linux/random.rs b/mudu_sys/src/linux/random.rs new file mode 100644 index 0000000..dad6e4d --- /dev/null +++ b/mudu_sys/src/linux/random.rs @@ -0,0 +1,10 @@ +use crate::api::random::SysRandom; +use uuid::Uuid; + +pub struct LinuxRandom; + +impl SysRandom for LinuxRandom { + fn uuid_v4(&self) -> Uuid { + Uuid::new_v4() + } +} diff --git a/mudu_sys/src/linux/sync.rs b/mudu_sys/src/linux/sync.rs new file mode 100644 index 0000000..1e44b43 --- /dev/null +++ b/mudu_sys/src/linux/sync.rs @@ -0,0 +1,107 @@ +use crate::api::sync::SysSync; +use crate::fd::RawFd; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; + +pub struct LinuxSync; + +impl SysSync for LinuxSync { + #[cfg(target_os = "linux")] + fn eventfd(&self) -> RS { + let fd = unsafe { libc::eventfd(0, libc::EFD_CLOEXEC) }; + if fd < 0 { + return Err(m_error!( + EC::NetErr, + "create eventfd error", + std::io::Error::last_os_error() + )); + } + Ok(fd) + } + + #[cfg(not(target_os = "linux"))] + fn eventfd(&self) -> RS { + Err(m_error!( + EC::NotImplemented, + "eventfd is only available on Linux" + )) + } + + #[cfg(target_os = "linux")] + fn notify_eventfd(&self, fd: RawFd) -> RS<()> { + let value: u64 = 1; + let rc = unsafe { + libc::write( + fd, + &value as *const u64 as *const libc::c_void, + std::mem::size_of::(), + ) + }; + if rc as usize != std::mem::size_of::() { + return Err(m_error!( + EC::NetErr, + "write eventfd error", + std::io::Error::last_os_error() + )); + } + Ok(()) + } + + #[cfg(not(target_os = "linux"))] + fn notify_eventfd(&self, _fd: RawFd) -> RS<()> { + Err(m_error!( + EC::NotImplemented, + "notify_eventfd is only available on Linux" + )) + } + + #[cfg(target_os = "linux")] + fn read_eventfd(&self, fd: RawFd) -> RS { + let mut value = 0u64; + let rc = unsafe { + libc::read( + fd, + (&mut value) as *mut u64 as *mut libc::c_void, + std::mem::size_of::(), + ) + }; + if rc as usize != std::mem::size_of::() { + return Err(m_error!( + EC::NetErr, + "read eventfd error", + std::io::Error::last_os_error() + )); + } + Ok(value) + } + + #[cfg(not(target_os = "linux"))] + fn read_eventfd(&self, _fd: RawFd) -> RS { + Err(m_error!( + EC::NotImplemented, + "read_eventfd is only available on Linux" + )) + } + + #[cfg(target_os = "linux")] + fn close_fd(&self, fd: RawFd) -> RS<()> { + let rc = unsafe { libc::close(fd) }; + if rc != 0 { + return Err(m_error!( + EC::NetErr, + "close fd error", + std::io::Error::last_os_error() + )); + } + Ok(()) + } + + #[cfg(not(target_os = "linux"))] + fn close_fd(&self, _fd: RawFd) -> RS<()> { + Err(m_error!( + EC::NotImplemented, + "close_fd is only available on Linux" + )) + } +} diff --git a/mudu_sys/src/linux/task.rs b/mudu_sys/src/linux/task.rs new file mode 100644 index 0000000..f0b873b --- /dev/null +++ b/mudu_sys/src/linux/task.rs @@ -0,0 +1,18 @@ +use crate::api::task::SysTask; +use async_trait::async_trait; +use mudu::common::result::RS; +use std::time::Duration; + +pub struct LinuxTask; + +#[async_trait] +impl SysTask for LinuxTask { + async fn sleep(&self, dur: Duration) -> RS<()> { + tokio::time::sleep(dur).await; + Ok(()) + } + + fn sleep_blocking(&self, dur: Duration) { + std::thread::sleep(dur); + } +} diff --git a/mudu_sys/src/linux/time.rs b/mudu_sys/src/linux/time.rs new file mode 100644 index 0000000..5dc499e --- /dev/null +++ b/mudu_sys/src/linux/time.rs @@ -0,0 +1,19 @@ +use crate::api::time::SysTime; +use chrono::{DateTime, Utc}; +use std::time::{Instant, SystemTime}; + +pub struct LinuxTime; + +impl SysTime for LinuxTime { + fn instant_now(&self) -> Instant { + Instant::now() + } + + fn system_time_now(&self) -> SystemTime { + SystemTime::now() + } + + fn utc_now(&self) -> DateTime { + Utc::now() + } +} diff --git a/mudu_sys/src/net.rs b/mudu_sys/src/net.rs new file mode 100644 index 0000000..94dbd23 --- /dev/null +++ b/mudu_sys/src/net.rs @@ -0,0 +1,102 @@ +use crate::env::default_env; +use crate::fd::RawFd; +use mudu::common::result::RS; +use std::net::SocketAddr; + +pub fn create_tcp_listener_fd(listen_addr: SocketAddr, backlog: i32) -> RS { + default_env() + .net() + .create_tcp_listener_fd(listen_addr, backlog) +} + +pub fn set_tcp_nodelay(fd: RawFd) -> RS<()> { + default_env().net().set_tcp_nodelay(fd) +} + +#[cfg(target_os = "linux")] +use crate::uring::SockAddrBuf; +#[cfg(target_os = "linux")] +use mudu::error::ec::EC; +#[cfg(target_os = "linux")] +use mudu::m_error; + +#[cfg(target_os = "linux")] +pub fn socket_addr_to_storage(addr: SocketAddr) -> RS { + match addr { + SocketAddr::V4(v4) => { + let mut storage = zeroed_sockaddr_storage(); + let raw = libc::sockaddr_in { + sin_family: libc::AF_INET as libc::sa_family_t, + sin_port: v4.port().to_be(), + sin_addr: libc::in_addr { + s_addr: u32::from_be_bytes(v4.ip().octets()).to_be(), + }, + sin_zero: [0; 8], + }; + unsafe { + std::ptr::write( + (&mut storage) as *mut rliburing::sockaddr_storage as *mut libc::sockaddr_in, + raw, + ); + } + Ok(SockAddrBuf::from_raw( + storage, + std::mem::size_of::() as u32, + )) + } + SocketAddr::V6(v6) => { + let mut storage = zeroed_sockaddr_storage(); + let raw = libc::sockaddr_in6 { + sin6_family: libc::AF_INET6 as libc::sa_family_t, + sin6_port: v6.port().to_be(), + sin6_flowinfo: v6.flowinfo(), + sin6_addr: libc::in6_addr { + s6_addr: v6.ip().octets(), + }, + sin6_scope_id: v6.scope_id(), + }; + unsafe { + std::ptr::write( + (&mut storage) as *mut rliburing::sockaddr_storage as *mut libc::sockaddr_in6, + raw, + ); + } + Ok(SockAddrBuf::from_raw( + storage, + std::mem::size_of::() as u32, + )) + } + } +} + +#[cfg(target_os = "linux")] +pub fn sockaddr_to_socket_addr(addr: &SockAddrBuf) -> RS { + match addr.raw().ss_family as i32 { + libc::AF_INET => { + if addr.len() < std::mem::size_of::() { + return Err(m_error!(EC::NetErr, "short sockaddr_in length")); + } + let raw = unsafe { &*(addr.raw() as *const rliburing::sockaddr_storage as *const libc::sockaddr_in) }; + let ip = std::net::Ipv4Addr::from(u32::from_be(raw.sin_addr.s_addr).to_be_bytes()); + Ok(SocketAddr::from((ip, u16::from_be(raw.sin_port)))) + } + libc::AF_INET6 => { + if addr.len() < std::mem::size_of::() { + return Err(m_error!(EC::NetErr, "short sockaddr_in6 length")); + } + let raw = + unsafe { &*(addr.raw() as *const rliburing::sockaddr_storage as *const libc::sockaddr_in6) }; + let ip = std::net::Ipv6Addr::from(raw.sin6_addr.s6_addr); + Ok(SocketAddr::from((ip, u16::from_be(raw.sin6_port)))) + } + family => Err(m_error!( + EC::NetErr, + format!("unsupported socket family {}", family) + )), + } +} + +#[cfg(target_os = "linux")] +fn zeroed_sockaddr_storage() -> rliburing::sockaddr_storage { + unsafe { std::mem::zeroed() } +} diff --git a/mudu_sys/src/sync.rs b/mudu_sys/src/sync.rs new file mode 100644 index 0000000..a5f14a8 --- /dev/null +++ b/mudu_sys/src/sync.rs @@ -0,0 +1,19 @@ +use crate::env::default_env; +use crate::fd::RawFd; +use mudu::common::result::RS; + +pub fn eventfd() -> RS { + default_env().sync().eventfd() +} + +pub fn notify_eventfd(fd: RawFd) -> RS<()> { + default_env().sync().notify_eventfd(fd) +} + +pub fn read_eventfd(fd: RawFd) -> RS { + default_env().sync().read_eventfd(fd) +} + +pub fn close_fd(fd: RawFd) -> RS<()> { + default_env().sync().close_fd(fd) +} diff --git a/mudu_sys/src/task.rs b/mudu_sys/src/task.rs new file mode 100644 index 0000000..e1d7806 --- /dev/null +++ b/mudu_sys/src/task.rs @@ -0,0 +1,42 @@ +use crate::env::default_env; +use mudu::common::result::RS; +use mudu::error::ec::EC; +use mudu::m_error; +use std::future::Future; +use std::thread; +use std::time::Duration; + +pub async fn sleep(dur: Duration) -> RS<()> { + default_env().task().sleep(dur).await +} + +pub fn sleep_blocking(dur: Duration) { + default_env().task().sleep_blocking(dur) +} + +pub fn spawn_thread(f: F) -> RS> +where + F: FnOnce() -> T + Send + 'static, + T: Send + 'static, +{ + Ok(thread::spawn(f)) +} + +pub fn spawn_thread_named(name: impl Into, f: F) -> RS> +where + F: FnOnce() -> T + Send + 'static, + T: Send + 'static, +{ + thread::Builder::new() + .name(name.into()) + .spawn(f) + .map_err(|e| m_error!(EC::ThreadErr, "spawn thread error", e)) +} + +pub fn spawn_tokio(fut: F) -> tokio::task::JoinHandle +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + tokio::spawn(fut) +} diff --git a/mudu_sys/src/uring.rs b/mudu_sys/src/uring.rs new file mode 100644 index 0000000..57bed22 --- /dev/null +++ b/mudu_sys/src/uring.rs @@ -0,0 +1,248 @@ +#[cfg(target_os = "linux")] +mod linux { + use std::ffi::CStr; + use std::marker::PhantomData; + use std::os::fd::RawFd; + use std::time::Duration; + + pub struct IoUring { + raw: rliburing::io_uring, + exited: bool, + } + + pub struct SubmissionQueueEntry<'a> { + raw: *mut rliburing::io_uring_sqe, + _marker: PhantomData<&'a mut rliburing::io_uring_sqe>, + } + + #[derive(Clone, Copy, Debug)] + pub struct Completion { + user_data: u64, + result: i32, + } + + #[derive(Clone, Copy)] + pub struct SockAddrBuf { + raw: rliburing::sockaddr_storage, + len: u32, + } + + impl IoUring { + pub fn new(entries: u32) -> Result { + let mut raw = unsafe { std::mem::zeroed() }; + let mut param = unsafe { std::mem::zeroed() }; + let rc = unsafe { rliburing::io_uring_queue_init_params(entries, &mut raw, &mut param) }; + if rc != 0 { + return Err(rc); + } + Ok(Self { raw, exited: false }) + } + + pub fn next_sqe(&mut self) -> Option> { + let sqe = unsafe { rliburing::io_uring_get_sqe(&mut self.raw) }; + (!sqe.is_null()).then_some(SubmissionQueueEntry { + raw: sqe, + _marker: PhantomData, + }) + } + + pub fn submit(&mut self) -> i32 { + unsafe { rliburing::io_uring_submit(&mut self.raw) } + } + + pub fn wait(&mut self) -> Result { + let mut cqe_ptr: *mut rliburing::io_uring_cqe = std::ptr::null_mut(); + let rc = unsafe { rliburing::io_uring_wait_cqe(&mut self.raw, &mut cqe_ptr) }; + if rc < 0 { + return Err(rc); + } + Ok(self.take_completion(cqe_ptr)) + } + + pub fn wait_timeout(&mut self, timeout: Duration) -> Result { + let mut cqe_ptr: *mut rliburing::io_uring_cqe = std::ptr::null_mut(); + let mut ts = rliburing::__kernel_timespec { + tv_sec: timeout.as_secs() as i64, + tv_nsec: timeout.subsec_nanos() as i64, + }; + let rc = + unsafe { rliburing::io_uring_wait_cqe_timeout(&mut self.raw, &mut cqe_ptr, &mut ts) }; + if rc < 0 { + return Err(rc); + } + Ok(self.take_completion(cqe_ptr)) + } + + pub fn peek(&mut self) -> Result, i32> { + let mut cqe_ptr: *mut rliburing::io_uring_cqe = std::ptr::null_mut(); + let rc = unsafe { rliburing::io_uring_peek_cqe(&mut self.raw, &mut cqe_ptr) }; + if rc == -libc::EAGAIN || cqe_ptr.is_null() { + return Ok(None); + } + if rc < 0 { + return Err(rc); + } + Ok(Some(self.take_completion(cqe_ptr))) + } + + pub fn exit(&mut self) { + if self.exited { + return; + } + unsafe { rliburing::io_uring_queue_exit(&mut self.raw) }; + self.exited = true; + } + + fn take_completion(&mut self, cqe_ptr: *mut rliburing::io_uring_cqe) -> Completion { + let completion = Completion { + user_data: unsafe { (*cqe_ptr).user_data }, + result: unsafe { (*cqe_ptr).res }, + }; + unsafe { rliburing::io_uring_cqe_seen(&mut self.raw, cqe_ptr) }; + completion + } + } + + impl Drop for IoUring { + fn drop(&mut self) { + self.exit(); + } + } + + impl Completion { + pub fn user_data(&self) -> u64 { + self.user_data + } + + pub fn result(&self) -> i32 { + self.result + } + } + + impl SubmissionQueueEntry<'_> { + pub fn set_user_data(&mut self, user_data: u64) { + unsafe { + (*self.raw).user_data = user_data; + } + } + + pub fn prep_openat(&mut self, dirfd: RawFd, path: &CStr, flags: i32, mode: u32) { + unsafe { + rliburing::io_uring_prep_openat(self.raw, dirfd, path.as_ptr(), flags, mode); + } + } + + pub fn prep_close(&mut self, fd: RawFd) { + unsafe { rliburing::io_uring_prep_close(self.raw, fd) }; + } + + pub fn prep_read_raw(&mut self, fd: RawFd, buf: *mut u8, len: usize, offset: u64) { + unsafe { + rliburing::io_uring_prep_read( + self.raw, + fd, + buf.cast(), + len as _, + offset as _, + ); + } + } + + pub fn prep_write_raw(&mut self, fd: RawFd, buf: *const u8, len: usize, offset: u64) { + unsafe { + rliburing::io_uring_prep_write( + self.raw, + fd, + buf.cast(), + len as _, + offset as _, + ); + } + } + + pub fn prep_fsync(&mut self, fd: RawFd) { + unsafe { rliburing::io_uring_prep_fsync(self.raw, fd, 0) }; + } + + pub fn prep_socket(&mut self, domain: i32, socket_type: i32, protocol: i32, flags: u32) { + unsafe { + rliburing::io_uring_prep_socket(self.raw, domain, socket_type, protocol, flags) + }; + } + + pub fn prep_connect(&mut self, fd: RawFd, addr: &SockAddrBuf) { + unsafe { + rliburing::io_uring_prep_connect(self.raw, fd, addr.sockaddr_ptr(), addr.socklen()) + }; + } + + pub fn prep_accept(&mut self, fd: RawFd, addr: &mut SockAddrBuf, flags: i32) { + unsafe { + rliburing::io_uring_prep_accept( + self.raw, + fd, + addr.sockaddr_mut_ptr(), + addr.socklen_mut_ptr(), + flags, + ) + }; + } + + pub fn prep_recv_raw(&mut self, fd: RawFd, buf: *mut u8, len: usize, flags: i32) { + unsafe { rliburing::io_uring_prep_recv(self.raw, fd, buf.cast(), len as _, flags) }; + } + + pub fn prep_send_raw(&mut self, fd: RawFd, buf: *const u8, len: usize, flags: i32) { + unsafe { rliburing::io_uring_prep_send(self.raw, fd, buf.cast(), len as _, flags) }; + } + + pub fn prep_shutdown(&mut self, fd: RawFd, how: i32) { + unsafe { rliburing::io_uring_prep_shutdown(self.raw, fd, how) }; + } + } + + impl SockAddrBuf { + pub fn new_empty() -> Self { + Self { + raw: unsafe { std::mem::zeroed() }, + len: std::mem::size_of::() as u32, + } + } + + pub fn len(&self) -> usize { + self.len as usize + } + + pub(crate) fn from_raw(raw: rliburing::sockaddr_storage, len: u32) -> Self { + Self { raw, len } + } + + pub(crate) fn raw(&self) -> &rliburing::sockaddr_storage { + &self.raw + } + + fn sockaddr_ptr(&self) -> *const rliburing::sockaddr { + (&self.raw as *const rliburing::sockaddr_storage).cast() + } + + fn sockaddr_mut_ptr(&mut self) -> *mut rliburing::sockaddr { + (&mut self.raw as *mut rliburing::sockaddr_storage).cast() + } + + fn socklen(&self) -> rliburing::socklen_t { + self.len + } + + fn socklen_mut_ptr(&mut self) -> *mut rliburing::socklen_t { + &mut self.len + } + } + + pub use Completion as Cqe; + pub use IoUring as Ring; + pub use SockAddrBuf as SocketAddrBuf; + pub use SubmissionQueueEntry as Sqe; +} + +#[cfg(target_os = "linux")] +pub use linux::{Cqe, IoUring, Ring, SockAddrBuf, SocketAddrBuf, Sqe, SubmissionQueueEntry}; diff --git a/mudu_transpiler/Cargo.toml b/mudu_transpiler/Cargo.toml index 1b8c75d..ab07d5e 100644 --- a/mudu_transpiler/Cargo.toml +++ b/mudu_transpiler/Cargo.toml @@ -13,7 +13,6 @@ mudu_contract = { workspace = true } mudu_utils = { workspace = true } askama = {workspace = true} tree-sitter-rust = { version = "0.24.0" } -sql_parser = {workspace = true} tree-sitter = {workspace = true} mudu_type = { workspace = true } diff --git a/mudu_type/src/dat_value.rs b/mudu_type/src/dat_value.rs index 705816e..ba8d84e 100644 --- a/mudu_type/src/dat_value.rs +++ b/mudu_type/src/dat_value.rs @@ -28,7 +28,7 @@ impl AsRef for DatValue { } /// Internal memory representation supporting various data types -/// Uses Box for heap allocation of complex types to avoid large enum variants +/// Uses Box for time_series allocation of complex types to avoid large enum variants enum ValueKind { F32(f32), F64(f64), diff --git a/mudu_wasm/Cargo.toml b/mudu_wasm/Cargo.toml index fb02a51..176f6b3 100644 --- a/mudu_wasm/Cargo.toml +++ b/mudu_wasm/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [features] default = ["transpile"] -macro = ["sys_interface/wasip1", "sys_interface/component-model"] +macro = ["sys_interface/component-model"] transpile = ["sys_interface/component-model", "sys_interface/async"] [lib] diff --git a/mudu_wasm/readme.md b/mudu_wasm/readme.md index 2cc7d09..c1b53d1 100644 --- a/mudu_wasm/readme.md +++ b/mudu_wasm/readme.md @@ -1,6 +1,6 @@ # Add wasm32 target - rustup target add wasm32-wasip1 + rustup target add wasm32-wasip2 ## Notice @@ -17,10 +17,9 @@ When compile with wasm32-unknown-unknown, wasm-time runtime would complain error # Build wasm32 target - cargo build --target wasm32-wasip1 + cargo build --target wasm32-wasip2 If no wasm32 target, it would complain: error[E0463]: can't find crate for `core` - diff --git a/mudud/src/main.rs b/mudud/src/main.rs index abcc291..bd8d64b 100644 --- a/mudud/src/main.rs +++ b/mudud/src/main.rs @@ -21,16 +21,15 @@ fn serve() -> RS<()> { let cfg = load_mududb_cfg(None)?; info!( server_mode = ?cfg.server_mode, - runtime_target = ?cfg.runtime_target(), + component_target = ?cfg.component_target(), listen_ip = %cfg.listen_ip, http_listen_port = cfg.http_listen_port, pg_listen_port = cfg.pg_listen_port, tcp_listen_port = cfg.tcp_listen_port, http_worker_threads = cfg.http_worker_threads, - enable_p2 = cfg.enable_p2, enable_async = cfg.enable_async, routing_mode = ?cfg.routing_mode, - data_path = %cfg.data_path, + data_path = %cfg.db_path, mpk_path = %cfg.mpk_path, "mudud starting" ); diff --git a/script/build/build.py b/script/build/build.py index 860080f..0c668c7 100644 --- a/script/build/build.py +++ b/script/build/build.py @@ -376,7 +376,7 @@ def __init__(self): self.logger = Logger() self.platform_manager = PlatformManager() self.args = parse_args() - self.targets = ["wasm32-wasip1", "wasm32-wasip2"] + self.targets = ["wasm32-wasip2"] # Set default values self.build_mode = "release" self.toggle_clean_cache = self.args.clean @@ -768,4 +768,4 @@ def main(): sys.exit(1) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/sys_interface/Cargo.toml b/sys_interface/Cargo.toml index 8fdd81d..380c730 100644 --- a/sys_interface/Cargo.toml +++ b/sys_interface/Cargo.toml @@ -8,7 +8,6 @@ crate-type = ["rlib", "cdylib"] [features] default = ["component-model"] -wasip1 = [] wasip2 = ["component-model"] component-model = [] async = [] diff --git a/sys_interface/src/api_impl/async_.rs b/sys_interface/src/api_impl/async_.rs index 49b471a..c228b2f 100644 --- a/sys_interface/src/api_impl/async_.rs +++ b/sys_interface/src/api_impl/async_.rs @@ -4,6 +4,7 @@ )))] use mudu::common::id::OID; use mudu::common::result::RS; +use mudu_binding::system::{command_invoke, query_invoke}; #[cfg(not(any( all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all(target_arch = "wasm32", feature = "component-model", feature = "async") @@ -19,6 +20,8 @@ use mudu_contract::database::entity::Entity; all(target_arch = "wasm32", feature = "component-model", feature = "async") )))] use mudu_contract::database::entity_set::RecordSet; +use mudu_contract::database::result_batch::ResultBatch; +use mudu_contract::database::sql::Context; #[cfg(not(any( all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all(target_arch = "wasm32", feature = "component-model", feature = "async") @@ -29,9 +32,6 @@ use mudu_contract::database::sql_params::SQLParams; all(target_arch = "wasm32", feature = "component-model", feature = "async") )))] use mudu_contract::database::sql_stmt::SQLStmt; -use mudu_binding::system::{command_invoke, query_invoke}; -use mudu_contract::database::result_batch::ResultBatch; -use mudu_contract::database::sql::Context; use crate::host; @@ -159,7 +159,8 @@ pub async fn mudu_fetch_bytes(cursor: &[u8]) -> RS> { format!("no such session/context {}", oid) ) })?; - let response = super::drain_context_rows(&context).map(|rows| ResultBatch::from(oid, rows, true)); + let response = + super::drain_context_rows(&context).map(|rows| ResultBatch::from(oid, rows, true)); super::serialize_fetch_result(response) } diff --git a/sys_interface/src/api_impl/mod.rs b/sys_interface/src/api_impl/mod.rs index 77b6e7e..cc5112a 100644 --- a/sys_interface/src/api_impl/mod.rs +++ b/sys_interface/src/api_impl/mod.rs @@ -8,10 +8,8 @@ pub mod sync; pub mod sync_standalone; #[cfg(all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ))] pub mod sync_wasm; diff --git a/sys_interface/src/api_impl/sync.rs b/sys_interface/src/api_impl/sync.rs index 5710b33..8392c1d 100644 --- a/sys_interface/src/api_impl/sync.rs +++ b/sys_interface/src/api_impl/sync.rs @@ -2,22 +2,19 @@ all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] use mudu::common::id::OID; use mudu::common::result::RS; +use mudu_binding::system::{command_invoke, query_invoke}; #[cfg(not(any( all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] use mudu_binding::universal::uni_session_open_argv::UniSessionOpenArgv; @@ -25,10 +22,8 @@ use mudu_binding::universal::uni_session_open_argv::UniSessionOpenArgv; all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] use mudu_contract::database::entity::Entity; @@ -36,21 +31,19 @@ use mudu_contract::database::entity::Entity; all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] use mudu_contract::database::entity_set::RecordSet; +use mudu_contract::database::result_batch::ResultBatch; +use mudu_contract::database::sql::Context; #[cfg(not(any( all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] use mudu_contract::database::sql_params::SQLParams; @@ -58,16 +51,11 @@ use mudu_contract::database::sql_params::SQLParams; all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] use mudu_contract::database::sql_stmt::SQLStmt; -use mudu_binding::system::{command_invoke, query_invoke}; -use mudu_contract::database::result_batch::ResultBatch; -use mudu_contract::database::sql::Context; use crate::host; @@ -81,10 +69,8 @@ pub use super::sync_standalone::*; #[cfg(all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ))] pub use super::sync_wasm::*; @@ -92,10 +78,8 @@ pub use super::sync_wasm::*; all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] pub fn mudu_query( @@ -110,10 +94,8 @@ pub fn mudu_query( all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] pub fn mudu_command(_oid: OID, _sql: &dyn SQLStmt, _params: &dyn SQLParams) -> RS { @@ -124,10 +106,8 @@ pub fn mudu_command(_oid: OID, _sql: &dyn SQLStmt, _params: &dyn SQLParams) -> R all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] pub fn mudu_batch(_oid: OID, _sql: &dyn SQLStmt, _params: &dyn SQLParams) -> RS { @@ -138,10 +118,8 @@ pub fn mudu_batch(_oid: OID, _sql: &dyn SQLStmt, _params: &dyn SQLParams) -> RS< all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] pub fn mudu_open() -> RS { @@ -152,10 +130,8 @@ pub fn mudu_open() -> RS { all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] pub fn mudu_open_argv(_argv: &UniSessionOpenArgv) -> RS { @@ -166,10 +142,8 @@ pub fn mudu_open_argv(_argv: &UniSessionOpenArgv) -> RS { all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] pub fn mudu_close(_session_id: OID) -> RS<()> { @@ -180,10 +154,8 @@ pub fn mudu_close(_session_id: OID) -> RS<()> { all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] pub fn mudu_get(_session_id: OID, _key: &[u8]) -> RS>> { @@ -194,10 +166,8 @@ pub fn mudu_get(_session_id: OID, _key: &[u8]) -> RS>> { all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] pub fn mudu_put(_session_id: OID, _key: &[u8], _value: &[u8]) -> RS<()> { @@ -208,10 +178,8 @@ pub fn mudu_put(_session_id: OID, _key: &[u8], _value: &[u8]) -> RS<()> { all(not(target_arch = "wasm32"), feature = "standalone-adapter"), all( target_arch = "wasm32", - any( - all(feature = "wasip1", not(feature = "component-model")), - all(feature = "component-model", not(feature = "async")) - ) + feature = "component-model", + not(feature = "async") ) )))] pub fn mudu_range( @@ -230,12 +198,14 @@ pub fn mudu_query_bytes(query_in: &[u8]) -> RS> { format!("no such session/context {}", oid) ) })?; - let response = context.query_raw(stmt.as_ref(), params.as_ref()).and_then(|result| { - let desc = result.1.as_ref().clone(); - let _ = context.cache_result(result)?; - let rows = super::drain_context_rows(&context)?; - Ok((ResultBatch::from(oid, rows, true), desc)) - }); + let response = context + .query_raw(stmt.as_ref(), params.as_ref()) + .and_then(|result| { + let desc = result.1.as_ref().clone(); + let _ = context.cache_result(result)?; + let rows = super::drain_context_rows(&context)?; + Ok((ResultBatch::from(oid, rows, true), desc)) + }); Ok(query_invoke::serialize_query_result(response)) } @@ -247,7 +217,8 @@ pub fn mudu_fetch_bytes(cursor: &[u8]) -> RS> { format!("no such session/context {}", oid) ) })?; - let response = super::drain_context_rows(&context).map(|rows| ResultBatch::from(oid, rows, true)); + let response = + super::drain_context_rows(&context).map(|rows| ResultBatch::from(oid, rows, true)); super::serialize_fetch_result(response) } diff --git a/sys_interface/src/api_impl/sync_wasm.rs b/sys_interface/src/api_impl/sync_wasm.rs index 808b69a..80bb990 100644 --- a/sys_interface/src/api_impl/sync_wasm.rs +++ b/sys_interface/src/api_impl/sync_wasm.rs @@ -6,15 +6,6 @@ use mudu_contract::database::entity_set::RecordSet; use mudu_contract::database::sql_params::SQLParams; use mudu_contract::database::sql_stmt::SQLStmt; -#[cfg(all(feature = "wasip1", not(feature = "component-model")))] -pub fn mudu_query( - oid: OID, - sql: &dyn SQLStmt, - params: &dyn SQLParams, -) -> RS> { - crate::inner_p1::inner_query(oid, sql, params) -} - #[cfg(all(feature = "component-model", not(feature = "async")))] pub fn mudu_query( oid: OID, @@ -24,85 +15,41 @@ pub fn mudu_query( crate::inner_component::inner_query(oid, sql, params) } -#[cfg(all(feature = "wasip1", not(feature = "component-model")))] -pub fn mudu_command(oid: OID, sql: &dyn SQLStmt, params: &dyn SQLParams) -> RS { - crate::inner_p1::inner_command(oid, sql, params) -} - #[cfg(all(feature = "component-model", not(feature = "async")))] pub fn mudu_command(oid: OID, sql: &dyn SQLStmt, params: &dyn SQLParams) -> RS { crate::inner_component::inner_command(oid, sql, params) } -#[cfg(all(feature = "wasip1", not(feature = "component-model")))] -pub fn mudu_batch(oid: OID, sql: &dyn SQLStmt, params: &dyn SQLParams) -> RS { - crate::inner_p1::inner_batch(oid, sql, params) -} - #[cfg(all(feature = "component-model", not(feature = "async")))] pub fn mudu_batch(oid: OID, sql: &dyn SQLStmt, params: &dyn SQLParams) -> RS { crate::inner_component::inner_batch(oid, sql, params) } -#[cfg(all(feature = "wasip1", not(feature = "component-model")))] -pub fn mudu_open() -> RS { - crate::inner_p1::inner_open() -} - #[cfg(all(feature = "component-model", not(feature = "async")))] pub fn mudu_open() -> RS { crate::inner_component::inner_open() } -#[cfg(all(feature = "wasip1", not(feature = "component-model")))] -pub fn mudu_open_argv(argv: &UniSessionOpenArgv) -> RS { - crate::inner_p1::inner_open_argv(argv) -} - #[cfg(all(feature = "component-model", not(feature = "async")))] pub fn mudu_open_argv(argv: &UniSessionOpenArgv) -> RS { crate::inner_component::inner_open_argv(argv) } -#[cfg(all(feature = "wasip1", not(feature = "component-model")))] -pub fn mudu_close(session_id: OID) -> RS<()> { - crate::inner_p1::inner_close(session_id) -} - #[cfg(all(feature = "component-model", not(feature = "async")))] pub fn mudu_close(session_id: OID) -> RS<()> { crate::inner_component::inner_close(session_id) } -#[cfg(all(feature = "wasip1", not(feature = "component-model")))] -pub fn mudu_get(session_id: OID, key: &[u8]) -> RS>> { - crate::inner_p1::inner_get(session_id, key) -} - #[cfg(all(feature = "component-model", not(feature = "async")))] pub fn mudu_get(session_id: OID, key: &[u8]) -> RS>> { crate::inner_component::inner_get(session_id, key) } -#[cfg(all(feature = "wasip1", not(feature = "component-model")))] -pub fn mudu_put(session_id: OID, key: &[u8], value: &[u8]) -> RS<()> { - crate::inner_p1::inner_put(session_id, key, value) -} - #[cfg(all(feature = "component-model", not(feature = "async")))] pub fn mudu_put(session_id: OID, key: &[u8], value: &[u8]) -> RS<()> { crate::inner_component::inner_put(session_id, key, value) } -#[cfg(all(feature = "wasip1", not(feature = "component-model")))] -pub fn mudu_range( - session_id: OID, - start_key: &[u8], - end_key: &[u8], -) -> RS, Vec)>> { - crate::inner_p1::inner_range(session_id, start_key, end_key) -} - #[cfg(all(feature = "component-model", not(feature = "async")))] pub fn mudu_range( session_id: OID, diff --git a/sys_interface/src/extern_c.rs b/sys_interface/src/extern_c.rs deleted file mode 100644 index af63e15..0000000 --- a/sys_interface/src/extern_c.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Declares kernel system calls. -// These functions are unsafe because they interact with raw pointers and have -// contracts that must be upheld by the caller to avoid undefined behavior. - -#[link(wasm_import_module = "env")] -unsafe extern "C" { - // Executes a SQL command with serialized parameters. - // - // # Arguments - // * `param_buf_ptr` - Pointer to read-only buffer containing serialized input parameters - // * `param_buf_len` - Length of the input parameter buffer in bytes - // * `out_buf_ptr` - Pointer to mutable output buffer for serialized results - // * `out_buf_len` - Capacity of the output buffer in bytes - // * `out_len` - Output parameter for minimal required buffer size (at least 4 byte, mut pointer) - // * `mem_id` - Output parameter for memory identifier when buffer is insufficient (at least 4 bytes, mut pointer) - // - // # Returns - // System-specific status code (non-zero indicates error) - // - // # Safety - // All pointer parameters must be valid: - // - Input buffers must contain properly serialized data - // - Output pointers must point to allocated memory of correct size - // - The function may write to output buffers and output parameters - pub fn sys_command( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, - ) -> i32; - - pub fn sys_batch( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, - ) -> i32; - - // Similar to sys_command but semantically used for query operations. - // Shares the same signature and safety requirements as `sys_command`. - pub fn sys_query( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, - ) -> i32; - - // Similar to sys_command but semantically used for fetch operations. - // Shares the same signature and safety requirements as `sys_command`. - pub fn sys_fetch( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, - ) -> i32; - - // Reads a key from the kernel pull-push interface. - pub fn sys_get( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, - ) -> i32; - - pub fn sys_open( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, - ) -> i32; - - pub fn sys_close( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, - ) -> i32; - - // Writes a pull-push pair through the kernel pull-push interface. - pub fn sys_put( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, - ) -> i32; - - // Scans the half-open key range [start, end) through the kernel pull-push interface. - pub fn sys_range( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, - ) -> i32; - - // Retrieves memory content using a previously obtained memory identifier. - // - // # Arguments - // * `mem_id` - Memory identifier obtained from failed sys_command/sys_query/sys_fetch call - // * `out_buf_ptr` - Pointer to buffer that will receive the memory contents - // * `out_buf_len` - Size of the output buffer in bytes - // - // # Safety - // - `mem_id` must be a valid identifier obtained from a prior system call - // - `out_buf_ptr` must point to valid memory of at least `out_buf_len` bytes obtained from - // sys_command/sys_query/sys_fetch. - // - The kernel may write up to `out_buf_len` bytes to the output buffer - pub fn sys_get_memory(mem_id: u32, out_buf_ptr: *mut u8, out_buf_len: usize) -> i32; -} diff --git a/sys_interface/src/inner_component_async.rs b/sys_interface/src/inner_component_async.rs index 229750d..bb6dd4b 100644 --- a/sys_interface/src/inner_component_async.rs +++ b/sys_interface/src/inner_component_async.rs @@ -1,7 +1,7 @@ use crate::host::{ - async_invoke_host_batch, async_invoke_host_close, async_invoke_host_command, async_invoke_host_open, - async_invoke_host_query, async_invoke_host_session_get, async_invoke_host_session_put, - async_invoke_host_session_range, + async_invoke_host_batch, async_invoke_host_close, async_invoke_host_command, + async_invoke_host_open, async_invoke_host_query, async_invoke_host_session_get, + async_invoke_host_session_put, async_invoke_host_session_range, }; use crate::inner_component_async::mududb::async_api::system; use mudu::common::id::OID; @@ -40,7 +40,10 @@ pub async fn inner_command(oid: OID, sql: &dyn SQLStmt, params: &dyn SQLParams) #[allow(unused)] pub async fn inner_batch(oid: OID, sql: &dyn SQLStmt, params: &dyn SQLParams) -> RS { - async_invoke_host_batch(oid, sql, params, async |param| Ok(system::batch(param).await)).await + async_invoke_host_batch(oid, sql, params, async |param| { + Ok(system::batch(param).await) + }) + .await } #[allow(unused)] diff --git a/sys_interface/src/inner_p1.rs b/sys_interface/src/inner_p1.rs deleted file mode 100644 index 6e5e73d..0000000 --- a/sys_interface/src/inner_p1.rs +++ /dev/null @@ -1,356 +0,0 @@ -use crate::extern_c; -use crate::host::{ - invoke_host_batch, invoke_host_close, invoke_host_command, invoke_host_open, invoke_host_query, - invoke_host_session_get, invoke_host_session_put, invoke_host_session_range, -}; -use mudu::common::endian::read_u32; -use mudu::common::id::OID; -use mudu::common::result::RS; -use mudu::error::ec::EC; -use mudu::m_error; -use mudu_binding::universal::uni_session_open_argv::UniSessionOpenArgv; -use mudu_contract::database::entity::Entity; -use mudu_contract::database::entity_set::RecordSet; -use mudu_contract::database::sql_params::SQLParams; -use mudu_contract::database::sql_stmt::SQLStmt; - -#[allow(unused)] -pub fn inner_query( - oid: OID, - sql: &dyn SQLStmt, - params: &dyn SQLParams, -) -> RS> { - invoke_host_query(oid, sql, params, __sys_query) -} - -#[allow(unused)] -pub fn inner_command(oid: OID, sql: &dyn SQLStmt, params: &dyn SQLParams) -> RS { - invoke_host_command(oid, sql, params, __sys_command) -} - -#[allow(unused)] -pub fn inner_batch(oid: OID, sql: &dyn SQLStmt, params: &dyn SQLParams) -> RS { - invoke_host_batch(oid, sql, params, __sys_batch) -} - -#[allow(unused)] -pub fn inner_open() -> RS { - invoke_host_open(__sys_open) -} - -#[allow(unused)] -pub fn inner_open_argv(argv: &UniSessionOpenArgv) -> RS { - crate::host::invoke_host_open_argv(argv, __sys_open) -} - -#[allow(unused)] -pub fn inner_close(session_id: OID) -> RS<()> { - invoke_host_close(session_id, __sys_close) -} - -#[allow(unused)] -pub fn inner_get(session_id: OID, key: &[u8]) -> RS>> { - invoke_host_session_get(session_id, key, __sys_get) -} - -#[allow(unused)] -pub fn inner_put(session_id: OID, key: &[u8], value: &[u8]) -> RS<()> { - invoke_host_session_put(session_id, key, value, __sys_put) -} - -#[allow(unused)] -pub fn inner_range( - session_id: OID, - start_key: &[u8], - end_key: &[u8], -) -> RS, Vec)>> { - invoke_host_session_range(session_id, start_key, end_key, __sys_range) -} - -struct OutMemory { - pub len: u32, - pub vec: Vec, -} - -impl Default for OutMemory { - fn default() -> OutMemory { - Self { - vec: vec![0; 512], - len: 0, - } - } -} - -impl OutMemory { - fn into(self) -> Vec { - self.vec - } - fn slice(&self) -> &[u8] { - &self.vec - } - - fn slice_mut(&mut self) -> &mut [u8] { - &mut self.vec - } -} - -fn __sys_call, F: Fn(*const u8, usize, *mut u8, usize, *mut u8, *mut u8) -> i32>( - p: P, - sys_fn: F, - fn_name: &'static str, -) -> RS> { - let param = p.as_ref(); - let mut out_mem = OutMemory::default(); - let ret_value = { - let mut out_mem_len = [0u8; size_of::()]; - let mut out_mem_id = [0u8; size_of::()]; - let n = sys_fn( - param.as_ptr(), - param.len(), - out_mem.slice_mut().as_mut_ptr(), - out_mem.slice().len(), - out_mem_len.as_mut_ptr(), - out_mem_id.as_mut_ptr(), - ); - let mem_id = read_u32(&out_mem_id); - out_mem.len = read_u32(&out_mem_len); - if mem_id != 0 { - // the provided memory is insufficient - out_mem.vec.resize(out_mem.len as usize, 0); - let size = unsafe { - extern_c::sys_get_memory( - mem_id, - out_mem.slice_mut().as_mut_ptr(), - out_mem.slice().len(), - ) - }; - if size != out_mem.len as i32 { - panic!("output memory does not match expected size") - } - } - n - }; - if ret_value != 0 { - return Err(m_error!( - EC::InternalErr, - format!("sys call {} error, return code:{}", fn_name, ret_value) - )); - } - Ok(out_mem.into()) -} - -fn __sys_query(query_in: &[u8]) -> RS> { - __sys_call(query_in, ___sys_query, "sys_query") -} - -fn __sys_command(command_in: &[u8]) -> RS> { - __sys_call(command_in, ___sys_command, "sys_command") -} - -fn __sys_batch(batch_in: &[u8]) -> RS> { - __sys_call(batch_in, ___sys_batch, "sys_batch") -} - -fn __sys_fetch(result_cursor: &[u8]) -> RS> { - __sys_call(result_cursor, ___sys_fetch, "sys_fetch") -} - -fn __sys_get(get_in: &[u8]) -> RS> { - __sys_call(get_in, ___sys_get, "sys_get") -} - -fn __sys_open(open_in: &[u8]) -> RS> { - __sys_call(open_in, ___sys_open, "sys_open") -} - -fn __sys_close(close_in: &[u8]) -> RS> { - __sys_call(close_in, ___sys_close, "sys_close") -} - -fn __sys_put(put_in: &[u8]) -> RS> { - __sys_call(put_in, ___sys_put, "sys_put") -} - -fn __sys_range(range_in: &[u8]) -> RS> { - __sys_call(range_in, ___sys_range, "sys_range") -} - -fn ___sys_query( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, -) -> i32 { - unsafe { - extern_c::sys_query( - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_len, - mem_id, - ) - } -} - -fn ___sys_command( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, -) -> i32 { - unsafe { - extern_c::sys_command( - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_len, - mem_id, - ) - } -} - -fn ___sys_batch( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, -) -> i32 { - unsafe { - extern_c::sys_batch( - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_len, - mem_id, - ) - } -} - -fn ___sys_fetch( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, -) -> i32 { - unsafe { - extern_c::sys_fetch( - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_len, - mem_id, - ) - } -} - -fn ___sys_get( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, -) -> i32 { - unsafe { - extern_c::sys_get( - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_len, - mem_id, - ) - } -} - -fn ___sys_open( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, -) -> i32 { - unsafe { - extern_c::sys_open( - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_len, - mem_id, - ) - } -} - -fn ___sys_close( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, -) -> i32 { - unsafe { - extern_c::sys_close( - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_len, - mem_id, - ) - } -} - -fn ___sys_put( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, -) -> i32 { - unsafe { - extern_c::sys_put( - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_len, - mem_id, - ) - } -} - -fn ___sys_range( - param_buf_ptr: *const u8, - param_buf_len: usize, - out_buf_ptr: *mut u8, - out_buf_len: usize, - out_len: *mut u8, - mem_id: *mut u8, -) -> i32 { - unsafe { - extern_c::sys_range( - param_buf_ptr, - param_buf_len, - out_buf_ptr, - out_buf_len, - out_len, - mem_id, - ) - } -} diff --git a/sys_interface/src/lib.rs b/sys_interface/src/lib.rs index 7fdbd57..55c2769 100644 --- a/sys_interface/src/lib.rs +++ b/sys_interface/src/lib.rs @@ -9,12 +9,6 @@ pub mod uniffi; #[cfg(feature = "uniffi-bindings")] ::uniffi::setup_scaffolding!(); -#[cfg(all( - target_arch = "wasm32", - feature = "wasip1", - not(feature = "component-model") -))] -pub mod extern_c; #[cfg(all( target_arch = "wasm32", feature = "component-model", @@ -23,9 +17,3 @@ pub mod extern_c; mod inner_component; #[cfg(all(target_arch = "wasm32", feature = "component-model", feature = "async"))] mod inner_component_async; -#[cfg(all( - target_arch = "wasm32", - feature = "wasip1", - not(feature = "component-model") -))] -pub mod inner_p1;